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.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { type RecipientKey, encryptToString, encryptBytes, keyId } from './util';
2
- export { keyId };
1
+ import { type RecipientKey, encryptToString, encryptBytes, keyId, verifyKeyId } from './util';
2
+ export { keyId, verifyKeyId };
3
3
  export { HpkeError, type HpkeErrorCode } from './errors';
4
4
  /**
5
5
  * Create a new AES key for the given public key.
@@ -127,14 +127,17 @@ declare function openBytes(keypair: CryptoKeyPair, wrapped: Uint8Array, opts?: {
127
127
  * @param aesKey Optional key to wrap, as either an AES-GCM `CryptoKey` or its
128
128
  * raw bytes (`Uint8Array`, 16 or 32 bytes). Omit to generate a fresh key
129
129
  * of `opts.size` bits. A supplied `CryptoKey` MUST be extractable (its
130
- * raw bytes are wrapped) -- that is an input constraint, unrelated to
131
- * `opts.extractable`, which governs the key this returns.
130
+ * raw bytes are wrapped) and is returned as-is: the same object, with
131
+ * its own `extractable` and usages. It does not need the `encrypt`
132
+ * usage, since wrapping never encrypts with it.
132
133
  * @param opts `size` (128/256, default 256; ignored when `aesKey` is
133
134
  * supplied), `info` (bound into the HPKE key schedule; default empty),
134
- * and `extractable` (whether the RETURNED key can be exported; default
135
- * false).
135
+ * and `extractable` (whether a key this function imports -- a
136
+ * generated key or raw `Uint8Array` bytes -- can be exported; default
137
+ * false). `extractable` does not apply to a supplied `CryptoKey`.
136
138
  * @returns { wrapped:Uint8Array, key:CryptoKey } The wrapped envelope
137
- * bytes and a usable AES-GCM `CryptoKey`.
139
+ * bytes and the AES-GCM `CryptoKey`: the supplied `CryptoKey` itself
140
+ * when one was given, otherwise a newly imported key.
138
141
  */
139
142
  export declare function encryptKey(recipient: RecipientKey, aesKey?: CryptoKey | Uint8Array | null, opts?: {
140
143
  size?: 128 | 256;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACH,KAAK,YAAY,EAGjB,eAAe,EACf,YAAY,EAOZ,KAAK,EACL,WAAW,EACd,MAAM,QAAQ,CAAA;AAYf,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,CAAA;AAC7B,OAAO,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,UAAU,CAAA;AAWxD;;;;;;;GAOG;AACH,wBAAsB,MAAM,CACxB,SAAS,EAAC,YAAY,EACtB,IAAI,CAAC,EAAC;IAEF,IAAI,CAAC,EAAC,GAAG,GAAC,GAAG,CAAA;IAEb,IAAI,CAAC,EAAC,UAAU,GAAC,MAAM,CAAA;IAEvB,WAAW,CAAC,EAAC,OAAO,CAAA;CACvB,GACH,OAAO,CAAC;IAAE,OAAO,EAAC,UAAU,CAAC,eAAe,CAAC,CAAC;IAAC,GAAG,EAAC,SAAS,CAAA;CAAE,CAAC,CAGhE;AAED;;;GAGG;AACH,eAAO,MAAM,IAAI;;CAEf,CAAA;AAEF;;;;GAIG;AACH,eAAO,MAAM,OAAO;;CAElB,CAAA;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,OAAO;;;CAGlB,CAAA;AAEF;;;;;;;;GAQG;AACH,iBAAe,YAAY,CACvB,OAAO,EAAC,aAAa,EACrB,OAAO,EAAC,UAAU,EAClB,IAAI,CAAC,EAAC;IAAE,IAAI,CAAC,EAAC,UAAU,GAAC,MAAM,CAAA;CAAE,GACnC,OAAO,CAAC,UAAU,CAAC,CA+CpB;AAED;;;;;;;;GAQG;AACH,iBAAe,eAAe,CAC1B,OAAO,EAAC,aAAa,EACrB,OAAO,EAAC,UAAU,EAClB,IAAI,CAAC,EAAC;IAAE,IAAI,CAAC,EAAC,UAAU,GAAC,MAAM,CAAA;CAAE,GACnC,OAAO,CAAC,MAAM,CAAC,CAGhB;AAED;;;;;;;;;;;;;GAaG;AACH,iBAAe,iBAAiB,CAC5B,OAAO,EAAC,aAAa,EACrB,OAAO,EAAC,MAAM,EACd,IAAI,CAAC,EAAC;IAAE,IAAI,CAAC,EAAC,UAAU,GAAC,MAAM,CAAC;IAAC,MAAM,CAAC,EAAC,KAAK,CAAA;CAAE,GAClD,OAAO,CAAC,MAAM,CAAC,CAAA;AAEjB,iBAAe,iBAAiB,CAC5B,OAAO,EAAC,aAAa,EACrB,OAAO,EAAC,MAAM,EACd,IAAI,EAAC;IAAE,IAAI,CAAC,EAAC,UAAU,GAAC,MAAM,CAAC;IAAC,MAAM,EAAC,IAAI,CAAA;CAAE,GAC/C,OAAO,CAAC,UAAU,CAAC,CAAA;AA6IrB;;;;;;;;;;GAUG;AACH,iBAAe,YAAY,CACvB,OAAO,EAAC,aAAa,EACrB,OAAO,EAAC,UAAU,EAClB,IAAI,CAAC,EAAC;IAAE,IAAI,CAAC,EAAC,UAAU,GAAC,MAAM,CAAA;CAAE,GACnC,OAAO,CAAC,UAAU,CAAC,CAepB;AAED;;;;;;;;;GASG;AACH,iBAAe,SAAS,CACpB,OAAO,EAAC,aAAa,EACrB,OAAO,EAAC,UAAU,EAClB,IAAI,CAAC,EAAC;IAAE,IAAI,CAAC,EAAC,UAAU,GAAC,MAAM,CAAC;IAAC,WAAW,CAAC,EAAC,OAAO,CAAA;CAAE,GACzD,OAAO,CAAC,SAAS,CAAC,CAGnB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,UAAU,CAC5B,SAAS,EAAC,YAAY,EACtB,MAAM,CAAC,EAAC,SAAS,GAAC,UAAU,GAAC,IAAI,EACjC,IAAI,CAAC,EAAC;IAEF,IAAI,CAAC,EAAC,GAAG,GAAC,GAAG,CAAA;IAEb,IAAI,CAAC,EAAC,UAAU,GAAC,MAAM,CAAA;IAGvB,WAAW,CAAC,EAAC,OAAO,CAAA;CACvB,GACH,OAAO,CAAC;IAAE,OAAO,EAAC,UAAU,CAAC;IAAC,GAAG,EAAC,SAAS,CAAA;CAAE,CAAC,CA8C/C"}
package/dist/index.js CHANGED
@@ -1365,6 +1365,12 @@ async function aeadOpen(key, nonce, ciphertext) {
1365
1365
  }
1366
1366
  __name(aeadOpen, "aeadOpen");
1367
1367
  async function encryptBytes(recipient, message, aesKey, opts) {
1368
+ if (aesKey instanceof CryptoKey && !aesKey.usages.includes("encrypt")) {
1369
+ throw new HpkeError(
1370
+ "ERR_INVALID_AES_KEY",
1371
+ 'aesKey is missing the "encrypt" usage, which encrypt needs to encrypt the message'
1372
+ );
1373
+ }
1368
1374
  const plaintext = typeof message === "string" ? new TextEncoder().encode(message) : message;
1369
1375
  const { wrapped, key } = await encryptKey(recipient, aesKey, opts);
1370
1376
  const iv = globalThis.crypto.getRandomValues(new Uint8Array(NN));
@@ -1463,37 +1469,28 @@ async function resolveRecipientPublicKey(recipient) {
1463
1469
  return importRawPublic(recipient);
1464
1470
  }
1465
1471
  if (recipient instanceof CryptoKey) {
1466
- if (recipient.type !== "public") {
1467
- throw new HpkeError(
1468
- "ERR_INVALID_RECIPIENT_KEY",
1469
- "recipient CryptoKey must be a public key"
1470
- );
1471
- }
1472
- assertX25519Algorithm(
1473
- recipient,
1474
- "recipient CryptoKey",
1475
- "ERR_INVALID_RECIPIENT_KEY"
1476
- );
1477
- return recipient;
1472
+ return assertRecipientPublicKey(recipient, "recipient CryptoKey");
1478
1473
  }
1479
1474
  if (typeof recipient.publicKey === "string") {
1480
1475
  const { publicKey, encoding } = recipient;
1481
- return importRawPublic(fromString2(publicKey, encoding ?? "base64url"));
1482
- }
1483
- const pair = recipient;
1484
- if (pair.publicKey instanceof CryptoKey) {
1485
- if (pair.publicKey.type !== "public") {
1476
+ let decoded;
1477
+ try {
1478
+ decoded = fromString2(publicKey, encoding ?? "base64url");
1479
+ } catch (err) {
1486
1480
  throw new HpkeError(
1487
1481
  "ERR_INVALID_RECIPIENT_KEY",
1488
- "recipient keypair publicKey must be a public key"
1482
+ `recipient publicKey string does not decode as ${encoding ?? "base64url"}`,
1483
+ { cause: err }
1489
1484
  );
1490
1485
  }
1491
- assertX25519Algorithm(
1486
+ return importRawPublic(decoded);
1487
+ }
1488
+ const pair = recipient;
1489
+ if (pair.publicKey instanceof CryptoKey) {
1490
+ return assertRecipientPublicKey(
1492
1491
  pair.publicKey,
1493
- "recipient keypair publicKey",
1494
- "ERR_INVALID_RECIPIENT_KEY"
1492
+ "recipient keypair publicKey"
1495
1493
  );
1496
- return pair.publicKey;
1497
1494
  }
1498
1495
  throw new HpkeError(
1499
1496
  "ERR_INVALID_RECIPIENT_KEY",
@@ -1501,6 +1498,23 @@ async function resolveRecipientPublicKey(recipient) {
1501
1498
  );
1502
1499
  }
1503
1500
  __name(resolveRecipientPublicKey, "resolveRecipientPublicKey");
1501
+ function assertRecipientPublicKey(key, context) {
1502
+ if (key.type !== "public") {
1503
+ throw new HpkeError(
1504
+ "ERR_INVALID_RECIPIENT_KEY",
1505
+ `${context} must be a public key`
1506
+ );
1507
+ }
1508
+ assertX25519Algorithm(key, context, "ERR_INVALID_RECIPIENT_KEY");
1509
+ if (!key.extractable) {
1510
+ throw new HpkeError(
1511
+ "ERR_INVALID_RECIPIENT_KEY",
1512
+ `${context} must be extractable (the KEM exports it)`
1513
+ );
1514
+ }
1515
+ return key;
1516
+ }
1517
+ __name(assertRecipientPublicKey, "assertRecipientPublicKey");
1504
1518
  function assertX25519Algorithm(key, context, code2) {
1505
1519
  if (key.algorithm.name !== "X25519") {
1506
1520
  throw new HpkeError(
@@ -1511,11 +1525,23 @@ function assertX25519Algorithm(key, context, code2) {
1511
1525
  }
1512
1526
  __name(assertX25519Algorithm, "assertX25519Algorithm");
1513
1527
  async function dh(priv, pub) {
1514
- const bits = await subtle.deriveBits(
1515
- { name: "X25519", public: pub },
1516
- priv,
1517
- 256
1518
- );
1528
+ let bits;
1529
+ try {
1530
+ bits = await subtle.deriveBits(
1531
+ { name: "X25519", public: pub },
1532
+ priv,
1533
+ 256
1534
+ );
1535
+ } catch (err) {
1536
+ if (err?.name === "OperationError") {
1537
+ throw new HpkeError(
1538
+ "ERR_SMALL_ORDER_KEY",
1539
+ "X25519 deriveBits rejected a small-order public key",
1540
+ { cause: err }
1541
+ );
1542
+ }
1543
+ throw err;
1544
+ }
1519
1545
  const bytes = new Uint8Array(bits);
1520
1546
  if (bytes.every((b) => b === 0)) {
1521
1547
  throw new HpkeError(
@@ -1568,6 +1594,23 @@ async function decap(enc, keypair) {
1568
1594
  "keypair.privateKey",
1569
1595
  "ERR_INVALID_KEYPAIR"
1570
1596
  );
1597
+ if (!keypair.privateKey.usages.includes("deriveBits")) {
1598
+ throw new HpkeError(
1599
+ "ERR_INVALID_KEYPAIR",
1600
+ "keypair.privateKey must have the deriveBits usage"
1601
+ );
1602
+ }
1603
+ if (keypair.publicKey.type !== "public" || !keypair.publicKey.extractable) {
1604
+ throw new HpkeError(
1605
+ "ERR_INVALID_KEYPAIR",
1606
+ "keypair.publicKey must be an extractable public key"
1607
+ );
1608
+ }
1609
+ assertX25519Algorithm(
1610
+ keypair.publicKey,
1611
+ "keypair.publicKey",
1612
+ "ERR_INVALID_KEYPAIR"
1613
+ );
1571
1614
  const pkE = await subtle.importKey(
1572
1615
  "raw",
1573
1616
  enc,
@@ -1610,6 +1653,16 @@ async function keyId(key) {
1610
1653
  return toString2(new Uint8Array(digest2), "base64url");
1611
1654
  }
1612
1655
  __name(keyId, "keyId");
1656
+ async function verifyKeyId(key, expected) {
1657
+ const actual = await keyId(key);
1658
+ if (actual.length !== expected.length) return false;
1659
+ let diff = 0;
1660
+ for (let i = 0; i < actual.length; i++) {
1661
+ diff |= actual.charCodeAt(i) ^ expected.charCodeAt(i);
1662
+ }
1663
+ return diff === 0;
1664
+ }
1665
+ __name(verifyKeyId, "verifyKeyId");
1613
1666
  function i2osp(n, len) {
1614
1667
  const out = new Uint8Array(len);
1615
1668
  let v = n;
@@ -1698,7 +1751,16 @@ async function decryptToString(keypair, message, opts) {
1698
1751
  }
1699
1752
  __name(decryptToString, "decryptToString");
1700
1753
  async function decryptFromString(keypair, message, opts) {
1701
- const bytes = fromString2(message, "base64url");
1754
+ let bytes;
1755
+ try {
1756
+ bytes = fromString2(message, "base64url");
1757
+ } catch (err) {
1758
+ throw new HpkeError(
1759
+ "ERR_MALFORMED_MESSAGE",
1760
+ "malformed message: not base64url",
1761
+ { cause: err }
1762
+ );
1763
+ }
1702
1764
  const plaintext = await decryptBytes(
1703
1765
  keypair,
1704
1766
  bytes,
@@ -1826,6 +1888,12 @@ async function encryptKey(recipient, aesKey, opts) {
1826
1888
  validateRawKeyBytes(aesKey);
1827
1889
  keyBytes = aesKey;
1828
1890
  } else if (aesKey) {
1891
+ if (aesKey.algorithm.name !== "AES-GCM") {
1892
+ throw new HpkeError(
1893
+ "ERR_INVALID_AES_KEY",
1894
+ "aesKey must be an AES-GCM key, got " + aesKey.algorithm.name
1895
+ );
1896
+ }
1829
1897
  keyBytes = await exportAesKeyBytes(aesKey);
1830
1898
  validateRawKeyBytes(keyBytes);
1831
1899
  } else {
@@ -1840,6 +1908,7 @@ async function encryptKey(recipient, aesKey, opts) {
1840
1908
  const { key, baseNonce } = await keySchedule(sharedSecret, info);
1841
1909
  const ciphertext = await aeadSeal(key, baseNonce, keyBytes);
1842
1910
  const wrapped = concat2(enc, ciphertext);
1911
+ if (aesKey instanceof CryptoKey) return { wrapped, key: aesKey };
1843
1912
  const aesGcmKey = await importAesKey(
1844
1913
  keyBytes,
1845
1914
  opts?.extractable ?? false
@@ -1854,6 +1923,7 @@ export {
1854
1923
  encrypt,
1855
1924
  encryptKey,
1856
1925
  keyId,
1857
- open
1926
+ open,
1927
+ verifyKeyId
1858
1928
  };
1859
1929
  //# sourceMappingURL=index.js.map