simple-hpke 0.1.5 → 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/README.md +137 -23
- package/dist/constants.cjs +115 -53
- 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 +115 -53
- 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 +147 -51
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +53 -32
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +147 -51
- 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 +79 -20
- package/dist/util.cjs +143 -53
- package/dist/util.cjs.map +4 -4
- package/dist/util.d.ts +33 -2
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +143 -53
- 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 +2 -2
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,13 @@ 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
|
-
|
|
28
|
+
encryptKey: () => encryptKey,
|
|
29
|
+
keyId: () => keyId,
|
|
30
|
+
open: () => open
|
|
29
31
|
});
|
|
30
32
|
module.exports = __toCommonJS(index_exports);
|
|
31
33
|
|
|
@@ -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,34 +1373,38 @@ async function aeadOpen(key, nonce, ciphertext) {
|
|
|
1356
1373
|
false,
|
|
1357
1374
|
["decrypt"]
|
|
1358
1375
|
);
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
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
|
-
|
|
1370
|
-
let key;
|
|
1371
|
-
if (!aesKey) {
|
|
1372
|
-
const keys = await create2(recipient, opts);
|
|
1373
|
-
enc = keys.enc;
|
|
1374
|
-
key = keys.key;
|
|
1375
|
-
} else {
|
|
1376
|
-
const keys = await seal(recipient, aesKey, opts);
|
|
1377
|
-
enc = keys.enc;
|
|
1378
|
-
key = keys.key;
|
|
1379
|
-
}
|
|
1395
|
+
const { wrapped, key } = await encryptKey(recipient, aesKey, opts);
|
|
1380
1396
|
const iv = globalThis.crypto.getRandomValues(new Uint8Array(NN));
|
|
1381
1397
|
const ct = new Uint8Array(await subtle.encrypt(
|
|
1382
1398
|
{ name: "AES-GCM", iv },
|
|
1383
1399
|
key,
|
|
1384
1400
|
plaintext
|
|
1385
1401
|
));
|
|
1386
|
-
return concat2(
|
|
1402
|
+
return concat2(
|
|
1403
|
+
i2osp(wrapped.length, WRAPPED_LEN_PREFIX),
|
|
1404
|
+
wrapped,
|
|
1405
|
+
iv,
|
|
1406
|
+
ct
|
|
1407
|
+
);
|
|
1387
1408
|
}
|
|
1388
1409
|
__name(encryptBytes, "encryptBytes");
|
|
1389
1410
|
async function encryptToString(recipient, message, aesKey, opts) {
|
|
@@ -1449,7 +1470,8 @@ async function exportRawPublic(key) {
|
|
|
1449
1470
|
__name(exportRawPublic, "exportRawPublic");
|
|
1450
1471
|
async function importRawPublic(raw) {
|
|
1451
1472
|
if (raw.length !== ENC_LENGTH) {
|
|
1452
|
-
throw new
|
|
1473
|
+
throw new HpkeError(
|
|
1474
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1453
1475
|
`invalid public key length: ${raw.length} bytes (expected ${ENC_LENGTH})`
|
|
1454
1476
|
);
|
|
1455
1477
|
}
|
|
@@ -1468,9 +1490,16 @@ async function resolveRecipientPublicKey(recipient) {
|
|
|
1468
1490
|
}
|
|
1469
1491
|
if (recipient instanceof CryptoKey) {
|
|
1470
1492
|
if (recipient.type !== "public") {
|
|
1471
|
-
throw new
|
|
1493
|
+
throw new HpkeError(
|
|
1494
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1495
|
+
"recipient CryptoKey must be a public key"
|
|
1496
|
+
);
|
|
1472
1497
|
}
|
|
1473
|
-
assertX25519Algorithm(
|
|
1498
|
+
assertX25519Algorithm(
|
|
1499
|
+
recipient,
|
|
1500
|
+
"recipient CryptoKey",
|
|
1501
|
+
"ERR_INVALID_RECIPIENT_KEY"
|
|
1502
|
+
);
|
|
1474
1503
|
return recipient;
|
|
1475
1504
|
}
|
|
1476
1505
|
if (typeof recipient.publicKey === "string") {
|
|
@@ -1480,17 +1509,28 @@ async function resolveRecipientPublicKey(recipient) {
|
|
|
1480
1509
|
const pair = recipient;
|
|
1481
1510
|
if (pair.publicKey instanceof CryptoKey) {
|
|
1482
1511
|
if (pair.publicKey.type !== "public") {
|
|
1483
|
-
throw new
|
|
1512
|
+
throw new HpkeError(
|
|
1513
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1514
|
+
"recipient keypair publicKey must be a public key"
|
|
1515
|
+
);
|
|
1484
1516
|
}
|
|
1485
|
-
assertX25519Algorithm(
|
|
1517
|
+
assertX25519Algorithm(
|
|
1518
|
+
pair.publicKey,
|
|
1519
|
+
"recipient keypair publicKey",
|
|
1520
|
+
"ERR_INVALID_RECIPIENT_KEY"
|
|
1521
|
+
);
|
|
1486
1522
|
return pair.publicKey;
|
|
1487
1523
|
}
|
|
1488
|
-
throw new
|
|
1524
|
+
throw new HpkeError(
|
|
1525
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1526
|
+
"unrecognized recipient key form"
|
|
1527
|
+
);
|
|
1489
1528
|
}
|
|
1490
1529
|
__name(resolveRecipientPublicKey, "resolveRecipientPublicKey");
|
|
1491
|
-
function assertX25519Algorithm(key, context) {
|
|
1530
|
+
function assertX25519Algorithm(key, context, code2) {
|
|
1492
1531
|
if (key.algorithm.name !== "X25519") {
|
|
1493
|
-
throw new
|
|
1532
|
+
throw new HpkeError(
|
|
1533
|
+
code2,
|
|
1494
1534
|
`${context} must be an X25519 key (got ${key.algorithm.name})`
|
|
1495
1535
|
);
|
|
1496
1536
|
}
|
|
@@ -1504,7 +1544,8 @@ async function dh(priv, pub) {
|
|
|
1504
1544
|
);
|
|
1505
1545
|
const bytes = new Uint8Array(bits);
|
|
1506
1546
|
if (bytes.every((b) => b === 0)) {
|
|
1507
|
-
throw new
|
|
1547
|
+
throw new HpkeError(
|
|
1548
|
+
"ERR_SMALL_ORDER_KEY",
|
|
1508
1549
|
"X25519 shared secret is all-zero (small-order public key?)"
|
|
1509
1550
|
);
|
|
1510
1551
|
}
|
|
@@ -1543,9 +1584,16 @@ async function encap(pkR) {
|
|
|
1543
1584
|
__name(encap, "encap");
|
|
1544
1585
|
async function decap(enc, keypair) {
|
|
1545
1586
|
if (keypair.privateKey.type !== "private") {
|
|
1546
|
-
throw new
|
|
1587
|
+
throw new HpkeError(
|
|
1588
|
+
"ERR_INVALID_KEYPAIR",
|
|
1589
|
+
"keypair.privateKey must be a private key"
|
|
1590
|
+
);
|
|
1547
1591
|
}
|
|
1548
|
-
assertX25519Algorithm(
|
|
1592
|
+
assertX25519Algorithm(
|
|
1593
|
+
keypair.privateKey,
|
|
1594
|
+
"keypair.privateKey",
|
|
1595
|
+
"ERR_INVALID_KEYPAIR"
|
|
1596
|
+
);
|
|
1549
1597
|
const pkE = await subtle.importKey(
|
|
1550
1598
|
"raw",
|
|
1551
1599
|
enc,
|
|
@@ -1559,6 +1607,35 @@ async function decap(enc, keypair) {
|
|
|
1559
1607
|
return extractAndExpand(dhBytes, kemContext);
|
|
1560
1608
|
}
|
|
1561
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");
|
|
1562
1639
|
function i2osp(n, len) {
|
|
1563
1640
|
const out = new Uint8Array(len);
|
|
1564
1641
|
let v = n;
|
|
@@ -1586,14 +1663,9 @@ __name(concat2, "concat");
|
|
|
1586
1663
|
var subtle2 = globalThis.crypto.subtle;
|
|
1587
1664
|
async function create2(recipient, opts) {
|
|
1588
1665
|
const { wrapped, key } = await encryptKey(recipient, null, opts);
|
|
1589
|
-
return {
|
|
1666
|
+
return { wrapped, key };
|
|
1590
1667
|
}
|
|
1591
1668
|
__name(create2, "create");
|
|
1592
|
-
async function seal(recipient, aesKey, opts) {
|
|
1593
|
-
const { wrapped, key } = await encryptKey(recipient, aesKey, opts);
|
|
1594
|
-
return { enc: wrapped, key };
|
|
1595
|
-
}
|
|
1596
|
-
__name(seal, "seal");
|
|
1597
1669
|
var open = Object.assign(openBytes, {
|
|
1598
1670
|
raw: openRawBytes
|
|
1599
1671
|
});
|
|
@@ -1606,13 +1678,19 @@ var decrypt = Object.assign(decryptBytes, {
|
|
|
1606
1678
|
});
|
|
1607
1679
|
async function decryptBytes(keypair, message, opts) {
|
|
1608
1680
|
if (message.byteLength < WRAPPED_LEN_PREFIX) {
|
|
1609
|
-
throw new
|
|
1681
|
+
throw new HpkeError(
|
|
1682
|
+
"ERR_MALFORMED_MESSAGE",
|
|
1683
|
+
"malformed message: too short"
|
|
1684
|
+
);
|
|
1610
1685
|
}
|
|
1611
1686
|
const wrappedLen = message[0] << 8 | message[1];
|
|
1612
1687
|
const ivStart = WRAPPED_LEN_PREFIX + wrappedLen;
|
|
1613
1688
|
const ctStart = ivStart + NN;
|
|
1614
1689
|
if (message.byteLength < ctStart + AEAD_TAG_LENGTH) {
|
|
1615
|
-
throw new
|
|
1690
|
+
throw new HpkeError(
|
|
1691
|
+
"ERR_MALFORMED_MESSAGE",
|
|
1692
|
+
"malformed message: too short"
|
|
1693
|
+
);
|
|
1616
1694
|
}
|
|
1617
1695
|
const wrapped = message.slice(WRAPPED_LEN_PREFIX, ivStart);
|
|
1618
1696
|
const iv = message.slice(ivStart, ctStart);
|
|
@@ -1623,11 +1701,20 @@ async function decryptBytes(keypair, message, opts) {
|
|
|
1623
1701
|
opts?.info !== void 0 ? { info: opts.info } : void 0
|
|
1624
1702
|
);
|
|
1625
1703
|
const key = await importAesKeyDecryptOnly(keyBytes);
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
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
|
+
}
|
|
1631
1718
|
return new Uint8Array(pt);
|
|
1632
1719
|
}
|
|
1633
1720
|
__name(decryptBytes, "decryptBytes");
|
|
@@ -1648,7 +1735,8 @@ async function decryptFromString(keypair, message, opts) {
|
|
|
1648
1735
|
__name(decryptFromString, "decryptFromString");
|
|
1649
1736
|
function validateKeysize(keysize) {
|
|
1650
1737
|
if (keysize !== 128 && keysize !== 256) {
|
|
1651
|
-
throw new
|
|
1738
|
+
throw new HpkeError(
|
|
1739
|
+
"ERR_INVALID_KEYSIZE",
|
|
1652
1740
|
`invalid keysize: ${keysize} (expected 128 or 256)`
|
|
1653
1741
|
);
|
|
1654
1742
|
}
|
|
@@ -1656,7 +1744,8 @@ function validateKeysize(keysize) {
|
|
|
1656
1744
|
__name(validateKeysize, "validateKeysize");
|
|
1657
1745
|
function validateRawKeyBytes(raw) {
|
|
1658
1746
|
if (raw.length !== 16 && raw.length !== 32) {
|
|
1659
|
-
throw new
|
|
1747
|
+
throw new HpkeError(
|
|
1748
|
+
"ERR_INVALID_AES_KEY",
|
|
1660
1749
|
`invalid aesKey length: ${raw.length} bytes (expected 16 or 32)`
|
|
1661
1750
|
);
|
|
1662
1751
|
}
|
|
@@ -1670,19 +1759,20 @@ function normalizeInfo(info) {
|
|
|
1670
1759
|
__name(normalizeInfo, "normalizeInfo");
|
|
1671
1760
|
async function exportAesKeyBytes(key) {
|
|
1672
1761
|
if (!key.extractable) {
|
|
1673
|
-
throw new
|
|
1762
|
+
throw new HpkeError(
|
|
1763
|
+
"ERR_INVALID_AES_KEY",
|
|
1674
1764
|
"aesKey must be extractable: its raw bytes are what get sealed"
|
|
1675
1765
|
);
|
|
1676
1766
|
}
|
|
1677
1767
|
return new Uint8Array(await subtle2.exportKey("raw", key));
|
|
1678
1768
|
}
|
|
1679
1769
|
__name(exportAesKeyBytes, "exportAesKeyBytes");
|
|
1680
|
-
async function importAesKey(raw) {
|
|
1770
|
+
async function importAesKey(raw, extractable) {
|
|
1681
1771
|
return subtle2.importKey(
|
|
1682
1772
|
"raw",
|
|
1683
1773
|
raw,
|
|
1684
1774
|
{ name: "AES-GCM" },
|
|
1685
|
-
|
|
1775
|
+
extractable,
|
|
1686
1776
|
["encrypt", "decrypt"]
|
|
1687
1777
|
);
|
|
1688
1778
|
}
|
|
@@ -1737,7 +1827,10 @@ async function keySchedule(sharedSecret, info) {
|
|
|
1737
1827
|
__name(keySchedule, "keySchedule");
|
|
1738
1828
|
async function openRawBytes(keypair, wrapped, opts) {
|
|
1739
1829
|
if (wrapped.byteLength < ENC_LENGTH + AEAD_TAG_LENGTH) {
|
|
1740
|
-
throw new
|
|
1830
|
+
throw new HpkeError(
|
|
1831
|
+
"ERR_MALFORMED_ENVELOPE",
|
|
1832
|
+
"malformed envelope: too short"
|
|
1833
|
+
);
|
|
1741
1834
|
}
|
|
1742
1835
|
const info = normalizeInfo(opts?.info);
|
|
1743
1836
|
const enc = wrapped.slice(0, ENC_LENGTH);
|
|
@@ -1749,7 +1842,7 @@ async function openRawBytes(keypair, wrapped, opts) {
|
|
|
1749
1842
|
__name(openRawBytes, "openRawBytes");
|
|
1750
1843
|
async function openBytes(keypair, wrapped, opts) {
|
|
1751
1844
|
const keyBytes = await openRawBytes(keypair, wrapped, opts);
|
|
1752
|
-
return importAesKey(keyBytes);
|
|
1845
|
+
return importAesKey(keyBytes, opts?.extractable ?? false);
|
|
1753
1846
|
}
|
|
1754
1847
|
__name(openBytes, "openBytes");
|
|
1755
1848
|
async function encryptKey(recipient, aesKey, opts) {
|
|
@@ -1773,7 +1866,10 @@ async function encryptKey(recipient, aesKey, opts) {
|
|
|
1773
1866
|
const { key, baseNonce } = await keySchedule(sharedSecret, info);
|
|
1774
1867
|
const ciphertext = await aeadSeal(key, baseNonce, keyBytes);
|
|
1775
1868
|
const wrapped = concat2(enc, ciphertext);
|
|
1776
|
-
const aesGcmKey = await importAesKey(
|
|
1869
|
+
const aesGcmKey = await importAesKey(
|
|
1870
|
+
keyBytes,
|
|
1871
|
+
opts?.extractable ?? false
|
|
1872
|
+
);
|
|
1777
1873
|
return { wrapped, key: aesGcmKey };
|
|
1778
1874
|
}
|
|
1779
1875
|
__name(encryptKey, "encryptKey");
|