Merge branch 'denizt-srp' into master
diff --git a/lib/api.dart b/lib/api.dart
index 3f5865b..15b7736 100644
--- a/lib/api.dart
+++ b/lib/api.dart
@@ -36,21 +36,14 @@
part 'src/api/parameters_with_salt_configuration.dart';
part 'src/api/private_key.dart';
part 'src/api/private_key_parameter.dart';
-
part 'src/api/public_key.dart';
-
part 'src/api/public_key_parameter.dart';
-
part 'src/api/registry_factory_exception.dart';
-
part 'src/api/secure_random.dart';
-
part 'src/api/signature.dart';
-
part 'src/api/signer.dart';
-
part 'src/api/stream_cipher.dart';
-
+part "src/api/srp_client.dart";
+part "src/api/srp_server.dart";
part 'src/api/aead_block_cipher.dart';
-
part 'src/api/xof.dart';
diff --git a/lib/src/api/srp_client.dart b/lib/src/api/srp_client.dart
new file mode 100644
index 0000000..c29d539
--- /dev/null
+++ b/lib/src/api/srp_client.dart
@@ -0,0 +1,35 @@
+// See file LICENSE for more information.
+
+part of api;
+
+abstract class SRPClient {
+ ///Computes the client evidence message M1 using the previously received values.
+ ///To be called after calculating the secret S.
+ ///returns M1: the client side generated evidence message
+ ///throws Exception
+ BigInt? calculateClientEvidenceMessage();
+
+ ///Generates the secret S given the server's credentials
+ ///@param serverB The server's credentials
+ ///@return Client's verification message for the server
+ ///@throws Exception If server's credentials are invalid
+ ///
+ BigInt? calculateSecret(BigInt serverB);
+
+ /// Computes the final session key as a result of the SRP successful mutual authentication
+ /// To be called after verifying the server evidence message M2.
+ /// returns Key: the mutually authenticated symmetric session key
+ /// throws Exception
+ BigInt? calculateSessionKey();
+
+ /// Generates the client's credentials that are to be sent to the server.
+ /// @return The client's public value
+ BigInt? generateClientCredentials(
+ Uint8List salt, Uint8List identity, Uint8List password);
+
+ /// Authenticates the server evidence message M2 received and saves it only if correct.
+ /// [serverM2] the server side generated evidence message
+ /// return A boolean indicating if the server message M2 was the expected one.
+ /// throws Exception
+ bool verifyServerEvidenceMessage(BigInt serverM2);
+}
diff --git a/lib/src/api/srp_server.dart b/lib/src/api/srp_server.dart
new file mode 100644
index 0000000..ff5abad
--- /dev/null
+++ b/lib/src/api/srp_server.dart
@@ -0,0 +1,37 @@
+// See file LICENSE for more information.
+
+part of api;
+
+/// Implements the server side SRP-6a protocol. Note that this class is stateful, and therefore NOT threadsafe.
+/// This implementation of SRP is based on the optimized message sequence put forth by Thomas Wu in the paper
+/// "SRP-6: Improvements and Refinements to the Secure Remote Password Protocol, 2002"
+abstract class SRPServer {
+ /// Processes the client's credentials. If valid the shared secret is generated and returned.
+ /// @param clientA The client's credentials
+ /// @return A shared secret BigInt
+ /// @throws CryptoException If client's credentials are invalid
+ BigInt? calculateSecret(BigInt clientA);
+
+ /// Computes the final session key as a result of the SRP successful mutual authentication
+ /// To be called after calculating the server evidence message M2.
+ /// @return Key: the mutual authenticated symmetric session key
+ /// @throws CryptoException
+ BigInt? calculateSessionKey();
+
+ /// Generates the server's credentials that are to be sent to the client.
+ /// @return The server's public value
+ BigInt? generateServerCredentials();
+
+ /// Computes the server evidence message M2 using the previously verified values.
+ /// To be called after successfully verifying the client evidence message M1.
+ /// @return M2: the server side generated evidence message
+ /// @throws CryptoException
+ BigInt? calculateServerEvidenceMessage();
+
+ /// Authenticates the received client evidence message M1 and saves it only if correct.
+ /// To be called after calculating the secret S.
+ /// @param clientM1 the client side generated evidence message
+ /// @return A boolean indicating if the client message M1 was the expected one.
+ /// @throws CryptoException
+ bool verifyClientEvidenceMessage(BigInt clientM1);
+}
diff --git a/lib/src/registry/registration.dart b/lib/src/registry/registration.dart
index 8f71fcb..a89e756 100644
--- a/lib/src/registry/registration.dart
+++ b/lib/src/registry/registration.dart
@@ -94,6 +94,10 @@
import 'package:pointycastle/signers/ecdsa_signer.dart';
import 'package:pointycastle/signers/rsa_signer.dart';
import 'package:pointycastle/src/registry/registry.dart';
+import 'package:pointycastle/srp/srp6_client.dart';
+import 'package:pointycastle/srp/srp6_server.dart';
+import 'package:pointycastle/srp/srp6_standard_groups.dart';
+import 'package:pointycastle/srp/srp6_verifier_generator.dart';
import 'package:pointycastle/stream/chacha20poly1305.dart';
import 'package:pointycastle/stream/chacha7539.dart';
import 'package:pointycastle/stream/ctr.dart';
diff --git a/lib/srp/srp6_client.dart b/lib/srp/srp6_client.dart
new file mode 100644
index 0000000..a65164d
--- /dev/null
+++ b/lib/srp/srp6_client.dart
@@ -0,0 +1,104 @@
+library impl.srp_client;
+
+import 'dart:typed_data';
+
+import 'package:pointycastle/srp/srp6_standard_groups.dart';
+import 'package:pointycastle/srp/srp6_util.dart';
+import 'package:pointycastle/api.dart';
+
+class SRP6Client implements SRPClient {
+ late BigInt N;
+ late BigInt g;
+
+ BigInt? a;
+ BigInt? A;
+
+ BigInt? B;
+
+ BigInt? x;
+ BigInt? u;
+ BigInt? S;
+
+ BigInt? M1;
+ BigInt? M2;
+ BigInt? Key;
+
+ Digest digest;
+ SecureRandom random;
+ SRP6GroupParameters group;
+
+ SRP6Client(
+ {required this.group, required this.digest, required this.random}) {
+ g = group.g;
+ N = group.N;
+ }
+
+ @override
+ BigInt? calculateClientEvidenceMessage() {
+ // Verify pre-requirements
+ if (A == null || B == null || S == null) {
+ throw Exception(
+ 'Impossible to compute M1: some data are missing from the previous operations (A,B,S)');
+ }
+ // compute the client evidence message 'M1'
+ M1 = SRP6Util.calculateM1(digest, N, A, B, S);
+ return M1;
+ }
+
+ ///S = (B - kg^x) ^ (a + ux)
+ BigInt? calculateS() {
+ var k = SRP6Util.calculateK(digest, N, g);
+ var exp = (u! * x!) + a!;
+ var tmp = g.modPow(x!, N) * (k % N);
+
+ return (B! - (tmp % (N))).modPow(exp, N);
+ }
+
+ @override
+ BigInt? calculateSecret(BigInt serverB) {
+ B = SRP6Util.validatePublicValue(N, serverB);
+ u = SRP6Util.calculateU(digest, N, A, B);
+ S = calculateS();
+ return S;
+ }
+
+ @override
+ BigInt? calculateSessionKey() {
+ // Verify pre-requirements (here we enforce a previous calculation of M1 and M2)
+ if (S == null || M1 == null || M2 == null) {
+ throw Exception(
+ 'Impossible to compute Key: some data are missing from the previous operations (S,M1,M2)');
+ }
+ Key = SRP6Util.calculateKey(digest, N, S!);
+ return Key;
+ }
+
+ @override
+ BigInt? generateClientCredentials(
+ Uint8List salt, Uint8List identity, Uint8List password) {
+ x = SRP6Util.calculateX(digest, N, salt, identity, password);
+ a = selectPrivateValue();
+ A = g.modPow(a!, N);
+ return A;
+ }
+
+ BigInt? selectPrivateValue() {
+ return SRP6Util.generatePrivateValue(digest, N, g, random);
+ }
+
+ @override
+ bool verifyServerEvidenceMessage(BigInt serverM2) {
+ // Verify pre-requirements
+ if (A == null || M1 == null || S == null) {
+ throw Exception('Impossible to compute and verify M2: '
+ 'some data are missing from the previous operations (A,M1,S)');
+ }
+ // Compute the own server evidence message 'M2'
+ var computedM2 = SRP6Util.calculateM2(digest, N, A, M1, S);
+ if (computedM2.compareTo(serverM2) == 0) {
+ M2 = serverM2;
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/lib/srp/srp6_server.dart b/lib/srp/srp6_server.dart
new file mode 100644
index 0000000..47ec256
--- /dev/null
+++ b/lib/srp/srp6_server.dart
@@ -0,0 +1,101 @@
+library impl.srp_server;
+
+import 'package:pointycastle/srp/srp6_standard_groups.dart';
+import 'package:pointycastle/srp/srp6_util.dart';
+import 'package:pointycastle/api.dart';
+
+class SRP6Server implements SRPServer {
+ late BigInt N;
+ late BigInt g;
+
+ BigInt v;
+ SecureRandom random;
+ Digest digest;
+
+ BigInt? A;
+
+ BigInt? b;
+ BigInt? B;
+
+ BigInt? u;
+ BigInt? S;
+ BigInt? M1;
+ BigInt? M2;
+ BigInt? Key;
+
+ SRP6Server(
+ {required SRP6GroupParameters group,
+ required this.v,
+ required this.digest,
+ required this.random}) {
+ g = group.g;
+ N = group.N;
+ }
+
+ @override
+ BigInt? calculateSecret(BigInt clientA) {
+ A = SRP6Util.validatePublicValue(N, clientA);
+ u = SRP6Util.calculateU(digest, N, A, B);
+ S = _calculateS();
+
+ return S;
+ }
+
+ @override
+ BigInt? calculateServerEvidenceMessage() {
+ // Verify pre-requirements
+ if (A == null || M1 == null || S == null) {
+ throw Exception(
+ 'Impossible to compute M2: some data are missing from the previous operations (A,M1,S)');
+ }
+
+ // Compute the server evidence message 'M2'
+ M2 = SRP6Util.calculateM2(digest, N, A!, M1!, S!);
+ return M2;
+ }
+
+ @override
+ BigInt? calculateSessionKey() {
+ // Verify pre-requirements
+ if (S == null || M1 == null || M2 == null) {
+ throw Exception(
+ 'Impossible to compute Key: some data are missing from the previous operations (S,M1,M2)');
+ }
+ Key = SRP6Util.calculateKey(digest, N, S);
+ return Key;
+ }
+
+ @override
+ BigInt? generateServerCredentials() {
+ var k = SRP6Util.calculateK(digest, N, g);
+ b = selectPrivateValue();
+ B = ((k * v + g.modPow(b!, N)) % N);
+
+ return B;
+ }
+
+ BigInt? selectPrivateValue() {
+ return SRP6Util.generatePrivateValue(digest, N, g, random);
+ }
+
+ @override
+ bool verifyClientEvidenceMessage(BigInt clientM1) {
+ // Verify pre-requirements
+ if (A == null || B == null || S == null) {
+ throw Exception(
+ 'Impossible to compute and verify M1: some data are missing from the previous operations (A,B,S)');
+ }
+
+ // Compute the own client evidence message 'M1'
+ var computedM1 = SRP6Util.calculateM1(digest, N, A, B, S);
+ if (computedM1.compareTo(clientM1) == 0) {
+ M1 = clientM1;
+ return true;
+ }
+ return false;
+ }
+
+ BigInt _calculateS() {
+ return ((v.modPow(u!, N) * A!) % N).modPow(b!, N);
+ }
+}
diff --git a/lib/srp/srp6_standard_groups.dart b/lib/srp/srp6_standard_groups.dart
new file mode 100644
index 0000000..2ae92ae
--- /dev/null
+++ b/lib/srp/srp6_standard_groups.dart
@@ -0,0 +1,176 @@
+library src.srp_standard_groups;
+
+class SRP6GroupParameters {
+ BigInt N, g;
+
+ SRP6GroupParameters({required this.N, required this.g});
+}
+
+class SRP6StandardGroups {
+ static final String rfc5054_1024_N =
+ 'EEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C'
+ '9C256576D674DF7496EA81D3383B4813D692C6E0E0D5D8E250B98BE4'
+ '8E495C1D6089DAD15DC7D7B46154D6B6CE8EF4AD69B15D4982559B29'
+ '7BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA9A'
+ 'FD5138FE8376435B9FC61D2FC0EB06E3';
+
+ static final String rfc5054_1024_g = '02';
+
+ /*
+ * * RFC 5054
+ * */
+ static final SRP6GroupParameters rfc5054_1024 =
+ fromNG(rfc5054_1024_N, rfc5054_1024_g);
+ static final String rfc5054_1536_N =
+ '9DEF3CAFB939277AB1F12A8617A47BBBDBA51DF499AC4C80BEEEA961'
+ '4B19CC4D5F4F5F556E27CBDE51C6A94BE4607A291558903BA0D0F843'
+ '80B655BB9A22E8DCDF028A7CEC67F0D08134B1C8B97989149B609E0B'
+ 'E3BAB63D47548381DBC5B1FC764E3F4B53DD9DA1158BFD3E2B9C8CF5'
+ '6EDF019539349627DB2FD53D24B7C48665772E437D6C7F8CE442734A'
+ 'F7CCB7AE837C264AE3A9BEB87F8A2FE9B8B5292E5A021FFF5E91479E'
+ '8CE7A28C2442C6F315180F93499A234DCF76E3FED135F9BB';
+ static final String rfc5054_1536_g = '02';
+
+ static final SRP6GroupParameters rfc5054_1536 =
+ fromNG(rfc5054_1536_N, rfc5054_1536_g);
+ static final String rfc5054_2048_N =
+ 'AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC319294'
+ '3DB56050A37329CBB4A099ED8193E0757767A13DD52312AB4B03310D'
+ 'CD7F48A9DA04FD50E8083969EDB767B0CF6095179A163AB3661A05FB'
+ 'D5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF74'
+ '7359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A'
+ '436C6481F1D2B9078717461A5B9D32E688F87748544523B524B0D57D'
+ '5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6AF874E73'
+ '03CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB6'
+ '94B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F'
+ '9E4AFF73';
+ static final String rfc5054_2048_g = '02';
+
+ static final SRP6GroupParameters rfc5054_2048 =
+ fromNG(rfc5054_2048_N, rfc5054_2048_g);
+ static final String rfc5054_3072_N =
+ 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08'
+ '8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B'
+ '302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9'
+ 'A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6'
+ '49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8'
+ 'FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D'
+ '670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C'
+ '180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718'
+ '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D'
+ '04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D'
+ 'B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226'
+ '1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C'
+ 'BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC'
+ 'E0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF';
+ static final String rfc5054_3072_g = '05';
+
+ static final SRP6GroupParameters rfc5054_3072 =
+ fromNG(rfc5054_3072_N, rfc5054_3072_g);
+ static final String rfc5054_4096_N =
+ 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08'
+ '8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B'
+ '302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9'
+ 'A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6'
+ '49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8'
+ 'FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D'
+ '670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C'
+ '180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718'
+ '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D'
+ '04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D'
+ 'B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226'
+ '1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C'
+ 'BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC'
+ 'E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26'
+ '99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB'
+ '04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2'
+ '233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127'
+ 'D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199'
+ 'FFFFFFFFFFFFFFFF';
+ static final String rfc5054_4096_g = '05';
+
+ static final SRP6GroupParameters rfc5054_4096 =
+ fromNG(rfc5054_4096_N, rfc5054_4096_g);
+ static final String rfc5054_6144_N =
+ 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08'
+ '8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B'
+ '302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9'
+ 'A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6'
+ '49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8'
+ 'FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D'
+ '670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C'
+ '180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718'
+ '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D'
+ '04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D'
+ 'B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226'
+ '1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C'
+ 'BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC'
+ 'E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26'
+ '99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB'
+ '04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2'
+ '233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127'
+ 'D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492'
+ '36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406'
+ 'AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918'
+ 'DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151'
+ '2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03'
+ 'F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F'
+ 'BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA'
+ 'CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B'
+ 'B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632'
+ '387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E'
+ '6DCC4024FFFFFFFFFFFFFFFF';
+ static final String rfc5054_6144_g = '05';
+
+ static final SRP6GroupParameters rfc5054_6144 =
+ fromNG(rfc5054_6144_N, rfc5054_6144_g);
+ static final String rfc5054_8192_N =
+ 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08'
+ '8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B'
+ '302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9'
+ 'A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6'
+ '49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8'
+ 'FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D'
+ '670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C'
+ '180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718'
+ '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D'
+ '04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D'
+ 'B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226'
+ '1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C'
+ 'BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC'
+ 'E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26'
+ '99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB'
+ '04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2'
+ '233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127'
+ 'D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492'
+ '36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406'
+ 'AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918'
+ 'DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151'
+ '2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03'
+ 'F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F'
+ 'BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA'
+ 'CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B'
+ 'B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632'
+ '387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E'
+ '6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA'
+ '3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C'
+ '5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9'
+ '22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC886'
+ '2F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A6'
+ '6D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC5'
+ '0846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268'
+ '359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6'
+ 'FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E71'
+ '60C980DD98EDD3DFFFFFFFFFFFFFFFFF';
+ static final String rfc5054_8192_g = '13';
+
+ static final SRP6GroupParameters rfc5054_8192 =
+ fromNG(rfc5054_8192_N, rfc5054_8192_g);
+ static BigInt fromHex(String hex) {
+ return BigInt.parse(hex, radix: 16);
+ }
+
+ static SRP6GroupParameters fromNG(String hexN, String hexG) {
+ return SRP6GroupParameters(N: fromHex(hexN), g: fromHex(hexG));
+ }
+}
diff --git a/lib/srp/srp6_util.dart b/lib/srp/srp6_util.dart
new file mode 100644
index 0000000..5733a75
--- /dev/null
+++ b/lib/srp/srp6_util.dart
@@ -0,0 +1,166 @@
+library src.srp_util;
+
+import 'dart:typed_data';
+import 'package:pointycastle/pointycastle.dart';
+import 'dart:math' as math;
+
+class SRP6Util {
+ static final _byteMask = BigInt.from(0xff);
+
+ static BigInt calculateK(Digest digest, BigInt N, BigInt g) {
+ return hashPaddedPair(digest, N, N, g);
+ }
+
+ static BigInt calculateU(Digest digest, BigInt N, BigInt? A, BigInt? B) {
+ return hashPaddedPair(digest, N, A, B);
+ }
+
+ static BigInt calculateX(Digest digest, BigInt N, Uint8List salt,
+ Uint8List identity, Uint8List password) {
+ var output = Uint8List(digest.digestSize);
+
+ digest.update(identity, 0, identity.length);
+ digest.updateByte(':'.codeUnitAt(0));
+ digest.update(password, 0, password.length);
+ digest.doFinal(output, 0);
+
+ digest.update(salt, 0, salt.length);
+ digest.update(output, 0, output.length);
+ digest.doFinal(output, 0);
+
+ return decodeBigInt(output);
+ }
+
+ /// Decode a BigInt from bytes in big-endian encoding.
+ static BigInt decodeBigInt(List<int> bytes) {
+ var result = BigInt.from(0);
+ for (var i = 0; i < bytes.length; i++) {
+ result += BigInt.from(bytes[bytes.length - i - 1]) << (8 * i);
+ }
+ return result;
+ }
+
+ /// Encode a BigInt into bytes using big-endian encoding.
+ static Uint8List encodeBigInt(BigInt number) {
+ // Not handling negative numbers. Decide how you want to do that.
+ var size = (number.bitLength + 7) >> 3;
+ var result = Uint8List(size);
+ for (var i = 0; i < size; i++) {
+ result[size - i - 1] = (number & _byteMask).toInt();
+ number = number >> 8;
+ }
+ return result;
+ }
+
+ static BigInt? generatePrivateValue(
+ Digest digest, BigInt N, BigInt g, SecureRandom random) {
+ var minBits = math.min(256, N.bitLength ~/ 2);
+ var min = BigInt.one << (minBits - 1);
+ var max = N - BigInt.one;
+
+ var result;
+ do {
+ result = random.nextBigInteger(minBits);
+ } while (result > max || result < min);
+ return result;
+ }
+
+ static BigInt validatePublicValue(BigInt N, BigInt val) {
+ val = val % N;
+
+ // Check that val % N != 0
+ if (val == BigInt.zero) {
+ throw Exception('Invalid public value: 0');
+ }
+
+ return val;
+ }
+
+ /// Computes the client evidence message (M1) according to the standard routine:
+ /// M1 = H( A | B | S )
+ /// [digest] The Digest used as the hashing function H
+ /// [N] Modulus used to get the pad length
+ /// [A] The public client value
+ /// [B] The public server value
+ /// [S] The secret calculated by both sides
+ /// [M1] The calculated client evidence message
+ static BigInt calculateM1(
+ Digest digest, BigInt N, BigInt? A, BigInt? B, BigInt? S) {
+ return hashPaddedTriplet(digest, N, A, B, S);
+ }
+
+ /// Computes the server evidence message (M2) according to the standard routine:
+ /// M2 = H( A | M1 | S )
+ /// [digest] The Digest used as the hashing function H
+ /// [N] Modulus used to get the pad length
+ /// [A] The public client value
+ /// [M1] The client evidence message
+ /// [S] The secret calculated by both sides
+ /// @return M2 The calculated server evidence message
+ static BigInt calculateM2(
+ Digest digest, BigInt N, BigInt? A, BigInt? M1, BigInt? S) {
+ return hashPaddedTriplet(digest, N, A, M1, S);
+ }
+
+ /// Computes the final Key according to the standard routine: Key = H(S)
+ /// [digest] The Digest used as the hashing function H
+ /// [N] Modulus used to get the pad length
+ /// [S] The secret calculated by both sides
+ /// @return the final Key value.
+ static BigInt calculateKey(Digest digest, BigInt N, BigInt? S) {
+ var padLength = (N.bitLength + 7) ~/ 8;
+ var _S = getPadded(S!, padLength);
+ digest.update(_S, 0, _S.length);
+
+ var output = Uint8List(digest.digestSize);
+ digest.doFinal(output, 0);
+ return decodeBigInt(output);
+ }
+
+ static BigInt hashPaddedTriplet(
+ Digest digest, BigInt N, BigInt? n1, BigInt? n2, BigInt? n3) {
+ var padLength = (N.bitLength + 7) ~/ 8;
+
+ var n1Bytes = getPadded(n1!, padLength);
+ var n2Bytes = getPadded(n2!, padLength);
+ var n3Bytes = getPadded(n3!, padLength);
+
+ digest.update(n1Bytes, 0, n1Bytes.length);
+ digest.update(n2Bytes, 0, n2Bytes.length);
+ digest.update(n3Bytes, 0, n3Bytes.length);
+
+ var output = Uint8List(digest.digestSize);
+ digest.doFinal(output, 0);
+
+ return decodeBigInt(output);
+ }
+
+ static Uint8List getPadded(BigInt n, int length) {
+ var bs = encodeBigInt(n);
+ if (bs.length < length) {
+ var tmp = Uint8List(length);
+ var start = (length - bs.length);
+ for (var i = 0; start < length; i++, start++) {
+ tmp[start] = bs[i];
+ }
+ bs = tmp;
+ }
+ return bs;
+ }
+
+ static BigInt hashPaddedPair(
+ Digest digest, BigInt N, BigInt? n1, BigInt? n2) {
+ var padLength = (N.bitLength + 7) ~/ 8;
+
+ var n1Bytes = getPadded(n1!, padLength);
+ var n2Bytes = getPadded(n2!, padLength);
+
+ digest.update(n1Bytes, 0, n1Bytes.length);
+ digest.update(n2Bytes, 0, n2Bytes.length);
+
+ var output = Uint8List(digest.digestSize);
+ digest.doFinal(output, 0);
+
+ return decodeBigInt(output);
+ }
+}
diff --git a/lib/srp/srp6_verifier_generator.dart b/lib/srp/srp6_verifier_generator.dart
new file mode 100644
index 0000000..d7fa558
--- /dev/null
+++ b/lib/srp/srp6_verifier_generator.dart
@@ -0,0 +1,32 @@
+library src.srp_verifier_generator;
+
+import 'dart:typed_data';
+
+import 'package:pointycastle/srp/srp6_standard_groups.dart';
+import 'package:pointycastle/srp/srp6_util.dart';
+import 'package:pointycastle/pointycastle.dart';
+
+/// Generates new SRP verifier for user
+class SRP6VerifierGenerator {
+ late BigInt N;
+ late BigInt g;
+ Digest digest;
+
+ SRP6VerifierGenerator(
+ {required SRP6GroupParameters group, required this.digest}) {
+ N = group.N;
+ g = group.g;
+ }
+
+ /// Creates a new SRP verifier
+ /// [salt] The salt to use, generally should be large and random
+ /// [identity] The user's identifying information (eg. username)
+ /// [password] The user's password
+ /// returns A new verifier for use in future SRP authentication
+ BigInt generateVerifier(
+ Uint8List salt, Uint8List identity, Uint8List password) {
+ var x = SRP6Util.calculateX(digest, N, salt, identity, password);
+
+ return g.modPow(x, N);
+ }
+}
diff --git a/test/all_tests_web.dart b/test/all_tests_web.dart
index 2ee374c..a5c8fec 100644
--- a/test/all_tests_web.dart
+++ b/test/all_tests_web.dart
@@ -55,6 +55,7 @@
import 'signers/ecdsa_signer_test.dart' as ecdsa_signer_test;
import 'signers/pss_signer_test.dart' as pss_signer_test;
import 'signers/rsa_signer_test.dart' as rsa_signer_test;
+import "srp/srp_test.dart" as srp_test;
import 'src/ufixnum_test.dart' as ufixnum_test;
import 'src/utils_test.dart' as utils_test;
import 'stream/chacha20_test.dart' as chacha20_test;
@@ -115,6 +116,7 @@
pss_signer_test.main();
ufixnum_test.main();
utils_test.main();
+ srp_test.main();
salsa20_test.main();
chacha20_test.main();
chacha20poly1305_test.main();
diff --git a/test/srp/srp_test.dart b/test/srp/srp_test.dart
new file mode 100644
index 0000000..6b4ebe5
--- /dev/null
+++ b/test/srp/srp_test.dart
@@ -0,0 +1,299 @@
+library test.srp_test;
+
+import 'dart:typed_data';
+import 'package:pointycastle/srp/srp6_client.dart';
+import 'package:pointycastle/srp/srp6_server.dart';
+import 'package:pointycastle/srp/srp6_standard_groups.dart';
+import 'package:pointycastle/srp/srp6_util.dart';
+import 'package:pointycastle/srp/srp6_verifier_generator.dart';
+
+import 'package:convert/convert.dart';
+import 'package:pointycastle/pointycastle.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('SRP', () {
+ group('rfc5054:', () {
+ test('rfc5054AppendixBTestVectors', () {
+ var I = Uint8List.fromList('alice'.codeUnits);
+ var P = Uint8List.fromList('password123'.codeUnits);
+ var s =
+ Uint8List.fromList(hex.decode('BEB25379D1A8581EB5A727673A2441EE'));
+ var N = SRP6StandardGroups.rfc5054_1024.N;
+ var g = SRP6StandardGroups.rfc5054_1024.g;
+
+ var expect_k =
+ BigInt.parse('7556AA045AEF2CDD07ABAF0F665C3E818913186F', radix: 16);
+ var expect_x =
+ BigInt.parse('94B7555AABE9127CC58CCF4993DB6CF84D16C124', radix: 16);
+ var expect_v = BigInt.parse(
+ '7E273DE8696FFC4F4E337D05B4B375BEB0DDE1569E8FA00A9886D812'
+ '9BADA1F1822223CA1A605B530E379BA4729FDC59F105B4787E5186F5'
+ 'C671085A1447B52A48CF1970B4FB6F8400BBF4CEBFBB168152E08AB5'
+ 'EA53D15C1AFF87B2B9DA6E04E058AD51CC72BFC9033B564E26480D78'
+ 'E955A5E29E7AB245DB2BE315E2099AFB',
+ radix: 16);
+ var expect_A = BigInt.parse(
+ '61D5E490F6F1B79547B0704C436F523DD0E560F0C64115BB72557EC4'
+ '4352E8903211C04692272D8B2D1A5358A2CF1B6E0BFCF99F921530EC'
+ '8E39356179EAE45E42BA92AEACED825171E1E8B9AF6D9C03E1327F44'
+ 'BE087EF06530E69F66615261EEF54073CA11CF5858F0EDFDFE15EFEA'
+ 'B349EF5D76988A3672FAC47B0769447B',
+ radix: 16);
+ var expect_B = BigInt.parse(
+ 'BD0C61512C692C0CB6D041FA01BB152D4916A1E77AF46AE105393011'
+ 'BAF38964DC46A0670DD125B95A981652236F99D9B681CBF87837EC99'
+ '6C6DA04453728610D0C6DDB58B318885D7D82C7F8DEB75CE7BD4FBAA'
+ '37089E6F9C6059F388838E7A00030B331EB76840910440B1B27AAEAE'
+ 'EB4012B7D7665238A8E3FB004B117B58',
+ radix: 16);
+ var expect_u =
+ BigInt.parse('CE38B9593487DA98554ED47D70A7AE5F462EF019', radix: 16);
+ var expect_S = BigInt.parse(
+ 'B0DC82BABCF30674AE450C0287745E7990A3381F63B387AAF271A10D'
+ '233861E359B48220F7C4693C9AE12B0A6F67809F0876E2D013800D6C'
+ '41BB59B6D5979B5C00A172B4A2A5903A0BDCAF8A709585EB2AFAFA8F'
+ '3499B200210DCC1F10EB33943CD67FC88A2F39A4BE5BEC4EC0A3212D'
+ 'C346D7E474B29EDE8A469FFECA686E5A',
+ radix: 16);
+
+ var k = SRP6Util.calculateK(Digest('SHA-1'), N, g);
+ if (k.compareTo(expect_k) != 0) {
+ fail("wrong value of 'k', expected $expect_k got $k");
+ }
+
+ var x = SRP6Util.calculateX(Digest('SHA-1'), N, s, I, P);
+ if (x.compareTo(expect_x) != 0) {
+ fail("wrong value of 'x'");
+ }
+
+ var gen = SRP6VerifierGenerator(
+ group: SRP6StandardGroups.rfc5054_1024, digest: Digest('SHA-1'));
+ var v = gen.generateVerifier(s, I, P);
+ if (v.compareTo(expect_v) != 0) {
+ fail("wrong value of 'v'");
+ }
+
+ var client = TestSRP6Client(
+ group: SRP6StandardGroups.rfc5054_1024,
+ digest: Digest('SHA-1'),
+ random: random);
+
+ var A = client.generateClientCredentials(s, I, P);
+ if (A!.compareTo(expect_A) != 0) {
+ fail("wrong value of 'A'");
+ }
+
+ var server = TestSRP6Server(
+ group: SRP6StandardGroups.rfc5054_1024,
+ v: v,
+ digest: Digest('SHA-1'),
+ random: random);
+
+ var B = server.generateServerCredentials();
+ if (B!.compareTo(expect_B) != 0) {
+ fail("wrong value of 'B', expected $expect_B got $B");
+ }
+
+ var u = SRP6Util.calculateU(Digest('SHA-1'), N, A, B);
+ if (u.compareTo(expect_u) != 0) {
+ fail("wrong value of 'u'");
+ }
+
+ var clientS = client.calculateSecret(B);
+ if (clientS!.compareTo(expect_S) != 0) {
+ fail("wrong value of 'S' (client)");
+ }
+
+ var serverS = server.calculateSecret(A);
+ if (serverS!.compareTo(expect_S) != 0) {
+ fail("wrong value of 'S' (server)");
+ }
+ });
+ });
+
+ group('mutual verification:', () {
+ test('testMutualVerificationWith1024', () {
+ var I = Uint8List.fromList('username'.codeUnits);
+ var P = Uint8List.fromList('password'.codeUnits);
+ final key = Uint8List.fromList('keywithsixteenth'.codeUnits);
+ final keyParam = KeyParameter(key);
+ final params = ParametersWithIV(keyParam, Uint8List(16));
+
+ random.seed(params);
+ var s = random.nextBytes(16);
+
+ var gen = SRP6VerifierGenerator(
+ group: SRP6StandardGroups.rfc5054_1024, digest: Digest('SHA-256'));
+ var v = gen.generateVerifier(s, I, P);
+
+ var client = SRP6Client(
+ group: SRP6StandardGroups.rfc5054_1024,
+ digest: Digest('SHA-256'),
+ random: random);
+
+ var server = SRP6Server(
+ group: SRP6StandardGroups.rfc5054_1024,
+ v: v,
+ digest: Digest('SHA-256'),
+ random: random);
+
+ var A = client.generateClientCredentials(s, I, P);
+ var B = server.generateServerCredentials();
+
+ var clientS = client.calculateSecret(B!);
+ var serverS = server.calculateSecret(A!);
+
+ if (clientS!.compareTo(serverS!) != 0) {
+ fail(
+ 'SRP agreement failed - client/server calculated different secrets');
+ }
+ });
+
+ test('testMutualVerificationWith2048', () {
+ var I = Uint8List.fromList('username'.codeUnits);
+ var P = Uint8List.fromList('password'.codeUnits);
+ final key = Uint8List.fromList('keywithsixteenth'.codeUnits);
+ final keyParam = KeyParameter(key);
+ final params = ParametersWithIV(keyParam, Uint8List(16));
+
+ random.seed(params);
+ var s = random.nextBytes(16);
+
+ var gen = SRP6VerifierGenerator(
+ group: SRP6StandardGroups.rfc5054_2048, digest: Digest('SHA-256'));
+ var v = gen.generateVerifier(s, I, P);
+
+ var client = SRP6Client(
+ group: SRP6StandardGroups.rfc5054_2048,
+ digest: Digest('SHA-256'),
+ random: random);
+
+ var server = SRP6Server(
+ group: SRP6StandardGroups.rfc5054_2048,
+ v: v,
+ digest: Digest('SHA-256'),
+ random: random);
+
+ var A = client.generateClientCredentials(s, I, P);
+ var B = server.generateServerCredentials();
+
+ var clientS = client.calculateSecret(B!);
+ var serverS = server.calculateSecret(A!);
+
+ if (clientS!.compareTo(serverS!) != 0) {
+ fail(
+ 'SRP agreement failed - client/server calculated different secrets');
+ }
+ try {
+ server.verifyClientEvidenceMessage(
+ client.calculateClientEvidenceMessage()!);
+ } catch (e) {
+ fail("Evidence messages don't match");
+ }
+ });
+ });
+
+ group('client catches bad paramaters:', () {
+ test('testClientCatchesBadB', () {
+ var I = Uint8List.fromList('username'.codeUnits);
+ var P = Uint8List.fromList('password'.codeUnits);
+ final key = Uint8List.fromList('keywithsixteenth'.codeUnits);
+ final keyParam = KeyParameter(key);
+ final params = ParametersWithIV(keyParam, Uint8List(16));
+
+ random.seed(params);
+ var s = random.nextBytes(16);
+
+ var standardsGroup = SRP6StandardGroups.rfc5054_1024;
+ var client = SRP6Client(
+ group: standardsGroup, digest: Digest('SHA-256'), random: random);
+
+ client.generateClientCredentials(s, I, P);
+
+ try {
+ client.calculateSecret(BigInt.zero);
+ fail("Client failed to detect invalid value for 'B'");
+ } catch (e) {
+ // Expected
+ }
+
+ try {
+ client.calculateSecret(standardsGroup.N);
+ fail("Client failed to detect invalid value for 'B'");
+ } catch (e) {
+ // Expected
+ }
+ });
+
+ test('testServerCatchesBadA', () {
+ var I = Uint8List.fromList('username'.codeUnits);
+ var P = Uint8List.fromList('password'.codeUnits);
+ final key = Uint8List.fromList('keywithsixteenth'.codeUnits);
+ final keyParam = KeyParameter(key);
+ final params = ParametersWithIV(keyParam, Uint8List(16));
+
+ random.seed(params);
+ var s = random.nextBytes(16);
+
+ var standardsGroup = SRP6StandardGroups.rfc5054_1024;
+ var gen = SRP6VerifierGenerator(
+ digest: Digest('SHA-256'), group: standardsGroup);
+ var v = gen.generateVerifier(s, I, P);
+
+ var server = SRP6Server(
+ digest: Digest('SHA-256'),
+ group: standardsGroup,
+ v: v,
+ random: random);
+ server.generateServerCredentials();
+
+ try {
+ server.calculateSecret(BigInt.zero);
+ fail("Client failed to detect invalid value for 'A'");
+ } catch (e) {
+ // Expected
+ }
+
+ try {
+ server.calculateSecret(standardsGroup.N);
+ fail("Client failed to detect invalid value for 'A'");
+ } catch (e) {
+ // Expected
+ }
+ });
+ });
+ });
+}
+
+final random = SecureRandom('AES/CTR/AUTO-SEED-PRNG');
+
+class TestSRP6Client extends SRP6Client {
+ @override
+ var a = BigInt.parse(
+ '60975527035CF2AD1989806F0407210BC81EDC04E2762A56AFD529DDDA2D4393',
+ radix: 16);
+
+ TestSRP6Client({required SRP6GroupParameters group, digest, random})
+ : super(group: group, digest: digest, random: random);
+
+ @override
+ BigInt? selectPrivateValue() {
+ return a;
+ }
+}
+
+class TestSRP6Server extends SRP6Server {
+ @override
+ var b = BigInt.parse(
+ 'E487CB59D31AC550471E81F00F6928E01DDA08E974A004F49E61F5D105284D20',
+ radix: 16);
+
+ TestSRP6Server({required SRP6GroupParameters group, v, digest, random})
+ : super(group: group, v: v, digest: digest, random: random);
+
+ @override
+ BigInt? selectPrivateValue() {
+ return b;
+ }
+}
diff --git a/tool/Makefile b/tool/Makefile
index 3955301..e6163f2 100644
--- a/tool/Makefile
+++ b/tool/Makefile
@@ -13,7 +13,7 @@
@echo "RUNNING TESTS"
@echo "********************************************************************************"
- dart ../test/all_tests.dart
+ dart ../test/all_tests_web.dart
doc: depends
@echo "********************************************************************************"