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);
}
}
}