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/constants.js CHANGED
@@ -1356,7 +1356,16 @@ async function decryptToString(keypair, message, opts) {
1356
1356
  }
1357
1357
  __name(decryptToString, "decryptToString");
1358
1358
  async function decryptFromString(keypair, message, opts) {
1359
- const bytes = fromString2(message, "base64url");
1359
+ let bytes;
1360
+ try {
1361
+ bytes = fromString2(message, "base64url");
1362
+ } catch (err) {
1363
+ throw new HpkeError(
1364
+ "ERR_MALFORMED_MESSAGE",
1365
+ "malformed message: not base64url",
1366
+ { cause: err }
1367
+ );
1368
+ }
1360
1369
  const plaintext = await decryptBytes(
1361
1370
  keypair,
1362
1371
  bytes,
@@ -1484,6 +1493,12 @@ async function encryptKey(recipient, aesKey, opts) {
1484
1493
  validateRawKeyBytes(aesKey);
1485
1494
  keyBytes = aesKey;
1486
1495
  } else if (aesKey) {
1496
+ if (aesKey.algorithm.name !== "AES-GCM") {
1497
+ throw new HpkeError(
1498
+ "ERR_INVALID_AES_KEY",
1499
+ "aesKey must be an AES-GCM key, got " + aesKey.algorithm.name
1500
+ );
1501
+ }
1487
1502
  keyBytes = await exportAesKeyBytes(aesKey);
1488
1503
  validateRawKeyBytes(keyBytes);
1489
1504
  } else {
@@ -1498,6 +1513,7 @@ async function encryptKey(recipient, aesKey, opts) {
1498
1513
  const { key, baseNonce } = await keySchedule(sharedSecret, info);
1499
1514
  const ciphertext = await aeadSeal(key, baseNonce, keyBytes);
1500
1515
  const wrapped = concat2(enc, ciphertext);
1516
+ if (aesKey instanceof CryptoKey) return { wrapped, key: aesKey };
1501
1517
  const aesGcmKey = await importAesKey(
1502
1518
  keyBytes,
1503
1519
  opts?.extractable ?? false
@@ -1550,6 +1566,12 @@ async function aeadOpen(key, nonce, ciphertext) {
1550
1566
  }
1551
1567
  __name(aeadOpen, "aeadOpen");
1552
1568
  async function encryptBytes(recipient, message, aesKey, opts) {
1569
+ if (aesKey instanceof CryptoKey && !aesKey.usages.includes("encrypt")) {
1570
+ throw new HpkeError(
1571
+ "ERR_INVALID_AES_KEY",
1572
+ 'aesKey is missing the "encrypt" usage, which encrypt needs to encrypt the message'
1573
+ );
1574
+ }
1553
1575
  const plaintext = typeof message === "string" ? new TextEncoder().encode(message) : message;
1554
1576
  const { wrapped, key } = await encryptKey(recipient, aesKey, opts);
1555
1577
  const iv = globalThis.crypto.getRandomValues(new Uint8Array(NN));
@@ -1648,37 +1670,28 @@ async function resolveRecipientPublicKey(recipient) {
1648
1670
  return importRawPublic(recipient);
1649
1671
  }
1650
1672
  if (recipient instanceof CryptoKey) {
1651
- if (recipient.type !== "public") {
1652
- throw new HpkeError(
1653
- "ERR_INVALID_RECIPIENT_KEY",
1654
- "recipient CryptoKey must be a public key"
1655
- );
1656
- }
1657
- assertX25519Algorithm(
1658
- recipient,
1659
- "recipient CryptoKey",
1660
- "ERR_INVALID_RECIPIENT_KEY"
1661
- );
1662
- return recipient;
1673
+ return assertRecipientPublicKey(recipient, "recipient CryptoKey");
1663
1674
  }
1664
1675
  if (typeof recipient.publicKey === "string") {
1665
1676
  const { publicKey, encoding } = recipient;
1666
- return importRawPublic(fromString2(publicKey, encoding ?? "base64url"));
1667
- }
1668
- const pair = recipient;
1669
- if (pair.publicKey instanceof CryptoKey) {
1670
- if (pair.publicKey.type !== "public") {
1677
+ let decoded;
1678
+ try {
1679
+ decoded = fromString2(publicKey, encoding ?? "base64url");
1680
+ } catch (err) {
1671
1681
  throw new HpkeError(
1672
1682
  "ERR_INVALID_RECIPIENT_KEY",
1673
- "recipient keypair publicKey must be a public key"
1683
+ `recipient publicKey string does not decode as ${encoding ?? "base64url"}`,
1684
+ { cause: err }
1674
1685
  );
1675
1686
  }
1676
- assertX25519Algorithm(
1687
+ return importRawPublic(decoded);
1688
+ }
1689
+ const pair = recipient;
1690
+ if (pair.publicKey instanceof CryptoKey) {
1691
+ return assertRecipientPublicKey(
1677
1692
  pair.publicKey,
1678
- "recipient keypair publicKey",
1679
- "ERR_INVALID_RECIPIENT_KEY"
1693
+ "recipient keypair publicKey"
1680
1694
  );
1681
- return pair.publicKey;
1682
1695
  }
1683
1696
  throw new HpkeError(
1684
1697
  "ERR_INVALID_RECIPIENT_KEY",
@@ -1686,6 +1699,23 @@ async function resolveRecipientPublicKey(recipient) {
1686
1699
  );
1687
1700
  }
1688
1701
  __name(resolveRecipientPublicKey, "resolveRecipientPublicKey");
1702
+ function assertRecipientPublicKey(key, context) {
1703
+ if (key.type !== "public") {
1704
+ throw new HpkeError(
1705
+ "ERR_INVALID_RECIPIENT_KEY",
1706
+ `${context} must be a public key`
1707
+ );
1708
+ }
1709
+ assertX25519Algorithm(key, context, "ERR_INVALID_RECIPIENT_KEY");
1710
+ if (!key.extractable) {
1711
+ throw new HpkeError(
1712
+ "ERR_INVALID_RECIPIENT_KEY",
1713
+ `${context} must be extractable (the KEM exports it)`
1714
+ );
1715
+ }
1716
+ return key;
1717
+ }
1718
+ __name(assertRecipientPublicKey, "assertRecipientPublicKey");
1689
1719
  function assertX25519Algorithm(key, context, code2) {
1690
1720
  if (key.algorithm.name !== "X25519") {
1691
1721
  throw new HpkeError(
@@ -1696,11 +1726,23 @@ function assertX25519Algorithm(key, context, code2) {
1696
1726
  }
1697
1727
  __name(assertX25519Algorithm, "assertX25519Algorithm");
1698
1728
  async function dh(priv, pub) {
1699
- const bits = await subtle2.deriveBits(
1700
- { name: "X25519", public: pub },
1701
- priv,
1702
- 256
1703
- );
1729
+ let bits;
1730
+ try {
1731
+ bits = await subtle2.deriveBits(
1732
+ { name: "X25519", public: pub },
1733
+ priv,
1734
+ 256
1735
+ );
1736
+ } catch (err) {
1737
+ if (err?.name === "OperationError") {
1738
+ throw new HpkeError(
1739
+ "ERR_SMALL_ORDER_KEY",
1740
+ "X25519 deriveBits rejected a small-order public key",
1741
+ { cause: err }
1742
+ );
1743
+ }
1744
+ throw err;
1745
+ }
1704
1746
  const bytes = new Uint8Array(bits);
1705
1747
  if (bytes.every((b) => b === 0)) {
1706
1748
  throw new HpkeError(
@@ -1753,6 +1795,23 @@ async function decap(enc, keypair) {
1753
1795
  "keypair.privateKey",
1754
1796
  "ERR_INVALID_KEYPAIR"
1755
1797
  );
1798
+ if (!keypair.privateKey.usages.includes("deriveBits")) {
1799
+ throw new HpkeError(
1800
+ "ERR_INVALID_KEYPAIR",
1801
+ "keypair.privateKey must have the deriveBits usage"
1802
+ );
1803
+ }
1804
+ if (keypair.publicKey.type !== "public" || !keypair.publicKey.extractable) {
1805
+ throw new HpkeError(
1806
+ "ERR_INVALID_KEYPAIR",
1807
+ "keypair.publicKey must be an extractable public key"
1808
+ );
1809
+ }
1810
+ assertX25519Algorithm(
1811
+ keypair.publicKey,
1812
+ "keypair.publicKey",
1813
+ "ERR_INVALID_KEYPAIR"
1814
+ );
1756
1815
  const pkE = await subtle2.importKey(
1757
1816
  "raw",
1758
1817
  enc,