@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.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
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
76
|
-
|
|
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
|
|
101
|
-
constructor() {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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)
|
|
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
|
|
170
|
-
constructor() {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
this.
|
|
182
|
-
|
|
183
|
-
|
|
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.
|
|
228
|
-
aesGcmOptions: AesGcmOptions.
|
|
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
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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-
|
|
556
|
-
var
|
|
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
|
-
|
|
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
|
|
601
|
+
* @param group - SRP group (determines modulus N, generator g, hash).
|
|
564
602
|
* @returns Verifier as Base64 string.
|
|
565
603
|
*/
|
|
566
|
-
async generateSrpVerifier(authHash,
|
|
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.
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
658
|
+
* @param sessionKeyK - Session key K as raw bytes.
|
|
620
659
|
* @param serverM2_b64 - Server proof M2 (Base64).
|
|
621
|
-
* @param
|
|
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,
|
|
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
|
|
640
|
-
* @
|
|
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,
|
|
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.
|
|
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
|
|
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,
|
|
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.
|
|
697
|
-
const S = SecurityUtils.
|
|
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-
|
|
710
|
-
var
|
|
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
|
-
*
|
|
817
|
-
*
|
|
818
|
-
*
|
|
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
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
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
|