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