unjwt 0.4.0 → 0.5.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.
Files changed (49) hide show
  1. package/README.md +445 -190
  2. package/dist/index.d.mts +6 -5
  3. package/dist/index.d.ts +6 -5
  4. package/dist/index.mjs +5 -5
  5. package/dist/jwe.d.mts +3 -3
  6. package/dist/jwe.d.ts +3 -3
  7. package/dist/jwe.mjs +4 -3
  8. package/dist/jwk.d.mts +2 -2
  9. package/dist/jwk.d.ts +2 -2
  10. package/dist/jwk.mjs +2 -2
  11. package/dist/jws.d.mts +3 -3
  12. package/dist/jws.d.ts +3 -3
  13. package/dist/jws.mjs +4 -4
  14. package/dist/shared/unjwt.1gbuWbc6.mjs +1507 -0
  15. package/dist/shared/unjwt.B0XDPf8a.d.ts +189 -0
  16. package/dist/shared/unjwt.B4fFiy1c.d.mts +52 -0
  17. package/dist/shared/unjwt.BG_R52ey.d.mts +100 -0
  18. package/dist/shared/unjwt.BISJ5yt1.d.ts +52 -0
  19. package/dist/shared/unjwt.BxqF-n1S.mjs +167 -0
  20. package/dist/shared/unjwt.C9Xhi7hE.mjs +101 -0
  21. package/dist/shared/unjwt.CVKbKOFy.d.ts +100 -0
  22. package/dist/shared/unjwt.Cd9nTEWg.d.mts +52 -0
  23. package/dist/shared/unjwt.Ce72NmIz.mjs +453 -0
  24. package/dist/shared/unjwt.CxhGjEAX.d.mts +202 -0
  25. package/dist/shared/unjwt.CxhGjEAX.d.ts +202 -0
  26. package/dist/shared/unjwt.D1J1DelP.d.mts +198 -0
  27. package/dist/shared/unjwt.DHdDAH0L.mjs +83 -0
  28. package/dist/shared/unjwt.DQIYSlHj.d.mts +189 -0
  29. package/dist/shared/unjwt.DqmNvXMd.d.ts +52 -0
  30. package/dist/shared/unjwt.WvWb2zNM.d.ts +198 -0
  31. package/dist/utils.d.mts +2 -0
  32. package/dist/utils.d.ts +2 -0
  33. package/dist/utils.mjs +1 -0
  34. package/package.json +8 -4
  35. package/dist/shared/unjwt.4uzJv0cA.d.mts +0 -110
  36. package/dist/shared/unjwt.A7HKFx3n.d.mts +0 -52
  37. package/dist/shared/unjwt.BdjvpIUY.d.mts +0 -79
  38. package/dist/shared/unjwt.BmjmfFWo.d.ts +0 -79
  39. package/dist/shared/unjwt.C9ELsKeK.mjs +0 -120
  40. package/dist/shared/unjwt.CVUxLNoy.d.ts +0 -52
  41. package/dist/shared/unjwt.ChP7VhqI.d.ts +0 -110
  42. package/dist/shared/unjwt.DD0p18Gd.mjs +0 -81
  43. package/dist/shared/unjwt.DGsIvgyn.d.ts +0 -106
  44. package/dist/shared/unjwt.DPTwlVpK.mjs +0 -28
  45. package/dist/shared/unjwt.Dr-mxtq9.d.mts +0 -106
  46. package/dist/shared/unjwt.KOd0vXyx.mjs +0 -90
  47. package/dist/shared/unjwt._GivNhs2.mjs +0 -213
  48. package/dist/shared/unjwt.zb2wBNCJ.d.mts +0 -82
  49. package/dist/shared/unjwt.zb2wBNCJ.d.ts +0 -82
package/README.md CHANGED
@@ -1,17 +1,26 @@
1
1
  # unjwt
2
2
 
3
- <!-- automd:badges color=yellow -->
3
+ <!-- automd:badges bundlephobia style="flat" color="FFDC3B" -->
4
4
 
5
- [![npm version](https://img.shields.io/npm/v/unjwt?color=yellow)](https://npmjs.com/package/unjwt)
6
- [![npm downloads](https://img.shields.io/npm/dm/unjwt?color=yellow)](https://npm.chart.dev/unjwt)
5
+ [![npm version](https://img.shields.io/npm/v/unjwt?color=FFDC3B)](https://npmjs.com/package/unjwt)
6
+ [![npm downloads](https://img.shields.io/npm/dm/unjwt?color=FFDC3B)](https://npm.chart.dev/unjwt)
7
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/unjwt?color=FFDC3B)](https://bundlephobia.com/package/unjwt)
7
8
 
8
9
  <!-- /automd -->
9
10
 
10
- Low-level JWT utilities using the Web Crypto API. Currently supports:
11
+ A collection of low-level JWT ([RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519)) utilities using the Web Crypto API. Supports:
11
12
 
12
- - JWE (JSON Web Encryption) with password-based key derivation (PBES2).
13
- - JWS (JSON Web Signature) with symmetric keys (HMAC).
14
- - JWK (JSON Web Key) generation, import, and export for symmetric keys (`oct`).
13
+ - **JWS (JSON Web Signature, [RFC 7515](https://datatracker.ietf.org/doc/html/rfc7515))**: sign and verify tokens using HMAC, RSA (RSASSA-PKCS1-v1_5 & RSA-PSS), and ECDSA algorithms.
14
+ - **JWE (JSON Web Encryption, [RFC 7516](https://datatracker.ietf.org/doc/html/rfc7516))**: encrypt and decrypt data using various key management algorithms (AES Key Wrap, AES-GCM Key Wrap, RSA-OAEP, PBES2, ECDH-ES) and content encryption algorithms (AES-GCM, AES-CBC HMAC-SHA2).
15
+ - **JWK (JSON Web Key, [RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517))**: generate, import, export, wrap, and unwrap keys in JWK format or as `CryptoKey` objects.
16
+
17
+ > [!WARNING]
18
+ > Please do note that some algorithms are not fully working out-of-the-box yet, such as:
19
+ >
20
+ > - ECDH-ES algorithms with AES Key Wrap (e.g., ECDH-ES+A128KW)
21
+ > - RSA algorithms in combination with some bigger CBC encodings (for example RSA-OAEP-256 with A256CBC-HS512)
22
+ >
23
+ > For these algorithms, you should be able to provide your own keys. Although I do plan to study and simplify this.
15
24
 
16
25
  ## Usage
17
26
 
@@ -28,308 +37,553 @@ Import:
28
37
 
29
38
  ```js
30
39
  import { jws, jwe, jwk } from "unjwt";
31
- import { seal, unseal } from "unjwt/jwe";
40
+ // JWS functions
32
41
  import { sign, verify } from "unjwt/jws";
33
- import { generateKey, exportKey, importKey } from "unjwt/jwk";
42
+ // JWE functions
43
+ import { encrypt, decrypt } from "unjwt/jwe";
44
+ // JWK functions
45
+ import {
46
+ generateKey,
47
+ deriveKeyFromPassword,
48
+ importKey,
49
+ exportKey,
50
+ wrapKey,
51
+ unwrapKey,
52
+ } from "unjwt/jwk";
53
+ // Utility functions
54
+ import { base64UrlEncode, base64UrlDecode, randomBytes } from "unjwt/utils";
34
55
  ```
35
56
 
36
57
  **CDN** (Deno, Bun and Browsers)
37
58
 
38
59
  ```js
39
60
  import { jws, jwe, jwk } from "https://esm.sh/unjwt";
40
- import { seal, unseal } from "https://esm.sh/unjwt/jwe";
61
+ // JWS functions
41
62
  import { sign, verify } from "https://esm.sh/unjwt/jws";
42
- import { generateKey, exportKey, importKey } from "https://esm.sh/unjwt/jwk";
63
+ // JWE functions
64
+ import { encrypt, decrypt } from "https://esm.sh/unjwt/jwe";
65
+ // JWK functions
66
+ import {
67
+ generateKey,
68
+ deriveKeyFromPassword,
69
+ importKey,
70
+ exportKey,
71
+ wrapKey,
72
+ unwrapKey,
73
+ } from "https://esm.sh/unjwt/jwk";
74
+ // Utility functions
75
+ import {
76
+ base64UrlEncode,
77
+ base64UrlDecode,
78
+ randomBytes,
79
+ } from "https://esm.sh/unjwt/utils";
43
80
  ```
44
81
 
45
- ### JWE (JSON Web Encryption)
82
+ ### JWS (JSON Web Signature, [RFC 7515](https://datatracker.ietf.org/doc/html/rfc7515))
46
83
 
47
- This library provides functions to encrypt (seal) and decrypt (unseal) data according to the JWE specification using password-based encryption.
84
+ Functions to sign and verify data according to the JWS specification.
48
85
 
49
- #### `seal(data, password, options?)`
86
+ #### `sign(payload, key, options)`
50
87
 
51
- Encrypts the provided data using a password.
88
+ Creates a JWS token.
52
89
 
53
- - `data`: The data to encrypt (string or `Uint8Array`).
54
- - `password`: The password to use for encryption (string or `Uint8Array`).
55
- - `options` (optional):
56
- - `iterations`: Number of PBKDF2 iterations (default: `2048`).
57
- - `saltSize`: Size of the random salt in bytes (default: `16`).
58
- - `protectedHeader`: An object containing JWE header parameters to include.
59
- - `alg`: Key Wrapping Algorithm (default: `PBES2-HS256+A128KW`). Supported: `PBES2-HS256+A128KW`, `PBES2-HS384+A192KW`, `PBES2-HS512+A256KW`.
60
- - `enc`: Content Encryption Algorithm (default: `A256GCM`). Supported: `A128GCM`, `A192GCM`, `A256GCM`, `A128CBC-HS256`, `A192CBC-HS384`, `A256CBC-HS512`.
61
- - Other standard JWE or custom header parameters can be added here.
90
+ - `payload`: The data to sign (`string`, `Uint8Array`, or a JSON-serializable `object`).
91
+ - `key`: The signing key (`CryptoKey`, `JWK`, or `Uint8Array` for symmetric keys).
92
+ - `options`:
93
+ - `alg`: (Required) The JWS algorithm (e.g., `"HS256"`, `"RS256"`, `"ES256"`, `"PS256"`).
94
+ - `protectedHeader`: An object for additional JWS Protected Header parameters (e.g., `kid`, `typ`, `cty`, `crit`, `b64`). If `payload` is an object and `typ` is not set, it defaults to `"JWT"`. The `b64` parameter ([RFC7797 section-3](https://datatracker.ietf.org/doc/html/rfc7797#section-3)) controls payload encoding (defaults to `true`, meaning Base64URL encoded).
62
95
 
63
- Returns a Promise resolving to the JWE token string in Compact Serialization format.
96
+ Returns a `Promise<string>` resolving to the JWS token in Compact Serialization format.
64
97
 
65
- **Example:**
98
+ **Example (HS256 with string secret):**
66
99
 
67
100
  ```ts
68
- import { seal } from "unjwt/jwe";
101
+ import { sign } from "unjwt/jws";
69
102
 
70
- const plaintext = "My secret data";
71
- const password = "strongpassword";
103
+ const payload = { message: "My important data" }; // Object payload
104
+ const secret = "supersecretkey"; // String secret, will be imported (length depends on choosen alg)
72
105
 
73
- const token = await seal(plaintext, password, {
74
- protectedHeader: {
75
- alg: "PBES2-HS512+A256KW",
76
- enc: "A256GCM",
77
- },
78
- });
106
+ const token = await sign(payload, secret, { alg: "HS256" });
107
+
108
+ console.log(token);
109
+ // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
110
+ ```
111
+
112
+ **Example (RS256 with CryptoKey):**
113
+
114
+ ```ts
115
+ import { sign } from "unjwt/jws";
116
+ import { generateKey } from "unjwt/jwk";
117
+
118
+ const payload = { userId: 123, permissions: ["read"] };
119
+ const { privateKey } = await generateKey("RS256"); // Generates a CryptoKeyPair
120
+
121
+ const token = await sign(payload, privateKey, { alg: "RS256" });
79
122
 
80
123
  console.log(token);
81
- // eyJhbGciOiJQQkVTMi1IUzUxMitBMjU2S1ciLCJlbmMiOiJBMjU2R0NNIiwicDJzIjoi...
124
+ // eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
82
125
  ```
83
126
 
84
- #### `unseal(token, password, options?)`
127
+ #### `verify(jws, key, options?)`
85
128
 
86
- Decrypts a JWE token using a password.
129
+ Verifies a JWS token.
87
130
 
88
- - `token`: The JWE token string (Compact Serialization).
89
- - `password`: The password used for encryption (string or `Uint8Array`).
131
+ - `jws`: The JWS token string.
132
+ - `key`: The verification key (`CryptoKey`, `JWK`, `Uint8Array`, or a `KeyLookupFunction`).
133
+ A `KeyLookupFunction` has the signature `(header: JWSProtectedHeader) => Promise<CryptoKey | JWK | Uint8Array> | CryptoKey | JWK | Uint8Array`.
90
134
  - `options` (optional):
91
- - `textOutput`: If `false`, returns the decrypted data as a `Uint8Array` instead of a string (default: `true`).
135
+ - `algorithms`: An array of allowed JWS `alg` values. If not provided, the `alg` from the JWS header is used.
136
+ - `critical`: An array of JWS header parameter names that the application understands and processes.
92
137
 
93
- Returns a Promise resolving to the decrypted data (string or `Uint8Array`).
138
+ Returns a `Promise<JWSVerifyResult<T>>` which is an object `{ payload: T, protectedHeader: JWSProtectedHeader }`.
139
+ The `payload` type `T` can be `JWTClaims` (object), `string`, or `Uint8Array` depending on the JWS content and headers.
94
140
 
95
- **Example:**
141
+ **Example (HS256):**
142
+
143
+ ```ts
144
+ import { verify } from "unjwt/jws";
145
+
146
+ const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."; // From HS256 sign example
147
+ const secret = "supersecretkey";
148
+
149
+ const { payload, protectedHeader } = await verify(token, secret);
150
+ console.log(payload); // { message: "My important data" }
151
+ console.log(protectedHeader); // { alg: "HS256", typ: "JWT" }
152
+ ```
153
+
154
+ **Example (RS256 with key lookup):**
96
155
 
97
156
  ```ts
98
- import { unseal } from "unjwt/jwe";
157
+ import { verify } from "unjwt/jws";
158
+ import { generateKey } from "unjwt/jwk";
159
+
160
+ const token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."; // From RS256 sign example
99
161
 
100
- const token =
101
- "eyJhbGciOiJQQkVTMi1IUzUxMitBMjU2S1ciLCJlbmMiOiJBMjU2R0NNIiwicDJzIjoi..."; // From seal example
102
- const password = "strongpassword";
162
+ // Example: using a key lookup function
163
+ const { publicKey: rsaPublicKey } = await generateKey("RS256"); // For example purposes, assume publicKey is stored/fetched during lookup
103
164
 
104
- // Decrypt as string (default)
105
- const decryptedString = await unseal(token, password);
106
- console.log(decryptedString); // "My secret data"
165
+ const keyLookup = async (header) => {
166
+ if (header.alg === "RS256" /* && header.kid === 'expected-kid' */) {
167
+ return rsaPublicKey;
168
+ }
169
+ throw new Error("Unsupported algorithm or key not found");
170
+ };
107
171
 
108
- // Decrypt as Uint8Array
109
- const decryptedBytes = await unseal(token, password, { textOutput: false });
110
- console.log(decryptedBytes); // Uint8Array [ 77, 121, 32, ... ]
172
+ const { payload } = await verify(token, keyLookup, { algorithms: ["RS256"] });
173
+ console.log(payload); // { userId: 123, permissions: ["read"] }
111
174
  ```
112
175
 
113
- ### JWS (JSON Web Signature)
176
+ ### JWE (JSON Web Encryption, [RFC 7516](https://datatracker.ietf.org/doc/html/rfc7516))
114
177
 
115
- This library provides functions to sign (sign) and verify (verify) data according to the JWS specification using HMAC with symmetric keys.
178
+ Functions to encrypt and decrypt data according to the JWE specification.
116
179
 
117
- #### `sign(data, secret, options?)`
180
+ #### `encrypt(payload, key, options)`
118
181
 
119
- Signs the provided payload using a symmetric secret.
182
+ Encrypts data to produce a JWE token.
120
183
 
121
- - `payload`: The data to sign (string or `Uint8Array`).
122
- - `secret`: The symmetric secret key to use for signing. Can be:
123
- - A `string`.
124
- - A `Uint8Array` containing the raw key bytes.
125
- - A symmetric JSON Web Key (`JWK`) object (with `kty: "oct"`).
126
- - `options` (optional):
127
- - `protectedHeader`: An object containing JWS header parameters to include.
128
- - alg: Signing Algorithm (default: `HS256`). Supported: `HS256`, `HS384`, `HS512`.
129
- - Other standard JWS or custom header parameters can be added here.
184
+ - `payload`: The data to encrypt (`string`, `Uint8Array`, or a JSON-serializable `object`).
185
+ - `key`: The Key Encryption Key (KEK) or password (`CryptoKey`, `JWK`, `string`, or `Uint8Array`).
186
+ - `options`:
187
+ - `alg`: (Required) The JWE Key Management algorithm (e.g., `"A128KW"`, `"RSA-OAEP-256"`, `"PBES2-HS256+A128KW"`, `"ECDH-ES+A128KW"`), defaults depends on the key provided.
188
+ - `enc`: (Required) The JWE Content Encryption algorithm (e.g., `"A128GCM"`, `"A256CBC-HS512"`), defaults depends on the key provided.
189
+ - `protectedHeader`: An object for JWE Protected Header parameters (e.g., `kid`, `typ`, `cty`, `crit`, `apu`, `apv`, `p2s`, `p2c`). If `payload` is an object and `typ` is not set, it defaults to `"JWT"`.
190
+ - `cek`: (Optional) Provide your own Content Encryption Key (`CryptoKey` or `Uint8Array`).
191
+ - `contentEncryptionIV`: (Optional) Provide your own Initialization Vector for content encryption (`Uint8Array`).
192
+ - Other algorithm-specific options like `p2s`, `p2c` (for PBES2), `keyManagementIV`, `ecdhPartyUInfo`, `ecdhPartyVInfo`.
130
193
 
131
- Returns a Promise resolving to the JWS token string in Compact Serialization format.
194
+ Returns a `Promise<string>` resolving to the JWE token in Compact Serialization format.
132
195
 
133
- **Example (using string secret):**
196
+ **Example (PBES2 password-based encryption):**
134
197
 
135
198
  ```ts
136
- import { sign } from "unjwt/jws";
199
+ import { encrypt } from "unjwt/jwe";
137
200
 
138
- const payload = JSON.stringify({ message: "My important data" });
139
- const secret = "supersecretkey";
201
+ const plaintext = "Secret message for password protection";
202
+ const password = "myVeryStrongPassword123!";
140
203
 
141
- const token = await sign(payload, secret, {
142
- protectedHeader: {
143
- alg: "HS512",
144
- },
145
- });
146
-
147
- console.log(token);
148
- // eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9...
204
+ // Fallback to PBES2-HS256+A128KW and A128GCM if no `alg` and `end` are provided
205
+ const jweToken = await encrypt(plaintext, password);
206
+ console.log(jweToken);
207
+ // JWE token string...
149
208
  ```
150
209
 
151
- **Example (using JWK secret):**
210
+ **Example (A128KW with A128GCM):**
152
211
 
153
212
  ```ts
154
- import { sign } from "unjwt/jws";
213
+ import { encrypt } from "unjwt/jwe";
155
214
  import { generateKey } from "unjwt/jwk";
156
215
 
157
- const payload = JSON.stringify({ message: "Data signed with JWK" });
158
- // Generate a JWK suitable for HS256
159
- const secretJwk = await generateKey("oct", 256, { alg: "HS256" });
216
+ const payload = { data: "sensitive information" };
217
+ const kek = await generateKey("A128KW"); // AES Key Wrap key
160
218
 
161
- const token = await sign(payload, secretJwk); // alg defaults to HS256
219
+ const jweToken = await encrypt(payload, kek, {
220
+ alg: "A128KW",
221
+ enc: "A128GCM",
222
+ protectedHeader: { kid: "aes-key-1" },
223
+ });
162
224
 
163
- console.log(token);
164
- // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
225
+ console.log(jweToken);
226
+ // eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwia2lkIjoiYWVzLWtleS0xIn0...
165
227
  ```
166
228
 
167
- #### `verify(token, secret, options?)`
229
+ #### `decrypt(jwe, key, options?)`
168
230
 
169
- Verifies a JWS token using a symmetric secret.
231
+ Decrypts a JWE token.
170
232
 
171
- - `token`: The JWS token string (Compact Serialization).
172
- - `secret`: The symmetric secret key used for signing. Can be:
173
- - A `string`.
174
- - A `Uint8Array` containing the raw key bytes.
175
- - A symmetric JSON Web Key (`JWK`) object (with `kty: "oct"`).
233
+ - `jwe`: The JWE token string.
234
+ - `key`: The Key Decryption Key (KDK) or password (`CryptoKey`, `JWK`, `string`, `Uint8Array`, or a `JWEKeyLookupFunction`).
235
+ A `JWEKeyLookupFunction` has the signature `(header: JWEHeaderParameters) => Promise<CryptoKey | JWK | string | Uint8Array> | CryptoKey | JWK | string | Uint8Array`.
176
236
  - `options` (optional):
177
- - `textOutput`: If `false`, returns the verified payload as a `Uint8Array` instead of a string (default: `true`).
237
+ - `algorithms`: Array of allowed JWE Key Management `alg` values.
238
+ - `encryptionAlgorithms`: Array of allowed JWE Content Encryption `enc` values.
239
+ - `critical`: Array of JWE header parameter names that the application understands.
240
+ - `unwrappedKeyAlgorithm`: (For `unwrapKey` internally) Algorithm details for the CEK after unwrapping.
241
+ - `keyUsage`: (For `unwrapKey` internally) Intended usages for the unwrapped CEK.
178
242
 
179
- Returns a Promise resolving to the verified payload (string or `Uint8Array`). Throws an error if the signature is invalid or the token is malformed.
243
+ Returns a `Promise<JWEDecryptResult<T>>` which is an object `{ payload: T, protectedHeader: JWEHeaderParameters, cek: Uint8Array, aad: Uint8Array }`.
244
+ The `payload` type `T` can be `JWTClaims` (object) or `string`.
180
245
 
181
- **Example (using string secret):**
246
+ **Example (PBES2 password-based decryption):**
182
247
 
183
248
  ```ts
184
- import { verify } from "unjwt/jws";
249
+ import { decrypt } from "unjwt/jwe";
250
+ // const jweToken = ...; // From PBES2 encrypt example
251
+ // const password = "myVeryStrongPassword123!";
185
252
 
186
- const token = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9..."; // From sign example
187
- const secret = "supersecretkey";
253
+ const { payload } = await decrypt(jweToken, password);
254
+ ```
188
255
 
189
- // Verify as string (default)
190
- const verifiedString = await verify(token, secret);
191
- console.log(verifiedString); // {"message":"My important data"}
256
+ **Example (A128KW with A128GCM):**
192
257
 
193
- // Verify as Uint8Array
194
- const verifiedBytes = await verify(token, secret, { textOutput: false });
195
- console.log(verifiedBytes); // Uint8Array [ 123, 34, ... ]
258
+ ```ts
259
+ import { decrypt } from "unjwt/jwe";
260
+ // const jweToken = "eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwia2lkIjoiYWVzLWtleS0xIn0...";
261
+ // const kek = ...; // The same AES Key Wrap key used for encryption
262
+
263
+ async function decryptData(jweToken: string, kek: CryptoKey) {
264
+ try {
265
+ const { payload, protectedHeader, cek } = await decrypt(jweToken, kek, {
266
+ algorithms: ["A128KW"],
267
+ encryptionAlgorithms: ["A128GCM"],
268
+ });
269
+ console.log("Decrypted Plaintext:", payload);
270
+ console.log("Protected Header:", protectedHeader);
271
+ // console.log("CEK (Content Encryption Key):", cek);
272
+ } catch (error) {
273
+ console.error("Decryption failed:", error);
274
+ }
275
+ }
196
276
  ```
197
277
 
198
- **Example (using JWK secret):**
278
+ ### JWK (JSON Web Key, [RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517))
279
+
280
+ Utilities for working with JSON Web Keys.
281
+
282
+ #### `generateKey(alg, options?)`
283
+
284
+ Generates a cryptographic key.
285
+
286
+ - `alg`: The JWA algorithm identifier for the key to be generated (e.g., `"HS256"`, `"RS256"`, `"ES256"`, `"A128KW"`, `"A128GCM"`, `"A128CBC-HS256"`).
287
+ - `options` (optional):
288
+ - `toJWK`: If `true`, returns the key(s) in JWK format. Otherwise, returns `CryptoKey`(s) or `Uint8Array` (for composite keys like AES-CBC-HS\*). Default `false`.
289
+ - `extractable`: Boolean, whether the generated `CryptoKey` can be exported. Default `true`.
290
+ - `keyUsage`: Array of `KeyUsage` strings. Defaults are algorithm-specific.
291
+ - `modulusLength`: For RSA keys (e.g., `2048`, `4096`). Default `2048`.
292
+ - `publicExponent`: For RSA keys. Default `new Uint8Array([0x01, 0x00, 0x01])`.
293
+
294
+ Returns a `Promise` resolving to `CryptoKey`, `CryptoKeyPair`, `Uint8Array` (for composite keys), `JWK`, or `{ privateKey: JWK, publicKey: JWK }` depending on `alg` and `options.toJWK`.
295
+
296
+ **Examples:**
199
297
 
200
298
  ```ts
201
- import { verify } from "unjwt/jws";
202
299
  import { generateKey } from "unjwt/jwk";
203
300
 
204
- const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."; // From sign example
205
- const secretJwk = { kty: "oct", k: "...", alg: "HS256" }; // Load or use the generated JWK
301
+ // Generate an HS256 CryptoKey
302
+ const hmacKey = await generateKey("HS256");
303
+ console.log(hmacKey); // CryptoKey
206
304
 
207
- const verifiedPayload = await verify(token, secretJwk);
208
- console.log(verifiedPayload); // {"message":"Data signed with JWK"}
209
- ```
305
+ // Generate an RS256 CryptoKeyPair
306
+ const rsaKeyPair = await generateKey("RS256", { modulusLength: 2048 });
307
+ console.log(rsaKeyPair.publicKey); // CryptoKey
308
+ console.log(rsaKeyPair.privateKey); // CryptoKey
210
309
 
211
- ### JWK (JSON Web Key)
310
+ // Generate an ES384 key pair as JWKs
311
+ const ecJwks = await generateKey("ES384", { toJWK: true });
312
+ console.log(ecJwks.publicKey); // JWK
313
+ console.log(ecJwks.privateKey); // JWK
212
314
 
213
- This library provides functions to generate (`generateKey`), export (`exportKey`), and import (`importKey`) symmetric keys (`kty: "oct"`) using the Web Crypto API.
315
+ // Generate a composite key for A128CBC-HS256 as Uint8Array
316
+ const aesCbcHsKeyBytes = await generateKey("A128CBC-HS256");
317
+ console.log(aesCbcHsKeyBytes); // Uint8Array (32 bytes: 16 for AES, 16 for HMAC)
214
318
 
215
- #### `generateKey(type, length, jwk?)`
319
+ // Generate an A256GCM key as a JWK
320
+ const aesGcmJwk = await generateKey("A256GCM", { toJWK: true });
321
+ console.log(aesGcmJwk); // JWK
322
+ ```
216
323
 
217
- Generates a new key as a JWK object.
324
+ #### `deriveKeyFromPassword(password, alg, options)`
218
325
 
219
- - `type`: The key type. Currently only supports `"oct"` (symmetric).
220
- - `length`(for `"oct"`): Key length in bits (e.g., `128`, `192`, `256`, `512`). Choose a length appropriate for the intended algorithm (e.g., >= 256 for HS256, >= 384 for HS384, >= 512 for HS512).
221
- - `jwk` (optional, for `"oct"`): Any valid JWK param such as a JWA algorithm identifier (e.g., `HS256`, `A128KW`) to include in the generated JWK.
326
+ Derives a key from a password using PBKDF2 for PBES2 algorithms.
222
327
 
223
- Returns a Promise resolving to the generated key as a JWK object (`{ kty: "oct", k: "...", ... }`).
328
+ - `password`: The password (`string` or `Uint8Array`).
329
+ - `alg`: The PBES2 algorithm (e.g., `"PBES2-HS256+A128KW"`).
330
+ - `options`:
331
+ - `salt`: The salt (`Uint8Array`, at least 8 octets).
332
+ - `iterations`: The iteration count (positive integer).
333
+ - `toJWK`: If `true`, returns a `JWK_oct`. Otherwise, `CryptoKey`. Default `false`.
334
+ - `extractable`: Boolean for `CryptoKey`. Default `false` unless `toJWK` is true.
335
+ - `keyUsage`: For `CryptoKey`. Default `["wrapKey", "unwrapKey"]`.
336
+
337
+ Returns a `Promise` resolving to `CryptoKey` or `JWK_oct`.
224
338
 
225
339
  **Example:**
226
340
 
227
341
  ```ts
228
- import { generateKey } from "unjwt/jwk";
342
+ import { deriveKeyFromPassword } from "unjwt/jwk";
343
+ import { randomBytes, textEncoder } from "unjwt/utils";
229
344
 
230
- // Generate a 512-bit key suitable for HS512
231
- const jwk = await generateKey("oct", 512, { alg: "HS512" });
345
+ const password = "mySecretPassword";
346
+ const salt = randomBytes(16);
347
+ const iterations = 4096;
232
348
 
233
- console.log(jwk);
234
- // {
235
- // kty: 'oct',
236
- // k: '...', // base64url encoded key material
237
- // ext: true,
238
- // alg: 'HS512'
239
- // }
349
+ const derivedKey = await deriveKeyFromPassword(password, "PBES2-HS384+A192KW", {
350
+ salt,
351
+ iterations,
352
+ });
353
+ console.log(derivedKey); // CryptoKey for AES-KW (192-bit)
354
+
355
+ const derivedJwk = await deriveKeyFromPassword(password, "PBES2-HS512+A256KW", {
356
+ salt,
357
+ iterations,
358
+ toJWK: true,
359
+ });
360
+ console.log(derivedJwk); // JWK_oct { kty: "oct", k: "...", alg: "A256KW" }
240
361
  ```
241
362
 
242
- #### `exportKey(key)`
363
+ #### `importKey(keyMaterial, alg?)`
243
364
 
244
- Exports a symmetric `CryptoKey` to JWK format.
365
+ Imports a key from various formats. This is a flexible wrapper.
245
366
 
246
- - `key`: The `CryptoKey` to export. It must be `extractable` and currently only of type `"secret"`.
367
+ - `keyMaterial`: The key to import. Can be:
368
+ - `CryptoKey`: Returned directly.
369
+ - `Uint8Array`: Returned directly (treated as raw symmetric key bytes).
370
+ - `string`: Encoded to `Uint8Array` and returned.
371
+ - `JWK_oct` (symmetric JWK with `k` property): The `k` value is Base64URL decoded and returned as `Uint8Array`.
372
+ - Other `JWK` types (asymmetric): Imported into a `CryptoKey`.
373
+ - `alg` (optional): The JWA algorithm string. **Required** when importing asymmetric JWKs (e.g., RSA, EC) to provide context for `crypto.subtle.importKey`.
247
374
 
248
- Returns a Promise resolving to the exported key as a JWK object. The JWK will include `kty`, `k`, `key_ops`, `ext`, and potentially `alg` based on the `CryptoKey`'s algorithm.
375
+ Returns a `Promise` resolving to `CryptoKey` or `Uint8Array`.
249
376
 
250
- **Example:**
377
+ **Examples:**
251
378
 
252
379
  ```ts
253
- import { importKey, exportKey } from "unjwt/jwk";
380
+ import { importKey } from "unjwt/jwk";
381
+ import { textEncoder, base64UrlDecode } from "unjwt/utils";
254
382
 
255
- // First, import or generate a CryptoKey
256
- const rawKeyBytes = new TextEncoder().encode(
257
- "a-very-secure-secret-key-for-hmac",
258
- );
259
- const cryptoKey = await importKey(
260
- rawKeyBytes,
261
- { name: "HMAC", hash: "SHA-256" },
262
- true, // Must be extractable to export
263
- ["sign", "verify"],
264
- );
383
+ // Import raw symmetric key bytes
384
+ const rawBytes = textEncoder.encode("a-32-byte-long-secret-key-123"); // 32 bytes for AES-256 or HS256
385
+ const symmetricKeyBytes = await importKey(rawBytes);
386
+ console.log(symmetricKeyBytes); // Uint8Array
387
+
388
+ // Import a symmetric JWK (kty: "oct")
389
+ const octJwk = {
390
+ kty: "oct",
391
+ k: "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr0", // Example key
392
+ };
393
+ const importedOctBytes = await importKey(octJwk); // Returns Uint8Array
394
+ console.log(importedOctBytes); // Uint8Array (decoded from k)
395
+
396
+ // Import an RSA Public Key JWK
397
+ const rsaPublicJwk = {
398
+ kty: "RSA",
399
+ n: "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3ok92YEnjsADC4Ue87zwRzH2J-TCwlcQrY3E9gGZJZL2g_2_5QjLhL0gR0xYj04_N4M",
400
+ e: "AQAB",
401
+ alg: "RS256",
402
+ kid: "rsa-pub-1",
403
+ };
404
+ const rsaPublicKey = await importKey(rsaPublicJwk, "RS256"); // 'alg' is crucial here
405
+ console.log(rsaPublicKey); // CryptoKey
406
+ ```
407
+
408
+ #### `exportKey(key, jwk?)`
409
+
410
+ Exports a `CryptoKey` to JWK format.
411
+
412
+ - `key`: The `CryptoKey` to export (must be `extractable`).
413
+ - `jwk` (optional): A partial `JWK` object to merge with the exported properties (e.g., to add `kid`, `use`, or override `alg`).
414
+
415
+ Returns a `Promise<JWK>`.
416
+
417
+ **Example:**
265
418
 
266
- // Now export the CryptoKey to JWK
267
- const jwk = await exportKey(cryptoKey);
419
+ ```ts
420
+ import { generateKey, exportKey } from "unjwt/jwk";
421
+
422
+ const { publicKey } = await generateKey("ES256"); // Generates an extractable CryptoKey
268
423
 
424
+ const jwk = await exportKey(publicKey, { kid: "ec-key-001", use: "sig" });
269
425
  console.log(jwk);
270
426
  // {
271
- // kty: 'oct',
272
- // k: 'YS12ZXJ5LXNlY3VyZS1zZWNyZXQta2V5LWZvci1obWFj',
273
- // alg: 'HS256',
274
- // key_ops: [ 'sign', 'verify' ],
275
- // ext: true
427
+ // kty: 'EC',
428
+ // crv: 'P-256',
429
+ // x: '...',
430
+ // y: '...',
431
+ // ext: true,
432
+ // key_ops: [ 'verify' ], // or as per generation
433
+ // kid: 'ec-key-001',
434
+ // use: 'sig'
276
435
  // }
277
436
  ```
278
437
 
279
- #### `importKey(key, algorithm, extractable, keyUsages)`
438
+ #### `wrapKey(alg, keyToWrap, wrappingKey, options?)`
280
439
 
281
- Imports a symmetric key from various formats into a `CryptoKey` object.
440
+ Wraps a Content Encryption Key (CEK).
282
441
 
283
- - `key`: The key material to import. Can be:
284
- - A symmetric JSON Web Key (`JWK`) object (with `kty: "oct"` and a `k` property).
285
- - A `string` representing the raw secret.
286
- - A `Uint8Array` containing the raw key bytes.
287
- - `algorithm`: The Web Crypto `AlgorithmIdentifier` object specifying the algorithm the key will be used for (e.g., `{ name: "HMAC", hash: "SHA-256" }`, `{ name: "AES-GCM", length: 256 }`).
288
- - `extractable`: A boolean indicating whether the generated `CryptoKey` can be exported later (using `exportKey` or `crypto.subtle.exportKey`).
289
- - `keyUsages`: An array of strings indicating the allowed operations for the key (e.g., `["sign", "verify"]`, `["encrypt", "decrypt"]`, `["wrapKey", "unwrapKey"]`).
442
+ - `alg`: The JWA key management algorithm (e.g., `"A128KW"`, `"RSA-OAEP"`).
443
+ - `keyToWrap`: The CEK to wrap (`CryptoKey` or `Uint8Array`).
444
+ - `wrappingKey`: The Key Encryption Key (KEK) (`CryptoKey`, `JWK`, or password `string`/`Uint8Array` for PBES2).
445
+ - `options` (optional): Algorithm-specific options (e.g., `p2s`, `p2c` for PBES2; `iv` for AES-GCMKW).
290
446
 
291
- Returns a Promise resolving to the imported `CryptoKey` object.
447
+ Returns a `Promise<WrapKeyResult>` containing `encryptedKey` and other parameters like `iv`, `tag`, `epk`, `p2s`, `p2c` as needed by the algorithm.
292
448
 
293
- **Example (importing a JWK):**
449
+ **Example (AES Key Wrap):**
294
450
 
295
451
  ```ts
296
- import { importKey } from "unjwt/jwk";
452
+ import { wrapKey, generateKey } from "unjwt/jwk";
453
+ import { randomBytes } from "unjwt/utils";
297
454
 
298
- const jwk = {
299
- kty: "oct",
300
- k: "AyMee--70a4a4xED9qS4sN0KxBas6KTMx7_Q9Gf6nvM", // Example base64url key
301
- alg: "HS256", // Optional hint, but algorithm param below is definitive
302
- };
455
+ const cekToWrap = randomBytes(32); // e.g., a 256-bit AES key as Uint8Array
456
+ const kek = await generateKey("A128KW"); // 128-bit AES Key Wrap key
303
457
 
304
- const cryptoKey = await importKey(
305
- jwk,
306
- { name: "HMAC", hash: "SHA-256" }, // Algorithm for the CryptoKey
307
- false, // Make this key non-extractable
308
- ["verify"], // Allow only verification usage
309
- );
310
-
311
- console.log(cryptoKey);
312
- // CryptoKey { type: 'secret', extractable: false, algorithm: { ... }, usages: [ 'verify' ] }
458
+ const { encryptedKey } = await wrapKey("A128KW", cekToWrap, kek);
459
+ console.log("Wrapped CEK:", encryptedKey); // Uint8Array
313
460
  ```
314
461
 
315
- **Example (importing a raw string):**
462
+ #### `unwrapKey(alg, wrappedKey, unwrappingKey, options?)`
463
+
464
+ Unwraps a Content Encryption Key (CEK).
465
+
466
+ - `alg`: The JWA key management algorithm.
467
+ - `wrappedKey`: The encrypted CEK (`Uint8Array`).
468
+ - `unwrappingKey`: The Key Decryption Key (KDK).
469
+ - `options` (optional):
470
+ - `returnAs`: If `false`, returns `Uint8Array`. If `true` (default) or undefined, returns `CryptoKey`.
471
+ - `unwrappedKeyAlgorithm`: `AlgorithmIdentifier` for the imported CEK if `returnAs` is `true`.
472
+ - `keyUsage`: `KeyUsage[]` for the imported CEK if `returnAs` is `true`.
473
+ - `extractable`: Boolean for the imported CEK.
474
+ - Other algorithm-specific options (e.g., `p2s`, `p2c`, `iv`, `tag`, `epk`).
475
+
476
+ Returns a `Promise` resolving to the unwrapped CEK as `CryptoKey` or `Uint8Array`.
477
+
478
+ **Example (AES Key Unwrap):**
316
479
 
317
480
  ```ts
318
- import { importKey } from "unjwt/jwk";
481
+ import { unwrapKey, generateKey } from "unjwt/jwk";
482
+ // const encryptedKey = ...; // From wrapKey example
483
+ // const kdk = ...; // Same KEK used for wrapping
484
+
485
+ async function unwrapMyKey(encryptedKey: Uint8Array, kdk: CryptoKey) {
486
+ const unwrappedCekBytes = await unwrapKey("A128KW", encryptedKey, kdk, {
487
+ returnAs: false, // Get raw bytes
488
+ });
489
+ console.log("Unwrapped CEK (bytes):", unwrappedCekBytes); // Uint8Array
490
+
491
+ const unwrappedCekCryptoKey = await unwrapKey("A128KW", encryptedKey, kdk, {
492
+ returnAs: true, // Get CryptoKey
493
+ unwrappedKeyAlgorithm: { name: "AES-GCM", length: 256 }, // Specify CEK's intended alg
494
+ keyUsage: ["encrypt", "decrypt"],
495
+ });
496
+ console.log("Unwrapped CEK (CryptoKey):", unwrappedCekCryptoKey); // CryptoKey
497
+ }
498
+ ```
499
+
500
+ #### `importJWKFromPEM(pem, pemType, alg, importOptions?, jwkExtras?)`
501
+
502
+ Imports a key from a PEM-encoded string and converts it to a JWK.
503
+
504
+ - `pem`: The PEM-encoded string (including `-----BEGIN ...-----` and `-----END ...-----` markers).
505
+ - `pemType`: The type of PEM encoding:
506
+ - `"pkcs8"`: For private keys in PKCS#8 format.
507
+ - `"spki"`: For public keys in SPKI format.
508
+ - `"x509"`: For X.509 certificates (extracts the public key).
509
+ - `alg`: The JWA algorithm identifier (e.g., `"RS256"`, `"ES256"`). This is crucial for `crypto.subtle.importKey` to understand the key's intended algorithm and for setting the `'alg'` field in the resulting JWK.
510
+ - `importOptions` (optional): Options for the underlying `crypto.subtle.importKey` call:
511
+ - `extractable`: Boolean, whether the imported `CryptoKey` should be extractable. Defaults to `true`.
512
+ - `keyUsage`: Array of `KeyUsage` strings for the imported `CryptoKey`.
513
+ - `jwkExtras` (optional): An object containing additional properties to merge into the resulting JWK (e.g., `"kid"`, `"use"`).
319
514
 
320
- const rawSecret = "my-raw-password-string";
515
+ Returns a `Promise<JWK>` resolving to the imported key as a JWK.
321
516
 
322
- const cryptoKey = await importKey(
323
- rawSecret,
324
- { name: "HMAC", hash: "SHA-384" },
325
- true,
326
- ["sign"],
517
+ **Example:**
518
+
519
+ ```ts
520
+ import { importJWKFromPEM } from "unjwt/jwk";
521
+
522
+ const rsaPublicJwk = await importJWKFromPEM(
523
+ provess.env.RSA_PEM_SPKI, // PEM string
524
+ "spki",
525
+ "RS256",
526
+ { extractable: false },
527
+ { kid: "my-rsa-key" }, // Additional properties to add to the JWK
327
528
  );
529
+ console.log(rsaPublicJwk);
530
+ // {
531
+ // kty: 'RSA',
532
+ // alg: 'RS256',
533
+ // kid: 'my-rsa-key',
534
+ // n: '...',
535
+ // e: 'AQAB',
536
+ // ext: false,
537
+ // key_ops: [ 'verify' ]
538
+ // }
539
+ ```
540
+
541
+ #### exportJWKToPEM(jwk, pemFormat, algForCryptoKeyImport?)
542
+
543
+ Exports a JWK to a PEM-encoded string.
544
+
545
+ - jwk: The JWK to export.
546
+ - pemFormat: The desired PEM format:
547
+ - "pkcs8": For private keys in PKCS#8 format.
548
+ - "spki": For public keys in SPKI format.
549
+ - algForCryptoKeyImport (optional): If the JWK does not have an 'alg' property, this algorithm hint is required to correctly convert it to a CryptoKey first. This is only needed if the JWK lacks an alg property.
550
+
551
+ Returns a `Promise<string>` resolving to the PEM-encoded key string.
328
552
 
329
- console.log(cryptoKey);
330
- // CryptoKey { type: 'secret', extractable: true, algorithm: { ... }, usages: [ 'sign' ] }
553
+ Example:
554
+
555
+ ```ts
556
+ import { exportJWKToPEM } from "unjwt/jwk";
557
+ import { rsaJWK } from "./keys"; // Assuming you have JWKs in keys.ts
558
+
559
+ const rsaPrivatePem = await exportJWKToPEM(rsaJWK.private, "pkcs8");
560
+ console.log(rsaPrivatePem);
561
+ // -----BEGIN PRIVATE KEY-----
562
+ // MII...
563
+ // -----END PRIVATE KEY-----
564
+
565
+ const rsaPublicSpki = await exportJWKToPEM(
566
+ rsaJWK.public,
567
+ "spki",
568
+ "RS256", // this is required if `rsaJWK.public.alg` is undefined
569
+ );
570
+ console.log(rsaPublicSpki);
571
+ // -----BEGIN PUBLIC KEY-----
572
+ // MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQE...
573
+ // -----END PUBLIC KEY-----
331
574
  ```
332
575
 
576
+ ### Utility Functions
577
+
578
+ `unjwt/utils` exports several helpful functions:
579
+
580
+ - `base64UrlEncode(data: Uint8Array | string): string`
581
+ - `base64UrlDecode(str?: string, toString?: boolean): Uint8Array | string` (Decodes to string by default, or `Uint8Array` if `toString` is `false`)
582
+ - `randomBytes(length: number): Uint8Array`
583
+ - `textEncoder: TextEncoder`
584
+ - `textDecoder: TextDecoder`
585
+ - Type guards: `isJWK(key)`, `isCryptoKey(key)`, `isCryptoKeyPair(keyPair)`
586
+
333
587
  ## Development
334
588
 
335
589
  <details>
@@ -347,6 +601,7 @@ console.log(cryptoKey);
347
601
  ## Credits
348
602
 
349
603
  Originally developed by [Johann Schopplich](https://github.com/johannschopplich/unjwt).
604
+ Heavily inspired by [Filip Skokan's work](https://github.com/panva/jose).
350
605
 
351
606
  ## License
352
607