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