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.
@@ -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
@@ -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 Error("malformed message: too short");
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 Error("malformed message: too short");
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
- const pt = await subtle.decrypt(
1318
- { name: "AES-GCM", iv },
1319
- key,
1320
- ciphertext
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 Error(
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 Error(
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 Error(
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
- true,
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 Error("malformed envelope: too short");
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(keyBytes);
1468
- return { enc: wrapped, key: aesGcmKey };
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
- const pt = await subtle2.decrypt(
1499
- { name: "AES-GCM", iv: nonce },
1500
- k,
1501
- ciphertext
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 { enc, key } = await encryptKey(recipient, aesKey, opts);
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(i2osp(enc.length, WRAPPED_LEN_PREFIX), enc, iv, ct);
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 Error(
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 Error("recipient CryptoKey must be a public key");
1652
+ throw new HpkeError(
1653
+ "ERR_INVALID_RECIPIENT_KEY",
1654
+ "recipient CryptoKey must be a public key"
1655
+ );
1601
1656
  }
1602
- assertX25519Algorithm(recipient, "recipient CryptoKey");
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 Error("recipient keypair publicKey must be a public key");
1671
+ throw new HpkeError(
1672
+ "ERR_INVALID_RECIPIENT_KEY",
1673
+ "recipient keypair publicKey must be a public key"
1674
+ );
1613
1675
  }
1614
- assertX25519Algorithm(pair.publicKey, "recipient keypair publicKey");
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 Error("unrecognized recipient key form");
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 Error(
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 Error(
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 Error("keypair.privateKey must be a private key");
1746
+ throw new HpkeError(
1747
+ "ERR_INVALID_KEYPAIR",
1748
+ "keypair.privateKey must be a private key"
1749
+ );
1676
1750
  }
1677
- assertX25519Algorithm(keypair.privateKey, "keypair.privateKey");
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,