unjwt 0.4.0 → 0.5.0
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 +360 -190
- package/dist/index.d.mts +6 -5
- package/dist/index.d.ts +6 -5
- package/dist/index.mjs +5 -5
- package/dist/jwe.d.mts +3 -3
- package/dist/jwe.d.ts +3 -3
- package/dist/jwe.mjs +4 -3
- package/dist/jwk.d.mts +2 -2
- package/dist/jwk.d.ts +2 -2
- package/dist/jwk.mjs +2 -2
- package/dist/jws.d.mts +3 -3
- package/dist/jws.d.ts +3 -3
- package/dist/jws.mjs +4 -4
- package/dist/shared/unjwt.B4e_rwol.mjs +453 -0
- package/dist/shared/unjwt.B9uGngq-.d.mts +154 -0
- package/dist/shared/unjwt.BKG_8Y9G.d.ts +189 -0
- package/dist/shared/unjwt.BOEZHxi_.d.ts +154 -0
- package/dist/shared/unjwt.Bzcov22m.mjs +167 -0
- package/dist/shared/unjwt.C0bBN0AC.d.mts +200 -0
- package/dist/shared/unjwt.C0bBN0AC.d.ts +200 -0
- package/dist/shared/unjwt.CJ_ohCFC.d.mts +46 -0
- package/dist/shared/unjwt.CjHZv3F3.mjs +57 -0
- package/dist/shared/unjwt.D90BbcE1.d.mts +52 -0
- package/dist/shared/unjwt.DKq5VF7t.mjs +1186 -0
- package/dist/shared/unjwt.DYFIh1YE.d.mts +100 -0
- package/dist/shared/unjwt.DoVzOnty.d.ts +52 -0
- package/dist/shared/unjwt.Dz5Oe9BG.d.mts +189 -0
- package/dist/shared/unjwt.mc4AEAT7.d.ts +46 -0
- package/dist/shared/unjwt.ppm5Y0k7.d.ts +100 -0
- package/dist/shared/unjwt.sux3_Z19.mjs +101 -0
- package/dist/utils.d.mts +2 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.mjs +1 -0
- package/package.json +8 -4
- package/dist/shared/unjwt.4uzJv0cA.d.mts +0 -110
- package/dist/shared/unjwt.A7HKFx3n.d.mts +0 -52
- package/dist/shared/unjwt.BdjvpIUY.d.mts +0 -79
- package/dist/shared/unjwt.BmjmfFWo.d.ts +0 -79
- package/dist/shared/unjwt.C9ELsKeK.mjs +0 -120
- package/dist/shared/unjwt.CVUxLNoy.d.ts +0 -52
- package/dist/shared/unjwt.ChP7VhqI.d.ts +0 -110
- package/dist/shared/unjwt.DD0p18Gd.mjs +0 -81
- package/dist/shared/unjwt.DGsIvgyn.d.ts +0 -106
- package/dist/shared/unjwt.DPTwlVpK.mjs +0 -28
- package/dist/shared/unjwt.Dr-mxtq9.d.mts +0 -106
- package/dist/shared/unjwt.KOd0vXyx.mjs +0 -90
- package/dist/shared/unjwt._GivNhs2.mjs +0 -213
- package/dist/shared/unjwt.zb2wBNCJ.d.mts +0 -82
- package/dist/shared/unjwt.zb2wBNCJ.d.ts +0 -82
package/README.md
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
<!-- /automd -->
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
A collection of low-level JWT ([RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519)) utilities using the Web Crypto API. Supports:
|
|
11
11
|
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
- JWK (JSON Web Key)
|
|
12
|
+
- **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.
|
|
13
|
+
- **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).
|
|
14
|
+
- **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.
|
|
15
15
|
|
|
16
16
|
## Usage
|
|
17
17
|
|
|
@@ -28,308 +28,477 @@ Import:
|
|
|
28
28
|
|
|
29
29
|
```js
|
|
30
30
|
import { jws, jwe, jwk } from "unjwt";
|
|
31
|
-
|
|
31
|
+
// JWS functions
|
|
32
32
|
import { sign, verify } from "unjwt/jws";
|
|
33
|
-
|
|
33
|
+
// JWE functions
|
|
34
|
+
import { encrypt, decrypt } from "unjwt/jwe";
|
|
35
|
+
// JWK functions
|
|
36
|
+
import {
|
|
37
|
+
generateKey,
|
|
38
|
+
deriveKeyFromPassword,
|
|
39
|
+
importKey,
|
|
40
|
+
exportKey,
|
|
41
|
+
wrapKey,
|
|
42
|
+
unwrapKey,
|
|
43
|
+
} from "unjwt/jwk";
|
|
44
|
+
// Utility functions
|
|
45
|
+
import { base64UrlEncode, base64UrlDecode, randomBytes } from "unjwt/utils";
|
|
34
46
|
```
|
|
35
47
|
|
|
36
48
|
**CDN** (Deno, Bun and Browsers)
|
|
37
49
|
|
|
38
50
|
```js
|
|
51
|
+
// JWS functions
|
|
39
52
|
import { jws, jwe, jwk } from "https://esm.sh/unjwt";
|
|
40
|
-
import { seal, unseal } from "https://esm.sh/unjwt/jwe";
|
|
41
53
|
import { sign, verify } from "https://esm.sh/unjwt/jws";
|
|
42
|
-
|
|
54
|
+
// JWE functions
|
|
55
|
+
import { encrypt, decrypt } from "https://esm.sh/unjwt/jwe";
|
|
56
|
+
// JWK functions
|
|
57
|
+
import {
|
|
58
|
+
generateKey,
|
|
59
|
+
deriveKeyFromPassword,
|
|
60
|
+
importKey,
|
|
61
|
+
exportKey,
|
|
62
|
+
wrapKey,
|
|
63
|
+
unwrapKey,
|
|
64
|
+
} from "https://esm.sh/unjwt/jwk";
|
|
65
|
+
// Utility functions
|
|
66
|
+
import {
|
|
67
|
+
base64UrlEncode,
|
|
68
|
+
base64UrlDecode,
|
|
69
|
+
randomBytes,
|
|
70
|
+
} from "https://esm.sh/unjwt/utils";
|
|
43
71
|
```
|
|
44
72
|
|
|
45
|
-
###
|
|
73
|
+
### JWS (JSON Web Signature, [RFC 7515](https://datatracker.ietf.org/doc/html/rfc7515))
|
|
46
74
|
|
|
47
|
-
|
|
75
|
+
Functions to sign and verify data according to the JWS specification.
|
|
48
76
|
|
|
49
|
-
#### `
|
|
77
|
+
#### `sign(payload, key, options)`
|
|
50
78
|
|
|
51
|
-
|
|
79
|
+
Creates a JWS token.
|
|
52
80
|
|
|
53
|
-
- `
|
|
54
|
-
- `
|
|
55
|
-
- `options
|
|
56
|
-
- `
|
|
57
|
-
- `
|
|
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.
|
|
81
|
+
- `payload`: The data to sign (`string`, `Uint8Array`, or a JSON-serializable `object`).
|
|
82
|
+
- `key`: The signing key (`CryptoKey`, `JWK`, or `Uint8Array` for symmetric keys).
|
|
83
|
+
- `options`:
|
|
84
|
+
- `alg`: (Required) The JWS algorithm (e.g., `"HS256"`, `"RS256"`, `"ES256"`, `"PS256"`).
|
|
85
|
+
- `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
86
|
|
|
63
|
-
Returns a Promise resolving to the
|
|
87
|
+
Returns a `Promise<string>` resolving to the JWS token in Compact Serialization format.
|
|
64
88
|
|
|
65
|
-
**Example:**
|
|
89
|
+
**Example (HS256 with string secret):**
|
|
66
90
|
|
|
67
91
|
```ts
|
|
68
|
-
import {
|
|
92
|
+
import { sign } from "unjwt/jws";
|
|
69
93
|
|
|
70
|
-
const
|
|
71
|
-
const
|
|
94
|
+
const payload = { message: "My important data" }; // Object payload
|
|
95
|
+
const secret = "supersecretkey"; // String secret, will be imported (length depends on choosen alg)
|
|
72
96
|
|
|
73
|
-
const token = await
|
|
74
|
-
protectedHeader: {
|
|
75
|
-
alg: "PBES2-HS512+A256KW",
|
|
76
|
-
enc: "A256GCM",
|
|
77
|
-
},
|
|
78
|
-
});
|
|
97
|
+
const token = await sign(payload, secret, { alg: "HS256" });
|
|
79
98
|
|
|
80
99
|
console.log(token);
|
|
81
|
-
//
|
|
100
|
+
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
82
101
|
```
|
|
83
102
|
|
|
84
|
-
|
|
103
|
+
**Example (RS256 with CryptoKey):**
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { sign } from "unjwt/jws";
|
|
107
|
+
import { generateKey } from "unjwt/jwk";
|
|
85
108
|
|
|
86
|
-
|
|
109
|
+
const payload = { userId: 123, permissions: ["read"] };
|
|
110
|
+
const { privateKey } = await generateKey("RS256"); // Generates a CryptoKeyPair
|
|
87
111
|
|
|
88
|
-
|
|
89
|
-
|
|
112
|
+
const token = await sign(payload, privateKey, { alg: "RS256" });
|
|
113
|
+
|
|
114
|
+
console.log(token);
|
|
115
|
+
// eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### `verify(jws, key, options?)`
|
|
119
|
+
|
|
120
|
+
Verifies a JWS token.
|
|
121
|
+
|
|
122
|
+
- `jws`: The JWS token string.
|
|
123
|
+
- `key`: The verification key (`CryptoKey`, `JWK`, `Uint8Array`, or a `KeyLookupFunction`).
|
|
124
|
+
A `KeyLookupFunction` has the signature `(header: JWSProtectedHeader) => Promise<CryptoKey | JWK | Uint8Array> | CryptoKey | JWK | Uint8Array`.
|
|
90
125
|
- `options` (optional):
|
|
91
|
-
- `
|
|
126
|
+
- `algorithms`: An array of allowed JWS `alg` values. If not provided, the `alg` from the JWS header is used.
|
|
127
|
+
- `critical`: An array of JWS header parameter names that the application understands and processes.
|
|
92
128
|
|
|
93
|
-
Returns a Promise
|
|
129
|
+
Returns a `Promise<JWSVerifyResult<T>>` which is an object `{ payload: T, protectedHeader: JWSProtectedHeader }`.
|
|
130
|
+
The `payload` type `T` can be `JWTClaims` (object), `string`, or `Uint8Array` depending on the JWS content and headers.
|
|
94
131
|
|
|
95
|
-
**Example:**
|
|
132
|
+
**Example (HS256):**
|
|
96
133
|
|
|
97
134
|
```ts
|
|
98
|
-
import {
|
|
135
|
+
import { verify } from "unjwt/jws";
|
|
99
136
|
|
|
100
|
-
const token =
|
|
101
|
-
|
|
102
|
-
const password = "strongpassword";
|
|
137
|
+
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."; // From HS256 sign example
|
|
138
|
+
const secret = "supersecretkey";
|
|
103
139
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
console.log(
|
|
140
|
+
const { payload, protectedHeader } = await verify(token, secret);
|
|
141
|
+
console.log(payload); // { message: "My important data" }
|
|
142
|
+
console.log(protectedHeader); // { alg: "HS256", typ: "JWT" }
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Example (RS256 with key lookup):**
|
|
107
146
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
147
|
+
```ts
|
|
148
|
+
import { verify } from "unjwt/jws";
|
|
149
|
+
import { generateKey } from "unjwt/jwk";
|
|
150
|
+
|
|
151
|
+
const token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."; // From RS256 sign example
|
|
152
|
+
|
|
153
|
+
// Example: using a key lookup function
|
|
154
|
+
const { publicKey: rsaPublicKey } = await generateKey("RS256"); // For example purposes, assume publicKey is stored/fetched during lookup
|
|
155
|
+
|
|
156
|
+
const keyLookup = async (header) => {
|
|
157
|
+
if (header.alg === "RS256" /* && header.kid === 'expected-kid' */) {
|
|
158
|
+
return rsaPublicKey;
|
|
159
|
+
}
|
|
160
|
+
throw new Error("Unsupported algorithm or key not found");
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const { payload } = await verify(token, keyLookup, { algorithms: ["RS256"] });
|
|
164
|
+
console.log(payload); // { userId: 123, permissions: ["read"] }
|
|
111
165
|
```
|
|
112
166
|
|
|
113
|
-
###
|
|
167
|
+
### JWE (JSON Web Encryption, [RFC 7516](https://datatracker.ietf.org/doc/html/rfc7516))
|
|
114
168
|
|
|
115
|
-
|
|
169
|
+
Functions to encrypt and decrypt data according to the JWE specification.
|
|
116
170
|
|
|
117
|
-
#### `
|
|
171
|
+
#### `encrypt(payload, key, options)`
|
|
118
172
|
|
|
119
|
-
|
|
173
|
+
Encrypts data to produce a JWE token.
|
|
120
174
|
|
|
121
|
-
- `payload`: The data to
|
|
122
|
-
- `
|
|
123
|
-
|
|
124
|
-
-
|
|
125
|
-
-
|
|
126
|
-
- `
|
|
127
|
-
- `
|
|
128
|
-
|
|
129
|
-
|
|
175
|
+
- `payload`: The data to encrypt (`string`, `Uint8Array`, or a JSON-serializable `object`).
|
|
176
|
+
- `key`: The Key Encryption Key (KEK) or password (`CryptoKey`, `JWK`, `string`, or `Uint8Array`).
|
|
177
|
+
- `options`:
|
|
178
|
+
- `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.
|
|
179
|
+
- `enc`: (Required) The JWE Content Encryption algorithm (e.g., `"A128GCM"`, `"A256CBC-HS512"`), defaults depends on the key provided.
|
|
180
|
+
- `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"`.
|
|
181
|
+
- `cek`: (Optional) Provide your own Content Encryption Key (`CryptoKey` or `Uint8Array`).
|
|
182
|
+
- `contentEncryptionIV`: (Optional) Provide your own Initialization Vector for content encryption (`Uint8Array`).
|
|
183
|
+
- Other algorithm-specific options like `p2s`, `p2c` (for PBES2), `keyManagementIV`, `ecdhPartyUInfo`, `ecdhPartyVInfo`.
|
|
130
184
|
|
|
131
|
-
Returns a Promise resolving to the
|
|
185
|
+
Returns a `Promise<string>` resolving to the JWE token in Compact Serialization format.
|
|
132
186
|
|
|
133
|
-
**Example (
|
|
187
|
+
**Example (PBES2 password-based encryption):**
|
|
134
188
|
|
|
135
189
|
```ts
|
|
136
|
-
import {
|
|
190
|
+
import { encrypt } from "unjwt/jwe";
|
|
137
191
|
|
|
138
|
-
const
|
|
139
|
-
const
|
|
192
|
+
const plaintext = "Secret message for password protection";
|
|
193
|
+
const password = "myVeryStrongPassword123!";
|
|
140
194
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
console.log(token);
|
|
148
|
-
// eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9...
|
|
195
|
+
// Fallback to PBES2-HS256+A128KW and A128GCM if no `alg` and `end` are provided
|
|
196
|
+
const jweToken = await encrypt(plaintext, password);
|
|
197
|
+
console.log(jweToken);
|
|
198
|
+
// JWE token string...
|
|
149
199
|
```
|
|
150
200
|
|
|
151
|
-
**Example (
|
|
201
|
+
**Example (A128KW with A128GCM):**
|
|
152
202
|
|
|
153
203
|
```ts
|
|
154
|
-
import {
|
|
204
|
+
import { encrypt } from "unjwt/jwe";
|
|
155
205
|
import { generateKey } from "unjwt/jwk";
|
|
156
206
|
|
|
157
|
-
const payload =
|
|
158
|
-
|
|
159
|
-
const secretJwk = await generateKey("oct", 256, { alg: "HS256" });
|
|
207
|
+
const payload = { data: "sensitive information" };
|
|
208
|
+
const kek = await generateKey("A128KW"); // AES Key Wrap key
|
|
160
209
|
|
|
161
|
-
const
|
|
210
|
+
const jweToken = await encrypt(payload, kek, {
|
|
211
|
+
alg: "A128KW",
|
|
212
|
+
enc: "A128GCM",
|
|
213
|
+
protectedHeader: { kid: "aes-key-1" },
|
|
214
|
+
});
|
|
162
215
|
|
|
163
|
-
console.log(
|
|
164
|
-
//
|
|
216
|
+
console.log(jweToken);
|
|
217
|
+
// eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwia2lkIjoiYWVzLWtleS0xIn0...
|
|
165
218
|
```
|
|
166
219
|
|
|
167
|
-
#### `
|
|
220
|
+
#### `decrypt(jwe, key, options?)`
|
|
168
221
|
|
|
169
|
-
|
|
222
|
+
Decrypts a JWE token.
|
|
170
223
|
|
|
171
|
-
- `
|
|
172
|
-
- `
|
|
173
|
-
|
|
174
|
-
- A `Uint8Array` containing the raw key bytes.
|
|
175
|
-
- A symmetric JSON Web Key (`JWK`) object (with `kty: "oct"`).
|
|
224
|
+
- `jwe`: The JWE token string.
|
|
225
|
+
- `key`: The Key Decryption Key (KDK) or password (`CryptoKey`, `JWK`, `string`, `Uint8Array`, or a `JWEKeyLookupFunction`).
|
|
226
|
+
A `JWEKeyLookupFunction` has the signature `(header: JWEHeaderParameters) => Promise<CryptoKey | JWK | string | Uint8Array> | CryptoKey | JWK | string | Uint8Array`.
|
|
176
227
|
- `options` (optional):
|
|
177
|
-
- `
|
|
228
|
+
- `algorithms`: Array of allowed JWE Key Management `alg` values.
|
|
229
|
+
- `encryptionAlgorithms`: Array of allowed JWE Content Encryption `enc` values.
|
|
230
|
+
- `critical`: Array of JWE header parameter names that the application understands.
|
|
231
|
+
- `unwrappedKeyAlgorithm`: (For `unwrapKey` internally) Algorithm details for the CEK after unwrapping.
|
|
232
|
+
- `keyUsage`: (For `unwrapKey` internally) Intended usages for the unwrapped CEK.
|
|
178
233
|
|
|
179
|
-
Returns a Promise
|
|
234
|
+
Returns a `Promise<JWEDecryptResult<T>>` which is an object `{ payload: T, protectedHeader: JWEHeaderParameters, cek: Uint8Array, aad: Uint8Array }`.
|
|
235
|
+
The `payload` type `T` can be `JWTClaims` (object) or `string`.
|
|
180
236
|
|
|
181
|
-
**Example (
|
|
237
|
+
**Example (PBES2 password-based decryption):**
|
|
182
238
|
|
|
183
239
|
```ts
|
|
184
|
-
import {
|
|
240
|
+
import { decrypt } from "unjwt/jwe";
|
|
241
|
+
// const jweToken = ...; // From PBES2 encrypt example
|
|
242
|
+
// const password = "myVeryStrongPassword123!";
|
|
185
243
|
|
|
186
|
-
const
|
|
187
|
-
|
|
244
|
+
const { payload } = await decrypt(jweToken, password);
|
|
245
|
+
```
|
|
188
246
|
|
|
189
|
-
|
|
190
|
-
const verifiedString = await verify(token, secret);
|
|
191
|
-
console.log(verifiedString); // {"message":"My important data"}
|
|
247
|
+
**Example (A128KW with A128GCM):**
|
|
192
248
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
249
|
+
```ts
|
|
250
|
+
import { decrypt } from "unjwt/jwe";
|
|
251
|
+
// const jweToken = "eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwia2lkIjoiYWVzLWtleS0xIn0...";
|
|
252
|
+
// const kek = ...; // The same AES Key Wrap key used for encryption
|
|
253
|
+
|
|
254
|
+
async function decryptData(jweToken: string, kek: CryptoKey) {
|
|
255
|
+
try {
|
|
256
|
+
const { payload, protectedHeader, cek } = await decrypt(jweToken, kek, {
|
|
257
|
+
algorithms: ["A128KW"],
|
|
258
|
+
encryptionAlgorithms: ["A128GCM"],
|
|
259
|
+
});
|
|
260
|
+
console.log("Decrypted Plaintext:", payload);
|
|
261
|
+
console.log("Protected Header:", protectedHeader);
|
|
262
|
+
// console.log("CEK (Content Encryption Key):", cek);
|
|
263
|
+
} catch (error) {
|
|
264
|
+
console.error("Decryption failed:", error);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
196
267
|
```
|
|
197
268
|
|
|
198
|
-
|
|
269
|
+
### JWK (JSON Web Key, [RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517))
|
|
270
|
+
|
|
271
|
+
Utilities for working with JSON Web Keys.
|
|
272
|
+
|
|
273
|
+
#### `generateKey(alg, options?)`
|
|
274
|
+
|
|
275
|
+
Generates a cryptographic key.
|
|
276
|
+
|
|
277
|
+
- `alg`: The JWA algorithm identifier for the key to be generated (e.g., `"HS256"`, `"RS256"`, `"ES256"`, `"A128KW"`, `"A128GCM"`, `"A128CBC-HS256"`).
|
|
278
|
+
- `options` (optional):
|
|
279
|
+
- `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`.
|
|
280
|
+
- `extractable`: Boolean, whether the generated `CryptoKey` can be exported. Default `true`.
|
|
281
|
+
- `keyUsage`: Array of `KeyUsage` strings. Defaults are algorithm-specific.
|
|
282
|
+
- `modulusLength`: For RSA keys (e.g., `2048`, `4096`). Default `2048`.
|
|
283
|
+
- `publicExponent`: For RSA keys. Default `new Uint8Array([0x01, 0x00, 0x01])`.
|
|
284
|
+
|
|
285
|
+
Returns a `Promise` resolving to `CryptoKey`, `CryptoKeyPair`, `Uint8Array` (for composite keys), `JWK`, or `{ privateKey: JWK, publicKey: JWK }` depending on `alg` and `options.toJWK`.
|
|
286
|
+
|
|
287
|
+
**Examples:**
|
|
199
288
|
|
|
200
289
|
```ts
|
|
201
|
-
import { verify } from "unjwt/jws";
|
|
202
290
|
import { generateKey } from "unjwt/jwk";
|
|
203
291
|
|
|
204
|
-
|
|
205
|
-
const
|
|
292
|
+
// Generate an HS256 CryptoKey
|
|
293
|
+
const hmacKey = await generateKey("HS256");
|
|
294
|
+
console.log(hmacKey); // CryptoKey
|
|
206
295
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
296
|
+
// Generate an RS256 CryptoKeyPair
|
|
297
|
+
const rsaKeyPair = await generateKey("RS256", { modulusLength: 2048 });
|
|
298
|
+
console.log(rsaKeyPair.publicKey); // CryptoKey
|
|
299
|
+
console.log(rsaKeyPair.privateKey); // CryptoKey
|
|
210
300
|
|
|
211
|
-
|
|
301
|
+
// Generate an ES384 key pair as JWKs
|
|
302
|
+
const ecJwks = await generateKey("ES384", { toJWK: true });
|
|
303
|
+
console.log(ecJwks.publicKey); // JWK
|
|
304
|
+
console.log(ecJwks.privateKey); // JWK
|
|
212
305
|
|
|
213
|
-
|
|
306
|
+
// Generate a composite key for A128CBC-HS256 as Uint8Array
|
|
307
|
+
const aesCbcHsKeyBytes = await generateKey("A128CBC-HS256");
|
|
308
|
+
console.log(aesCbcHsKeyBytes); // Uint8Array (32 bytes: 16 for AES, 16 for HMAC)
|
|
214
309
|
|
|
215
|
-
|
|
310
|
+
// Generate an A256GCM key as a JWK
|
|
311
|
+
const aesGcmJwk = await generateKey("A256GCM", { toJWK: true });
|
|
312
|
+
console.log(aesGcmJwk); // JWK
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
#### `deriveKeyFromPassword(password, alg, options)`
|
|
216
316
|
|
|
217
|
-
|
|
317
|
+
Derives a key from a password using PBKDF2 for PBES2 algorithms.
|
|
218
318
|
|
|
219
|
-
- `
|
|
220
|
-
- `
|
|
221
|
-
- `
|
|
319
|
+
- `password`: The password (`string` or `Uint8Array`).
|
|
320
|
+
- `alg`: The PBES2 algorithm (e.g., `"PBES2-HS256+A128KW"`).
|
|
321
|
+
- `options`:
|
|
322
|
+
- `salt`: The salt (`Uint8Array`, at least 8 octets).
|
|
323
|
+
- `iterations`: The iteration count (positive integer).
|
|
324
|
+
- `toJWK`: If `true`, returns a `JWK_oct`. Otherwise, `CryptoKey`. Default `false`.
|
|
325
|
+
- `extractable`: Boolean for `CryptoKey`. Default `false` unless `toJWK` is true.
|
|
326
|
+
- `keyUsage`: For `CryptoKey`. Default `["wrapKey", "unwrapKey"]`.
|
|
222
327
|
|
|
223
|
-
Returns a Promise resolving to
|
|
328
|
+
Returns a `Promise` resolving to `CryptoKey` or `JWK_oct`.
|
|
224
329
|
|
|
225
330
|
**Example:**
|
|
226
331
|
|
|
227
332
|
```ts
|
|
228
|
-
import {
|
|
333
|
+
import { deriveKeyFromPassword } from "unjwt/jwk";
|
|
334
|
+
import { randomBytes, textEncoder } from "unjwt/utils";
|
|
229
335
|
|
|
230
|
-
|
|
231
|
-
const
|
|
336
|
+
const password = "mySecretPassword";
|
|
337
|
+
const salt = randomBytes(16);
|
|
338
|
+
const iterations = 4096;
|
|
232
339
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
|
|
340
|
+
const derivedKey = await deriveKeyFromPassword(password, "PBES2-HS384+A192KW", {
|
|
341
|
+
salt,
|
|
342
|
+
iterations,
|
|
343
|
+
});
|
|
344
|
+
console.log(derivedKey); // CryptoKey for AES-KW (192-bit)
|
|
345
|
+
|
|
346
|
+
const derivedJwk = await deriveKeyFromPassword(password, "PBES2-HS512+A256KW", {
|
|
347
|
+
salt,
|
|
348
|
+
iterations,
|
|
349
|
+
toJWK: true,
|
|
350
|
+
});
|
|
351
|
+
console.log(derivedJwk); // JWK_oct { kty: "oct", k: "...", alg: "A256KW" }
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
#### `importKey(keyMaterial, alg?)`
|
|
355
|
+
|
|
356
|
+
Imports a key from various formats. This is a flexible wrapper.
|
|
357
|
+
|
|
358
|
+
- `keyMaterial`: The key to import. Can be:
|
|
359
|
+
- `CryptoKey`: Returned directly.
|
|
360
|
+
- `Uint8Array`: Returned directly (treated as raw symmetric key bytes).
|
|
361
|
+
- `string`: Encoded to `Uint8Array` and returned.
|
|
362
|
+
- `JWK_oct` (symmetric JWK with `k` property): The `k` value is Base64URL decoded and returned as `Uint8Array`.
|
|
363
|
+
- Other `JWK` types (asymmetric): Imported into a `CryptoKey`.
|
|
364
|
+
- `alg` (optional): The JWA algorithm string. **Required** when importing asymmetric JWKs (e.g., RSA, EC) to provide context for `crypto.subtle.importKey`.
|
|
365
|
+
|
|
366
|
+
Returns a `Promise` resolving to `CryptoKey` or `Uint8Array`.
|
|
367
|
+
|
|
368
|
+
**Examples:**
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
import { importKey } from "unjwt/jwk";
|
|
372
|
+
import { textEncoder, base64UrlDecode } from "unjwt/utils";
|
|
373
|
+
|
|
374
|
+
// Import raw symmetric key bytes
|
|
375
|
+
const rawBytes = textEncoder.encode("a-32-byte-long-secret-key-123"); // 32 bytes for AES-256 or HS256
|
|
376
|
+
const symmetricKeyBytes = await importKey(rawBytes);
|
|
377
|
+
console.log(symmetricKeyBytes); // Uint8Array
|
|
378
|
+
|
|
379
|
+
// Import a symmetric JWK (kty: "oct")
|
|
380
|
+
const octJwk = {
|
|
381
|
+
kty: "oct",
|
|
382
|
+
k: "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr0", // Example key
|
|
383
|
+
};
|
|
384
|
+
const importedOctBytes = await importKey(octJwk); // Returns Uint8Array
|
|
385
|
+
console.log(importedOctBytes); // Uint8Array (decoded from k)
|
|
386
|
+
|
|
387
|
+
// Import an RSA Public Key JWK
|
|
388
|
+
const rsaPublicJwk = {
|
|
389
|
+
kty: "RSA",
|
|
390
|
+
n: "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3ok92YEnjsADC4Ue87zwRzH2J-TCwlcQrY3E9gGZJZL2g_2_5QjLhL0gR0xYj04_N4M",
|
|
391
|
+
e: "AQAB",
|
|
392
|
+
alg: "RS256",
|
|
393
|
+
kid: "rsa-pub-1",
|
|
394
|
+
};
|
|
395
|
+
const rsaPublicKey = await importKey(rsaPublicJwk, "RS256"); // 'alg' is crucial here
|
|
396
|
+
console.log(rsaPublicKey); // CryptoKey
|
|
240
397
|
```
|
|
241
398
|
|
|
242
|
-
#### `exportKey(key)`
|
|
399
|
+
#### `exportKey(key, jwk?)`
|
|
243
400
|
|
|
244
|
-
Exports a
|
|
401
|
+
Exports a `CryptoKey` to JWK format.
|
|
245
402
|
|
|
246
|
-
- `key`: The `CryptoKey` to export
|
|
403
|
+
- `key`: The `CryptoKey` to export (must be `extractable`).
|
|
404
|
+
- `jwk` (optional): A partial `JWK` object to merge with the exported properties (e.g., to add `kid`, `use`, or override `alg`).
|
|
247
405
|
|
|
248
|
-
Returns a Promise
|
|
406
|
+
Returns a `Promise<JWK>`.
|
|
249
407
|
|
|
250
408
|
**Example:**
|
|
251
409
|
|
|
252
410
|
```ts
|
|
253
|
-
import {
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
);
|
|
265
|
-
|
|
266
|
-
// Now export the CryptoKey to JWK
|
|
267
|
-
const jwk = await exportKey(cryptoKey);
|
|
411
|
+
import { generateKey, exportKey } from "unjwt/jwk";
|
|
412
|
+
|
|
413
|
+
const { publicKey } = await generateKey("ES256"); // Generates an extractable CryptoKey
|
|
268
414
|
|
|
415
|
+
const jwk = await exportKey(publicKey, { kid: "ec-key-001", use: "sig" });
|
|
269
416
|
console.log(jwk);
|
|
270
417
|
// {
|
|
271
|
-
// kty: '
|
|
272
|
-
//
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
// ext: true
|
|
418
|
+
// kty: 'EC',
|
|
419
|
+
// crv: 'P-256',
|
|
420
|
+
// x: '...',
|
|
421
|
+
// y: '...',
|
|
422
|
+
// ext: true,
|
|
423
|
+
// key_ops: [ 'verify' ], // or as per generation
|
|
424
|
+
// kid: 'ec-key-001',
|
|
425
|
+
// use: 'sig'
|
|
276
426
|
// }
|
|
277
427
|
```
|
|
278
428
|
|
|
279
|
-
#### `
|
|
429
|
+
#### `wrapKey(alg, keyToWrap, wrappingKey, options?)`
|
|
280
430
|
|
|
281
|
-
|
|
431
|
+
Wraps a Content Encryption Key (CEK).
|
|
282
432
|
|
|
283
|
-
- `
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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"]`).
|
|
433
|
+
- `alg`: The JWA key management algorithm (e.g., `"A128KW"`, `"RSA-OAEP"`).
|
|
434
|
+
- `keyToWrap`: The CEK to wrap (`CryptoKey` or `Uint8Array`).
|
|
435
|
+
- `wrappingKey`: The Key Encryption Key (KEK) (`CryptoKey`, `JWK`, or password `string`/`Uint8Array` for PBES2).
|
|
436
|
+
- `options` (optional): Algorithm-specific options (e.g., `p2s`, `p2c` for PBES2; `iv` for AES-GCMKW).
|
|
290
437
|
|
|
291
|
-
Returns a Promise
|
|
438
|
+
Returns a `Promise<WrapKeyResult>` containing `encryptedKey` and other parameters like `iv`, `tag`, `epk`, `p2s`, `p2c` as needed by the algorithm.
|
|
292
439
|
|
|
293
|
-
**Example (
|
|
440
|
+
**Example (AES Key Wrap):**
|
|
294
441
|
|
|
295
442
|
```ts
|
|
296
|
-
import {
|
|
443
|
+
import { wrapKey, generateKey } from "unjwt/jwk";
|
|
444
|
+
import { randomBytes } from "unjwt/utils";
|
|
297
445
|
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
k: "AyMee--70a4a4xED9qS4sN0KxBas6KTMx7_Q9Gf6nvM", // Example base64url key
|
|
301
|
-
alg: "HS256", // Optional hint, but algorithm param below is definitive
|
|
302
|
-
};
|
|
303
|
-
|
|
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
|
-
);
|
|
446
|
+
const cekToWrap = randomBytes(32); // e.g., a 256-bit AES key as Uint8Array
|
|
447
|
+
const kek = await generateKey("A128KW"); // 128-bit AES Key Wrap key
|
|
310
448
|
|
|
311
|
-
|
|
312
|
-
|
|
449
|
+
const { encryptedKey } = await wrapKey("A128KW", cekToWrap, kek);
|
|
450
|
+
console.log("Wrapped CEK:", encryptedKey); // Uint8Array
|
|
313
451
|
```
|
|
314
452
|
|
|
315
|
-
|
|
453
|
+
#### `unwrapKey(alg, wrappedKey, unwrappingKey, options?)`
|
|
316
454
|
|
|
317
|
-
|
|
318
|
-
import { importKey } from "unjwt/jwk";
|
|
455
|
+
Unwraps a Content Encryption Key (CEK).
|
|
319
456
|
|
|
320
|
-
|
|
457
|
+
- `alg`: The JWA key management algorithm.
|
|
458
|
+
- `wrappedKey`: The encrypted CEK (`Uint8Array`).
|
|
459
|
+
- `unwrappingKey`: The Key Decryption Key (KDK).
|
|
460
|
+
- `options` (optional):
|
|
461
|
+
- `returnAs`: If `false`, returns `Uint8Array`. If `true` (default) or undefined, returns `CryptoKey`.
|
|
462
|
+
- `unwrappedKeyAlgorithm`: `AlgorithmIdentifier` for the imported CEK if `returnAs` is `true`.
|
|
463
|
+
- `keyUsage`: `KeyUsage[]` for the imported CEK if `returnAs` is `true`.
|
|
464
|
+
- `extractable`: Boolean for the imported CEK.
|
|
465
|
+
- Other algorithm-specific options (e.g., `p2s`, `p2c`, `iv`, `tag`, `epk`).
|
|
321
466
|
|
|
322
|
-
|
|
323
|
-
rawSecret,
|
|
324
|
-
{ name: "HMAC", hash: "SHA-384" },
|
|
325
|
-
true,
|
|
326
|
-
["sign"],
|
|
327
|
-
);
|
|
467
|
+
Returns a `Promise` resolving to the unwrapped CEK as `CryptoKey` or `Uint8Array`.
|
|
328
468
|
|
|
329
|
-
|
|
330
|
-
|
|
469
|
+
**Example (AES Key Unwrap):**
|
|
470
|
+
|
|
471
|
+
```ts
|
|
472
|
+
import { unwrapKey, generateKey } from "unjwt/jwk";
|
|
473
|
+
// const encryptedKey = ...; // From wrapKey example
|
|
474
|
+
// const kdk = ...; // Same KEK used for wrapping
|
|
475
|
+
|
|
476
|
+
async function unwrapMyKey(encryptedKey: Uint8Array, kdk: CryptoKey) {
|
|
477
|
+
const unwrappedCekBytes = await unwrapKey("A128KW", encryptedKey, kdk, {
|
|
478
|
+
returnAs: false, // Get raw bytes
|
|
479
|
+
});
|
|
480
|
+
console.log("Unwrapped CEK (bytes):", unwrappedCekBytes); // Uint8Array
|
|
481
|
+
|
|
482
|
+
const unwrappedCekCryptoKey = await unwrapKey("A128KW", encryptedKey, kdk, {
|
|
483
|
+
returnAs: true, // Get CryptoKey
|
|
484
|
+
unwrappedKeyAlgorithm: { name: "AES-GCM", length: 256 }, // Specify CEK's intended alg
|
|
485
|
+
keyUsage: ["encrypt", "decrypt"],
|
|
486
|
+
});
|
|
487
|
+
console.log("Unwrapped CEK (CryptoKey):", unwrappedCekCryptoKey); // CryptoKey
|
|
488
|
+
}
|
|
331
489
|
```
|
|
332
490
|
|
|
491
|
+
### Utility Functions
|
|
492
|
+
|
|
493
|
+
`unjwt/utils` exports several helpful functions:
|
|
494
|
+
|
|
495
|
+
- `base64UrlEncode(data: Uint8Array | string): string`
|
|
496
|
+
- `base64UrlDecode(str?: string, toString?: boolean): Uint8Array | string` (Decodes to string by default, or `Uint8Array` if `toString` is `false`)
|
|
497
|
+
- `randomBytes(length: number): Uint8Array`
|
|
498
|
+
- `textEncoder: TextEncoder`
|
|
499
|
+
- `textDecoder: TextDecoder`
|
|
500
|
+
- Type guards: `isJWK(key)`, `isCryptoKey(key)`, `isCryptoKeyPair(keyPair)`
|
|
501
|
+
|
|
333
502
|
## Development
|
|
334
503
|
|
|
335
504
|
<details>
|
|
@@ -347,6 +516,7 @@ console.log(cryptoKey);
|
|
|
347
516
|
## Credits
|
|
348
517
|
|
|
349
518
|
Originally developed by [Johann Schopplich](https://github.com/johannschopplich/unjwt).
|
|
519
|
+
Heavily inspired by [Filip Skokan's work](https://github.com/panva/jose).
|
|
350
520
|
|
|
351
521
|
## License
|
|
352
522
|
|