diff options
| author | fiaxh <git@lightrise.org> | 2026-04-24 11:09:10 +0200 |
|---|---|---|
| committer | fiaxh <git@lightrise.org> | 2026-04-24 11:17:43 +0200 |
| commit | 9abf8f6869bc35c76bdfa86416012d4e7f0dda78 (patch) | |
| tree | 3c3f7628d7fdc5410113a8efc6b96a4372c6d80b /xmpp-vala/src/module/sasl.vala | |
| parent | c5ba4275ae68dd558108ce273dafa1bf890259e8 (diff) | |
Add support for Sasl2 + Bind2
Diffstat (limited to 'xmpp-vala/src/module/sasl.vala')
| -rw-r--r-- | xmpp-vala/src/module/sasl.vala | 179 |
1 files changed, 96 insertions, 83 deletions
diff --git a/xmpp-vala/src/module/sasl.vala b/xmpp-vala/src/module/sasl.vala index 3f3eca58..035cc723 100644 --- a/xmpp-vala/src/module/sasl.vala +++ b/xmpp-vala/src/module/sasl.vala @@ -44,52 +44,6 @@ namespace Xmpp.Sasl { stream.received_nonza.disconnect(this.received_nonza); } - private static size_t SHA1_SIZE = 20; - - private static uint8[] sha1(uint8[] data) { - Checksum checksum = new Checksum(ChecksumType.SHA1); - checksum.update(data, data.length); - uint8[] res = new uint8[SHA1_SIZE]; - checksum.get_digest(res, ref SHA1_SIZE); - return res; - } - - private static uint8[] hmac_sha1(uint8[] key, uint8[] data) { - Hmac hmac = new Hmac(ChecksumType.SHA1, key); - hmac.update(data); - uint8[] res = new uint8[SHA1_SIZE]; - hmac.get_digest(res, ref SHA1_SIZE); - return res; - } - - private static uint8[] pbkdf2_sha1(string password, uint8[] salt, uint iterations) { - uint8[] res = new uint8[SHA1_SIZE]; - uint8[] last = new uint8[salt.length + 4]; - for(int i = 0; i < salt.length; i++) { - last[i] = salt[i]; - } - last[salt.length + 3] = 1; - for(int i = 0; i < iterations; i++) { - last = hmac_sha1((uint8[]) password.to_utf8(), last); - xor_inplace(res, last); - } - return res; - } - - private static void xor_inplace(uint8[] mix, uint8[] a2) { - for(int i = 0; i < mix.length; i++) { - mix[i] = mix[i] ^ a2[i]; - } - } - - private static uint8[] xor(uint8[] a1, uint8[] a2) { - uint8[] mix = new uint8[a1.length]; - for(int i = 0; i < a1.length; i++) { - mix[i] = a1[i] ^ a2[i]; - } - return mix; - } - public void received_nonza(XmppStream stream, StanzaNode node) { if (node.ns_uri == NS_URI) { if (node.name == "success") { @@ -119,33 +73,10 @@ namespace Xmpp.Sasl { } else if (node.name == "challenge" && stream.has_flag(Flag.IDENTITY)) { Flag flag = stream.get_flag(Flag.IDENTITY); if (flag.mechanism == Mechanism.SCRAM_SHA_1) { - string challenge = (string) Base64.decode(node.get_string_content()); - string? server_nonce = null; - uint8[] salt = null; - uint iterations = 0; - foreach(string c in challenge.split(",")) { - string[] split = c.split("=", 2); - if (split.length != 2) continue; - switch(split[0]) { - case "r": server_nonce = split[1]; break; - case "s": salt = Base64.decode(split[1]); break; - case "i": iterations = int.parse(split[1]); break; - } - } - if (server_nonce == null || salt == null || iterations == 0) return; - if (!server_nonce.has_prefix(flag.client_nonce)) return; - string client_final_message_bare = @"c=biws,r=$server_nonce"; - uint8[] salted_password = pbkdf2_sha1(flag.password, salt, iterations); - uint8[] client_key = hmac_sha1(salted_password, (uint8[]) "Client Key".to_utf8()); - uint8[] stored_key = sha1(client_key); - string auth_message = @"n=$(flag.name),r=$(flag.client_nonce),$challenge,$client_final_message_bare"; - uint8[] client_signature = hmac_sha1(stored_key, (uint8[]) auth_message.to_utf8()); - uint8[] client_proof = xor(client_key, client_signature); - uint8[] server_key = hmac_sha1(salted_password, (uint8[]) "Server Key".to_utf8()); - flag.server_signature = hmac_sha1(server_key, (uint8[]) auth_message.to_utf8()); - string client_final_message = @"$client_final_message_bare,p=$(Base64.encode(client_proof))"; + var challenge_response = compute_challenge_response(node.get_string_content(), flag.password, flag.client_nonce, flag.name); + flag.server_signature = challenge_response.expected_server_signature; stream.write(new StanzaNode.build("response", NS_URI).add_self_xmlns() - .put_node(new StanzaNode.text(Base64.encode((uchar[]) (client_final_message).to_utf8())))); + .put_node(new StanzaNode.text(Base64.encode((uchar[]) (challenge_response.response).to_utf8())))); } } } @@ -206,17 +137,6 @@ namespace Xmpp.Sasl { } } - private static uchar[] get_plain_bytes(string name_s, string password_s) { - var name = name_s.to_utf8(); - var password = password_s.to_utf8(); - uchar[] res = new uchar[name.length + password.length + 2]; - res[0] = 0; - res[name.length + 1] = 0; - for(int i = 0; i < name.length; i++) { res[i + 1] = (uchar) name[i]; } - for(int i = 0; i < password.length; i++) { res[i + name.length + 2] = (uchar) password[i]; } - return res; - } - public override bool mandatory_outstanding(XmppStream stream) { return !stream.has_flag(Flag.IDENTITY) || !stream.get_flag(Flag.IDENTITY).finished; } @@ -228,4 +148,97 @@ namespace Xmpp.Sasl { public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } + + public static uchar[] get_plain_bytes(string name_s, string password_s) { + var name = name_s.to_utf8(); + var password = password_s.to_utf8(); + uchar[] res = new uchar[name.length + password.length + 2]; + res[0] = 0; + res[name.length + 1] = 0; + for(int i = 0; i < name.length; i++) { res[i + 1] = (uchar) name[i]; } + for(int i = 0; i < password.length; i++) { res[i + name.length + 2] = (uchar) password[i]; } + return res; + } + + public class ChallengeResponse { + public uint8[] expected_server_signature { get; set; } + public string response { get; set; } + } + + public static ChallengeResponse compute_challenge_response(string challenge, string password, string client_nonce, string name) { + string challenge_base64 = (string) Base64.decode(challenge); + string? server_nonce = null; + uint8[] salt = null; + uint iterations = 0; + foreach(string c in challenge_base64.split(",")) { + string[] split = c.split("=", 2); + if (split.length != 2) continue; + switch(split[0]) { + case "r": server_nonce = split[1]; break; + case "s": salt = Base64.decode(split[1]); break; + case "i": iterations = int.parse(split[1]); break; + } + } + if (server_nonce == null || salt == null || iterations == 0) throw new Error(-1, 0, "SASL error"); + if (!server_nonce.has_prefix(client_nonce)) throw new Error(-1, 0, "SASL error"); + string client_final_message_bare = @"c=biws,r=$server_nonce"; + uint8[] salted_password = pbkdf2_sha1(password, salt, iterations); + uint8[] client_key = hmac_sha1(salted_password, (uint8[]) "Client Key".to_utf8()); + uint8[] stored_key = sha1(client_key); + string auth_message = @"n=$(name),r=$(client_nonce),$challenge_base64,$client_final_message_bare"; + uint8[] client_signature = hmac_sha1(stored_key, (uint8[]) auth_message.to_utf8()); + uint8[] client_proof = xor(client_key, client_signature); + uint8[] server_key = hmac_sha1(salted_password, (uint8[]) "Server Key".to_utf8()); + + return new ChallengeResponse() { + expected_server_signature = hmac_sha1(server_key, (uint8[]) auth_message.to_utf8()), + response = @"$client_final_message_bare,p=$(Base64.encode(client_proof))" + }; + } + + private static size_t SHA1_SIZE = 20; + + private static uint8[] sha1(uint8[] data) { + Checksum checksum = new Checksum(ChecksumType.SHA1); + checksum.update(data, data.length); + uint8[] res = new uint8[SHA1_SIZE]; + checksum.get_digest(res, ref SHA1_SIZE); + return res; + } + + private static uint8[] hmac_sha1(uint8[] key, uint8[] data) { + Hmac hmac = new Hmac(ChecksumType.SHA1, key); + hmac.update(data); + uint8[] res = new uint8[SHA1_SIZE]; + hmac.get_digest(res, ref SHA1_SIZE); + return res; + } + + private static uint8[] pbkdf2_sha1(string password, uint8[] salt, uint iterations) { + uint8[] res = new uint8[SHA1_SIZE]; + uint8[] last = new uint8[salt.length + 4]; + for(int i = 0; i < salt.length; i++) { + last[i] = salt[i]; + } + last[salt.length + 3] = 1; + for(int i = 0; i < iterations; i++) { + last = hmac_sha1((uint8[]) password.to_utf8(), last); + xor_inplace(res, last); + } + return res; + } + + private static void xor_inplace(uint8[] mix, uint8[] a2) { + for(int i = 0; i < mix.length; i++) { + mix[i] = mix[i] ^ a2[i]; + } + } + + private static uint8[] xor(uint8[] a1, uint8[] a2) { + uint8[] mix = new uint8[a1.length]; + for(int i = 0; i < a1.length; i++) { + mix[i] = a1[i] ^ a2[i]; + } + return mix; + } } |
