simple-hpke 0.1.5 → 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 +137 -23
- package/dist/constants.cjs +115 -53
- 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 +115 -53
- 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 -51
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +53 -32
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +147 -51
- 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 +79 -20
- package/dist/util.cjs +143 -53
- package/dist/util.cjs.map +4 -4
- package/dist/util.d.ts +33 -2
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +143 -53
- 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,18 +1283,21 @@ 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
|
-
async function create2(recipient, opts) {
|
|
1289
|
-
const { wrapped, key } = await encryptKey(recipient, null, opts);
|
|
1290
|
-
return { enc: wrapped, key };
|
|
1291
|
-
}
|
|
1292
|
-
__name(create2, "create");
|
|
1293
|
-
async function seal(recipient, aesKey, opts) {
|
|
1294
|
-
const { wrapped, key } = await encryptKey(recipient, aesKey, opts);
|
|
1295
|
-
return { enc: wrapped, key };
|
|
1296
|
-
}
|
|
1297
|
-
__name(seal, "seal");
|
|
1298
1301
|
var open = Object.assign(openBytes, {
|
|
1299
1302
|
raw: openRawBytes
|
|
1300
1303
|
});
|
|
@@ -1307,13 +1310,19 @@ var decrypt = Object.assign(decryptBytes, {
|
|
|
1307
1310
|
});
|
|
1308
1311
|
async function decryptBytes(keypair, message, opts) {
|
|
1309
1312
|
if (message.byteLength < WRAPPED_LEN_PREFIX) {
|
|
1310
|
-
throw new
|
|
1313
|
+
throw new HpkeError(
|
|
1314
|
+
"ERR_MALFORMED_MESSAGE",
|
|
1315
|
+
"malformed message: too short"
|
|
1316
|
+
);
|
|
1311
1317
|
}
|
|
1312
1318
|
const wrappedLen = message[0] << 8 | message[1];
|
|
1313
1319
|
const ivStart = WRAPPED_LEN_PREFIX + wrappedLen;
|
|
1314
1320
|
const ctStart = ivStart + NN;
|
|
1315
1321
|
if (message.byteLength < ctStart + AEAD_TAG_LENGTH) {
|
|
1316
|
-
throw new
|
|
1322
|
+
throw new HpkeError(
|
|
1323
|
+
"ERR_MALFORMED_MESSAGE",
|
|
1324
|
+
"malformed message: too short"
|
|
1325
|
+
);
|
|
1317
1326
|
}
|
|
1318
1327
|
const wrapped = message.slice(WRAPPED_LEN_PREFIX, ivStart);
|
|
1319
1328
|
const iv = message.slice(ivStart, ctStart);
|
|
@@ -1324,11 +1333,20 @@ async function decryptBytes(keypair, message, opts) {
|
|
|
1324
1333
|
opts?.info !== void 0 ? { info: opts.info } : void 0
|
|
1325
1334
|
);
|
|
1326
1335
|
const key = await importAesKeyDecryptOnly(keyBytes);
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
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
|
+
}
|
|
1332
1350
|
return new Uint8Array(pt);
|
|
1333
1351
|
}
|
|
1334
1352
|
__name(decryptBytes, "decryptBytes");
|
|
@@ -1349,7 +1367,8 @@ async function decryptFromString(keypair, message, opts) {
|
|
|
1349
1367
|
__name(decryptFromString, "decryptFromString");
|
|
1350
1368
|
function validateKeysize(keysize) {
|
|
1351
1369
|
if (keysize !== 128 && keysize !== 256) {
|
|
1352
|
-
throw new
|
|
1370
|
+
throw new HpkeError(
|
|
1371
|
+
"ERR_INVALID_KEYSIZE",
|
|
1353
1372
|
`invalid keysize: ${keysize} (expected 128 or 256)`
|
|
1354
1373
|
);
|
|
1355
1374
|
}
|
|
@@ -1357,7 +1376,8 @@ function validateKeysize(keysize) {
|
|
|
1357
1376
|
__name(validateKeysize, "validateKeysize");
|
|
1358
1377
|
function validateRawKeyBytes(raw) {
|
|
1359
1378
|
if (raw.length !== 16 && raw.length !== 32) {
|
|
1360
|
-
throw new
|
|
1379
|
+
throw new HpkeError(
|
|
1380
|
+
"ERR_INVALID_AES_KEY",
|
|
1361
1381
|
`invalid aesKey length: ${raw.length} bytes (expected 16 or 32)`
|
|
1362
1382
|
);
|
|
1363
1383
|
}
|
|
@@ -1371,19 +1391,20 @@ function normalizeInfo(info) {
|
|
|
1371
1391
|
__name(normalizeInfo, "normalizeInfo");
|
|
1372
1392
|
async function exportAesKeyBytes(key) {
|
|
1373
1393
|
if (!key.extractable) {
|
|
1374
|
-
throw new
|
|
1394
|
+
throw new HpkeError(
|
|
1395
|
+
"ERR_INVALID_AES_KEY",
|
|
1375
1396
|
"aesKey must be extractable: its raw bytes are what get sealed"
|
|
1376
1397
|
);
|
|
1377
1398
|
}
|
|
1378
1399
|
return new Uint8Array(await subtle.exportKey("raw", key));
|
|
1379
1400
|
}
|
|
1380
1401
|
__name(exportAesKeyBytes, "exportAesKeyBytes");
|
|
1381
|
-
async function importAesKey(raw) {
|
|
1402
|
+
async function importAesKey(raw, extractable) {
|
|
1382
1403
|
return subtle.importKey(
|
|
1383
1404
|
"raw",
|
|
1384
1405
|
raw,
|
|
1385
1406
|
{ name: "AES-GCM" },
|
|
1386
|
-
|
|
1407
|
+
extractable,
|
|
1387
1408
|
["encrypt", "decrypt"]
|
|
1388
1409
|
);
|
|
1389
1410
|
}
|
|
@@ -1438,7 +1459,10 @@ async function keySchedule(sharedSecret, info) {
|
|
|
1438
1459
|
__name(keySchedule, "keySchedule");
|
|
1439
1460
|
async function openRawBytes(keypair, wrapped, opts) {
|
|
1440
1461
|
if (wrapped.byteLength < ENC_LENGTH + AEAD_TAG_LENGTH) {
|
|
1441
|
-
throw new
|
|
1462
|
+
throw new HpkeError(
|
|
1463
|
+
"ERR_MALFORMED_ENVELOPE",
|
|
1464
|
+
"malformed envelope: too short"
|
|
1465
|
+
);
|
|
1442
1466
|
}
|
|
1443
1467
|
const info = normalizeInfo(opts?.info);
|
|
1444
1468
|
const enc = wrapped.slice(0, ENC_LENGTH);
|
|
@@ -1450,7 +1474,7 @@ async function openRawBytes(keypair, wrapped, opts) {
|
|
|
1450
1474
|
__name(openRawBytes, "openRawBytes");
|
|
1451
1475
|
async function openBytes(keypair, wrapped, opts) {
|
|
1452
1476
|
const keyBytes = await openRawBytes(keypair, wrapped, opts);
|
|
1453
|
-
return importAesKey(keyBytes);
|
|
1477
|
+
return importAesKey(keyBytes, opts?.extractable ?? false);
|
|
1454
1478
|
}
|
|
1455
1479
|
__name(openBytes, "openBytes");
|
|
1456
1480
|
async function encryptKey(recipient, aesKey, opts) {
|
|
@@ -1474,7 +1498,10 @@ async function encryptKey(recipient, aesKey, opts) {
|
|
|
1474
1498
|
const { key, baseNonce } = await keySchedule(sharedSecret, info);
|
|
1475
1499
|
const ciphertext = await aeadSeal(key, baseNonce, keyBytes);
|
|
1476
1500
|
const wrapped = concat2(enc, ciphertext);
|
|
1477
|
-
const aesGcmKey = await importAesKey(
|
|
1501
|
+
const aesGcmKey = await importAesKey(
|
|
1502
|
+
keyBytes,
|
|
1503
|
+
opts?.extractable ?? false
|
|
1504
|
+
);
|
|
1478
1505
|
return { wrapped, key: aesGcmKey };
|
|
1479
1506
|
}
|
|
1480
1507
|
__name(encryptKey, "encryptKey");
|
|
@@ -1505,34 +1532,38 @@ async function aeadOpen(key, nonce, ciphertext) {
|
|
|
1505
1532
|
false,
|
|
1506
1533
|
["decrypt"]
|
|
1507
1534
|
);
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
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
|
+
}
|
|
1513
1549
|
return new Uint8Array(pt);
|
|
1514
1550
|
}
|
|
1515
1551
|
__name(aeadOpen, "aeadOpen");
|
|
1516
1552
|
async function encryptBytes(recipient, message, aesKey, opts) {
|
|
1517
1553
|
const plaintext = typeof message === "string" ? new TextEncoder().encode(message) : message;
|
|
1518
|
-
|
|
1519
|
-
let key;
|
|
1520
|
-
if (!aesKey) {
|
|
1521
|
-
const keys = await create2(recipient, opts);
|
|
1522
|
-
enc = keys.enc;
|
|
1523
|
-
key = keys.key;
|
|
1524
|
-
} else {
|
|
1525
|
-
const keys = await seal(recipient, aesKey, opts);
|
|
1526
|
-
enc = keys.enc;
|
|
1527
|
-
key = keys.key;
|
|
1528
|
-
}
|
|
1554
|
+
const { wrapped, key } = await encryptKey(recipient, aesKey, opts);
|
|
1529
1555
|
const iv = globalThis.crypto.getRandomValues(new Uint8Array(NN));
|
|
1530
1556
|
const ct = new Uint8Array(await subtle2.encrypt(
|
|
1531
1557
|
{ name: "AES-GCM", iv },
|
|
1532
1558
|
key,
|
|
1533
1559
|
plaintext
|
|
1534
1560
|
));
|
|
1535
|
-
return concat2(
|
|
1561
|
+
return concat2(
|
|
1562
|
+
i2osp(wrapped.length, WRAPPED_LEN_PREFIX),
|
|
1563
|
+
wrapped,
|
|
1564
|
+
iv,
|
|
1565
|
+
ct
|
|
1566
|
+
);
|
|
1536
1567
|
}
|
|
1537
1568
|
__name(encryptBytes, "encryptBytes");
|
|
1538
1569
|
async function encryptToString(recipient, message, aesKey, opts) {
|
|
@@ -1598,7 +1629,8 @@ async function exportRawPublic(key) {
|
|
|
1598
1629
|
__name(exportRawPublic, "exportRawPublic");
|
|
1599
1630
|
async function importRawPublic(raw) {
|
|
1600
1631
|
if (raw.length !== ENC_LENGTH) {
|
|
1601
|
-
throw new
|
|
1632
|
+
throw new HpkeError(
|
|
1633
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1602
1634
|
`invalid public key length: ${raw.length} bytes (expected ${ENC_LENGTH})`
|
|
1603
1635
|
);
|
|
1604
1636
|
}
|
|
@@ -1617,9 +1649,16 @@ async function resolveRecipientPublicKey(recipient) {
|
|
|
1617
1649
|
}
|
|
1618
1650
|
if (recipient instanceof CryptoKey) {
|
|
1619
1651
|
if (recipient.type !== "public") {
|
|
1620
|
-
throw new
|
|
1652
|
+
throw new HpkeError(
|
|
1653
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1654
|
+
"recipient CryptoKey must be a public key"
|
|
1655
|
+
);
|
|
1621
1656
|
}
|
|
1622
|
-
assertX25519Algorithm(
|
|
1657
|
+
assertX25519Algorithm(
|
|
1658
|
+
recipient,
|
|
1659
|
+
"recipient CryptoKey",
|
|
1660
|
+
"ERR_INVALID_RECIPIENT_KEY"
|
|
1661
|
+
);
|
|
1623
1662
|
return recipient;
|
|
1624
1663
|
}
|
|
1625
1664
|
if (typeof recipient.publicKey === "string") {
|
|
@@ -1629,17 +1668,28 @@ async function resolveRecipientPublicKey(recipient) {
|
|
|
1629
1668
|
const pair = recipient;
|
|
1630
1669
|
if (pair.publicKey instanceof CryptoKey) {
|
|
1631
1670
|
if (pair.publicKey.type !== "public") {
|
|
1632
|
-
throw new
|
|
1671
|
+
throw new HpkeError(
|
|
1672
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1673
|
+
"recipient keypair publicKey must be a public key"
|
|
1674
|
+
);
|
|
1633
1675
|
}
|
|
1634
|
-
assertX25519Algorithm(
|
|
1676
|
+
assertX25519Algorithm(
|
|
1677
|
+
pair.publicKey,
|
|
1678
|
+
"recipient keypair publicKey",
|
|
1679
|
+
"ERR_INVALID_RECIPIENT_KEY"
|
|
1680
|
+
);
|
|
1635
1681
|
return pair.publicKey;
|
|
1636
1682
|
}
|
|
1637
|
-
throw new
|
|
1683
|
+
throw new HpkeError(
|
|
1684
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1685
|
+
"unrecognized recipient key form"
|
|
1686
|
+
);
|
|
1638
1687
|
}
|
|
1639
1688
|
__name(resolveRecipientPublicKey, "resolveRecipientPublicKey");
|
|
1640
|
-
function assertX25519Algorithm(key, context) {
|
|
1689
|
+
function assertX25519Algorithm(key, context, code2) {
|
|
1641
1690
|
if (key.algorithm.name !== "X25519") {
|
|
1642
|
-
throw new
|
|
1691
|
+
throw new HpkeError(
|
|
1692
|
+
code2,
|
|
1643
1693
|
`${context} must be an X25519 key (got ${key.algorithm.name})`
|
|
1644
1694
|
);
|
|
1645
1695
|
}
|
|
@@ -1653,7 +1703,8 @@ async function dh(priv, pub) {
|
|
|
1653
1703
|
);
|
|
1654
1704
|
const bytes = new Uint8Array(bits);
|
|
1655
1705
|
if (bytes.every((b) => b === 0)) {
|
|
1656
|
-
throw new
|
|
1706
|
+
throw new HpkeError(
|
|
1707
|
+
"ERR_SMALL_ORDER_KEY",
|
|
1657
1708
|
"X25519 shared secret is all-zero (small-order public key?)"
|
|
1658
1709
|
);
|
|
1659
1710
|
}
|
|
@@ -1692,9 +1743,16 @@ async function encap(pkR) {
|
|
|
1692
1743
|
__name(encap, "encap");
|
|
1693
1744
|
async function decap(enc, keypair) {
|
|
1694
1745
|
if (keypair.privateKey.type !== "private") {
|
|
1695
|
-
throw new
|
|
1746
|
+
throw new HpkeError(
|
|
1747
|
+
"ERR_INVALID_KEYPAIR",
|
|
1748
|
+
"keypair.privateKey must be a private key"
|
|
1749
|
+
);
|
|
1696
1750
|
}
|
|
1697
|
-
assertX25519Algorithm(
|
|
1751
|
+
assertX25519Algorithm(
|
|
1752
|
+
keypair.privateKey,
|
|
1753
|
+
"keypair.privateKey",
|
|
1754
|
+
"ERR_INVALID_KEYPAIR"
|
|
1755
|
+
);
|
|
1698
1756
|
const pkE = await subtle2.importKey(
|
|
1699
1757
|
"raw",
|
|
1700
1758
|
enc,
|
|
@@ -1753,6 +1811,8 @@ var HPKE_SUITE_ID = concat2(
|
|
|
1753
1811
|
i2osp(KDF_ID, 2),
|
|
1754
1812
|
i2osp(AEAD_ID, 2)
|
|
1755
1813
|
);
|
|
1814
|
+
var KEY_ID_LABEL = new TextEncoder().encode("simple-hpke/keyId/v1");
|
|
1815
|
+
var KEY_ID_NONCE_LENGTH = 12;
|
|
1756
1816
|
export {
|
|
1757
1817
|
AEAD_ID,
|
|
1758
1818
|
AEAD_TAG_LENGTH,
|
|
@@ -1762,6 +1822,8 @@ export {
|
|
|
1762
1822
|
KDF_ID,
|
|
1763
1823
|
KEM_ID,
|
|
1764
1824
|
KEM_SUITE_ID,
|
|
1825
|
+
KEY_ID_LABEL,
|
|
1826
|
+
KEY_ID_NONCE_LENGTH,
|
|
1765
1827
|
MODE_BASE,
|
|
1766
1828
|
NK,
|
|
1767
1829
|
NN,
|