Skip to content

Commit b35bc98

Browse files
Fix CRA authenticate to include salt
1 parent 31823ea commit b35bc98

File tree

3 files changed

+66
-1
lines changed

3 files changed

+66
-1
lines changed

lib/src/auth/wampcra.dart

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import "dart:math";
33
import "dart:typed_data";
44

55
import "package:crypto/crypto.dart";
6+
import "package:pointycastle/pointycastle.dart";
67
import "package:wampproto/messages.dart";
78
import "package:wampproto/src/auth/auth.dart";
89

@@ -14,11 +15,36 @@ class WAMPCRAAuthenticator extends IClientAuthenticator {
1415

1516
@override
1617
Authenticate authenticate(Challenge challenge) {
17-
String signed = signWampCRAChallenge(challenge.extra["challenge"], utf8.encode(_secret));
18+
Uint8List? rawSecret;
19+
String? saltStr = challenge.extra["salt"] as String?;
20+
if (saltStr != null && saltStr.isNotEmpty) {
21+
int iters = challenge.extra["iterations"] as int? ?? 0;
22+
int keylen = challenge.extra["keylen"] as int? ?? 32;
23+
24+
rawSecret = deriveCRAKey(saltStr, _secret, iters, keylen);
25+
} else {
26+
rawSecret = Uint8List.fromList(utf8.encode(_secret));
27+
}
28+
29+
String signed = signWampCRAChallenge(challenge.extra["challenge"], rawSecret);
1830
return Authenticate(signed, {});
1931
}
2032
}
2133

34+
Uint8List deriveCRAKey(String saltStr, String secret, int iterations, int keyLength) {
35+
final salt = utf8.encode(saltStr);
36+
final secretBytes = utf8.encode(secret);
37+
38+
final iter = iterations > 0 ? iterations : 1000;
39+
final keyLen = keyLength > 0 ? keyLength : 32;
40+
41+
final params = Pbkdf2Parameters(Uint8List.fromList(salt), iter, keyLen);
42+
final pbkdf2 = KeyDerivator("SHA-256/HMAC/PBKDF2")..init(params);
43+
final derivedKey = pbkdf2.process(Uint8List.fromList(secretBytes));
44+
45+
return Uint8List.fromList(base64.encode(derivedKey).codeUnits);
46+
}
47+
2248
String generateNonce() {
2349
final random = Random.secure();
2450
final nonceBytes = Uint8List.fromList(List.generate(16, (_) => random.nextInt(256)));

pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ dependencies:
1313
crypto: ^3.0.3
1414
msgpack_dart: ^1.0.1
1515
pinenacl: ^0.6.0
16+
pointycastle: ^3.9.1
1617

1718
dev_dependencies:
1819
lints: ^3.0.0

test/interoptests/auth_test.dart

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import "package:pinenacl/ed25519.dart";
22
import "package:test/test.dart";
33

44
import "package:wampproto/auth.dart";
5+
import "package:wampproto/messages.dart";
56
import "package:wampproto/src/auth/cryptosign.dart";
67
import "package:wampproto/src/auth/wampcra.dart";
78

@@ -59,6 +60,19 @@ void main() {
5960
const testSecret = "private";
6061
var testSecretBytes = Uint8List.fromList(testSecret.codeUnits);
6162

63+
const String authID = "foo";
64+
const String salt = "salt";
65+
const int keyLength = 32;
66+
const int iterations = 1000;
67+
const String craChallenge =
68+
'''{"nonce":"cdcb3b12d56e12825be99f38f55ba43f","authprovider":"provider","authid":"foo","authrole":"anonymous","authmethod":"wampcra","session":1,"timestamp":"2024-05-07T09:25:13.307Z"}''';
69+
final Map<String, dynamic> authExtra = {
70+
"challenge": craChallenge,
71+
"salt": salt,
72+
"iterations": iterations,
73+
"keylen": keyLength,
74+
};
75+
6276
test("GenerateCRAChallenge", () async {
6377
var challenge = generateWampCRAChallenge(1, "anonymous", "anonymous", "static");
6478

@@ -89,5 +103,29 @@ void main() {
89103
var isVerified = verifyWampCRASignature(signature.trim(), challenge.trim(), testSecretBytes);
90104
expect(isVerified, true);
91105
});
106+
107+
test("SignCRAChallengeWithSalt", () async {
108+
final challenge = Challenge(WAMPCRAAuthenticator.type, authExtra);
109+
final authenticator = WAMPCRAAuthenticator(authID, authExtra, testSecret);
110+
111+
final authenticate = authenticator.authenticate(challenge);
112+
final saltSecret = await runCommand("auth cra derive-key $salt $testSecret -i $iterations -l $keyLength");
113+
114+
await runCommand("auth cra verify-signature $craChallenge ${authenticate.signature} ${saltSecret.trim()}");
115+
});
116+
117+
test("VerifyCRASignatureWithSalt", () async {
118+
final challenge = await runCommand("auth cra generate-challenge 1 $authID anonymous provider");
119+
final saltSecret = await runCommand("auth cra derive-key $salt $testSecret -i $iterations -l $keyLength");
120+
121+
final signature = await runCommand("auth cra sign-challenge ${challenge.trim()} ${saltSecret.trim()}");
122+
123+
final isVerified = verifyWampCRASignature(
124+
signature.trim(),
125+
challenge.trim(),
126+
Uint8List.fromList(saltSecret.trim().codeUnits),
127+
);
128+
expect(isVerified, true);
129+
});
92130
});
93131
}

0 commit comments

Comments
 (0)