Search in sources :

Example 1 with SmackSaslException

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;
}
Also used : SmackSaslException(org.jivesoftware.smack.SmackException.SmackSaslException)

Example 2 with SmackSaslException

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;
}
Also used : SmackSaslException(org.jivesoftware.smack.SmackException.SmackSaslException)

Aggregations

SmackSaslException (org.jivesoftware.smack.SmackException.SmackSaslException)2