Example #1
0
        /// <summary>
        /// Parses the server's challenge token and returns the next challenge response.
        /// </summary>
        /// <remarks>
        /// Parses the server's challenge token and returns the next challenge response.
        /// </remarks>
        /// <returns>The next challenge response.</returns>
        /// <param name="token">The server's challenge token.</param>
        /// <param name="startIndex">The index into the token specifying where the server's challenge begins.</param>
        /// <param name="length">The length of the server's challenge.</param>
        /// <exception cref="System.InvalidOperationException">
        /// The SASL mechanism is already authenticated.
        /// </exception>
        /// <exception cref="SaslException">
        /// An error has occurred while parsing the server's challenge token.
        /// </exception>
        protected override byte[] Challenge(byte[] token, int startIndex, int length)
        {
            if (IsAuthenticated)
            {
                throw new InvalidOperationException();
            }

            var cred = Credentials.GetCredential(Uri, MechanismName);

            byte[] response, signature;

            switch (state)
            {
            case LoginState.Initial:
                if (string.IsNullOrEmpty(cnonce))
                {
                    var entropy = new byte[18];

                    using (var rng = RandomNumberGenerator.Create())
                        rng.GetBytes(entropy);

                    cnonce = Convert.ToBase64String(entropy);
                }

                client   = "n=" + Normalize(cred.UserName) + ",r=" + cnonce;
                response = Encoding.UTF8.GetBytes("n,," + client);
                state    = LoginState.Final;
                break;

            case LoginState.Final:
                server = Encoding.UTF8.GetString(token, startIndex, length);
                var    tokens = ParseServerChallenge(server);
                string salt, nonce, iterations;
                int    count;

                if (!tokens.TryGetValue('s', out salt))
                {
                    throw new SaslException(MechanismName, SaslErrorCode.IncompleteChallenge, "Challenge did not contain a salt.");
                }

                if (!tokens.TryGetValue('r', out nonce))
                {
                    throw new SaslException(MechanismName, SaslErrorCode.IncompleteChallenge, "Challenge did not contain a nonce.");
                }

                if (!tokens.TryGetValue('i', out iterations))
                {
                    throw new SaslException(MechanismName, SaslErrorCode.IncompleteChallenge, "Challenge did not contain an iteration count.");
                }

                if (!nonce.StartsWith(cnonce, StringComparison.Ordinal))
                {
                    throw new SaslException(MechanismName, SaslErrorCode.InvalidChallenge, "Challenge contained an invalid nonce.");
                }

                if (!int.TryParse(iterations, out count) || count < 1)
                {
                    throw new SaslException(MechanismName, SaslErrorCode.InvalidChallenge, "Challenge contained an invalid iteration count.");
                }

                var password = Encoding.UTF8.GetBytes(SaslPrep(cred.Password));
                salted = Hi(password, Convert.FromBase64String(salt), count);

                var withoutProof = "c=" + Convert.ToBase64String(Encoding.ASCII.GetBytes("n,,")) + ",r=" + nonce;
                auth = Encoding.UTF8.GetBytes(client + "," + server + "," + withoutProof);

                var key = HMAC(salted, Encoding.ASCII.GetBytes("Client Key"));
                signature = HMAC(Hash(key), auth);
                Xor(key, signature);

                response = Encoding.UTF8.GetBytes(withoutProof + ",p=" + Convert.ToBase64String(key));
                state    = LoginState.Validate;
                break;

            case LoginState.Validate:
                var challenge = Encoding.UTF8.GetString(token, startIndex, length);

                if (!challenge.StartsWith("v=", StringComparison.Ordinal))
                {
                    throw new SaslException(MechanismName, SaslErrorCode.InvalidChallenge, "Challenge did not start with a signature.");
                }

                signature = Convert.FromBase64String(challenge.Substring(2));
                var serverKey  = HMAC(salted, Encoding.ASCII.GetBytes("Server Key"));
                var calculated = HMAC(serverKey, auth);

                if (signature.Length != calculated.Length)
                {
                    throw new SaslException(MechanismName, SaslErrorCode.IncorrectHash, "Challenge contained a signature with an invalid length.");
                }

                for (int i = 0; i < signature.Length; i++)
                {
                    if (signature[i] != calculated[i])
                    {
                        throw new SaslException(MechanismName, SaslErrorCode.IncorrectHash, "Challenge contained an invalid signatire.");
                    }
                }

                IsAuthenticated = true;
                response        = new byte[0];
                break;

            default:
                throw new IndexOutOfRangeException("state");
            }

            return(response);
        }
        /// <summary>
        /// Parses the server's challenge token and returns the next challenge response.
        /// </summary>
        /// <remarks>
        /// Parses the server's challenge token and returns the next challenge response.
        /// </remarks>
        /// <returns>The next challenge response.</returns>
        /// <param name="token">The server's challenge token.</param>
        /// <param name="startIndex">The index into the token specifying where the server's challenge begins.</param>
        /// <param name="length">The length of the server's challenge.</param>
        /// <exception cref="System.InvalidOperationException">
        /// The SASL mechanism is already authenticated.
        /// </exception>
        /// <exception cref="System.NotSupportedException">
        /// THe SASL mechanism does not support SASL-IR.
        /// </exception>
        /// <exception cref="SaslException">
        /// An error has occurred while parsing the server's challenge token.
        /// </exception>
        protected override byte[] Challenge(byte[] token, int startIndex, int length)
        {
            if (IsAuthenticated)
            {
                throw new InvalidOperationException();
            }

            if (token == null)
            {
                throw new NotSupportedException("DIGEST-MD5 does not support SASL-IR.");
            }

            var cred = Credentials.GetCredential(Uri, MechanismName);

            switch (state)
            {
            case LoginState.Auth:
                if (token.Length > 2048)
                {
                    throw new SaslException(MechanismName, SaslErrorCode.ChallengeTooLong, "Server challenge too long.");
                }

                challenge = DigestChallenge.Parse(Encoding.UTF8.GetString(token, startIndex, length));

                if (string.IsNullOrEmpty(cnonce))
                {
                    var entropy = new byte[15];

                    using (var rng = RandomNumberGenerator.Create())
                        rng.GetBytes(entropy);

                    cnonce = Convert.ToBase64String(entropy);
                }

                response = new DigestResponse(challenge, Uri.Scheme, Uri.DnsSafeHost, cred.UserName, cred.Password, cnonce);
                state    = LoginState.Final;
                return(response.Encode());

            case LoginState.Final:
                if (token.Length == 0)
                {
                    throw new SaslException(MechanismName, SaslErrorCode.MissingChallenge, "Server response did not contain any authentication data.");
                }

                var    text = Encoding.UTF8.GetString(token, startIndex, length);
                string key, value;
                int    index = 0;

                if (!DigestChallenge.TryParseKeyValuePair(text, ref index, out key, out value))
                {
                    throw new SaslException(MechanismName, SaslErrorCode.IncompleteChallenge, "Server response contained incomplete authentication data.");
                }

                var expected = response.ComputeHash(cred.Password, false);
                if (value != expected)
                {
                    throw new SaslException(MechanismName, SaslErrorCode.IncorrectHash, "Server response did not contain the expected hash.");
                }

                IsAuthenticated = true;
                return(new byte[0]);

            default:
                throw new IndexOutOfRangeException("state");
            }
        }