simple-hpke 0.3.1 → 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 +82 -14
- package/dist/constants.cjs +87 -28
- package/dist/constants.cjs.map +2 -2
- package/dist/constants.js +87 -28
- package/dist/constants.js.map +2 -2
- package/dist/constants.min.cjs +1 -1
- package/dist/constants.min.cjs.map +3 -3
- package/dist/constants.min.js +1 -1
- package/dist/constants.min.js.map +3 -3
- package/dist/index.cjs +99 -29
- package/dist/index.cjs.map +2 -2
- package/dist/index.d.ts +10 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +99 -29
- package/dist/index.js.map +2 -2
- package/dist/index.min.cjs +1 -1
- package/dist/index.min.cjs.map +3 -3
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +3 -3
- package/dist/meta.json +18 -16
- package/dist/util.cjs +99 -29
- package/dist/util.cjs.map +2 -2
- package/dist/util.d.ts +24 -2
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +99 -29
- package/dist/util.js.map +2 -2
- package/dist/util.min.cjs +1 -1
- package/dist/util.min.cjs.map +3 -3
- package/dist/util.min.js +1 -1
- package/dist/util.min.js.map +3 -3
- package/package.json +5 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type RecipientKey, encryptToString, encryptBytes, keyId } from './util';
|
|
2
|
-
export { keyId };
|
|
1
|
+
import { type RecipientKey, encryptToString, encryptBytes, keyId, verifyKeyId } from './util';
|
|
2
|
+
export { keyId, verifyKeyId };
|
|
3
3
|
export { HpkeError, type HpkeErrorCode } from './errors';
|
|
4
4
|
/**
|
|
5
5
|
* Create a new AES key for the given public key.
|
|
@@ -127,14 +127,17 @@ declare function openBytes(keypair: CryptoKeyPair, wrapped: Uint8Array, opts?: {
|
|
|
127
127
|
* @param aesKey Optional key to wrap, as either an AES-GCM `CryptoKey` or its
|
|
128
128
|
* raw bytes (`Uint8Array`, 16 or 32 bytes). Omit to generate a fresh key
|
|
129
129
|
* of `opts.size` bits. A supplied `CryptoKey` MUST be extractable (its
|
|
130
|
-
* raw bytes are wrapped)
|
|
131
|
-
* `
|
|
130
|
+
* raw bytes are wrapped) and is returned as-is: the same object, with
|
|
131
|
+
* its own `extractable` and usages. It does not need the `encrypt`
|
|
132
|
+
* usage, since wrapping never encrypts with it.
|
|
132
133
|
* @param opts `size` (128/256, default 256; ignored when `aesKey` is
|
|
133
134
|
* supplied), `info` (bound into the HPKE key schedule; default empty),
|
|
134
|
-
* and `extractable` (whether
|
|
135
|
-
*
|
|
135
|
+
* and `extractable` (whether a key this function imports -- a
|
|
136
|
+
* generated key or raw `Uint8Array` bytes -- can be exported; default
|
|
137
|
+
* false). `extractable` does not apply to a supplied `CryptoKey`.
|
|
136
138
|
* @returns { wrapped:Uint8Array, key:CryptoKey } The wrapped envelope
|
|
137
|
-
* bytes and
|
|
139
|
+
* bytes and the AES-GCM `CryptoKey`: the supplied `CryptoKey` itself
|
|
140
|
+
* when one was given, otherwise a newly imported key.
|
|
138
141
|
*/
|
|
139
142
|
export declare function encryptKey(recipient: RecipientKey, aesKey?: CryptoKey | Uint8Array | null, opts?: {
|
|
140
143
|
size?: 128 | 256;
|
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,EAOZ,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1365,6 +1365,12 @@ async function aeadOpen(key, nonce, ciphertext) {
|
|
|
1365
1365
|
}
|
|
1366
1366
|
__name(aeadOpen, "aeadOpen");
|
|
1367
1367
|
async function encryptBytes(recipient, message, aesKey, opts) {
|
|
1368
|
+
if (aesKey instanceof CryptoKey && !aesKey.usages.includes("encrypt")) {
|
|
1369
|
+
throw new HpkeError(
|
|
1370
|
+
"ERR_INVALID_AES_KEY",
|
|
1371
|
+
'aesKey is missing the "encrypt" usage, which encrypt needs to encrypt the message'
|
|
1372
|
+
);
|
|
1373
|
+
}
|
|
1368
1374
|
const plaintext = typeof message === "string" ? new TextEncoder().encode(message) : message;
|
|
1369
1375
|
const { wrapped, key } = await encryptKey(recipient, aesKey, opts);
|
|
1370
1376
|
const iv = globalThis.crypto.getRandomValues(new Uint8Array(NN));
|
|
@@ -1463,37 +1469,28 @@ async function resolveRecipientPublicKey(recipient) {
|
|
|
1463
1469
|
return importRawPublic(recipient);
|
|
1464
1470
|
}
|
|
1465
1471
|
if (recipient instanceof CryptoKey) {
|
|
1466
|
-
|
|
1467
|
-
throw new HpkeError(
|
|
1468
|
-
"ERR_INVALID_RECIPIENT_KEY",
|
|
1469
|
-
"recipient CryptoKey must be a public key"
|
|
1470
|
-
);
|
|
1471
|
-
}
|
|
1472
|
-
assertX25519Algorithm(
|
|
1473
|
-
recipient,
|
|
1474
|
-
"recipient CryptoKey",
|
|
1475
|
-
"ERR_INVALID_RECIPIENT_KEY"
|
|
1476
|
-
);
|
|
1477
|
-
return recipient;
|
|
1472
|
+
return assertRecipientPublicKey(recipient, "recipient CryptoKey");
|
|
1478
1473
|
}
|
|
1479
1474
|
if (typeof recipient.publicKey === "string") {
|
|
1480
1475
|
const { publicKey, encoding } = recipient;
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
if (pair.publicKey.type !== "public") {
|
|
1476
|
+
let decoded;
|
|
1477
|
+
try {
|
|
1478
|
+
decoded = fromString2(publicKey, encoding ?? "base64url");
|
|
1479
|
+
} catch (err) {
|
|
1486
1480
|
throw new HpkeError(
|
|
1487
1481
|
"ERR_INVALID_RECIPIENT_KEY",
|
|
1488
|
-
|
|
1482
|
+
`recipient publicKey string does not decode as ${encoding ?? "base64url"}`,
|
|
1483
|
+
{ cause: err }
|
|
1489
1484
|
);
|
|
1490
1485
|
}
|
|
1491
|
-
|
|
1486
|
+
return importRawPublic(decoded);
|
|
1487
|
+
}
|
|
1488
|
+
const pair = recipient;
|
|
1489
|
+
if (pair.publicKey instanceof CryptoKey) {
|
|
1490
|
+
return assertRecipientPublicKey(
|
|
1492
1491
|
pair.publicKey,
|
|
1493
|
-
"recipient keypair publicKey"
|
|
1494
|
-
"ERR_INVALID_RECIPIENT_KEY"
|
|
1492
|
+
"recipient keypair publicKey"
|
|
1495
1493
|
);
|
|
1496
|
-
return pair.publicKey;
|
|
1497
1494
|
}
|
|
1498
1495
|
throw new HpkeError(
|
|
1499
1496
|
"ERR_INVALID_RECIPIENT_KEY",
|
|
@@ -1501,6 +1498,23 @@ async function resolveRecipientPublicKey(recipient) {
|
|
|
1501
1498
|
);
|
|
1502
1499
|
}
|
|
1503
1500
|
__name(resolveRecipientPublicKey, "resolveRecipientPublicKey");
|
|
1501
|
+
function assertRecipientPublicKey(key, context) {
|
|
1502
|
+
if (key.type !== "public") {
|
|
1503
|
+
throw new HpkeError(
|
|
1504
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1505
|
+
`${context} must be a public key`
|
|
1506
|
+
);
|
|
1507
|
+
}
|
|
1508
|
+
assertX25519Algorithm(key, context, "ERR_INVALID_RECIPIENT_KEY");
|
|
1509
|
+
if (!key.extractable) {
|
|
1510
|
+
throw new HpkeError(
|
|
1511
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1512
|
+
`${context} must be extractable (the KEM exports it)`
|
|
1513
|
+
);
|
|
1514
|
+
}
|
|
1515
|
+
return key;
|
|
1516
|
+
}
|
|
1517
|
+
__name(assertRecipientPublicKey, "assertRecipientPublicKey");
|
|
1504
1518
|
function assertX25519Algorithm(key, context, code2) {
|
|
1505
1519
|
if (key.algorithm.name !== "X25519") {
|
|
1506
1520
|
throw new HpkeError(
|
|
@@ -1511,11 +1525,23 @@ function assertX25519Algorithm(key, context, code2) {
|
|
|
1511
1525
|
}
|
|
1512
1526
|
__name(assertX25519Algorithm, "assertX25519Algorithm");
|
|
1513
1527
|
async function dh(priv, pub) {
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1528
|
+
let bits;
|
|
1529
|
+
try {
|
|
1530
|
+
bits = await subtle.deriveBits(
|
|
1531
|
+
{ name: "X25519", public: pub },
|
|
1532
|
+
priv,
|
|
1533
|
+
256
|
|
1534
|
+
);
|
|
1535
|
+
} catch (err) {
|
|
1536
|
+
if (err?.name === "OperationError") {
|
|
1537
|
+
throw new HpkeError(
|
|
1538
|
+
"ERR_SMALL_ORDER_KEY",
|
|
1539
|
+
"X25519 deriveBits rejected a small-order public key",
|
|
1540
|
+
{ cause: err }
|
|
1541
|
+
);
|
|
1542
|
+
}
|
|
1543
|
+
throw err;
|
|
1544
|
+
}
|
|
1519
1545
|
const bytes = new Uint8Array(bits);
|
|
1520
1546
|
if (bytes.every((b) => b === 0)) {
|
|
1521
1547
|
throw new HpkeError(
|
|
@@ -1568,6 +1594,23 @@ async function decap(enc, keypair) {
|
|
|
1568
1594
|
"keypair.privateKey",
|
|
1569
1595
|
"ERR_INVALID_KEYPAIR"
|
|
1570
1596
|
);
|
|
1597
|
+
if (!keypair.privateKey.usages.includes("deriveBits")) {
|
|
1598
|
+
throw new HpkeError(
|
|
1599
|
+
"ERR_INVALID_KEYPAIR",
|
|
1600
|
+
"keypair.privateKey must have the deriveBits usage"
|
|
1601
|
+
);
|
|
1602
|
+
}
|
|
1603
|
+
if (keypair.publicKey.type !== "public" || !keypair.publicKey.extractable) {
|
|
1604
|
+
throw new HpkeError(
|
|
1605
|
+
"ERR_INVALID_KEYPAIR",
|
|
1606
|
+
"keypair.publicKey must be an extractable public key"
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1609
|
+
assertX25519Algorithm(
|
|
1610
|
+
keypair.publicKey,
|
|
1611
|
+
"keypair.publicKey",
|
|
1612
|
+
"ERR_INVALID_KEYPAIR"
|
|
1613
|
+
);
|
|
1571
1614
|
const pkE = await subtle.importKey(
|
|
1572
1615
|
"raw",
|
|
1573
1616
|
enc,
|
|
@@ -1610,6 +1653,16 @@ async function keyId(key) {
|
|
|
1610
1653
|
return toString2(new Uint8Array(digest2), "base64url");
|
|
1611
1654
|
}
|
|
1612
1655
|
__name(keyId, "keyId");
|
|
1656
|
+
async function verifyKeyId(key, expected) {
|
|
1657
|
+
const actual = await keyId(key);
|
|
1658
|
+
if (actual.length !== expected.length) return false;
|
|
1659
|
+
let diff = 0;
|
|
1660
|
+
for (let i = 0; i < actual.length; i++) {
|
|
1661
|
+
diff |= actual.charCodeAt(i) ^ expected.charCodeAt(i);
|
|
1662
|
+
}
|
|
1663
|
+
return diff === 0;
|
|
1664
|
+
}
|
|
1665
|
+
__name(verifyKeyId, "verifyKeyId");
|
|
1613
1666
|
function i2osp(n, len) {
|
|
1614
1667
|
const out = new Uint8Array(len);
|
|
1615
1668
|
let v = n;
|
|
@@ -1698,7 +1751,16 @@ async function decryptToString(keypair, message, opts) {
|
|
|
1698
1751
|
}
|
|
1699
1752
|
__name(decryptToString, "decryptToString");
|
|
1700
1753
|
async function decryptFromString(keypair, message, opts) {
|
|
1701
|
-
|
|
1754
|
+
let bytes;
|
|
1755
|
+
try {
|
|
1756
|
+
bytes = fromString2(message, "base64url");
|
|
1757
|
+
} catch (err) {
|
|
1758
|
+
throw new HpkeError(
|
|
1759
|
+
"ERR_MALFORMED_MESSAGE",
|
|
1760
|
+
"malformed message: not base64url",
|
|
1761
|
+
{ cause: err }
|
|
1762
|
+
);
|
|
1763
|
+
}
|
|
1702
1764
|
const plaintext = await decryptBytes(
|
|
1703
1765
|
keypair,
|
|
1704
1766
|
bytes,
|
|
@@ -1826,6 +1888,12 @@ async function encryptKey(recipient, aesKey, opts) {
|
|
|
1826
1888
|
validateRawKeyBytes(aesKey);
|
|
1827
1889
|
keyBytes = aesKey;
|
|
1828
1890
|
} else if (aesKey) {
|
|
1891
|
+
if (aesKey.algorithm.name !== "AES-GCM") {
|
|
1892
|
+
throw new HpkeError(
|
|
1893
|
+
"ERR_INVALID_AES_KEY",
|
|
1894
|
+
"aesKey must be an AES-GCM key, got " + aesKey.algorithm.name
|
|
1895
|
+
);
|
|
1896
|
+
}
|
|
1829
1897
|
keyBytes = await exportAesKeyBytes(aesKey);
|
|
1830
1898
|
validateRawKeyBytes(keyBytes);
|
|
1831
1899
|
} else {
|
|
@@ -1840,6 +1908,7 @@ async function encryptKey(recipient, aesKey, opts) {
|
|
|
1840
1908
|
const { key, baseNonce } = await keySchedule(sharedSecret, info);
|
|
1841
1909
|
const ciphertext = await aeadSeal(key, baseNonce, keyBytes);
|
|
1842
1910
|
const wrapped = concat2(enc, ciphertext);
|
|
1911
|
+
if (aesKey instanceof CryptoKey) return { wrapped, key: aesKey };
|
|
1843
1912
|
const aesGcmKey = await importAesKey(
|
|
1844
1913
|
keyBytes,
|
|
1845
1914
|
opts?.extractable ?? false
|
|
@@ -1854,6 +1923,7 @@ export {
|
|
|
1854
1923
|
encrypt,
|
|
1855
1924
|
encryptKey,
|
|
1856
1925
|
keyId,
|
|
1857
|
-
open
|
|
1926
|
+
open,
|
|
1927
|
+
verifyKeyId
|
|
1858
1928
|
};
|
|
1859
1929
|
//# sourceMappingURL=index.js.map
|