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.
@@ -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,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 Error("malformed message: too short");
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 Error("malformed message: too short");
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
- const pt = await subtle.decrypt(
1328
- { name: "AES-GCM", iv },
1329
- key,
1330
- ciphertext
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 Error(
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 Error(
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 Error(
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
- true,
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 Error("malformed envelope: too short");
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(keyBytes);
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
- const pt = await subtle2.decrypt(
1509
- { name: "AES-GCM", iv: nonce },
1510
- k,
1511
- ciphertext
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
- let enc;
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(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
+ );
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 Error(
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 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
+ );
1621
1656
  }
1622
- assertX25519Algorithm(recipient, "recipient CryptoKey");
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 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
+ );
1633
1675
  }
1634
- assertX25519Algorithm(pair.publicKey, "recipient keypair publicKey");
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 Error("unrecognized recipient key form");
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 Error(
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 Error(
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 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
+ );
1696
1750
  }
1697
- assertX25519Algorithm(keypair.privateKey, "keypair.privateKey");
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,