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.
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/errors.ts
22
+ var errors_exports = {};
23
+ __export(errors_exports, {
24
+ HpkeError: () => HpkeError
25
+ });
26
+ module.exports = __toCommonJS(errors_exports);
27
+ var HpkeError = class extends Error {
28
+ static {
29
+ __name(this, "HpkeError");
30
+ }
31
+ code;
32
+ constructor(code, message, opts) {
33
+ super(message, opts);
34
+ this.name = "HpkeError";
35
+ this.code = code;
36
+ }
37
+ };
38
+ //# sourceMappingURL=errors.cjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/errors.ts"],
4
+ "sourcesContent": ["/**\n * The set of stable error codes this package raises.\n *\n * Branch on `err.code`, not on `err.message`: the codes are part of the\n * public API, the prose is not.\n */\nexport type HpkeErrorCode =\n 'ERR_INVALID_RECIPIENT_KEY'|\n 'ERR_INVALID_KEYPAIR'|\n 'ERR_SMALL_ORDER_KEY'|\n 'ERR_MALFORMED_ENVELOPE'|\n 'ERR_MALFORMED_MESSAGE'|\n 'ERR_INVALID_KEYSIZE'|\n 'ERR_INVALID_AES_KEY'|\n 'ERR_DECRYPT_FAILED'\n\n/**\n * An `Error` carrying a stable `code`. Every throw site in this package\n * raises one of these, so callers can branch on the failure instead of\n * pattern matching on a message.\n *\n * Where a WebCrypto failure is being wrapped, the original is kept on\n * `cause`.\n */\nexport class HpkeError extends Error {\n readonly code:HpkeErrorCode\n\n constructor (\n code:HpkeErrorCode,\n message:string,\n opts?:{ cause?:unknown }\n ) {\n super(message, opts)\n this.name = 'HpkeError'\n this.code = code\n }\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAwBO,IAAM,YAAN,cAAwB,MAAM;AAAA,EAxBrC,OAwBqC;AAAA;AAAA;AAAA,EACxB;AAAA,EAET,YACI,MACA,SACA,MACF;AACE,UAAM,SAAS,IAAI;AACnB,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,EAChB;AACJ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The set of stable error codes this package raises.
3
+ *
4
+ * Branch on `err.code`, not on `err.message`: the codes are part of the
5
+ * public API, the prose is not.
6
+ */
7
+ export type HpkeErrorCode = 'ERR_INVALID_RECIPIENT_KEY' | 'ERR_INVALID_KEYPAIR' | 'ERR_SMALL_ORDER_KEY' | 'ERR_MALFORMED_ENVELOPE' | 'ERR_MALFORMED_MESSAGE' | 'ERR_INVALID_KEYSIZE' | 'ERR_INVALID_AES_KEY' | 'ERR_DECRYPT_FAILED';
8
+ /**
9
+ * An `Error` carrying a stable `code`. Every throw site in this package
10
+ * raises one of these, so callers can branch on the failure instead of
11
+ * pattern matching on a message.
12
+ *
13
+ * Where a WebCrypto failure is being wrapped, the original is kept on
14
+ * `cause`.
15
+ */
16
+ export declare class HpkeError extends Error {
17
+ readonly code: HpkeErrorCode;
18
+ constructor(code: HpkeErrorCode, message: string, opts?: {
19
+ cause?: unknown;
20
+ });
21
+ }
22
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GACrB,2BAA2B,GAC3B,qBAAqB,GACrB,qBAAqB,GACrB,wBAAwB,GACxB,uBAAuB,GACvB,qBAAqB,GACrB,qBAAqB,GACrB,oBAAoB,CAAA;AAExB;;;;;;;GAOG;AACH,qBAAa,SAAU,SAAQ,KAAK;IAChC,QAAQ,CAAC,IAAI,EAAC,aAAa,CAAA;gBAGvB,IAAI,EAAC,aAAa,EAClB,OAAO,EAAC,MAAM,EACd,IAAI,CAAC,EAAC;QAAE,KAAK,CAAC,EAAC,OAAO,CAAA;KAAE;CAM/B"}
package/dist/errors.js ADDED
@@ -0,0 +1,19 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/errors.ts
5
+ var HpkeError = class extends Error {
6
+ static {
7
+ __name(this, "HpkeError");
8
+ }
9
+ code;
10
+ constructor(code, message, opts) {
11
+ super(message, opts);
12
+ this.name = "HpkeError";
13
+ this.code = code;
14
+ }
15
+ };
16
+ export {
17
+ HpkeError
18
+ };
19
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/errors.ts"],
4
+ "sourcesContent": ["/**\n * The set of stable error codes this package raises.\n *\n * Branch on `err.code`, not on `err.message`: the codes are part of the\n * public API, the prose is not.\n */\nexport type HpkeErrorCode =\n 'ERR_INVALID_RECIPIENT_KEY'|\n 'ERR_INVALID_KEYPAIR'|\n 'ERR_SMALL_ORDER_KEY'|\n 'ERR_MALFORMED_ENVELOPE'|\n 'ERR_MALFORMED_MESSAGE'|\n 'ERR_INVALID_KEYSIZE'|\n 'ERR_INVALID_AES_KEY'|\n 'ERR_DECRYPT_FAILED'\n\n/**\n * An `Error` carrying a stable `code`. Every throw site in this package\n * raises one of these, so callers can branch on the failure instead of\n * pattern matching on a message.\n *\n * Where a WebCrypto failure is being wrapped, the original is kept on\n * `cause`.\n */\nexport class HpkeError extends Error {\n readonly code:HpkeErrorCode\n\n constructor (\n code:HpkeErrorCode,\n message:string,\n opts?:{ cause?:unknown }\n ) {\n super(message, opts)\n this.name = 'HpkeError'\n this.code = code\n }\n}\n"],
5
+ "mappings": ";;;;AAwBO,IAAM,YAAN,cAAwB,MAAM;AAAA,EAxBrC,OAwBqC;AAAA;AAAA;AAAA,EACxB;AAAA,EAET,YACI,MACA,SACA,MACF;AACE,UAAM,SAAS,IAAI;AACnB,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,EAChB;AACJ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";var o=Object.defineProperty;var L=Object.getOwnPropertyDescriptor;var s=Object.getOwnPropertyNames;var t=Object.prototype.hasOwnProperty;var A=(R,E)=>o(R,"name",{value:E,configurable:!0});var D=(R,E)=>{for(var r in E)o(R,r,{get:E[r],enumerable:!0})},n=(R,E,r,e)=>{if(E&&typeof E=="object"||typeof E=="function")for(let _ of s(E))!t.call(R,_)&&_!==r&&o(R,_,{get:()=>E[_],enumerable:!(e=L(E,_))||e.enumerable});return R};var d=R=>n(o({},"__esModule",{value:!0}),R);var p={};D(p,{HpkeError:()=>I});module.exports=d(p);var I=class extends Error{static{A(this,"HpkeError")}code;constructor(E,r,e){super(r,e),this.name="HpkeError",this.code=E}};
2
+ //# sourceMappingURL=errors.min.cjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/errors.ts"],
4
+ "sourcesContent": ["/**\n * The set of stable error codes this package raises.\n *\n * Branch on `err.code`, not on `err.message`: the codes are part of the\n * public API, the prose is not.\n */\nexport type HpkeErrorCode =\n 'ERR_INVALID_RECIPIENT_KEY'|\n 'ERR_INVALID_KEYPAIR'|\n 'ERR_SMALL_ORDER_KEY'|\n 'ERR_MALFORMED_ENVELOPE'|\n 'ERR_MALFORMED_MESSAGE'|\n 'ERR_INVALID_KEYSIZE'|\n 'ERR_INVALID_AES_KEY'|\n 'ERR_DECRYPT_FAILED'\n\n/**\n * An `Error` carrying a stable `code`. Every throw site in this package\n * raises one of these, so callers can branch on the failure instead of\n * pattern matching on a message.\n *\n * Where a WebCrypto failure is being wrapped, the original is kept on\n * `cause`.\n */\nexport class HpkeError extends Error {\n readonly code:HpkeErrorCode\n\n constructor (\n code:HpkeErrorCode,\n message:string,\n opts?:{ cause?:unknown }\n ) {\n super(message, opts)\n this.name = 'HpkeError'\n this.code = code\n }\n}\n"],
5
+ "mappings": "4dAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,eAAAE,IAAA,eAAAC,EAAAH,GAwBO,IAAMI,EAAN,cAAwB,KAAM,CAxBrC,MAwBqC,CAAAC,EAAA,kBACxB,KAET,YACIC,EACAC,EACAC,EACF,CACE,MAAMD,EAASC,CAAI,EACnB,KAAK,KAAO,YACZ,KAAK,KAAOF,CAChB,CACJ",
6
+ "names": ["errors_exports", "__export", "HpkeError", "__toCommonJS", "HpkeError", "__name", "code", "message", "opts"]
7
+ }
@@ -0,0 +1,2 @@
1
+ var I=Object.defineProperty;var r=(R,E)=>I(R,"name",{value:E,configurable:!0});var _=class extends Error{static{r(this,"HpkeError")}code;constructor(E,e,o){super(e,o),this.name="HpkeError",this.code=E}};export{_ as HpkeError};
2
+ //# sourceMappingURL=errors.min.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/errors.ts"],
4
+ "sourcesContent": ["/**\n * The set of stable error codes this package raises.\n *\n * Branch on `err.code`, not on `err.message`: the codes are part of the\n * public API, the prose is not.\n */\nexport type HpkeErrorCode =\n 'ERR_INVALID_RECIPIENT_KEY'|\n 'ERR_INVALID_KEYPAIR'|\n 'ERR_SMALL_ORDER_KEY'|\n 'ERR_MALFORMED_ENVELOPE'|\n 'ERR_MALFORMED_MESSAGE'|\n 'ERR_INVALID_KEYSIZE'|\n 'ERR_INVALID_AES_KEY'|\n 'ERR_DECRYPT_FAILED'\n\n/**\n * An `Error` carrying a stable `code`. Every throw site in this package\n * raises one of these, so callers can branch on the failure instead of\n * pattern matching on a message.\n *\n * Where a WebCrypto failure is being wrapped, the original is kept on\n * `cause`.\n */\nexport class HpkeError extends Error {\n readonly code:HpkeErrorCode\n\n constructor (\n code:HpkeErrorCode,\n message:string,\n opts?:{ cause?:unknown }\n ) {\n super(message, opts)\n this.name = 'HpkeError'\n this.code = code\n }\n}\n"],
5
+ "mappings": "+EAwBO,IAAMA,EAAN,cAAwB,KAAM,CAxBrC,MAwBqC,CAAAC,EAAA,kBACxB,KAET,YACIC,EACAC,EACAC,EACF,CACE,MAAMD,EAASC,CAAI,EACnB,KAAK,KAAO,YACZ,KAAK,KAAOF,CAChB,CACJ",
6
+ "names": ["HpkeError", "__name", "code", "message", "opts"]
7
+ }
package/dist/index.cjs CHANGED
@@ -21,10 +21,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  // src/index.ts
22
22
  var index_exports = {};
23
23
  __export(index_exports, {
24
+ HpkeError: () => HpkeError,
24
25
  create: () => create2,
25
26
  decrypt: () => decrypt,
26
27
  encrypt: () => encrypt,
27
28
  encryptKey: () => encryptKey,
29
+ keyId: () => keyId,
28
30
  open: () => open
29
31
  });
30
32
  module.exports = __toCommonJS(index_exports);
@@ -1307,6 +1309,19 @@ function toString2(array, encoding = "utf8") {
1307
1309
  }
1308
1310
  __name(toString2, "toString");
1309
1311
 
1312
+ // src/errors.ts
1313
+ var HpkeError = class extends Error {
1314
+ static {
1315
+ __name(this, "HpkeError");
1316
+ }
1317
+ code;
1318
+ constructor(code2, message, opts) {
1319
+ super(message, opts);
1320
+ this.name = "HpkeError";
1321
+ this.code = code2;
1322
+ }
1323
+ };
1324
+
1310
1325
  // src/constants.ts
1311
1326
  var KEM_ID = 32;
1312
1327
  var KDF_ID = 1;
@@ -1329,6 +1344,8 @@ var HPKE_SUITE_ID = concat2(
1329
1344
  i2osp(KDF_ID, 2),
1330
1345
  i2osp(AEAD_ID, 2)
1331
1346
  );
1347
+ var KEY_ID_LABEL = new TextEncoder().encode("simple-hpke/keyId/v1");
1348
+ var KEY_ID_NONCE_LENGTH = 12;
1332
1349
 
1333
1350
  // src/util.ts
1334
1351
  var subtle = globalThis.crypto.subtle;
@@ -1356,24 +1373,38 @@ async function aeadOpen(key, nonce, ciphertext) {
1356
1373
  false,
1357
1374
  ["decrypt"]
1358
1375
  );
1359
- const pt = await subtle.decrypt(
1360
- { name: "AES-GCM", iv: nonce },
1361
- k,
1362
- ciphertext
1363
- );
1376
+ let pt;
1377
+ try {
1378
+ pt = await subtle.decrypt(
1379
+ { name: "AES-GCM", iv: nonce },
1380
+ k,
1381
+ ciphertext
1382
+ );
1383
+ } catch (err) {
1384
+ throw new HpkeError(
1385
+ "ERR_DECRYPT_FAILED",
1386
+ "failed to decrypt the wrapped key: wrong keypair, wrong info, or a tampered envelope",
1387
+ { cause: err }
1388
+ );
1389
+ }
1364
1390
  return new Uint8Array(pt);
1365
1391
  }
1366
1392
  __name(aeadOpen, "aeadOpen");
1367
1393
  async function encryptBytes(recipient, message, aesKey, opts) {
1368
1394
  const plaintext = typeof message === "string" ? new TextEncoder().encode(message) : message;
1369
- const { enc, key } = await encryptKey(recipient, aesKey, opts);
1395
+ const { wrapped, key } = await encryptKey(recipient, aesKey, opts);
1370
1396
  const iv = globalThis.crypto.getRandomValues(new Uint8Array(NN));
1371
1397
  const ct = new Uint8Array(await subtle.encrypt(
1372
1398
  { name: "AES-GCM", iv },
1373
1399
  key,
1374
1400
  plaintext
1375
1401
  ));
1376
- return concat2(i2osp(enc.length, WRAPPED_LEN_PREFIX), enc, iv, ct);
1402
+ return concat2(
1403
+ i2osp(wrapped.length, WRAPPED_LEN_PREFIX),
1404
+ wrapped,
1405
+ iv,
1406
+ ct
1407
+ );
1377
1408
  }
1378
1409
  __name(encryptBytes, "encryptBytes");
1379
1410
  async function encryptToString(recipient, message, aesKey, opts) {
@@ -1439,7 +1470,8 @@ async function exportRawPublic(key) {
1439
1470
  __name(exportRawPublic, "exportRawPublic");
1440
1471
  async function importRawPublic(raw) {
1441
1472
  if (raw.length !== ENC_LENGTH) {
1442
- throw new Error(
1473
+ throw new HpkeError(
1474
+ "ERR_INVALID_RECIPIENT_KEY",
1443
1475
  `invalid public key length: ${raw.length} bytes (expected ${ENC_LENGTH})`
1444
1476
  );
1445
1477
  }
@@ -1458,9 +1490,16 @@ async function resolveRecipientPublicKey(recipient) {
1458
1490
  }
1459
1491
  if (recipient instanceof CryptoKey) {
1460
1492
  if (recipient.type !== "public") {
1461
- throw new Error("recipient CryptoKey must be a public key");
1493
+ throw new HpkeError(
1494
+ "ERR_INVALID_RECIPIENT_KEY",
1495
+ "recipient CryptoKey must be a public key"
1496
+ );
1462
1497
  }
1463
- assertX25519Algorithm(recipient, "recipient CryptoKey");
1498
+ assertX25519Algorithm(
1499
+ recipient,
1500
+ "recipient CryptoKey",
1501
+ "ERR_INVALID_RECIPIENT_KEY"
1502
+ );
1464
1503
  return recipient;
1465
1504
  }
1466
1505
  if (typeof recipient.publicKey === "string") {
@@ -1470,17 +1509,28 @@ async function resolveRecipientPublicKey(recipient) {
1470
1509
  const pair = recipient;
1471
1510
  if (pair.publicKey instanceof CryptoKey) {
1472
1511
  if (pair.publicKey.type !== "public") {
1473
- throw new Error("recipient keypair publicKey must be a public key");
1512
+ throw new HpkeError(
1513
+ "ERR_INVALID_RECIPIENT_KEY",
1514
+ "recipient keypair publicKey must be a public key"
1515
+ );
1474
1516
  }
1475
- assertX25519Algorithm(pair.publicKey, "recipient keypair publicKey");
1517
+ assertX25519Algorithm(
1518
+ pair.publicKey,
1519
+ "recipient keypair publicKey",
1520
+ "ERR_INVALID_RECIPIENT_KEY"
1521
+ );
1476
1522
  return pair.publicKey;
1477
1523
  }
1478
- throw new Error("unrecognized recipient key form");
1524
+ throw new HpkeError(
1525
+ "ERR_INVALID_RECIPIENT_KEY",
1526
+ "unrecognized recipient key form"
1527
+ );
1479
1528
  }
1480
1529
  __name(resolveRecipientPublicKey, "resolveRecipientPublicKey");
1481
- function assertX25519Algorithm(key, context) {
1530
+ function assertX25519Algorithm(key, context, code2) {
1482
1531
  if (key.algorithm.name !== "X25519") {
1483
- throw new Error(
1532
+ throw new HpkeError(
1533
+ code2,
1484
1534
  `${context} must be an X25519 key (got ${key.algorithm.name})`
1485
1535
  );
1486
1536
  }
@@ -1494,7 +1544,8 @@ async function dh(priv, pub) {
1494
1544
  );
1495
1545
  const bytes = new Uint8Array(bits);
1496
1546
  if (bytes.every((b) => b === 0)) {
1497
- throw new Error(
1547
+ throw new HpkeError(
1548
+ "ERR_SMALL_ORDER_KEY",
1498
1549
  "X25519 shared secret is all-zero (small-order public key?)"
1499
1550
  );
1500
1551
  }
@@ -1533,9 +1584,16 @@ async function encap(pkR) {
1533
1584
  __name(encap, "encap");
1534
1585
  async function decap(enc, keypair) {
1535
1586
  if (keypair.privateKey.type !== "private") {
1536
- throw new Error("keypair.privateKey must be a private key");
1587
+ throw new HpkeError(
1588
+ "ERR_INVALID_KEYPAIR",
1589
+ "keypair.privateKey must be a private key"
1590
+ );
1537
1591
  }
1538
- assertX25519Algorithm(keypair.privateKey, "keypair.privateKey");
1592
+ assertX25519Algorithm(
1593
+ keypair.privateKey,
1594
+ "keypair.privateKey",
1595
+ "ERR_INVALID_KEYPAIR"
1596
+ );
1539
1597
  const pkE = await subtle.importKey(
1540
1598
  "raw",
1541
1599
  enc,
@@ -1549,6 +1607,35 @@ async function decap(enc, keypair) {
1549
1607
  return extractAndExpand(dhBytes, kemContext);
1550
1608
  }
1551
1609
  __name(decap, "decap");
1610
+ async function keyId(key) {
1611
+ if (key.algorithm.name !== "AES-GCM") {
1612
+ throw new HpkeError(
1613
+ "ERR_INVALID_AES_KEY",
1614
+ `keyId needs an AES-GCM key (got ${key.algorithm.name})`
1615
+ );
1616
+ }
1617
+ if (!key.usages.includes("encrypt")) {
1618
+ throw new HpkeError(
1619
+ "ERR_INVALID_AES_KEY",
1620
+ "keyId needs a key with the 'encrypt' usage"
1621
+ );
1622
+ }
1623
+ const labelHash = new Uint8Array(
1624
+ await subtle.digest("SHA-256", KEY_ID_LABEL)
1625
+ );
1626
+ const nonce = labelHash.slice(0, KEY_ID_NONCE_LENGTH);
1627
+ const ciphertext = new Uint8Array(await subtle.encrypt(
1628
+ { name: "AES-GCM", iv: nonce },
1629
+ key,
1630
+ KEY_ID_LABEL
1631
+ ));
1632
+ const digest2 = await subtle.digest(
1633
+ "SHA-256",
1634
+ concat2(KEY_ID_LABEL, ciphertext)
1635
+ );
1636
+ return toString2(new Uint8Array(digest2), "base64url");
1637
+ }
1638
+ __name(keyId, "keyId");
1552
1639
  function i2osp(n, len) {
1553
1640
  const out = new Uint8Array(len);
1554
1641
  let v = n;
@@ -1575,8 +1662,8 @@ __name(concat2, "concat");
1575
1662
  // src/index.ts
1576
1663
  var subtle2 = globalThis.crypto.subtle;
1577
1664
  async function create2(recipient, opts) {
1578
- const { enc, key } = await encryptKey(recipient, null, opts);
1579
- return { enc, key };
1665
+ const { wrapped, key } = await encryptKey(recipient, null, opts);
1666
+ return { wrapped, key };
1580
1667
  }
1581
1668
  __name(create2, "create");
1582
1669
  var open = Object.assign(openBytes, {
@@ -1591,13 +1678,19 @@ var decrypt = Object.assign(decryptBytes, {
1591
1678
  });
1592
1679
  async function decryptBytes(keypair, message, opts) {
1593
1680
  if (message.byteLength < WRAPPED_LEN_PREFIX) {
1594
- throw new Error("malformed message: too short");
1681
+ throw new HpkeError(
1682
+ "ERR_MALFORMED_MESSAGE",
1683
+ "malformed message: too short"
1684
+ );
1595
1685
  }
1596
1686
  const wrappedLen = message[0] << 8 | message[1];
1597
1687
  const ivStart = WRAPPED_LEN_PREFIX + wrappedLen;
1598
1688
  const ctStart = ivStart + NN;
1599
1689
  if (message.byteLength < ctStart + AEAD_TAG_LENGTH) {
1600
- throw new Error("malformed message: too short");
1690
+ throw new HpkeError(
1691
+ "ERR_MALFORMED_MESSAGE",
1692
+ "malformed message: too short"
1693
+ );
1601
1694
  }
1602
1695
  const wrapped = message.slice(WRAPPED_LEN_PREFIX, ivStart);
1603
1696
  const iv = message.slice(ivStart, ctStart);
@@ -1608,11 +1701,20 @@ async function decryptBytes(keypair, message, opts) {
1608
1701
  opts?.info !== void 0 ? { info: opts.info } : void 0
1609
1702
  );
1610
1703
  const key = await importAesKeyDecryptOnly(keyBytes);
1611
- const pt = await subtle2.decrypt(
1612
- { name: "AES-GCM", iv },
1613
- key,
1614
- ciphertext
1615
- );
1704
+ let pt;
1705
+ try {
1706
+ pt = await subtle2.decrypt(
1707
+ { name: "AES-GCM", iv },
1708
+ key,
1709
+ ciphertext
1710
+ );
1711
+ } catch (err) {
1712
+ throw new HpkeError(
1713
+ "ERR_DECRYPT_FAILED",
1714
+ "failed to decrypt the message: tampered ciphertext",
1715
+ { cause: err }
1716
+ );
1717
+ }
1616
1718
  return new Uint8Array(pt);
1617
1719
  }
1618
1720
  __name(decryptBytes, "decryptBytes");
@@ -1633,7 +1735,8 @@ async function decryptFromString(keypair, message, opts) {
1633
1735
  __name(decryptFromString, "decryptFromString");
1634
1736
  function validateKeysize(keysize) {
1635
1737
  if (keysize !== 128 && keysize !== 256) {
1636
- throw new Error(
1738
+ throw new HpkeError(
1739
+ "ERR_INVALID_KEYSIZE",
1637
1740
  `invalid keysize: ${keysize} (expected 128 or 256)`
1638
1741
  );
1639
1742
  }
@@ -1641,7 +1744,8 @@ function validateKeysize(keysize) {
1641
1744
  __name(validateKeysize, "validateKeysize");
1642
1745
  function validateRawKeyBytes(raw) {
1643
1746
  if (raw.length !== 16 && raw.length !== 32) {
1644
- throw new Error(
1747
+ throw new HpkeError(
1748
+ "ERR_INVALID_AES_KEY",
1645
1749
  `invalid aesKey length: ${raw.length} bytes (expected 16 or 32)`
1646
1750
  );
1647
1751
  }
@@ -1655,19 +1759,20 @@ function normalizeInfo(info) {
1655
1759
  __name(normalizeInfo, "normalizeInfo");
1656
1760
  async function exportAesKeyBytes(key) {
1657
1761
  if (!key.extractable) {
1658
- throw new Error(
1762
+ throw new HpkeError(
1763
+ "ERR_INVALID_AES_KEY",
1659
1764
  "aesKey must be extractable: its raw bytes are what get sealed"
1660
1765
  );
1661
1766
  }
1662
1767
  return new Uint8Array(await subtle2.exportKey("raw", key));
1663
1768
  }
1664
1769
  __name(exportAesKeyBytes, "exportAesKeyBytes");
1665
- async function importAesKey(raw) {
1770
+ async function importAesKey(raw, extractable) {
1666
1771
  return subtle2.importKey(
1667
1772
  "raw",
1668
1773
  raw,
1669
1774
  { name: "AES-GCM" },
1670
- true,
1775
+ extractable,
1671
1776
  ["encrypt", "decrypt"]
1672
1777
  );
1673
1778
  }
@@ -1722,7 +1827,10 @@ async function keySchedule(sharedSecret, info) {
1722
1827
  __name(keySchedule, "keySchedule");
1723
1828
  async function openRawBytes(keypair, wrapped, opts) {
1724
1829
  if (wrapped.byteLength < ENC_LENGTH + AEAD_TAG_LENGTH) {
1725
- throw new Error("malformed envelope: too short");
1830
+ throw new HpkeError(
1831
+ "ERR_MALFORMED_ENVELOPE",
1832
+ "malformed envelope: too short"
1833
+ );
1726
1834
  }
1727
1835
  const info = normalizeInfo(opts?.info);
1728
1836
  const enc = wrapped.slice(0, ENC_LENGTH);
@@ -1734,7 +1842,7 @@ async function openRawBytes(keypair, wrapped, opts) {
1734
1842
  __name(openRawBytes, "openRawBytes");
1735
1843
  async function openBytes(keypair, wrapped, opts) {
1736
1844
  const keyBytes = await openRawBytes(keypair, wrapped, opts);
1737
- return importAesKey(keyBytes);
1845
+ return importAesKey(keyBytes, opts?.extractable ?? false);
1738
1846
  }
1739
1847
  __name(openBytes, "openBytes");
1740
1848
  async function encryptKey(recipient, aesKey, opts) {
@@ -1758,8 +1866,11 @@ async function encryptKey(recipient, aesKey, opts) {
1758
1866
  const { key, baseNonce } = await keySchedule(sharedSecret, info);
1759
1867
  const ciphertext = await aeadSeal(key, baseNonce, keyBytes);
1760
1868
  const wrapped = concat2(enc, ciphertext);
1761
- const aesGcmKey = await importAesKey(keyBytes);
1762
- return { enc: wrapped, key: aesGcmKey };
1869
+ const aesGcmKey = await importAesKey(
1870
+ keyBytes,
1871
+ opts?.extractable ?? false
1872
+ );
1873
+ return { wrapped, key: aesGcmKey };
1763
1874
  }
1764
1875
  __name(encryptKey, "encryptKey");
1765
1876
  //# sourceMappingURL=index.cjs.map