@hpke/jose 0.0.0-stage → 0.0.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/LICENSE +21 -0
- package/README.md +111 -2
- package/esm/mod.d.ts +17 -0
- package/esm/mod.d.ts.map +1 -0
- package/esm/mod.js +11 -0
- package/esm/package.json +4 -0
- package/esm/src/alg.d.ts +58 -0
- package/esm/src/alg.d.ts.map +1 -0
- package/esm/src/alg.js +97 -0
- package/esm/src/encrypt.d.ts +83 -0
- package/esm/src/encrypt.d.ts.map +1 -0
- package/esm/src/encrypt.js +228 -0
- package/esm/src/encrypt0.d.ts +61 -0
- package/esm/src/encrypt0.d.ts.map +1 -0
- package/esm/src/encrypt0.js +125 -0
- package/esm/src/errors.d.ts +7 -0
- package/esm/src/errors.d.ts.map +1 -0
- package/esm/src/errors.js +18 -0
- package/esm/src/hpke0.d.ts +16 -0
- package/esm/src/hpke0.d.ts.map +1 -0
- package/esm/src/hpke0.js +30 -0
- package/esm/src/hpke1.d.ts +16 -0
- package/esm/src/hpke1.d.ts.map +1 -0
- package/esm/src/hpke1.js +30 -0
- package/esm/src/hpke2.d.ts +16 -0
- package/esm/src/hpke2.d.ts.map +1 -0
- package/esm/src/hpke2.js +30 -0
- package/esm/src/hpke3.d.ts +16 -0
- package/esm/src/hpke3.d.ts.map +1 -0
- package/esm/src/hpke3.js +30 -0
- package/esm/src/hpke4.d.ts +4 -0
- package/esm/src/hpke4.d.ts.map +1 -0
- package/esm/src/hpke4.js +13 -0
- package/esm/src/hpke5.d.ts +16 -0
- package/esm/src/hpke5.d.ts.map +1 -0
- package/esm/src/hpke5.js +31 -0
- package/esm/src/hpke6.d.ts +4 -0
- package/esm/src/hpke6.d.ts.map +1 -0
- package/esm/src/hpke6.js +14 -0
- package/esm/src/hpke7.d.ts +16 -0
- package/esm/src/hpke7.d.ts.map +1 -0
- package/esm/src/hpke7.js +30 -0
- package/esm/src/jwk.d.ts +18 -0
- package/esm/src/jwk.d.ts.map +1 -0
- package/esm/src/jwk.js +77 -0
- package/esm/src/serialization.d.ts +61 -0
- package/esm/src/serialization.d.ts.map +1 -0
- package/esm/src/serialization.js +107 -0
- package/esm/src/structures.d.ts +14 -0
- package/esm/src/structures.d.ts.map +1 -0
- package/esm/src/structures.js +31 -0
- package/esm/src/utils.d.ts +5 -0
- package/esm/src/utils.d.ts.map +1 -0
- package/esm/src/utils.js +26 -0
- package/package.json +49 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Ajitomi Daisuke
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,112 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @hpke/jose
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A TypeScript implementation of
|
|
4
|
+
[JOSE-HPKE](https://datatracker.ietf.org/doc/draft-ietf-jose-hpke-encrypt/)
|
|
5
|
+
encryption based on [@hpke/core](https://github.com/dajiaji/hpke-js).
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **JWE Integrated Encryption** — direct HPKE encryption for single-recipient
|
|
10
|
+
JWE.
|
|
11
|
+
- **JWE Key Encryption** — HPKE key wrapping with standard JWE content
|
|
12
|
+
encryption (A128GCM, A256GCM) for multi-recipient JWE.
|
|
13
|
+
- `seal()` produces the Compact Serialization for Integrated Encryption and the
|
|
14
|
+
General JSON Serialization for Key Encryption. `open()` accepts the Compact,
|
|
15
|
+
Flattened JSON and General JSON Serializations for both.
|
|
16
|
+
- PSK mode. The `psk_id` header parameter carries `BASE64URL(id)`.
|
|
17
|
+
|
|
18
|
+
## Supported Environments
|
|
19
|
+
|
|
20
|
+
This package is distributed as ESM only. On Node.js, version 20 or later is
|
|
21
|
+
required.
|
|
22
|
+
|
|
23
|
+
## Supported Algorithms
|
|
24
|
+
|
|
25
|
+
Implements
|
|
26
|
+
[draft-ietf-jose-hpke-encrypt-22](https://datatracker.ietf.org/doc/html/draft-ietf-jose-hpke-encrypt-22).
|
|
27
|
+
|
|
28
|
+
| Integrated | Key Encryption | KEM | KDF | AEAD |
|
|
29
|
+
| ---------- | -------------- | -------------------------- | ----------- | ---------------- |
|
|
30
|
+
| HPKE-0 | HPKE-0-KE | DHKEM(P-256, HKDF-SHA256) | HKDF-SHA256 | AES-128-GCM |
|
|
31
|
+
| HPKE-1 | HPKE-1-KE | DHKEM(P-384, HKDF-SHA384) | HKDF-SHA384 | AES-256-GCM |
|
|
32
|
+
| HPKE-2 | HPKE-2-KE | DHKEM(P-521, HKDF-SHA512) | HKDF-SHA512 | AES-256-GCM |
|
|
33
|
+
| HPKE-3 | HPKE-3-KE | DHKEM(X25519, HKDF-SHA256) | HKDF-SHA256 | AES-128-GCM |
|
|
34
|
+
| HPKE-4 | - | DHKEM(X25519, HKDF-SHA256) | HKDF-SHA256 | ChaCha20Poly1305 |
|
|
35
|
+
| HPKE-5 | HPKE-5-KE | DHKEM(X448, HKDF-SHA512) | HKDF-SHA512 | AES-256-GCM |
|
|
36
|
+
| HPKE-6 | - | DHKEM(X448, HKDF-SHA512) | HKDF-SHA512 | ChaCha20Poly1305 |
|
|
37
|
+
| HPKE-7 | HPKE-7-KE | DHKEM(P-256, HKDF-SHA256) | HKDF-SHA256 | AES-256-GCM |
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
### Integrated Encryption (Compact JWE)
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { createJoseHpke3 } from "@hpke/jose";
|
|
45
|
+
|
|
46
|
+
const enc0 = createJoseHpke3();
|
|
47
|
+
const rkp = await enc0.suite.kem.generateKeyPair();
|
|
48
|
+
|
|
49
|
+
const plaintext = new TextEncoder().encode("Hello, JOSE-HPKE!");
|
|
50
|
+
const jwe = await enc0.seal(rkp.publicKey, plaintext);
|
|
51
|
+
|
|
52
|
+
const pt = await enc0.open(rkp, jwe);
|
|
53
|
+
// pt === plaintext
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Key Encryption (JSON JWE) with A128GCM
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { Aes128Gcm, createJoseHpke3Ke } from "@hpke/jose";
|
|
60
|
+
|
|
61
|
+
const enc = createJoseHpke3Ke(new Aes128Gcm());
|
|
62
|
+
const rkp = await enc.generateKemKeyPair();
|
|
63
|
+
|
|
64
|
+
const plaintext = new TextEncoder().encode("Hello, JOSE-HPKE KE!");
|
|
65
|
+
const jwe = await enc.seal(
|
|
66
|
+
[{ recipientPublicKey: rkp.publicKey }],
|
|
67
|
+
plaintext,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const pt = await enc.open(rkp, jwe);
|
|
71
|
+
// pt === plaintext
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The factory selects the HPKE suite for key wrapping; its AEAD argument selects
|
|
75
|
+
content encryption independently. This example uses AES-128-GCM for key wrapping
|
|
76
|
+
and AES-256-GCM for content encryption:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { Aes256Gcm, createJoseHpke3Ke } from "@hpke/jose";
|
|
80
|
+
|
|
81
|
+
const enc = createJoseHpke3Ke(new Aes256Gcm());
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Supported content AEADs are `Aes128Gcm` and `Aes256Gcm`; other AEADs are
|
|
85
|
+
rejected when constructing the instance.
|
|
86
|
+
|
|
87
|
+
### Key Encryption with Multiple Recipients
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { Aes128Gcm, createJoseHpke3Ke } from "@hpke/jose";
|
|
91
|
+
|
|
92
|
+
const enc = createJoseHpke3Ke(new Aes128Gcm());
|
|
93
|
+
const rkp1 = await enc.generateKemKeyPair();
|
|
94
|
+
const rkp2 = await enc.generateKemKeyPair();
|
|
95
|
+
|
|
96
|
+
const plaintext = new TextEncoder().encode("Multi-recipient!");
|
|
97
|
+
const jwe = await enc.seal(
|
|
98
|
+
[
|
|
99
|
+
{ recipientPublicKey: rkp1.publicKey },
|
|
100
|
+
{ recipientPublicKey: rkp2.publicKey },
|
|
101
|
+
],
|
|
102
|
+
plaintext,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// Both recipients can decrypt
|
|
106
|
+
const pt1 = await enc.open(rkp1, jwe);
|
|
107
|
+
const pt2 = await enc.open(rkp2, jwe);
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
MIT
|
package/esm/mod.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type { AeadInterface } from "@hpke/core";
|
|
2
|
+
export { Aes128Gcm, Aes256Gcm } from "@hpke/core";
|
|
3
|
+
export { ContentEncAlg, JoseHpkeAlg } from "./src/alg.js";
|
|
4
|
+
export type { JoseEncrypt0, JoseEncrypt0Options } from "./src/encrypt0.js";
|
|
5
|
+
export type { JoseEncrypt } from "./src/encrypt.js";
|
|
6
|
+
export type { JoseEncryptOpenOptions, JoseEncryptSealOptions, JoseRecipient, } from "./src/encrypt.js";
|
|
7
|
+
export type { JweFlattenedJson, JweGeneralJson, JweGeneralRecipientJson, JweJson, JweRecipientJson, } from "./src/serialization.js";
|
|
8
|
+
export { JoseError } from "./src/errors.js";
|
|
9
|
+
export { createJoseHpke0, createJoseHpke0Ke } from "./src/hpke0.js";
|
|
10
|
+
export { createJoseHpke1, createJoseHpke1Ke } from "./src/hpke1.js";
|
|
11
|
+
export { createJoseHpke2, createJoseHpke2Ke } from "./src/hpke2.js";
|
|
12
|
+
export { createJoseHpke3, createJoseHpke3Ke } from "./src/hpke3.js";
|
|
13
|
+
export { createJoseHpke4 } from "./src/hpke4.js";
|
|
14
|
+
export { createJoseHpke5, createJoseHpke5Ke } from "./src/hpke5.js";
|
|
15
|
+
export { createJoseHpke6 } from "./src/hpke6.js";
|
|
16
|
+
export { createJoseHpke7, createJoseHpke7Ke } from "./src/hpke7.js";
|
|
17
|
+
//# sourceMappingURL=mod.d.ts.map
|
package/esm/mod.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../src/mod.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAElD,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC1D,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC3E,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,YAAY,EACV,sBAAsB,EACtB,sBAAsB,EACtB,aAAa,GACd,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,gBAAgB,EAChB,cAAc,EACd,uBAAuB,EACvB,OAAO,EACP,gBAAgB,GACjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/esm/mod.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { Aes128Gcm, Aes256Gcm } from "@hpke/core";
|
|
2
|
+
export { ContentEncAlg, JoseHpkeAlg } from "./src/alg.js";
|
|
3
|
+
export { JoseError } from "./src/errors.js";
|
|
4
|
+
export { createJoseHpke0, createJoseHpke0Ke } from "./src/hpke0.js";
|
|
5
|
+
export { createJoseHpke1, createJoseHpke1Ke } from "./src/hpke1.js";
|
|
6
|
+
export { createJoseHpke2, createJoseHpke2Ke } from "./src/hpke2.js";
|
|
7
|
+
export { createJoseHpke3, createJoseHpke3Ke } from "./src/hpke3.js";
|
|
8
|
+
export { createJoseHpke4 } from "./src/hpke4.js";
|
|
9
|
+
export { createJoseHpke5, createJoseHpke5Ke } from "./src/hpke5.js";
|
|
10
|
+
export { createJoseHpke6 } from "./src/hpke6.js";
|
|
11
|
+
export { createJoseHpke7, createJoseHpke7Ke } from "./src/hpke7.js";
|
package/esm/package.json
ADDED
package/esm/src/alg.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type AeadInterface } from "@hpke/core";
|
|
2
|
+
/**
|
|
3
|
+
* JOSE HPKE algorithm identifiers from draft-ietf-jose-hpke-encrypt-22.
|
|
4
|
+
*
|
|
5
|
+
* Integrated Encryption: "HPKE-0" through "HPKE-7".
|
|
6
|
+
* Key Encryption: "HPKE-0-KE" through "HPKE-7-KE", except "HPKE-4-KE" and
|
|
7
|
+
* "HPKE-6-KE", which the draft does not define.
|
|
8
|
+
*/
|
|
9
|
+
export declare const JoseHpkeAlg: {
|
|
10
|
+
/** DHKEM(P-256), HKDF-SHA256, AES-128-GCM */
|
|
11
|
+
readonly HPKE_0: "HPKE-0";
|
|
12
|
+
/** DHKEM(P-384), HKDF-SHA384, AES-256-GCM */
|
|
13
|
+
readonly HPKE_1: "HPKE-1";
|
|
14
|
+
/** DHKEM(P-521), HKDF-SHA512, AES-256-GCM */
|
|
15
|
+
readonly HPKE_2: "HPKE-2";
|
|
16
|
+
/** DHKEM(X25519), HKDF-SHA256, AES-128-GCM */
|
|
17
|
+
readonly HPKE_3: "HPKE-3";
|
|
18
|
+
/** DHKEM(X25519), HKDF-SHA256, ChaCha20Poly1305 */
|
|
19
|
+
readonly HPKE_4: "HPKE-4";
|
|
20
|
+
/** DHKEM(X448), HKDF-SHA512, AES-256-GCM */
|
|
21
|
+
readonly HPKE_5: "HPKE-5";
|
|
22
|
+
/** DHKEM(X448), HKDF-SHA512, ChaCha20Poly1305 */
|
|
23
|
+
readonly HPKE_6: "HPKE-6";
|
|
24
|
+
/** DHKEM(P-256), HKDF-SHA256, AES-256-GCM */
|
|
25
|
+
readonly HPKE_7: "HPKE-7";
|
|
26
|
+
/** Key Encryption: DHKEM(P-256), HKDF-SHA256, AES-128-GCM */
|
|
27
|
+
readonly HPKE_0_KE: "HPKE-0-KE";
|
|
28
|
+
/** Key Encryption: DHKEM(P-384), HKDF-SHA384, AES-256-GCM */
|
|
29
|
+
readonly HPKE_1_KE: "HPKE-1-KE";
|
|
30
|
+
/** Key Encryption: DHKEM(P-521), HKDF-SHA512, AES-256-GCM */
|
|
31
|
+
readonly HPKE_2_KE: "HPKE-2-KE";
|
|
32
|
+
/** Key Encryption: DHKEM(X25519), HKDF-SHA256, AES-128-GCM */
|
|
33
|
+
readonly HPKE_3_KE: "HPKE-3-KE";
|
|
34
|
+
/** Key Encryption: DHKEM(X448), HKDF-SHA512, AES-256-GCM */
|
|
35
|
+
readonly HPKE_5_KE: "HPKE-5-KE";
|
|
36
|
+
/** Key Encryption: DHKEM(P-256), HKDF-SHA256, AES-256-GCM */
|
|
37
|
+
readonly HPKE_7_KE: "HPKE-7-KE";
|
|
38
|
+
};
|
|
39
|
+
export type JoseHpkeAlg = (typeof JoseHpkeAlg)[keyof typeof JoseHpkeAlg];
|
|
40
|
+
/** JWE content-encryption algorithm identifiers. */
|
|
41
|
+
export declare const ContentEncAlg: {
|
|
42
|
+
/** AES-128-GCM */
|
|
43
|
+
readonly A128GCM: "A128GCM";
|
|
44
|
+
/** AES-256-GCM */
|
|
45
|
+
readonly A256GCM: "A256GCM";
|
|
46
|
+
};
|
|
47
|
+
export type ContentEncAlg = (typeof ContentEncAlg)[keyof typeof ContentEncAlg];
|
|
48
|
+
/**
|
|
49
|
+
* Map an HPKE AEAD object to its supported JWE content-encryption algorithm.
|
|
50
|
+
*
|
|
51
|
+
* @throws {JoseError} if the AEAD has no supported JWE equivalent.
|
|
52
|
+
*/
|
|
53
|
+
export declare function contentEncAlgFromAead(aead: AeadInterface): ContentEncAlg;
|
|
54
|
+
/** Whether the algorithm is for Key Encryption. */
|
|
55
|
+
export declare function isKeyEncryption(alg: JoseHpkeAlg): boolean;
|
|
56
|
+
/** Whether the algorithm is for Integrated Encryption. */
|
|
57
|
+
export declare function isIntegratedEncryption(alg: JoseHpkeAlg): boolean;
|
|
58
|
+
//# sourceMappingURL=alg.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alg.d.ts","sourceRoot":"","sources":["../../src/src/alg.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAIxD;;;;;;GAMG;AACH,eAAO,MAAM,WAAW;IACtB,6CAA6C;;IAE7C,6CAA6C;;IAE7C,6CAA6C;;IAE7C,8CAA8C;;IAE9C,mDAAmD;;IAEnD,4CAA4C;;IAE5C,iDAAiD;;IAEjD,6CAA6C;;IAE7C,6DAA6D;;IAE7D,6DAA6D;;IAE7D,6DAA6D;;IAE7D,8DAA8D;;IAE9D,4DAA4D;;IAE5D,6DAA6D;;CAErD,CAAC;AAEX,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,WAAW,CAAC,CAAC;AAEzE,oDAAoD;AACpD,eAAO,MAAM,aAAa;IACxB,kBAAkB;;IAElB,kBAAkB;;CAEV,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,OAAO,aAAa,CAAC,CAAC;AAE/E;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,aAAa,GAAG,aAAa,CA2BxE;AAqBD,mDAAmD;AACnD,wBAAgB,eAAe,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAEzD;AAED,0DAA0D;AAC1D,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAEhE"}
|
package/esm/src/alg.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { AeadId } from "@hpke/core";
|
|
2
|
+
import { JoseError } from "./errors.js";
|
|
3
|
+
/**
|
|
4
|
+
* JOSE HPKE algorithm identifiers from draft-ietf-jose-hpke-encrypt-22.
|
|
5
|
+
*
|
|
6
|
+
* Integrated Encryption: "HPKE-0" through "HPKE-7".
|
|
7
|
+
* Key Encryption: "HPKE-0-KE" through "HPKE-7-KE", except "HPKE-4-KE" and
|
|
8
|
+
* "HPKE-6-KE", which the draft does not define.
|
|
9
|
+
*/
|
|
10
|
+
export const JoseHpkeAlg = {
|
|
11
|
+
/** DHKEM(P-256), HKDF-SHA256, AES-128-GCM */
|
|
12
|
+
HPKE_0: "HPKE-0",
|
|
13
|
+
/** DHKEM(P-384), HKDF-SHA384, AES-256-GCM */
|
|
14
|
+
HPKE_1: "HPKE-1",
|
|
15
|
+
/** DHKEM(P-521), HKDF-SHA512, AES-256-GCM */
|
|
16
|
+
HPKE_2: "HPKE-2",
|
|
17
|
+
/** DHKEM(X25519), HKDF-SHA256, AES-128-GCM */
|
|
18
|
+
HPKE_3: "HPKE-3",
|
|
19
|
+
/** DHKEM(X25519), HKDF-SHA256, ChaCha20Poly1305 */
|
|
20
|
+
HPKE_4: "HPKE-4",
|
|
21
|
+
/** DHKEM(X448), HKDF-SHA512, AES-256-GCM */
|
|
22
|
+
HPKE_5: "HPKE-5",
|
|
23
|
+
/** DHKEM(X448), HKDF-SHA512, ChaCha20Poly1305 */
|
|
24
|
+
HPKE_6: "HPKE-6",
|
|
25
|
+
/** DHKEM(P-256), HKDF-SHA256, AES-256-GCM */
|
|
26
|
+
HPKE_7: "HPKE-7",
|
|
27
|
+
/** Key Encryption: DHKEM(P-256), HKDF-SHA256, AES-128-GCM */
|
|
28
|
+
HPKE_0_KE: "HPKE-0-KE",
|
|
29
|
+
/** Key Encryption: DHKEM(P-384), HKDF-SHA384, AES-256-GCM */
|
|
30
|
+
HPKE_1_KE: "HPKE-1-KE",
|
|
31
|
+
/** Key Encryption: DHKEM(P-521), HKDF-SHA512, AES-256-GCM */
|
|
32
|
+
HPKE_2_KE: "HPKE-2-KE",
|
|
33
|
+
/** Key Encryption: DHKEM(X25519), HKDF-SHA256, AES-128-GCM */
|
|
34
|
+
HPKE_3_KE: "HPKE-3-KE",
|
|
35
|
+
/** Key Encryption: DHKEM(X448), HKDF-SHA512, AES-256-GCM */
|
|
36
|
+
HPKE_5_KE: "HPKE-5-KE",
|
|
37
|
+
/** Key Encryption: DHKEM(P-256), HKDF-SHA256, AES-256-GCM */
|
|
38
|
+
HPKE_7_KE: "HPKE-7-KE",
|
|
39
|
+
};
|
|
40
|
+
/** JWE content-encryption algorithm identifiers. */
|
|
41
|
+
export const ContentEncAlg = {
|
|
42
|
+
/** AES-128-GCM */
|
|
43
|
+
A128GCM: "A128GCM",
|
|
44
|
+
/** AES-256-GCM */
|
|
45
|
+
A256GCM: "A256GCM",
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Map an HPKE AEAD object to its supported JWE content-encryption algorithm.
|
|
49
|
+
*
|
|
50
|
+
* @throws {JoseError} if the AEAD has no supported JWE equivalent.
|
|
51
|
+
*/
|
|
52
|
+
export function contentEncAlgFromAead(aead) {
|
|
53
|
+
let alg;
|
|
54
|
+
let keySize;
|
|
55
|
+
switch (aead.id) {
|
|
56
|
+
case AeadId.Aes128Gcm:
|
|
57
|
+
alg = ContentEncAlg.A128GCM;
|
|
58
|
+
keySize = 16;
|
|
59
|
+
break;
|
|
60
|
+
case AeadId.Aes256Gcm:
|
|
61
|
+
alg = ContentEncAlg.A256GCM;
|
|
62
|
+
keySize = 32;
|
|
63
|
+
break;
|
|
64
|
+
default:
|
|
65
|
+
throw new JoseError(`Unsupported content encryption AEAD: id=0x${aead.id.toString(16).padStart(4, "0")}`);
|
|
66
|
+
}
|
|
67
|
+
if (aead.keySize !== keySize || aead.nonceSize !== 12 || aead.tagSize !== 16) {
|
|
68
|
+
throw new JoseError(`Invalid content encryption AEAD parameters for ${alg}: expected keySize=${keySize}, nonceSize=12, tagSize=16`);
|
|
69
|
+
}
|
|
70
|
+
return alg;
|
|
71
|
+
}
|
|
72
|
+
const KEY_ENCRYPTION_ALGS = new Set([
|
|
73
|
+
"HPKE-0-KE",
|
|
74
|
+
"HPKE-1-KE",
|
|
75
|
+
"HPKE-2-KE",
|
|
76
|
+
"HPKE-3-KE",
|
|
77
|
+
"HPKE-5-KE",
|
|
78
|
+
"HPKE-7-KE",
|
|
79
|
+
]);
|
|
80
|
+
const INTEGRATED_ENCRYPTION_ALGS = new Set([
|
|
81
|
+
"HPKE-0",
|
|
82
|
+
"HPKE-1",
|
|
83
|
+
"HPKE-2",
|
|
84
|
+
"HPKE-3",
|
|
85
|
+
"HPKE-4",
|
|
86
|
+
"HPKE-5",
|
|
87
|
+
"HPKE-6",
|
|
88
|
+
"HPKE-7",
|
|
89
|
+
]);
|
|
90
|
+
/** Whether the algorithm is for Key Encryption. */
|
|
91
|
+
export function isKeyEncryption(alg) {
|
|
92
|
+
return KEY_ENCRYPTION_ALGS.has(alg);
|
|
93
|
+
}
|
|
94
|
+
/** Whether the algorithm is for Integrated Encryption. */
|
|
95
|
+
export function isIntegratedEncryption(alg) {
|
|
96
|
+
return INTEGRATED_ENCRYPTION_ALGS.has(alg);
|
|
97
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { AeadInterface, CipherSuite } from "@hpke/core";
|
|
2
|
+
import { type JoseHpkeAlg } from "./alg.js";
|
|
3
|
+
import { type JweFlattenedJson, type JweGeneralJson, type JweJson } from "./serialization.js";
|
|
4
|
+
/** A recipient descriptor for JoseEncrypt.seal(). */
|
|
5
|
+
export interface JoseRecipient {
|
|
6
|
+
/** The recipient's public key. */
|
|
7
|
+
recipientPublicKey: CryptoKey;
|
|
8
|
+
/** Key identifier for the recipient. */
|
|
9
|
+
kid?: string;
|
|
10
|
+
/** Extra info embedded in Recipient_structure. */
|
|
11
|
+
extraInfo?: Uint8Array;
|
|
12
|
+
/**
|
|
13
|
+
* Pre-shared key for PSK mode. `id` is the HPKE psk_id; it is carried in the
|
|
14
|
+
* "psk_id" header parameter as BASE64URL(id).
|
|
15
|
+
*/
|
|
16
|
+
psk?: {
|
|
17
|
+
id: Uint8Array;
|
|
18
|
+
key: Uint8Array;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/** Options for JoseEncrypt.seal(). */
|
|
22
|
+
export interface JoseEncryptSealOptions {
|
|
23
|
+
/** Additional authenticated data (JWE AAD). */
|
|
24
|
+
aad?: Uint8Array;
|
|
25
|
+
}
|
|
26
|
+
/** Options for JoseEncrypt.open(). */
|
|
27
|
+
export interface JoseEncryptOpenOptions {
|
|
28
|
+
/** Extra info for Recipient_structure. */
|
|
29
|
+
extraInfo?: Uint8Array;
|
|
30
|
+
/** Pre-shared key for PSK mode. `id` is the HPKE psk_id. */
|
|
31
|
+
psk?: {
|
|
32
|
+
id: Uint8Array;
|
|
33
|
+
key: Uint8Array;
|
|
34
|
+
};
|
|
35
|
+
/** Key identifier to match against recipients. */
|
|
36
|
+
kid?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* JWE Key Encryption using HPKE.
|
|
40
|
+
*
|
|
41
|
+
* Implements draft-ietf-jose-hpke-encrypt-22 Section 6. seal() produces the
|
|
42
|
+
* General JSON Serialization; open() accepts all JWE serializations.
|
|
43
|
+
*/
|
|
44
|
+
export interface JoseEncrypt {
|
|
45
|
+
/** The underlying HPKE CipherSuite. */
|
|
46
|
+
readonly suite: CipherSuite;
|
|
47
|
+
/** Generate a KEM key pair. */
|
|
48
|
+
generateKemKeyPair(): Promise<CryptoKeyPair>;
|
|
49
|
+
/**
|
|
50
|
+
* Encrypt plaintext into a JWE JSON Serialization structure.
|
|
51
|
+
*/
|
|
52
|
+
seal(recipients: JoseRecipient[], plaintext: Uint8Array, options?: JoseEncryptSealOptions): Promise<JweJson>;
|
|
53
|
+
/**
|
|
54
|
+
* Decrypt a JWE in the Compact, Flattened JSON or General JSON Serialization.
|
|
55
|
+
* Header parameters are read from the union of the protected, shared
|
|
56
|
+
* unprotected and per-recipient headers.
|
|
57
|
+
*/
|
|
58
|
+
open(recipientKey: CryptoKeyPair | CryptoKey, jwe: string | JweFlattenedJson | JweGeneralJson, options?: JoseEncryptOpenOptions): Promise<Uint8Array>;
|
|
59
|
+
/**
|
|
60
|
+
* Import a public key from a JWK.
|
|
61
|
+
*/
|
|
62
|
+
importPublicJwk(jwk: JsonWebKey): Promise<CryptoKey>;
|
|
63
|
+
/**
|
|
64
|
+
* Import a private key from a JWK.
|
|
65
|
+
*/
|
|
66
|
+
importPrivateJwk(jwk: JsonWebKey): Promise<CryptoKey>;
|
|
67
|
+
}
|
|
68
|
+
/** @internal */
|
|
69
|
+
export declare class JoseEncryptImpl implements JoseEncrypt {
|
|
70
|
+
private _suite;
|
|
71
|
+
private _alg;
|
|
72
|
+
private _contentEncAlg;
|
|
73
|
+
private _contentAead;
|
|
74
|
+
constructor(suite: CipherSuite, alg: JoseHpkeAlg, contentAead: AeadInterface);
|
|
75
|
+
get suite(): CipherSuite;
|
|
76
|
+
generateKemKeyPair(): Promise<CryptoKeyPair>;
|
|
77
|
+
seal(recipients: JoseRecipient[], plaintext: Uint8Array, options?: JoseEncryptSealOptions): Promise<JweJson>;
|
|
78
|
+
open(recipientKey: CryptoKeyPair | CryptoKey, jwe: string | JweFlattenedJson | JweGeneralJson, options?: JoseEncryptOpenOptions): Promise<Uint8Array>;
|
|
79
|
+
private _wrapCek;
|
|
80
|
+
importPublicJwk(jwk: JsonWebKey): Promise<CryptoKey>;
|
|
81
|
+
importPrivateJwk(jwk: JsonWebKey): Promise<CryptoKey>;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=encrypt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"encrypt.d.ts","sourceRoot":"","sources":["../../src/src/encrypt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE7D,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,UAAU,CAAC;AAOlB,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,OAAO,EAGb,MAAM,oBAAoB,CAAC;AAM5B,qDAAqD;AACrD,MAAM,WAAW,aAAa;IAC5B,kCAAkC;IAClC,kBAAkB,EAAE,SAAS,CAAC;IAC9B,wCAAwC;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,SAAS,CAAC,EAAE,UAAU,CAAC;IACvB;;;OAGG;IACH,GAAG,CAAC,EAAE;QAAE,EAAE,EAAE,UAAU,CAAC;QAAC,GAAG,EAAE,UAAU,CAAA;KAAE,CAAC;CAC3C;AAED,sCAAsC;AACtC,MAAM,WAAW,sBAAsB;IACrC,+CAA+C;IAC/C,GAAG,CAAC,EAAE,UAAU,CAAC;CAClB;AAED,sCAAsC;AACtC,MAAM,WAAW,sBAAsB;IACrC,0CAA0C;IAC1C,SAAS,CAAC,EAAE,UAAU,CAAC;IACvB,4DAA4D;IAC5D,GAAG,CAAC,EAAE;QAAE,EAAE,EAAE,UAAU,CAAC;QAAC,GAAG,EAAE,UAAU,CAAA;KAAE,CAAC;IAC1C,kDAAkD;IAClD,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,uCAAuC;IACvC,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAE5B,+BAA+B;IAC/B,kBAAkB,IAAI,OAAO,CAAC,aAAa,CAAC,CAAC;IAE7C;;OAEG;IACH,IAAI,CACF,UAAU,EAAE,aAAa,EAAE,EAC3B,SAAS,EAAE,UAAU,EACrB,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,OAAO,CAAC,CAAC;IAEpB;;;;OAIG;IACH,IAAI,CACF,YAAY,EAAE,aAAa,GAAG,SAAS,EACvC,GAAG,EAAE,MAAM,GAAG,gBAAgB,GAAG,cAAc,EAC/C,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,UAAU,CAAC,CAAC;IAEvB;;OAEG;IACH,eAAe,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAErD;;OAEG;IACH,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;CACvD;AAED,gBAAgB;AAChB,qBAAa,eAAgB,YAAW,WAAW;IACjD,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,IAAI,CAAc;IAC1B,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,YAAY,CAAgB;gBAGlC,KAAK,EAAE,WAAW,EAClB,GAAG,EAAE,WAAW,EAChB,WAAW,EAAE,aAAa;IAa5B,IAAI,KAAK,IAAI,WAAW,CAEvB;IAEK,kBAAkB,IAAI,OAAO,CAAC,aAAa,CAAC;IAI5C,IAAI,CACR,UAAU,EAAE,aAAa,EAAE,EAC3B,SAAS,EAAE,UAAU,EACrB,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,OAAO,CAAC;IAsEb,IAAI,CACR,YAAY,EAAE,aAAa,GAAG,SAAS,EACvC,GAAG,EAAE,MAAM,GAAG,gBAAgB,GAAG,cAAc,EAC/C,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,UAAU,CAAC;YAwGR,QAAQ;IA0ChB,eAAe,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC;IASpD,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC;CAQ5D"}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { contentEncAlgFromAead, isKeyEncryption, } from "./alg.js";
|
|
2
|
+
import { decodeBase64Url, encodeBase64Url } from "./utils.js";
|
|
3
|
+
import { JoseError, withJoseError } from "./errors.js";
|
|
4
|
+
import { extractPrivateKeyBytesFromJwk, extractPublicKeyBytesFromJwk, } from "./jwk.js";
|
|
5
|
+
import { parseJwe, } from "./serialization.js";
|
|
6
|
+
import { buildRecipientStructure } from "./structures.js";
|
|
7
|
+
const EMPTY = new Uint8Array(0);
|
|
8
|
+
const te = new TextEncoder();
|
|
9
|
+
/** @internal */
|
|
10
|
+
export class JoseEncryptImpl {
|
|
11
|
+
constructor(suite, alg, contentAead) {
|
|
12
|
+
Object.defineProperty(this, "_suite", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
configurable: true,
|
|
15
|
+
writable: true,
|
|
16
|
+
value: void 0
|
|
17
|
+
});
|
|
18
|
+
Object.defineProperty(this, "_alg", {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
configurable: true,
|
|
21
|
+
writable: true,
|
|
22
|
+
value: void 0
|
|
23
|
+
});
|
|
24
|
+
Object.defineProperty(this, "_contentEncAlg", {
|
|
25
|
+
enumerable: true,
|
|
26
|
+
configurable: true,
|
|
27
|
+
writable: true,
|
|
28
|
+
value: void 0
|
|
29
|
+
});
|
|
30
|
+
Object.defineProperty(this, "_contentAead", {
|
|
31
|
+
enumerable: true,
|
|
32
|
+
configurable: true,
|
|
33
|
+
writable: true,
|
|
34
|
+
value: void 0
|
|
35
|
+
});
|
|
36
|
+
if (!isKeyEncryption(alg)) {
|
|
37
|
+
throw new JoseError(`JoseEncrypt requires a Key Encryption algorithm, got ${alg}`);
|
|
38
|
+
}
|
|
39
|
+
this._suite = suite;
|
|
40
|
+
this._alg = alg;
|
|
41
|
+
this._contentEncAlg = contentEncAlgFromAead(contentAead);
|
|
42
|
+
this._contentAead = contentAead;
|
|
43
|
+
}
|
|
44
|
+
get suite() {
|
|
45
|
+
return this._suite;
|
|
46
|
+
}
|
|
47
|
+
async generateKemKeyPair() {
|
|
48
|
+
return await this._suite.kem.generateKeyPair();
|
|
49
|
+
}
|
|
50
|
+
async seal(recipients, plaintext, options) {
|
|
51
|
+
if (recipients.length === 0) {
|
|
52
|
+
throw new JoseError("At least one recipient is required");
|
|
53
|
+
}
|
|
54
|
+
const keySize = this._contentAead.keySize;
|
|
55
|
+
const nonceSize = this._contentAead.nonceSize;
|
|
56
|
+
const cek = crypto.getRandomValues(new Uint8Array(keySize));
|
|
57
|
+
try {
|
|
58
|
+
return await withJoseError("Encryption failed", async () => {
|
|
59
|
+
const nonce = crypto.getRandomValues(new Uint8Array(nonceSize));
|
|
60
|
+
// Build protected header: { "enc": "A128GCM" }
|
|
61
|
+
const protectedHeaderObj = {
|
|
62
|
+
enc: this._contentEncAlg,
|
|
63
|
+
};
|
|
64
|
+
const encodedProtectedHeader = encodeBase64Url(te.encode(JSON.stringify(protectedHeaderObj)));
|
|
65
|
+
// Content AAD = ASCII(EPH) or ASCII(EPH || '.' || BASE64URL(JWE AAD))
|
|
66
|
+
let contentAad;
|
|
67
|
+
let jweAad;
|
|
68
|
+
if (options?.aad && options.aad.length > 0) {
|
|
69
|
+
jweAad = encodeBase64Url(options.aad);
|
|
70
|
+
contentAad = `${encodedProtectedHeader}.${jweAad}`;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
contentAad = encodedProtectedHeader;
|
|
74
|
+
}
|
|
75
|
+
const aadBytes = te.encode(contentAad);
|
|
76
|
+
// Encrypt content with CEK
|
|
77
|
+
const contentCtx = this._contentAead.createEncryptionContext(cek);
|
|
78
|
+
const combined = new Uint8Array(await contentCtx.seal(nonce, plaintext, aadBytes));
|
|
79
|
+
// Split ciphertext and tag
|
|
80
|
+
const ct = combined.slice(0, combined.length - this._contentAead.tagSize);
|
|
81
|
+
const tag = combined.slice(combined.length - this._contentAead.tagSize);
|
|
82
|
+
// Wrap CEK for each recipient
|
|
83
|
+
const recipientEntries = [];
|
|
84
|
+
for (const r of recipients) {
|
|
85
|
+
const entry = await this._wrapCek(r, cek);
|
|
86
|
+
recipientEntries.push(entry);
|
|
87
|
+
}
|
|
88
|
+
const result = {
|
|
89
|
+
protected: encodedProtectedHeader,
|
|
90
|
+
iv: encodeBase64Url(nonce),
|
|
91
|
+
ciphertext: encodeBase64Url(ct),
|
|
92
|
+
tag: encodeBase64Url(tag),
|
|
93
|
+
recipients: recipientEntries,
|
|
94
|
+
};
|
|
95
|
+
if (jweAad) {
|
|
96
|
+
result.aad = jweAad;
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
// Best effort: crypto contexts may retain their own key copies.
|
|
103
|
+
cek.fill(0);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async open(recipientKey, jwe, options) {
|
|
107
|
+
const extraInfo = options?.extraInfo ?? EMPTY;
|
|
108
|
+
const parsed = parseJwe(jwe);
|
|
109
|
+
// Decode IV, ciphertext, tag
|
|
110
|
+
if (!parsed.iv) {
|
|
111
|
+
throw new JoseError("Missing IV");
|
|
112
|
+
}
|
|
113
|
+
if (!parsed.tag) {
|
|
114
|
+
throw new JoseError("Missing authentication tag");
|
|
115
|
+
}
|
|
116
|
+
const nonce = decodeBase64Url(parsed.iv);
|
|
117
|
+
const ct = decodeBase64Url(parsed.ciphertext);
|
|
118
|
+
const tag = decodeBase64Url(parsed.tag);
|
|
119
|
+
const privateKey = "privateKey" in recipientKey
|
|
120
|
+
? recipientKey.privateKey
|
|
121
|
+
: recipientKey;
|
|
122
|
+
// Find matching recipient and unwrap CEK
|
|
123
|
+
let cek = null;
|
|
124
|
+
for (const { joseHeader, encryptedKey } of parsed.recipients) {
|
|
125
|
+
// All recipients of a JWE share one content encryption algorithm
|
|
126
|
+
if (joseHeader.enc !== this._contentEncAlg) {
|
|
127
|
+
throw new JoseError(`Content encryption algorithm mismatch: expected ${this._contentEncAlg}, got ${joseHeader.enc}`);
|
|
128
|
+
}
|
|
129
|
+
// Check algorithm matches
|
|
130
|
+
if (joseHeader.alg !== this._alg)
|
|
131
|
+
continue;
|
|
132
|
+
// Match by kid if specified
|
|
133
|
+
if (options?.kid && joseHeader.kid !== options.kid)
|
|
134
|
+
continue;
|
|
135
|
+
// Get ek (encapsulated key)
|
|
136
|
+
const rEk = joseHeader.ek;
|
|
137
|
+
if (!rEk || !encryptedKey)
|
|
138
|
+
continue;
|
|
139
|
+
// PSK consistency
|
|
140
|
+
if (options?.psk) {
|
|
141
|
+
if (joseHeader.psk_id !== encodeBase64Url(options.psk.id))
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
if ("psk_id" in joseHeader)
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const encBytes = decodeBase64Url(rEk);
|
|
149
|
+
const wrappedCek = decodeBase64Url(encryptedKey);
|
|
150
|
+
// Build HPKE info = Recipient_structure(enc_alg, extra_info)
|
|
151
|
+
const hpkeInfo = buildRecipientStructure(this._contentEncAlg, extraInfo);
|
|
152
|
+
try {
|
|
153
|
+
const recipientParams = {
|
|
154
|
+
recipientKey: privateKey,
|
|
155
|
+
enc: encBytes,
|
|
156
|
+
info: hpkeInfo,
|
|
157
|
+
};
|
|
158
|
+
if (options?.psk) {
|
|
159
|
+
recipientParams.psk = options.psk;
|
|
160
|
+
}
|
|
161
|
+
const ctx = await this._suite.createRecipientContext(recipientParams);
|
|
162
|
+
cek = new Uint8Array(await ctx.open(wrappedCek, EMPTY));
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (cek === null) {
|
|
170
|
+
throw new JoseError("No matching recipient found or decryption failed");
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
if (cek.length !== this._contentAead.keySize) {
|
|
174
|
+
throw new JoseError(`Invalid CEK length: expected ${this._contentAead.keySize}, got ${cek.length}`);
|
|
175
|
+
}
|
|
176
|
+
return await withJoseError("Content decryption failed", async () => {
|
|
177
|
+
// Combine ciphertext and tag for AES-GCM
|
|
178
|
+
const combined = new Uint8Array(ct.length + tag.length);
|
|
179
|
+
combined.set(ct);
|
|
180
|
+
combined.set(tag, ct.length);
|
|
181
|
+
const contentCtx = this._contentAead.createEncryptionContext(cek);
|
|
182
|
+
return new Uint8Array(await contentCtx.open(nonce, combined, parsed.additionalData));
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
// Best effort: crypto contexts may retain their own key copies.
|
|
187
|
+
cek.fill(0);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async _wrapCek(recipient, cek) {
|
|
191
|
+
const extraInfo = recipient.extraInfo ?? EMPTY;
|
|
192
|
+
// Build HPKE info = Recipient_structure(enc_alg, extra_info)
|
|
193
|
+
const hpkeInfo = buildRecipientStructure(this._contentEncAlg, extraInfo);
|
|
194
|
+
const senderParams = {
|
|
195
|
+
recipientPublicKey: recipient.recipientPublicKey,
|
|
196
|
+
info: hpkeInfo,
|
|
197
|
+
};
|
|
198
|
+
if (recipient.psk) {
|
|
199
|
+
senderParams.psk = recipient.psk;
|
|
200
|
+
}
|
|
201
|
+
const ctx = await this._suite.createSenderContext(senderParams);
|
|
202
|
+
const encBytes = new Uint8Array(ctx.enc);
|
|
203
|
+
const wrappedCek = new Uint8Array(await ctx.seal(cek, EMPTY));
|
|
204
|
+
// Per-recipient unprotected header
|
|
205
|
+
const rHeader = {
|
|
206
|
+
alg: this._alg,
|
|
207
|
+
ek: encodeBase64Url(encBytes),
|
|
208
|
+
};
|
|
209
|
+
if (recipient.kid) {
|
|
210
|
+
rHeader.kid = recipient.kid;
|
|
211
|
+
}
|
|
212
|
+
if (recipient.psk) {
|
|
213
|
+
rHeader.psk_id = encodeBase64Url(recipient.psk.id);
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
header: rHeader,
|
|
217
|
+
encrypted_key: encodeBase64Url(wrappedCek),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
async importPublicJwk(jwk) {
|
|
221
|
+
const rawBytes = extractPublicKeyBytesFromJwk(jwk, this._alg);
|
|
222
|
+
return await this._suite.kem.importKey("raw", rawBytes.buffer, true);
|
|
223
|
+
}
|
|
224
|
+
async importPrivateJwk(jwk) {
|
|
225
|
+
const rawBytes = extractPrivateKeyBytesFromJwk(jwk, this._alg);
|
|
226
|
+
return await this._suite.kem.importKey("raw", rawBytes.buffer, false);
|
|
227
|
+
}
|
|
228
|
+
}
|