@crossdyne/security 0.2.0-beta.1 → 0.3.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 +603 -109
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +362 -28
- package/dist/index.d.ts +362 -28
- package/dist/index.js +590 -109
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,84 +1,152 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// src/configurations/security-constants.ts
|
|
4
|
+
var SecurityConstants = {
|
|
5
|
+
AesGcmNonceSize: 12,
|
|
6
|
+
AesGcmTagSize: 16,
|
|
7
|
+
AesGcmTagSizeMin: 12,
|
|
8
|
+
AesGcmTagSizeMax: 16,
|
|
9
|
+
KeySizeBytes: 32,
|
|
10
|
+
// 256 bits
|
|
11
|
+
Pbkdf2IterationsDefault: 6e5,
|
|
12
|
+
Pbkdf2IterationsMinimum: 1e5
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// src/configurations/supported-hash-algorithms.ts
|
|
16
|
+
var SupportedHashAlgorithms = ["SHA-256", "SHA-384", "SHA-512"];
|
|
17
|
+
|
|
3
18
|
// src/utils/security.utils.ts
|
|
4
19
|
var SecurityUtils = class {
|
|
5
|
-
|
|
6
|
-
Base64 helpers
|
|
7
|
-
*/
|
|
20
|
+
/** Encodes Uint8Array to standard Base64. */
|
|
8
21
|
static toBase64(bytes) {
|
|
9
22
|
let binary = "";
|
|
10
|
-
for (let i = 0; i < bytes.
|
|
23
|
+
for (let i = 0; i < bytes.length; i++)
|
|
11
24
|
binary += String.fromCharCode(bytes[i]);
|
|
12
|
-
return
|
|
25
|
+
return btoa(binary);
|
|
13
26
|
}
|
|
27
|
+
/** Decodes URL-safe or standard Base64 to Uint8Array. */
|
|
14
28
|
static fromBase64(base64) {
|
|
15
|
-
|
|
16
|
-
const
|
|
29
|
+
let cleaned = base64.replace(/-/g, "+").replace(/_/g, "/").replace(/\s/g, "");
|
|
30
|
+
const mod = cleaned.length % 4;
|
|
31
|
+
if (mod !== 0) {
|
|
32
|
+
cleaned += "=".repeat(4 - mod);
|
|
33
|
+
}
|
|
34
|
+
const binary = atob(cleaned);
|
|
17
35
|
const bytes = new Uint8Array(binary.length);
|
|
18
36
|
for (let i = 0; i < binary.length; i++)
|
|
19
37
|
bytes[i] = binary.charCodeAt(i);
|
|
20
38
|
return bytes;
|
|
21
39
|
}
|
|
40
|
+
/** Converts big-endian bytes to bigint. */
|
|
41
|
+
static bytesToBigInt(bytes) {
|
|
42
|
+
if (bytes.length === 0)
|
|
43
|
+
return 0n;
|
|
44
|
+
return BigInt("0x" + Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join(""));
|
|
45
|
+
}
|
|
46
|
+
/** Converts bigint to fixed-length big-endian bytes (pads/truncates). */
|
|
22
47
|
static bigIntToFixedBytes(bn, length) {
|
|
23
48
|
let hex = bn.toString(16);
|
|
24
49
|
if (hex.length % 2 !== 0)
|
|
25
50
|
hex = "0" + hex;
|
|
26
51
|
if (hex.length > length * 2)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
52
|
+
hex = hex.slice(hex.length - length * 2);
|
|
53
|
+
else
|
|
54
|
+
hex = hex.padStart(length * 2, "0");
|
|
55
|
+
return new Uint8Array(hex.match(/.{1,2}/g)?.map((b) => parseInt(b, 16)) || []);
|
|
30
56
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
57
|
+
/** Constant-time comparison of two Uint8Arrays. */
|
|
58
|
+
static fixedTimeEquals(a, b) {
|
|
59
|
+
if (a.length !== b.length)
|
|
60
|
+
return false;
|
|
61
|
+
let diff = 0;
|
|
62
|
+
for (let i = 0; i < a.length; i++)
|
|
63
|
+
diff |= a[i] ^ b[i];
|
|
64
|
+
return diff === 0;
|
|
34
65
|
}
|
|
35
|
-
|
|
36
|
-
Math
|
|
37
|
-
*/
|
|
66
|
+
/** Modular exponentiation (base^exp mod mod) using binary exponentiation. */
|
|
38
67
|
static expMod(base, exp, mod) {
|
|
39
68
|
let res = BigInt(1);
|
|
40
69
|
base = base % mod;
|
|
41
|
-
while (exp >
|
|
42
|
-
if (exp %
|
|
70
|
+
while (exp > 0n) {
|
|
71
|
+
if (exp % 2n === 1n)
|
|
72
|
+
res = res * base % mod;
|
|
43
73
|
base = base * base % mod;
|
|
44
|
-
exp = exp /
|
|
74
|
+
exp = exp / 2n;
|
|
45
75
|
}
|
|
46
76
|
return res;
|
|
47
77
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// src/crypto/aes-gcm-options.ts
|
|
81
|
+
var AesGcmOptions = class _AesGcmOptions {
|
|
82
|
+
constructor() {
|
|
83
|
+
this._nonceSize = SecurityConstants.AesGcmNonceSize;
|
|
84
|
+
this._tagSize = SecurityConstants.AesGcmTagSize;
|
|
85
|
+
}
|
|
86
|
+
/** Nonce size in bytes (must be 12). */
|
|
87
|
+
get nonceSize() {
|
|
88
|
+
return this._nonceSize;
|
|
89
|
+
}
|
|
90
|
+
set nonceSize(v) {
|
|
91
|
+
if (v !== SecurityConstants.AesGcmNonceSize) throw new RangeError(`AES-GCM requires ${SecurityConstants.AesGcmNonceSize}-byte nonce`);
|
|
92
|
+
this._nonceSize = v;
|
|
93
|
+
}
|
|
94
|
+
/** Tag size in bytes (12–16, default 16). */
|
|
95
|
+
get tagSize() {
|
|
96
|
+
return this._tagSize;
|
|
97
|
+
}
|
|
98
|
+
set tagSize(v) {
|
|
99
|
+
if (v < SecurityConstants.AesGcmTagSizeMin || v > SecurityConstants.AesGcmTagSizeMax) throw new RangeError(`Tag size must be between ${SecurityConstants.AesGcmTagSizeMin} and ${SecurityConstants.AesGcmTagSizeMax}`);
|
|
100
|
+
this._tagSize = v;
|
|
101
|
+
}
|
|
102
|
+
/** Validates that {@link tagSize} is in the allowed range. */
|
|
103
|
+
validate() {
|
|
104
|
+
if (this.tagSize < SecurityConstants.AesGcmTagSizeMin || this.tagSize > SecurityConstants.AesGcmTagSizeMax) throw new Error(`Invalid Tag Size: ${this.tagSize}`);
|
|
105
|
+
}
|
|
106
|
+
/** Default preset: nonce=12, tag=16, no AAD. */
|
|
107
|
+
static get default() {
|
|
108
|
+
return new _AesGcmOptions();
|
|
109
|
+
}
|
|
110
|
+
/** Fluent setter for {@link tagSize}. */
|
|
111
|
+
withTagSize(s) {
|
|
112
|
+
this.tagSize = s;
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Fluent setter for {@link associatedData}.
|
|
117
|
+
* Accepts a byte array or a UTF-8 string (encoded internally).
|
|
118
|
+
*/
|
|
119
|
+
withAssociatedData(aad) {
|
|
120
|
+
this.associatedData = typeof aad === "string" ? new TextEncoder().encode(aad) : aad;
|
|
121
|
+
return this;
|
|
122
|
+
}
|
|
123
|
+
/** Validates and returns this instance. */
|
|
124
|
+
build() {
|
|
125
|
+
this.validate();
|
|
126
|
+
return this;
|
|
62
127
|
}
|
|
63
128
|
};
|
|
64
|
-
SecurityUtils.MODULUS_SIZE = 384;
|
|
65
|
-
SecurityUtils.NONCE_SIZE = 12;
|
|
66
|
-
SecurityUtils.TAG_SIZE = 16;
|
|
67
|
-
SecurityUtils.KEY_SIZE = 32;
|
|
68
|
-
SecurityUtils.N = BigInt("0xAC6BDB41324A9A9BF166DE5E1F403D434A6E1B3B94A7E62AC1211858E002C75AD4455C9D19C0A3180296917A376205164043E20144FF485719D181A99EB574671AC58054457ED444A67032EA17D03AD43464D2397449CA593630A670D90D95A78E846A3C8AF80862098D80F33C42ED7059E75225E0A52718E2379369F65B79680A6560B080092EE71986066735A96A7D42E7597116742B02D3A154471B6A23D84E0D642C790D597A2BB7F5A48F734898BDD138C69493E723491959C1B4BD40C91C1C7924F88D046467A006507E781220A80C55A927906A7C6C9C227E674686DD5D1B855D28F0D604E24586C608630B9A34C4808381A54F0D9080A5F90B60187F");
|
|
69
|
-
SecurityUtils.g = BigInt(2);
|
|
70
|
-
SecurityUtils.k = BigInt("0xD55AE1AEC9F9115621E93E16E5DE4517DF8450D0957024D1256AA32B71E4E412");
|
|
71
|
-
/*
|
|
72
|
-
BigInt helpers
|
|
73
|
-
*/
|
|
74
|
-
SecurityUtils.bytesToBigInt = (bytes) => BigInt("0x" + Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join(""));
|
|
75
129
|
|
|
76
130
|
// src/crypto/crypto.service.ts
|
|
77
131
|
var CryptoService = class {
|
|
78
132
|
constructor() {
|
|
133
|
+
/**
|
|
134
|
+
* Generates cryptographically secure random bytes.
|
|
135
|
+
* @param length - Number of bytes (default 32).
|
|
136
|
+
* @returns Uint8Array of random bytes.
|
|
137
|
+
*/
|
|
79
138
|
this.generateRandomBytes = (length = 32) => crypto.getRandomValues(new Uint8Array(length));
|
|
80
139
|
}
|
|
81
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Encrypts a serializable object to a Base64 string.
|
|
142
|
+
* @param dataModel - Object or Uint8Array to encrypt.
|
|
143
|
+
* @param key - AES-256 key (32 bytes).
|
|
144
|
+
* @param options - AES-GCM configuration; uses default if omitted.
|
|
145
|
+
* @returns Base64-encoded ciphertext with prepended nonce.
|
|
146
|
+
*/
|
|
147
|
+
async encryptData(dataModel, key, options) {
|
|
148
|
+
const opts = options ?? AesGcmOptions.default;
|
|
149
|
+
opts.validate();
|
|
82
150
|
const encoder = new TextEncoder();
|
|
83
151
|
let jsonString;
|
|
84
152
|
if (dataModel instanceof Uint8Array) {
|
|
@@ -87,7 +155,7 @@ var CryptoService = class {
|
|
|
87
155
|
jsonString = JSON.stringify(dataModel);
|
|
88
156
|
}
|
|
89
157
|
const plainBytes = encoder.encode(jsonString);
|
|
90
|
-
const nonce = crypto.getRandomValues(new Uint8Array(
|
|
158
|
+
const nonce = crypto.getRandomValues(new Uint8Array(opts.nonceSize));
|
|
91
159
|
const cryptoKey = await crypto.subtle.importKey(
|
|
92
160
|
"raw",
|
|
93
161
|
key,
|
|
@@ -95,28 +163,43 @@ var CryptoService = class {
|
|
|
95
163
|
false,
|
|
96
164
|
["encrypt"]
|
|
97
165
|
);
|
|
166
|
+
let associatedData = new Uint8Array(0);
|
|
167
|
+
if (opts.associatedData != null) {
|
|
168
|
+
associatedData = opts.associatedData;
|
|
169
|
+
}
|
|
98
170
|
const encryptedContent = await crypto.subtle.encrypt(
|
|
99
171
|
{
|
|
100
172
|
name: "AES-GCM",
|
|
101
173
|
iv: nonce,
|
|
102
|
-
tagLength:
|
|
174
|
+
tagLength: opts.tagSize * 8,
|
|
175
|
+
additionalData: associatedData
|
|
103
176
|
},
|
|
104
177
|
cryptoKey,
|
|
105
178
|
plainBytes
|
|
106
179
|
);
|
|
107
|
-
const result = new Uint8Array(
|
|
180
|
+
const result = new Uint8Array(opts.nonceSize + encryptedContent.byteLength);
|
|
108
181
|
result.set(nonce, 0);
|
|
109
|
-
result.set(new Uint8Array(encryptedContent),
|
|
182
|
+
result.set(new Uint8Array(encryptedContent), opts.nonceSize);
|
|
110
183
|
return SecurityUtils.toBase64(result);
|
|
111
184
|
}
|
|
112
|
-
|
|
185
|
+
/**
|
|
186
|
+
* Decrypts a Base64-encoded ciphertext back to the original object.
|
|
187
|
+
* @param encryptedBase64 - The encrypted data.
|
|
188
|
+
* @param key - AES-256 key (32 bytes).
|
|
189
|
+
* @param options - AES-GCM configuration; uses default if omitted.
|
|
190
|
+
* @returns Deserialized object, or null if input is empty.
|
|
191
|
+
* @throws If authentication tag mismatch or corrupted data.
|
|
192
|
+
*/
|
|
193
|
+
async decryptData(encryptedBase64, key, options) {
|
|
113
194
|
if (!encryptedBase64)
|
|
114
195
|
return null;
|
|
196
|
+
const opts = options ?? AesGcmOptions.default;
|
|
197
|
+
opts.validate();
|
|
115
198
|
const encryptedBytes = SecurityUtils.fromBase64(encryptedBase64);
|
|
116
|
-
if (encryptedBytes.length <
|
|
117
|
-
throw new Error(
|
|
118
|
-
const nonce = encryptedBytes.slice(0,
|
|
119
|
-
const ciphertextWithTag = encryptedBytes.slice(
|
|
199
|
+
if (encryptedBytes.length < opts.nonceSize + opts.tagSize)
|
|
200
|
+
throw new Error(`Invalid format: minimum expected ${opts.nonceSize + opts.tagSize} byte.`);
|
|
201
|
+
const nonce = encryptedBytes.slice(0, opts.nonceSize);
|
|
202
|
+
const ciphertextWithTag = encryptedBytes.slice(opts.nonceSize);
|
|
120
203
|
const cryptoKey = await crypto.subtle.importKey(
|
|
121
204
|
"raw",
|
|
122
205
|
key,
|
|
@@ -125,11 +208,16 @@ var CryptoService = class {
|
|
|
125
208
|
["decrypt"]
|
|
126
209
|
);
|
|
127
210
|
try {
|
|
211
|
+
let associatedData = new Uint8Array(0);
|
|
212
|
+
if (opts.associatedData != null) {
|
|
213
|
+
associatedData = opts.associatedData;
|
|
214
|
+
}
|
|
128
215
|
const decryptedBuffer = await crypto.subtle.decrypt(
|
|
129
216
|
{
|
|
130
217
|
name: "AES-GCM",
|
|
131
218
|
iv: nonce,
|
|
132
|
-
tagLength:
|
|
219
|
+
tagLength: opts.tagSize * 8,
|
|
220
|
+
additionalData: associatedData
|
|
133
221
|
},
|
|
134
222
|
cryptoKey,
|
|
135
223
|
ciphertextWithTag
|
|
@@ -138,112 +226,518 @@ var CryptoService = class {
|
|
|
138
226
|
const jsonString = decoder.decode(decryptedBuffer);
|
|
139
227
|
return JSON.parse(jsonString);
|
|
140
228
|
} catch (e) {
|
|
141
|
-
console.error("
|
|
142
|
-
|
|
229
|
+
console.error("Decryption error:", e);
|
|
230
|
+
throw new Error("Decryption failed: authentication tag mismatch or corrupted data.");
|
|
143
231
|
}
|
|
144
232
|
}
|
|
145
233
|
};
|
|
146
234
|
|
|
147
|
-
// src/crypto/
|
|
148
|
-
var
|
|
235
|
+
// src/crypto/kdf-options.ts
|
|
236
|
+
var KdfOptions = class _KdfOptions {
|
|
149
237
|
constructor() {
|
|
150
|
-
this.
|
|
151
|
-
this.
|
|
238
|
+
this._pbkdf2Iterations = SecurityConstants.Pbkdf2IterationsDefault;
|
|
239
|
+
this._hashAlgorithm = "SHA-256";
|
|
240
|
+
}
|
|
241
|
+
/** PBKDF2 iteration count (minimum 100_000). */
|
|
242
|
+
get pbkdf2Iterations() {
|
|
243
|
+
return this._pbkdf2Iterations;
|
|
244
|
+
}
|
|
245
|
+
set pbkdf2Iterations(v) {
|
|
246
|
+
if (v < SecurityConstants.Pbkdf2IterationsMinimum)
|
|
247
|
+
throw new RangeError(`PBKDF2 iterations must be \u2265 ${SecurityConstants.Pbkdf2IterationsMinimum}`);
|
|
248
|
+
this._pbkdf2Iterations = v;
|
|
249
|
+
}
|
|
250
|
+
/** Hash algorithm used by PBKDF2. Must be one of {@link SupportedHashAlgorithms}. */
|
|
251
|
+
get hashAlgorithm() {
|
|
252
|
+
return this._hashAlgorithm;
|
|
253
|
+
}
|
|
254
|
+
set hashAlgorithm(v) {
|
|
255
|
+
if (!SupportedHashAlgorithms.includes(v))
|
|
256
|
+
throw new RangeError(`Unsupported hash: ${v}`);
|
|
257
|
+
this._hashAlgorithm = v;
|
|
258
|
+
}
|
|
259
|
+
/** Validates iterations and hash algorithm. */
|
|
260
|
+
validate() {
|
|
261
|
+
if (this.pbkdf2Iterations < SecurityConstants.Pbkdf2IterationsMinimum)
|
|
262
|
+
throw new Error(`Pbkdf2Iterations (${this.pbkdf2Iterations}) below minimum`);
|
|
263
|
+
if (!SupportedHashAlgorithms.includes(this.hashAlgorithm))
|
|
264
|
+
throw new Error(`Invalid hash algorithm: ${this.hashAlgorithm}`);
|
|
152
265
|
}
|
|
153
|
-
|
|
266
|
+
/** Default preset: SHA-256, 600_000 iterations. */
|
|
267
|
+
static get default() {
|
|
268
|
+
return new _KdfOptions();
|
|
269
|
+
}
|
|
270
|
+
/** Fluent setter for {@link pbkdf2Iterations}. */
|
|
271
|
+
withPbkdf2Iterations(i) {
|
|
272
|
+
this.pbkdf2Iterations = i;
|
|
273
|
+
return this;
|
|
274
|
+
}
|
|
275
|
+
/** Fluent setter for {@link hashAlgorithm}. */
|
|
276
|
+
withHashAlgorithm(h) {
|
|
277
|
+
this.hashAlgorithm = h;
|
|
278
|
+
return this;
|
|
279
|
+
}
|
|
280
|
+
/** Validates and returns this instance. */
|
|
281
|
+
build() {
|
|
282
|
+
this.validate();
|
|
283
|
+
return this;
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
// src/crypto/key-derivation.service.ts
|
|
288
|
+
var KeyDerivationService = class {
|
|
289
|
+
/**
|
|
290
|
+
* Derives KEK and Base64 AuthHash. Identity is normalized (trimmed, lowercase).
|
|
291
|
+
* @param identity - User identity (email, username).
|
|
292
|
+
* @param password - User password.
|
|
293
|
+
* @param salt - Random salt.
|
|
294
|
+
* @param options - KDF configuration; uses default if omitted.
|
|
295
|
+
* @returns Object with `kek` (Uint8Array) and `authHash` (Base64 string).
|
|
296
|
+
*/
|
|
297
|
+
async deriveKeysFromPassword(identity, password, salt, options) {
|
|
298
|
+
const opts = options ?? KdfOptions.default;
|
|
299
|
+
opts.validate();
|
|
154
300
|
const safeSalt = new Uint8Array(salt);
|
|
155
301
|
const encoder = new TextEncoder();
|
|
156
|
-
const normalizedLogin =
|
|
302
|
+
const normalizedLogin = identity.trim().toLowerCase();
|
|
157
303
|
const combinedPassword = `${normalizedLogin}:${password}`;
|
|
158
304
|
const passwordBytes = encoder.encode(combinedPassword);
|
|
159
305
|
const baseKey = await crypto.subtle.importKey("raw", passwordBytes, "PBKDF2", false, ["deriveBits", "deriveKey"]);
|
|
160
306
|
const masterKeyBits = await crypto.subtle.deriveBits({
|
|
161
307
|
name: "PBKDF2",
|
|
162
308
|
salt: safeSalt,
|
|
163
|
-
iterations:
|
|
164
|
-
hash:
|
|
165
|
-
}, baseKey,
|
|
309
|
+
iterations: opts.pbkdf2Iterations,
|
|
310
|
+
hash: opts.hashAlgorithm
|
|
311
|
+
}, baseKey, SecurityConstants.KeySizeBytes * 8);
|
|
166
312
|
const masterKey = await crypto.subtle.importKey("raw", masterKeyBits, "HKDF", false, ["deriveBits"]);
|
|
167
313
|
const kek = await crypto.subtle.deriveBits(
|
|
168
|
-
{ name: "HKDF", hash:
|
|
314
|
+
{ name: "HKDF", hash: opts.hashAlgorithm, salt: new Uint8Array(0), info: encoder.encode("AES-GCM-KEK-v1") },
|
|
169
315
|
masterKey,
|
|
170
|
-
|
|
316
|
+
SecurityConstants.KeySizeBytes * 8
|
|
171
317
|
);
|
|
172
318
|
const authBytes = await crypto.subtle.deriveBits(
|
|
173
|
-
{ name: "HKDF", hash:
|
|
319
|
+
{ name: "HKDF", hash: opts.hashAlgorithm, salt: new Uint8Array(0), info: encoder.encode("SERVER-AUTH-HASH-v1") },
|
|
174
320
|
masterKey,
|
|
175
|
-
|
|
321
|
+
SecurityConstants.KeySizeBytes * 8
|
|
176
322
|
);
|
|
177
323
|
return {
|
|
178
324
|
kek: new Uint8Array(kek),
|
|
179
325
|
authHash: SecurityUtils.toBase64(new Uint8Array(authBytes))
|
|
180
326
|
};
|
|
181
327
|
}
|
|
328
|
+
/**
|
|
329
|
+
* Derives an SRP-compatible authentication hash (output size = hash output length).
|
|
330
|
+
* @param identity - User identity.
|
|
331
|
+
* @param password - User password.
|
|
332
|
+
* @param salt - Random salt.
|
|
333
|
+
* @param srpHashAlgorithm - SRP hash algorithm (SHA-256/384/512).
|
|
334
|
+
* @param options - KDF configuration; uses default if omitted.
|
|
335
|
+
* @returns Raw hash bytes for use as SRP verifier input (x).
|
|
336
|
+
*/
|
|
337
|
+
async deriveAuthHashForSrp(identity, password, salt, srpHashAlgorithm, options) {
|
|
338
|
+
const opts = options ?? KdfOptions.default;
|
|
339
|
+
opts.validate();
|
|
340
|
+
const srpHashSize = srpHashAlgorithm === "SHA-256" ? 32 : srpHashAlgorithm === "SHA-384" ? 48 : 64;
|
|
341
|
+
const normalizedLogin = identity.trim().toLowerCase();
|
|
342
|
+
const combinedPassword = `${normalizedLogin}:${password}`;
|
|
343
|
+
const passwordBytes = new TextEncoder().encode(combinedPassword);
|
|
344
|
+
const baseKey = await crypto.subtle.importKey(
|
|
345
|
+
"raw",
|
|
346
|
+
passwordBytes,
|
|
347
|
+
"PBKDF2",
|
|
348
|
+
false,
|
|
349
|
+
["deriveBits"]
|
|
350
|
+
);
|
|
351
|
+
const masterKeyBits = await crypto.subtle.deriveBits({
|
|
352
|
+
name: "PBKDF2",
|
|
353
|
+
salt,
|
|
354
|
+
iterations: opts.pbkdf2Iterations,
|
|
355
|
+
hash: srpHashAlgorithm
|
|
356
|
+
}, baseKey, SecurityConstants.KeySizeBytes * 8);
|
|
357
|
+
const masterKey = await crypto.subtle.importKey(
|
|
358
|
+
"raw",
|
|
359
|
+
masterKeyBits,
|
|
360
|
+
"HKDF",
|
|
361
|
+
false,
|
|
362
|
+
["deriveBits"]
|
|
363
|
+
);
|
|
364
|
+
const info = new TextEncoder().encode("SRP-AUTH-HASH-v1");
|
|
365
|
+
const authBytes = await crypto.subtle.deriveBits(
|
|
366
|
+
{
|
|
367
|
+
name: "HKDF",
|
|
368
|
+
hash: srpHashAlgorithm,
|
|
369
|
+
salt: new Uint8Array(0),
|
|
370
|
+
info
|
|
371
|
+
},
|
|
372
|
+
masterKey,
|
|
373
|
+
srpHashSize * 8
|
|
374
|
+
);
|
|
375
|
+
return new Uint8Array(authBytes);
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
// src/crypto/crypto-version.ts
|
|
380
|
+
var CryptoVersion = /* @__PURE__ */ ((CryptoVersion2) => {
|
|
381
|
+
CryptoVersion2[CryptoVersion2["V1"] = 1] = "V1";
|
|
382
|
+
return CryptoVersion2;
|
|
383
|
+
})(CryptoVersion || {});
|
|
384
|
+
|
|
385
|
+
// src/crypto/crypto-profile.ts
|
|
386
|
+
var CryptoProfile = class {
|
|
387
|
+
/**
|
|
388
|
+
* Creates a new CryptoProfile.
|
|
389
|
+
* @param params - Object containing version, kdfOptions, aesGcmOptions.
|
|
390
|
+
*/
|
|
391
|
+
constructor(params) {
|
|
392
|
+
this.version = params.version;
|
|
393
|
+
this.kdfOptions = params.kdfOptions;
|
|
394
|
+
this.aesGcmOptions = params.aesGcmOptions;
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
// src/crypto/crypto-profile-registry.ts
|
|
399
|
+
var CryptoProfileRegistry = class {
|
|
400
|
+
static getProfile(version) {
|
|
401
|
+
switch (version) {
|
|
402
|
+
case 1 /* V1 */:
|
|
403
|
+
return new CryptoProfile({
|
|
404
|
+
version: 1 /* V1 */,
|
|
405
|
+
kdfOptions: KdfOptions.default,
|
|
406
|
+
aesGcmOptions: AesGcmOptions.default
|
|
407
|
+
});
|
|
408
|
+
default:
|
|
409
|
+
throw new Error(`Unsupported crypto version: ${version}`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
/** Latest supported profile (currently V1). */
|
|
413
|
+
static get latest() {
|
|
414
|
+
return this.getProfile(1 /* V1 */);
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
// src/utils/srp-encoding.ts
|
|
419
|
+
var SrpEncoding = class {
|
|
420
|
+
/** Serializes a bigint to modulus-sized big-endian bytes. */
|
|
421
|
+
static toModulusBytes(ctx, value) {
|
|
422
|
+
return SecurityUtils.bigIntToFixedBytes(value, ctx.modulusSize);
|
|
423
|
+
}
|
|
424
|
+
/** Serializes a bigint to hash-sized big-endian bytes. */
|
|
425
|
+
static toHashBytes(ctx, value) {
|
|
426
|
+
return SecurityUtils.bigIntToFixedBytes(value, ctx.hashSize);
|
|
427
|
+
}
|
|
428
|
+
/** Hashes modulus-sized values (e.g., u = H(A, B)). */
|
|
429
|
+
static async hashModuli(ctx, ...values) {
|
|
430
|
+
const buffers = values.map((v) => this.toModulusBytes(ctx, v));
|
|
431
|
+
return this.hash(ctx.hashAlgorithmName, ...buffers);
|
|
432
|
+
}
|
|
433
|
+
/** Computes M1 = H(A || B || sessionKeyK). */
|
|
434
|
+
static async computeM1(ctx, A, B, sessionKeyK) {
|
|
435
|
+
return this.hash(ctx.hashAlgorithmName, this.toModulusBytes(ctx, A), this.toModulusBytes(ctx, B), sessionKeyK);
|
|
436
|
+
}
|
|
437
|
+
/** Computes M2 = H(A || M1 || sessionKeyK). */
|
|
438
|
+
static async computeM2(ctx, A, M1, sessionKeyK) {
|
|
439
|
+
return this.hash(ctx.hashAlgorithmName, this.toModulusBytes(ctx, A), this.toHashBytes(ctx, M1), sessionKeyK);
|
|
440
|
+
}
|
|
441
|
+
/** Computes session key K = H(S). */
|
|
442
|
+
static async computeSessionKey(ctx, S) {
|
|
443
|
+
const sBytes = this.toModulusBytes(ctx, S);
|
|
444
|
+
const hashBuffer = await crypto.subtle.digest(
|
|
445
|
+
ctx.hashAlgorithmName,
|
|
446
|
+
sBytes
|
|
447
|
+
);
|
|
448
|
+
return new Uint8Array(hashBuffer);
|
|
449
|
+
}
|
|
450
|
+
/** Concatenates byte arrays and returns the hash as bigint. */
|
|
451
|
+
static async hash(algo, ...buffers) {
|
|
452
|
+
const totalLen = buffers.reduce((sum, b) => sum + b.length, 0);
|
|
453
|
+
const combined = new Uint8Array(totalLen);
|
|
454
|
+
let offset = 0;
|
|
455
|
+
for (const buf of buffers) {
|
|
456
|
+
combined.set(buf, offset);
|
|
457
|
+
offset += buf.length;
|
|
458
|
+
}
|
|
459
|
+
const hashBuffer = await crypto.subtle.digest(algo, combined);
|
|
460
|
+
return SecurityUtils.bytesToBigInt(new Uint8Array(hashBuffer));
|
|
461
|
+
}
|
|
182
462
|
};
|
|
183
463
|
|
|
184
|
-
// src/srp/srp
|
|
185
|
-
var
|
|
464
|
+
// src/srp/srp-client-service.ts
|
|
465
|
+
var SrpClientService = class {
|
|
186
466
|
constructor() {
|
|
187
467
|
this.keyDerivation = new KeyDerivationService();
|
|
188
468
|
}
|
|
189
|
-
|
|
469
|
+
/**
|
|
470
|
+
* Computes SRP verifier v = g^x mod N from the authentication hash.
|
|
471
|
+
* @param authHash - Auth hash (Base64).
|
|
472
|
+
* @param ctx - SRP context (N, g, hash algorithm, etc.).
|
|
473
|
+
* @returns Verifier as Base64 string.
|
|
474
|
+
*/
|
|
475
|
+
async generateSrpVerifier(authHash, ctx) {
|
|
190
476
|
const x = SecurityUtils.bytesToBigInt(SecurityUtils.fromBase64(authHash));
|
|
191
|
-
const v = SecurityUtils.expMod(
|
|
192
|
-
return SecurityUtils.toBase64(
|
|
477
|
+
const v = SecurityUtils.expMod(ctx.g, x, ctx.N);
|
|
478
|
+
return SecurityUtils.toBase64(SrpEncoding.toModulusBytes(ctx, v));
|
|
193
479
|
}
|
|
194
|
-
|
|
480
|
+
/**
|
|
481
|
+
* Generates client proof (A, M1, session key S) from server challenge.
|
|
482
|
+
* @param login - User login.
|
|
483
|
+
* @param password - Plaintext password.
|
|
484
|
+
* @param saltBase64 - Server salt (URL-safe Base64).
|
|
485
|
+
* @param B_base64 - Server public ephemeral B (URL-safe Base64).
|
|
486
|
+
* @param ctx - SRP context.
|
|
487
|
+
* @returns Object with A, M1, S as Base64 strings.
|
|
488
|
+
*/
|
|
489
|
+
async generateSrpProof(login, password, saltBase64, B_base64, ctx) {
|
|
195
490
|
const salt = SecurityUtils.fromBase64(saltBase64);
|
|
196
|
-
const
|
|
197
|
-
const x = SecurityUtils.bytesToBigInt(
|
|
491
|
+
const authHash = await this.keyDerivation.deriveAuthHashForSrp(login, password, salt, ctx.hashAlgorithmName);
|
|
492
|
+
const x = SecurityUtils.bytesToBigInt(authHash);
|
|
198
493
|
const aBytes = crypto.getRandomValues(new Uint8Array(32));
|
|
199
494
|
const a = SecurityUtils.bytesToBigInt(aBytes);
|
|
200
|
-
const A = SecurityUtils.expMod(
|
|
201
|
-
if (A %
|
|
202
|
-
throw new Error("
|
|
495
|
+
const A = SecurityUtils.expMod(ctx.g, a, ctx.N);
|
|
496
|
+
if (A % ctx.N === 0n)
|
|
497
|
+
throw new Error("Critical error: A % N === 0");
|
|
203
498
|
const B = SecurityUtils.bytesToBigInt(SecurityUtils.fromBase64(B_base64));
|
|
204
|
-
if (B %
|
|
205
|
-
throw new Error("
|
|
206
|
-
const u = await
|
|
207
|
-
{ value: A, length: SecurityUtils.MODULUS_SIZE },
|
|
208
|
-
{ value: B, length: SecurityUtils.MODULUS_SIZE }
|
|
209
|
-
]);
|
|
499
|
+
if (B % ctx.N === 0n)
|
|
500
|
+
throw new Error("Critical error: B % N === 0");
|
|
501
|
+
const u = await SrpEncoding.hashModuli(ctx, A, B);
|
|
210
502
|
if (u === 0n)
|
|
211
503
|
throw new Error("\u041D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 u");
|
|
212
|
-
const gX = SecurityUtils.expMod(
|
|
213
|
-
const term =
|
|
214
|
-
const base = (B - term +
|
|
504
|
+
const gX = SecurityUtils.expMod(ctx.g, x, ctx.N);
|
|
505
|
+
const term = ctx.k * gX % ctx.N;
|
|
506
|
+
const base = (B - term + ctx.N) % ctx.N;
|
|
215
507
|
const exponent = a + u * x;
|
|
216
|
-
const S = SecurityUtils.expMod(base, exponent,
|
|
508
|
+
const S = SecurityUtils.expMod(base, exponent, ctx.N);
|
|
217
509
|
if (S === 0n)
|
|
218
|
-
throw new Error("
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
{ value: B, length: SecurityUtils.MODULUS_SIZE },
|
|
222
|
-
{ value: S, length: SecurityUtils.MODULUS_SIZE }
|
|
223
|
-
]);
|
|
510
|
+
throw new Error("Critical error: S === 0");
|
|
511
|
+
const sessionKeyK = await SrpEncoding.computeSessionKey(ctx, S);
|
|
512
|
+
const M1 = await SrpEncoding.computeM1(ctx, A, B, sessionKeyK);
|
|
224
513
|
return {
|
|
225
|
-
A: SecurityUtils.toBase64(
|
|
226
|
-
M1: SecurityUtils.toBase64(
|
|
227
|
-
S: SecurityUtils.toBase64(
|
|
514
|
+
A: SecurityUtils.toBase64(SrpEncoding.toModulusBytes(ctx, A)),
|
|
515
|
+
M1: SecurityUtils.toBase64(SrpEncoding.toHashBytes(ctx, M1)),
|
|
516
|
+
S: SecurityUtils.toBase64(SrpEncoding.toModulusBytes(ctx, S))
|
|
228
517
|
};
|
|
229
518
|
}
|
|
230
|
-
|
|
519
|
+
/**
|
|
520
|
+
* Validates the server proof M2 to authenticate the server.
|
|
521
|
+
* @param A_b64 - Client public A (Base64).
|
|
522
|
+
* @param M1_b64 - Client proof M1 (Base64).
|
|
523
|
+
* @param S_b64 - Session key S (Base64).
|
|
524
|
+
* @param serverM2_b64 - Server proof M2 (Base64).
|
|
525
|
+
* @param ctx - SRP context.
|
|
526
|
+
* @returns True if the server proof is valid.
|
|
527
|
+
*/
|
|
528
|
+
async verifyServerM2(A_b64, M1_b64, S_b64, serverM2_b64, ctx) {
|
|
231
529
|
const A = SecurityUtils.bytesToBigInt(SecurityUtils.fromBase64(A_b64));
|
|
232
530
|
const M1 = SecurityUtils.bytesToBigInt(SecurityUtils.fromBase64(M1_b64));
|
|
233
531
|
const S = SecurityUtils.bytesToBigInt(SecurityUtils.fromBase64(S_b64));
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
532
|
+
const sessionKeyK = await SrpEncoding.computeSessionKey(ctx, S);
|
|
533
|
+
const computedM2 = await SrpEncoding.computeM2(ctx, A, M1, sessionKeyK);
|
|
534
|
+
const computedM2Bytes = SrpEncoding.toHashBytes(ctx, computedM2);
|
|
535
|
+
const serverM2Bytes = SecurityUtils.fromBase64(serverM2_b64);
|
|
536
|
+
return SecurityUtils.fixedTimeEquals(computedM2Bytes, serverM2Bytes);
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
// src/srp/srp-server-service.ts
|
|
541
|
+
var SrpServerService = class {
|
|
542
|
+
/**
|
|
543
|
+
* Generates server challenge B and session state from verifier.
|
|
544
|
+
* @param login - User login.
|
|
545
|
+
* @param verifierBytes - Stored verifier v as byte array.
|
|
546
|
+
* @param ctx - SRP context (hash, N, g, etc.).
|
|
547
|
+
* @returns Session state with private b, verifier, and public B.
|
|
548
|
+
*/
|
|
549
|
+
async getSrpChallenge(login, verifierBytes, ctx) {
|
|
550
|
+
const v = SecurityUtils.bytesToBigInt(verifierBytes);
|
|
551
|
+
const bBytes = crypto.getRandomValues(new Uint8Array(32));
|
|
552
|
+
const b = SecurityUtils.bytesToBigInt(bBytes);
|
|
553
|
+
const gB = SecurityUtils.expMod(ctx.g, b, ctx.N);
|
|
554
|
+
const B = (ctx.k * v + gB) % ctx.N;
|
|
555
|
+
return {
|
|
556
|
+
login,
|
|
557
|
+
privateKeyB: SecurityUtils.toBase64(bBytes),
|
|
558
|
+
verifier: SecurityUtils.toBase64(verifierBytes),
|
|
559
|
+
publicKeyB: SecurityUtils.toBase64(SrpEncoding.toModulusBytes(ctx, B))
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Verifies client M1 proof and returns server M2 proof.
|
|
564
|
+
* @param sessionState - Server session state.
|
|
565
|
+
* @param a - Client public A (Base64).
|
|
566
|
+
* @param m1 - Client proof M1 (Base64).
|
|
567
|
+
* @param ctx - SRP context.
|
|
568
|
+
* @returns Server proof M2 as Base64 string.
|
|
569
|
+
* @throws If verification fails or input is invalid.
|
|
570
|
+
*/
|
|
571
|
+
async verifySrpProof(sessionState, a, m1, ctx) {
|
|
572
|
+
const A = SecurityUtils.bytesToBigInt(SecurityUtils.fromBase64(a));
|
|
573
|
+
const M1_client = SecurityUtils.bytesToBigInt(SecurityUtils.fromBase64(m1));
|
|
574
|
+
const b = SecurityUtils.bytesToBigInt(SecurityUtils.fromBase64(sessionState.privateKeyB));
|
|
575
|
+
const v = SecurityUtils.bytesToBigInt(SecurityUtils.fromBase64(sessionState.verifier));
|
|
576
|
+
const B = SecurityUtils.bytesToBigInt(SecurityUtils.fromBase64(sessionState.publicKeyB));
|
|
577
|
+
if (v <= 0n)
|
|
578
|
+
throw new Error("The verifier is corrupted");
|
|
579
|
+
if (A % ctx.N === 0n)
|
|
580
|
+
throw new Error("Incorrect value of A");
|
|
581
|
+
if (A <= 0n || A >= ctx.N)
|
|
582
|
+
throw new Error("Invalid A (out of range)");
|
|
583
|
+
const u = await SrpEncoding.hashModuli(ctx, A, B);
|
|
584
|
+
if (u === 0n)
|
|
585
|
+
throw new Error("Error in calculating the parameter u");
|
|
586
|
+
const vU = SecurityUtils.expMod(v, u, ctx.N);
|
|
587
|
+
const S = SecurityUtils.expMod(A * vU % ctx.N, b, ctx.N);
|
|
588
|
+
const sessionKeyK = await SrpEncoding.computeSessionKey(ctx, S);
|
|
589
|
+
const M1_server = await SrpEncoding.computeM1(ctx, A, B, sessionKeyK);
|
|
590
|
+
const m1ServerBytes = SrpEncoding.toHashBytes(ctx, M1_server);
|
|
591
|
+
const m1ClientBytes = SrpEncoding.toHashBytes(ctx, M1_client);
|
|
592
|
+
if (!SecurityUtils.fixedTimeEquals(m1ServerBytes, m1ClientBytes))
|
|
593
|
+
throw new Error("Invalid password");
|
|
594
|
+
const M2_server = await SrpEncoding.computeM2(ctx, A, M1_client, sessionKeyK);
|
|
595
|
+
return SecurityUtils.toBase64(SrpEncoding.toHashBytes(ctx, M2_server));
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
// src/srp/srp-group.ts
|
|
600
|
+
var SrpGroup = /* @__PURE__ */ ((SrpGroup2) => {
|
|
601
|
+
SrpGroup2[SrpGroup2["Rfc5054_1024"] = 1] = "Rfc5054_1024";
|
|
602
|
+
SrpGroup2[SrpGroup2["Rfc5054_1536"] = 2] = "Rfc5054_1536";
|
|
603
|
+
SrpGroup2[SrpGroup2["Rfc5054_2048"] = 3] = "Rfc5054_2048";
|
|
604
|
+
SrpGroup2[SrpGroup2["Rfc5054_3072"] = 4] = "Rfc5054_3072";
|
|
605
|
+
SrpGroup2[SrpGroup2["Rfc5054_4096"] = 5] = "Rfc5054_4096";
|
|
606
|
+
SrpGroup2[SrpGroup2["Rfc5054_6144"] = 6] = "Rfc5054_6144";
|
|
607
|
+
SrpGroup2[SrpGroup2["Rfc5054_8192"] = 7] = "Rfc5054_8192";
|
|
608
|
+
SrpGroup2[SrpGroup2["Custom"] = 99] = "Custom";
|
|
609
|
+
return SrpGroup2;
|
|
610
|
+
})(SrpGroup || {});
|
|
611
|
+
|
|
612
|
+
// src/srp/srp-group-params.ts
|
|
613
|
+
var SrpGroupParams = class {
|
|
614
|
+
/**
|
|
615
|
+
* Returns the prime modulus N for the given SRP group.
|
|
616
|
+
* @param group - SRP group identifier.
|
|
617
|
+
* @returns N as a bigint.
|
|
618
|
+
*/
|
|
619
|
+
static getN(group) {
|
|
620
|
+
switch (group) {
|
|
621
|
+
case 1 /* Rfc5054_1024 */:
|
|
622
|
+
return BigInt("0xEEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C9C256576D674DF7496EA81D3383B4813D692C6E0E0D5D8E250B98BE48E495C1D6089DAD15DC7D7B46154D6B6CE8EF4AD69B15D4982559B297BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA9AFD5138FE8376435B9FC61D2FC0EB06E3");
|
|
623
|
+
case 2 /* Rfc5054_1536 */:
|
|
624
|
+
return BigInt("0x9DEF3CAFB939277AB1F12A8617A47BBBDBA51DF499AC4C80BEEEA9614B19CC4D5F4F5F556E27CBDE51C6A94BE4607A291558903BA0D0F84380B655BB9A22E8DCDF028A7CEC67F0D08134B1C8B97989149B609E0BE3BAB63D47548381DBC5B1FC764E3F4B53DD9DA1158BFD3E2B9C8CF56EDF019539349627DB2FD53D24B7C48665772E437D6C7F8CE442734AF7CCB7AE837C264AE3A9BEB87F8A2FE9B8B5292E5A021FFF5E91479E8CE7A28C2442C6F315180F93499A234DCF76E3FED135F9BB");
|
|
625
|
+
case 3 /* Rfc5054_2048 */:
|
|
626
|
+
return BigInt("0xAC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC3192943DB56050A37329CBB4A099ED8193E0757767A13DD52312AB4B03310DCD7F48A9DA04FD50E8083969EDB767B0CF6095179A163AB3661A05FBD5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF747359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A436C6481F1D2B9078717461A5B9D32E688F87748544523B524B0D57D5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6AF874E7303CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB694B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F9E4AFF73");
|
|
627
|
+
case 4 /* Rfc5054_3072 */:
|
|
628
|
+
return BigInt("0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF");
|
|
629
|
+
case 5 /* Rfc5054_4096 */:
|
|
630
|
+
return BigInt("0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF");
|
|
631
|
+
case 6 /* Rfc5054_6144 */:
|
|
632
|
+
return BigInt("0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF");
|
|
633
|
+
case 7 /* Rfc5054_8192 */:
|
|
634
|
+
return BigInt("0xFFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AACC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF");
|
|
635
|
+
default:
|
|
636
|
+
throw new Error("Unsupported group");
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Returns the generator g for the given SRP group.
|
|
641
|
+
* @param group - SRP group identifier.
|
|
642
|
+
* @returns g as a bigint (2, 5, or 19).
|
|
643
|
+
*/
|
|
644
|
+
static getG(group) {
|
|
645
|
+
switch (group) {
|
|
646
|
+
case 1 /* Rfc5054_1024 */:
|
|
647
|
+
return BigInt(2);
|
|
648
|
+
case 2 /* Rfc5054_1536 */:
|
|
649
|
+
return BigInt(2);
|
|
650
|
+
case 3 /* Rfc5054_2048 */:
|
|
651
|
+
return BigInt(2);
|
|
652
|
+
case 4 /* Rfc5054_3072 */:
|
|
653
|
+
return BigInt(5);
|
|
654
|
+
case 5 /* Rfc5054_4096 */:
|
|
655
|
+
return BigInt(5);
|
|
656
|
+
case 6 /* Rfc5054_6144 */:
|
|
657
|
+
return BigInt(5);
|
|
658
|
+
case 7 /* Rfc5054_8192 */:
|
|
659
|
+
return BigInt(19);
|
|
660
|
+
default:
|
|
661
|
+
throw new Error("Unsupported group");
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
// src/srp/srp-options.ts
|
|
667
|
+
var SrpOptions = class {
|
|
668
|
+
constructor() {
|
|
669
|
+
/** Diffie-Hellman group. Default Rfc5054_3072 (g=5). */
|
|
670
|
+
this.group = 4 /* Rfc5054_3072 */;
|
|
671
|
+
/** Hash algorithm for SRP computations. Default SHA-256. */
|
|
672
|
+
this.hashAlgorithmName = "SHA-256";
|
|
673
|
+
/** Salt size in bytes. Default 32. */
|
|
674
|
+
this.saltSize = 32;
|
|
675
|
+
}
|
|
676
|
+
/** Prime modulus N for the selected group. */
|
|
677
|
+
get N() {
|
|
678
|
+
return SrpGroupParams.getN(this.group);
|
|
679
|
+
}
|
|
680
|
+
/** Generator g for the selected group (2, 5, or 19). */
|
|
681
|
+
get g() {
|
|
682
|
+
return SrpGroupParams.getG(this.group);
|
|
683
|
+
}
|
|
684
|
+
/** Byte length of N (ceil(bitLength / 8)). */
|
|
685
|
+
get modulusSize() {
|
|
686
|
+
return Math.floor((this.N.toString(2).length + 7) / 8);
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Computes the multiplier parameter k = H(PAD(N) || PAD(g)) (RFC 5054, 2.5.3).
|
|
690
|
+
* @returns k as a bigint.
|
|
691
|
+
*/
|
|
692
|
+
async computeK() {
|
|
693
|
+
const nBytes = SecurityUtils.bigIntToFixedBytes(this.N, this.modulusSize);
|
|
694
|
+
const gBytes = SecurityUtils.bigIntToFixedBytes(this.g, this.modulusSize);
|
|
695
|
+
const combined = new Uint8Array(nBytes.length + gBytes.length);
|
|
696
|
+
combined.set(nBytes, 0);
|
|
697
|
+
combined.set(gBytes, nBytes.length);
|
|
698
|
+
const hash = await crypto.subtle.digest(this.hashAlgorithmName, combined);
|
|
699
|
+
return SecurityUtils.bytesToBigInt(new Uint8Array(hash));
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
// src/srp/srp-context-factory.ts
|
|
704
|
+
var SrpContextFactory = class {
|
|
705
|
+
/**
|
|
706
|
+
* Creates an SRP context with the specified group.
|
|
707
|
+
* @param group - SRP group (default Rfc5054_3072).
|
|
708
|
+
* @returns A ready-to-use SrpContext.
|
|
709
|
+
*/
|
|
710
|
+
static async create(group = 4 /* Rfc5054_3072 */) {
|
|
711
|
+
const opts = new SrpOptions();
|
|
712
|
+
opts.group = group;
|
|
713
|
+
opts.hashAlgorithmName = group === 5 /* Rfc5054_4096 */ || group === 6 /* Rfc5054_6144 */ || group === 7 /* Rfc5054_8192 */ ? "SHA-384" : "SHA-256";
|
|
714
|
+
return {
|
|
715
|
+
N: opts.N,
|
|
716
|
+
g: opts.g,
|
|
717
|
+
k: await opts.computeK(),
|
|
718
|
+
modulusSize: opts.modulusSize,
|
|
719
|
+
hashAlgorithmName: opts.hashAlgorithmName,
|
|
720
|
+
hashSize: opts.hashAlgorithmName === "SHA-256" ? 32 : opts.hashAlgorithmName === "SHA-384" ? 48 : 64
|
|
721
|
+
};
|
|
241
722
|
}
|
|
242
723
|
};
|
|
243
724
|
|
|
725
|
+
exports.AesGcmOptions = AesGcmOptions;
|
|
726
|
+
exports.CryptoProfile = CryptoProfile;
|
|
727
|
+
exports.CryptoProfileRegistry = CryptoProfileRegistry;
|
|
244
728
|
exports.CryptoService = CryptoService;
|
|
729
|
+
exports.CryptoVersion = CryptoVersion;
|
|
730
|
+
exports.KdfOptions = KdfOptions;
|
|
245
731
|
exports.KeyDerivationService = KeyDerivationService;
|
|
732
|
+
exports.SecurityConstants = SecurityConstants;
|
|
246
733
|
exports.SecurityUtils = SecurityUtils;
|
|
247
|
-
exports.
|
|
734
|
+
exports.SrpClientService = SrpClientService;
|
|
735
|
+
exports.SrpContextFactory = SrpContextFactory;
|
|
736
|
+
exports.SrpEncoding = SrpEncoding;
|
|
737
|
+
exports.SrpGroup = SrpGroup;
|
|
738
|
+
exports.SrpGroupParams = SrpGroupParams;
|
|
739
|
+
exports.SrpOptions = SrpOptions;
|
|
740
|
+
exports.SrpServerService = SrpServerService;
|
|
741
|
+
exports.SupportedHashAlgorithms = SupportedHashAlgorithms;
|
|
248
742
|
//# sourceMappingURL=index.cjs.map
|
|
249
743
|
//# sourceMappingURL=index.cjs.map
|