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/index.d.ts
CHANGED
|
@@ -1,40 +1,31 @@
|
|
|
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 `
|
|
7
|
-
* @returns {{
|
|
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
|
-
|
|
17
|
+
wrapped: Uint8Array<ArrayBufferLike>;
|
|
15
18
|
key: CryptoKey;
|
|
16
19
|
}>;
|
|
17
20
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* @returns {{ enc, key }} The wrapped envelope bytes and a usable AES-GCM
|
|
21
|
-
* key with the same raw bytes as `aesKey`.
|
|
22
|
-
*/
|
|
23
|
-
export declare function seal(recipient: RecipientKey, aesKey: CryptoKey | Uint8Array | null, opts?: {
|
|
24
|
-
info?: Uint8Array | string;
|
|
25
|
-
}): Promise<{
|
|
26
|
-
enc: Uint8Array<ArrayBufferLike>;
|
|
27
|
-
key: CryptoKey;
|
|
28
|
-
}>;
|
|
29
|
-
/**
|
|
30
|
-
* Recover the AES key wrapped by `create` or `seal`. Call `open(...)` for a
|
|
31
|
-
* usable AES-GCM `CryptoKey`, or `open.raw(...)` for the raw key bytes.
|
|
21
|
+
* Recover the AES key wrapped by `create` or `encryptKey`. Call `open(...)`
|
|
22
|
+
* for a usable AES-GCM `CryptoKey`, or `open.raw(...)` for the raw key bytes.
|
|
32
23
|
*/
|
|
33
24
|
export declare const open: typeof openBytes & {
|
|
34
25
|
raw: typeof openRawBytes;
|
|
35
26
|
};
|
|
36
27
|
/**
|
|
37
|
-
*
|
|
28
|
+
* Wrap an AES key to `recipient` and AES-GCM encrypt a message under it.
|
|
38
29
|
* Call `encrypt(...)` for the raw envelope bytes, or `encrypt.asString(...)`
|
|
39
30
|
* for the same envelope as an encoded string.
|
|
40
31
|
*/
|
|
@@ -58,7 +49,7 @@ export declare const decrypt: typeof decryptBytes & {
|
|
|
58
49
|
*
|
|
59
50
|
* @param keypair The same recipient `CryptoKeyPair` used to `encrypt`.
|
|
60
51
|
* @param message The envelope returned by `encrypt`.
|
|
61
|
-
* @param opts `info`
|
|
52
|
+
* @param opts `info` -- must match the value passed to `encrypt`.
|
|
62
53
|
* @returns The decrypted plaintext bytes. Use `decrypt.asString` for a string.
|
|
63
54
|
*/
|
|
64
55
|
declare function decryptBytes(keypair: CryptoKeyPair, message: Uint8Array, opts?: {
|
|
@@ -70,7 +61,7 @@ declare function decryptBytes(keypair: CryptoKeyPair, message: Uint8Array, opts?
|
|
|
70
61
|
*
|
|
71
62
|
* @param keypair The same recipient `CryptoKeyPair` used to `encrypt`.
|
|
72
63
|
* @param message The envelope returned by `encrypt`.
|
|
73
|
-
* @param opts `info`
|
|
64
|
+
* @param opts `info` -- must match the value passed to `encrypt`.
|
|
74
65
|
* @returns The decrypted plaintext as a string.
|
|
75
66
|
*/
|
|
76
67
|
declare function decryptToString(keypair: CryptoKeyPair, message: Uint8Array, opts?: {
|
|
@@ -102,25 +93,55 @@ declare function decryptFromString(keypair: CryptoKeyPair, message: string, opts
|
|
|
102
93
|
* Like `open`, but returns the recovered key as raw bytes instead of
|
|
103
94
|
* importing it as an AES-GCM `CryptoKey`. Exposed as `open.raw`.
|
|
104
95
|
*
|
|
105
|
-
* @param keypair The same X25519 `CryptoKeyPair` used to create or
|
|
106
|
-
* @param wrapped The `
|
|
107
|
-
* @param opts `info`
|
|
96
|
+
* @param keypair The same X25519 `CryptoKeyPair` used 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`.
|
|
108
100
|
* @returns The recovered key bytes (16 or 32 bytes, matching whatever
|
|
109
101
|
* was wrapped).
|
|
110
102
|
*/
|
|
111
103
|
declare function openRawBytes(keypair: CryptoKeyPair, wrapped: Uint8Array, opts?: {
|
|
112
|
-
info
|
|
104
|
+
info?: Uint8Array | string;
|
|
113
105
|
}): Promise<Uint8Array>;
|
|
114
106
|
/**
|
|
115
|
-
* Recover an AES key that was wrapped with `create` or `
|
|
107
|
+
* Recover an AES key that was wrapped with `create` or `encryptKey`.
|
|
116
108
|
*
|
|
117
|
-
* @param keypair The same X25519 `CryptoKeyPair` used to create or
|
|
118
|
-
* @param wrapped The `
|
|
119
|
-
* @param opts `info`
|
|
120
|
-
*
|
|
109
|
+
* @param keypair The same X25519 `CryptoKeyPair` used to create or encryptKey.
|
|
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.
|
|
121
115
|
*/
|
|
122
116
|
declare function openBytes(keypair: CryptoKeyPair, wrapped: Uint8Array, opts?: {
|
|
123
|
-
info
|
|
117
|
+
info?: Uint8Array | string;
|
|
118
|
+
extractable?: boolean;
|
|
124
119
|
}): Promise<CryptoKey>;
|
|
125
|
-
|
|
120
|
+
/**
|
|
121
|
+
* Wrap an AES key to a recipient's public key. `create(...)` calls this
|
|
122
|
+
* helper with no `aesKey`, so it generates a fresh AES key first.
|
|
123
|
+
*
|
|
124
|
+
* @param recipient The recipient's X25519 public key, as a `CryptoKey`,
|
|
125
|
+
* `CryptoKeyPair` (its `.publicKey` is used), 32 raw bytes (`Uint8Array`),
|
|
126
|
+
* or `{ publicKey:string, encoding? }` (encoding defaults to `base64url`).
|
|
127
|
+
* @param aesKey Optional key to wrap, as either an AES-GCM `CryptoKey` or its
|
|
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.
|
|
132
|
+
* @param opts `size` (128/256, default 256; ignored when `aesKey` is
|
|
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`.
|
|
138
|
+
*/
|
|
139
|
+
export declare function encryptKey(recipient: RecipientKey, aesKey?: CryptoKey | Uint8Array | null, opts?: {
|
|
140
|
+
size?: 128 | 256;
|
|
141
|
+
info?: Uint8Array | string;
|
|
142
|
+
extractable?: boolean;
|
|
143
|
+
}): Promise<{
|
|
144
|
+
wrapped: Uint8Array;
|
|
145
|
+
key: CryptoKey;
|
|
146
|
+
}>;
|
|
126
147
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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,34 +1347,38 @@ async function aeadOpen(key, nonce, ciphertext) {
|
|
|
1332
1347
|
false,
|
|
1333
1348
|
["decrypt"]
|
|
1334
1349
|
);
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
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
|
-
|
|
1346
|
-
let key;
|
|
1347
|
-
if (!aesKey) {
|
|
1348
|
-
const keys = await create2(recipient, opts);
|
|
1349
|
-
enc = keys.enc;
|
|
1350
|
-
key = keys.key;
|
|
1351
|
-
} else {
|
|
1352
|
-
const keys = await seal(recipient, aesKey, opts);
|
|
1353
|
-
enc = keys.enc;
|
|
1354
|
-
key = keys.key;
|
|
1355
|
-
}
|
|
1369
|
+
const { wrapped, key } = await encryptKey(recipient, aesKey, opts);
|
|
1356
1370
|
const iv = globalThis.crypto.getRandomValues(new Uint8Array(NN));
|
|
1357
1371
|
const ct = new Uint8Array(await subtle.encrypt(
|
|
1358
1372
|
{ name: "AES-GCM", iv },
|
|
1359
1373
|
key,
|
|
1360
1374
|
plaintext
|
|
1361
1375
|
));
|
|
1362
|
-
return concat2(
|
|
1376
|
+
return concat2(
|
|
1377
|
+
i2osp(wrapped.length, WRAPPED_LEN_PREFIX),
|
|
1378
|
+
wrapped,
|
|
1379
|
+
iv,
|
|
1380
|
+
ct
|
|
1381
|
+
);
|
|
1363
1382
|
}
|
|
1364
1383
|
__name(encryptBytes, "encryptBytes");
|
|
1365
1384
|
async function encryptToString(recipient, message, aesKey, opts) {
|
|
@@ -1425,7 +1444,8 @@ async function exportRawPublic(key) {
|
|
|
1425
1444
|
__name(exportRawPublic, "exportRawPublic");
|
|
1426
1445
|
async function importRawPublic(raw) {
|
|
1427
1446
|
if (raw.length !== ENC_LENGTH) {
|
|
1428
|
-
throw new
|
|
1447
|
+
throw new HpkeError(
|
|
1448
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1429
1449
|
`invalid public key length: ${raw.length} bytes (expected ${ENC_LENGTH})`
|
|
1430
1450
|
);
|
|
1431
1451
|
}
|
|
@@ -1444,9 +1464,16 @@ async function resolveRecipientPublicKey(recipient) {
|
|
|
1444
1464
|
}
|
|
1445
1465
|
if (recipient instanceof CryptoKey) {
|
|
1446
1466
|
if (recipient.type !== "public") {
|
|
1447
|
-
throw new
|
|
1467
|
+
throw new HpkeError(
|
|
1468
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1469
|
+
"recipient CryptoKey must be a public key"
|
|
1470
|
+
);
|
|
1448
1471
|
}
|
|
1449
|
-
assertX25519Algorithm(
|
|
1472
|
+
assertX25519Algorithm(
|
|
1473
|
+
recipient,
|
|
1474
|
+
"recipient CryptoKey",
|
|
1475
|
+
"ERR_INVALID_RECIPIENT_KEY"
|
|
1476
|
+
);
|
|
1450
1477
|
return recipient;
|
|
1451
1478
|
}
|
|
1452
1479
|
if (typeof recipient.publicKey === "string") {
|
|
@@ -1456,17 +1483,28 @@ async function resolveRecipientPublicKey(recipient) {
|
|
|
1456
1483
|
const pair = recipient;
|
|
1457
1484
|
if (pair.publicKey instanceof CryptoKey) {
|
|
1458
1485
|
if (pair.publicKey.type !== "public") {
|
|
1459
|
-
throw new
|
|
1486
|
+
throw new HpkeError(
|
|
1487
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1488
|
+
"recipient keypair publicKey must be a public key"
|
|
1489
|
+
);
|
|
1460
1490
|
}
|
|
1461
|
-
assertX25519Algorithm(
|
|
1491
|
+
assertX25519Algorithm(
|
|
1492
|
+
pair.publicKey,
|
|
1493
|
+
"recipient keypair publicKey",
|
|
1494
|
+
"ERR_INVALID_RECIPIENT_KEY"
|
|
1495
|
+
);
|
|
1462
1496
|
return pair.publicKey;
|
|
1463
1497
|
}
|
|
1464
|
-
throw new
|
|
1498
|
+
throw new HpkeError(
|
|
1499
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1500
|
+
"unrecognized recipient key form"
|
|
1501
|
+
);
|
|
1465
1502
|
}
|
|
1466
1503
|
__name(resolveRecipientPublicKey, "resolveRecipientPublicKey");
|
|
1467
|
-
function assertX25519Algorithm(key, context) {
|
|
1504
|
+
function assertX25519Algorithm(key, context, code2) {
|
|
1468
1505
|
if (key.algorithm.name !== "X25519") {
|
|
1469
|
-
throw new
|
|
1506
|
+
throw new HpkeError(
|
|
1507
|
+
code2,
|
|
1470
1508
|
`${context} must be an X25519 key (got ${key.algorithm.name})`
|
|
1471
1509
|
);
|
|
1472
1510
|
}
|
|
@@ -1480,7 +1518,8 @@ async function dh(priv, pub) {
|
|
|
1480
1518
|
);
|
|
1481
1519
|
const bytes = new Uint8Array(bits);
|
|
1482
1520
|
if (bytes.every((b) => b === 0)) {
|
|
1483
|
-
throw new
|
|
1521
|
+
throw new HpkeError(
|
|
1522
|
+
"ERR_SMALL_ORDER_KEY",
|
|
1484
1523
|
"X25519 shared secret is all-zero (small-order public key?)"
|
|
1485
1524
|
);
|
|
1486
1525
|
}
|
|
@@ -1519,9 +1558,16 @@ async function encap(pkR) {
|
|
|
1519
1558
|
__name(encap, "encap");
|
|
1520
1559
|
async function decap(enc, keypair) {
|
|
1521
1560
|
if (keypair.privateKey.type !== "private") {
|
|
1522
|
-
throw new
|
|
1561
|
+
throw new HpkeError(
|
|
1562
|
+
"ERR_INVALID_KEYPAIR",
|
|
1563
|
+
"keypair.privateKey must be a private key"
|
|
1564
|
+
);
|
|
1523
1565
|
}
|
|
1524
|
-
assertX25519Algorithm(
|
|
1566
|
+
assertX25519Algorithm(
|
|
1567
|
+
keypair.privateKey,
|
|
1568
|
+
"keypair.privateKey",
|
|
1569
|
+
"ERR_INVALID_KEYPAIR"
|
|
1570
|
+
);
|
|
1525
1571
|
const pkE = await subtle.importKey(
|
|
1526
1572
|
"raw",
|
|
1527
1573
|
enc,
|
|
@@ -1535,6 +1581,35 @@ async function decap(enc, keypair) {
|
|
|
1535
1581
|
return extractAndExpand(dhBytes, kemContext);
|
|
1536
1582
|
}
|
|
1537
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");
|
|
1538
1613
|
function i2osp(n, len) {
|
|
1539
1614
|
const out = new Uint8Array(len);
|
|
1540
1615
|
let v = n;
|
|
@@ -1562,14 +1637,9 @@ __name(concat2, "concat");
|
|
|
1562
1637
|
var subtle2 = globalThis.crypto.subtle;
|
|
1563
1638
|
async function create2(recipient, opts) {
|
|
1564
1639
|
const { wrapped, key } = await encryptKey(recipient, null, opts);
|
|
1565
|
-
return {
|
|
1640
|
+
return { wrapped, key };
|
|
1566
1641
|
}
|
|
1567
1642
|
__name(create2, "create");
|
|
1568
|
-
async function seal(recipient, aesKey, opts) {
|
|
1569
|
-
const { wrapped, key } = await encryptKey(recipient, aesKey, opts);
|
|
1570
|
-
return { enc: wrapped, key };
|
|
1571
|
-
}
|
|
1572
|
-
__name(seal, "seal");
|
|
1573
1643
|
var open = Object.assign(openBytes, {
|
|
1574
1644
|
raw: openRawBytes
|
|
1575
1645
|
});
|
|
@@ -1582,13 +1652,19 @@ var decrypt = Object.assign(decryptBytes, {
|
|
|
1582
1652
|
});
|
|
1583
1653
|
async function decryptBytes(keypair, message, opts) {
|
|
1584
1654
|
if (message.byteLength < WRAPPED_LEN_PREFIX) {
|
|
1585
|
-
throw new
|
|
1655
|
+
throw new HpkeError(
|
|
1656
|
+
"ERR_MALFORMED_MESSAGE",
|
|
1657
|
+
"malformed message: too short"
|
|
1658
|
+
);
|
|
1586
1659
|
}
|
|
1587
1660
|
const wrappedLen = message[0] << 8 | message[1];
|
|
1588
1661
|
const ivStart = WRAPPED_LEN_PREFIX + wrappedLen;
|
|
1589
1662
|
const ctStart = ivStart + NN;
|
|
1590
1663
|
if (message.byteLength < ctStart + AEAD_TAG_LENGTH) {
|
|
1591
|
-
throw new
|
|
1664
|
+
throw new HpkeError(
|
|
1665
|
+
"ERR_MALFORMED_MESSAGE",
|
|
1666
|
+
"malformed message: too short"
|
|
1667
|
+
);
|
|
1592
1668
|
}
|
|
1593
1669
|
const wrapped = message.slice(WRAPPED_LEN_PREFIX, ivStart);
|
|
1594
1670
|
const iv = message.slice(ivStart, ctStart);
|
|
@@ -1599,11 +1675,20 @@ async function decryptBytes(keypair, message, opts) {
|
|
|
1599
1675
|
opts?.info !== void 0 ? { info: opts.info } : void 0
|
|
1600
1676
|
);
|
|
1601
1677
|
const key = await importAesKeyDecryptOnly(keyBytes);
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
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
|
+
}
|
|
1607
1692
|
return new Uint8Array(pt);
|
|
1608
1693
|
}
|
|
1609
1694
|
__name(decryptBytes, "decryptBytes");
|
|
@@ -1624,7 +1709,8 @@ async function decryptFromString(keypair, message, opts) {
|
|
|
1624
1709
|
__name(decryptFromString, "decryptFromString");
|
|
1625
1710
|
function validateKeysize(keysize) {
|
|
1626
1711
|
if (keysize !== 128 && keysize !== 256) {
|
|
1627
|
-
throw new
|
|
1712
|
+
throw new HpkeError(
|
|
1713
|
+
"ERR_INVALID_KEYSIZE",
|
|
1628
1714
|
`invalid keysize: ${keysize} (expected 128 or 256)`
|
|
1629
1715
|
);
|
|
1630
1716
|
}
|
|
@@ -1632,7 +1718,8 @@ function validateKeysize(keysize) {
|
|
|
1632
1718
|
__name(validateKeysize, "validateKeysize");
|
|
1633
1719
|
function validateRawKeyBytes(raw) {
|
|
1634
1720
|
if (raw.length !== 16 && raw.length !== 32) {
|
|
1635
|
-
throw new
|
|
1721
|
+
throw new HpkeError(
|
|
1722
|
+
"ERR_INVALID_AES_KEY",
|
|
1636
1723
|
`invalid aesKey length: ${raw.length} bytes (expected 16 or 32)`
|
|
1637
1724
|
);
|
|
1638
1725
|
}
|
|
@@ -1646,19 +1733,20 @@ function normalizeInfo(info) {
|
|
|
1646
1733
|
__name(normalizeInfo, "normalizeInfo");
|
|
1647
1734
|
async function exportAesKeyBytes(key) {
|
|
1648
1735
|
if (!key.extractable) {
|
|
1649
|
-
throw new
|
|
1736
|
+
throw new HpkeError(
|
|
1737
|
+
"ERR_INVALID_AES_KEY",
|
|
1650
1738
|
"aesKey must be extractable: its raw bytes are what get sealed"
|
|
1651
1739
|
);
|
|
1652
1740
|
}
|
|
1653
1741
|
return new Uint8Array(await subtle2.exportKey("raw", key));
|
|
1654
1742
|
}
|
|
1655
1743
|
__name(exportAesKeyBytes, "exportAesKeyBytes");
|
|
1656
|
-
async function importAesKey(raw) {
|
|
1744
|
+
async function importAesKey(raw, extractable) {
|
|
1657
1745
|
return subtle2.importKey(
|
|
1658
1746
|
"raw",
|
|
1659
1747
|
raw,
|
|
1660
1748
|
{ name: "AES-GCM" },
|
|
1661
|
-
|
|
1749
|
+
extractable,
|
|
1662
1750
|
["encrypt", "decrypt"]
|
|
1663
1751
|
);
|
|
1664
1752
|
}
|
|
@@ -1713,7 +1801,10 @@ async function keySchedule(sharedSecret, info) {
|
|
|
1713
1801
|
__name(keySchedule, "keySchedule");
|
|
1714
1802
|
async function openRawBytes(keypair, wrapped, opts) {
|
|
1715
1803
|
if (wrapped.byteLength < ENC_LENGTH + AEAD_TAG_LENGTH) {
|
|
1716
|
-
throw new
|
|
1804
|
+
throw new HpkeError(
|
|
1805
|
+
"ERR_MALFORMED_ENVELOPE",
|
|
1806
|
+
"malformed envelope: too short"
|
|
1807
|
+
);
|
|
1717
1808
|
}
|
|
1718
1809
|
const info = normalizeInfo(opts?.info);
|
|
1719
1810
|
const enc = wrapped.slice(0, ENC_LENGTH);
|
|
@@ -1725,7 +1816,7 @@ async function openRawBytes(keypair, wrapped, opts) {
|
|
|
1725
1816
|
__name(openRawBytes, "openRawBytes");
|
|
1726
1817
|
async function openBytes(keypair, wrapped, opts) {
|
|
1727
1818
|
const keyBytes = await openRawBytes(keypair, wrapped, opts);
|
|
1728
|
-
return importAesKey(keyBytes);
|
|
1819
|
+
return importAesKey(keyBytes, opts?.extractable ?? false);
|
|
1729
1820
|
}
|
|
1730
1821
|
__name(openBytes, "openBytes");
|
|
1731
1822
|
async function encryptKey(recipient, aesKey, opts) {
|
|
@@ -1749,15 +1840,20 @@ async function encryptKey(recipient, aesKey, opts) {
|
|
|
1749
1840
|
const { key, baseNonce } = await keySchedule(sharedSecret, info);
|
|
1750
1841
|
const ciphertext = await aeadSeal(key, baseNonce, keyBytes);
|
|
1751
1842
|
const wrapped = concat2(enc, ciphertext);
|
|
1752
|
-
const aesGcmKey = await importAesKey(
|
|
1843
|
+
const aesGcmKey = await importAesKey(
|
|
1844
|
+
keyBytes,
|
|
1845
|
+
opts?.extractable ?? false
|
|
1846
|
+
);
|
|
1753
1847
|
return { wrapped, key: aesGcmKey };
|
|
1754
1848
|
}
|
|
1755
1849
|
__name(encryptKey, "encryptKey");
|
|
1756
1850
|
export {
|
|
1851
|
+
HpkeError,
|
|
1757
1852
|
create2 as create,
|
|
1758
1853
|
decrypt,
|
|
1759
1854
|
encrypt,
|
|
1760
|
-
|
|
1761
|
-
|
|
1855
|
+
encryptKey,
|
|
1856
|
+
keyId,
|
|
1857
|
+
open
|
|
1762
1858
|
};
|
|
1763
1859
|
//# sourceMappingURL=index.js.map
|