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