/// <summary> /// Parse the server's challenge token and return 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> /// <param name="cancellationToken">The cancellation token.</param> /// <exception cref="System.NotSupportedException"> /// The SASL mechanism does not support SASL-IR. /// </exception> /// <exception cref="System.OperationCanceledException"> /// The operation was canceled via the cancellation token. /// </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, CancellationToken cancellationToken) { if (token == null) { throw new NotSupportedException("DIGEST-MD5 does not support SASL-IR."); } if (IsAuthenticated) { return(null); } 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)); encoding = challenge.Charset != null ? Encoding.UTF8 : TextEncodings.Latin1; cnonce = cnonce ?? GenerateEntropy(15); response = new DigestResponse(challenge, encoding, Uri.Scheme, Uri.DnsSafeHost, AuthorizationId, Credentials.UserName, Credentials.Password, cnonce); state = LoginState.Final; return(response.Encode(encoding)); case LoginState.Final: if (token.Length == 0) { throw new SaslException(MechanismName, SaslErrorCode.MissingChallenge, "Server response did not contain any authentication data."); } var text = encoding.GetString(token, startIndex, length); string key, value; if (!DigestChallenge.TryParseKeyValuePair(text, out key, out value)) { throw new SaslException(MechanismName, SaslErrorCode.IncompleteChallenge, "Server response contained incomplete authentication data."); } if (!key.Equals("rspauth", StringComparison.OrdinalIgnoreCase)) { throw new SaslException(MechanismName, SaslErrorCode.InvalidChallenge, "Server response contained invalid data."); } var expected = response.ComputeHash(encoding, Credentials.Password, false); if (value != expected) { throw new SaslException(MechanismName, SaslErrorCode.IncorrectHash, "Server response did not contain the expected hash."); } IsAuthenticated = true; break; } return(null); }
/// <summary> /// Parses the server's challenge token and returns the next challenge response. /// </summary> /// <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="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) { return(null); } 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)); response = new DigestResponse(challenge, Uri.Scheme, Uri.DnsSafeHost, cred.UserName, cred.Password); 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); 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 ArgumentOutOfRangeException(); } }
/// <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."); } 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, AuthorizationId, Credentials.UserName, Credentials.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(Credentials.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"); } }