@crossdyne/security 0.5.0-beta.2 → 2.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 +367 -8
- package/dist/index.cjs +89 -125
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +60 -48
- package/dist/index.d.ts +60 -48
- package/dist/index.js +89 -125
- 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 = {
|
|
@@ -108,54 +110,36 @@ var SecurityUtils = class {
|
|
|
108
110
|
};
|
|
109
111
|
|
|
110
112
|
// src/crypto/aes-gcm-options.ts
|
|
111
|
-
var
|
|
112
|
-
constructor() {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return this._tagSize;
|
|
127
|
-
}
|
|
128
|
-
set tagSize(v) {
|
|
129
|
-
if (v < SecurityConstants.AesGcmTagSizeMin || v > SecurityConstants.AesGcmTagSizeMax) throw new RangeError(`Tag size must be between ${SecurityConstants.AesGcmTagSizeMin} and ${SecurityConstants.AesGcmTagSizeMax}`);
|
|
130
|
-
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);
|
|
131
128
|
}
|
|
132
129
|
/** Validates that {@link tagSize} is in the allowed range. */
|
|
133
130
|
validate() {
|
|
134
|
-
if (this.tagSize < SecurityConstants.AesGcmTagSizeMin || this.tagSize > SecurityConstants.AesGcmTagSizeMax)
|
|
135
|
-
|
|
136
|
-
/** Default preset: nonce=12, tag=16, no AAD. */
|
|
137
|
-
static get default() {
|
|
138
|
-
return new _AesGcmOptions();
|
|
139
|
-
}
|
|
140
|
-
/** Fluent setter for {@link tagSize}. */
|
|
141
|
-
withTagSize(s) {
|
|
142
|
-
this.tagSize = s;
|
|
143
|
-
return this;
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Fluent setter for {@link associatedData}.
|
|
147
|
-
* Accepts a byte array or a UTF-8 string (encoded internally).
|
|
148
|
-
*/
|
|
149
|
-
withAssociatedData(aad) {
|
|
150
|
-
this.associatedData = typeof aad === "string" ? new TextEncoder().encode(aad) : aad;
|
|
151
|
-
return this;
|
|
152
|
-
}
|
|
153
|
-
/** Validates and returns this instance. */
|
|
154
|
-
build() {
|
|
155
|
-
this.validate();
|
|
156
|
-
return this;
|
|
131
|
+
if (this.tagSize < SecurityConstants.AesGcmTagSizeMin || this.tagSize > SecurityConstants.AesGcmTagSizeMax)
|
|
132
|
+
throw new Error(`Invalid Tag Size: ${this.tagSize}`);
|
|
157
133
|
}
|
|
158
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;
|
|
159
143
|
|
|
160
144
|
// src/crypto/crypto-profile.ts
|
|
161
145
|
var CryptoProfile = class {
|
|
@@ -165,6 +149,7 @@ var CryptoProfile = class {
|
|
|
165
149
|
*/
|
|
166
150
|
constructor(params) {
|
|
167
151
|
this.version = params.version;
|
|
152
|
+
this.algorithmName = params.algorithmName;
|
|
168
153
|
this.kdfOptions = params.kdfOptions;
|
|
169
154
|
this.aesGcmOptions = params.aesGcmOptions;
|
|
170
155
|
}
|
|
@@ -177,28 +162,21 @@ var CryptoVersion = /* @__PURE__ */ ((CryptoVersion2) => {
|
|
|
177
162
|
})(CryptoVersion || {});
|
|
178
163
|
|
|
179
164
|
// src/crypto/kdf-options.ts
|
|
180
|
-
var
|
|
181
|
-
constructor() {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
this.
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
get hashAlgorithm() {
|
|
196
|
-
return this._hashAlgorithm;
|
|
197
|
-
}
|
|
198
|
-
set hashAlgorithm(v) {
|
|
199
|
-
if (!SupportedHashAlgorithms.includes(v))
|
|
200
|
-
throw new RangeError(`Unsupported hash: ${v}`);
|
|
201
|
-
this._hashAlgorithm = v;
|
|
165
|
+
var _KdfOptions = class _KdfOptions {
|
|
166
|
+
constructor(pbkdf2Iterations, hashAlgorithm) {
|
|
167
|
+
if (pbkdf2Iterations < SecurityConstants.Pbkdf2IterationsMinimum) {
|
|
168
|
+
throw new RangeError(
|
|
169
|
+
`PBKDF2 iterations must be at least ${SecurityConstants.Pbkdf2IterationsMinimum}.`
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
if (!SupportedHashAlgorithms.includes(hashAlgorithm)) {
|
|
173
|
+
throw new RangeError(
|
|
174
|
+
`Unsupported hash algorithm: ${hashAlgorithm}. Supported: ${SupportedHashAlgorithms.join(", ")}.`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
this.pbkdf2Iterations = pbkdf2Iterations;
|
|
178
|
+
this.hashAlgorithm = hashAlgorithm;
|
|
179
|
+
Object.freeze(this);
|
|
202
180
|
}
|
|
203
181
|
/** Validates iterations and hash algorithm. */
|
|
204
182
|
validate() {
|
|
@@ -207,46 +185,33 @@ var KdfOptions = class _KdfOptions {
|
|
|
207
185
|
if (!SupportedHashAlgorithms.includes(this.hashAlgorithm))
|
|
208
186
|
throw new Error(`Invalid hash algorithm: ${this.hashAlgorithm}`);
|
|
209
187
|
}
|
|
210
|
-
/** Default preset: SHA-256, 600_000 iterations. */
|
|
211
|
-
static get default() {
|
|
212
|
-
return new _KdfOptions();
|
|
213
|
-
}
|
|
214
|
-
/** Fluent setter for {@link pbkdf2Iterations}. */
|
|
215
|
-
withPbkdf2Iterations(i) {
|
|
216
|
-
this.pbkdf2Iterations = i;
|
|
217
|
-
return this;
|
|
218
|
-
}
|
|
219
|
-
/** Fluent setter for {@link hashAlgorithm}. */
|
|
220
|
-
withHashAlgorithm(h) {
|
|
221
|
-
this.hashAlgorithm = h;
|
|
222
|
-
return this;
|
|
223
|
-
}
|
|
224
|
-
/** Validates and returns this instance. */
|
|
225
|
-
build() {
|
|
226
|
-
this.validate();
|
|
227
|
-
return this;
|
|
228
|
-
}
|
|
229
188
|
};
|
|
189
|
+
/**
|
|
190
|
+
* V1 preset: SHA-256, 600_000 iterations.
|
|
191
|
+
* These exact values are frozen for all V1-derived keys.
|
|
192
|
+
*/
|
|
193
|
+
_KdfOptions.V1 = Object.freeze(
|
|
194
|
+
new _KdfOptions(6e5, "SHA-256")
|
|
195
|
+
);
|
|
196
|
+
var KdfOptions = _KdfOptions;
|
|
230
197
|
|
|
231
198
|
// src/crypto/crypto-profile-registry.ts
|
|
232
199
|
var CryptoProfileRegistry = class {
|
|
233
200
|
static getProfile(version) {
|
|
234
201
|
switch (version) {
|
|
235
202
|
case 1 /* V1 */:
|
|
236
|
-
return
|
|
237
|
-
version: 1 /* V1 */,
|
|
238
|
-
kdfOptions: KdfOptions.default,
|
|
239
|
-
aesGcmOptions: AesGcmOptions.default
|
|
240
|
-
});
|
|
203
|
+
return this.V1_PROFILE;
|
|
241
204
|
default:
|
|
242
205
|
throw new Error(`Unsupported crypto version: ${version}`);
|
|
243
206
|
}
|
|
244
207
|
}
|
|
245
|
-
/** Latest supported profile (currently V1). */
|
|
246
|
-
static get latest() {
|
|
247
|
-
return this.getProfile(1 /* V1 */);
|
|
248
|
-
}
|
|
249
208
|
};
|
|
209
|
+
CryptoProfileRegistry.V1_PROFILE = new CryptoProfile({
|
|
210
|
+
version: 1 /* V1 */,
|
|
211
|
+
algorithmName: "AES-GCM",
|
|
212
|
+
kdfOptions: KdfOptions.V1,
|
|
213
|
+
aesGcmOptions: AesGcmOptions.V1
|
|
214
|
+
});
|
|
250
215
|
|
|
251
216
|
// src/crypto/crypto.service.ts
|
|
252
217
|
var CryptoService = class {
|
|
@@ -261,7 +226,7 @@ var CryptoService = class {
|
|
|
261
226
|
/**
|
|
262
227
|
* Encrypts a serializable object to a Base64 string.
|
|
263
228
|
* @param dataModel - Object or Uint8Array to encrypt.
|
|
264
|
-
* @param key -
|
|
229
|
+
* @param key - A non-extractable CryptoKey obtained via {@link CryptoService.importKey}.
|
|
265
230
|
* @returns Base64-encoded ciphertext with prepended nonce.
|
|
266
231
|
*/
|
|
267
232
|
async encryptData(dataModel, key, version = 1 /* V1 */) {
|
|
@@ -277,25 +242,13 @@ var CryptoService = class {
|
|
|
277
242
|
}
|
|
278
243
|
const plainBytes = encoder.encode(jsonString);
|
|
279
244
|
const nonce = crypto.getRandomValues(new Uint8Array(opts.nonceSize));
|
|
280
|
-
const cryptoKey = await crypto.subtle.importKey(
|
|
281
|
-
"raw",
|
|
282
|
-
key,
|
|
283
|
-
"AES-GCM",
|
|
284
|
-
false,
|
|
285
|
-
["encrypt"]
|
|
286
|
-
);
|
|
287
|
-
let associatedData = new Uint8Array(0);
|
|
288
|
-
if (opts.associatedData != null) {
|
|
289
|
-
associatedData = opts.associatedData;
|
|
290
|
-
}
|
|
291
245
|
const encryptedContent = await crypto.subtle.encrypt(
|
|
292
246
|
{
|
|
293
247
|
name: "AES-GCM",
|
|
294
248
|
iv: nonce,
|
|
295
|
-
tagLength: opts.tagSize * 8
|
|
296
|
-
additionalData: associatedData
|
|
249
|
+
tagLength: opts.tagSize * 8
|
|
297
250
|
},
|
|
298
|
-
|
|
251
|
+
key,
|
|
299
252
|
plainBytes
|
|
300
253
|
);
|
|
301
254
|
const result = new Uint8Array(1 + opts.nonceSize + encryptedContent.byteLength);
|
|
@@ -307,7 +260,7 @@ var CryptoService = class {
|
|
|
307
260
|
/**
|
|
308
261
|
* Decrypts a Base64-encoded ciphertext back to the original object.
|
|
309
262
|
* @param encryptedBase64 - The encrypted data.
|
|
310
|
-
* @param key -
|
|
263
|
+
* @param key - A non-extractable CryptoKey obtained via {@link CryptoService.importKey}.
|
|
311
264
|
* @returns Deserialized object, or null if input is empty.
|
|
312
265
|
* @throws If authentication tag mismatch or corrupted data.
|
|
313
266
|
*/
|
|
@@ -324,25 +277,14 @@ var CryptoService = class {
|
|
|
324
277
|
throw new Error(`Invalid format: minimum expected ${opts.nonceSize + opts.tagSize} byte.`);
|
|
325
278
|
const nonce = payload.slice(0, opts.nonceSize);
|
|
326
279
|
const ciphertextWithTag = payload.slice(opts.nonceSize);
|
|
327
|
-
const cryptoKey = await crypto.subtle.importKey(
|
|
328
|
-
"raw",
|
|
329
|
-
key,
|
|
330
|
-
"AES-GCM",
|
|
331
|
-
false,
|
|
332
|
-
["decrypt"]
|
|
333
|
-
);
|
|
334
280
|
try {
|
|
335
|
-
let associatedData = new Uint8Array(0);
|
|
336
|
-
if (opts.associatedData != null)
|
|
337
|
-
associatedData = opts.associatedData;
|
|
338
281
|
const decryptedBuffer = await crypto.subtle.decrypt(
|
|
339
282
|
{
|
|
340
283
|
name: "AES-GCM",
|
|
341
284
|
iv: nonce,
|
|
342
|
-
tagLength: opts.tagSize * 8
|
|
343
|
-
additionalData: associatedData
|
|
285
|
+
tagLength: opts.tagSize * 8
|
|
344
286
|
},
|
|
345
|
-
|
|
287
|
+
key,
|
|
346
288
|
ciphertextWithTag
|
|
347
289
|
);
|
|
348
290
|
const decoder = new TextDecoder();
|
|
@@ -357,6 +299,28 @@ var CryptoService = class {
|
|
|
357
299
|
throw new Error("Decryption failed: authentication tag mismatch or corrupted data.");
|
|
358
300
|
}
|
|
359
301
|
}
|
|
302
|
+
/**
|
|
303
|
+
* Imports raw key bytes into a non-extractable CryptoKey based on the crypto profile version.
|
|
304
|
+
*
|
|
305
|
+
* The resulting key cannot be exported back to raw bytes (extractable: false),
|
|
306
|
+
* ensuring that sensitive key material does not persist in JavaScript-accessible memory.
|
|
307
|
+
* Callers should securely wipe the original raw key buffer immediately after import.
|
|
308
|
+
*
|
|
309
|
+
* @param rawKey - Raw key bytes (e.g. a Data Encryption Key).
|
|
310
|
+
* @param version - Crypto profile version that determines the algorithm and parameters.
|
|
311
|
+
* @param usages - Allowed key operations (e.g. ['encrypt', 'decrypt']).
|
|
312
|
+
* @returns A non-extractable CryptoKey bound to the algorithm defined by the profile.
|
|
313
|
+
*/
|
|
314
|
+
async importKey(rawKey, version, usages) {
|
|
315
|
+
const profile = CryptoProfileRegistry.getProfile(version);
|
|
316
|
+
return await crypto.subtle.importKey(
|
|
317
|
+
"raw",
|
|
318
|
+
rawKey,
|
|
319
|
+
profile.algorithmName,
|
|
320
|
+
false,
|
|
321
|
+
usages
|
|
322
|
+
);
|
|
323
|
+
}
|
|
360
324
|
};
|
|
361
325
|
|
|
362
326
|
// src/crypto/key-derivation.service.ts
|