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/util.d.ts
CHANGED
|
@@ -32,7 +32,11 @@ export declare function aeadOpen(key: Uint8Array, nonce: Uint8Array, ciphertext:
|
|
|
32
32
|
* @param message Plaintext to encrypt. A `string` is UTF-8 encoded.
|
|
33
33
|
* @param aesKey Optional key, as either an AES-GCM `CryptoKey` or its raw
|
|
34
34
|
* bytes (`Uint8Array`, 16 or 32 bytes). Omit to generate a fresh key of
|
|
35
|
-
* `opts.size` bits. A supplied `CryptoKey` MUST be extractable
|
|
35
|
+
* `opts.size` bits. A supplied `CryptoKey` MUST be extractable and
|
|
36
|
+
* MUST have the `encrypt` usage (the message is encrypted under it
|
|
37
|
+
* directly; otherwise this throws `ERR_INVALID_AES_KEY`). It is used
|
|
38
|
+
* as-is and never re-imported, so the `opts.extractable` setting that
|
|
39
|
+
* `encryptKey` takes does not apply to it. Each
|
|
36
40
|
* call picks a fresh random 96-bit IV for the message ciphertext, so
|
|
37
41
|
* reusing the same `aesKey` across many calls carries the standard
|
|
38
42
|
* birthday bound for random nonces (collision risk becomes
|
|
@@ -60,7 +64,11 @@ export declare function encryptBytes(recipient: RecipientKey, message: Uint8Arra
|
|
|
60
64
|
* @param message Plaintext to encrypt. A `string` is UTF-8 encoded.
|
|
61
65
|
* @param aesKey Optional key, as either an AES-GCM `CryptoKey` or its raw
|
|
62
66
|
* bytes (`Uint8Array`, 16 or 32 bytes). Omit to generate a fresh key of
|
|
63
|
-
* `opts.size` bits. A supplied `CryptoKey` MUST be extractable
|
|
67
|
+
* `opts.size` bits. A supplied `CryptoKey` MUST be extractable and
|
|
68
|
+
* MUST have the `encrypt` usage (the message is encrypted under it
|
|
69
|
+
* directly; otherwise this throws `ERR_INVALID_AES_KEY`). It is used
|
|
70
|
+
* as-is and never re-imported, so the `opts.extractable` setting that
|
|
71
|
+
* `encryptKey` takes does not apply to it. Each
|
|
64
72
|
* call picks a fresh random 96-bit IV for the message ciphertext, so
|
|
65
73
|
* reusing the same `aesKey` across many calls carries the standard
|
|
66
74
|
* birthday bound for random nonces (collision risk becomes
|
|
@@ -116,6 +124,20 @@ export declare function decap(enc: Uint8Array, keypair: CryptoKeyPair): Promise<
|
|
|
116
124
|
* or cannot encrypt.
|
|
117
125
|
*/
|
|
118
126
|
export declare function keyId(key: CryptoKey): Promise<string>;
|
|
127
|
+
/**
|
|
128
|
+
* Check whether `key` matches a stored `keyId`.
|
|
129
|
+
*
|
|
130
|
+
* Compares in constant time over the string. Errors from `keyId`
|
|
131
|
+
* propagate: a key that cannot be fingerprinted rejects rather than
|
|
132
|
+
* resolving `false`, so "cannot check" never reads as "wrong key".
|
|
133
|
+
*
|
|
134
|
+
* @param key An AES-GCM `CryptoKey` with the `encrypt` usage.
|
|
135
|
+
* @param expected A `keyId` string recorded earlier.
|
|
136
|
+
* @returns `true` if `keyId(key)` equals `expected`, else `false`.
|
|
137
|
+
* @throws {HpkeError} `ERR_INVALID_AES_KEY` if `key` is not an AES-GCM key
|
|
138
|
+
* or cannot encrypt.
|
|
139
|
+
*/
|
|
140
|
+
export declare function verifyKeyId(key: CryptoKey, expected: string): Promise<boolean>;
|
|
119
141
|
export declare function i2osp(n: number, len: number): Uint8Array;
|
|
120
142
|
export declare function concat(...arrays: Uint8Array[]): Uint8Array;
|
|
121
143
|
//# sourceMappingURL=util.d.ts.map
|
package/dist/util.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,OAAO,EAGH,KAAK,kBAAkB,EAC1B,MAAM,aAAa,CAAA;AAepB;;;;;;;;GAQG;AACH,MAAM,MAAM,YAAY,GAClB,SAAS,GACT,aAAa,GACb,UAAU,GACV;IAAE,SAAS,EAAC,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAC,kBAAkB,CAAA;CAAE,CAAA;AAExD;;GAEG;AACH,wBAAsB,QAAQ,CAC1B,GAAG,EAAC,UAAU,EACd,KAAK,EAAC,UAAU,EAChB,SAAS,EAAC,UAAU,GACtB,OAAO,CAAC,UAAU,CAAC,CAcpB;AAED,wBAAsB,QAAQ,CAC1B,GAAG,EAAC,UAAU,EACd,KAAK,EAAC,UAAU,EAChB,UAAU,EAAC,UAAU,GACvB,OAAO,CAAC,UAAU,CAAC,CA2BpB;AAED
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"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"}
|
package/dist/util.js
CHANGED
|
@@ -1381,7 +1381,16 @@ async function decryptToString(keypair, message, opts) {
|
|
|
1381
1381
|
}
|
|
1382
1382
|
__name(decryptToString, "decryptToString");
|
|
1383
1383
|
async function decryptFromString(keypair, message, opts) {
|
|
1384
|
-
|
|
1384
|
+
let bytes;
|
|
1385
|
+
try {
|
|
1386
|
+
bytes = fromString2(message, "base64url");
|
|
1387
|
+
} catch (err) {
|
|
1388
|
+
throw new HpkeError(
|
|
1389
|
+
"ERR_MALFORMED_MESSAGE",
|
|
1390
|
+
"malformed message: not base64url",
|
|
1391
|
+
{ cause: err }
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1385
1394
|
const plaintext = await decryptBytes(
|
|
1386
1395
|
keypair,
|
|
1387
1396
|
bytes,
|
|
@@ -1509,6 +1518,12 @@ async function encryptKey(recipient, aesKey, opts) {
|
|
|
1509
1518
|
validateRawKeyBytes(aesKey);
|
|
1510
1519
|
keyBytes = aesKey;
|
|
1511
1520
|
} else if (aesKey) {
|
|
1521
|
+
if (aesKey.algorithm.name !== "AES-GCM") {
|
|
1522
|
+
throw new HpkeError(
|
|
1523
|
+
"ERR_INVALID_AES_KEY",
|
|
1524
|
+
"aesKey must be an AES-GCM key, got " + aesKey.algorithm.name
|
|
1525
|
+
);
|
|
1526
|
+
}
|
|
1512
1527
|
keyBytes = await exportAesKeyBytes(aesKey);
|
|
1513
1528
|
validateRawKeyBytes(keyBytes);
|
|
1514
1529
|
} else {
|
|
@@ -1523,6 +1538,7 @@ async function encryptKey(recipient, aesKey, opts) {
|
|
|
1523
1538
|
const { key, baseNonce } = await keySchedule(sharedSecret, info);
|
|
1524
1539
|
const ciphertext = await aeadSeal(key, baseNonce, keyBytes);
|
|
1525
1540
|
const wrapped = concat2(enc, ciphertext);
|
|
1541
|
+
if (aesKey instanceof CryptoKey) return { wrapped, key: aesKey };
|
|
1526
1542
|
const aesGcmKey = await importAesKey(
|
|
1527
1543
|
keyBytes,
|
|
1528
1544
|
opts?.extractable ?? false
|
|
@@ -1575,6 +1591,12 @@ async function aeadOpen(key, nonce, ciphertext) {
|
|
|
1575
1591
|
}
|
|
1576
1592
|
__name(aeadOpen, "aeadOpen");
|
|
1577
1593
|
async function encryptBytes(recipient, message, aesKey, opts) {
|
|
1594
|
+
if (aesKey instanceof CryptoKey && !aesKey.usages.includes("encrypt")) {
|
|
1595
|
+
throw new HpkeError(
|
|
1596
|
+
"ERR_INVALID_AES_KEY",
|
|
1597
|
+
'aesKey is missing the "encrypt" usage, which encrypt needs to encrypt the message'
|
|
1598
|
+
);
|
|
1599
|
+
}
|
|
1578
1600
|
const plaintext = typeof message === "string" ? new TextEncoder().encode(message) : message;
|
|
1579
1601
|
const { wrapped, key } = await encryptKey(recipient, aesKey, opts);
|
|
1580
1602
|
const iv = globalThis.crypto.getRandomValues(new Uint8Array(NN));
|
|
@@ -1673,37 +1695,28 @@ async function resolveRecipientPublicKey(recipient) {
|
|
|
1673
1695
|
return importRawPublic(recipient);
|
|
1674
1696
|
}
|
|
1675
1697
|
if (recipient instanceof CryptoKey) {
|
|
1676
|
-
|
|
1677
|
-
throw new HpkeError(
|
|
1678
|
-
"ERR_INVALID_RECIPIENT_KEY",
|
|
1679
|
-
"recipient CryptoKey must be a public key"
|
|
1680
|
-
);
|
|
1681
|
-
}
|
|
1682
|
-
assertX25519Algorithm(
|
|
1683
|
-
recipient,
|
|
1684
|
-
"recipient CryptoKey",
|
|
1685
|
-
"ERR_INVALID_RECIPIENT_KEY"
|
|
1686
|
-
);
|
|
1687
|
-
return recipient;
|
|
1698
|
+
return assertRecipientPublicKey(recipient, "recipient CryptoKey");
|
|
1688
1699
|
}
|
|
1689
1700
|
if (typeof recipient.publicKey === "string") {
|
|
1690
1701
|
const { publicKey, encoding } = recipient;
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
if (pair.publicKey.type !== "public") {
|
|
1702
|
+
let decoded;
|
|
1703
|
+
try {
|
|
1704
|
+
decoded = fromString2(publicKey, encoding ?? "base64url");
|
|
1705
|
+
} catch (err) {
|
|
1696
1706
|
throw new HpkeError(
|
|
1697
1707
|
"ERR_INVALID_RECIPIENT_KEY",
|
|
1698
|
-
|
|
1708
|
+
`recipient publicKey string does not decode as ${encoding ?? "base64url"}`,
|
|
1709
|
+
{ cause: err }
|
|
1699
1710
|
);
|
|
1700
1711
|
}
|
|
1701
|
-
|
|
1712
|
+
return importRawPublic(decoded);
|
|
1713
|
+
}
|
|
1714
|
+
const pair = recipient;
|
|
1715
|
+
if (pair.publicKey instanceof CryptoKey) {
|
|
1716
|
+
return assertRecipientPublicKey(
|
|
1702
1717
|
pair.publicKey,
|
|
1703
|
-
"recipient keypair publicKey"
|
|
1704
|
-
"ERR_INVALID_RECIPIENT_KEY"
|
|
1718
|
+
"recipient keypair publicKey"
|
|
1705
1719
|
);
|
|
1706
|
-
return pair.publicKey;
|
|
1707
1720
|
}
|
|
1708
1721
|
throw new HpkeError(
|
|
1709
1722
|
"ERR_INVALID_RECIPIENT_KEY",
|
|
@@ -1711,6 +1724,23 @@ async function resolveRecipientPublicKey(recipient) {
|
|
|
1711
1724
|
);
|
|
1712
1725
|
}
|
|
1713
1726
|
__name(resolveRecipientPublicKey, "resolveRecipientPublicKey");
|
|
1727
|
+
function assertRecipientPublicKey(key, context) {
|
|
1728
|
+
if (key.type !== "public") {
|
|
1729
|
+
throw new HpkeError(
|
|
1730
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1731
|
+
`${context} must be a public key`
|
|
1732
|
+
);
|
|
1733
|
+
}
|
|
1734
|
+
assertX25519Algorithm(key, context, "ERR_INVALID_RECIPIENT_KEY");
|
|
1735
|
+
if (!key.extractable) {
|
|
1736
|
+
throw new HpkeError(
|
|
1737
|
+
"ERR_INVALID_RECIPIENT_KEY",
|
|
1738
|
+
`${context} must be extractable (the KEM exports it)`
|
|
1739
|
+
);
|
|
1740
|
+
}
|
|
1741
|
+
return key;
|
|
1742
|
+
}
|
|
1743
|
+
__name(assertRecipientPublicKey, "assertRecipientPublicKey");
|
|
1714
1744
|
function assertX25519Algorithm(key, context, code2) {
|
|
1715
1745
|
if (key.algorithm.name !== "X25519") {
|
|
1716
1746
|
throw new HpkeError(
|
|
@@ -1721,11 +1751,23 @@ function assertX25519Algorithm(key, context, code2) {
|
|
|
1721
1751
|
}
|
|
1722
1752
|
__name(assertX25519Algorithm, "assertX25519Algorithm");
|
|
1723
1753
|
async function dh(priv, pub) {
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1754
|
+
let bits;
|
|
1755
|
+
try {
|
|
1756
|
+
bits = await subtle2.deriveBits(
|
|
1757
|
+
{ name: "X25519", public: pub },
|
|
1758
|
+
priv,
|
|
1759
|
+
256
|
|
1760
|
+
);
|
|
1761
|
+
} catch (err) {
|
|
1762
|
+
if (err?.name === "OperationError") {
|
|
1763
|
+
throw new HpkeError(
|
|
1764
|
+
"ERR_SMALL_ORDER_KEY",
|
|
1765
|
+
"X25519 deriveBits rejected a small-order public key",
|
|
1766
|
+
{ cause: err }
|
|
1767
|
+
);
|
|
1768
|
+
}
|
|
1769
|
+
throw err;
|
|
1770
|
+
}
|
|
1729
1771
|
const bytes = new Uint8Array(bits);
|
|
1730
1772
|
if (bytes.every((b) => b === 0)) {
|
|
1731
1773
|
throw new HpkeError(
|
|
@@ -1778,6 +1820,23 @@ async function decap(enc, keypair) {
|
|
|
1778
1820
|
"keypair.privateKey",
|
|
1779
1821
|
"ERR_INVALID_KEYPAIR"
|
|
1780
1822
|
);
|
|
1823
|
+
if (!keypair.privateKey.usages.includes("deriveBits")) {
|
|
1824
|
+
throw new HpkeError(
|
|
1825
|
+
"ERR_INVALID_KEYPAIR",
|
|
1826
|
+
"keypair.privateKey must have the deriveBits usage"
|
|
1827
|
+
);
|
|
1828
|
+
}
|
|
1829
|
+
if (keypair.publicKey.type !== "public" || !keypair.publicKey.extractable) {
|
|
1830
|
+
throw new HpkeError(
|
|
1831
|
+
"ERR_INVALID_KEYPAIR",
|
|
1832
|
+
"keypair.publicKey must be an extractable public key"
|
|
1833
|
+
);
|
|
1834
|
+
}
|
|
1835
|
+
assertX25519Algorithm(
|
|
1836
|
+
keypair.publicKey,
|
|
1837
|
+
"keypair.publicKey",
|
|
1838
|
+
"ERR_INVALID_KEYPAIR"
|
|
1839
|
+
);
|
|
1781
1840
|
const pkE = await subtle2.importKey(
|
|
1782
1841
|
"raw",
|
|
1783
1842
|
enc,
|
|
@@ -1820,6 +1879,16 @@ async function keyId(key) {
|
|
|
1820
1879
|
return toString2(new Uint8Array(digest2), "base64url");
|
|
1821
1880
|
}
|
|
1822
1881
|
__name(keyId, "keyId");
|
|
1882
|
+
async function verifyKeyId(key, expected) {
|
|
1883
|
+
const actual = await keyId(key);
|
|
1884
|
+
if (actual.length !== expected.length) return false;
|
|
1885
|
+
let diff = 0;
|
|
1886
|
+
for (let i = 0; i < actual.length; i++) {
|
|
1887
|
+
diff |= actual.charCodeAt(i) ^ expected.charCodeAt(i);
|
|
1888
|
+
}
|
|
1889
|
+
return diff === 0;
|
|
1890
|
+
}
|
|
1891
|
+
__name(verifyKeyId, "verifyKeyId");
|
|
1823
1892
|
function i2osp(n, len) {
|
|
1824
1893
|
const out = new Uint8Array(len);
|
|
1825
1894
|
let v = n;
|
|
@@ -1855,6 +1924,7 @@ export {
|
|
|
1855
1924
|
keyId,
|
|
1856
1925
|
labeledExpand,
|
|
1857
1926
|
labeledExtract,
|
|
1858
|
-
resolveRecipientPublicKey
|
|
1927
|
+
resolveRecipientPublicKey,
|
|
1928
|
+
verifyKeyId
|
|
1859
1929
|
};
|
|
1860
1930
|
//# sourceMappingURL=util.js.map
|