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.
package/dist/index.d.ts CHANGED
@@ -1,17 +1,20 @@
1
- import { type RecipientKey, encryptToString, encryptBytes } from './util';
1
+ import { type RecipientKey, encryptToString, encryptBytes, keyId } from './util';
2
+ export { keyId };
3
+ export { HpkeError, type HpkeErrorCode } from './errors';
2
4
  /**
3
5
  * Create a new AES key for the given public key.
4
6
  *
5
7
  * @param recipient Public key for decryptor
6
- * @param opts `size` and `info`
7
- * @returns {{ enc, key }} The wrapped envelope bytes and the generated
8
+ * @param opts `size`, `info`, and `extractable`
9
+ * @returns {{ wrapped, key }} The wrapped envelope bytes and the generated
8
10
  * AES-GCM key.
9
11
  */
10
12
  export declare function create(recipient: RecipientKey, opts?: {
11
13
  size?: 128 | 256;
12
14
  info?: Uint8Array | string;
15
+ extractable?: boolean;
13
16
  }): Promise<{
14
- enc: Uint8Array<ArrayBufferLike>;
17
+ wrapped: Uint8Array<ArrayBufferLike>;
15
18
  key: CryptoKey;
16
19
  }>;
17
20
  /**
@@ -46,7 +49,7 @@ export declare const decrypt: typeof decryptBytes & {
46
49
  *
47
50
  * @param keypair The same recipient `CryptoKeyPair` used to `encrypt`.
48
51
  * @param message The envelope returned by `encrypt`.
49
- * @param opts `info` — must match the value passed to `encrypt`.
52
+ * @param opts `info` -- must match the value passed to `encrypt`.
50
53
  * @returns The decrypted plaintext bytes. Use `decrypt.asString` for a string.
51
54
  */
52
55
  declare function decryptBytes(keypair: CryptoKeyPair, message: Uint8Array, opts?: {
@@ -58,7 +61,7 @@ declare function decryptBytes(keypair: CryptoKeyPair, message: Uint8Array, opts?
58
61
  *
59
62
  * @param keypair The same recipient `CryptoKeyPair` used to `encrypt`.
60
63
  * @param message The envelope returned by `encrypt`.
61
- * @param opts `info` — must match the value passed to `encrypt`.
64
+ * @param opts `info` -- must match the value passed to `encrypt`.
62
65
  * @returns The decrypted plaintext as a string.
63
66
  */
64
67
  declare function decryptToString(keypair: CryptoKeyPair, message: Uint8Array, opts?: {
@@ -91,24 +94,28 @@ declare function decryptFromString(keypair: CryptoKeyPair, message: string, opts
91
94
  * importing it as an AES-GCM `CryptoKey`. Exposed as `open.raw`.
92
95
  *
93
96
  * @param keypair The same X25519 `CryptoKeyPair` used to create or encryptKey.
94
- * @param wrapped The `enc` bytes returned by `create` or `encryptKey`.
95
- * @param opts `info` — must match the value passed to `create` or `encryptKey`.
97
+ * @param wrapped The `wrapped` bytes returned by `create` or `encryptKey`.
98
+ * @param opts `info` -- must match the value passed to `create` or
99
+ * `encryptKey`.
96
100
  * @returns The recovered key bytes (16 or 32 bytes, matching whatever
97
101
  * was wrapped).
98
102
  */
99
103
  declare function openRawBytes(keypair: CryptoKeyPair, wrapped: Uint8Array, opts?: {
100
- info: Uint8Array | string;
104
+ info?: Uint8Array | string;
101
105
  }): Promise<Uint8Array>;
102
106
  /**
103
107
  * Recover an AES key that was wrapped with `create` or `encryptKey`.
104
108
  *
105
109
  * @param keypair The same X25519 `CryptoKeyPair` used to create or encryptKey.
106
- * @param wrapped The `enc` bytes returned by `create` or `encryptKey`.
107
- * @param opts `info` — must match the value passed to `create` or `encryptKey`.
108
- * @returns The recovered AES-GCM `CryptoKey` (extractable).
110
+ * @param wrapped The `wrapped` bytes returned by `create` or `encryptKey`.
111
+ * @param opts `info` -- must match the value passed to `create` or
112
+ * `encryptKey` -- and `extractable` (default false).
113
+ * @returns The recovered AES-GCM `CryptoKey`, non-extractable unless
114
+ * `opts.extractable` is true.
109
115
  */
110
116
  declare function openBytes(keypair: CryptoKeyPair, wrapped: Uint8Array, opts?: {
111
- info: Uint8Array | string;
117
+ info?: Uint8Array | string;
118
+ extractable?: boolean;
112
119
  }): Promise<CryptoKey>;
113
120
  /**
114
121
  * Wrap an AES key to a recipient's public key. `create(...)` calls this
@@ -118,20 +125,23 @@ declare function openBytes(keypair: CryptoKeyPair, wrapped: Uint8Array, opts?: {
118
125
  * `CryptoKeyPair` (its `.publicKey` is used), 32 raw bytes (`Uint8Array`),
119
126
  * or `{ publicKey:string, encoding? }` (encoding defaults to `base64url`).
120
127
  * @param aesKey Optional key to wrap, as either an AES-GCM `CryptoKey` or its
121
- * raw bytes (`Uint8Array`, 16 or 32 bytes). Omit to generate a fresh
122
- * extractable key of `opts.size` bits. A supplied `CryptoKey` MUST be
123
- * extractable (its raw bytes are wrapped).
128
+ * raw bytes (`Uint8Array`, 16 or 32 bytes). Omit to generate a fresh key
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.
124
132
  * @param opts `size` (128/256, default 256; ignored when `aesKey` is
125
- * supplied) and `info` (bound into the HPKE key schedule; default empty).
126
- * @returns { enc:Uint8Array, key:CryptoKey } The wrapped envelope bytes
127
- * and a usable AES-GCM `CryptoKey`.
133
+ * supplied), `info` (bound into the HPKE key schedule; default empty),
134
+ * and `extractable` (whether the RETURNED key can be exported; default
135
+ * false).
136
+ * @returns { wrapped:Uint8Array, key:CryptoKey } The wrapped envelope
137
+ * bytes and a usable AES-GCM `CryptoKey`.
128
138
  */
129
139
  export declare function encryptKey(recipient: RecipientKey, aesKey?: CryptoKey | Uint8Array | null, opts?: {
130
140
  size?: 128 | 256;
131
141
  info?: Uint8Array | string;
142
+ extractable?: boolean;
132
143
  }): Promise<{
133
- enc: Uint8Array;
144
+ wrapped: Uint8Array;
134
145
  key: CryptoKey;
135
146
  }>;
136
- export {};
137
147
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACH,KAAK,YAAY,EAGjB,eAAe,EACf,YAAY,EAOf,MAAM,QAAQ,CAAA;AAoBf;;;;;;;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;CAC1B,GACH,OAAO,CAAC;IAAE,GAAG,EAAC,UAAU,CAAC,eAAe,CAAC,CAAC;IAAC,GAAG,EAAC,SAAS,CAAA;CAAE,CAAC,CAG5D;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,CA6BpB;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;AA0HrB;;;;;;;;;GASG;AACH,iBAAe,YAAY,CACvB,OAAO,EAAC,aAAa,EACrB,OAAO,EAAC,UAAU,EAClB,IAAI,CAAC,EAAC;IAAE,IAAI,EAAC,UAAU,GAAC,MAAM,CAAA;CAAE,GAClC,OAAO,CAAC,UAAU,CAAC,CAYpB;AAED;;;;;;;GAOG;AACH,iBAAe,SAAS,CACpB,OAAO,EAAC,aAAa,EACrB,OAAO,EAAC,UAAU,EAClB,IAAI,CAAC,EAAC;IAAE,IAAI,EAAC,UAAU,GAAC,MAAM,CAAA;CAAE,GAClC,OAAO,CAAC,SAAS,CAAC,CAGnB;AAED;;;;;;;;;;;;;;;GAeG;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;CAC1B,GACH,OAAO,CAAC;IAAE,GAAG,EAAC,UAAU,CAAC;IAAC,GAAG,EAAC,SAAS,CAAA;CAAE,CAAC,CA0B3C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.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/constants.ts
1287
1300
  var KEM_ID = 32;
1288
1301
  var KDF_ID = 1;
@@ -1305,6 +1318,8 @@ var HPKE_SUITE_ID = concat2(
1305
1318
  i2osp(KDF_ID, 2),
1306
1319
  i2osp(AEAD_ID, 2)
1307
1320
  );
1321
+ var KEY_ID_LABEL = new TextEncoder().encode("simple-hpke/keyId/v1");
1322
+ var KEY_ID_NONCE_LENGTH = 12;
1308
1323
 
1309
1324
  // src/util.ts
1310
1325
  var subtle = globalThis.crypto.subtle;
@@ -1332,24 +1347,38 @@ async function aeadOpen(key, nonce, ciphertext) {
1332
1347
  false,
1333
1348
  ["decrypt"]
1334
1349
  );
1335
- const pt = await subtle.decrypt(
1336
- { name: "AES-GCM", iv: nonce },
1337
- k,
1338
- ciphertext
1339
- );
1350
+ let pt;
1351
+ try {
1352
+ pt = await subtle.decrypt(
1353
+ { name: "AES-GCM", iv: nonce },
1354
+ k,
1355
+ ciphertext
1356
+ );
1357
+ } catch (err) {
1358
+ throw new HpkeError(
1359
+ "ERR_DECRYPT_FAILED",
1360
+ "failed to decrypt the wrapped key: wrong keypair, wrong info, or a tampered envelope",
1361
+ { cause: err }
1362
+ );
1363
+ }
1340
1364
  return new Uint8Array(pt);
1341
1365
  }
1342
1366
  __name(aeadOpen, "aeadOpen");
1343
1367
  async function encryptBytes(recipient, message, aesKey, opts) {
1344
1368
  const plaintext = typeof message === "string" ? new TextEncoder().encode(message) : message;
1345
- const { enc, key } = await encryptKey(recipient, aesKey, opts);
1369
+ const { wrapped, key } = await encryptKey(recipient, aesKey, opts);
1346
1370
  const iv = globalThis.crypto.getRandomValues(new Uint8Array(NN));
1347
1371
  const ct = new Uint8Array(await subtle.encrypt(
1348
1372
  { name: "AES-GCM", iv },
1349
1373
  key,
1350
1374
  plaintext
1351
1375
  ));
1352
- return concat2(i2osp(enc.length, WRAPPED_LEN_PREFIX), enc, iv, ct);
1376
+ return concat2(
1377
+ i2osp(wrapped.length, WRAPPED_LEN_PREFIX),
1378
+ wrapped,
1379
+ iv,
1380
+ ct
1381
+ );
1353
1382
  }
1354
1383
  __name(encryptBytes, "encryptBytes");
1355
1384
  async function encryptToString(recipient, message, aesKey, opts) {
@@ -1415,7 +1444,8 @@ async function exportRawPublic(key) {
1415
1444
  __name(exportRawPublic, "exportRawPublic");
1416
1445
  async function importRawPublic(raw) {
1417
1446
  if (raw.length !== ENC_LENGTH) {
1418
- throw new Error(
1447
+ throw new HpkeError(
1448
+ "ERR_INVALID_RECIPIENT_KEY",
1419
1449
  `invalid public key length: ${raw.length} bytes (expected ${ENC_LENGTH})`
1420
1450
  );
1421
1451
  }
@@ -1434,9 +1464,16 @@ async function resolveRecipientPublicKey(recipient) {
1434
1464
  }
1435
1465
  if (recipient instanceof CryptoKey) {
1436
1466
  if (recipient.type !== "public") {
1437
- throw new Error("recipient CryptoKey must be a public key");
1467
+ throw new HpkeError(
1468
+ "ERR_INVALID_RECIPIENT_KEY",
1469
+ "recipient CryptoKey must be a public key"
1470
+ );
1438
1471
  }
1439
- assertX25519Algorithm(recipient, "recipient CryptoKey");
1472
+ assertX25519Algorithm(
1473
+ recipient,
1474
+ "recipient CryptoKey",
1475
+ "ERR_INVALID_RECIPIENT_KEY"
1476
+ );
1440
1477
  return recipient;
1441
1478
  }
1442
1479
  if (typeof recipient.publicKey === "string") {
@@ -1446,17 +1483,28 @@ async function resolveRecipientPublicKey(recipient) {
1446
1483
  const pair = recipient;
1447
1484
  if (pair.publicKey instanceof CryptoKey) {
1448
1485
  if (pair.publicKey.type !== "public") {
1449
- throw new Error("recipient keypair publicKey must be a public key");
1486
+ throw new HpkeError(
1487
+ "ERR_INVALID_RECIPIENT_KEY",
1488
+ "recipient keypair publicKey must be a public key"
1489
+ );
1450
1490
  }
1451
- assertX25519Algorithm(pair.publicKey, "recipient keypair publicKey");
1491
+ assertX25519Algorithm(
1492
+ pair.publicKey,
1493
+ "recipient keypair publicKey",
1494
+ "ERR_INVALID_RECIPIENT_KEY"
1495
+ );
1452
1496
  return pair.publicKey;
1453
1497
  }
1454
- throw new Error("unrecognized recipient key form");
1498
+ throw new HpkeError(
1499
+ "ERR_INVALID_RECIPIENT_KEY",
1500
+ "unrecognized recipient key form"
1501
+ );
1455
1502
  }
1456
1503
  __name(resolveRecipientPublicKey, "resolveRecipientPublicKey");
1457
- function assertX25519Algorithm(key, context) {
1504
+ function assertX25519Algorithm(key, context, code2) {
1458
1505
  if (key.algorithm.name !== "X25519") {
1459
- throw new Error(
1506
+ throw new HpkeError(
1507
+ code2,
1460
1508
  `${context} must be an X25519 key (got ${key.algorithm.name})`
1461
1509
  );
1462
1510
  }
@@ -1470,7 +1518,8 @@ async function dh(priv, pub) {
1470
1518
  );
1471
1519
  const bytes = new Uint8Array(bits);
1472
1520
  if (bytes.every((b) => b === 0)) {
1473
- throw new Error(
1521
+ throw new HpkeError(
1522
+ "ERR_SMALL_ORDER_KEY",
1474
1523
  "X25519 shared secret is all-zero (small-order public key?)"
1475
1524
  );
1476
1525
  }
@@ -1509,9 +1558,16 @@ async function encap(pkR) {
1509
1558
  __name(encap, "encap");
1510
1559
  async function decap(enc, keypair) {
1511
1560
  if (keypair.privateKey.type !== "private") {
1512
- throw new Error("keypair.privateKey must be a private key");
1561
+ throw new HpkeError(
1562
+ "ERR_INVALID_KEYPAIR",
1563
+ "keypair.privateKey must be a private key"
1564
+ );
1513
1565
  }
1514
- assertX25519Algorithm(keypair.privateKey, "keypair.privateKey");
1566
+ assertX25519Algorithm(
1567
+ keypair.privateKey,
1568
+ "keypair.privateKey",
1569
+ "ERR_INVALID_KEYPAIR"
1570
+ );
1515
1571
  const pkE = await subtle.importKey(
1516
1572
  "raw",
1517
1573
  enc,
@@ -1525,6 +1581,35 @@ async function decap(enc, keypair) {
1525
1581
  return extractAndExpand(dhBytes, kemContext);
1526
1582
  }
1527
1583
  __name(decap, "decap");
1584
+ async function keyId(key) {
1585
+ if (key.algorithm.name !== "AES-GCM") {
1586
+ throw new HpkeError(
1587
+ "ERR_INVALID_AES_KEY",
1588
+ `keyId needs an AES-GCM key (got ${key.algorithm.name})`
1589
+ );
1590
+ }
1591
+ if (!key.usages.includes("encrypt")) {
1592
+ throw new HpkeError(
1593
+ "ERR_INVALID_AES_KEY",
1594
+ "keyId needs a key with the 'encrypt' usage"
1595
+ );
1596
+ }
1597
+ const labelHash = new Uint8Array(
1598
+ await subtle.digest("SHA-256", KEY_ID_LABEL)
1599
+ );
1600
+ const nonce = labelHash.slice(0, KEY_ID_NONCE_LENGTH);
1601
+ const ciphertext = new Uint8Array(await subtle.encrypt(
1602
+ { name: "AES-GCM", iv: nonce },
1603
+ key,
1604
+ KEY_ID_LABEL
1605
+ ));
1606
+ const digest2 = await subtle.digest(
1607
+ "SHA-256",
1608
+ concat2(KEY_ID_LABEL, ciphertext)
1609
+ );
1610
+ return toString2(new Uint8Array(digest2), "base64url");
1611
+ }
1612
+ __name(keyId, "keyId");
1528
1613
  function i2osp(n, len) {
1529
1614
  const out = new Uint8Array(len);
1530
1615
  let v = n;
@@ -1551,8 +1636,8 @@ __name(concat2, "concat");
1551
1636
  // src/index.ts
1552
1637
  var subtle2 = globalThis.crypto.subtle;
1553
1638
  async function create2(recipient, opts) {
1554
- const { enc, key } = await encryptKey(recipient, null, opts);
1555
- return { enc, key };
1639
+ const { wrapped, key } = await encryptKey(recipient, null, opts);
1640
+ return { wrapped, key };
1556
1641
  }
1557
1642
  __name(create2, "create");
1558
1643
  var open = Object.assign(openBytes, {
@@ -1567,13 +1652,19 @@ var decrypt = Object.assign(decryptBytes, {
1567
1652
  });
1568
1653
  async function decryptBytes(keypair, message, opts) {
1569
1654
  if (message.byteLength < WRAPPED_LEN_PREFIX) {
1570
- throw new Error("malformed message: too short");
1655
+ throw new HpkeError(
1656
+ "ERR_MALFORMED_MESSAGE",
1657
+ "malformed message: too short"
1658
+ );
1571
1659
  }
1572
1660
  const wrappedLen = message[0] << 8 | message[1];
1573
1661
  const ivStart = WRAPPED_LEN_PREFIX + wrappedLen;
1574
1662
  const ctStart = ivStart + NN;
1575
1663
  if (message.byteLength < ctStart + AEAD_TAG_LENGTH) {
1576
- throw new Error("malformed message: too short");
1664
+ throw new HpkeError(
1665
+ "ERR_MALFORMED_MESSAGE",
1666
+ "malformed message: too short"
1667
+ );
1577
1668
  }
1578
1669
  const wrapped = message.slice(WRAPPED_LEN_PREFIX, ivStart);
1579
1670
  const iv = message.slice(ivStart, ctStart);
@@ -1584,11 +1675,20 @@ async function decryptBytes(keypair, message, opts) {
1584
1675
  opts?.info !== void 0 ? { info: opts.info } : void 0
1585
1676
  );
1586
1677
  const key = await importAesKeyDecryptOnly(keyBytes);
1587
- const pt = await subtle2.decrypt(
1588
- { name: "AES-GCM", iv },
1589
- key,
1590
- ciphertext
1591
- );
1678
+ let pt;
1679
+ try {
1680
+ pt = await subtle2.decrypt(
1681
+ { name: "AES-GCM", iv },
1682
+ key,
1683
+ ciphertext
1684
+ );
1685
+ } catch (err) {
1686
+ throw new HpkeError(
1687
+ "ERR_DECRYPT_FAILED",
1688
+ "failed to decrypt the message: tampered ciphertext",
1689
+ { cause: err }
1690
+ );
1691
+ }
1592
1692
  return new Uint8Array(pt);
1593
1693
  }
1594
1694
  __name(decryptBytes, "decryptBytes");
@@ -1609,7 +1709,8 @@ async function decryptFromString(keypair, message, opts) {
1609
1709
  __name(decryptFromString, "decryptFromString");
1610
1710
  function validateKeysize(keysize) {
1611
1711
  if (keysize !== 128 && keysize !== 256) {
1612
- throw new Error(
1712
+ throw new HpkeError(
1713
+ "ERR_INVALID_KEYSIZE",
1613
1714
  `invalid keysize: ${keysize} (expected 128 or 256)`
1614
1715
  );
1615
1716
  }
@@ -1617,7 +1718,8 @@ function validateKeysize(keysize) {
1617
1718
  __name(validateKeysize, "validateKeysize");
1618
1719
  function validateRawKeyBytes(raw) {
1619
1720
  if (raw.length !== 16 && raw.length !== 32) {
1620
- throw new Error(
1721
+ throw new HpkeError(
1722
+ "ERR_INVALID_AES_KEY",
1621
1723
  `invalid aesKey length: ${raw.length} bytes (expected 16 or 32)`
1622
1724
  );
1623
1725
  }
@@ -1631,19 +1733,20 @@ function normalizeInfo(info) {
1631
1733
  __name(normalizeInfo, "normalizeInfo");
1632
1734
  async function exportAesKeyBytes(key) {
1633
1735
  if (!key.extractable) {
1634
- throw new Error(
1736
+ throw new HpkeError(
1737
+ "ERR_INVALID_AES_KEY",
1635
1738
  "aesKey must be extractable: its raw bytes are what get sealed"
1636
1739
  );
1637
1740
  }
1638
1741
  return new Uint8Array(await subtle2.exportKey("raw", key));
1639
1742
  }
1640
1743
  __name(exportAesKeyBytes, "exportAesKeyBytes");
1641
- async function importAesKey(raw) {
1744
+ async function importAesKey(raw, extractable) {
1642
1745
  return subtle2.importKey(
1643
1746
  "raw",
1644
1747
  raw,
1645
1748
  { name: "AES-GCM" },
1646
- true,
1749
+ extractable,
1647
1750
  ["encrypt", "decrypt"]
1648
1751
  );
1649
1752
  }
@@ -1698,7 +1801,10 @@ async function keySchedule(sharedSecret, info) {
1698
1801
  __name(keySchedule, "keySchedule");
1699
1802
  async function openRawBytes(keypair, wrapped, opts) {
1700
1803
  if (wrapped.byteLength < ENC_LENGTH + AEAD_TAG_LENGTH) {
1701
- throw new Error("malformed envelope: too short");
1804
+ throw new HpkeError(
1805
+ "ERR_MALFORMED_ENVELOPE",
1806
+ "malformed envelope: too short"
1807
+ );
1702
1808
  }
1703
1809
  const info = normalizeInfo(opts?.info);
1704
1810
  const enc = wrapped.slice(0, ENC_LENGTH);
@@ -1710,7 +1816,7 @@ async function openRawBytes(keypair, wrapped, opts) {
1710
1816
  __name(openRawBytes, "openRawBytes");
1711
1817
  async function openBytes(keypair, wrapped, opts) {
1712
1818
  const keyBytes = await openRawBytes(keypair, wrapped, opts);
1713
- return importAesKey(keyBytes);
1819
+ return importAesKey(keyBytes, opts?.extractable ?? false);
1714
1820
  }
1715
1821
  __name(openBytes, "openBytes");
1716
1822
  async function encryptKey(recipient, aesKey, opts) {
@@ -1734,15 +1840,20 @@ async function encryptKey(recipient, aesKey, opts) {
1734
1840
  const { key, baseNonce } = await keySchedule(sharedSecret, info);
1735
1841
  const ciphertext = await aeadSeal(key, baseNonce, keyBytes);
1736
1842
  const wrapped = concat2(enc, ciphertext);
1737
- const aesGcmKey = await importAesKey(keyBytes);
1738
- return { enc: wrapped, key: aesGcmKey };
1843
+ const aesGcmKey = await importAesKey(
1844
+ keyBytes,
1845
+ opts?.extractable ?? false
1846
+ );
1847
+ return { wrapped, key: aesGcmKey };
1739
1848
  }
1740
1849
  __name(encryptKey, "encryptKey");
1741
1850
  export {
1851
+ HpkeError,
1742
1852
  create2 as create,
1743
1853
  decrypt,
1744
1854
  encrypt,
1745
1855
  encryptKey,
1856
+ keyId,
1746
1857
  open
1747
1858
  };
1748
1859
  //# sourceMappingURL=index.js.map