using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; namespace S22.Sasl.Mechanisms { /// /// Implements the Sasl Cram-Md5 authentication method as described in /// RFC 2195. /// internal class SaslCramMd5 : SaslMechanism { bool Completed = false; /// /// Server sends the first message in the authentication exchange. /// public override bool HasInitial { get { return false; } } /// /// True if the authentication exchange between client and server /// has been completed. /// public override bool IsCompleted { get { return Completed; } } /// /// The IANA name for the Cram-Md5 authentication mechanism as described /// in RFC 2195. /// public override string Name { get { return "CRAM-MD5"; } } /// /// The username to authenticate with. /// string Username { get { return Properties.ContainsKey("Username") ? Properties["Username"] as string : null; } set { Properties["Username"] = value; } } /// /// The password to authenticate with. /// string Password { get { return Properties.ContainsKey("Password") ? Properties["Password"] as string : null; } set { Properties["Password"] = value; } } /// /// Private constructor for use with Sasl.SaslFactory. /// private SaslCramMd5() { // Nothing to do here. } /// /// Creates and initializes a new instance of the SaslCramMd5 class /// using the specified username and password. /// /// The username to authenticate with. /// The plaintext password to authenticate /// with. /// Thrown if the username /// or the password parameter is null. /// Thrown if the username /// parameter is empty. public SaslCramMd5(string username, string password) { username.ThrowIfNull("username"); if (username == String.Empty) throw new ArgumentException("The username must not be empty."); password.ThrowIfNull("password"); Username = username; Password = password; } /// /// Computes the client response to the specified Cram-Md5 challenge. /// /// The challenge sent by the server /// The response to the Cram-Md5 challenge. /// Thrown if the response could not /// be computed. protected override byte[] ComputeResponse(byte[] challenge) { // Precondition: Ensure username and password are not null and // username is not empty. if (String.IsNullOrEmpty(Username) || Password == null) { throw new SaslException("The username must not be null or empty and " + "the password must not be null."); } // Sasl Cram-Md5 does not involve another roundtrip. Completed = true; // Compute the encrypted challenge as a hex-string. string hex = String.Empty; using (var hmac = new HMACMD5(Encoding.ASCII.GetBytes(Password))) { byte[] encrypted = hmac.ComputeHash(challenge); hex = BitConverter.ToString(encrypted).ToLower().Replace("-", String.Empty); } return Encoding.ASCII.GetBytes(Username + " " + hex); } } }