From b35bc9814e8b4ba47c1189c7a059a669bbf6174c Mon Sep 17 00:00:00 2001 From: Muzzammil Shahid Date: Mon, 2 Dec 2024 15:14:45 +0500 Subject: [PATCH] Fix CRA authenticate to include salt --- lib/src/auth/wampcra.dart | 28 ++++++++++++++++++++++- pubspec.yaml | 1 + test/interoptests/auth_test.dart | 38 ++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/lib/src/auth/wampcra.dart b/lib/src/auth/wampcra.dart index fb58490..cfdf43c 100644 --- a/lib/src/auth/wampcra.dart +++ b/lib/src/auth/wampcra.dart @@ -3,6 +3,7 @@ import "dart:math"; import "dart:typed_data"; import "package:crypto/crypto.dart"; +import "package:pointycastle/pointycastle.dart"; import "package:wampproto/messages.dart"; import "package:wampproto/src/auth/auth.dart"; @@ -14,11 +15,36 @@ class WAMPCRAAuthenticator extends IClientAuthenticator { @override Authenticate authenticate(Challenge challenge) { - String signed = signWampCRAChallenge(challenge.extra["challenge"], utf8.encode(_secret)); + Uint8List? rawSecret; + String? saltStr = challenge.extra["salt"] as String?; + if (saltStr != null && saltStr.isNotEmpty) { + int iters = challenge.extra["iterations"] as int? ?? 0; + int keylen = challenge.extra["keylen"] as int? ?? 32; + + rawSecret = deriveCRAKey(saltStr, _secret, iters, keylen); + } else { + rawSecret = Uint8List.fromList(utf8.encode(_secret)); + } + + String signed = signWampCRAChallenge(challenge.extra["challenge"], rawSecret); return Authenticate(signed, {}); } } +Uint8List deriveCRAKey(String saltStr, String secret, int iterations, int keyLength) { + final salt = utf8.encode(saltStr); + final secretBytes = utf8.encode(secret); + + final iter = iterations > 0 ? iterations : 1000; + final keyLen = keyLength > 0 ? keyLength : 32; + + final params = Pbkdf2Parameters(Uint8List.fromList(salt), iter, keyLen); + final pbkdf2 = KeyDerivator("SHA-256/HMAC/PBKDF2")..init(params); + final derivedKey = pbkdf2.process(Uint8List.fromList(secretBytes)); + + return Uint8List.fromList(base64.encode(derivedKey).codeUnits); +} + String generateNonce() { final random = Random.secure(); final nonceBytes = Uint8List.fromList(List.generate(16, (_) => random.nextInt(256))); diff --git a/pubspec.yaml b/pubspec.yaml index 0c73c2e..3e8e3d3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,6 +13,7 @@ dependencies: crypto: ^3.0.3 msgpack_dart: ^1.0.1 pinenacl: ^0.6.0 + pointycastle: ^3.9.1 dev_dependencies: lints: ^3.0.0 diff --git a/test/interoptests/auth_test.dart b/test/interoptests/auth_test.dart index f0d4c76..dae8ae3 100644 --- a/test/interoptests/auth_test.dart +++ b/test/interoptests/auth_test.dart @@ -2,6 +2,7 @@ import "package:pinenacl/ed25519.dart"; import "package:test/test.dart"; import "package:wampproto/auth.dart"; +import "package:wampproto/messages.dart"; import "package:wampproto/src/auth/cryptosign.dart"; import "package:wampproto/src/auth/wampcra.dart"; @@ -59,6 +60,19 @@ void main() { const testSecret = "private"; var testSecretBytes = Uint8List.fromList(testSecret.codeUnits); + const String authID = "foo"; + const String salt = "salt"; + const int keyLength = 32; + const int iterations = 1000; + const String craChallenge = + '''{"nonce":"cdcb3b12d56e12825be99f38f55ba43f","authprovider":"provider","authid":"foo","authrole":"anonymous","authmethod":"wampcra","session":1,"timestamp":"2024-05-07T09:25:13.307Z"}'''; + final Map authExtra = { + "challenge": craChallenge, + "salt": salt, + "iterations": iterations, + "keylen": keyLength, + }; + test("GenerateCRAChallenge", () async { var challenge = generateWampCRAChallenge(1, "anonymous", "anonymous", "static"); @@ -89,5 +103,29 @@ void main() { var isVerified = verifyWampCRASignature(signature.trim(), challenge.trim(), testSecretBytes); expect(isVerified, true); }); + + test("SignCRAChallengeWithSalt", () async { + final challenge = Challenge(WAMPCRAAuthenticator.type, authExtra); + final authenticator = WAMPCRAAuthenticator(authID, authExtra, testSecret); + + final authenticate = authenticator.authenticate(challenge); + final saltSecret = await runCommand("auth cra derive-key $salt $testSecret -i $iterations -l $keyLength"); + + await runCommand("auth cra verify-signature $craChallenge ${authenticate.signature} ${saltSecret.trim()}"); + }); + + test("VerifyCRASignatureWithSalt", () async { + final challenge = await runCommand("auth cra generate-challenge 1 $authID anonymous provider"); + final saltSecret = await runCommand("auth cra derive-key $salt $testSecret -i $iterations -l $keyLength"); + + final signature = await runCommand("auth cra sign-challenge ${challenge.trim()} ${saltSecret.trim()}"); + + final isVerified = verifyWampCRASignature( + signature.trim(), + challenge.trim(), + Uint8List.fromList(saltSecret.trim().codeUnits), + ); + expect(isVerified, true); + }); }); }