use of org.jivesoftware.smack.SmackException.SmackSaslException in project Smack by igniterealtime.
the class ScramMechanism method evaluateChallenge.
@Override
protected byte[] evaluateChallenge(byte[] challenge) throws SmackSaslException {
// TODO: Where is it specified that this is an UTF-8 encoded string?
String challengeString = new String(challenge, StandardCharsets.UTF_8);
switch(state) {
case AUTH_TEXT_SENT:
final String serverFirstMessage = challengeString;
Map<Character, String> attributes = parseAttributes(challengeString);
// Handle server random ASCII (nonce)
String rvalue = attributes.get('r');
if (rvalue == null) {
throw new SmackSaslException("Server random ASCII is null");
}
if (rvalue.length() <= clientRandomAscii.length()) {
throw new SmackSaslException("Server random ASCII is shorter then client random ASCII");
}
String receivedClientRandomAscii = rvalue.substring(0, clientRandomAscii.length());
if (!receivedClientRandomAscii.equals(clientRandomAscii)) {
throw new SmackSaslException("Received client random ASCII does not match client random ASCII");
}
// Handle iterations
int iterations;
String iterationsString = attributes.get('i');
if (iterationsString == null) {
throw new SmackSaslException("Iterations attribute not set");
}
try {
iterations = Integer.parseInt(iterationsString);
} catch (NumberFormatException e) {
throw new SmackSaslException("Exception parsing iterations", e);
}
// Handle salt
String salt = attributes.get('s');
if (salt == null) {
throw new SmackSaslException("SALT not send");
}
// Parsing and error checking is done, we can now begin to calculate the values
// First the client-final-message-without-proof
String channelBinding = "c=" + Base64.encodeToString(getCBindInput());
String clientFinalMessageWithoutProof = channelBinding + ",r=" + rvalue;
// AuthMessage := client-first-message-bare + "," + server-first-message + "," +
// client-final-message-without-proof
byte[] authMessage = toBytes(clientFirstMessageBare + ',' + serverFirstMessage + ',' + clientFinalMessageWithoutProof);
// RFC 5802 § 5.1 "Note that a client implementation MAY cache ClientKey&ServerKey … for later reauthentication …
// as it is likely that the server is going to advertise the same salt value upon reauthentication."
// Note that we also mangle the mechanism's name into the cache key, since the cache is used by multiple
// mechanisms.
final String cacheKey = password + ',' + salt + ',' + getName();
byte[] serverKey, clientKey;
Keys keys = CACHE.lookup(cacheKey);
if (keys == null) {
// SaltedPassword := Hi(Normalize(password), salt, i)
byte[] saltedPassword = hi(saslPrep(password), Base64.decode(salt), iterations);
// ServerKey := HMAC(SaltedPassword, "Server Key")
serverKey = hmac(saltedPassword, SERVER_KEY_BYTES);
// ClientKey := HMAC(SaltedPassword, "Client Key")
clientKey = hmac(saltedPassword, CLIENT_KEY_BYTES);
keys = new Keys(clientKey, serverKey);
CACHE.put(cacheKey, keys);
} else {
serverKey = keys.serverKey;
clientKey = keys.clientKey;
}
// ServerSignature := HMAC(ServerKey, AuthMessage)
serverSignature = hmac(serverKey, authMessage);
// StoredKey := H(ClientKey)
byte[] storedKey = SHA1.bytes(clientKey);
// ClientSignature := HMAC(StoredKey, AuthMessage)
byte[] clientSignature = hmac(storedKey, authMessage);
// ClientProof := ClientKey XOR ClientSignature
byte[] clientProof = new byte[clientKey.length];
for (int i = 0; i < clientProof.length; i++) {
clientProof[i] = (byte) (clientKey[i] ^ clientSignature[i]);
}
String clientFinalMessage = clientFinalMessageWithoutProof + ",p=" + Base64.encodeToString(clientProof);
state = State.RESPONSE_SENT;
return toBytes(clientFinalMessage);
case RESPONSE_SENT:
String clientCalculatedServerFinalMessage = "v=" + Base64.encodeToString(serverSignature);
if (!clientCalculatedServerFinalMessage.equals(challengeString)) {
throw new SmackSaslException("Server final message does not match calculated one");
}
state = State.VALID_SERVER_RESPONSE;
break;
default:
throw new SmackSaslException("Invalid state");
}
return null;
}
use of org.jivesoftware.smack.SmackException.SmackSaslException in project Smack by igniterealtime.
the class SASLDigestMD5Mechanism method evaluateChallenge.
@Override
protected byte[] evaluateChallenge(byte[] challenge) throws SmackSaslException {
if (challenge.length == 0) {
throw new SmackSaslException("Initial challenge has zero length");
}
String challengeString = new String(challenge, StandardCharsets.UTF_8);
String[] challengeParts = challengeString.split(",");
byte[] response = null;
switch(state) {
case INITIAL:
for (String part : challengeParts) {
String[] keyValue = part.split("=", 2);
String key = keyValue[0];
// RFC 2831 § 7.1 about the formatting of the digest-challenge:
// "The full form is "<n>#<m>element" indicating at least <n> and
// at most <m> elements, each separated by one or more commas
// (",") and OPTIONAL linear white space (LWS)."
// Which means the key value may be preceded by whitespace,
// which is what we remove: *Only the preceding whitespace*.
key = key.replaceFirst("^\\s+", "");
String value = keyValue[1];
if ("nonce".equals(key)) {
if (nonce != null) {
throw new SmackSaslException("Nonce value present multiple times");
}
nonce = value.replace("\"", "");
} else if ("qop".equals(key)) {
value = value.replace("\"", "");
if (!value.equals("auth")) {
throw new SmackSaslException("Unsupported qop operation: " + value);
}
}
}
if (nonce == null) {
// abort the authentication exchange."
throw new SmackSaslException("nonce value not present in initial challenge");
}
// RFC 2831 2.1.2.1 defines A1, A2, KD and response-value
byte[] a1FirstPart = MD5.bytes(authenticationId + ':' + serviceName + ':' + password);
cnonce = StringUtils.randomString(32);
byte[] a1 = ByteUtils.concat(a1FirstPart, toBytes(':' + nonce + ':' + cnonce));
digestUri = "xmpp/" + serviceName;
hex_hashed_a1 = StringUtils.encodeHex(MD5.bytes(a1));
String responseValue = calcResponse(DigestType.ClientResponse);
// @formatter:off
// See RFC 2831 2.1.2 digest-response
String authzid;
if (authorizationId == null) {
authzid = "";
} else {
authzid = ",authzid=\"" + authorizationId + '"';
}
String saslString = "username=\"" + quoteBackslash(authenticationId) + '"' + authzid + ",realm=\"" + serviceName + '"' + ",nonce=\"" + nonce + '"' + ",cnonce=\"" + cnonce + '"' + ",nc=" + INITAL_NONCE + ",qop=auth" + ",digest-uri=\"" + digestUri + '"' + ",response=" + responseValue + ",charset=utf-8";
// @formatter:on
response = toBytes(saslString);
state = State.RESPONSE_SENT;
break;
case RESPONSE_SENT:
if (verifyServerResponse) {
String serverResponse = null;
for (String part : challengeParts) {
String[] keyValue = part.split("=");
assert keyValue.length == 2;
String key = keyValue[0];
String value = keyValue[1];
if ("rspauth".equals(key)) {
serverResponse = value;
break;
}
}
if (serverResponse == null) {
throw new SmackSaslException("No server response received while performing " + NAME + " authentication");
}
String expectedServerResponse = calcResponse(DigestType.ServerResponse);
if (!serverResponse.equals(expectedServerResponse)) {
throw new SmackSaslException("Invalid server response while performing " + NAME + " authentication");
}
}
state = State.VALID_SERVER_RESPONSE;
break;
default:
throw new IllegalStateException();
}
return response;
}
Aggregations