@brashkie/signalis-core 0.1.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/LICENSE +201 -0
- package/NOTICE +12 -0
- package/README.es.md +599 -0
- package/README.md +665 -0
- package/dist/index.cjs +712 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +764 -0
- package/dist/index.d.ts +764 -0
- package/dist/index.mjs +622 -0
- package/dist/index.mjs.map +1 -0
- package/index.d.ts +23 -0
- package/index.js +328 -0
- package/package.json +125 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,712 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
AES_256_CBC_IV_SIZE: () => AES_256_CBC_IV_SIZE,
|
|
34
|
+
AES_256_GCM_NONCE_SIZE: () => AES_256_GCM_NONCE_SIZE,
|
|
35
|
+
AES_256_GCM_TAG_SIZE: () => AES_256_GCM_TAG_SIZE,
|
|
36
|
+
AES_256_KEY_SIZE: () => AES_256_KEY_SIZE,
|
|
37
|
+
AES_BLOCK_SIZE: () => AES_BLOCK_SIZE,
|
|
38
|
+
AES_CBC: () => AES_CBC,
|
|
39
|
+
AES_GCM: () => AES_GCM,
|
|
40
|
+
AES_GCM_MAX_PLAINTEXT_SIZE: () => AES_GCM_MAX_PLAINTEXT_SIZE,
|
|
41
|
+
AES_GCM_RECOMMENDED_MESSAGES_PER_KEY: () => AES_GCM_RECOMMENDED_MESSAGES_PER_KEY,
|
|
42
|
+
AuthenticationError: () => AuthenticationError,
|
|
43
|
+
CURVE25519_PRIVATE_KEY_SIZE: () => CURVE25519_PRIVATE_KEY_SIZE,
|
|
44
|
+
CURVE25519_PUBLIC_KEY_SIZE: () => CURVE25519_PUBLIC_KEY_SIZE,
|
|
45
|
+
CURVE25519_SHARED_SECRET_SIZE: () => CURVE25519_SHARED_SECRET_SIZE,
|
|
46
|
+
CryptoError: () => CryptoError,
|
|
47
|
+
Curve25519: () => Curve25519,
|
|
48
|
+
HKDF: () => HKDF,
|
|
49
|
+
HKDF_MAX_OUTPUT_SIZE: () => HKDF_MAX_OUTPUT_SIZE,
|
|
50
|
+
HKDF_PRK_SIZE: () => HKDF_PRK_SIZE,
|
|
51
|
+
HMAC: () => HMAC,
|
|
52
|
+
HMAC_SHA256_TAG_SIZE: () => HMAC_SHA256_TAG_SIZE,
|
|
53
|
+
KeyDerivationError: () => KeyDerivationError,
|
|
54
|
+
LengthError: () => LengthError,
|
|
55
|
+
SHA256: () => SHA256,
|
|
56
|
+
SHA256_BLOCK_SIZE: () => SHA256_BLOCK_SIZE,
|
|
57
|
+
SHA256_OUTPUT_SIZE: () => SHA256_OUTPUT_SIZE,
|
|
58
|
+
SignalisError: () => SignalisError,
|
|
59
|
+
VERSION: () => VERSION,
|
|
60
|
+
ValidationError: () => ValidationError,
|
|
61
|
+
asPrivateKey: () => asPrivateKey,
|
|
62
|
+
asPublicKey: () => asPublicKey,
|
|
63
|
+
asSharedSecret: () => asSharedSecret,
|
|
64
|
+
assertBuffer: () => assertBuffer,
|
|
65
|
+
assertBufferLength: () => assertBufferLength,
|
|
66
|
+
assertBufferOfSize: () => assertBufferOfSize,
|
|
67
|
+
assertHkdfLength: () => assertHkdfLength,
|
|
68
|
+
assertPositiveInteger: () => assertPositiveInteger,
|
|
69
|
+
bufferToString: () => bufferToString,
|
|
70
|
+
buffersSameLength: () => buffersSameLength,
|
|
71
|
+
concat: () => concat,
|
|
72
|
+
constantTimeEqual: () => constantTimeEqual,
|
|
73
|
+
default: () => index_default,
|
|
74
|
+
fromBase64: () => fromBase64,
|
|
75
|
+
fromBase64Url: () => fromBase64Url,
|
|
76
|
+
fromHex: () => fromHex,
|
|
77
|
+
nativeVersion: () => nativeVersion,
|
|
78
|
+
randomIv: () => randomIv,
|
|
79
|
+
randomKey: () => randomKey,
|
|
80
|
+
randomNonce: () => randomNonce,
|
|
81
|
+
secureRandom: () => secureRandom,
|
|
82
|
+
stringToBuffer: () => stringToBuffer,
|
|
83
|
+
toBase64: () => toBase64,
|
|
84
|
+
toBase64Url: () => toBase64Url,
|
|
85
|
+
toHex: () => toHex,
|
|
86
|
+
xor: () => xor,
|
|
87
|
+
zeroize: () => zeroize
|
|
88
|
+
});
|
|
89
|
+
module.exports = __toCommonJS(index_exports);
|
|
90
|
+
|
|
91
|
+
// src/core.ts
|
|
92
|
+
var native = __toESM(require("../index.js"));
|
|
93
|
+
|
|
94
|
+
// src/constants.ts
|
|
95
|
+
var CURVE25519_PRIVATE_KEY_SIZE = 32;
|
|
96
|
+
var CURVE25519_PUBLIC_KEY_SIZE = 32;
|
|
97
|
+
var CURVE25519_SHARED_SECRET_SIZE = 32;
|
|
98
|
+
var HKDF_PRK_SIZE = 32;
|
|
99
|
+
var HKDF_MAX_OUTPUT_SIZE = 8160;
|
|
100
|
+
var AES_256_KEY_SIZE = 32;
|
|
101
|
+
var AES_256_GCM_NONCE_SIZE = 12;
|
|
102
|
+
var AES_256_GCM_TAG_SIZE = 16;
|
|
103
|
+
var AES_256_CBC_IV_SIZE = 16;
|
|
104
|
+
var AES_BLOCK_SIZE = 16;
|
|
105
|
+
var SHA256_OUTPUT_SIZE = 32;
|
|
106
|
+
var HMAC_SHA256_TAG_SIZE = 32;
|
|
107
|
+
var SHA256_BLOCK_SIZE = 64;
|
|
108
|
+
var AES_GCM_MAX_PLAINTEXT_SIZE = 64 * 1024 * 1024 * 1024;
|
|
109
|
+
var AES_GCM_RECOMMENDED_MESSAGES_PER_KEY = 4294967296;
|
|
110
|
+
|
|
111
|
+
// src/errors.ts
|
|
112
|
+
var SignalisError = class extends Error {
|
|
113
|
+
/** Error code for programmatic handling. */
|
|
114
|
+
code;
|
|
115
|
+
constructor(message, code) {
|
|
116
|
+
super(message);
|
|
117
|
+
this.name = "SignalisError";
|
|
118
|
+
this.code = code;
|
|
119
|
+
if (typeof Error.captureStackTrace === "function") {
|
|
120
|
+
Error.captureStackTrace(this, this.constructor);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
var ValidationError = class extends SignalisError {
|
|
125
|
+
/** Name of the parameter that failed validation. */
|
|
126
|
+
parameter;
|
|
127
|
+
/** Expected value description. */
|
|
128
|
+
expected;
|
|
129
|
+
/** Actual value received. */
|
|
130
|
+
actual;
|
|
131
|
+
constructor(message, options = {}) {
|
|
132
|
+
super(message, "VALIDATION_ERROR");
|
|
133
|
+
this.name = "ValidationError";
|
|
134
|
+
this.parameter = options.parameter;
|
|
135
|
+
this.expected = options.expected;
|
|
136
|
+
this.actual = options.actual;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
var CryptoError = class extends SignalisError {
|
|
140
|
+
/** The crypto operation that failed. */
|
|
141
|
+
operation;
|
|
142
|
+
constructor(message, operation) {
|
|
143
|
+
super(message, "CRYPTO_ERROR");
|
|
144
|
+
this.name = "CryptoError";
|
|
145
|
+
this.operation = operation;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
var AuthenticationError = class extends CryptoError {
|
|
149
|
+
constructor(message = "Authentication tag verification failed") {
|
|
150
|
+
super(message, "authenticate");
|
|
151
|
+
this.name = "AuthenticationError";
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
var KeyDerivationError = class extends CryptoError {
|
|
155
|
+
constructor(message) {
|
|
156
|
+
super(message, "derive_key");
|
|
157
|
+
this.name = "KeyDerivationError";
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
var LengthError = class extends ValidationError {
|
|
161
|
+
constructor(message, options = {}) {
|
|
162
|
+
super(message, options);
|
|
163
|
+
this.name = "LengthError";
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// src/validators.ts
|
|
168
|
+
function assertBuffer(value, parameter) {
|
|
169
|
+
if (!Buffer.isBuffer(value)) {
|
|
170
|
+
throw new ValidationError(`${parameter} must be a Buffer`, {
|
|
171
|
+
parameter,
|
|
172
|
+
expected: "Buffer",
|
|
173
|
+
actual: typeof value
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function assertBufferLength(value, expectedLength, parameter) {
|
|
178
|
+
if (value.length !== expectedLength) {
|
|
179
|
+
throw new ValidationError(
|
|
180
|
+
`${parameter} must be ${expectedLength} bytes, got ${value.length}`,
|
|
181
|
+
{
|
|
182
|
+
parameter,
|
|
183
|
+
expected: `${expectedLength} bytes`,
|
|
184
|
+
actual: value.length
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function assertBufferOfSize(value, expectedLength, parameter) {
|
|
190
|
+
assertBuffer(value, parameter);
|
|
191
|
+
assertBufferLength(value, expectedLength, parameter);
|
|
192
|
+
}
|
|
193
|
+
function assertPositiveInteger(value, parameter) {
|
|
194
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
|
|
195
|
+
throw new ValidationError(`${parameter} must be a non-negative integer`, {
|
|
196
|
+
parameter,
|
|
197
|
+
expected: "non-negative integer",
|
|
198
|
+
actual: String(value)
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function assertHkdfLength(length) {
|
|
203
|
+
assertPositiveInteger(length, "length");
|
|
204
|
+
if (length > HKDF_MAX_OUTPUT_SIZE) {
|
|
205
|
+
throw new LengthError(
|
|
206
|
+
`HKDF output length cannot exceed ${HKDF_MAX_OUTPUT_SIZE} bytes (255 * 32)`,
|
|
207
|
+
{
|
|
208
|
+
expected: `<= ${HKDF_MAX_OUTPUT_SIZE}`,
|
|
209
|
+
actual: length
|
|
210
|
+
}
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
if (length === 0) {
|
|
214
|
+
throw new LengthError("HKDF output length must be greater than 0", {
|
|
215
|
+
expected: "> 0",
|
|
216
|
+
actual: 0
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function buffersSameLength(a, b) {
|
|
221
|
+
return a.length === b.length;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/core.ts
|
|
225
|
+
var Curve25519 = Object.freeze({
|
|
226
|
+
/**
|
|
227
|
+
* Generate a new random Curve25519 keypair.
|
|
228
|
+
*
|
|
229
|
+
* Uses the OS's cryptographically secure RNG (via Rust's `OsRng`).
|
|
230
|
+
*
|
|
231
|
+
* @returns A {@link KeyPair} with private and public keys (32 bytes each)
|
|
232
|
+
*/
|
|
233
|
+
generateKeyPair() {
|
|
234
|
+
const kp = native.curve25519GenerateKeypair();
|
|
235
|
+
return Object.freeze({
|
|
236
|
+
privateKey: kp.private,
|
|
237
|
+
publicKey: kp.public
|
|
238
|
+
});
|
|
239
|
+
},
|
|
240
|
+
/**
|
|
241
|
+
* Derive the public key corresponding to a given private key.
|
|
242
|
+
*
|
|
243
|
+
* @param privateKey - 32-byte private key
|
|
244
|
+
* @returns The corresponding 32-byte public key
|
|
245
|
+
* @throws {ValidationError} If `privateKey` is not a 32-byte Buffer.
|
|
246
|
+
*/
|
|
247
|
+
publicFromPrivate(privateKey) {
|
|
248
|
+
assertBufferOfSize(privateKey, CURVE25519_PRIVATE_KEY_SIZE, "privateKey");
|
|
249
|
+
return native.curve25519PublicFromPrivate(privateKey);
|
|
250
|
+
},
|
|
251
|
+
/**
|
|
252
|
+
* Perform X25519 Diffie-Hellman key agreement.
|
|
253
|
+
*
|
|
254
|
+
* Both parties run this with swapped (private, peer-public) arguments
|
|
255
|
+
* and obtain the same 32-byte shared secret.
|
|
256
|
+
*
|
|
257
|
+
* **⚠️ CRITICAL:** Do NOT use the returned secret directly as an
|
|
258
|
+
* encryption key. Always derive an actual key through HKDF:
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* ```ts
|
|
262
|
+
* const shared = Curve25519.diffieHellman(myPriv, theirPub);
|
|
263
|
+
* const key = HKDF.derive(salt, shared, Buffer.from('aes-key'), 32);
|
|
264
|
+
* ```
|
|
265
|
+
*
|
|
266
|
+
* @param privateKey - Your 32-byte private key
|
|
267
|
+
* @param peerPublicKey - Peer's 32-byte public key
|
|
268
|
+
* @returns 32-byte shared secret
|
|
269
|
+
* @throws {ValidationError} If keys are not 32 bytes.
|
|
270
|
+
* @throws {CryptoError} If the operation fails.
|
|
271
|
+
*/
|
|
272
|
+
diffieHellman(privateKey, peerPublicKey) {
|
|
273
|
+
assertBufferOfSize(privateKey, CURVE25519_PRIVATE_KEY_SIZE, "privateKey");
|
|
274
|
+
assertBufferOfSize(peerPublicKey, CURVE25519_PUBLIC_KEY_SIZE, "peerPublicKey");
|
|
275
|
+
return native.curve25519DiffieHellman(privateKey, peerPublicKey);
|
|
276
|
+
},
|
|
277
|
+
/**
|
|
278
|
+
* The size of a Curve25519 private key in bytes (32).
|
|
279
|
+
*/
|
|
280
|
+
PRIVATE_KEY_SIZE: CURVE25519_PRIVATE_KEY_SIZE,
|
|
281
|
+
/**
|
|
282
|
+
* The size of a Curve25519 public key in bytes (32).
|
|
283
|
+
*/
|
|
284
|
+
PUBLIC_KEY_SIZE: CURVE25519_PUBLIC_KEY_SIZE,
|
|
285
|
+
/**
|
|
286
|
+
* The size of an X25519 shared secret in bytes (32).
|
|
287
|
+
*/
|
|
288
|
+
SHARED_SECRET_SIZE: CURVE25519_SHARED_SECRET_SIZE
|
|
289
|
+
});
|
|
290
|
+
var HKDF = Object.freeze({
|
|
291
|
+
/**
|
|
292
|
+
* HKDF-Extract: produces a 32-byte pseudorandom key (PRK).
|
|
293
|
+
*
|
|
294
|
+
* @param salt - Optional salt (pass `Buffer.alloc(0)` if not available)
|
|
295
|
+
* @param ikm - Input keying material
|
|
296
|
+
* @returns 32-byte PRK
|
|
297
|
+
* @throws {ValidationError} If inputs are not Buffers.
|
|
298
|
+
*/
|
|
299
|
+
extract(salt, ikm) {
|
|
300
|
+
assertBuffer(salt, "salt");
|
|
301
|
+
assertBuffer(ikm, "ikm");
|
|
302
|
+
return native.hkdfExtract(salt, ikm);
|
|
303
|
+
},
|
|
304
|
+
/**
|
|
305
|
+
* HKDF-Expand: produces `length` bytes of output keying material.
|
|
306
|
+
*
|
|
307
|
+
* @param prk - 32-byte pseudorandom key from {@link extract}
|
|
308
|
+
* @param info - Context-specific information (binds output to a usage)
|
|
309
|
+
* @param length - Desired output length (1 to 8160 bytes)
|
|
310
|
+
* @returns OKM of requested length
|
|
311
|
+
* @throws {ValidationError} If PRK is not 32 bytes or length is out of bounds.
|
|
312
|
+
*/
|
|
313
|
+
expand(prk, info, length) {
|
|
314
|
+
assertBufferOfSize(prk, HKDF_PRK_SIZE, "prk");
|
|
315
|
+
assertBuffer(info, "info");
|
|
316
|
+
assertHkdfLength(length);
|
|
317
|
+
return native.hkdfExpand(prk, info, length);
|
|
318
|
+
},
|
|
319
|
+
/**
|
|
320
|
+
* HKDF one-shot: extract + expand in a single call.
|
|
321
|
+
*
|
|
322
|
+
* Use this whenever possible — it's the standard HKDF API.
|
|
323
|
+
*
|
|
324
|
+
* @param salt - Optional salt
|
|
325
|
+
* @param ikm - Input keying material
|
|
326
|
+
* @param info - Context info
|
|
327
|
+
* @param length - Desired output length
|
|
328
|
+
* @returns OKM of requested length
|
|
329
|
+
*/
|
|
330
|
+
derive(salt, ikm, info, length) {
|
|
331
|
+
assertBuffer(salt, "salt");
|
|
332
|
+
assertBuffer(ikm, "ikm");
|
|
333
|
+
assertBuffer(info, "info");
|
|
334
|
+
assertHkdfLength(length);
|
|
335
|
+
return native.hkdfDerive(salt, ikm, info, length);
|
|
336
|
+
},
|
|
337
|
+
/**
|
|
338
|
+
* Derive multiple keys from the same shared secret in a single call.
|
|
339
|
+
*
|
|
340
|
+
* Useful for deriving e.g., a send key AND a receive key simultaneously.
|
|
341
|
+
*
|
|
342
|
+
* @example
|
|
343
|
+
* ```ts
|
|
344
|
+
* const [sendKey, recvKey] = HKDF.deriveMultiple(
|
|
345
|
+
* salt,
|
|
346
|
+
* sharedSecret,
|
|
347
|
+
* Buffer.from('signalis-channel-v1'),
|
|
348
|
+
* [32, 32],
|
|
349
|
+
* );
|
|
350
|
+
* ```
|
|
351
|
+
*
|
|
352
|
+
* @param salt - Optional salt
|
|
353
|
+
* @param ikm - Input keying material
|
|
354
|
+
* @param info - Context info
|
|
355
|
+
* @param lengths - Array of output lengths
|
|
356
|
+
* @returns Array of derived keys, one per requested length
|
|
357
|
+
*/
|
|
358
|
+
deriveMultiple(salt, ikm, info, lengths) {
|
|
359
|
+
assertBuffer(salt, "salt");
|
|
360
|
+
assertBuffer(ikm, "ikm");
|
|
361
|
+
assertBuffer(info, "info");
|
|
362
|
+
if (!Array.isArray(lengths) || lengths.length === 0) {
|
|
363
|
+
throw new TypeError("lengths must be a non-empty array of integers");
|
|
364
|
+
}
|
|
365
|
+
const total = lengths.reduce((sum, len) => sum + len, 0);
|
|
366
|
+
assertHkdfLength(total);
|
|
367
|
+
const okm = this.derive(salt, ikm, info, total);
|
|
368
|
+
const results = [];
|
|
369
|
+
let offset = 0;
|
|
370
|
+
for (const len of lengths) {
|
|
371
|
+
results.push(okm.subarray(offset, offset + len));
|
|
372
|
+
offset += len;
|
|
373
|
+
}
|
|
374
|
+
return results;
|
|
375
|
+
},
|
|
376
|
+
/**
|
|
377
|
+
* Derive a key using an {@link HkdfParams} object (alternative API).
|
|
378
|
+
*/
|
|
379
|
+
deriveFromParams(params) {
|
|
380
|
+
return this.derive(params.salt, params.ikm, params.info, params.length);
|
|
381
|
+
},
|
|
382
|
+
/**
|
|
383
|
+
* The size of an HKDF PRK in bytes (32).
|
|
384
|
+
*/
|
|
385
|
+
PRK_SIZE: HKDF_PRK_SIZE
|
|
386
|
+
});
|
|
387
|
+
var AES_GCM = Object.freeze({
|
|
388
|
+
/**
|
|
389
|
+
* Encrypt plaintext.
|
|
390
|
+
*
|
|
391
|
+
* Returns: ciphertext || 16-byte authentication tag (concatenated).
|
|
392
|
+
*
|
|
393
|
+
* @param key - 32-byte symmetric key
|
|
394
|
+
* @param nonce - 12-byte unique nonce (NEVER reuse with same key)
|
|
395
|
+
* @param plaintext - Data to encrypt
|
|
396
|
+
* @returns Ciphertext + auth tag (output is `plaintext.length + 16` bytes)
|
|
397
|
+
* @throws {ValidationError} On invalid sizes.
|
|
398
|
+
* @throws {CryptoError} If the operation fails.
|
|
399
|
+
*/
|
|
400
|
+
encrypt(key, nonce, plaintext) {
|
|
401
|
+
assertBufferOfSize(key, AES_256_KEY_SIZE, "key");
|
|
402
|
+
assertBufferOfSize(nonce, AES_256_GCM_NONCE_SIZE, "nonce");
|
|
403
|
+
assertBuffer(plaintext, "plaintext");
|
|
404
|
+
return native.aes256GcmEncrypt(key, nonce, plaintext);
|
|
405
|
+
},
|
|
406
|
+
/**
|
|
407
|
+
* Decrypt ciphertext and verify authentication tag.
|
|
408
|
+
*
|
|
409
|
+
* @param key - 32-byte symmetric key (same as encryption)
|
|
410
|
+
* @param nonce - 12-byte nonce (same as encryption)
|
|
411
|
+
* @param ciphertext - Ciphertext || auth tag
|
|
412
|
+
* @returns Original plaintext
|
|
413
|
+
* @throws {ValidationError} On invalid sizes.
|
|
414
|
+
* @throws {AuthenticationError} If the tag is invalid (tampered or wrong key).
|
|
415
|
+
*/
|
|
416
|
+
decrypt(key, nonce, ciphertext) {
|
|
417
|
+
assertBufferOfSize(key, AES_256_KEY_SIZE, "key");
|
|
418
|
+
assertBufferOfSize(nonce, AES_256_GCM_NONCE_SIZE, "nonce");
|
|
419
|
+
assertBuffer(ciphertext, "ciphertext");
|
|
420
|
+
if (ciphertext.length < AES_256_GCM_TAG_SIZE) {
|
|
421
|
+
throw new CryptoError(
|
|
422
|
+
`Ciphertext too short: must be at least ${AES_256_GCM_TAG_SIZE} bytes (tag size)`,
|
|
423
|
+
"aes_gcm_decrypt"
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
try {
|
|
427
|
+
return native.aes256GcmDecrypt(key, nonce, ciphertext);
|
|
428
|
+
} catch (e) {
|
|
429
|
+
throw new AuthenticationError(
|
|
430
|
+
`AES-256-GCM authentication failed: ${e.message}`
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
},
|
|
434
|
+
/** Key size in bytes (32). */
|
|
435
|
+
KEY_SIZE: AES_256_KEY_SIZE,
|
|
436
|
+
/** Nonce size in bytes (12). */
|
|
437
|
+
NONCE_SIZE: AES_256_GCM_NONCE_SIZE,
|
|
438
|
+
/** Tag size in bytes (16). */
|
|
439
|
+
TAG_SIZE: AES_256_GCM_TAG_SIZE
|
|
440
|
+
});
|
|
441
|
+
var AES_CBC = Object.freeze({
|
|
442
|
+
/**
|
|
443
|
+
* Encrypt with PKCS#7 padding.
|
|
444
|
+
*
|
|
445
|
+
* @param key - 32-byte symmetric key
|
|
446
|
+
* @param iv - 16-byte initialization vector
|
|
447
|
+
* @param plaintext - Data to encrypt
|
|
448
|
+
* @returns Ciphertext (padded to nearest 16-byte block)
|
|
449
|
+
*/
|
|
450
|
+
encrypt(key, iv, plaintext) {
|
|
451
|
+
assertBufferOfSize(key, AES_256_KEY_SIZE, "key");
|
|
452
|
+
assertBufferOfSize(iv, AES_256_CBC_IV_SIZE, "iv");
|
|
453
|
+
assertBuffer(plaintext, "plaintext");
|
|
454
|
+
return native.aes256CbcEncrypt(key, iv, plaintext);
|
|
455
|
+
},
|
|
456
|
+
/**
|
|
457
|
+
* Decrypt with PKCS#7 padding (no authentication).
|
|
458
|
+
*
|
|
459
|
+
* @param key - 32-byte symmetric key
|
|
460
|
+
* @param iv - 16-byte IV (same as encryption)
|
|
461
|
+
* @param ciphertext - Ciphertext
|
|
462
|
+
* @returns Original plaintext
|
|
463
|
+
* @throws {CryptoError} On padding errors.
|
|
464
|
+
*/
|
|
465
|
+
decrypt(key, iv, ciphertext) {
|
|
466
|
+
assertBufferOfSize(key, AES_256_KEY_SIZE, "key");
|
|
467
|
+
assertBufferOfSize(iv, AES_256_CBC_IV_SIZE, "iv");
|
|
468
|
+
assertBuffer(ciphertext, "ciphertext");
|
|
469
|
+
try {
|
|
470
|
+
return native.aes256CbcDecrypt(key, iv, ciphertext);
|
|
471
|
+
} catch (e) {
|
|
472
|
+
throw new CryptoError(
|
|
473
|
+
`AES-256-CBC decryption failed: ${e.message}`,
|
|
474
|
+
"aes_cbc_decrypt"
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
/** Key size in bytes (32). */
|
|
479
|
+
KEY_SIZE: AES_256_KEY_SIZE,
|
|
480
|
+
/** IV size in bytes (16). */
|
|
481
|
+
IV_SIZE: AES_256_CBC_IV_SIZE
|
|
482
|
+
});
|
|
483
|
+
var HMAC = Object.freeze({
|
|
484
|
+
/**
|
|
485
|
+
* Compute HMAC-SHA256 of `data` using `key`.
|
|
486
|
+
*
|
|
487
|
+
* @param key - Authentication key (any length)
|
|
488
|
+
* @param data - Data to authenticate
|
|
489
|
+
* @returns 32-byte HMAC tag
|
|
490
|
+
*/
|
|
491
|
+
sha256(key, data) {
|
|
492
|
+
assertBuffer(key, "key");
|
|
493
|
+
assertBuffer(data, "data");
|
|
494
|
+
return native.hmacSha256(key, data);
|
|
495
|
+
},
|
|
496
|
+
/**
|
|
497
|
+
* Verify an HMAC-SHA256 tag in **constant time**.
|
|
498
|
+
*
|
|
499
|
+
* Always use this instead of `===` to prevent timing attacks.
|
|
500
|
+
*
|
|
501
|
+
* @param key - Authentication key
|
|
502
|
+
* @param data - Original data
|
|
503
|
+
* @param expectedTag - Tag to verify
|
|
504
|
+
* @returns `true` if tag matches, `false` otherwise
|
|
505
|
+
*/
|
|
506
|
+
verifySha256(key, data, expectedTag) {
|
|
507
|
+
assertBuffer(key, "key");
|
|
508
|
+
assertBuffer(data, "data");
|
|
509
|
+
assertBuffer(expectedTag, "expectedTag");
|
|
510
|
+
return native.hmacSha256Verify(key, data, expectedTag);
|
|
511
|
+
},
|
|
512
|
+
/** Tag size in bytes (32). */
|
|
513
|
+
TAG_SIZE: HMAC_SHA256_TAG_SIZE
|
|
514
|
+
});
|
|
515
|
+
var SHA256 = Object.freeze({
|
|
516
|
+
/**
|
|
517
|
+
* Compute SHA-256 hash of `data`.
|
|
518
|
+
*
|
|
519
|
+
* @param data - Data to hash
|
|
520
|
+
* @returns 32-byte digest
|
|
521
|
+
*/
|
|
522
|
+
hash(data) {
|
|
523
|
+
assertBuffer(data, "data");
|
|
524
|
+
return native.sha256(data);
|
|
525
|
+
},
|
|
526
|
+
/**
|
|
527
|
+
* Hash multiple Buffers concatenated together.
|
|
528
|
+
*
|
|
529
|
+
* Equivalent to `SHA256.hash(Buffer.concat([...]))` but slightly more
|
|
530
|
+
* efficient (avoids the intermediate concat).
|
|
531
|
+
*/
|
|
532
|
+
hashAll(buffers) {
|
|
533
|
+
if (!Array.isArray(buffers)) {
|
|
534
|
+
throw new TypeError("buffers must be an array");
|
|
535
|
+
}
|
|
536
|
+
for (let i = 0; i < buffers.length; i++) {
|
|
537
|
+
assertBuffer(buffers[i], `buffers[${i}]`);
|
|
538
|
+
}
|
|
539
|
+
return this.hash(Buffer.concat(buffers));
|
|
540
|
+
},
|
|
541
|
+
/** Output size in bytes (32). */
|
|
542
|
+
OUTPUT_SIZE: SHA256_OUTPUT_SIZE
|
|
543
|
+
});
|
|
544
|
+
var nativeVersion = native.version();
|
|
545
|
+
|
|
546
|
+
// src/types.ts
|
|
547
|
+
function asPublicKey(buf) {
|
|
548
|
+
return buf;
|
|
549
|
+
}
|
|
550
|
+
function asPrivateKey(buf) {
|
|
551
|
+
return buf;
|
|
552
|
+
}
|
|
553
|
+
function asSharedSecret(buf) {
|
|
554
|
+
return buf;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// src/utils.ts
|
|
558
|
+
var import_node_crypto = require("crypto");
|
|
559
|
+
function secureRandom(length) {
|
|
560
|
+
if (!Number.isInteger(length) || length < 0) {
|
|
561
|
+
throw new RangeError(`length must be a non-negative integer, got ${length}`);
|
|
562
|
+
}
|
|
563
|
+
return (0, import_node_crypto.randomBytes)(length);
|
|
564
|
+
}
|
|
565
|
+
function randomNonce() {
|
|
566
|
+
return (0, import_node_crypto.randomBytes)(12);
|
|
567
|
+
}
|
|
568
|
+
function randomIv() {
|
|
569
|
+
return (0, import_node_crypto.randomBytes)(16);
|
|
570
|
+
}
|
|
571
|
+
function randomKey() {
|
|
572
|
+
return (0, import_node_crypto.randomBytes)(32);
|
|
573
|
+
}
|
|
574
|
+
function toHex(buf) {
|
|
575
|
+
return buf.toString("hex");
|
|
576
|
+
}
|
|
577
|
+
function fromHex(hex) {
|
|
578
|
+
if (!/^[0-9a-fA-F]*$/.test(hex)) {
|
|
579
|
+
throw new Error("Invalid hex string: contains non-hex characters");
|
|
580
|
+
}
|
|
581
|
+
if (hex.length % 2 !== 0) {
|
|
582
|
+
throw new Error("Invalid hex string: odd number of characters");
|
|
583
|
+
}
|
|
584
|
+
return Buffer.from(hex, "hex");
|
|
585
|
+
}
|
|
586
|
+
function toBase64(buf) {
|
|
587
|
+
return buf.toString("base64");
|
|
588
|
+
}
|
|
589
|
+
function fromBase64(b64) {
|
|
590
|
+
return Buffer.from(b64, "base64");
|
|
591
|
+
}
|
|
592
|
+
function toBase64Url(buf) {
|
|
593
|
+
return buf.toString("base64url");
|
|
594
|
+
}
|
|
595
|
+
function fromBase64Url(b64url) {
|
|
596
|
+
return Buffer.from(b64url, "base64url");
|
|
597
|
+
}
|
|
598
|
+
function bufferToString(buf, encoding = "hex") {
|
|
599
|
+
return buf.toString(encoding);
|
|
600
|
+
}
|
|
601
|
+
function stringToBuffer(str, encoding = "hex") {
|
|
602
|
+
return Buffer.from(str, encoding);
|
|
603
|
+
}
|
|
604
|
+
function constantTimeEqual(a, b) {
|
|
605
|
+
if (a.length !== b.length) {
|
|
606
|
+
(0, import_node_crypto.timingSafeEqual)(a, a);
|
|
607
|
+
return false;
|
|
608
|
+
}
|
|
609
|
+
return (0, import_node_crypto.timingSafeEqual)(a, b);
|
|
610
|
+
}
|
|
611
|
+
function concat(buffers) {
|
|
612
|
+
return Buffer.concat(buffers);
|
|
613
|
+
}
|
|
614
|
+
function zeroize(buf) {
|
|
615
|
+
buf.fill(0);
|
|
616
|
+
}
|
|
617
|
+
function xor(a, b) {
|
|
618
|
+
if (a.length !== b.length) {
|
|
619
|
+
throw new Error(`XOR operands must have equal length: ${a.length} vs ${b.length}`);
|
|
620
|
+
}
|
|
621
|
+
const result = Buffer.alloc(a.length);
|
|
622
|
+
for (let i = 0; i < a.length; i++) {
|
|
623
|
+
result[i] = a[i] ^ b[i];
|
|
624
|
+
}
|
|
625
|
+
return result;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// src/index.ts
|
|
629
|
+
var VERSION = "0.1.0";
|
|
630
|
+
var SignalisCore = Object.freeze({
|
|
631
|
+
// Crypto primitives
|
|
632
|
+
Curve25519,
|
|
633
|
+
HKDF,
|
|
634
|
+
AES_GCM,
|
|
635
|
+
AES_CBC,
|
|
636
|
+
HMAC,
|
|
637
|
+
SHA256,
|
|
638
|
+
// Random
|
|
639
|
+
secureRandom,
|
|
640
|
+
randomNonce,
|
|
641
|
+
randomIv,
|
|
642
|
+
randomKey,
|
|
643
|
+
// Encoding
|
|
644
|
+
toHex,
|
|
645
|
+
fromHex,
|
|
646
|
+
toBase64,
|
|
647
|
+
fromBase64,
|
|
648
|
+
// Security
|
|
649
|
+
constantTimeEqual,
|
|
650
|
+
// Version
|
|
651
|
+
VERSION: "0.1.0",
|
|
652
|
+
nativeVersion
|
|
653
|
+
});
|
|
654
|
+
var index_default = SignalisCore;
|
|
655
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
656
|
+
0 && (module.exports = {
|
|
657
|
+
AES_256_CBC_IV_SIZE,
|
|
658
|
+
AES_256_GCM_NONCE_SIZE,
|
|
659
|
+
AES_256_GCM_TAG_SIZE,
|
|
660
|
+
AES_256_KEY_SIZE,
|
|
661
|
+
AES_BLOCK_SIZE,
|
|
662
|
+
AES_CBC,
|
|
663
|
+
AES_GCM,
|
|
664
|
+
AES_GCM_MAX_PLAINTEXT_SIZE,
|
|
665
|
+
AES_GCM_RECOMMENDED_MESSAGES_PER_KEY,
|
|
666
|
+
AuthenticationError,
|
|
667
|
+
CURVE25519_PRIVATE_KEY_SIZE,
|
|
668
|
+
CURVE25519_PUBLIC_KEY_SIZE,
|
|
669
|
+
CURVE25519_SHARED_SECRET_SIZE,
|
|
670
|
+
CryptoError,
|
|
671
|
+
Curve25519,
|
|
672
|
+
HKDF,
|
|
673
|
+
HKDF_MAX_OUTPUT_SIZE,
|
|
674
|
+
HKDF_PRK_SIZE,
|
|
675
|
+
HMAC,
|
|
676
|
+
HMAC_SHA256_TAG_SIZE,
|
|
677
|
+
KeyDerivationError,
|
|
678
|
+
LengthError,
|
|
679
|
+
SHA256,
|
|
680
|
+
SHA256_BLOCK_SIZE,
|
|
681
|
+
SHA256_OUTPUT_SIZE,
|
|
682
|
+
SignalisError,
|
|
683
|
+
VERSION,
|
|
684
|
+
ValidationError,
|
|
685
|
+
asPrivateKey,
|
|
686
|
+
asPublicKey,
|
|
687
|
+
asSharedSecret,
|
|
688
|
+
assertBuffer,
|
|
689
|
+
assertBufferLength,
|
|
690
|
+
assertBufferOfSize,
|
|
691
|
+
assertHkdfLength,
|
|
692
|
+
assertPositiveInteger,
|
|
693
|
+
bufferToString,
|
|
694
|
+
buffersSameLength,
|
|
695
|
+
concat,
|
|
696
|
+
constantTimeEqual,
|
|
697
|
+
fromBase64,
|
|
698
|
+
fromBase64Url,
|
|
699
|
+
fromHex,
|
|
700
|
+
nativeVersion,
|
|
701
|
+
randomIv,
|
|
702
|
+
randomKey,
|
|
703
|
+
randomNonce,
|
|
704
|
+
secureRandom,
|
|
705
|
+
stringToBuffer,
|
|
706
|
+
toBase64,
|
|
707
|
+
toBase64Url,
|
|
708
|
+
toHex,
|
|
709
|
+
xor,
|
|
710
|
+
zeroize
|
|
711
|
+
});
|
|
712
|
+
//# sourceMappingURL=index.cjs.map
|