@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/README.md +358 -8
- package/dist/index.cjs +301 -320
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +132 -123
- package/dist/index.d.ts +132 -123
- package/dist/index.js +301 -321
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
78
|
-
|
|
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
|
|
103
|
-
constructor() {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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)
|
|
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
|
|
172
|
-
constructor() {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
this.
|
|
184
|
-
|
|
185
|
-
|
|
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.
|
|
230
|
-
aesGcmOptions: AesGcmOptions.
|
|
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
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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-
|
|
558
|
-
var
|
|
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
|
-
|
|
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
|
|
603
|
+
* @param group - SRP group (determines modulus N, generator g, hash).
|
|
566
604
|
* @returns Verifier as Base64 string.
|
|
567
605
|
*/
|
|
568
|
-
async generateSrpVerifier(authHash,
|
|
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.
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
660
|
+
* @param sessionKeyK - Session key K as raw bytes.
|
|
622
661
|
* @param serverM2_b64 - Server proof M2 (Base64).
|
|
623
|
-
* @param
|
|
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,
|
|
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
|
|
642
|
-
* @
|
|
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,
|
|
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.
|
|
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
|
|
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,
|
|
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.
|
|
699
|
-
const S = SecurityUtils.
|
|
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-
|
|
712
|
-
var
|
|
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
|
-
*
|
|
819
|
-
*
|
|
820
|
-
*
|
|
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
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
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;
|