@crossdyne/security 0.5.0-beta.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2,13 +2,15 @@
2
2
 
3
3
  // src/configurations/security-constants.ts
4
4
  var SecurityConstants = {
5
+ /** Standard nonce size for AES-GCM (96 bits). Fixed by NIST SP 800-38D. */
5
6
  AesGcmNonceSize: 12,
6
- AesGcmTagSize: 16,
7
+ /** Minimum allowed authentication tag size (96 bits). */
7
8
  AesGcmTagSizeMin: 12,
9
+ /** Maximum allowed authentication tag size (128 bits). */
8
10
  AesGcmTagSizeMax: 16,
11
+ /** Key size for AES-256 (256 bits). */
9
12
  KeySizeBytes: 32,
10
- // 256 bits
11
- Pbkdf2IterationsDefault: 6e5,
13
+ /** Absolute minimum PBKDF2 iterations for any profile version. */
12
14
  Pbkdf2IterationsMinimum: 1e5
13
15
  };
14
16
  var HashSizes = {
@@ -74,20 +76,31 @@ var SecurityUtils = class {
74
76
  diff |= a[i] ^ b[i];
75
77
  return diff === 0;
76
78
  }
77
- /** Modular exponentiation (base^exp mod mod) using binary exponentiation. */
78
- static expMod(base, exp, mod) {
79
+ /**
80
+ * Async modular exponentiation with event-loop yielding.
81
+ * @param base - The base value.
82
+ * @param exp - The exponent.
83
+ * @param mod - The modulus.
84
+ * @param yieldEvery - Number of iterations before yielding (default 64).
85
+ */
86
+ static async expModAsync(base, exp, mod, yieldEvery = 64) {
79
87
  if (mod === 1n) return 0n;
80
88
  let res = 1n;
81
89
  base = base % mod;
90
+ let i = 0;
82
91
  while (exp > 0n) {
83
92
  if ((exp & 1n) === 1n)
84
93
  res = res * base % mod;
85
94
  exp >>= 1n;
86
95
  if (exp > 0n)
87
96
  base = base * base % mod;
97
+ if (++i % yieldEvery === 0) {
98
+ await new Promise((resolve) => setTimeout(resolve, 0));
99
+ }
88
100
  }
89
101
  return res;
90
102
  }
103
+ /** Converts bigint to minimal-length big-endian bytes (no padding). */
91
104
  static bigIntToRawBytes(bn) {
92
105
  if (bn === 0n)
93
106
  return new Uint8Array([0]);
@@ -99,54 +112,36 @@ var SecurityUtils = class {
99
112
  };
100
113
 
101
114
  // src/crypto/aes-gcm-options.ts
102
- var AesGcmOptions = class _AesGcmOptions {
103
- constructor() {
104
- this._nonceSize = SecurityConstants.AesGcmNonceSize;
105
- this._tagSize = SecurityConstants.AesGcmTagSize;
106
- }
107
- /** Nonce size in bytes (must be 12). */
108
- get nonceSize() {
109
- return this._nonceSize;
110
- }
111
- set nonceSize(v) {
112
- if (v !== SecurityConstants.AesGcmNonceSize) throw new RangeError(`AES-GCM requires ${SecurityConstants.AesGcmNonceSize}-byte nonce`);
113
- this._nonceSize = v;
114
- }
115
- /** Tag size in bytes (12–16, default 16). */
116
- get tagSize() {
117
- return this._tagSize;
118
- }
119
- set tagSize(v) {
120
- if (v < SecurityConstants.AesGcmTagSizeMin || v > SecurityConstants.AesGcmTagSizeMax) throw new RangeError(`Tag size must be between ${SecurityConstants.AesGcmTagSizeMin} and ${SecurityConstants.AesGcmTagSizeMax}`);
121
- this._tagSize = v;
115
+ var _AesGcmOptions = class _AesGcmOptions {
116
+ constructor(nonceSize, tagSize) {
117
+ if (nonceSize !== SecurityConstants.AesGcmNonceSize) {
118
+ throw new RangeError(
119
+ `AES-GCM requires exactly ${SecurityConstants.AesGcmNonceSize}-byte nonce per NIST SP 800-38D.`
120
+ );
121
+ }
122
+ if (tagSize < SecurityConstants.AesGcmTagSizeMin || tagSize > SecurityConstants.AesGcmTagSizeMax) {
123
+ throw new RangeError(
124
+ `Tag size must be between ${SecurityConstants.AesGcmTagSizeMin} and ${SecurityConstants.AesGcmTagSizeMax} bytes.`
125
+ );
126
+ }
127
+ this.nonceSize = nonceSize;
128
+ this.tagSize = tagSize;
129
+ Object.freeze(this);
122
130
  }
123
131
  /** Validates that {@link tagSize} is in the allowed range. */
124
132
  validate() {
125
- if (this.tagSize < SecurityConstants.AesGcmTagSizeMin || this.tagSize > SecurityConstants.AesGcmTagSizeMax) throw new Error(`Invalid Tag Size: ${this.tagSize}`);
126
- }
127
- /** Default preset: nonce=12, tag=16, no AAD. */
128
- static get default() {
129
- return new _AesGcmOptions();
130
- }
131
- /** Fluent setter for {@link tagSize}. */
132
- withTagSize(s) {
133
- this.tagSize = s;
134
- return this;
135
- }
136
- /**
137
- * Fluent setter for {@link associatedData}.
138
- * Accepts a byte array or a UTF-8 string (encoded internally).
139
- */
140
- withAssociatedData(aad) {
141
- this.associatedData = typeof aad === "string" ? new TextEncoder().encode(aad) : aad;
142
- return this;
143
- }
144
- /** Validates and returns this instance. */
145
- build() {
146
- this.validate();
147
- return this;
133
+ if (this.tagSize < SecurityConstants.AesGcmTagSizeMin || this.tagSize > SecurityConstants.AesGcmTagSizeMax)
134
+ throw new Error(`Invalid Tag Size: ${this.tagSize}`);
148
135
  }
149
136
  };
137
+ /**
138
+ * V1 preset: nonce=12, tag=16, no AAD.
139
+ * These exact values are frozen for all V1-encrypted payloads.
140
+ */
141
+ _AesGcmOptions.V1 = Object.freeze(
142
+ new _AesGcmOptions(12, 16)
143
+ );
144
+ var AesGcmOptions = _AesGcmOptions;
150
145
 
151
146
  // src/crypto/crypto-profile.ts
152
147
  var CryptoProfile = class {
@@ -168,28 +163,21 @@ var CryptoVersion = /* @__PURE__ */ ((CryptoVersion2) => {
168
163
  })(CryptoVersion || {});
169
164
 
170
165
  // src/crypto/kdf-options.ts
171
- var KdfOptions = class _KdfOptions {
172
- constructor() {
173
- this._pbkdf2Iterations = SecurityConstants.Pbkdf2IterationsDefault;
174
- this._hashAlgorithm = "SHA-256";
175
- }
176
- /** PBKDF2 iteration count (minimum 100_000). */
177
- get pbkdf2Iterations() {
178
- return this._pbkdf2Iterations;
179
- }
180
- set pbkdf2Iterations(v) {
181
- if (v < SecurityConstants.Pbkdf2IterationsMinimum)
182
- throw new RangeError(`PBKDF2 iterations must be \u2265 ${SecurityConstants.Pbkdf2IterationsMinimum}`);
183
- this._pbkdf2Iterations = v;
184
- }
185
- /** Hash algorithm used by PBKDF2. Must be one of {@link SupportedHashAlgorithms}. */
186
- get hashAlgorithm() {
187
- return this._hashAlgorithm;
188
- }
189
- set hashAlgorithm(v) {
190
- if (!SupportedHashAlgorithms.includes(v))
191
- throw new RangeError(`Unsupported hash: ${v}`);
192
- this._hashAlgorithm = v;
166
+ var _KdfOptions = class _KdfOptions {
167
+ constructor(pbkdf2Iterations, hashAlgorithm) {
168
+ if (pbkdf2Iterations < SecurityConstants.Pbkdf2IterationsMinimum) {
169
+ throw new RangeError(
170
+ `PBKDF2 iterations must be at least ${SecurityConstants.Pbkdf2IterationsMinimum}.`
171
+ );
172
+ }
173
+ if (!SupportedHashAlgorithms.includes(hashAlgorithm)) {
174
+ throw new RangeError(
175
+ `Unsupported hash algorithm: ${hashAlgorithm}. Supported: ${SupportedHashAlgorithms.join(", ")}.`
176
+ );
177
+ }
178
+ this.pbkdf2Iterations = pbkdf2Iterations;
179
+ this.hashAlgorithm = hashAlgorithm;
180
+ Object.freeze(this);
193
181
  }
194
182
  /** Validates iterations and hash algorithm. */
195
183
  validate() {
@@ -198,26 +186,15 @@ var KdfOptions = class _KdfOptions {
198
186
  if (!SupportedHashAlgorithms.includes(this.hashAlgorithm))
199
187
  throw new Error(`Invalid hash algorithm: ${this.hashAlgorithm}`);
200
188
  }
201
- /** Default preset: SHA-256, 600_000 iterations. */
202
- static get default() {
203
- return new _KdfOptions();
204
- }
205
- /** Fluent setter for {@link pbkdf2Iterations}. */
206
- withPbkdf2Iterations(i) {
207
- this.pbkdf2Iterations = i;
208
- return this;
209
- }
210
- /** Fluent setter for {@link hashAlgorithm}. */
211
- withHashAlgorithm(h) {
212
- this.hashAlgorithm = h;
213
- return this;
214
- }
215
- /** Validates and returns this instance. */
216
- build() {
217
- this.validate();
218
- return this;
219
- }
220
189
  };
190
+ /**
191
+ * V1 preset: SHA-256, 600_000 iterations.
192
+ * These exact values are frozen for all V1-derived keys.
193
+ */
194
+ _KdfOptions.V1 = Object.freeze(
195
+ new _KdfOptions(6e5, "SHA-256")
196
+ );
197
+ var KdfOptions = _KdfOptions;
221
198
 
222
199
  // src/crypto/crypto-profile-registry.ts
223
200
  var CryptoProfileRegistry = class {
@@ -226,8 +203,8 @@ var CryptoProfileRegistry = class {
226
203
  case 1 /* V1 */:
227
204
  return new CryptoProfile({
228
205
  version: 1 /* V1 */,
229
- kdfOptions: KdfOptions.default,
230
- aesGcmOptions: AesGcmOptions.default
206
+ kdfOptions: KdfOptions.V1,
207
+ aesGcmOptions: AesGcmOptions.V1
231
208
  });
232
209
  default:
233
210
  throw new Error(`Unsupported crypto version: ${version}`);
@@ -253,7 +230,6 @@ var CryptoService = class {
253
230
  * Encrypts a serializable object to a Base64 string.
254
231
  * @param dataModel - Object or Uint8Array to encrypt.
255
232
  * @param key - AES-256 key (32 bytes).
256
- * @param options - AES-GCM configuration; uses default if omitted.
257
233
  * @returns Base64-encoded ciphertext with prepended nonce.
258
234
  */
259
235
  async encryptData(dataModel, key, version = 1 /* V1 */) {
@@ -276,16 +252,11 @@ var CryptoService = class {
276
252
  false,
277
253
  ["encrypt"]
278
254
  );
279
- let associatedData = new Uint8Array(0);
280
- if (opts.associatedData != null) {
281
- associatedData = opts.associatedData;
282
- }
283
255
  const encryptedContent = await crypto.subtle.encrypt(
284
256
  {
285
257
  name: "AES-GCM",
286
258
  iv: nonce,
287
- tagLength: opts.tagSize * 8,
288
- additionalData: associatedData
259
+ tagLength: opts.tagSize * 8
289
260
  },
290
261
  cryptoKey,
291
262
  plainBytes
@@ -300,7 +271,6 @@ var CryptoService = class {
300
271
  * Decrypts a Base64-encoded ciphertext back to the original object.
301
272
  * @param encryptedBase64 - The encrypted data.
302
273
  * @param key - AES-256 key (32 bytes).
303
- * @param options - AES-GCM configuration; uses default if omitted.
304
274
  * @returns Deserialized object, or null if input is empty.
305
275
  * @throws If authentication tag mismatch or corrupted data.
306
276
  */
@@ -325,15 +295,11 @@ var CryptoService = class {
325
295
  ["decrypt"]
326
296
  );
327
297
  try {
328
- let associatedData = new Uint8Array(0);
329
- if (opts.associatedData != null)
330
- associatedData = opts.associatedData;
331
298
  const decryptedBuffer = await crypto.subtle.decrypt(
332
299
  {
333
300
  name: "AES-GCM",
334
301
  iv: nonce,
335
- tagLength: opts.tagSize * 8,
336
- additionalData: associatedData
302
+ tagLength: opts.tagSize * 8
337
303
  },
338
304
  cryptoKey,
339
305
  ciphertextWithTag
@@ -355,15 +321,14 @@ var CryptoService = class {
355
321
  // src/crypto/key-derivation.service.ts
356
322
  var KeyDerivationService = class {
357
323
  /**
358
- * Derives KEK and Base64 AuthHash. Identity is hashed as-is — caller must
359
- * normalize (trim, lowercase, etc.) before calling.
360
- * @param identity - User identity (pre-normalized by caller).
361
- * @param identity - User identity (email, username).
362
- * @param password - User password.
363
- * @param salt - Random salt.
364
- * @param options - KDF configuration; uses default if omitted.
365
- * @returns Object with `kek` (Uint8Array) and `authHash` (Base64 string).
366
- */
324
+ * Derives KEK and Base64 AuthHash. Identity is used as-is in the
325
+ * combined salt string — caller must normalize (trim, lowercase, etc.) before calling.
326
+ * @param identity - User identity (email, username). Must be pre-normalized by caller.
327
+ * @param password - User password.
328
+ * @param salt - Random salt (minimum 16 bytes).
329
+ * @param version - Crypto version for profile selection.
330
+ * @returns Object with `kek` (Uint8Array) and `authHash` (Base64 string).
331
+ */
367
332
  async deriveKeysFromPassword(identity, password, salt, version) {
368
333
  if (!identity || identity.length === 0)
369
334
  throw new Error("Identity cannot be null or empty.");
@@ -402,63 +367,6 @@ var KeyDerivationService = class {
402
367
  authHash: SecurityUtils.toBase64(new Uint8Array(authBytes))
403
368
  };
404
369
  }
405
- /**
406
- * Derives an SRP-compatible authentication hash (output size = hash output length).
407
- * Identity is hashed as-is — caller must normalize (trim, lowercase, etc.) before calling.
408
- * @param identity - User identity (pre-normalized by caller).
409
- * @param identity - User identity.
410
- * @param password - User password.
411
- * @param salt - Random salt.
412
- * @param srpHashAlgorithm - SRP hash algorithm (SHA-256/384/512).
413
- * @param options - KDF configuration; uses default if omitted.
414
- * @returns Raw hash bytes for use as SRP verifier input (x).
415
- */
416
- async deriveAuthHashForSrp(identity, password, salt, srpHashAlgorithm, version) {
417
- if (!identity || identity.trim().length === 0)
418
- throw new Error("Identity cannot be null or empty.");
419
- if (!password || password.trim().length === 0)
420
- throw new Error("Password cannot be null or empty.");
421
- if (!salt || salt.length < 16)
422
- throw new Error("Salt must be at least 16 bytes.");
423
- const profile = CryptoProfileRegistry.getProfile(version);
424
- const opts = profile.kdfOptions;
425
- opts.validate();
426
- const srpHashSize = HashSizes[srpHashAlgorithm];
427
- const combinedPassword = `${identity}:${password}`;
428
- const passwordBytes = new TextEncoder().encode(combinedPassword);
429
- const baseKey = await crypto.subtle.importKey(
430
- "raw",
431
- passwordBytes,
432
- "PBKDF2",
433
- false,
434
- ["deriveBits"]
435
- );
436
- const masterKeyBits = await crypto.subtle.deriveBits({
437
- name: "PBKDF2",
438
- salt,
439
- iterations: opts.pbkdf2Iterations,
440
- hash: srpHashAlgorithm
441
- }, baseKey, srpHashSize * 8);
442
- const masterKey = await crypto.subtle.importKey(
443
- "raw",
444
- masterKeyBits,
445
- "HKDF",
446
- false,
447
- ["deriveBits"]
448
- );
449
- const info = new TextEncoder().encode("SRP-AUTH-HASH-v1");
450
- const authBytes = await crypto.subtle.deriveBits(
451
- {
452
- name: "HKDF",
453
- hash: srpHashAlgorithm,
454
- salt: new Uint8Array(0),
455
- info
456
- },
457
- masterKey,
458
- srpHashSize * 8
459
- );
460
- return new Uint8Array(authBytes);
461
- }
462
370
  };
463
371
 
464
372
  // src/utils/srp-encoding.ts
@@ -482,7 +390,7 @@ var SrpEncoding = class {
482
390
  * Follows RFC 5054 / SRP-6a:
483
391
  * - H(N) and H(g) are hashed as modulus-sized values.
484
392
  * - Identity (I) is hashed as raw UTF-8 bytes.
485
- * - A and B are padded to the modulus size before hashing.
393
+ * - A and B are zero-padded to the modulus size before hashing.
486
394
  * - K is the session key (H(S) without padding).
487
395
  *
488
396
  * @param ctx - SRP context containing N, g, hash algorithm, and modulus size.
@@ -513,7 +421,10 @@ var SrpEncoding = class {
513
421
  sessionKeyK
514
422
  );
515
423
  }
516
- /** Computes M2 = H(A || M1 || sessionKeyK). */
424
+ /**
425
+ * Computes M2 = H( PAD(A) || M1 || sessionKeyK ).
426
+ * A is zero-padded to the modulus size before hashing.
427
+ */
517
428
  static async computeM2(ctx, A, m1Bytes, sessionKeyK) {
518
429
  return this.computeHash(
519
430
  ctx.hashAlgorithmName,
@@ -525,10 +436,14 @@ var SrpEncoding = class {
525
436
  /** Computes session key K = H(S). */
526
437
  static async computeSessionKey(ctx, S) {
527
438
  const sBytes = SecurityUtils.bigIntToRawBytes(S);
528
- const hashBuffer = await crypto.subtle.digest(ctx.hashAlgorithmName, sBytes.buffer);
439
+ const safeBuffer = sBytes.buffer.slice(
440
+ sBytes.byteOffset,
441
+ sBytes.byteOffset + sBytes.byteLength
442
+ );
443
+ const hashBuffer = await crypto.subtle.digest(ctx.hashAlgorithmName, safeBuffer);
529
444
  return new Uint8Array(hashBuffer);
530
445
  }
531
- /** Hashes bytes and returns raw Uint8Array (для M1/M2). */
446
+ /** Hashes concatenated byte arrays and returns raw Uint8Array. */
532
447
  static async computeHash(algo, ...buffers) {
533
448
  const totalLen = buffers.reduce((sum, b) => sum + b.length, 0);
534
449
  const combined = new Uint8Array(totalLen);
@@ -554,35 +469,159 @@ var SrpEncoding = class {
554
469
  }
555
470
  };
556
471
 
557
- // src/srp/srp-client-service.ts
558
- var SrpClientService = class {
472
+ // src/srp/srp-group.ts
473
+ var SrpGroup = /* @__PURE__ */ ((SrpGroup2) => {
474
+ SrpGroup2[SrpGroup2["Rfc5054_1024"] = 1] = "Rfc5054_1024";
475
+ SrpGroup2[SrpGroup2["Rfc5054_1536"] = 2] = "Rfc5054_1536";
476
+ SrpGroup2[SrpGroup2["Rfc5054_2048"] = 3] = "Rfc5054_2048";
477
+ SrpGroup2[SrpGroup2["Rfc5054_3072"] = 4] = "Rfc5054_3072";
478
+ SrpGroup2[SrpGroup2["Rfc5054_4096"] = 5] = "Rfc5054_4096";
479
+ SrpGroup2[SrpGroup2["Rfc5054_6144"] = 6] = "Rfc5054_6144";
480
+ SrpGroup2[SrpGroup2["Rfc5054_8192"] = 7] = "Rfc5054_8192";
481
+ SrpGroup2[SrpGroup2["Custom"] = 99] = "Custom";
482
+ return SrpGroup2;
483
+ })(SrpGroup || {});
484
+
485
+ // src/srp/srp-group-params.ts
486
+ var SrpGroupParams = class {
487
+ /**
488
+ * Returns the prime modulus N for the given SRP group.
489
+ * @param group - SRP group identifier.
490
+ * @returns N as a bigint.
491
+ */
492
+ static getN(group) {
493
+ switch (group) {
494
+ case 1 /* Rfc5054_1024 */:
495
+ return BigInt("0xEEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C9C256576D674DF7496EA81D3383B4813D692C6E0E0D5D8E250B98BE48E495C1D6089DAD15DC7D7B46154D6B6CE8EF4AD69B15D4982559B297BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA9AFD5138FE8376435B9FC61D2FC0EB06E3");
496
+ case 2 /* Rfc5054_1536 */:
497
+ return BigInt("0x9DEF3CAFB939277AB1F12A8617A47BBBDBA51DF499AC4C80BEEEA9614B19CC4D5F4F5F556E27CBDE51C6A94BE4607A291558903BA0D0F84380B655BB9A22E8DCDF028A7CEC67F0D08134B1C8B97989149B609E0BE3BAB63D47548381DBC5B1FC764E3F4B53DD9DA1158BFD3E2B9C8CF56EDF019539349627DB2FD53D24B7C48665772E437D6C7F8CE442734AF7CCB7AE837C264AE3A9BEB87F8A2FE9B8B5292E5A021FFF5E91479E8CE7A28C2442C6F315180F93499A234DCF76E3FED135F9BB");
498
+ case 3 /* Rfc5054_2048 */:
499
+ return BigInt("0xAC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF6095179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B9078717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73");
500
+ case 4 /* Rfc5054_3072 */:
501
+ return BigInt("0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF");
502
+ case 5 /* Rfc5054_4096 */:
503
+ return BigInt("0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF");
504
+ case 6 /* Rfc5054_6144 */:
505
+ return BigInt("0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF");
506
+ case 7 /* Rfc5054_8192 */:
507
+ return BigInt("0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF");
508
+ default:
509
+ throw new Error("Unsupported group");
510
+ }
511
+ }
512
+ /**
513
+ * Returns the generator g for the given SRP group.
514
+ * @param group - SRP group identifier.
515
+ * @returns g as a bigint (2, 5, or 19).
516
+ */
517
+ static getG(group) {
518
+ switch (group) {
519
+ case 1 /* Rfc5054_1024 */:
520
+ return BigInt(2);
521
+ case 2 /* Rfc5054_1536 */:
522
+ return BigInt(2);
523
+ case 3 /* Rfc5054_2048 */:
524
+ return BigInt(2);
525
+ case 4 /* Rfc5054_3072 */:
526
+ return BigInt(5);
527
+ case 5 /* Rfc5054_4096 */:
528
+ return BigInt(5);
529
+ case 6 /* Rfc5054_6144 */:
530
+ return BigInt(5);
531
+ case 7 /* Rfc5054_8192 */:
532
+ return BigInt(19);
533
+ default:
534
+ throw new Error("Unsupported group");
535
+ }
536
+ }
537
+ };
538
+
539
+ // src/srp/srp-options.ts
540
+ var SrpOptions = class {
559
541
  constructor() {
560
- this.keyDerivation = new KeyDerivationService();
542
+ /** Diffie-Hellman group. Default Rfc5054_3072 (g=5). */
543
+ this.group = 4 /* Rfc5054_3072 */;
544
+ /** Hash algorithm for SRP computations. Default SHA-256. */
545
+ this.hashAlgorithmName = "SHA-256";
546
+ /** Salt size in bytes. Default 32. */
547
+ this.saltSize = 32;
548
+ }
549
+ /** Prime modulus N for the selected group. */
550
+ get N() {
551
+ return SrpGroupParams.getN(this.group);
552
+ }
553
+ /** Generator g for the selected group (2, 5, or 19). */
554
+ get g() {
555
+ return SrpGroupParams.getG(this.group);
556
+ }
557
+ /** Byte length of N (ceil(bitLength / 8)). */
558
+ get modulusSize() {
559
+ return Math.floor((this.N.toString(2).length + 7) / 8);
560
+ }
561
+ /**
562
+ * Computes the multiplier parameter k = H(PAD(N) || PAD(g)) (RFC 5054, 2.5.3).
563
+ * @returns k as a bigint.
564
+ */
565
+ async computeK() {
566
+ const nBytes = SecurityUtils.bigIntToFixedBytes(this.N, this.modulusSize);
567
+ const gBytes = SecurityUtils.bigIntToFixedBytes(this.g, this.modulusSize);
568
+ const combined = new Uint8Array(nBytes.length + gBytes.length);
569
+ combined.set(nBytes, 0);
570
+ combined.set(gBytes, nBytes.length);
571
+ const hash = await crypto.subtle.digest(this.hashAlgorithmName, combined);
572
+ return SecurityUtils.bytesToBigInt(new Uint8Array(hash));
573
+ }
574
+ };
575
+
576
+ // src/srp/srp-context-factory.ts
577
+ var SrpContextFactory = class {
578
+ /**
579
+ * Creates an SRP context with the specified group.
580
+ * @param group - SRP group (default Rfc5054_3072).
581
+ * @returns A ready-to-use SrpContext.
582
+ */
583
+ static async create(group = 4 /* Rfc5054_3072 */) {
584
+ const opts = new SrpOptions();
585
+ opts.group = group;
586
+ opts.hashAlgorithmName = group === 5 /* Rfc5054_4096 */ || group === 6 /* Rfc5054_6144 */ || group === 7 /* Rfc5054_8192 */ ? "SHA-384" : "SHA-256";
587
+ return {
588
+ N: opts.N,
589
+ g: opts.g,
590
+ k: await opts.computeK(),
591
+ modulusSize: opts.modulusSize,
592
+ hashAlgorithmName: opts.hashAlgorithmName,
593
+ hashSize: opts.hashAlgorithmName === "SHA-256" ? 32 : opts.hashAlgorithmName === "SHA-384" ? 48 : 64
594
+ };
561
595
  }
596
+ };
597
+
598
+ // src/srp/srp-client-service.ts
599
+ var SrpClientService = class {
562
600
  /**
563
601
  * Computes SRP verifier v = g^x mod N from the authentication hash.
564
602
  * @param authHash - Auth hash (Base64).
565
- * @param ctx - SRP context (N, g, hash algorithm, etc.).
603
+ * @param group - SRP group (determines modulus N, generator g, hash).
566
604
  * @returns Verifier as Base64 string.
567
605
  */
568
- async generateSrpVerifier(authHash, ctx) {
606
+ async generateSrpVerifier(authHash, group) {
607
+ const ctx = await SrpContextFactory.create(group);
569
608
  const x = SecurityUtils.bytesToBigInt(SecurityUtils.fromBase64(authHash));
570
- const v = SecurityUtils.expMod(ctx.g, x, ctx.N);
609
+ const v = await SecurityUtils.expModAsync(ctx.g, x, ctx.N);
571
610
  return SecurityUtils.toBase64(SrpEncoding.toModulusBytes(ctx, v));
572
611
  }
573
612
  /**
574
613
  * Generates client proof (A, M1, session key S) from server challenge.
575
614
  * @param login - User login.
576
- * @param password - Plaintext password.
615
+ * @param authHashBytes - SRP private exponent x as raw bytes (derived from auth hash).
577
616
  * @param saltBase64 - Server salt (standard Base64).
578
617
  * @param B_base64 - Server public ephemeral B (standard Base64).
579
- * @param ctx - SRP context.
618
+ * @param group - SRP group (determines modulus N, generator g, hash).
580
619
  * @returns Object with A and M1 as standard Base64; SessionKeyK as raw bytes.
581
620
  */
582
- async generateSrpProof(login, password, saltBase64, B_base64, ctx, version) {
621
+ async generateSrpProof(login, authHashBytes, saltBase64, B_base64, group) {
622
+ const ctx = await SrpContextFactory.create(group);
583
623
  const salt = SecurityUtils.fromBase64(saltBase64);
584
- const authHash = await this.keyDerivation.deriveAuthHashForSrp(login, password, salt, ctx.hashAlgorithmName, version);
585
- const x = SecurityUtils.bytesToBigInt(authHash);
624
+ const x = SecurityUtils.bytesToBigInt(authHashBytes);
586
625
  const privateKeySize = Math.max(32, Math.floor(ctx.modulusSize / 2));
587
626
  let aBytes;
588
627
  let a;
@@ -590,7 +629,7 @@ var SrpClientService = class {
590
629
  aBytes = crypto.getRandomValues(new Uint8Array(privateKeySize));
591
630
  a = SecurityUtils.bytesToBigInt(aBytes);
592
631
  } while (a === 0n);
593
- const A = SecurityUtils.expMod(ctx.g, a, ctx.N);
632
+ const A = await SecurityUtils.expModAsync(ctx.g, a, ctx.N);
594
633
  if (A <= 0n || A >= ctx.N)
595
634
  throw new Error("Invalid client public key A.");
596
635
  const B = SecurityUtils.bytesToBigInt(SecurityUtils.fromBase64(B_base64));
@@ -599,11 +638,11 @@ var SrpClientService = class {
599
638
  const u = await SrpEncoding.hashModuli(ctx, A, B);
600
639
  if (u === 0n)
601
640
  throw new Error("\u041D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 u");
602
- const gX = SecurityUtils.expMod(ctx.g, x, ctx.N);
641
+ const gX = await SecurityUtils.expModAsync(ctx.g, x, ctx.N);
603
642
  const term = ctx.k * gX % ctx.N;
604
643
  const base = (B - term + ctx.N) % ctx.N;
605
644
  const exponent = a + u * x;
606
- const S = SecurityUtils.expMod(base, exponent, ctx.N);
645
+ const S = await SecurityUtils.expModAsync(base, exponent, ctx.N);
607
646
  if (S === 0n)
608
647
  throw new Error("Critical error: S === 0");
609
648
  const sessionKeyK = await SrpEncoding.computeSessionKey(ctx, S);
@@ -618,12 +657,13 @@ var SrpClientService = class {
618
657
  * Validates the server proof M2 to authenticate the server.
619
658
  * @param A_b64 - Client public A (Base64).
620
659
  * @param M1_b64 - Client proof M1 (Base64).
621
- * @param S_b64 - Session key S (Base64).
660
+ * @param sessionKeyK - Session key K as raw bytes.
622
661
  * @param serverM2_b64 - Server proof M2 (Base64).
623
- * @param ctx - SRP context.
662
+ * @param group - SRP group (determines modulus N, generator g, hash).
624
663
  * @returns True if the server proof is valid.
625
664
  */
626
- async verifyServerM2(A_b64, M1_b64, sessionKeyK, serverM2_b64, ctx) {
665
+ async verifyServerM2(A_b64, M1_b64, sessionKeyK, serverM2_b64, group) {
666
+ const ctx = await SrpContextFactory.create(group);
627
667
  const A = SecurityUtils.bytesToBigInt(SecurityUtils.fromBase64(A_b64));
628
668
  const M1 = SecurityUtils.fromBase64(M1_b64);
629
669
  const computedM2 = await SrpEncoding.computeM2(ctx, A, M1, sessionKeyK);
@@ -638,14 +678,16 @@ var SrpServerService = class {
638
678
  * Generates server challenge B and session state from verifier.
639
679
  * @param login - User login.
640
680
  * @param verifierBytes - Stored verifier v as byte array.
641
- * @param ctx - SRP context (hash, N, g, etc.).
642
- * @returns Session state with private b, verifier, and public B.
681
+ * @param salt - User salt (raw bytes).
682
+ * @param group - SRP group (determines modulus N, generator g, hash).
683
+ * @returns Session state with private b, verifier, public B, and salt.
643
684
  */
644
- async getSrpChallenge(login, verifierBytes, salt, ctx) {
685
+ async getSrpChallenge(login, verifierBytes, salt, group) {
645
686
  if (!login || login.trim().length === 0)
646
687
  throw new Error("Login cannot be null or empty.");
647
688
  if (!verifierBytes || verifierBytes.length === 0)
648
689
  throw new Error("Verifier cannot be null or empty.");
690
+ const ctx = await SrpContextFactory.create(group);
649
691
  const v = SecurityUtils.bytesToBigInt(verifierBytes);
650
692
  if (v <= 0n || v >= ctx.N)
651
693
  throw new Error("The verifier is corrupted");
@@ -656,7 +698,7 @@ var SrpServerService = class {
656
698
  if (b === 0n) {
657
699
  continue;
658
700
  }
659
- const gB = SecurityUtils.expMod(ctx.g, b, ctx.N);
701
+ const gB = await SecurityUtils.expModAsync(ctx.g, b, ctx.N);
660
702
  const B = (ctx.k * v + gB) % ctx.N;
661
703
  if (B !== 0n) {
662
704
  return {
@@ -674,11 +716,12 @@ var SrpServerService = class {
674
716
  * @param sessionState - Server session state.
675
717
  * @param a - Client public A (Base64).
676
718
  * @param m1 - Client proof M1 (Base64).
677
- * @param ctx - SRP context.
719
+ * @param group - SRP group (determines modulus N, generator g, hash).
678
720
  * @returns Server proof M2 as Base64 string.
679
721
  * @throws If verification fails or input is invalid.
680
722
  */
681
- async verifySrpProof(sessionState, a, m1, ctx) {
723
+ async verifySrpProof(sessionState, a, m1, group) {
724
+ const ctx = await SrpContextFactory.create(group);
682
725
  const A = SecurityUtils.bytesToBigInt(SecurityUtils.fromBase64(a));
683
726
  const M1_client = SecurityUtils.fromBase64(m1);
684
727
  const b = SecurityUtils.bytesToBigInt(sessionState.privateKeyB);
@@ -695,8 +738,8 @@ var SrpServerService = class {
695
738
  const u = await SrpEncoding.hashModuli(ctx, A, B);
696
739
  if (u === 0n)
697
740
  throw new Error("Error in calculating the parameter u");
698
- const vU = SecurityUtils.expMod(v, u, ctx.N);
699
- const S = SecurityUtils.expMod(A * vU % ctx.N, b, ctx.N);
741
+ const vU = await SecurityUtils.expModAsync(v, u, ctx.N);
742
+ const S = await SecurityUtils.expModAsync(A * vU % ctx.N, b, ctx.N);
700
743
  if (S === 0n)
701
744
  throw new Error("Critical error: shared secret S is zero (possible malicious A).");
702
745
  const sessionKeyK = await SrpEncoding.computeSessionKey(ctx, S);
@@ -708,129 +751,66 @@ var SrpServerService = class {
708
751
  }
709
752
  };
710
753
 
711
- // src/srp/srp-group.ts
712
- var SrpGroup = /* @__PURE__ */ ((SrpGroup2) => {
713
- SrpGroup2[SrpGroup2["Rfc5054_1024"] = 1] = "Rfc5054_1024";
714
- SrpGroup2[SrpGroup2["Rfc5054_1536"] = 2] = "Rfc5054_1536";
715
- SrpGroup2[SrpGroup2["Rfc5054_2048"] = 3] = "Rfc5054_2048";
716
- SrpGroup2[SrpGroup2["Rfc5054_3072"] = 4] = "Rfc5054_3072";
717
- SrpGroup2[SrpGroup2["Rfc5054_4096"] = 5] = "Rfc5054_4096";
718
- SrpGroup2[SrpGroup2["Rfc5054_6144"] = 6] = "Rfc5054_6144";
719
- SrpGroup2[SrpGroup2["Rfc5054_8192"] = 7] = "Rfc5054_8192";
720
- SrpGroup2[SrpGroup2["Custom"] = 99] = "Custom";
721
- return SrpGroup2;
722
- })(SrpGroup || {});
723
-
724
- // src/srp/srp-group-params.ts
725
- var SrpGroupParams = class {
726
- /**
727
- * Returns the prime modulus N for the given SRP group.
728
- * @param group - SRP group identifier.
729
- * @returns N as a bigint.
730
- */
731
- static getN(group) {
732
- switch (group) {
733
- case 1 /* Rfc5054_1024 */:
734
- return BigInt("0xEEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C9C256576D674DF7496EA81D3383B4813D692C6E0E0D5D8E250B98BE48E495C1D6089DAD15DC7D7B46154D6B6CE8EF4AD69B15D4982559B297BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA9AFD5138FE8376435B9FC61D2FC0EB06E3");
735
- case 2 /* Rfc5054_1536 */:
736
- return BigInt("0x9DEF3CAFB939277AB1F12A8617A47BBBDBA51DF499AC4C80BEEEA9614B19CC4D5F4F5F556E27CBDE51C6A94BE4607A291558903BA0D0F84380B655BB9A22E8DCDF028A7CEC67F0D08134B1C8B97989149B609E0BE3BAB63D47548381DBC5B1FC764E3F4B53DD9DA1158BFD3E2B9C8CF56EDF019539349627DB2FD53D24B7C48665772E437D6C7F8CE442734AF7CCB7AE837C264AE3A9BEB87F8A2FE9B8B5292E5A021FFF5E91479E8CE7A28C2442C6F315180F93499A234DCF76E3FED135F9BB");
737
- case 3 /* Rfc5054_2048 */:
738
- return BigInt("0xAC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF6095179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B9078717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73");
739
- case 4 /* Rfc5054_3072 */:
740
- return BigInt("0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF");
741
- case 5 /* Rfc5054_4096 */:
742
- return BigInt("0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF");
743
- case 6 /* Rfc5054_6144 */:
744
- return BigInt("0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF");
745
- case 7 /* Rfc5054_8192 */:
746
- return BigInt("0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF");
747
- default:
748
- throw new Error("Unsupported group");
749
- }
750
- }
751
- /**
752
- * Returns the generator g for the given SRP group.
753
- * @param group - SRP group identifier.
754
- * @returns g as a bigint (2, 5, or 19).
755
- */
756
- static getG(group) {
757
- switch (group) {
758
- case 1 /* Rfc5054_1024 */:
759
- return BigInt(2);
760
- case 2 /* Rfc5054_1536 */:
761
- return BigInt(2);
762
- case 3 /* Rfc5054_2048 */:
763
- return BigInt(2);
764
- case 4 /* Rfc5054_3072 */:
765
- return BigInt(5);
766
- case 5 /* Rfc5054_4096 */:
767
- return BigInt(5);
768
- case 6 /* Rfc5054_6144 */:
769
- return BigInt(5);
770
- case 7 /* Rfc5054_8192 */:
771
- return BigInt(19);
772
- default:
773
- throw new Error("Unsupported group");
774
- }
775
- }
776
- };
777
-
778
- // src/srp/srp-options.ts
779
- var SrpOptions = class {
780
- constructor() {
781
- /** Diffie-Hellman group. Default Rfc5054_3072 (g=5). */
782
- this.group = 4 /* Rfc5054_3072 */;
783
- /** Hash algorithm for SRP computations. Default SHA-256. */
784
- this.hashAlgorithmName = "SHA-256";
785
- /** Salt size in bytes. Default 32. */
786
- this.saltSize = 32;
787
- }
788
- /** Prime modulus N for the selected group. */
789
- get N() {
790
- return SrpGroupParams.getN(this.group);
791
- }
792
- /** Generator g for the selected group (2, 5, or 19). */
793
- get g() {
794
- return SrpGroupParams.getG(this.group);
795
- }
796
- /** Byte length of N (ceil(bitLength / 8)). */
797
- get modulusSize() {
798
- return Math.floor((this.N.toString(2).length + 7) / 8);
799
- }
800
- /**
801
- * Computes the multiplier parameter k = H(PAD(N) || PAD(g)) (RFC 5054, 2.5.3).
802
- * @returns k as a bigint.
803
- */
804
- async computeK() {
805
- const nBytes = SecurityUtils.bigIntToFixedBytes(this.N, this.modulusSize);
806
- const gBytes = SecurityUtils.bigIntToFixedBytes(this.g, this.modulusSize);
807
- const combined = new Uint8Array(nBytes.length + gBytes.length);
808
- combined.set(nBytes, 0);
809
- combined.set(gBytes, nBytes.length);
810
- const hash = await crypto.subtle.digest(this.hashAlgorithmName, combined);
811
- return SecurityUtils.bytesToBigInt(new Uint8Array(hash));
812
- }
813
- };
814
-
815
- // src/srp/srp-context-factory.ts
816
- var SrpContextFactory = class {
754
+ // src/srp/srp-key-derivation.service.ts
755
+ var SrpKeyDerivationService = class {
817
756
  /**
818
- * Creates an SRP context with the specified group.
819
- * @param group - SRP group (default Rfc5054_3072).
820
- * @returns A ready-to-use SrpContext.
757
+ * Derives an SRP-compatible authentication hash (output size = hash output length).
758
+ * Identity is used as-is in the combined string — caller must normalize
759
+ * (trim, lowercase, etc.) before calling.
760
+ * @param identity - User identity (email, username). Must be pre-normalized by caller.
761
+ * @param password - User password.
762
+ * @param salt - Random salt (minimum 16 bytes).
763
+ * @param srpGroup - SRP group (determines hash algorithm and modulus).
764
+ * @param version - Crypto version for KDF profile selection.
765
+ * @returns Raw hash bytes for use as SRP verifier input (x).
821
766
  */
822
- static async create(group = 4 /* Rfc5054_3072 */) {
823
- const opts = new SrpOptions();
824
- opts.group = group;
825
- opts.hashAlgorithmName = group === 5 /* Rfc5054_4096 */ || group === 6 /* Rfc5054_6144 */ || group === 7 /* Rfc5054_8192 */ ? "SHA-384" : "SHA-256";
826
- return {
827
- N: opts.N,
828
- g: opts.g,
829
- k: await opts.computeK(),
830
- modulusSize: opts.modulusSize,
831
- hashAlgorithmName: opts.hashAlgorithmName,
832
- hashSize: opts.hashAlgorithmName === "SHA-256" ? 32 : opts.hashAlgorithmName === "SHA-384" ? 48 : 64
833
- };
767
+ async deriveAuthHashForSrp(identity, password, salt, srpGroup, version) {
768
+ if (!identity || identity.trim().length === 0)
769
+ throw new Error("Identity cannot be null or empty.");
770
+ if (!password || password.trim().length === 0)
771
+ throw new Error("Password cannot be null or empty.");
772
+ if (!salt || salt.length < 16)
773
+ throw new Error("Salt must be at least 16 bytes.");
774
+ const profile = CryptoProfileRegistry.getProfile(version);
775
+ const opts = profile.kdfOptions;
776
+ opts.validate();
777
+ const ctx = await SrpContextFactory.create(srpGroup);
778
+ const srpHashAlgorithm = ctx.hashAlgorithmName;
779
+ const srpHashSize = HashSizes[srpHashAlgorithm];
780
+ const combinedPassword = `${identity}:${password}`;
781
+ const passwordBytes = new TextEncoder().encode(combinedPassword);
782
+ const baseKey = await crypto.subtle.importKey(
783
+ "raw",
784
+ passwordBytes,
785
+ "PBKDF2",
786
+ false,
787
+ ["deriveBits"]
788
+ );
789
+ const masterKeyBits = await crypto.subtle.deriveBits({
790
+ name: "PBKDF2",
791
+ salt,
792
+ iterations: opts.pbkdf2Iterations,
793
+ hash: srpHashAlgorithm
794
+ }, baseKey, srpHashSize * 8);
795
+ const masterKey = await crypto.subtle.importKey(
796
+ "raw",
797
+ masterKeyBits,
798
+ "HKDF",
799
+ false,
800
+ ["deriveBits"]
801
+ );
802
+ const info = new TextEncoder().encode("SRP-AUTH-HASH-v1");
803
+ const authBytes = await crypto.subtle.deriveBits(
804
+ {
805
+ name: "HKDF",
806
+ hash: srpHashAlgorithm,
807
+ salt: new Uint8Array(0),
808
+ info
809
+ },
810
+ masterKey,
811
+ srpHashSize * 8
812
+ );
813
+ return new Uint8Array(authBytes);
834
814
  }
835
815
  };
836
816
 
@@ -848,6 +828,7 @@ exports.SrpContextFactory = SrpContextFactory;
848
828
  exports.SrpEncoding = SrpEncoding;
849
829
  exports.SrpGroup = SrpGroup;
850
830
  exports.SrpGroupParams = SrpGroupParams;
831
+ exports.SrpKeyDerivationService = SrpKeyDerivationService;
851
832
  exports.SrpOptions = SrpOptions;
852
833
  exports.SrpServerService = SrpServerService;
853
834
  exports.SupportedHashAlgorithms = SupportedHashAlgorithms;