aboutsummaryrefslogtreecommitdiff
path: root/xmpp-vala/src/module/sasl.vala
diff options
context:
space:
mode:
authorfiaxh <git@lightrise.org>2026-04-24 11:09:10 +0200
committerfiaxh <git@lightrise.org>2026-04-24 11:17:43 +0200
commit9abf8f6869bc35c76bdfa86416012d4e7f0dda78 (patch)
tree3c3f7628d7fdc5410113a8efc6b96a4372c6d80b /xmpp-vala/src/module/sasl.vala
parentc5ba4275ae68dd558108ce273dafa1bf890259e8 (diff)
Add support for Sasl2 + Bind2
Diffstat (limited to 'xmpp-vala/src/module/sasl.vala')
-rw-r--r--xmpp-vala/src/module/sasl.vala179
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;
+ }
}