@crossdyne/security 0.3.1-beta.1 → 0.4.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +109 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -11
- package/dist/index.d.ts +31 -11
- package/dist/index.js +109 -42
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -11,6 +11,11 @@ var SecurityConstants = {
|
|
|
11
11
|
Pbkdf2IterationsDefault: 6e5,
|
|
12
12
|
Pbkdf2IterationsMinimum: 1e5
|
|
13
13
|
};
|
|
14
|
+
var HashSizes = {
|
|
15
|
+
"SHA-256": 32,
|
|
16
|
+
"SHA-384": 48,
|
|
17
|
+
"SHA-512": 64
|
|
18
|
+
};
|
|
14
19
|
|
|
15
20
|
// src/configurations/supported-hash-algorithms.ts
|
|
16
21
|
var SupportedHashAlgorithms = ["SHA-256", "SHA-384", "SHA-512"];
|
|
@@ -299,12 +304,17 @@ var KeyDerivationService = class {
|
|
|
299
304
|
* @returns Object with `kek` (Uint8Array) and `authHash` (Base64 string).
|
|
300
305
|
*/
|
|
301
306
|
async deriveKeysFromPassword(identity, password, salt, options) {
|
|
307
|
+
if (!identity || identity.trim().length === 0)
|
|
308
|
+
throw new Error("Identity cannot be null or empty.");
|
|
309
|
+
if (!password || password.trim().length === 0)
|
|
310
|
+
throw new Error("Password cannot be null or empty.");
|
|
311
|
+
if (!salt || salt.length < 16)
|
|
312
|
+
throw new Error("Salt must be at least 16 bytes.");
|
|
302
313
|
const opts = options ?? KdfOptions.default;
|
|
303
314
|
opts.validate();
|
|
304
315
|
const safeSalt = new Uint8Array(salt);
|
|
305
316
|
const encoder = new TextEncoder();
|
|
306
|
-
const
|
|
307
|
-
const combinedPassword = `${normalizedLogin}:${password}`;
|
|
317
|
+
const combinedPassword = `${identity}:${password}`;
|
|
308
318
|
const passwordBytes = encoder.encode(combinedPassword);
|
|
309
319
|
const baseKey = await crypto.subtle.importKey("raw", passwordBytes, "PBKDF2", false, ["deriveBits", "deriveKey"]);
|
|
310
320
|
const masterKeyBits = await crypto.subtle.deriveBits({
|
|
@@ -339,11 +349,16 @@ var KeyDerivationService = class {
|
|
|
339
349
|
* @returns Raw hash bytes for use as SRP verifier input (x).
|
|
340
350
|
*/
|
|
341
351
|
async deriveAuthHashForSrp(identity, password, salt, srpHashAlgorithm, options) {
|
|
352
|
+
if (!identity || identity.trim().length === 0)
|
|
353
|
+
throw new Error("Identity cannot be null or empty.");
|
|
354
|
+
if (!password || password.trim().length === 0)
|
|
355
|
+
throw new Error("Password cannot be null or empty.");
|
|
356
|
+
if (!salt || salt.length < 16)
|
|
357
|
+
throw new Error("Salt must be at least 16 bytes.");
|
|
342
358
|
const opts = options ?? KdfOptions.default;
|
|
343
359
|
opts.validate();
|
|
344
|
-
const srpHashSize = srpHashAlgorithm
|
|
345
|
-
const
|
|
346
|
-
const combinedPassword = `${normalizedLogin}:${password}`;
|
|
360
|
+
const srpHashSize = HashSizes[srpHashAlgorithm];
|
|
361
|
+
const combinedPassword = `${identity}:${password}`;
|
|
347
362
|
const passwordBytes = new TextEncoder().encode(combinedPassword);
|
|
348
363
|
const baseKey = await crypto.subtle.importKey(
|
|
349
364
|
"raw",
|
|
@@ -434,21 +449,71 @@ var SrpEncoding = class {
|
|
|
434
449
|
const buffers = values.map((v) => this.toModulusBytes(ctx, v));
|
|
435
450
|
return this.hash(ctx.hashAlgorithmName, ...buffers);
|
|
436
451
|
}
|
|
437
|
-
/**
|
|
438
|
-
|
|
439
|
-
|
|
452
|
+
/**
|
|
453
|
+
* Computes the client proof M1 = H( H(N) ⊕ H(g) | H(I) | s | PAD(A) | PAD(B) | K ).
|
|
454
|
+
*
|
|
455
|
+
* Follows RFC 5054 / SRP-6a:
|
|
456
|
+
* - H(N) and H(g) are hashed as modulus-sized values.
|
|
457
|
+
* - Identity (I) is hashed as raw UTF-8 bytes.
|
|
458
|
+
* - A and B are padded to the modulus size before hashing.
|
|
459
|
+
* - K is the session key (H(S) without padding).
|
|
460
|
+
*
|
|
461
|
+
* @param ctx - SRP context containing N, g, hash algorithm, and modulus size.
|
|
462
|
+
* @param A - Client ephemeral public key.
|
|
463
|
+
* @param B - Server ephemeral public key.
|
|
464
|
+
* @param sessionKeyK - Session key K as raw bytes.
|
|
465
|
+
* @param identity - User identity (login). Should already be normalized (trimmed / lowercased) by the caller.
|
|
466
|
+
* @param salt - User-specific salt bytes.
|
|
467
|
+
* @returns The M1 proof as raw hash bytes.
|
|
468
|
+
*/
|
|
469
|
+
static async computeM1(ctx, A, B, sessionKeyK, identity, salt) {
|
|
470
|
+
const nBytes = this.toModulusBytes(ctx, ctx.N);
|
|
471
|
+
const gBytes = this.toModulusBytes(ctx, ctx.g);
|
|
472
|
+
const hashN = await this.computeHash(ctx.hashAlgorithmName, nBytes);
|
|
473
|
+
const hashG = await this.computeHash(ctx.hashAlgorithmName, gBytes);
|
|
474
|
+
const xorNg = new Uint8Array(hashN.length);
|
|
475
|
+
for (let i = 0; i < hashN.length; i++)
|
|
476
|
+
xorNg[i] = hashN[i] ^ hashG[i];
|
|
477
|
+
const encoder = new TextEncoder();
|
|
478
|
+
const hashI = await this.computeHash(ctx.hashAlgorithmName, encoder.encode(identity));
|
|
479
|
+
return this.computeHash(
|
|
480
|
+
ctx.hashAlgorithmName,
|
|
481
|
+
xorNg,
|
|
482
|
+
hashI,
|
|
483
|
+
salt,
|
|
484
|
+
this.toModulusBytes(ctx, A),
|
|
485
|
+
this.toModulusBytes(ctx, B),
|
|
486
|
+
sessionKeyK
|
|
487
|
+
);
|
|
440
488
|
}
|
|
441
489
|
/** Computes M2 = H(A || M1 || sessionKeyK). */
|
|
442
|
-
static async computeM2(ctx, A,
|
|
443
|
-
return this.
|
|
490
|
+
static async computeM2(ctx, A, m1Bytes, sessionKeyK) {
|
|
491
|
+
return this.computeHash(
|
|
492
|
+
ctx.hashAlgorithmName,
|
|
493
|
+
this.toModulusBytes(ctx, A),
|
|
494
|
+
m1Bytes,
|
|
495
|
+
sessionKeyK
|
|
496
|
+
);
|
|
444
497
|
}
|
|
445
498
|
/** Computes session key K = H(S). */
|
|
446
499
|
static async computeSessionKey(ctx, S) {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
);
|
|
500
|
+
let hex = S.toString(16);
|
|
501
|
+
if (hex.length % 2 !== 0)
|
|
502
|
+
hex = "0" + hex;
|
|
503
|
+
const sBytes = new Uint8Array(hex.match(/.{1,2}/g).map((b) => parseInt(b, 16)));
|
|
504
|
+
const hashBuffer = await crypto.subtle.digest(ctx.hashAlgorithmName, sBytes);
|
|
505
|
+
return new Uint8Array(hashBuffer);
|
|
506
|
+
}
|
|
507
|
+
/** Hashes bytes and returns raw Uint8Array (для M1/M2). */
|
|
508
|
+
static async computeHash(algo, ...buffers) {
|
|
509
|
+
const totalLen = buffers.reduce((sum, b) => sum + b.length, 0);
|
|
510
|
+
const combined = new Uint8Array(totalLen);
|
|
511
|
+
let offset = 0;
|
|
512
|
+
for (const buf of buffers) {
|
|
513
|
+
combined.set(buf, offset);
|
|
514
|
+
offset += buf.length;
|
|
515
|
+
}
|
|
516
|
+
const hashBuffer = await crypto.subtle.digest(algo, combined);
|
|
452
517
|
return new Uint8Array(hashBuffer);
|
|
453
518
|
}
|
|
454
519
|
/** Concatenates byte arrays and returns the hash as bigint. */
|
|
@@ -494,7 +559,8 @@ var SrpClientService = class {
|
|
|
494
559
|
const salt = SecurityUtils.fromBase64(saltBase64);
|
|
495
560
|
const authHash = await this.keyDerivation.deriveAuthHashForSrp(login, password, salt, ctx.hashAlgorithmName);
|
|
496
561
|
const x = SecurityUtils.bytesToBigInt(authHash);
|
|
497
|
-
const
|
|
562
|
+
const privateKeySize = Math.max(32, Math.floor(ctx.modulusSize / 2));
|
|
563
|
+
const aBytes = crypto.getRandomValues(new Uint8Array(privateKeySize));
|
|
498
564
|
const a = SecurityUtils.bytesToBigInt(aBytes);
|
|
499
565
|
const A = SecurityUtils.expMod(ctx.g, a, ctx.N);
|
|
500
566
|
if (A % ctx.N === 0n)
|
|
@@ -513,11 +579,11 @@ var SrpClientService = class {
|
|
|
513
579
|
if (S === 0n)
|
|
514
580
|
throw new Error("Critical error: S === 0");
|
|
515
581
|
const sessionKeyK = await SrpEncoding.computeSessionKey(ctx, S);
|
|
516
|
-
const M1 = await SrpEncoding.computeM1(ctx, A, B, sessionKeyK);
|
|
582
|
+
const M1 = await SrpEncoding.computeM1(ctx, A, B, sessionKeyK, login, salt);
|
|
517
583
|
return {
|
|
518
584
|
A: SecurityUtils.toBase64(SrpEncoding.toModulusBytes(ctx, A)),
|
|
519
|
-
M1: SecurityUtils.toBase64(
|
|
520
|
-
|
|
585
|
+
M1: SecurityUtils.toBase64(M1),
|
|
586
|
+
SessionKeyK: sessionKeyK
|
|
521
587
|
};
|
|
522
588
|
}
|
|
523
589
|
/**
|
|
@@ -529,15 +595,12 @@ var SrpClientService = class {
|
|
|
529
595
|
* @param ctx - SRP context.
|
|
530
596
|
* @returns True if the server proof is valid.
|
|
531
597
|
*/
|
|
532
|
-
async verifyServerM2(A_b64, M1_b64,
|
|
598
|
+
async verifyServerM2(A_b64, M1_b64, sessionKeyK, serverM2_b64, ctx) {
|
|
533
599
|
const A = SecurityUtils.bytesToBigInt(SecurityUtils.fromBase64(A_b64));
|
|
534
|
-
const M1 = SecurityUtils.
|
|
535
|
-
const S = SecurityUtils.bytesToBigInt(SecurityUtils.fromBase64(S_b64));
|
|
536
|
-
const sessionKeyK = await SrpEncoding.computeSessionKey(ctx, S);
|
|
600
|
+
const M1 = SecurityUtils.fromBase64(M1_b64);
|
|
537
601
|
const computedM2 = await SrpEncoding.computeM2(ctx, A, M1, sessionKeyK);
|
|
538
|
-
const computedM2Bytes = SrpEncoding.toHashBytes(ctx, computedM2);
|
|
539
602
|
const serverM2Bytes = SecurityUtils.fromBase64(serverM2_b64);
|
|
540
|
-
return SecurityUtils.fixedTimeEquals(
|
|
603
|
+
return SecurityUtils.fixedTimeEquals(computedM2, serverM2Bytes);
|
|
541
604
|
}
|
|
542
605
|
};
|
|
543
606
|
|
|
@@ -550,17 +613,23 @@ var SrpServerService = class {
|
|
|
550
613
|
* @param ctx - SRP context (hash, N, g, etc.).
|
|
551
614
|
* @returns Session state with private b, verifier, and public B.
|
|
552
615
|
*/
|
|
553
|
-
async getSrpChallenge(login, verifierBytes, ctx) {
|
|
616
|
+
async getSrpChallenge(login, verifierBytes, salt, ctx) {
|
|
554
617
|
const v = SecurityUtils.bytesToBigInt(verifierBytes);
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
618
|
+
const privateKeySize = Math.max(32, Math.floor(ctx.modulusSize / 2));
|
|
619
|
+
let bBytes;
|
|
620
|
+
let B;
|
|
621
|
+
do {
|
|
622
|
+
bBytes = crypto.getRandomValues(new Uint8Array(privateKeySize));
|
|
623
|
+
const b = SecurityUtils.bytesToBigInt(bBytes);
|
|
624
|
+
const gB = SecurityUtils.expMod(ctx.g, b, ctx.N);
|
|
625
|
+
B = (ctx.k * v + gB) % ctx.N;
|
|
626
|
+
} while (B === 0n);
|
|
559
627
|
return {
|
|
560
628
|
login,
|
|
561
|
-
privateKeyB:
|
|
562
|
-
verifier:
|
|
563
|
-
publicKeyB:
|
|
629
|
+
privateKeyB: bBytes,
|
|
630
|
+
verifier: verifierBytes,
|
|
631
|
+
publicKeyB: SrpEncoding.toModulusBytes(ctx, B),
|
|
632
|
+
salt
|
|
564
633
|
};
|
|
565
634
|
}
|
|
566
635
|
/**
|
|
@@ -574,10 +643,10 @@ var SrpServerService = class {
|
|
|
574
643
|
*/
|
|
575
644
|
async verifySrpProof(sessionState, a, m1, ctx) {
|
|
576
645
|
const A = SecurityUtils.bytesToBigInt(SecurityUtils.fromBase64(a));
|
|
577
|
-
const M1_client = SecurityUtils.
|
|
578
|
-
const b = SecurityUtils.bytesToBigInt(
|
|
579
|
-
const v = SecurityUtils.bytesToBigInt(
|
|
580
|
-
const B = SecurityUtils.bytesToBigInt(
|
|
646
|
+
const M1_client = SecurityUtils.fromBase64(m1);
|
|
647
|
+
const b = SecurityUtils.bytesToBigInt(sessionState.privateKeyB);
|
|
648
|
+
const v = SecurityUtils.bytesToBigInt(sessionState.verifier);
|
|
649
|
+
const B = SecurityUtils.bytesToBigInt(sessionState.publicKeyB);
|
|
581
650
|
if (v <= 0n)
|
|
582
651
|
throw new Error("The verifier is corrupted");
|
|
583
652
|
if (A % ctx.N === 0n)
|
|
@@ -590,13 +659,11 @@ var SrpServerService = class {
|
|
|
590
659
|
const vU = SecurityUtils.expMod(v, u, ctx.N);
|
|
591
660
|
const S = SecurityUtils.expMod(A * vU % ctx.N, b, ctx.N);
|
|
592
661
|
const sessionKeyK = await SrpEncoding.computeSessionKey(ctx, S);
|
|
593
|
-
const M1_server = await SrpEncoding.computeM1(ctx, A, B, sessionKeyK);
|
|
594
|
-
|
|
595
|
-
const m1ClientBytes = SrpEncoding.toHashBytes(ctx, M1_client);
|
|
596
|
-
if (!SecurityUtils.fixedTimeEquals(m1ServerBytes, m1ClientBytes))
|
|
662
|
+
const M1_server = await SrpEncoding.computeM1(ctx, A, B, sessionKeyK, sessionState.login, sessionState.salt);
|
|
663
|
+
if (!SecurityUtils.fixedTimeEquals(M1_server, M1_client))
|
|
597
664
|
throw new Error("Invalid password");
|
|
598
665
|
const M2_server = await SrpEncoding.computeM2(ctx, A, M1_client, sessionKeyK);
|
|
599
|
-
return SecurityUtils.toBase64(
|
|
666
|
+
return SecurityUtils.toBase64(M2_server);
|
|
600
667
|
}
|
|
601
668
|
};
|
|
602
669
|
|