simple-hpke 0.3.1 → 0.3.3

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/index.cjs CHANGED
@@ -27,7 +27,8 @@ __export(index_exports, {
27
27
  encrypt: () => encrypt,
28
28
  encryptKey: () => encryptKey,
29
29
  keyId: () => keyId,
30
- open: () => open
30
+ open: () => open,
31
+ verifyKeyId: () => verifyKeyId
31
32
  });
32
33
  module.exports = __toCommonJS(index_exports);
33
34
 
@@ -1391,6 +1392,12 @@ async function aeadOpen(key, nonce, ciphertext) {
1391
1392
  }
1392
1393
  __name(aeadOpen, "aeadOpen");
1393
1394
  async function encryptBytes(recipient, message, aesKey, opts) {
1395
+ if (aesKey instanceof CryptoKey && !aesKey.usages.includes("encrypt")) {
1396
+ throw new HpkeError(
1397
+ "ERR_INVALID_AES_KEY",
1398
+ 'aesKey is missing the "encrypt" usage, which encrypt needs to encrypt the message'
1399
+ );
1400
+ }
1394
1401
  const plaintext = typeof message === "string" ? new TextEncoder().encode(message) : message;
1395
1402
  const { wrapped, key } = await encryptKey(recipient, aesKey, opts);
1396
1403
  const iv = globalThis.crypto.getRandomValues(new Uint8Array(NN));
@@ -1489,37 +1496,28 @@ async function resolveRecipientPublicKey(recipient) {
1489
1496
  return importRawPublic(recipient);
1490
1497
  }
1491
1498
  if (recipient instanceof CryptoKey) {
1492
- if (recipient.type !== "public") {
1493
- throw new HpkeError(
1494
- "ERR_INVALID_RECIPIENT_KEY",
1495
- "recipient CryptoKey must be a public key"
1496
- );
1497
- }
1498
- assertX25519Algorithm(
1499
- recipient,
1500
- "recipient CryptoKey",
1501
- "ERR_INVALID_RECIPIENT_KEY"
1502
- );
1503
- return recipient;
1499
+ return assertRecipientPublicKey(recipient, "recipient CryptoKey");
1504
1500
  }
1505
1501
  if (typeof recipient.publicKey === "string") {
1506
1502
  const { publicKey, encoding } = recipient;
1507
- return importRawPublic(fromString2(publicKey, encoding ?? "base64url"));
1508
- }
1509
- const pair = recipient;
1510
- if (pair.publicKey instanceof CryptoKey) {
1511
- if (pair.publicKey.type !== "public") {
1503
+ let decoded;
1504
+ try {
1505
+ decoded = fromString2(publicKey, encoding ?? "base64url");
1506
+ } catch (err) {
1512
1507
  throw new HpkeError(
1513
1508
  "ERR_INVALID_RECIPIENT_KEY",
1514
- "recipient keypair publicKey must be a public key"
1509
+ `recipient publicKey string does not decode as ${encoding ?? "base64url"}`,
1510
+ { cause: err }
1515
1511
  );
1516
1512
  }
1517
- assertX25519Algorithm(
1513
+ return importRawPublic(decoded);
1514
+ }
1515
+ const pair = recipient;
1516
+ if (pair.publicKey instanceof CryptoKey) {
1517
+ return assertRecipientPublicKey(
1518
1518
  pair.publicKey,
1519
- "recipient keypair publicKey",
1520
- "ERR_INVALID_RECIPIENT_KEY"
1519
+ "recipient keypair publicKey"
1521
1520
  );
1522
- return pair.publicKey;
1523
1521
  }
1524
1522
  throw new HpkeError(
1525
1523
  "ERR_INVALID_RECIPIENT_KEY",
@@ -1527,6 +1525,23 @@ async function resolveRecipientPublicKey(recipient) {
1527
1525
  );
1528
1526
  }
1529
1527
  __name(resolveRecipientPublicKey, "resolveRecipientPublicKey");
1528
+ function assertRecipientPublicKey(key, context) {
1529
+ if (key.type !== "public") {
1530
+ throw new HpkeError(
1531
+ "ERR_INVALID_RECIPIENT_KEY",
1532
+ `${context} must be a public key`
1533
+ );
1534
+ }
1535
+ assertX25519Algorithm(key, context, "ERR_INVALID_RECIPIENT_KEY");
1536
+ if (!key.extractable) {
1537
+ throw new HpkeError(
1538
+ "ERR_INVALID_RECIPIENT_KEY",
1539
+ `${context} must be extractable (the KEM exports it)`
1540
+ );
1541
+ }
1542
+ return key;
1543
+ }
1544
+ __name(assertRecipientPublicKey, "assertRecipientPublicKey");
1530
1545
  function assertX25519Algorithm(key, context, code2) {
1531
1546
  if (key.algorithm.name !== "X25519") {
1532
1547
  throw new HpkeError(
@@ -1537,11 +1552,23 @@ function assertX25519Algorithm(key, context, code2) {
1537
1552
  }
1538
1553
  __name(assertX25519Algorithm, "assertX25519Algorithm");
1539
1554
  async function dh(priv, pub) {
1540
- const bits = await subtle.deriveBits(
1541
- { name: "X25519", public: pub },
1542
- priv,
1543
- 256
1544
- );
1555
+ let bits;
1556
+ try {
1557
+ bits = await subtle.deriveBits(
1558
+ { name: "X25519", public: pub },
1559
+ priv,
1560
+ 256
1561
+ );
1562
+ } catch (err) {
1563
+ if (err?.name === "OperationError") {
1564
+ throw new HpkeError(
1565
+ "ERR_SMALL_ORDER_KEY",
1566
+ "X25519 deriveBits rejected a small-order public key",
1567
+ { cause: err }
1568
+ );
1569
+ }
1570
+ throw err;
1571
+ }
1545
1572
  const bytes = new Uint8Array(bits);
1546
1573
  if (bytes.every((b) => b === 0)) {
1547
1574
  throw new HpkeError(
@@ -1594,6 +1621,23 @@ async function decap(enc, keypair) {
1594
1621
  "keypair.privateKey",
1595
1622
  "ERR_INVALID_KEYPAIR"
1596
1623
  );
1624
+ if (!keypair.privateKey.usages.includes("deriveBits")) {
1625
+ throw new HpkeError(
1626
+ "ERR_INVALID_KEYPAIR",
1627
+ "keypair.privateKey must have the deriveBits usage"
1628
+ );
1629
+ }
1630
+ if (keypair.publicKey.type !== "public" || !keypair.publicKey.extractable) {
1631
+ throw new HpkeError(
1632
+ "ERR_INVALID_KEYPAIR",
1633
+ "keypair.publicKey must be an extractable public key"
1634
+ );
1635
+ }
1636
+ assertX25519Algorithm(
1637
+ keypair.publicKey,
1638
+ "keypair.publicKey",
1639
+ "ERR_INVALID_KEYPAIR"
1640
+ );
1597
1641
  const pkE = await subtle.importKey(
1598
1642
  "raw",
1599
1643
  enc,
@@ -1636,6 +1680,16 @@ async function keyId(key) {
1636
1680
  return toString2(new Uint8Array(digest2), "base64url");
1637
1681
  }
1638
1682
  __name(keyId, "keyId");
1683
+ async function verifyKeyId(key, expected) {
1684
+ const actual = await keyId(key);
1685
+ if (actual.length !== expected.length) return false;
1686
+ let diff = 0;
1687
+ for (let i = 0; i < actual.length; i++) {
1688
+ diff |= actual.charCodeAt(i) ^ expected.charCodeAt(i);
1689
+ }
1690
+ return diff === 0;
1691
+ }
1692
+ __name(verifyKeyId, "verifyKeyId");
1639
1693
  function i2osp(n, len) {
1640
1694
  const out = new Uint8Array(len);
1641
1695
  let v = n;
@@ -1724,7 +1778,16 @@ async function decryptToString(keypair, message, opts) {
1724
1778
  }
1725
1779
  __name(decryptToString, "decryptToString");
1726
1780
  async function decryptFromString(keypair, message, opts) {
1727
- const bytes = fromString2(message, "base64url");
1781
+ let bytes;
1782
+ try {
1783
+ bytes = fromString2(message, "base64url");
1784
+ } catch (err) {
1785
+ throw new HpkeError(
1786
+ "ERR_MALFORMED_MESSAGE",
1787
+ "malformed message: not base64url",
1788
+ { cause: err }
1789
+ );
1790
+ }
1728
1791
  const plaintext = await decryptBytes(
1729
1792
  keypair,
1730
1793
  bytes,
@@ -1852,6 +1915,12 @@ async function encryptKey(recipient, aesKey, opts) {
1852
1915
  validateRawKeyBytes(aesKey);
1853
1916
  keyBytes = aesKey;
1854
1917
  } else if (aesKey) {
1918
+ if (aesKey.algorithm.name !== "AES-GCM") {
1919
+ throw new HpkeError(
1920
+ "ERR_INVALID_AES_KEY",
1921
+ "aesKey must be an AES-GCM key, got " + aesKey.algorithm.name
1922
+ );
1923
+ }
1855
1924
  keyBytes = await exportAesKeyBytes(aesKey);
1856
1925
  validateRawKeyBytes(keyBytes);
1857
1926
  } else {
@@ -1866,6 +1935,7 @@ async function encryptKey(recipient, aesKey, opts) {
1866
1935
  const { key, baseNonce } = await keySchedule(sharedSecret, info);
1867
1936
  const ciphertext = await aeadSeal(key, baseNonce, keyBytes);
1868
1937
  const wrapped = concat2(enc, ciphertext);
1938
+ if (aesKey instanceof CryptoKey) return { wrapped, key: aesKey };
1869
1939
  const aesGcmKey = await importAesKey(
1870
1940
  keyBytes,
1871
1941
  opts?.extractable ?? false