Not logged in. · Lost password · Register
Forum: agsXMPP RSS
Avatar
dmex #1
Member since Jul 2010 · 3 posts
Group memberships: Members
Show profile · Link to this post
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.

    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;
            }
        }
    }
Avatar
Alex #2
Member since Feb 2003 · 4449 posts · Location: Germany
Group memberships: Administrators, Members
Show profile · Link to this post
I tried your code and added some more logging in the Prosody code for SCRAM.
Your ComputeHMACHash functions must be wrong.

try:

  1. private byte[] ComputeHMACHash(string key, byte[] data)
  2. {
  3.    using (var hmacsha1 = new HMACSHA1(data, true))
  4.    {
  5.       byte[] hashBytes = hmacsha1.ComputeHash(Encoding.UTF8.GetBytes(key));
  6.       return hashBytes;
  7.     }
  8. }

Alex
Avatar
Alex #3
Member since Feb 2003 · 4449 posts · Location: Germany
Group memberships: Administrators, Members
Show profile · Link to this post
I also changed the client proof and was able to login with success.

  1. byte[] client_Proof = new byte[client_Key.Length];
  2. for (int i = 0; i < client_Key.Length; ++i)
  3. {
  4.    client_Proof[i] = (byte) (client_Key[i] ^ client_Signature[i]);
  5. }

Alex
Avatar
Alex #4
Member since Feb 2003 · 4449 posts · Location: Germany
Group memberships: Administrators, Members
Show profile · Link to this post
added SCRAM-SHA-1s support and committed it to SVN.
Please check it out and let me know if it works for you.

Alex
Avatar
dmex #5
Member since Jul 2010 · 3 posts
Group memberships: Members
Show profile · Link to this post
Yes it works, Thanks Alex :-)
Close Smaller – Larger + Reply to this post:
Verification code: VeriCode Please enter the word from the image into the text field below. (Type the letters only, lower case is okay.)
Smileys: :-) ;-) :-D :-p :blush: :cool: :rolleyes: :huh: :-/ <_< :-( :'( :#: :scared: 8-( :nuts: :-O
Special characters:
Forum: agsXMPP RSS