simple-hpke 0.2.0 → 0.3.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/README.md +131 -19
- package/dist/constants.cjs +116 -34
- package/dist/constants.cjs.map +4 -4
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +116 -34
- package/dist/constants.js.map +4 -4
- package/dist/constants.min.cjs +1 -1
- package/dist/constants.min.cjs.map +4 -4
- package/dist/constants.min.js +1 -1
- package/dist/constants.min.js.map +4 -4
- package/dist/errors.cjs +38 -0
- package/dist/errors.cjs.map +7 -0
- package/dist/errors.d.ts +22 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +19 -0
- package/dist/errors.js.map +7 -0
- package/dist/errors.min.cjs +2 -0
- package/dist/errors.min.cjs.map +7 -0
- package/dist/errors.min.js +2 -0
- package/dist/errors.min.js.map +7 -0
- package/dist/index.cjs +147 -36
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +31 -21
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +147 -36
- package/dist/index.js.map +4 -4
- package/dist/index.min.cjs +1 -1
- package/dist/index.min.cjs.map +4 -4
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +4 -4
- package/dist/meta.json +77 -18
- package/dist/util.cjs +144 -34
- package/dist/util.cjs.map +4 -4
- package/dist/util.d.ts +32 -1
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +144 -34
- package/dist/util.js.map +4 -4
- package/dist/util.min.cjs +1 -1
- package/dist/util.min.cjs.map +4 -4
- package/dist/util.min.js +1 -1
- package/dist/util.min.js.map +4 -4
- package/package.json +2 -2
package/dist/constants.d.ts
CHANGED
|
@@ -11,4 +11,6 @@ export declare const HPKE_V1: Uint8Array<ArrayBuffer>;
|
|
|
11
11
|
export declare const WRAPPED_LEN_PREFIX = 2;
|
|
12
12
|
export declare const KEM_SUITE_ID: Uint8Array<ArrayBufferLike>;
|
|
13
13
|
export declare const HPKE_SUITE_ID: Uint8Array<ArrayBufferLike>;
|
|
14
|
+
export declare const KEY_ID_LABEL: Uint8Array<ArrayBuffer>;
|
|
15
|
+
export declare const KEY_ID_NONCE_LENGTH = 12;
|
|
14
16
|
//# sourceMappingURL=constants.d.ts.map
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,MAAM,KAAS,CAAA;AAC5B,eAAO,MAAM,MAAM,IAAS,CAAA;AAC5B,eAAO,MAAM,OAAO,IAAS,CAAA;AAI7B,eAAO,MAAM,OAAO,KAAK,CAAA;AACzB,eAAO,MAAM,EAAE,KAAK,CAAA;AACpB,eAAO,MAAM,EAAE,KAAK,CAAA;AACpB,eAAO,MAAM,UAAU,KAAK,CAAA;AAC5B,eAAO,MAAM,eAAe,KAAK,CAAA;AAGjC,eAAO,MAAM,SAAS,IAAO,CAAA;AAE7B,eAAO,MAAM,OAAO,yBAAsC,CAAA;AAK1D,eAAO,MAAM,kBAAkB,IAAI,CAAA;AAGnC,eAAO,MAAM,YAAY,6BAGxB,CAAA;AAID,eAAO,MAAM,aAAa,6BAKzB,CAAA"}
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,MAAM,KAAS,CAAA;AAC5B,eAAO,MAAM,MAAM,IAAS,CAAA;AAC5B,eAAO,MAAM,OAAO,IAAS,CAAA;AAI7B,eAAO,MAAM,OAAO,KAAK,CAAA;AACzB,eAAO,MAAM,EAAE,KAAK,CAAA;AACpB,eAAO,MAAM,EAAE,KAAK,CAAA;AACpB,eAAO,MAAM,UAAU,KAAK,CAAA;AAC5B,eAAO,MAAM,eAAe,KAAK,CAAA;AAGjC,eAAO,MAAM,SAAS,IAAO,CAAA;AAE7B,eAAO,MAAM,OAAO,yBAAsC,CAAA;AAK1D,eAAO,MAAM,kBAAkB,IAAI,CAAA;AAGnC,eAAO,MAAM,YAAY,6BAGxB,CAAA;AAID,eAAO,MAAM,aAAa,6BAKzB,CAAA;AAUD,eAAO,MAAM,YAAY,yBACU,CAAA;AAMnC,eAAO,MAAM,mBAAmB,KAAK,CAAA"}
|
package/dist/constants.js
CHANGED
|
@@ -1283,6 +1283,19 @@ function toString2(array, encoding = "utf8") {
|
|
|
1283
1283
|
}
|
|
1284
1284
|
__name(toString2, "toString");
|
|
1285
1285
|
|
|
1286
|
+
// src/errors.ts
|
|
1287
|
+
var HpkeError = class extends Error {
|
|
1288
|
+
static {
|
|
1289
|
+
__name(this, "HpkeError");
|
|
1290
|
+
}
|
|
1291
|
+
code;
|
|
1292
|
+
constructor(code2, message, opts) {
|
|
1293
|
+
super(message, opts);
|
|
1294
|
+
this.name = "HpkeError";
|
|
1295
|
+
this.code = code2;
|
|
1296
|
+
}
|
|
1297
|
+
};
|
|
1298
|
+
|
|
1286
1299
|
// src/index.ts
|
|
1287
1300
|
var subtle = globalThis.crypto.subtle;
|
|
1288
1301
|
var open = Object.assign(openBytes, {
|
|
@@ -1297,13 +1310,19 @@ var decrypt = Object.assign(decryptBytes, {
|
|
|
1297
1310
|
});
|
|
1298
1311
|
async function decryptBytes(keypair, message, opts) {
|
|
1299
1312
|
if (message.byteLength < WRAPPED_LEN_PREFIX) {
|
|
1300
|
-
throw new
|
|
1313
|
+
throw new HpkeError(
|
|
1314
|
+
"ERR_MALFORMED_MESSAGE",
|
|
1315
|
+
"malformed message: too short"
|
|
1316
|
+
);
|
|
1301
1317
|
}
|
|
1302
1318
|
const wrappedLen = message[0] << 8 | message[1];
|
|
1303
1319
|
const ivStart = WRAPPED_LEN_PREFIX + wrappedLen;
|
|
1304
1320
|
const ctStart = ivStart + NN;
|
|
1305
1321
|
if (message.byteLength < ctStart + AEAD_TAG_LENGTH) {
|
|
1306
|
-
throw new
|
|
1322
|
+
throw new HpkeError(
|
|
1323
|
+
"ERR_MALFORMED_MESSAGE",
|
|
1324
|
+
"malformed message: too short"
|
|
1325
|
+
);
|
|
1307
1326
|
}
|
|
1308
1327
|
const wrapped = message.slice(WRAPPED_LEN_PREFIX, ivStart);
|
|
1309
1328
|
const iv = message.slice(ivStart, ctStart);
|
|
@@ -1314,11 +1333,20 @@ async function decryptBytes(keypair, message, opts) {
|
|
|
1314
1333
|
opts?.info !== void 0 ? { info: opts.info } : void 0
|
|
1315
1334
|
);
|
|
1316
1335
|
const key = await importAesKeyDecryptOnly(keyBytes);
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1336
|
+
let pt;
|
|
1337
|
+
try {
|
|
1338
|
+
pt = await subtle.decrypt(
|
|
1339
|
+
{ name: "AES-GCM", iv },
|
|
1340
|
+
key,
|
|
1341
|
+
ciphertext
|
|
1342
|
+
);
|
|
1343
|
+
} catch (err) {
|
|
1344
|
+
throw new HpkeError(
|
|
1345
|
+
"ERR_DECRYPT_FAILED",
|
|
1346
|
+
"failed to decrypt the message: tampered ciphertext",
|
|
1347
|
+
{ cause: err }
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1322
1350
|
return new Uint8Array(pt);
|
|
1323
1351
|
}
|
|
1324
1352
|
__name(decryptBytes, "decryptBytes");
|
|
@@ -1339,7 +1367,8 @@ async function decryptFromString(keypair, message, opts) {
|
|
|
1339
1367
|
__name(decryptFromString, "decryptFromString");
|
|
1340
1368
|
function validateKeysize(keysize) {
|
|
1341
1369
|
if (keysize !== 128 && keysize !== 256) {
|
|
1342
|
-
throw new
|
|
1370
|
+
throw new HpkeError(
|
|
1371
|
+
"ERR_INVALID_KEYSIZE",
|
|
1343
1372
|
`invalid keysize: ${keysize} (expected 128 or 256)`
|
|
1344
1373
|
);
|
|
1345
1374
|
}
|
|
@@ -1347,7 +1376,8 @@ function validateKeysize(keysize) {
|
|
|
1347
1376
|
__name(validateKeysize, "validateKeysize");
|
|
1348
1377
|
function validateRawKeyBytes(raw) {
|
|
1349
1378
|
if (raw.length !== 16 && raw.length !== 32) {
|
|
1350
|
-
throw new
|
|
1379
|
+
throw new HpkeError(
|
|
1380
|
+
"ERR_INVALID_AES_KEY",
|
|
1351
1381
|
`invalid aesKey length: ${raw.length} bytes (expected 16 or 32)`
|
|
1352
1382
|
);
|
|
1353
1383
|
}
|
|
@@ -1361,19 +1391,20 @@ function normalizeInfo(info) {
|
|
|
1361
1391
|
__name(normalizeInfo, "normalizeInfo");
|
|
1362
1392
|
async function exportAesKeyBytes(key) {
|
|
1363
1393
|
if (!key.extractable) {
|
|
1364
|
-
throw new
|
|
1394
|
+
throw new HpkeError(
|
|
1395
|
+
"ERR_INVALID_AES_KEY",
|
|
1365
1396
|
"aesKey must be extractable: its raw bytes are what get sealed"
|
|
1366
1397
|
);
|
|
1367
1398
|
}
|
|
1368
1399
|
return new Uint8Array(await subtle.exportKey("raw", key));
|
|
1369
1400
|
}
|
|
1370
1401
|
__name(exportAesKeyBytes, "exportAesKeyBytes");
|
|
1371
|
-
async function importAesKey(raw) {
|
|
1402
|
+
async function importAesKey(raw, extractable) {
|
|
1372
1403
|
return subtle.importKey(
|
|
1373
1404
|
"raw",
|
|
1374
1405
|
raw,
|
|
1375
1406
|
{ name: "AES-GCM" },
|
|
1376
|
-
|
|
1407
|
+
extractable,
|
|
1377
1408
|
["encrypt", "decrypt"]
|
|
1378
1409
|
);
|
|
1379
1410
|
}
|
|
@@ -1428,7 +1459,10 @@ async function keySchedule(sharedSecret, info) {
|
|
|
1428
1459
|
__name(keySchedule, "keySchedule");
|
|
1429
1460
|
async function openRawBytes(keypair, wrapped, opts) {
|
|
1430
1461
|
if (wrapped.byteLength < ENC_LENGTH + AEAD_TAG_LENGTH) {
|
|
1431
|
-
throw new
|
|
1462
|
+
throw new HpkeError(
|
|
1463
|
+
"ERR_MALFORMED_ENVELOPE",
|
|
1464
|
+
"malformed envelope: too short"
|
|
1465
|
+
);
|
|
1432
1466
|
}
|
|
1433
1467
|
const info = normalizeInfo(opts?.info);
|
|
1434
1468
|
const enc = wrapped.slice(0, ENC_LENGTH);
|
|
@@ -1440,7 +1474,7 @@ async function openRawBytes(keypair, wrapped, opts) {
|
|
|
1440
1474
|
__name(openRawBytes, "openRawBytes");
|
|
1441
1475
|
async function openBytes(keypair, wrapped, opts) {
|
|
1442
1476
|
const keyBytes = await openRawBytes(keypair, wrapped, opts);
|
|
1443
|
-
return importAesKey(keyBytes);
|
|
1477
|
+
return importAesKey(keyBytes, opts?.extractable ?? false);
|
|
1444
1478
|
}
|
|
1445
1479
|
__name(openBytes, "openBytes");
|
|
1446
1480
|
async function encryptKey(recipient, aesKey, opts) {
|
|
@@ -1464,8 +1498,11 @@ async function encryptKey(recipient, aesKey, opts) {
|
|
|
1464
1498
|
const { key, baseNonce } = await keySchedule(sharedSecret, info);
|
|
1465
1499
|
const ciphertext = await aeadSeal(key, baseNonce, keyBytes);
|
|
1466
1500
|
const wrapped = concat2(enc, ciphertext);
|
|
1467
|
-
const aesGcmKey = await importAesKey(
|
|
1468
|
-
|
|
1501
|
+
const aesGcmKey = await importAesKey(
|
|
1502
|
+
keyBytes,
|
|
1503
|
+
opts?.extractable ?? false
|
|
1504
|
+
);
|
|
1505
|
+
return { wrapped, key: aesGcmKey };
|
|
1469
1506
|
}
|
|
1470
1507
|
__name(encryptKey, "encryptKey");
|
|
1471
1508
|
|
|
@@ -1495,24 +1532,38 @@ async function aeadOpen(key, nonce, ciphertext) {
|
|
|
1495
1532
|
false,
|
|
1496
1533
|
["decrypt"]
|
|
1497
1534
|
);
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1535
|
+
let pt;
|
|
1536
|
+
try {
|
|
1537
|
+
pt = await subtle2.decrypt(
|
|
1538
|
+
{ name: "AES-GCM", iv: nonce },
|
|
1539
|
+
k,
|
|
1540
|
+
ciphertext
|
|
1541
|
+
);
|
|
1542
|
+
} catch (err) {
|
|
1543
|
+
throw new HpkeError(
|
|
1544
|
+
"ERR_DECRYPT_FAILED",
|
|
1545
|
+
"failed to decrypt the wrapped key: wrong keypair, wrong info, or a tampered envelope",
|
|
1546
|
+
{ cause: err }
|
|
1547
|
+
);
|
|
1548
|
+
}
|
|
1503
1549
|
return new Uint8Array(pt);
|
|
1504
1550
|
}
|
|
1505
1551
|
__name(aeadOpen, "aeadOpen");
|
|
1506
1552
|
async function encryptBytes(recipient, message, aesKey, opts) {
|
|
1507
1553
|
const plaintext = typeof message === "string" ? new TextEncoder().encode(message) : message;
|
|
1508
|
-
const {
|
|
1554
|
+
const { wrapped, key } = await encryptKey(recipient, aesKey, opts);
|
|
1509
1555
|
const iv = globalThis.crypto.getRandomValues(new Uint8Array(NN));
|
|
1510
1556
|
const ct = new Uint8Array(await subtle2.encrypt(
|
|
1511
1557
|
{ name: "AES-GCM", iv },
|
|
1512
1558
|
key,
|
|
1513
1559
|
plaintext
|
|
1514
1560
|
));
|
|
1515
|
-
return concat2(
|
|
1561
|
+
return concat2(
|
|
1562
|
+
i2osp(wrapped.length, WRAPPED_LEN_PREFIX),
|
|
1563
|
+
wrapped,
|
|
1564
|
+
iv,
|
|
1565
|
+
ct
|
|
1566
|
+
);
|
|
1516
1567
|
}
|
|
1517
1568
|
__name(encryptBytes, "encryptBytes");
|
|
1518
1569
|
async function encryptToString(recipient, message, aesKey, opts) {
|
|
@@ -1578,7 +1629,8 @@ async function exportRawPublic(key) {
|
|
|
1578
1629
|
__name(exportRawPublic, "exportRawPublic");
|
|
1579
1630
|
async function importRawPublic(raw) {
|
|
1580
1631
|
if (raw.length !== ENC_LENGTH) {
|
|
1581
|
-
throw new
|
|
1632
|
+
throw new HpkeError(
|
|
1633
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1582
1634
|
`invalid public key length: ${raw.length} bytes (expected ${ENC_LENGTH})`
|
|
1583
1635
|
);
|
|
1584
1636
|
}
|
|
@@ -1597,9 +1649,16 @@ async function resolveRecipientPublicKey(recipient) {
|
|
|
1597
1649
|
}
|
|
1598
1650
|
if (recipient instanceof CryptoKey) {
|
|
1599
1651
|
if (recipient.type !== "public") {
|
|
1600
|
-
throw new
|
|
1652
|
+
throw new HpkeError(
|
|
1653
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1654
|
+
"recipient CryptoKey must be a public key"
|
|
1655
|
+
);
|
|
1601
1656
|
}
|
|
1602
|
-
assertX25519Algorithm(
|
|
1657
|
+
assertX25519Algorithm(
|
|
1658
|
+
recipient,
|
|
1659
|
+
"recipient CryptoKey",
|
|
1660
|
+
"ERR_INVALID_RECIPIENT_KEY"
|
|
1661
|
+
);
|
|
1603
1662
|
return recipient;
|
|
1604
1663
|
}
|
|
1605
1664
|
if (typeof recipient.publicKey === "string") {
|
|
@@ -1609,17 +1668,28 @@ async function resolveRecipientPublicKey(recipient) {
|
|
|
1609
1668
|
const pair = recipient;
|
|
1610
1669
|
if (pair.publicKey instanceof CryptoKey) {
|
|
1611
1670
|
if (pair.publicKey.type !== "public") {
|
|
1612
|
-
throw new
|
|
1671
|
+
throw new HpkeError(
|
|
1672
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1673
|
+
"recipient keypair publicKey must be a public key"
|
|
1674
|
+
);
|
|
1613
1675
|
}
|
|
1614
|
-
assertX25519Algorithm(
|
|
1676
|
+
assertX25519Algorithm(
|
|
1677
|
+
pair.publicKey,
|
|
1678
|
+
"recipient keypair publicKey",
|
|
1679
|
+
"ERR_INVALID_RECIPIENT_KEY"
|
|
1680
|
+
);
|
|
1615
1681
|
return pair.publicKey;
|
|
1616
1682
|
}
|
|
1617
|
-
throw new
|
|
1683
|
+
throw new HpkeError(
|
|
1684
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1685
|
+
"unrecognized recipient key form"
|
|
1686
|
+
);
|
|
1618
1687
|
}
|
|
1619
1688
|
__name(resolveRecipientPublicKey, "resolveRecipientPublicKey");
|
|
1620
|
-
function assertX25519Algorithm(key, context) {
|
|
1689
|
+
function assertX25519Algorithm(key, context, code2) {
|
|
1621
1690
|
if (key.algorithm.name !== "X25519") {
|
|
1622
|
-
throw new
|
|
1691
|
+
throw new HpkeError(
|
|
1692
|
+
code2,
|
|
1623
1693
|
`${context} must be an X25519 key (got ${key.algorithm.name})`
|
|
1624
1694
|
);
|
|
1625
1695
|
}
|
|
@@ -1633,7 +1703,8 @@ async function dh(priv, pub) {
|
|
|
1633
1703
|
);
|
|
1634
1704
|
const bytes = new Uint8Array(bits);
|
|
1635
1705
|
if (bytes.every((b) => b === 0)) {
|
|
1636
|
-
throw new
|
|
1706
|
+
throw new HpkeError(
|
|
1707
|
+
"ERR_SMALL_ORDER_KEY",
|
|
1637
1708
|
"X25519 shared secret is all-zero (small-order public key?)"
|
|
1638
1709
|
);
|
|
1639
1710
|
}
|
|
@@ -1672,9 +1743,16 @@ async function encap(pkR) {
|
|
|
1672
1743
|
__name(encap, "encap");
|
|
1673
1744
|
async function decap(enc, keypair) {
|
|
1674
1745
|
if (keypair.privateKey.type !== "private") {
|
|
1675
|
-
throw new
|
|
1746
|
+
throw new HpkeError(
|
|
1747
|
+
"ERR_INVALID_KEYPAIR",
|
|
1748
|
+
"keypair.privateKey must be a private key"
|
|
1749
|
+
);
|
|
1676
1750
|
}
|
|
1677
|
-
assertX25519Algorithm(
|
|
1751
|
+
assertX25519Algorithm(
|
|
1752
|
+
keypair.privateKey,
|
|
1753
|
+
"keypair.privateKey",
|
|
1754
|
+
"ERR_INVALID_KEYPAIR"
|
|
1755
|
+
);
|
|
1678
1756
|
const pkE = await subtle2.importKey(
|
|
1679
1757
|
"raw",
|
|
1680
1758
|
enc,
|
|
@@ -1733,6 +1811,8 @@ var HPKE_SUITE_ID = concat2(
|
|
|
1733
1811
|
i2osp(KDF_ID, 2),
|
|
1734
1812
|
i2osp(AEAD_ID, 2)
|
|
1735
1813
|
);
|
|
1814
|
+
var KEY_ID_LABEL = new TextEncoder().encode("simple-hpke/keyId/v1");
|
|
1815
|
+
var KEY_ID_NONCE_LENGTH = 12;
|
|
1736
1816
|
export {
|
|
1737
1817
|
AEAD_ID,
|
|
1738
1818
|
AEAD_TAG_LENGTH,
|
|
@@ -1742,6 +1822,8 @@ export {
|
|
|
1742
1822
|
KDF_ID,
|
|
1743
1823
|
KEM_ID,
|
|
1744
1824
|
KEM_SUITE_ID,
|
|
1825
|
+
KEY_ID_LABEL,
|
|
1826
|
+
KEY_ID_NONCE_LENGTH,
|
|
1745
1827
|
MODE_BASE,
|
|
1746
1828
|
NK,
|
|
1747
1829
|
NN,
|