simple-hpke 0.2.0 → 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/README.md +207 -27
- package/dist/constants.cjs +188 -47
- package/dist/constants.cjs.map +4 -4
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +188 -47
- package/dist/constants.js.map +4 -4
- package/dist/constants.min.cjs +1 -1
- package/dist/constants.min.cjs.map +4 -4
- package/dist/constants.min.js +1 -1
- package/dist/constants.min.js.map +4 -4
- package/dist/errors.cjs +38 -0
- package/dist/errors.cjs.map +7 -0
- package/dist/errors.d.ts +22 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +19 -0
- package/dist/errors.js.map +7 -0
- package/dist/errors.min.cjs +2 -0
- package/dist/errors.min.cjs.map +7 -0
- package/dist/errors.min.js +2 -0
- package/dist/errors.min.js.map +7 -0
- package/dist/index.cjs +231 -50
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +34 -21
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +231 -50
- package/dist/index.js.map +4 -4
- package/dist/index.min.cjs +1 -1
- package/dist/index.min.cjs.map +4 -4
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +4 -4
- package/dist/meta.json +81 -20
- package/dist/util.cjs +228 -48
- package/dist/util.cjs.map +4 -4
- package/dist/util.d.ts +56 -3
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +228 -48
- package/dist/util.js.map +4 -4
- package/dist/util.min.cjs +1 -1
- package/dist/util.min.cjs.map +4 -4
- package/dist/util.min.js +1 -1
- package/dist/util.min.js.map +4 -4
- package/package.json +6 -5
package/dist/errors.cjs
ADDED
|
@@ -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
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -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,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,11 +21,14 @@ 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,
|
|
28
|
-
|
|
29
|
+
keyId: () => keyId,
|
|
30
|
+
open: () => open,
|
|
31
|
+
verifyKeyId: () => verifyKeyId
|
|
29
32
|
});
|
|
30
33
|
module.exports = __toCommonJS(index_exports);
|
|
31
34
|
|
|
@@ -1307,6 +1310,19 @@ function toString2(array, encoding = "utf8") {
|
|
|
1307
1310
|
}
|
|
1308
1311
|
__name(toString2, "toString");
|
|
1309
1312
|
|
|
1313
|
+
// src/errors.ts
|
|
1314
|
+
var HpkeError = class extends Error {
|
|
1315
|
+
static {
|
|
1316
|
+
__name(this, "HpkeError");
|
|
1317
|
+
}
|
|
1318
|
+
code;
|
|
1319
|
+
constructor(code2, message, opts) {
|
|
1320
|
+
super(message, opts);
|
|
1321
|
+
this.name = "HpkeError";
|
|
1322
|
+
this.code = code2;
|
|
1323
|
+
}
|
|
1324
|
+
};
|
|
1325
|
+
|
|
1310
1326
|
// src/constants.ts
|
|
1311
1327
|
var KEM_ID = 32;
|
|
1312
1328
|
var KDF_ID = 1;
|
|
@@ -1329,6 +1345,8 @@ var HPKE_SUITE_ID = concat2(
|
|
|
1329
1345
|
i2osp(KDF_ID, 2),
|
|
1330
1346
|
i2osp(AEAD_ID, 2)
|
|
1331
1347
|
);
|
|
1348
|
+
var KEY_ID_LABEL = new TextEncoder().encode("simple-hpke/keyId/v1");
|
|
1349
|
+
var KEY_ID_NONCE_LENGTH = 12;
|
|
1332
1350
|
|
|
1333
1351
|
// src/util.ts
|
|
1334
1352
|
var subtle = globalThis.crypto.subtle;
|
|
@@ -1356,24 +1374,44 @@ async function aeadOpen(key, nonce, ciphertext) {
|
|
|
1356
1374
|
false,
|
|
1357
1375
|
["decrypt"]
|
|
1358
1376
|
);
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1377
|
+
let pt;
|
|
1378
|
+
try {
|
|
1379
|
+
pt = await subtle.decrypt(
|
|
1380
|
+
{ name: "AES-GCM", iv: nonce },
|
|
1381
|
+
k,
|
|
1382
|
+
ciphertext
|
|
1383
|
+
);
|
|
1384
|
+
} catch (err) {
|
|
1385
|
+
throw new HpkeError(
|
|
1386
|
+
"ERR_DECRYPT_FAILED",
|
|
1387
|
+
"failed to decrypt the wrapped key: wrong keypair, wrong info, or a tampered envelope",
|
|
1388
|
+
{ cause: err }
|
|
1389
|
+
);
|
|
1390
|
+
}
|
|
1364
1391
|
return new Uint8Array(pt);
|
|
1365
1392
|
}
|
|
1366
1393
|
__name(aeadOpen, "aeadOpen");
|
|
1367
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
|
+
}
|
|
1368
1401
|
const plaintext = typeof message === "string" ? new TextEncoder().encode(message) : message;
|
|
1369
|
-
const {
|
|
1402
|
+
const { wrapped, key } = await encryptKey(recipient, aesKey, opts);
|
|
1370
1403
|
const iv = globalThis.crypto.getRandomValues(new Uint8Array(NN));
|
|
1371
1404
|
const ct = new Uint8Array(await subtle.encrypt(
|
|
1372
1405
|
{ name: "AES-GCM", iv },
|
|
1373
1406
|
key,
|
|
1374
1407
|
plaintext
|
|
1375
1408
|
));
|
|
1376
|
-
return concat2(
|
|
1409
|
+
return concat2(
|
|
1410
|
+
i2osp(wrapped.length, WRAPPED_LEN_PREFIX),
|
|
1411
|
+
wrapped,
|
|
1412
|
+
iv,
|
|
1413
|
+
ct
|
|
1414
|
+
);
|
|
1377
1415
|
}
|
|
1378
1416
|
__name(encryptBytes, "encryptBytes");
|
|
1379
1417
|
async function encryptToString(recipient, message, aesKey, opts) {
|
|
@@ -1439,7 +1477,8 @@ async function exportRawPublic(key) {
|
|
|
1439
1477
|
__name(exportRawPublic, "exportRawPublic");
|
|
1440
1478
|
async function importRawPublic(raw) {
|
|
1441
1479
|
if (raw.length !== ENC_LENGTH) {
|
|
1442
|
-
throw new
|
|
1480
|
+
throw new HpkeError(
|
|
1481
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1443
1482
|
`invalid public key length: ${raw.length} bytes (expected ${ENC_LENGTH})`
|
|
1444
1483
|
);
|
|
1445
1484
|
}
|
|
@@ -1457,44 +1496,83 @@ async function resolveRecipientPublicKey(recipient) {
|
|
|
1457
1496
|
return importRawPublic(recipient);
|
|
1458
1497
|
}
|
|
1459
1498
|
if (recipient instanceof CryptoKey) {
|
|
1460
|
-
|
|
1461
|
-
throw new Error("recipient CryptoKey must be a public key");
|
|
1462
|
-
}
|
|
1463
|
-
assertX25519Algorithm(recipient, "recipient CryptoKey");
|
|
1464
|
-
return recipient;
|
|
1499
|
+
return assertRecipientPublicKey(recipient, "recipient CryptoKey");
|
|
1465
1500
|
}
|
|
1466
1501
|
if (typeof recipient.publicKey === "string") {
|
|
1467
1502
|
const { publicKey, encoding } = recipient;
|
|
1468
|
-
|
|
1503
|
+
let decoded;
|
|
1504
|
+
try {
|
|
1505
|
+
decoded = fromString2(publicKey, encoding ?? "base64url");
|
|
1506
|
+
} catch (err) {
|
|
1507
|
+
throw new HpkeError(
|
|
1508
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1509
|
+
`recipient publicKey string does not decode as ${encoding ?? "base64url"}`,
|
|
1510
|
+
{ cause: err }
|
|
1511
|
+
);
|
|
1512
|
+
}
|
|
1513
|
+
return importRawPublic(decoded);
|
|
1469
1514
|
}
|
|
1470
1515
|
const pair = recipient;
|
|
1471
1516
|
if (pair.publicKey instanceof CryptoKey) {
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
return pair.publicKey;
|
|
1517
|
+
return assertRecipientPublicKey(
|
|
1518
|
+
pair.publicKey,
|
|
1519
|
+
"recipient keypair publicKey"
|
|
1520
|
+
);
|
|
1477
1521
|
}
|
|
1478
|
-
throw new
|
|
1522
|
+
throw new HpkeError(
|
|
1523
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1524
|
+
"unrecognized recipient key form"
|
|
1525
|
+
);
|
|
1479
1526
|
}
|
|
1480
1527
|
__name(resolveRecipientPublicKey, "resolveRecipientPublicKey");
|
|
1481
|
-
function
|
|
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");
|
|
1545
|
+
function assertX25519Algorithm(key, context, code2) {
|
|
1482
1546
|
if (key.algorithm.name !== "X25519") {
|
|
1483
|
-
throw new
|
|
1547
|
+
throw new HpkeError(
|
|
1548
|
+
code2,
|
|
1484
1549
|
`${context} must be an X25519 key (got ${key.algorithm.name})`
|
|
1485
1550
|
);
|
|
1486
1551
|
}
|
|
1487
1552
|
}
|
|
1488
1553
|
__name(assertX25519Algorithm, "assertX25519Algorithm");
|
|
1489
1554
|
async function dh(priv, pub) {
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
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
|
+
}
|
|
1495
1572
|
const bytes = new Uint8Array(bits);
|
|
1496
1573
|
if (bytes.every((b) => b === 0)) {
|
|
1497
|
-
throw new
|
|
1574
|
+
throw new HpkeError(
|
|
1575
|
+
"ERR_SMALL_ORDER_KEY",
|
|
1498
1576
|
"X25519 shared secret is all-zero (small-order public key?)"
|
|
1499
1577
|
);
|
|
1500
1578
|
}
|
|
@@ -1533,9 +1611,33 @@ async function encap(pkR) {
|
|
|
1533
1611
|
__name(encap, "encap");
|
|
1534
1612
|
async function decap(enc, keypair) {
|
|
1535
1613
|
if (keypair.privateKey.type !== "private") {
|
|
1536
|
-
throw new
|
|
1614
|
+
throw new HpkeError(
|
|
1615
|
+
"ERR_INVALID_KEYPAIR",
|
|
1616
|
+
"keypair.privateKey must be a private key"
|
|
1617
|
+
);
|
|
1537
1618
|
}
|
|
1538
|
-
assertX25519Algorithm(
|
|
1619
|
+
assertX25519Algorithm(
|
|
1620
|
+
keypair.privateKey,
|
|
1621
|
+
"keypair.privateKey",
|
|
1622
|
+
"ERR_INVALID_KEYPAIR"
|
|
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
|
+
);
|
|
1539
1641
|
const pkE = await subtle.importKey(
|
|
1540
1642
|
"raw",
|
|
1541
1643
|
enc,
|
|
@@ -1549,6 +1651,45 @@ async function decap(enc, keypair) {
|
|
|
1549
1651
|
return extractAndExpand(dhBytes, kemContext);
|
|
1550
1652
|
}
|
|
1551
1653
|
__name(decap, "decap");
|
|
1654
|
+
async function keyId(key) {
|
|
1655
|
+
if (key.algorithm.name !== "AES-GCM") {
|
|
1656
|
+
throw new HpkeError(
|
|
1657
|
+
"ERR_INVALID_AES_KEY",
|
|
1658
|
+
`keyId needs an AES-GCM key (got ${key.algorithm.name})`
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
if (!key.usages.includes("encrypt")) {
|
|
1662
|
+
throw new HpkeError(
|
|
1663
|
+
"ERR_INVALID_AES_KEY",
|
|
1664
|
+
"keyId needs a key with the 'encrypt' usage"
|
|
1665
|
+
);
|
|
1666
|
+
}
|
|
1667
|
+
const labelHash = new Uint8Array(
|
|
1668
|
+
await subtle.digest("SHA-256", KEY_ID_LABEL)
|
|
1669
|
+
);
|
|
1670
|
+
const nonce = labelHash.slice(0, KEY_ID_NONCE_LENGTH);
|
|
1671
|
+
const ciphertext = new Uint8Array(await subtle.encrypt(
|
|
1672
|
+
{ name: "AES-GCM", iv: nonce },
|
|
1673
|
+
key,
|
|
1674
|
+
KEY_ID_LABEL
|
|
1675
|
+
));
|
|
1676
|
+
const digest2 = await subtle.digest(
|
|
1677
|
+
"SHA-256",
|
|
1678
|
+
concat2(KEY_ID_LABEL, ciphertext)
|
|
1679
|
+
);
|
|
1680
|
+
return toString2(new Uint8Array(digest2), "base64url");
|
|
1681
|
+
}
|
|
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");
|
|
1552
1693
|
function i2osp(n, len) {
|
|
1553
1694
|
const out = new Uint8Array(len);
|
|
1554
1695
|
let v = n;
|
|
@@ -1575,8 +1716,8 @@ __name(concat2, "concat");
|
|
|
1575
1716
|
// src/index.ts
|
|
1576
1717
|
var subtle2 = globalThis.crypto.subtle;
|
|
1577
1718
|
async function create2(recipient, opts) {
|
|
1578
|
-
const {
|
|
1579
|
-
return {
|
|
1719
|
+
const { wrapped, key } = await encryptKey(recipient, null, opts);
|
|
1720
|
+
return { wrapped, key };
|
|
1580
1721
|
}
|
|
1581
1722
|
__name(create2, "create");
|
|
1582
1723
|
var open = Object.assign(openBytes, {
|
|
@@ -1591,13 +1732,19 @@ var decrypt = Object.assign(decryptBytes, {
|
|
|
1591
1732
|
});
|
|
1592
1733
|
async function decryptBytes(keypair, message, opts) {
|
|
1593
1734
|
if (message.byteLength < WRAPPED_LEN_PREFIX) {
|
|
1594
|
-
throw new
|
|
1735
|
+
throw new HpkeError(
|
|
1736
|
+
"ERR_MALFORMED_MESSAGE",
|
|
1737
|
+
"malformed message: too short"
|
|
1738
|
+
);
|
|
1595
1739
|
}
|
|
1596
1740
|
const wrappedLen = message[0] << 8 | message[1];
|
|
1597
1741
|
const ivStart = WRAPPED_LEN_PREFIX + wrappedLen;
|
|
1598
1742
|
const ctStart = ivStart + NN;
|
|
1599
1743
|
if (message.byteLength < ctStart + AEAD_TAG_LENGTH) {
|
|
1600
|
-
throw new
|
|
1744
|
+
throw new HpkeError(
|
|
1745
|
+
"ERR_MALFORMED_MESSAGE",
|
|
1746
|
+
"malformed message: too short"
|
|
1747
|
+
);
|
|
1601
1748
|
}
|
|
1602
1749
|
const wrapped = message.slice(WRAPPED_LEN_PREFIX, ivStart);
|
|
1603
1750
|
const iv = message.slice(ivStart, ctStart);
|
|
@@ -1608,11 +1755,20 @@ async function decryptBytes(keypair, message, opts) {
|
|
|
1608
1755
|
opts?.info !== void 0 ? { info: opts.info } : void 0
|
|
1609
1756
|
);
|
|
1610
1757
|
const key = await importAesKeyDecryptOnly(keyBytes);
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1758
|
+
let pt;
|
|
1759
|
+
try {
|
|
1760
|
+
pt = await subtle2.decrypt(
|
|
1761
|
+
{ name: "AES-GCM", iv },
|
|
1762
|
+
key,
|
|
1763
|
+
ciphertext
|
|
1764
|
+
);
|
|
1765
|
+
} catch (err) {
|
|
1766
|
+
throw new HpkeError(
|
|
1767
|
+
"ERR_DECRYPT_FAILED",
|
|
1768
|
+
"failed to decrypt the message: tampered ciphertext",
|
|
1769
|
+
{ cause: err }
|
|
1770
|
+
);
|
|
1771
|
+
}
|
|
1616
1772
|
return new Uint8Array(pt);
|
|
1617
1773
|
}
|
|
1618
1774
|
__name(decryptBytes, "decryptBytes");
|
|
@@ -1622,7 +1778,16 @@ async function decryptToString(keypair, message, opts) {
|
|
|
1622
1778
|
}
|
|
1623
1779
|
__name(decryptToString, "decryptToString");
|
|
1624
1780
|
async function decryptFromString(keypair, message, opts) {
|
|
1625
|
-
|
|
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
|
+
}
|
|
1626
1791
|
const plaintext = await decryptBytes(
|
|
1627
1792
|
keypair,
|
|
1628
1793
|
bytes,
|
|
@@ -1633,7 +1798,8 @@ async function decryptFromString(keypair, message, opts) {
|
|
|
1633
1798
|
__name(decryptFromString, "decryptFromString");
|
|
1634
1799
|
function validateKeysize(keysize) {
|
|
1635
1800
|
if (keysize !== 128 && keysize !== 256) {
|
|
1636
|
-
throw new
|
|
1801
|
+
throw new HpkeError(
|
|
1802
|
+
"ERR_INVALID_KEYSIZE",
|
|
1637
1803
|
`invalid keysize: ${keysize} (expected 128 or 256)`
|
|
1638
1804
|
);
|
|
1639
1805
|
}
|
|
@@ -1641,7 +1807,8 @@ function validateKeysize(keysize) {
|
|
|
1641
1807
|
__name(validateKeysize, "validateKeysize");
|
|
1642
1808
|
function validateRawKeyBytes(raw) {
|
|
1643
1809
|
if (raw.length !== 16 && raw.length !== 32) {
|
|
1644
|
-
throw new
|
|
1810
|
+
throw new HpkeError(
|
|
1811
|
+
"ERR_INVALID_AES_KEY",
|
|
1645
1812
|
`invalid aesKey length: ${raw.length} bytes (expected 16 or 32)`
|
|
1646
1813
|
);
|
|
1647
1814
|
}
|
|
@@ -1655,19 +1822,20 @@ function normalizeInfo(info) {
|
|
|
1655
1822
|
__name(normalizeInfo, "normalizeInfo");
|
|
1656
1823
|
async function exportAesKeyBytes(key) {
|
|
1657
1824
|
if (!key.extractable) {
|
|
1658
|
-
throw new
|
|
1825
|
+
throw new HpkeError(
|
|
1826
|
+
"ERR_INVALID_AES_KEY",
|
|
1659
1827
|
"aesKey must be extractable: its raw bytes are what get sealed"
|
|
1660
1828
|
);
|
|
1661
1829
|
}
|
|
1662
1830
|
return new Uint8Array(await subtle2.exportKey("raw", key));
|
|
1663
1831
|
}
|
|
1664
1832
|
__name(exportAesKeyBytes, "exportAesKeyBytes");
|
|
1665
|
-
async function importAesKey(raw) {
|
|
1833
|
+
async function importAesKey(raw, extractable) {
|
|
1666
1834
|
return subtle2.importKey(
|
|
1667
1835
|
"raw",
|
|
1668
1836
|
raw,
|
|
1669
1837
|
{ name: "AES-GCM" },
|
|
1670
|
-
|
|
1838
|
+
extractable,
|
|
1671
1839
|
["encrypt", "decrypt"]
|
|
1672
1840
|
);
|
|
1673
1841
|
}
|
|
@@ -1722,7 +1890,10 @@ async function keySchedule(sharedSecret, info) {
|
|
|
1722
1890
|
__name(keySchedule, "keySchedule");
|
|
1723
1891
|
async function openRawBytes(keypair, wrapped, opts) {
|
|
1724
1892
|
if (wrapped.byteLength < ENC_LENGTH + AEAD_TAG_LENGTH) {
|
|
1725
|
-
throw new
|
|
1893
|
+
throw new HpkeError(
|
|
1894
|
+
"ERR_MALFORMED_ENVELOPE",
|
|
1895
|
+
"malformed envelope: too short"
|
|
1896
|
+
);
|
|
1726
1897
|
}
|
|
1727
1898
|
const info = normalizeInfo(opts?.info);
|
|
1728
1899
|
const enc = wrapped.slice(0, ENC_LENGTH);
|
|
@@ -1734,7 +1905,7 @@ async function openRawBytes(keypair, wrapped, opts) {
|
|
|
1734
1905
|
__name(openRawBytes, "openRawBytes");
|
|
1735
1906
|
async function openBytes(keypair, wrapped, opts) {
|
|
1736
1907
|
const keyBytes = await openRawBytes(keypair, wrapped, opts);
|
|
1737
|
-
return importAesKey(keyBytes);
|
|
1908
|
+
return importAesKey(keyBytes, opts?.extractable ?? false);
|
|
1738
1909
|
}
|
|
1739
1910
|
__name(openBytes, "openBytes");
|
|
1740
1911
|
async function encryptKey(recipient, aesKey, opts) {
|
|
@@ -1744,6 +1915,12 @@ async function encryptKey(recipient, aesKey, opts) {
|
|
|
1744
1915
|
validateRawKeyBytes(aesKey);
|
|
1745
1916
|
keyBytes = aesKey;
|
|
1746
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
|
+
}
|
|
1747
1924
|
keyBytes = await exportAesKeyBytes(aesKey);
|
|
1748
1925
|
validateRawKeyBytes(keyBytes);
|
|
1749
1926
|
} else {
|
|
@@ -1758,8 +1935,12 @@ async function encryptKey(recipient, aesKey, opts) {
|
|
|
1758
1935
|
const { key, baseNonce } = await keySchedule(sharedSecret, info);
|
|
1759
1936
|
const ciphertext = await aeadSeal(key, baseNonce, keyBytes);
|
|
1760
1937
|
const wrapped = concat2(enc, ciphertext);
|
|
1761
|
-
|
|
1762
|
-
|
|
1938
|
+
if (aesKey instanceof CryptoKey) return { wrapped, key: aesKey };
|
|
1939
|
+
const aesGcmKey = await importAesKey(
|
|
1940
|
+
keyBytes,
|
|
1941
|
+
opts?.extractable ?? false
|
|
1942
|
+
);
|
|
1943
|
+
return { wrapped, key: aesGcmKey };
|
|
1763
1944
|
}
|
|
1764
1945
|
__name(encryptKey, "encryptKey");
|
|
1765
1946
|
//# sourceMappingURL=index.cjs.map
|