@didcid/gatekeeper 0.3.7 → 0.4.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/cjs/gatekeeper.cjs +1022 -36
- package/package.json +3 -3
package/dist/cjs/gatekeeper.cjs
CHANGED
|
@@ -30534,6 +30534,11 @@ function requireBn () {
|
|
|
30534
30534
|
this.words[this.length - 1] &= mask;
|
|
30535
30535
|
}
|
|
30536
30536
|
|
|
30537
|
+
if (this.length === 0) {
|
|
30538
|
+
this.words[0] = 0;
|
|
30539
|
+
this.length = 1;
|
|
30540
|
+
}
|
|
30541
|
+
|
|
30537
30542
|
return this.strip();
|
|
30538
30543
|
};
|
|
30539
30544
|
|
|
@@ -38283,7 +38288,7 @@ const toU8 = (a, len) => abytes$1(isStr(a) ? hexToBytes(a) : u8fr(abytes$1(a)),
|
|
|
38283
38288
|
const cr = () => globalThis?.crypto; // WebCrypto is available in all modern environments
|
|
38284
38289
|
const subtle = () => cr()?.subtle ?? err('crypto.subtle must be defined');
|
|
38285
38290
|
// prettier-ignore
|
|
38286
|
-
const concatBytes$
|
|
38291
|
+
const concatBytes$2 = (...arrs) => {
|
|
38287
38292
|
const r = u8n(arrs.reduce((sum, a) => sum + abytes$1(a).length, 0)); // create u8a of summed length
|
|
38288
38293
|
let pad = 0; // walk through each array,
|
|
38289
38294
|
arrs.forEach(a => { r.set(a, pad); pad += a.length; }); // ensure they have proper type
|
|
@@ -38524,8 +38529,8 @@ class Point {
|
|
|
38524
38529
|
const { x, y } = this.assertValidity().toAffine();
|
|
38525
38530
|
const x32b = numTo32b(x);
|
|
38526
38531
|
if (isCompressed)
|
|
38527
|
-
return concatBytes$
|
|
38528
|
-
return concatBytes$
|
|
38532
|
+
return concatBytes$2(getPrefix(y), x32b);
|
|
38533
|
+
return concatBytes$2(u8of(0x04), x32b, numTo32b(y));
|
|
38529
38534
|
}
|
|
38530
38535
|
/** Create 3d xyz point from 2d xy. (0, 0) => (0, 1, 0), not (0, 0, 1) */
|
|
38531
38536
|
static fromAffine(ap) {
|
|
@@ -38599,7 +38604,7 @@ class Signature {
|
|
|
38599
38604
|
}
|
|
38600
38605
|
toBytes() {
|
|
38601
38606
|
const { r, s } = this;
|
|
38602
|
-
return concatBytes$
|
|
38607
|
+
return concatBytes$2(numTo32b(r), numTo32b(s));
|
|
38603
38608
|
}
|
|
38604
38609
|
/** Copy signature, with newly added recovery bit. */
|
|
38605
38610
|
addRecoveryBit(bit) {
|
|
@@ -38691,7 +38696,7 @@ const prepSig = (msgh, priv, opts = signOpts) => {
|
|
|
38691
38696
|
}
|
|
38692
38697
|
return new Signature(r, normS, recovery); // use normS, not s
|
|
38693
38698
|
};
|
|
38694
|
-
return { seed: concatBytes$
|
|
38699
|
+
return { seed: concatBytes$2(...seed), k2sig };
|
|
38695
38700
|
};
|
|
38696
38701
|
// HMAC-DRBG from NIST 800-90. Minimal, non-full-spec - used for RFC6979 signatures.
|
|
38697
38702
|
const hmacDrbg = (asynchronous) => {
|
|
@@ -38809,7 +38814,7 @@ const recoverPublicKey = (sig, msgh) => {
|
|
|
38809
38814
|
const radj = recovery === 2 || recovery === 3 ? r + N : r;
|
|
38810
38815
|
afield(radj); // ensure q.x is still a field element
|
|
38811
38816
|
const head = getPrefix(big(recovery)); // head is 0x02 or 0x03
|
|
38812
|
-
const Rb = concatBytes$
|
|
38817
|
+
const Rb = concatBytes$2(head, numTo32b(radj)); // concat head + r
|
|
38813
38818
|
const R = Point.fromBytes(Rb);
|
|
38814
38819
|
const ir = invert(radj, N); // r^-1
|
|
38815
38820
|
const u1 = modN(-h * ir); // -hr^-1
|
|
@@ -38842,7 +38847,7 @@ const _sha = 'SHA-256';
|
|
|
38842
38847
|
const etc = {
|
|
38843
38848
|
hexToBytes: hexToBytes,
|
|
38844
38849
|
bytesToHex: bytesToHex,
|
|
38845
|
-
concatBytes: concatBytes$
|
|
38850
|
+
concatBytes: concatBytes$2,
|
|
38846
38851
|
bytesToNumberBE: bytesToNumBE,
|
|
38847
38852
|
numberToBytesBE: numTo32b,
|
|
38848
38853
|
mod: M,
|
|
@@ -38851,7 +38856,7 @@ const etc = {
|
|
|
38851
38856
|
const s = subtle();
|
|
38852
38857
|
const name = 'HMAC';
|
|
38853
38858
|
const k = await s.importKey('raw', key, { name, hash: { name: _sha } }, false, ['sign']);
|
|
38854
|
-
return u8n(await s.sign(name, k, concatBytes$
|
|
38859
|
+
return u8n(await s.sign(name, k, concatBytes$2(...msgs)));
|
|
38855
38860
|
},
|
|
38856
38861
|
hmacSha256Sync: undefined, // For TypeScript. Actual logic is below
|
|
38857
38862
|
hashToPrivateKey: hashToPrivateKey,
|
|
@@ -39387,6 +39392,7 @@ const sha256$1 = sha256$2;
|
|
|
39387
39392
|
|
|
39388
39393
|
/*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */
|
|
39389
39394
|
// Cast array to different type
|
|
39395
|
+
const u8 = (arr) => new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
39390
39396
|
const u32 = (arr) => new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
|
|
39391
39397
|
function isBytes$1(a) {
|
|
39392
39398
|
return (a instanceof Uint8Array ||
|
|
@@ -39427,7 +39433,7 @@ function toBytes(data) {
|
|
|
39427
39433
|
/**
|
|
39428
39434
|
* Copies several Uint8Arrays into one.
|
|
39429
39435
|
*/
|
|
39430
|
-
function concatBytes(...arrays) {
|
|
39436
|
+
function concatBytes$1(...arrays) {
|
|
39431
39437
|
let sum = 0;
|
|
39432
39438
|
for (let i = 0; i < arrays.length; i++) {
|
|
39433
39439
|
const a = arrays[i];
|
|
@@ -39479,8 +39485,8 @@ function setBigUint64(view, byteOffset, value, isLE) {
|
|
|
39479
39485
|
const _u32_max = BigInt(0xffffffff);
|
|
39480
39486
|
const wh = Number((value >> _32n) & _u32_max);
|
|
39481
39487
|
const wl = Number(value & _u32_max);
|
|
39482
|
-
const h = 4 ;
|
|
39483
|
-
const l = 0 ;
|
|
39488
|
+
const h = isLE ? 4 : 0;
|
|
39489
|
+
const l = isLE ? 0 : 4;
|
|
39484
39490
|
view.setUint32(byteOffset + h, wh, isLE);
|
|
39485
39491
|
view.setUint32(byteOffset + l, wl, isLE);
|
|
39486
39492
|
}
|
|
@@ -39771,7 +39777,7 @@ class Poly1305 {
|
|
|
39771
39777
|
return res;
|
|
39772
39778
|
}
|
|
39773
39779
|
}
|
|
39774
|
-
function wrapConstructorWithKey(hashCons) {
|
|
39780
|
+
function wrapConstructorWithKey$1(hashCons) {
|
|
39775
39781
|
const hashC = (msg, key) => hashCons(key).update(toBytes(msg)).digest();
|
|
39776
39782
|
const tmp = hashCons(new Uint8Array(32));
|
|
39777
39783
|
hashC.outputLen = tmp.outputLen;
|
|
@@ -39779,7 +39785,7 @@ function wrapConstructorWithKey(hashCons) {
|
|
|
39779
39785
|
hashC.create = (key) => hashCons(key);
|
|
39780
39786
|
return hashC;
|
|
39781
39787
|
}
|
|
39782
|
-
const poly1305 = wrapConstructorWithKey((key) => new Poly1305(key));
|
|
39788
|
+
const poly1305 = wrapConstructorWithKey$1((key) => new Poly1305(key));
|
|
39783
39789
|
|
|
39784
39790
|
// Basic utils for ARX (add-rotate-xor) salsa and chacha ciphers.
|
|
39785
39791
|
/*
|
|
@@ -40140,17 +40146,17 @@ const xchacha20 = /* @__PURE__ */ createCipher(chachaCore, {
|
|
|
40140
40146
|
extendNonceFn: hchacha,
|
|
40141
40147
|
allowShortKeys: false,
|
|
40142
40148
|
});
|
|
40143
|
-
const ZEROS16 = /* @__PURE__ */ new Uint8Array(16);
|
|
40149
|
+
const ZEROS16$1 = /* @__PURE__ */ new Uint8Array(16);
|
|
40144
40150
|
// Pad to digest size with zeros
|
|
40145
40151
|
const updatePadded = (h, msg) => {
|
|
40146
40152
|
h.update(msg);
|
|
40147
40153
|
const left = msg.length % 16;
|
|
40148
40154
|
if (left)
|
|
40149
|
-
h.update(ZEROS16.subarray(left));
|
|
40155
|
+
h.update(ZEROS16$1.subarray(left));
|
|
40150
40156
|
};
|
|
40151
|
-
const ZEROS32 = /* @__PURE__ */ new Uint8Array(32);
|
|
40152
|
-
function computeTag(fn, key, nonce, data, AAD) {
|
|
40153
|
-
const authKey = fn(key, nonce, ZEROS32);
|
|
40157
|
+
const ZEROS32$1 = /* @__PURE__ */ new Uint8Array(32);
|
|
40158
|
+
function computeTag$1(fn, key, nonce, data, AAD) {
|
|
40159
|
+
const authKey = fn(key, nonce, ZEROS32$1);
|
|
40154
40160
|
const h = poly1305.create(authKey);
|
|
40155
40161
|
if (AAD)
|
|
40156
40162
|
updatePadded(h, AAD);
|
|
@@ -40188,7 +40194,7 @@ const _poly1305_aead = (xorStream) => (key, nonce, AAD) => {
|
|
|
40188
40194
|
output = new Uint8Array(clength);
|
|
40189
40195
|
}
|
|
40190
40196
|
xorStream(key, nonce, plaintext, output, 1);
|
|
40191
|
-
const tag = computeTag(xorStream, key, nonce, output.subarray(0, -tagLength), AAD);
|
|
40197
|
+
const tag = computeTag$1(xorStream, key, nonce, output.subarray(0, -tagLength), AAD);
|
|
40192
40198
|
output.set(tag, plength); // append tag
|
|
40193
40199
|
return output;
|
|
40194
40200
|
},
|
|
@@ -40205,7 +40211,7 @@ const _poly1305_aead = (xorStream) => (key, nonce, AAD) => {
|
|
|
40205
40211
|
}
|
|
40206
40212
|
const data = ciphertext.subarray(0, -tagLength);
|
|
40207
40213
|
const passedTag = ciphertext.subarray(-tagLength);
|
|
40208
|
-
const tag = computeTag(xorStream, key, nonce, data, AAD);
|
|
40214
|
+
const tag = computeTag$1(xorStream, key, nonce, data, AAD);
|
|
40209
40215
|
if (!equalBytes(passedTag, tag))
|
|
40210
40216
|
throw new Error('invalid tag');
|
|
40211
40217
|
xorStream(key, nonce, data, output, 1);
|
|
@@ -40245,7 +40251,7 @@ function managedNonce(fn) {
|
|
|
40245
40251
|
const { nonceLength } = fn;
|
|
40246
40252
|
const nonce = randomBytes(nonceLength);
|
|
40247
40253
|
const ciphertext = fn(key, nonce, ...args).encrypt(plaintext, ...argsEnc);
|
|
40248
|
-
const out = concatBytes(nonce, ciphertext);
|
|
40254
|
+
const out = concatBytes$1(nonce, ciphertext);
|
|
40249
40255
|
ciphertext.fill(0);
|
|
40250
40256
|
return out;
|
|
40251
40257
|
},
|
|
@@ -40272,6 +40278,979 @@ function managedNonce(fn) {
|
|
|
40272
40278
|
// const wcbc2 = managedNonce(managedNonce(cbc));
|
|
40273
40279
|
// const wecb = managedNonce(ecb);
|
|
40274
40280
|
|
|
40281
|
+
// GHash from AES-GCM and its little-endian "mirror image" Polyval from AES-SIV.
|
|
40282
|
+
// Implemented in terms of GHash with conversion function for keys
|
|
40283
|
+
// GCM GHASH from NIST SP800-38d, SIV from RFC 8452.
|
|
40284
|
+
// https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
|
|
40285
|
+
// GHASH modulo: x^128 + x^7 + x^2 + x + 1
|
|
40286
|
+
// POLYVAL modulo: x^128 + x^127 + x^126 + x^121 + 1
|
|
40287
|
+
const BLOCK_SIZE$1 = 16;
|
|
40288
|
+
// TODO: rewrite
|
|
40289
|
+
// temporary padding buffer
|
|
40290
|
+
const ZEROS16 = /* @__PURE__ */ new Uint8Array(16);
|
|
40291
|
+
const ZEROS32 = u32(ZEROS16);
|
|
40292
|
+
const POLY$1 = 0xe1; // v = 2*v % POLY
|
|
40293
|
+
// v = 2*v % POLY
|
|
40294
|
+
// NOTE: because x + x = 0 (add/sub is same), mul2(x) != x+x
|
|
40295
|
+
// We can multiply any number using montgomery ladder and this function (works as double, add is simple xor)
|
|
40296
|
+
const mul2$1 = (s0, s1, s2, s3) => {
|
|
40297
|
+
const hiBit = s3 & 1;
|
|
40298
|
+
return {
|
|
40299
|
+
s3: (s2 << 31) | (s3 >>> 1),
|
|
40300
|
+
s2: (s1 << 31) | (s2 >>> 1),
|
|
40301
|
+
s1: (s0 << 31) | (s1 >>> 1),
|
|
40302
|
+
s0: (s0 >>> 1) ^ ((POLY$1 << 24) & -(hiBit & 1)), // reduce % poly
|
|
40303
|
+
};
|
|
40304
|
+
};
|
|
40305
|
+
const swapLE = (n) => (((n >>> 0) & 0xff) << 24) |
|
|
40306
|
+
(((n >>> 8) & 0xff) << 16) |
|
|
40307
|
+
(((n >>> 16) & 0xff) << 8) |
|
|
40308
|
+
((n >>> 24) & 0xff) |
|
|
40309
|
+
0;
|
|
40310
|
+
/**
|
|
40311
|
+
* `mulX_POLYVAL(ByteReverse(H))` from spec
|
|
40312
|
+
* @param k mutated in place
|
|
40313
|
+
*/
|
|
40314
|
+
function _toGHASHKey(k) {
|
|
40315
|
+
k.reverse();
|
|
40316
|
+
const hiBit = k[15] & 1;
|
|
40317
|
+
// k >>= 1
|
|
40318
|
+
let carry = 0;
|
|
40319
|
+
for (let i = 0; i < k.length; i++) {
|
|
40320
|
+
const t = k[i];
|
|
40321
|
+
k[i] = (t >>> 1) | carry;
|
|
40322
|
+
carry = (t & 1) << 7;
|
|
40323
|
+
}
|
|
40324
|
+
k[0] ^= -hiBit & 0xe1; // if (hiBit) n ^= 0xe1000000000000000000000000000000;
|
|
40325
|
+
return k;
|
|
40326
|
+
}
|
|
40327
|
+
const estimateWindow = (bytes) => {
|
|
40328
|
+
if (bytes > 64 * 1024)
|
|
40329
|
+
return 8;
|
|
40330
|
+
if (bytes > 1024)
|
|
40331
|
+
return 4;
|
|
40332
|
+
return 2;
|
|
40333
|
+
};
|
|
40334
|
+
class GHASH {
|
|
40335
|
+
// We select bits per window adaptively based on expectedLength
|
|
40336
|
+
constructor(key, expectedLength) {
|
|
40337
|
+
this.blockLen = BLOCK_SIZE$1;
|
|
40338
|
+
this.outputLen = BLOCK_SIZE$1;
|
|
40339
|
+
this.s0 = 0;
|
|
40340
|
+
this.s1 = 0;
|
|
40341
|
+
this.s2 = 0;
|
|
40342
|
+
this.s3 = 0;
|
|
40343
|
+
this.finished = false;
|
|
40344
|
+
key = toBytes(key);
|
|
40345
|
+
ensureBytes(key, 16);
|
|
40346
|
+
const kView = createView(key);
|
|
40347
|
+
let k0 = kView.getUint32(0, false);
|
|
40348
|
+
let k1 = kView.getUint32(4, false);
|
|
40349
|
+
let k2 = kView.getUint32(8, false);
|
|
40350
|
+
let k3 = kView.getUint32(12, false);
|
|
40351
|
+
// generate table of doubled keys (half of montgomery ladder)
|
|
40352
|
+
const doubles = [];
|
|
40353
|
+
for (let i = 0; i < 128; i++) {
|
|
40354
|
+
doubles.push({ s0: swapLE(k0), s1: swapLE(k1), s2: swapLE(k2), s3: swapLE(k3) });
|
|
40355
|
+
({ s0: k0, s1: k1, s2: k2, s3: k3 } = mul2$1(k0, k1, k2, k3));
|
|
40356
|
+
}
|
|
40357
|
+
const W = estimateWindow(expectedLength || 1024);
|
|
40358
|
+
if (![1, 2, 4, 8].includes(W))
|
|
40359
|
+
throw new Error(`ghash: wrong window size=${W}, should be 2, 4 or 8`);
|
|
40360
|
+
this.W = W;
|
|
40361
|
+
const bits = 128; // always 128 bits;
|
|
40362
|
+
const windows = bits / W;
|
|
40363
|
+
const windowSize = (this.windowSize = 2 ** W);
|
|
40364
|
+
const items = [];
|
|
40365
|
+
// Create precompute table for window of W bits
|
|
40366
|
+
for (let w = 0; w < windows; w++) {
|
|
40367
|
+
// truth table: 00, 01, 10, 11
|
|
40368
|
+
for (let byte = 0; byte < windowSize; byte++) {
|
|
40369
|
+
// prettier-ignore
|
|
40370
|
+
let s0 = 0, s1 = 0, s2 = 0, s3 = 0;
|
|
40371
|
+
for (let j = 0; j < W; j++) {
|
|
40372
|
+
const bit = (byte >>> (W - j - 1)) & 1;
|
|
40373
|
+
if (!bit)
|
|
40374
|
+
continue;
|
|
40375
|
+
const { s0: d0, s1: d1, s2: d2, s3: d3 } = doubles[W * w + j];
|
|
40376
|
+
(s0 ^= d0), (s1 ^= d1), (s2 ^= d2), (s3 ^= d3);
|
|
40377
|
+
}
|
|
40378
|
+
items.push({ s0, s1, s2, s3 });
|
|
40379
|
+
}
|
|
40380
|
+
}
|
|
40381
|
+
this.t = items;
|
|
40382
|
+
}
|
|
40383
|
+
_updateBlock(s0, s1, s2, s3) {
|
|
40384
|
+
(s0 ^= this.s0), (s1 ^= this.s1), (s2 ^= this.s2), (s3 ^= this.s3);
|
|
40385
|
+
const { W, t, windowSize } = this;
|
|
40386
|
+
// prettier-ignore
|
|
40387
|
+
let o0 = 0, o1 = 0, o2 = 0, o3 = 0;
|
|
40388
|
+
const mask = (1 << W) - 1; // 2**W will kill performance.
|
|
40389
|
+
let w = 0;
|
|
40390
|
+
for (const num of [s0, s1, s2, s3]) {
|
|
40391
|
+
for (let bytePos = 0; bytePos < 4; bytePos++) {
|
|
40392
|
+
const byte = (num >>> (8 * bytePos)) & 0xff;
|
|
40393
|
+
for (let bitPos = 8 / W - 1; bitPos >= 0; bitPos--) {
|
|
40394
|
+
const bit = (byte >>> (W * bitPos)) & mask;
|
|
40395
|
+
const { s0: e0, s1: e1, s2: e2, s3: e3 } = t[w * windowSize + bit];
|
|
40396
|
+
(o0 ^= e0), (o1 ^= e1), (o2 ^= e2), (o3 ^= e3);
|
|
40397
|
+
w += 1;
|
|
40398
|
+
}
|
|
40399
|
+
}
|
|
40400
|
+
}
|
|
40401
|
+
this.s0 = o0;
|
|
40402
|
+
this.s1 = o1;
|
|
40403
|
+
this.s2 = o2;
|
|
40404
|
+
this.s3 = o3;
|
|
40405
|
+
}
|
|
40406
|
+
update(data) {
|
|
40407
|
+
data = toBytes(data);
|
|
40408
|
+
exists(this);
|
|
40409
|
+
const b32 = u32(data);
|
|
40410
|
+
const blocks = Math.floor(data.length / BLOCK_SIZE$1);
|
|
40411
|
+
const left = data.length % BLOCK_SIZE$1;
|
|
40412
|
+
for (let i = 0; i < blocks; i++) {
|
|
40413
|
+
this._updateBlock(b32[i * 4 + 0], b32[i * 4 + 1], b32[i * 4 + 2], b32[i * 4 + 3]);
|
|
40414
|
+
}
|
|
40415
|
+
if (left) {
|
|
40416
|
+
ZEROS16.set(data.subarray(blocks * BLOCK_SIZE$1));
|
|
40417
|
+
this._updateBlock(ZEROS32[0], ZEROS32[1], ZEROS32[2], ZEROS32[3]);
|
|
40418
|
+
ZEROS32.fill(0); // clean tmp buffer
|
|
40419
|
+
}
|
|
40420
|
+
return this;
|
|
40421
|
+
}
|
|
40422
|
+
destroy() {
|
|
40423
|
+
const { t } = this;
|
|
40424
|
+
// clean precompute table
|
|
40425
|
+
for (const elm of t) {
|
|
40426
|
+
(elm.s0 = 0), (elm.s1 = 0), (elm.s2 = 0), (elm.s3 = 0);
|
|
40427
|
+
}
|
|
40428
|
+
}
|
|
40429
|
+
digestInto(out) {
|
|
40430
|
+
exists(this);
|
|
40431
|
+
output(out, this);
|
|
40432
|
+
this.finished = true;
|
|
40433
|
+
const { s0, s1, s2, s3 } = this;
|
|
40434
|
+
const o32 = u32(out);
|
|
40435
|
+
o32[0] = s0;
|
|
40436
|
+
o32[1] = s1;
|
|
40437
|
+
o32[2] = s2;
|
|
40438
|
+
o32[3] = s3;
|
|
40439
|
+
return out;
|
|
40440
|
+
}
|
|
40441
|
+
digest() {
|
|
40442
|
+
const res = new Uint8Array(BLOCK_SIZE$1);
|
|
40443
|
+
this.digestInto(res);
|
|
40444
|
+
this.destroy();
|
|
40445
|
+
return res;
|
|
40446
|
+
}
|
|
40447
|
+
}
|
|
40448
|
+
class Polyval extends GHASH {
|
|
40449
|
+
constructor(key, expectedLength) {
|
|
40450
|
+
key = toBytes(key);
|
|
40451
|
+
const ghKey = _toGHASHKey(key.slice());
|
|
40452
|
+
super(ghKey, expectedLength);
|
|
40453
|
+
ghKey.fill(0);
|
|
40454
|
+
}
|
|
40455
|
+
update(data) {
|
|
40456
|
+
data = toBytes(data);
|
|
40457
|
+
exists(this);
|
|
40458
|
+
const b32 = u32(data);
|
|
40459
|
+
const left = data.length % BLOCK_SIZE$1;
|
|
40460
|
+
const blocks = Math.floor(data.length / BLOCK_SIZE$1);
|
|
40461
|
+
for (let i = 0; i < blocks; i++) {
|
|
40462
|
+
this._updateBlock(swapLE(b32[i * 4 + 3]), swapLE(b32[i * 4 + 2]), swapLE(b32[i * 4 + 1]), swapLE(b32[i * 4 + 0]));
|
|
40463
|
+
}
|
|
40464
|
+
if (left) {
|
|
40465
|
+
ZEROS16.set(data.subarray(blocks * BLOCK_SIZE$1));
|
|
40466
|
+
this._updateBlock(swapLE(ZEROS32[3]), swapLE(ZEROS32[2]), swapLE(ZEROS32[1]), swapLE(ZEROS32[0]));
|
|
40467
|
+
ZEROS32.fill(0); // clean tmp buffer
|
|
40468
|
+
}
|
|
40469
|
+
return this;
|
|
40470
|
+
}
|
|
40471
|
+
digestInto(out) {
|
|
40472
|
+
exists(this);
|
|
40473
|
+
output(out, this);
|
|
40474
|
+
this.finished = true;
|
|
40475
|
+
// tmp ugly hack
|
|
40476
|
+
const { s0, s1, s2, s3 } = this;
|
|
40477
|
+
const o32 = u32(out);
|
|
40478
|
+
o32[0] = s0;
|
|
40479
|
+
o32[1] = s1;
|
|
40480
|
+
o32[2] = s2;
|
|
40481
|
+
o32[3] = s3;
|
|
40482
|
+
return out.reverse();
|
|
40483
|
+
}
|
|
40484
|
+
}
|
|
40485
|
+
function wrapConstructorWithKey(hashCons) {
|
|
40486
|
+
const hashC = (msg, key) => hashCons(key, msg.length).update(toBytes(msg)).digest();
|
|
40487
|
+
const tmp = hashCons(new Uint8Array(16), 0);
|
|
40488
|
+
hashC.outputLen = tmp.outputLen;
|
|
40489
|
+
hashC.blockLen = tmp.blockLen;
|
|
40490
|
+
hashC.create = (key, expectedLength) => hashCons(key, expectedLength);
|
|
40491
|
+
return hashC;
|
|
40492
|
+
}
|
|
40493
|
+
const ghash = wrapConstructorWithKey((key, expectedLength) => new GHASH(key, expectedLength));
|
|
40494
|
+
const polyval = wrapConstructorWithKey((key, expectedLength) => new Polyval(key, expectedLength));
|
|
40495
|
+
|
|
40496
|
+
// AES (Advanced Encryption Standard) aka Rijndael block cipher.
|
|
40497
|
+
//
|
|
40498
|
+
// Data is split into 128-bit blocks. Encrypted in 10/12/14 rounds (128/192/256bit). Every round:
|
|
40499
|
+
// 1. **S-box**, table substitution
|
|
40500
|
+
// 2. **Shift rows**, cyclic shift left of all rows of data array
|
|
40501
|
+
// 3. **Mix columns**, multiplying every column by fixed polynomial
|
|
40502
|
+
// 4. **Add round key**, round_key xor i-th column of array
|
|
40503
|
+
//
|
|
40504
|
+
// Resources:
|
|
40505
|
+
// - FIPS-197 https://csrc.nist.gov/files/pubs/fips/197/final/docs/fips-197.pdf
|
|
40506
|
+
// - Original proposal: https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/aes-development/rijndael-ammended.pdf
|
|
40507
|
+
const BLOCK_SIZE = 16;
|
|
40508
|
+
const BLOCK_SIZE32 = 4;
|
|
40509
|
+
const EMPTY_BLOCK = new Uint8Array(BLOCK_SIZE);
|
|
40510
|
+
const POLY = 0x11b; // 1 + x + x**3 + x**4 + x**8
|
|
40511
|
+
// TODO: remove multiplication, binary ops only
|
|
40512
|
+
function mul2(n) {
|
|
40513
|
+
return (n << 1) ^ (POLY & -(n >> 7));
|
|
40514
|
+
}
|
|
40515
|
+
function mul(a, b) {
|
|
40516
|
+
let res = 0;
|
|
40517
|
+
for (; b > 0; b >>= 1) {
|
|
40518
|
+
// Montgomery ladder
|
|
40519
|
+
res ^= a & -(b & 1); // if (b&1) res ^=a (but const-time).
|
|
40520
|
+
a = mul2(a); // a = 2*a
|
|
40521
|
+
}
|
|
40522
|
+
return res;
|
|
40523
|
+
}
|
|
40524
|
+
// AES S-box is generated using finite field inversion,
|
|
40525
|
+
// an affine transform, and xor of a constant 0x63.
|
|
40526
|
+
const _sbox = /* @__PURE__ */ (() => {
|
|
40527
|
+
let t = new Uint8Array(256);
|
|
40528
|
+
for (let i = 0, x = 1; i < 256; i++, x ^= mul2(x))
|
|
40529
|
+
t[i] = x;
|
|
40530
|
+
const sbox = new Uint8Array(256);
|
|
40531
|
+
sbox[0] = 0x63; // first elm
|
|
40532
|
+
for (let i = 0; i < 255; i++) {
|
|
40533
|
+
let x = t[255 - i];
|
|
40534
|
+
x |= x << 8;
|
|
40535
|
+
sbox[t[i]] = (x ^ (x >> 4) ^ (x >> 5) ^ (x >> 6) ^ (x >> 7) ^ 0x63) & 0xff;
|
|
40536
|
+
}
|
|
40537
|
+
return sbox;
|
|
40538
|
+
})();
|
|
40539
|
+
// Inverted S-box
|
|
40540
|
+
const _inv_sbox = /* @__PURE__ */ _sbox.map((_, j) => _sbox.indexOf(j));
|
|
40541
|
+
// Rotate u32 by 8
|
|
40542
|
+
const rotr32_8 = (n) => (n << 24) | (n >>> 8);
|
|
40543
|
+
const rotl32_8 = (n) => (n << 8) | (n >>> 24);
|
|
40544
|
+
// T-table is optimization suggested in 5.2 of original proposal (missed from FIPS-197). Changes:
|
|
40545
|
+
// - LE instead of BE
|
|
40546
|
+
// - bigger tables: T0 and T1 are merged into T01 table and T2 & T3 into T23;
|
|
40547
|
+
// so index is u16, instead of u8. This speeds up things, unexpectedly
|
|
40548
|
+
function genTtable(sbox, fn) {
|
|
40549
|
+
if (sbox.length !== 256)
|
|
40550
|
+
throw new Error('Wrong sbox length');
|
|
40551
|
+
const T0 = new Uint32Array(256).map((_, j) => fn(sbox[j]));
|
|
40552
|
+
const T1 = T0.map(rotl32_8);
|
|
40553
|
+
const T2 = T1.map(rotl32_8);
|
|
40554
|
+
const T3 = T2.map(rotl32_8);
|
|
40555
|
+
const T01 = new Uint32Array(256 * 256);
|
|
40556
|
+
const T23 = new Uint32Array(256 * 256);
|
|
40557
|
+
const sbox2 = new Uint16Array(256 * 256);
|
|
40558
|
+
for (let i = 0; i < 256; i++) {
|
|
40559
|
+
for (let j = 0; j < 256; j++) {
|
|
40560
|
+
const idx = i * 256 + j;
|
|
40561
|
+
T01[idx] = T0[i] ^ T1[j];
|
|
40562
|
+
T23[idx] = T2[i] ^ T3[j];
|
|
40563
|
+
sbox2[idx] = (sbox[i] << 8) | sbox[j];
|
|
40564
|
+
}
|
|
40565
|
+
}
|
|
40566
|
+
return { sbox, sbox2, T0, T1, T2, T3, T01, T23 };
|
|
40567
|
+
}
|
|
40568
|
+
const TABLE_ENC = /* @__PURE__ */ genTtable(_sbox, (s) => (mul(s, 3) << 24) | (s << 16) | (s << 8) | mul(s, 2));
|
|
40569
|
+
const TABLE_DEC = /* @__PURE__ */ genTtable(_inv_sbox, (s) => (mul(s, 11) << 24) | (mul(s, 13) << 16) | (mul(s, 9) << 8) | mul(s, 14));
|
|
40570
|
+
const POWX = /* @__PURE__ */ (() => {
|
|
40571
|
+
const p = new Uint8Array(16);
|
|
40572
|
+
for (let i = 0, x = 1; i < 16; i++, x = mul2(x))
|
|
40573
|
+
p[i] = x;
|
|
40574
|
+
return p;
|
|
40575
|
+
})();
|
|
40576
|
+
function expandKeyLE(key) {
|
|
40577
|
+
ensureBytes(key);
|
|
40578
|
+
const len = key.length;
|
|
40579
|
+
if (![16, 24, 32].includes(len))
|
|
40580
|
+
throw new Error(`aes: wrong key size: should be 16, 24 or 32, got: ${len}`);
|
|
40581
|
+
const { sbox2 } = TABLE_ENC;
|
|
40582
|
+
const k32 = u32(key);
|
|
40583
|
+
const Nk = k32.length;
|
|
40584
|
+
const subByte = (n) => applySbox(sbox2, n, n, n, n);
|
|
40585
|
+
const xk = new Uint32Array(len + 28); // expanded key
|
|
40586
|
+
xk.set(k32);
|
|
40587
|
+
// 4.3.1 Key expansion
|
|
40588
|
+
for (let i = Nk; i < xk.length; i++) {
|
|
40589
|
+
let t = xk[i - 1];
|
|
40590
|
+
if (i % Nk === 0)
|
|
40591
|
+
t = subByte(rotr32_8(t)) ^ POWX[i / Nk - 1];
|
|
40592
|
+
else if (Nk > 6 && i % Nk === 4)
|
|
40593
|
+
t = subByte(t);
|
|
40594
|
+
xk[i] = xk[i - Nk] ^ t;
|
|
40595
|
+
}
|
|
40596
|
+
return xk;
|
|
40597
|
+
}
|
|
40598
|
+
function expandKeyDecLE(key) {
|
|
40599
|
+
const encKey = expandKeyLE(key);
|
|
40600
|
+
const xk = encKey.slice();
|
|
40601
|
+
const Nk = encKey.length;
|
|
40602
|
+
const { sbox2 } = TABLE_ENC;
|
|
40603
|
+
const { T0, T1, T2, T3 } = TABLE_DEC;
|
|
40604
|
+
// Inverse key by chunks of 4 (rounds)
|
|
40605
|
+
for (let i = 0; i < Nk; i += 4) {
|
|
40606
|
+
for (let j = 0; j < 4; j++)
|
|
40607
|
+
xk[i + j] = encKey[Nk - i - 4 + j];
|
|
40608
|
+
}
|
|
40609
|
+
encKey.fill(0);
|
|
40610
|
+
// apply InvMixColumn except first & last round
|
|
40611
|
+
for (let i = 4; i < Nk - 4; i++) {
|
|
40612
|
+
const x = xk[i];
|
|
40613
|
+
const w = applySbox(sbox2, x, x, x, x);
|
|
40614
|
+
xk[i] = T0[w & 0xff] ^ T1[(w >>> 8) & 0xff] ^ T2[(w >>> 16) & 0xff] ^ T3[w >>> 24];
|
|
40615
|
+
}
|
|
40616
|
+
return xk;
|
|
40617
|
+
}
|
|
40618
|
+
// Apply tables
|
|
40619
|
+
function apply0123(T01, T23, s0, s1, s2, s3) {
|
|
40620
|
+
return (T01[((s0 << 8) & 0xff00) | ((s1 >>> 8) & 0xff)] ^
|
|
40621
|
+
T23[((s2 >>> 8) & 0xff00) | ((s3 >>> 24) & 0xff)]);
|
|
40622
|
+
}
|
|
40623
|
+
function applySbox(sbox2, s0, s1, s2, s3) {
|
|
40624
|
+
return (sbox2[(s0 & 0xff) | (s1 & 0xff00)] |
|
|
40625
|
+
(sbox2[((s2 >>> 16) & 0xff) | ((s3 >>> 16) & 0xff00)] << 16));
|
|
40626
|
+
}
|
|
40627
|
+
function encrypt(xk, s0, s1, s2, s3) {
|
|
40628
|
+
const { sbox2, T01, T23 } = TABLE_ENC;
|
|
40629
|
+
let k = 0;
|
|
40630
|
+
(s0 ^= xk[k++]), (s1 ^= xk[k++]), (s2 ^= xk[k++]), (s3 ^= xk[k++]);
|
|
40631
|
+
const rounds = xk.length / 4 - 2;
|
|
40632
|
+
for (let i = 0; i < rounds; i++) {
|
|
40633
|
+
const t0 = xk[k++] ^ apply0123(T01, T23, s0, s1, s2, s3);
|
|
40634
|
+
const t1 = xk[k++] ^ apply0123(T01, T23, s1, s2, s3, s0);
|
|
40635
|
+
const t2 = xk[k++] ^ apply0123(T01, T23, s2, s3, s0, s1);
|
|
40636
|
+
const t3 = xk[k++] ^ apply0123(T01, T23, s3, s0, s1, s2);
|
|
40637
|
+
(s0 = t0), (s1 = t1), (s2 = t2), (s3 = t3);
|
|
40638
|
+
}
|
|
40639
|
+
// last round (without mixcolumns, so using SBOX2 table)
|
|
40640
|
+
const t0 = xk[k++] ^ applySbox(sbox2, s0, s1, s2, s3);
|
|
40641
|
+
const t1 = xk[k++] ^ applySbox(sbox2, s1, s2, s3, s0);
|
|
40642
|
+
const t2 = xk[k++] ^ applySbox(sbox2, s2, s3, s0, s1);
|
|
40643
|
+
const t3 = xk[k++] ^ applySbox(sbox2, s3, s0, s1, s2);
|
|
40644
|
+
return { s0: t0, s1: t1, s2: t2, s3: t3 };
|
|
40645
|
+
}
|
|
40646
|
+
function decrypt(xk, s0, s1, s2, s3) {
|
|
40647
|
+
const { sbox2, T01, T23 } = TABLE_DEC;
|
|
40648
|
+
let k = 0;
|
|
40649
|
+
(s0 ^= xk[k++]), (s1 ^= xk[k++]), (s2 ^= xk[k++]), (s3 ^= xk[k++]);
|
|
40650
|
+
const rounds = xk.length / 4 - 2;
|
|
40651
|
+
for (let i = 0; i < rounds; i++) {
|
|
40652
|
+
const t0 = xk[k++] ^ apply0123(T01, T23, s0, s3, s2, s1);
|
|
40653
|
+
const t1 = xk[k++] ^ apply0123(T01, T23, s1, s0, s3, s2);
|
|
40654
|
+
const t2 = xk[k++] ^ apply0123(T01, T23, s2, s1, s0, s3);
|
|
40655
|
+
const t3 = xk[k++] ^ apply0123(T01, T23, s3, s2, s1, s0);
|
|
40656
|
+
(s0 = t0), (s1 = t1), (s2 = t2), (s3 = t3);
|
|
40657
|
+
}
|
|
40658
|
+
// Last round
|
|
40659
|
+
const t0 = xk[k++] ^ applySbox(sbox2, s0, s3, s2, s1);
|
|
40660
|
+
const t1 = xk[k++] ^ applySbox(sbox2, s1, s0, s3, s2);
|
|
40661
|
+
const t2 = xk[k++] ^ applySbox(sbox2, s2, s1, s0, s3);
|
|
40662
|
+
const t3 = xk[k++] ^ applySbox(sbox2, s3, s2, s1, s0);
|
|
40663
|
+
return { s0: t0, s1: t1, s2: t2, s3: t3 };
|
|
40664
|
+
}
|
|
40665
|
+
function getDst(len, dst) {
|
|
40666
|
+
if (!dst)
|
|
40667
|
+
return new Uint8Array(len);
|
|
40668
|
+
ensureBytes(dst);
|
|
40669
|
+
if (dst.length < len)
|
|
40670
|
+
throw new Error(`aes: wrong destination length, expected at least ${len}, got: ${dst.length}`);
|
|
40671
|
+
return dst;
|
|
40672
|
+
}
|
|
40673
|
+
// TODO: investigate merging with ctr32
|
|
40674
|
+
function ctrCounter(xk, nonce, src, dst) {
|
|
40675
|
+
ensureBytes(nonce, BLOCK_SIZE);
|
|
40676
|
+
ensureBytes(src);
|
|
40677
|
+
const srcLen = src.length;
|
|
40678
|
+
dst = getDst(srcLen, dst);
|
|
40679
|
+
const ctr = nonce;
|
|
40680
|
+
const c32 = u32(ctr);
|
|
40681
|
+
// Fill block (empty, ctr=0)
|
|
40682
|
+
let { s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]);
|
|
40683
|
+
const src32 = u32(src);
|
|
40684
|
+
const dst32 = u32(dst);
|
|
40685
|
+
// process blocks
|
|
40686
|
+
for (let i = 0; i + 4 <= src32.length; i += 4) {
|
|
40687
|
+
dst32[i + 0] = src32[i + 0] ^ s0;
|
|
40688
|
+
dst32[i + 1] = src32[i + 1] ^ s1;
|
|
40689
|
+
dst32[i + 2] = src32[i + 2] ^ s2;
|
|
40690
|
+
dst32[i + 3] = src32[i + 3] ^ s3;
|
|
40691
|
+
// Full 128 bit counter with wrap around
|
|
40692
|
+
let carry = 1;
|
|
40693
|
+
for (let i = ctr.length - 1; i >= 0; i--) {
|
|
40694
|
+
carry = (carry + (ctr[i] & 0xff)) | 0;
|
|
40695
|
+
ctr[i] = carry & 0xff;
|
|
40696
|
+
carry >>>= 8;
|
|
40697
|
+
}
|
|
40698
|
+
({ s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]));
|
|
40699
|
+
}
|
|
40700
|
+
// leftovers (less than block)
|
|
40701
|
+
// It's possible to handle > u32 fast, but is it worth it?
|
|
40702
|
+
const start = BLOCK_SIZE * Math.floor(src32.length / BLOCK_SIZE32);
|
|
40703
|
+
if (start < srcLen) {
|
|
40704
|
+
const b32 = new Uint32Array([s0, s1, s2, s3]);
|
|
40705
|
+
const buf = u8(b32);
|
|
40706
|
+
for (let i = start, pos = 0; i < srcLen; i++, pos++)
|
|
40707
|
+
dst[i] = src[i] ^ buf[pos];
|
|
40708
|
+
}
|
|
40709
|
+
return dst;
|
|
40710
|
+
}
|
|
40711
|
+
// AES CTR with overflowing 32 bit counter
|
|
40712
|
+
// It's possible to do 32le significantly simpler (and probably faster) by using u32.
|
|
40713
|
+
// But, we need both, and perf bottleneck is in ghash anyway.
|
|
40714
|
+
function ctr32(xk, isLE, nonce, src, dst) {
|
|
40715
|
+
ensureBytes(nonce, BLOCK_SIZE);
|
|
40716
|
+
ensureBytes(src);
|
|
40717
|
+
dst = getDst(src.length, dst);
|
|
40718
|
+
const ctr = nonce; // write new value to nonce, so it can be re-used
|
|
40719
|
+
const c32 = u32(ctr);
|
|
40720
|
+
const view = createView(ctr);
|
|
40721
|
+
const src32 = u32(src);
|
|
40722
|
+
const dst32 = u32(dst);
|
|
40723
|
+
const ctrPos = isLE ? 0 : 12;
|
|
40724
|
+
const srcLen = src.length;
|
|
40725
|
+
// Fill block (empty, ctr=0)
|
|
40726
|
+
let ctrNum = view.getUint32(ctrPos, isLE); // read current counter value
|
|
40727
|
+
let { s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]);
|
|
40728
|
+
// process blocks
|
|
40729
|
+
for (let i = 0; i + 4 <= src32.length; i += 4) {
|
|
40730
|
+
dst32[i + 0] = src32[i + 0] ^ s0;
|
|
40731
|
+
dst32[i + 1] = src32[i + 1] ^ s1;
|
|
40732
|
+
dst32[i + 2] = src32[i + 2] ^ s2;
|
|
40733
|
+
dst32[i + 3] = src32[i + 3] ^ s3;
|
|
40734
|
+
ctrNum = (ctrNum + 1) >>> 0; // u32 wrap
|
|
40735
|
+
view.setUint32(ctrPos, ctrNum, isLE);
|
|
40736
|
+
({ s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]));
|
|
40737
|
+
}
|
|
40738
|
+
// leftovers (less than a block)
|
|
40739
|
+
const start = BLOCK_SIZE * Math.floor(src32.length / BLOCK_SIZE32);
|
|
40740
|
+
if (start < srcLen) {
|
|
40741
|
+
const b32 = new Uint32Array([s0, s1, s2, s3]);
|
|
40742
|
+
const buf = u8(b32);
|
|
40743
|
+
for (let i = start, pos = 0; i < srcLen; i++, pos++)
|
|
40744
|
+
dst[i] = src[i] ^ buf[pos];
|
|
40745
|
+
}
|
|
40746
|
+
return dst;
|
|
40747
|
+
}
|
|
40748
|
+
/**
|
|
40749
|
+
* CTR: counter mode. Creates stream cipher.
|
|
40750
|
+
* Requires good IV. Parallelizable. OK, but no MAC.
|
|
40751
|
+
*/
|
|
40752
|
+
wrapCipher({ blockSize: 16, nonceLength: 16 }, function ctr(key, nonce) {
|
|
40753
|
+
ensureBytes(key);
|
|
40754
|
+
ensureBytes(nonce, BLOCK_SIZE);
|
|
40755
|
+
function processCtr(buf, dst) {
|
|
40756
|
+
const xk = expandKeyLE(key);
|
|
40757
|
+
const n = nonce.slice();
|
|
40758
|
+
const out = ctrCounter(xk, n, buf, dst);
|
|
40759
|
+
xk.fill(0);
|
|
40760
|
+
n.fill(0);
|
|
40761
|
+
return out;
|
|
40762
|
+
}
|
|
40763
|
+
return {
|
|
40764
|
+
encrypt: (plaintext, dst) => processCtr(plaintext, dst),
|
|
40765
|
+
decrypt: (ciphertext, dst) => processCtr(ciphertext, dst),
|
|
40766
|
+
};
|
|
40767
|
+
});
|
|
40768
|
+
function validateBlockDecrypt(data) {
|
|
40769
|
+
ensureBytes(data);
|
|
40770
|
+
if (data.length % BLOCK_SIZE !== 0) {
|
|
40771
|
+
throw new Error(`aes/(cbc-ecb).decrypt ciphertext should consist of blocks with size ${BLOCK_SIZE}`);
|
|
40772
|
+
}
|
|
40773
|
+
}
|
|
40774
|
+
function validateBlockEncrypt(plaintext, pcks5, dst) {
|
|
40775
|
+
let outLen = plaintext.length;
|
|
40776
|
+
const remaining = outLen % BLOCK_SIZE;
|
|
40777
|
+
if (!pcks5 && remaining !== 0)
|
|
40778
|
+
throw new Error('aec/(cbc-ecb): unpadded plaintext with disabled padding');
|
|
40779
|
+
const b = u32(plaintext);
|
|
40780
|
+
if (pcks5) {
|
|
40781
|
+
let left = BLOCK_SIZE - remaining;
|
|
40782
|
+
if (!left)
|
|
40783
|
+
left = BLOCK_SIZE; // if no bytes left, create empty padding block
|
|
40784
|
+
outLen = outLen + left;
|
|
40785
|
+
}
|
|
40786
|
+
const out = getDst(outLen, dst);
|
|
40787
|
+
const o = u32(out);
|
|
40788
|
+
return { b, o, out };
|
|
40789
|
+
}
|
|
40790
|
+
function validatePCKS(data, pcks5) {
|
|
40791
|
+
if (!pcks5)
|
|
40792
|
+
return data;
|
|
40793
|
+
const len = data.length;
|
|
40794
|
+
if (!len)
|
|
40795
|
+
throw new Error(`aes/pcks5: empty ciphertext not allowed`);
|
|
40796
|
+
const lastByte = data[len - 1];
|
|
40797
|
+
if (lastByte <= 0 || lastByte > 16)
|
|
40798
|
+
throw new Error(`aes/pcks5: wrong padding byte: ${lastByte}`);
|
|
40799
|
+
const out = data.subarray(0, -lastByte);
|
|
40800
|
+
for (let i = 0; i < lastByte; i++)
|
|
40801
|
+
if (data[len - i - 1] !== lastByte)
|
|
40802
|
+
throw new Error(`aes/pcks5: wrong padding`);
|
|
40803
|
+
return out;
|
|
40804
|
+
}
|
|
40805
|
+
function padPCKS(left) {
|
|
40806
|
+
const tmp = new Uint8Array(16);
|
|
40807
|
+
const tmp32 = u32(tmp);
|
|
40808
|
+
tmp.set(left);
|
|
40809
|
+
const paddingByte = BLOCK_SIZE - left.length;
|
|
40810
|
+
for (let i = BLOCK_SIZE - paddingByte; i < BLOCK_SIZE; i++)
|
|
40811
|
+
tmp[i] = paddingByte;
|
|
40812
|
+
return tmp32;
|
|
40813
|
+
}
|
|
40814
|
+
/**
|
|
40815
|
+
* ECB: Electronic CodeBook. Simple deterministic replacement.
|
|
40816
|
+
* Dangerous: always map x to y. See [AES Penguin](https://words.filippo.io/the-ecb-penguin/).
|
|
40817
|
+
*/
|
|
40818
|
+
wrapCipher({ blockSize: 16 }, function ecb(key, opts = {}) {
|
|
40819
|
+
ensureBytes(key);
|
|
40820
|
+
const pcks5 = !opts.disablePadding;
|
|
40821
|
+
return {
|
|
40822
|
+
encrypt: (plaintext, dst) => {
|
|
40823
|
+
ensureBytes(plaintext);
|
|
40824
|
+
const { b, o, out: _out } = validateBlockEncrypt(plaintext, pcks5, dst);
|
|
40825
|
+
const xk = expandKeyLE(key);
|
|
40826
|
+
let i = 0;
|
|
40827
|
+
for (; i + 4 <= b.length;) {
|
|
40828
|
+
const { s0, s1, s2, s3 } = encrypt(xk, b[i + 0], b[i + 1], b[i + 2], b[i + 3]);
|
|
40829
|
+
(o[i++] = s0), (o[i++] = s1), (o[i++] = s2), (o[i++] = s3);
|
|
40830
|
+
}
|
|
40831
|
+
if (pcks5) {
|
|
40832
|
+
const tmp32 = padPCKS(plaintext.subarray(i * 4));
|
|
40833
|
+
const { s0, s1, s2, s3 } = encrypt(xk, tmp32[0], tmp32[1], tmp32[2], tmp32[3]);
|
|
40834
|
+
(o[i++] = s0), (o[i++] = s1), (o[i++] = s2), (o[i++] = s3);
|
|
40835
|
+
}
|
|
40836
|
+
xk.fill(0);
|
|
40837
|
+
return _out;
|
|
40838
|
+
},
|
|
40839
|
+
decrypt: (ciphertext, dst) => {
|
|
40840
|
+
validateBlockDecrypt(ciphertext);
|
|
40841
|
+
const xk = expandKeyDecLE(key);
|
|
40842
|
+
const out = getDst(ciphertext.length, dst);
|
|
40843
|
+
const b = u32(ciphertext);
|
|
40844
|
+
const o = u32(out);
|
|
40845
|
+
for (let i = 0; i + 4 <= b.length;) {
|
|
40846
|
+
const { s0, s1, s2, s3 } = decrypt(xk, b[i + 0], b[i + 1], b[i + 2], b[i + 3]);
|
|
40847
|
+
(o[i++] = s0), (o[i++] = s1), (o[i++] = s2), (o[i++] = s3);
|
|
40848
|
+
}
|
|
40849
|
+
xk.fill(0);
|
|
40850
|
+
return validatePCKS(out, pcks5);
|
|
40851
|
+
},
|
|
40852
|
+
};
|
|
40853
|
+
});
|
|
40854
|
+
/**
|
|
40855
|
+
* CBC: Cipher-Block-Chaining. Key is previous round’s block.
|
|
40856
|
+
* Fragile: needs proper padding. Unauthenticated: needs MAC.
|
|
40857
|
+
*/
|
|
40858
|
+
wrapCipher({ blockSize: 16, nonceLength: 16 }, function cbc(key, iv, opts = {}) {
|
|
40859
|
+
ensureBytes(key);
|
|
40860
|
+
ensureBytes(iv, 16);
|
|
40861
|
+
const pcks5 = !opts.disablePadding;
|
|
40862
|
+
return {
|
|
40863
|
+
encrypt: (plaintext, dst) => {
|
|
40864
|
+
const xk = expandKeyLE(key);
|
|
40865
|
+
const { b, o, out: _out } = validateBlockEncrypt(plaintext, pcks5, dst);
|
|
40866
|
+
const n32 = u32(iv);
|
|
40867
|
+
// prettier-ignore
|
|
40868
|
+
let s0 = n32[0], s1 = n32[1], s2 = n32[2], s3 = n32[3];
|
|
40869
|
+
let i = 0;
|
|
40870
|
+
for (; i + 4 <= b.length;) {
|
|
40871
|
+
(s0 ^= b[i + 0]), (s1 ^= b[i + 1]), (s2 ^= b[i + 2]), (s3 ^= b[i + 3]);
|
|
40872
|
+
({ s0, s1, s2, s3 } = encrypt(xk, s0, s1, s2, s3));
|
|
40873
|
+
(o[i++] = s0), (o[i++] = s1), (o[i++] = s2), (o[i++] = s3);
|
|
40874
|
+
}
|
|
40875
|
+
if (pcks5) {
|
|
40876
|
+
const tmp32 = padPCKS(plaintext.subarray(i * 4));
|
|
40877
|
+
(s0 ^= tmp32[0]), (s1 ^= tmp32[1]), (s2 ^= tmp32[2]), (s3 ^= tmp32[3]);
|
|
40878
|
+
({ s0, s1, s2, s3 } = encrypt(xk, s0, s1, s2, s3));
|
|
40879
|
+
(o[i++] = s0), (o[i++] = s1), (o[i++] = s2), (o[i++] = s3);
|
|
40880
|
+
}
|
|
40881
|
+
xk.fill(0);
|
|
40882
|
+
return _out;
|
|
40883
|
+
},
|
|
40884
|
+
decrypt: (ciphertext, dst) => {
|
|
40885
|
+
validateBlockDecrypt(ciphertext);
|
|
40886
|
+
const xk = expandKeyDecLE(key);
|
|
40887
|
+
const n32 = u32(iv);
|
|
40888
|
+
const out = getDst(ciphertext.length, dst);
|
|
40889
|
+
const b = u32(ciphertext);
|
|
40890
|
+
const o = u32(out);
|
|
40891
|
+
// prettier-ignore
|
|
40892
|
+
let s0 = n32[0], s1 = n32[1], s2 = n32[2], s3 = n32[3];
|
|
40893
|
+
for (let i = 0; i + 4 <= b.length;) {
|
|
40894
|
+
// prettier-ignore
|
|
40895
|
+
const ps0 = s0, ps1 = s1, ps2 = s2, ps3 = s3;
|
|
40896
|
+
(s0 = b[i + 0]), (s1 = b[i + 1]), (s2 = b[i + 2]), (s3 = b[i + 3]);
|
|
40897
|
+
const { s0: o0, s1: o1, s2: o2, s3: o3 } = decrypt(xk, s0, s1, s2, s3);
|
|
40898
|
+
(o[i++] = o0 ^ ps0), (o[i++] = o1 ^ ps1), (o[i++] = o2 ^ ps2), (o[i++] = o3 ^ ps3);
|
|
40899
|
+
}
|
|
40900
|
+
xk.fill(0);
|
|
40901
|
+
return validatePCKS(out, pcks5);
|
|
40902
|
+
},
|
|
40903
|
+
};
|
|
40904
|
+
});
|
|
40905
|
+
// TODO: merge with chacha, however gcm has bitLen while chacha has byteLen
|
|
40906
|
+
function computeTag(fn, isLE, key, data, AAD) {
|
|
40907
|
+
const h = fn.create(key, data.length + (AAD?.length || 0));
|
|
40908
|
+
if (AAD)
|
|
40909
|
+
h.update(AAD);
|
|
40910
|
+
h.update(data);
|
|
40911
|
+
const num = new Uint8Array(16);
|
|
40912
|
+
const view = createView(num);
|
|
40913
|
+
if (AAD)
|
|
40914
|
+
setBigUint64(view, 0, BigInt(AAD.length * 8), isLE);
|
|
40915
|
+
setBigUint64(view, 8, BigInt(data.length * 8), isLE);
|
|
40916
|
+
h.update(num);
|
|
40917
|
+
return h.digest();
|
|
40918
|
+
}
|
|
40919
|
+
/**
|
|
40920
|
+
* GCM: Galois/Counter Mode.
|
|
40921
|
+
* Good, modern version of CTR, parallel, with MAC.
|
|
40922
|
+
* Be careful: MACs can be forged.
|
|
40923
|
+
*/
|
|
40924
|
+
const gcm = wrapCipher({ blockSize: 16, nonceLength: 12, tagLength: 16 }, function gcm(key, nonce, AAD) {
|
|
40925
|
+
ensureBytes(nonce);
|
|
40926
|
+
// Nonce can be pretty much anything (even 1 byte). But smaller nonces less secure.
|
|
40927
|
+
if (nonce.length === 0)
|
|
40928
|
+
throw new Error('aes/gcm: empty nonce');
|
|
40929
|
+
const tagLength = 16;
|
|
40930
|
+
function _computeTag(authKey, tagMask, data) {
|
|
40931
|
+
const tag = computeTag(ghash, false, authKey, data, AAD);
|
|
40932
|
+
for (let i = 0; i < tagMask.length; i++)
|
|
40933
|
+
tag[i] ^= tagMask[i];
|
|
40934
|
+
return tag;
|
|
40935
|
+
}
|
|
40936
|
+
function deriveKeys() {
|
|
40937
|
+
const xk = expandKeyLE(key);
|
|
40938
|
+
const authKey = EMPTY_BLOCK.slice();
|
|
40939
|
+
const counter = EMPTY_BLOCK.slice();
|
|
40940
|
+
ctr32(xk, false, counter, counter, authKey);
|
|
40941
|
+
if (nonce.length === 12) {
|
|
40942
|
+
counter.set(nonce);
|
|
40943
|
+
}
|
|
40944
|
+
else {
|
|
40945
|
+
// Spec (NIST 800-38d) supports variable size nonce.
|
|
40946
|
+
// Not supported for now, but can be useful.
|
|
40947
|
+
const nonceLen = EMPTY_BLOCK.slice();
|
|
40948
|
+
const view = createView(nonceLen);
|
|
40949
|
+
setBigUint64(view, 8, BigInt(nonce.length * 8), false);
|
|
40950
|
+
// ghash(nonce || u64be(0) || u64be(nonceLen*8))
|
|
40951
|
+
ghash.create(authKey).update(nonce).update(nonceLen).digestInto(counter);
|
|
40952
|
+
}
|
|
40953
|
+
const tagMask = ctr32(xk, false, counter, EMPTY_BLOCK);
|
|
40954
|
+
return { xk, authKey, counter, tagMask };
|
|
40955
|
+
}
|
|
40956
|
+
return {
|
|
40957
|
+
encrypt: (plaintext) => {
|
|
40958
|
+
ensureBytes(plaintext);
|
|
40959
|
+
const { xk, authKey, counter, tagMask } = deriveKeys();
|
|
40960
|
+
const out = new Uint8Array(plaintext.length + tagLength);
|
|
40961
|
+
ctr32(xk, false, counter, plaintext, out);
|
|
40962
|
+
const tag = _computeTag(authKey, tagMask, out.subarray(0, out.length - tagLength));
|
|
40963
|
+
out.set(tag, plaintext.length);
|
|
40964
|
+
xk.fill(0);
|
|
40965
|
+
return out;
|
|
40966
|
+
},
|
|
40967
|
+
decrypt: (ciphertext) => {
|
|
40968
|
+
ensureBytes(ciphertext);
|
|
40969
|
+
if (ciphertext.length < tagLength)
|
|
40970
|
+
throw new Error(`aes/gcm: ciphertext less than tagLen (${tagLength})`);
|
|
40971
|
+
const { xk, authKey, counter, tagMask } = deriveKeys();
|
|
40972
|
+
const data = ciphertext.subarray(0, -tagLength);
|
|
40973
|
+
const passedTag = ciphertext.subarray(-tagLength);
|
|
40974
|
+
const tag = _computeTag(authKey, tagMask, data);
|
|
40975
|
+
if (!equalBytes(tag, passedTag))
|
|
40976
|
+
throw new Error('aes/gcm: invalid ghash tag');
|
|
40977
|
+
const out = ctr32(xk, false, counter, data);
|
|
40978
|
+
authKey.fill(0);
|
|
40979
|
+
tagMask.fill(0);
|
|
40980
|
+
xk.fill(0);
|
|
40981
|
+
return out;
|
|
40982
|
+
},
|
|
40983
|
+
};
|
|
40984
|
+
});
|
|
40985
|
+
const limit = (name, min, max) => (value) => {
|
|
40986
|
+
if (!Number.isSafeInteger(value) || min > value || value > max)
|
|
40987
|
+
throw new Error(`${name}: invalid value=${value}, must be [${min}..${max}]`);
|
|
40988
|
+
};
|
|
40989
|
+
/**
|
|
40990
|
+
* AES-GCM-SIV: classic AES-GCM with nonce-misuse resistance.
|
|
40991
|
+
* Guarantees that, when a nonce is repeated, the only security loss is that identical
|
|
40992
|
+
* plaintexts will produce identical ciphertexts.
|
|
40993
|
+
* RFC 8452, https://datatracker.ietf.org/doc/html/rfc8452
|
|
40994
|
+
*/
|
|
40995
|
+
wrapCipher({ blockSize: 16, nonceLength: 12, tagLength: 16 }, function siv(key, nonce, AAD) {
|
|
40996
|
+
const tagLength = 16;
|
|
40997
|
+
// From RFC 8452: Section 6
|
|
40998
|
+
const AAD_LIMIT = limit('AAD', 0, 2 ** 36);
|
|
40999
|
+
const PLAIN_LIMIT = limit('plaintext', 0, 2 ** 36);
|
|
41000
|
+
const NONCE_LIMIT = limit('nonce', 12, 12);
|
|
41001
|
+
const CIPHER_LIMIT = limit('ciphertext', 16, 2 ** 36 + 16);
|
|
41002
|
+
ensureBytes(nonce);
|
|
41003
|
+
NONCE_LIMIT(nonce.length);
|
|
41004
|
+
if (AAD) {
|
|
41005
|
+
ensureBytes(AAD);
|
|
41006
|
+
AAD_LIMIT(AAD.length);
|
|
41007
|
+
}
|
|
41008
|
+
function deriveKeys() {
|
|
41009
|
+
const len = key.length;
|
|
41010
|
+
if (len !== 16 && len !== 24 && len !== 32)
|
|
41011
|
+
throw new Error(`key length must be 16, 24 or 32 bytes, got: ${len} bytes`);
|
|
41012
|
+
const xk = expandKeyLE(key);
|
|
41013
|
+
const encKey = new Uint8Array(len);
|
|
41014
|
+
const authKey = new Uint8Array(16);
|
|
41015
|
+
const n32 = u32(nonce);
|
|
41016
|
+
// prettier-ignore
|
|
41017
|
+
let s0 = 0, s1 = n32[0], s2 = n32[1], s3 = n32[2];
|
|
41018
|
+
let counter = 0;
|
|
41019
|
+
for (const derivedKey of [authKey, encKey].map(u32)) {
|
|
41020
|
+
const d32 = u32(derivedKey);
|
|
41021
|
+
for (let i = 0; i < d32.length; i += 2) {
|
|
41022
|
+
// aes(u32le(0) || nonce)[:8] || aes(u32le(1) || nonce)[:8] ...
|
|
41023
|
+
const { s0: o0, s1: o1 } = encrypt(xk, s0, s1, s2, s3);
|
|
41024
|
+
d32[i + 0] = o0;
|
|
41025
|
+
d32[i + 1] = o1;
|
|
41026
|
+
s0 = ++counter; // increment counter inside state
|
|
41027
|
+
}
|
|
41028
|
+
}
|
|
41029
|
+
xk.fill(0);
|
|
41030
|
+
return { authKey, encKey: expandKeyLE(encKey) };
|
|
41031
|
+
}
|
|
41032
|
+
function _computeTag(encKey, authKey, data) {
|
|
41033
|
+
const tag = computeTag(polyval, true, authKey, data, AAD);
|
|
41034
|
+
// Compute the expected tag by XORing S_s and the nonce, clearing the
|
|
41035
|
+
// most significant bit of the last byte and encrypting with the
|
|
41036
|
+
// message-encryption key.
|
|
41037
|
+
for (let i = 0; i < 12; i++)
|
|
41038
|
+
tag[i] ^= nonce[i];
|
|
41039
|
+
tag[15] &= 0x7f; // Clear the highest bit
|
|
41040
|
+
// encrypt tag as block
|
|
41041
|
+
const t32 = u32(tag);
|
|
41042
|
+
// prettier-ignore
|
|
41043
|
+
let s0 = t32[0], s1 = t32[1], s2 = t32[2], s3 = t32[3];
|
|
41044
|
+
({ s0, s1, s2, s3 } = encrypt(encKey, s0, s1, s2, s3));
|
|
41045
|
+
(t32[0] = s0), (t32[1] = s1), (t32[2] = s2), (t32[3] = s3);
|
|
41046
|
+
return tag;
|
|
41047
|
+
}
|
|
41048
|
+
// actual decrypt/encrypt of message.
|
|
41049
|
+
function processSiv(encKey, tag, input) {
|
|
41050
|
+
let block = tag.slice();
|
|
41051
|
+
block[15] |= 0x80; // Force highest bit
|
|
41052
|
+
return ctr32(encKey, true, block, input);
|
|
41053
|
+
}
|
|
41054
|
+
return {
|
|
41055
|
+
encrypt: (plaintext) => {
|
|
41056
|
+
ensureBytes(plaintext);
|
|
41057
|
+
PLAIN_LIMIT(plaintext.length);
|
|
41058
|
+
const { encKey, authKey } = deriveKeys();
|
|
41059
|
+
const tag = _computeTag(encKey, authKey, plaintext);
|
|
41060
|
+
const out = new Uint8Array(plaintext.length + tagLength);
|
|
41061
|
+
out.set(tag, plaintext.length);
|
|
41062
|
+
out.set(processSiv(encKey, tag, plaintext));
|
|
41063
|
+
encKey.fill(0);
|
|
41064
|
+
authKey.fill(0);
|
|
41065
|
+
return out;
|
|
41066
|
+
},
|
|
41067
|
+
decrypt: (ciphertext) => {
|
|
41068
|
+
ensureBytes(ciphertext);
|
|
41069
|
+
CIPHER_LIMIT(ciphertext.length);
|
|
41070
|
+
const tag = ciphertext.subarray(-tagLength);
|
|
41071
|
+
const { encKey, authKey } = deriveKeys();
|
|
41072
|
+
const plaintext = processSiv(encKey, tag, ciphertext.subarray(0, -tagLength));
|
|
41073
|
+
const expectedTag = _computeTag(encKey, authKey, plaintext);
|
|
41074
|
+
encKey.fill(0);
|
|
41075
|
+
authKey.fill(0);
|
|
41076
|
+
if (!equalBytes(tag, expectedTag))
|
|
41077
|
+
throw new Error('invalid polyval tag');
|
|
41078
|
+
return plaintext;
|
|
41079
|
+
},
|
|
41080
|
+
};
|
|
41081
|
+
});
|
|
41082
|
+
|
|
41083
|
+
/**
|
|
41084
|
+
* Concat KDF (Single Step KDF) per NIST SP 800-56A and RFC 7518 §4.6.2.
|
|
41085
|
+
*
|
|
41086
|
+
* Derives a symmetric key from an ECDH shared secret for use in JWE.
|
|
41087
|
+
*
|
|
41088
|
+
* @param sharedSecret - The raw ECDH shared secret (Z)
|
|
41089
|
+
* @param keyBitLength - Desired key length in bits (e.g. 128, 256)
|
|
41090
|
+
* @param algorithmId - The "enc" value for ECDH-ES or "alg" for ECDH-ES+A*KW
|
|
41091
|
+
* @param apu - Agreement PartyUInfo (typically empty)
|
|
41092
|
+
* @param apv - Agreement PartyVInfo (typically empty)
|
|
41093
|
+
* @returns Derived key as Uint8Array
|
|
41094
|
+
*/
|
|
41095
|
+
function concatKdf(sharedSecret, keyBitLength, algorithmId, apu = new Uint8Array(0), apv = new Uint8Array(0)) {
|
|
41096
|
+
const algIdBytes = new TextEncoder().encode(algorithmId);
|
|
41097
|
+
// Build otherInfo per RFC 7518 §4.6.2:
|
|
41098
|
+
// AlgorithmID = len(algId) || algId
|
|
41099
|
+
// PartyUInfo = len(apu) || apu
|
|
41100
|
+
// PartyVInfo = len(apv) || apv
|
|
41101
|
+
// SuppPubInfo = keydatalen (32-bit BE, in bits)
|
|
41102
|
+
const otherInfo = concatBytes(uint32BE(algIdBytes.length), algIdBytes, uint32BE(apu.length), apu, uint32BE(apv.length), apv, uint32BE(keyBitLength));
|
|
41103
|
+
const hashLength = 256; // SHA-256 output in bits
|
|
41104
|
+
const reps = Math.ceil(keyBitLength / hashLength);
|
|
41105
|
+
const result = new Uint8Array(reps * 32);
|
|
41106
|
+
for (let counter = 1; counter <= reps; counter++) {
|
|
41107
|
+
const input = concatBytes(uint32BE(counter), sharedSecret, otherInfo);
|
|
41108
|
+
const digest = sha256$1(input);
|
|
41109
|
+
result.set(digest, (counter - 1) * 32);
|
|
41110
|
+
}
|
|
41111
|
+
return result.slice(0, keyBitLength / 8);
|
|
41112
|
+
}
|
|
41113
|
+
function uint32BE(value) {
|
|
41114
|
+
const buf = new Uint8Array(4);
|
|
41115
|
+
buf[0] = (value >>> 24) & 0xff;
|
|
41116
|
+
buf[1] = (value >>> 16) & 0xff;
|
|
41117
|
+
buf[2] = (value >>> 8) & 0xff;
|
|
41118
|
+
buf[3] = value & 0xff;
|
|
41119
|
+
return buf;
|
|
41120
|
+
}
|
|
41121
|
+
function concatBytes(...arrays) {
|
|
41122
|
+
let totalLength = 0;
|
|
41123
|
+
for (const arr of arrays)
|
|
41124
|
+
totalLength += arr.length;
|
|
41125
|
+
const result = new Uint8Array(totalLength);
|
|
41126
|
+
let offset = 0;
|
|
41127
|
+
for (const arr of arrays) {
|
|
41128
|
+
result.set(arr, offset);
|
|
41129
|
+
offset += arr.length;
|
|
41130
|
+
}
|
|
41131
|
+
return result;
|
|
41132
|
+
}
|
|
41133
|
+
|
|
41134
|
+
const ENCODER = new TextEncoder();
|
|
41135
|
+
const DECODER = new TextDecoder();
|
|
41136
|
+
/**
|
|
41137
|
+
* Build a JWE Compact Serialization string using ECDH-ES + A256GCM.
|
|
41138
|
+
*
|
|
41139
|
+
* Uses an ephemeral secp256k1 keypair for key agreement.
|
|
41140
|
+
* The sender's identity key is NOT involved — only the recipient's public key.
|
|
41141
|
+
*
|
|
41142
|
+
* @param recipientPubKey - Recipient's public key (secp256k1 JWK)
|
|
41143
|
+
* @param plaintext - Data to encrypt
|
|
41144
|
+
* @returns JWE Compact string: header.encryptedKey.iv.ciphertext.tag
|
|
41145
|
+
*/
|
|
41146
|
+
function buildJweCompact(recipientPubKey, plaintext) {
|
|
41147
|
+
// 1. Generate ephemeral keypair
|
|
41148
|
+
const ephemeralPrivKey = utils.randomPrivateKey();
|
|
41149
|
+
const ephemeralPubKeyBytes = getPublicKey(ephemeralPrivKey);
|
|
41150
|
+
// Get uncompressed public key coordinates for the JWE header
|
|
41151
|
+
const ephemeralPubHex = etc.bytesToHex(ephemeralPubKeyBytes);
|
|
41152
|
+
const curvePoints = Point.fromHex(ephemeralPubHex);
|
|
41153
|
+
const uncompressed = curvePoints.toRawBytes(false);
|
|
41154
|
+
const epkX = base64url.baseEncode(uncompressed.subarray(1, 33));
|
|
41155
|
+
const epkY = base64url.baseEncode(uncompressed.subarray(33, 65));
|
|
41156
|
+
// 2. Build protected header
|
|
41157
|
+
const header = {
|
|
41158
|
+
alg: 'ECDH-ES',
|
|
41159
|
+
enc: 'A256GCM',
|
|
41160
|
+
epk: {
|
|
41161
|
+
kty: 'EC',
|
|
41162
|
+
crv: 'secp256k1',
|
|
41163
|
+
x: epkX,
|
|
41164
|
+
y: epkY,
|
|
41165
|
+
},
|
|
41166
|
+
};
|
|
41167
|
+
const headerJson = JSON.stringify(header);
|
|
41168
|
+
const headerB64 = base64url.baseEncode(ENCODER.encode(headerJson));
|
|
41169
|
+
// 3. ECDH key agreement: ephemeral private + recipient public
|
|
41170
|
+
const recipientPubBytes = jwkToCompressedBytes(recipientPubKey);
|
|
41171
|
+
const sharedSecret = getSharedSecret(ephemeralPrivKey, recipientPubBytes);
|
|
41172
|
+
// 4. Derive CEK via Concat KDF (RFC 7518 §4.6.2)
|
|
41173
|
+
// For ECDH-ES (direct), algorithmId = enc value
|
|
41174
|
+
const cek = concatKdf(sharedSecret.slice(1), 256, 'A256GCM');
|
|
41175
|
+
// 5. Generate random 96-bit IV
|
|
41176
|
+
const iv = utils.randomPrivateKey().slice(0, 12);
|
|
41177
|
+
// 6. Encrypt with AES-256-GCM
|
|
41178
|
+
// AAD = ASCII bytes of the base64url-encoded protected header
|
|
41179
|
+
const aad = ENCODER.encode(headerB64);
|
|
41180
|
+
const cipher = gcm(cek, iv, aad);
|
|
41181
|
+
const encrypted = cipher.encrypt(plaintext); // returns ciphertext || tag
|
|
41182
|
+
// 7. Split ciphertext and tag (tag is last 16 bytes)
|
|
41183
|
+
const ciphertext = encrypted.slice(0, encrypted.length - 16);
|
|
41184
|
+
const tag = encrypted.slice(encrypted.length - 16);
|
|
41185
|
+
// 8. Assemble JWE Compact: header.encryptedKey.iv.ciphertext.tag
|
|
41186
|
+
// For ECDH-ES (direct), encrypted key is empty
|
|
41187
|
+
return [
|
|
41188
|
+
headerB64,
|
|
41189
|
+
'', // empty encrypted key
|
|
41190
|
+
base64url.baseEncode(iv),
|
|
41191
|
+
base64url.baseEncode(ciphertext),
|
|
41192
|
+
base64url.baseEncode(tag),
|
|
41193
|
+
].join('.');
|
|
41194
|
+
}
|
|
41195
|
+
/**
|
|
41196
|
+
* Parse and decrypt a JWE Compact Serialization string.
|
|
41197
|
+
*
|
|
41198
|
+
* @param recipientPrivKey - Recipient's private key (secp256k1 JWK)
|
|
41199
|
+
* @param jweCompact - The JWE Compact string to decrypt
|
|
41200
|
+
* @returns Decrypted plaintext
|
|
41201
|
+
*/
|
|
41202
|
+
function parseJweCompact(recipientPrivKey, jweCompact) {
|
|
41203
|
+
// 1. Split into 5 parts
|
|
41204
|
+
const parts = jweCompact.split('.');
|
|
41205
|
+
if (parts.length !== 5) {
|
|
41206
|
+
throw new Error('Invalid JWE Compact: expected 5 segments');
|
|
41207
|
+
}
|
|
41208
|
+
const [headerB64, , ivB64, ciphertextB64, tagB64] = parts;
|
|
41209
|
+
// 2. Parse protected header
|
|
41210
|
+
const headerJson = DECODER.decode(base64url.baseDecode(headerB64));
|
|
41211
|
+
const header = JSON.parse(headerJson);
|
|
41212
|
+
if (header.alg !== 'ECDH-ES') {
|
|
41213
|
+
throw new Error(`Unsupported JWE alg: ${header.alg}`);
|
|
41214
|
+
}
|
|
41215
|
+
if (header.enc !== 'A256GCM') {
|
|
41216
|
+
throw new Error(`Unsupported JWE enc: ${header.enc}`);
|
|
41217
|
+
}
|
|
41218
|
+
// 3. Reconstruct ephemeral public key from header
|
|
41219
|
+
const epk = header.epk;
|
|
41220
|
+
const ephemeralPubBytes = jwkToCompressedBytes(epk);
|
|
41221
|
+
// 4. ECDH key agreement: recipient private + ephemeral public
|
|
41222
|
+
const recipientPrivBytes = base64url.baseDecode(recipientPrivKey.d);
|
|
41223
|
+
const sharedSecret = getSharedSecret(recipientPrivBytes, ephemeralPubBytes);
|
|
41224
|
+
// 5. Derive CEK via Concat KDF
|
|
41225
|
+
const cek = concatKdf(sharedSecret.slice(1), 256, 'A256GCM');
|
|
41226
|
+
// 6. Decrypt with AES-256-GCM
|
|
41227
|
+
const iv = base64url.baseDecode(ivB64);
|
|
41228
|
+
const ciphertext = base64url.baseDecode(ciphertextB64);
|
|
41229
|
+
const tag = base64url.baseDecode(tagB64);
|
|
41230
|
+
// Reassemble ciphertext || tag for @noble/ciphers GCM
|
|
41231
|
+
const encrypted = new Uint8Array(ciphertext.length + tag.length);
|
|
41232
|
+
encrypted.set(ciphertext, 0);
|
|
41233
|
+
encrypted.set(tag, ciphertext.length);
|
|
41234
|
+
const aad = ENCODER.encode(headerB64);
|
|
41235
|
+
const cipher = gcm(cek, iv, aad);
|
|
41236
|
+
return cipher.decrypt(encrypted);
|
|
41237
|
+
}
|
|
41238
|
+
/**
|
|
41239
|
+
* Detect whether a string is a JWE Compact Serialization.
|
|
41240
|
+
*/
|
|
41241
|
+
function isJweCompact(ciphertext) {
|
|
41242
|
+
return ciphertext.startsWith('eyJ') && ciphertext.split('.').length === 5;
|
|
41243
|
+
}
|
|
41244
|
+
/**
|
|
41245
|
+
* Convert a JWK public key to compressed secp256k1 bytes.
|
|
41246
|
+
*/
|
|
41247
|
+
function jwkToCompressedBytes(jwk) {
|
|
41248
|
+
const xBytes = base64url.baseDecode(jwk.x);
|
|
41249
|
+
const yBytes = base64url.baseDecode(jwk.y);
|
|
41250
|
+
const prefix = yBytes[yBytes.length - 1] % 2 === 0 ? 0x02 : 0x03;
|
|
41251
|
+
return new Uint8Array([prefix, ...xBytes]);
|
|
41252
|
+
}
|
|
41253
|
+
|
|
40275
41254
|
const canonicalize = canonicalizeModule;
|
|
40276
41255
|
// Polyfill for synchronous signatures
|
|
40277
41256
|
etc.hmacSha256Sync = (k, ...m) => hmac(sha256$1, k, etc.concatBytes(...m));
|
|
@@ -40326,16 +41305,27 @@ class CipherBase {
|
|
|
40326
41305
|
const signature = Signature.fromCompact(sigHex);
|
|
40327
41306
|
return verify(signature, msgHash, compressedPublicKeyBytes);
|
|
40328
41307
|
}
|
|
40329
|
-
encryptBytes(
|
|
40330
|
-
|
|
40331
|
-
|
|
40332
|
-
|
|
40333
|
-
|
|
40334
|
-
|
|
40335
|
-
|
|
40336
|
-
|
|
41308
|
+
encryptBytes(recipientPubKey, data) {
|
|
41309
|
+
return buildJweCompact(recipientPubKey, data);
|
|
41310
|
+
}
|
|
41311
|
+
decryptBytes(recipientPrivKey, ciphertext, legacyPubKey) {
|
|
41312
|
+
if (isJweCompact(ciphertext)) {
|
|
41313
|
+
return parseJweCompact(recipientPrivKey, ciphertext);
|
|
41314
|
+
}
|
|
41315
|
+
if (legacyPubKey) {
|
|
41316
|
+
return this.decryptBytesLegacy(legacyPubKey, recipientPrivKey, ciphertext);
|
|
41317
|
+
}
|
|
41318
|
+
throw new Error('Cannot decrypt: not a JWE and no legacy public key provided. Pass legacyPubKey as the third argument for old ciphertext.');
|
|
41319
|
+
}
|
|
41320
|
+
encryptMessage(recipientPubKey, message) {
|
|
41321
|
+
const data = utf8ToBytes(message);
|
|
41322
|
+
return this.encryptBytes(recipientPubKey, data);
|
|
40337
41323
|
}
|
|
40338
|
-
|
|
41324
|
+
decryptMessage(recipientPrivKey, ciphertext, legacyPubKey) {
|
|
41325
|
+
const data = this.decryptBytes(recipientPrivKey, ciphertext, legacyPubKey);
|
|
41326
|
+
return bytesToUtf8(data);
|
|
41327
|
+
}
|
|
41328
|
+
decryptBytesLegacy(pubKey, privKey, ciphertext) {
|
|
40339
41329
|
const priv = base64url.baseDecode(privKey.d);
|
|
40340
41330
|
const pub = this.convertJwkToCompressedBytes(pubKey);
|
|
40341
41331
|
const ss = getSharedSecret(priv, pub);
|
|
@@ -40344,12 +41334,8 @@ class CipherBase {
|
|
|
40344
41334
|
const cipherdata = base64url.baseDecode(ciphertext);
|
|
40345
41335
|
return chacha.decrypt(cipherdata);
|
|
40346
41336
|
}
|
|
40347
|
-
|
|
40348
|
-
const data =
|
|
40349
|
-
return this.encryptBytes(pubKey, privKey, data);
|
|
40350
|
-
}
|
|
40351
|
-
decryptMessage(pubKey, privKey, ciphertext) {
|
|
40352
|
-
const data = this.decryptBytes(pubKey, privKey, ciphertext);
|
|
41337
|
+
decryptMessageLegacy(pubKey, privKey, ciphertext) {
|
|
41338
|
+
const data = this.decryptBytesLegacy(pubKey, privKey, ciphertext);
|
|
40353
41339
|
return bytesToUtf8(data);
|
|
40354
41340
|
}
|
|
40355
41341
|
hasLeadingZeroBits(hexHash, bits) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@didcid/gatekeeper",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Archon Gatekeeper",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
"author": "David McFadzean <davidmc@gmail.com>",
|
|
107
107
|
"license": "MIT",
|
|
108
108
|
"dependencies": {
|
|
109
|
-
"@didcid/cipher": "^0.1
|
|
109
|
+
"@didcid/cipher": "^0.2.1",
|
|
110
110
|
"@didcid/common": "^0.1.3",
|
|
111
111
|
"@didcid/ipfs": "^0.1.3",
|
|
112
112
|
"axios": "^1.7.7",
|
|
@@ -124,5 +124,5 @@
|
|
|
124
124
|
"devDependencies": {
|
|
125
125
|
"@rollup/plugin-json": "^6.1.0"
|
|
126
126
|
},
|
|
127
|
-
"gitHead": "
|
|
127
|
+
"gitHead": "40bac9cb578004fc32d9c937c68da9179210ec23"
|
|
128
128
|
}
|