Subject: SCRAM-SHA1 authentication
Hello,
Ive tried implementing SCRAM-SHA1 authentication (http://tools.ietf.org/html/rfc5802) However im getting something wrong, The problem is forming the correct client-final-message to complete the authentication and some servers like jabber.org's will not return a status to identify the cause of failure.
I know this code is ugly, the plan is cleaning it up once it works
Any suggestions would be appreciated.
Ive tried implementing SCRAM-SHA1 authentication (http://tools.ietf.org/html/rfc5802) However im getting something wrong, The problem is forming the correct client-final-message to complete the authentication and some servers like jabber.org's will not return a status to identify the cause of failure.
I know this code is ugly, the plan is cleaning it up once it works

Any suggestions would be appreciated.
public class DigestSHA1Mechanism : Mechanism
{
public override void Init(XmppClientConnection con)
{
this.XmppClientConnection = con;
//TODO: randomize ClientNonce.
string client_first_message = "n,,n=" + base.Username + ",r=ClientNonce";
//send the auth message as a base64 encoded string.
this.XmppClientConnection.Send(new protocol.sasl.Auth(protocol.sasl.MechanismType.SCRAM_SHA_1, true, client_first_message));
}
public override void Parse(Node node)
{
if (node is protocol.sasl.Challenge)
{
protocol.sasl.Challenge c = node as protocol.sasl.Challenge;
string pwd = this.Password;
// split the string, TODO: regex split.
string[] str = c.TextBase64.Split(','); // r=ClientNonce/YMPXqjHXQfIPdPRjk/AMnX0vGD5ug6t,s=GG+w6IZT9ljQ8TQB2wv4Sg==,i=4096
string clientservernonce = str[0];
// the user's salt - (base64 encoded)
string salt = str[1].Substring(2);
// user's iteration count
string iteration = str[2].Substring(2);
// SaltedPassword := Hi(Normalize(password), salt, i)...salt the user pass (already normalized) using the salt and iteration from the first-server-message.
byte[] salted_password = this.Hi(pwd, Convert.FromBase64String(salt), Convert.ToInt32(iteration));
// ClientKey := HMAC(SaltedPassword, "Client Key"), get the client-key by signing the salted password with "Client Key".
byte[] client_Key = this.ComputeHMACHash("Client Key", salted_password);
// StoredKey := H(ClientKey), get the stored_key by hashing the client-key
byte[] stored_key = util.Hash.Sha1HashBytes(client_Key);
// our client-first message, TODO: randomize ClientNonce.
string client_first_bare = "n,,n=" + base.Username + ",r=ClientNonce";
// our client final message, c= our channel binding, r= clientservernonce.
string client_final_message_without_proof = "c=" + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes("n,,")) + "," + clientservernonce;
// AuthMessage := client-first-message-bare + "," + server-first-message + "," + client-final-message-without-proof
string auth_Message = client_first_bare + "," + c.TextBase64 + "," + client_final_message_without_proof;
// ClientSignature := HMAC(StoredKey, AuthMessage)
byte[] client_Signature = this.ComputeHMACHash(auth_Message, stored_key);
// ClientProof := ClientKey XOR ClientSignature
byte[] client_Proof = new byte[client_Key.Length];
for (int i = 0; i < client_Key.Length; ++i)
{
client_Proof[i] = (byte)(client_Key[i] ^= client_Signature[i]);
}
//var clientKeyArray = new System.Collections.BitArray(client_Key);
//var clientSigArray = new System.Collections.BitArray(client_Signature);
//clientKeyArray.Xor(clientSigArray).CopyTo(client_Proof, 0);
string client_final_message = client_final_message_without_proof + ",p=" + Convert.ToBase64String(client_Proof);
// ServerKey := HMAC(SaltedPassword, "Server Key")
// ServerSignature := HMAC(ServerKey, AuthMessage)
this.XmppClientConnection.Send(new agsXMPP.protocol.sasl.Response(client_final_message));
}
else
{
throw new ArithmeticException("SHA1 Node is not a Challenge Request!");
}
}
private byte[] Hi(string password, byte[] salt, int ini)
{
Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(password, salt, ini);
byte[] encrypted = pdb.GetBytes(20);
return encrypted;
}
private byte[] ComputeHMACHash(string key, byte[] data)
{
using (HMACSHA1 hmacsha1 = new HMACSHA1(System.Text.Encoding.UTF8.GetBytes(key), true))
{
byte[] hashBytes = hmacsha1.ComputeHash(data);
return hashBytes;
}
}
}
{
public override void Init(XmppClientConnection con)
{
this.XmppClientConnection = con;
//TODO: randomize ClientNonce.
string client_first_message = "n,,n=" + base.Username + ",r=ClientNonce";
//send the auth message as a base64 encoded string.
this.XmppClientConnection.Send(new protocol.sasl.Auth(protocol.sasl.MechanismType.SCRAM_SHA_1, true, client_first_message));
}
public override void Parse(Node node)
{
if (node is protocol.sasl.Challenge)
{
protocol.sasl.Challenge c = node as protocol.sasl.Challenge;
string pwd = this.Password;
// split the string, TODO: regex split.
string[] str = c.TextBase64.Split(','); // r=ClientNonce/YMPXqjHXQfIPdPRjk/AMnX0vGD5ug6t,s=GG+w6IZT9ljQ8TQB2wv4Sg==,i=4096
string clientservernonce = str[0];
// the user's salt - (base64 encoded)
string salt = str[1].Substring(2);
// user's iteration count
string iteration = str[2].Substring(2);
// SaltedPassword := Hi(Normalize(password), salt, i)...salt the user pass (already normalized) using the salt and iteration from the first-server-message.
byte[] salted_password = this.Hi(pwd, Convert.FromBase64String(salt), Convert.ToInt32(iteration));
// ClientKey := HMAC(SaltedPassword, "Client Key"), get the client-key by signing the salted password with "Client Key".
byte[] client_Key = this.ComputeHMACHash("Client Key", salted_password);
// StoredKey := H(ClientKey), get the stored_key by hashing the client-key
byte[] stored_key = util.Hash.Sha1HashBytes(client_Key);
// our client-first message, TODO: randomize ClientNonce.
string client_first_bare = "n,,n=" + base.Username + ",r=ClientNonce";
// our client final message, c= our channel binding, r= clientservernonce.
string client_final_message_without_proof = "c=" + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes("n,,")) + "," + clientservernonce;
// AuthMessage := client-first-message-bare + "," + server-first-message + "," + client-final-message-without-proof
string auth_Message = client_first_bare + "," + c.TextBase64 + "," + client_final_message_without_proof;
// ClientSignature := HMAC(StoredKey, AuthMessage)
byte[] client_Signature = this.ComputeHMACHash(auth_Message, stored_key);
// ClientProof := ClientKey XOR ClientSignature
byte[] client_Proof = new byte[client_Key.Length];
for (int i = 0; i < client_Key.Length; ++i)
{
client_Proof[i] = (byte)(client_Key[i] ^= client_Signature[i]);
}
//var clientKeyArray = new System.Collections.BitArray(client_Key);
//var clientSigArray = new System.Collections.BitArray(client_Signature);
//clientKeyArray.Xor(clientSigArray).CopyTo(client_Proof, 0);
string client_final_message = client_final_message_without_proof + ",p=" + Convert.ToBase64String(client_Proof);
// ServerKey := HMAC(SaltedPassword, "Server Key")
// ServerSignature := HMAC(ServerKey, AuthMessage)
this.XmppClientConnection.Send(new agsXMPP.protocol.sasl.Response(client_final_message));
}
else
{
throw new ArithmeticException("SHA1 Node is not a Challenge Request!");
}
}
private byte[] Hi(string password, byte[] salt, int ini)
{
Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(password, salt, ini);
byte[] encrypted = pdb.GetBytes(20);
return encrypted;
}
private byte[] ComputeHMACHash(string key, byte[] data)
{
using (HMACSHA1 hmacsha1 = new HMACSHA1(System.Text.Encoding.UTF8.GetBytes(key), true))
{
byte[] hashBytes = hmacsha1.ComputeHash(data);
return hashBytes;
}
}
}