unjwt 0.3.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/LICENSE +21 -21
- package/README.md +518 -48
- package/dist/index.d.mts +6 -79
- package/dist/index.d.ts +6 -79
- package/dist/index.mjs +5 -141
- package/dist/jwe.d.mts +3 -0
- package/dist/jwe.d.ts +3 -0
- package/dist/jwe.mjs +4 -0
- package/dist/jwk.d.mts +2 -0
- package/dist/jwk.d.ts +2 -0
- package/dist/jwk.mjs +2 -0
- package/dist/jws.d.mts +3 -0
- package/dist/jws.d.ts +3 -0
- package/dist/jws.mjs +4 -0
- 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 +44 -84
- package/dist/crypto/node.cjs +0 -19
- package/dist/crypto/node.d.cts +0 -5
- package/dist/crypto/node.d.mts +0 -5
- package/dist/crypto/node.d.ts +0 -5
- package/dist/crypto/node.mjs +0 -11
- package/dist/crypto/web.cjs +0 -14
- package/dist/crypto/web.d.cts +0 -5
- package/dist/crypto/web.d.mts +0 -5
- package/dist/crypto/web.d.ts +0 -5
- package/dist/crypto/web.mjs +0 -10
- package/dist/index.cjs +0 -146
- package/dist/index.d.cts +0 -79
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c)
|
|
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.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Sandro Circi
|
|
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,70 +1,540 @@
|
|
|
1
1
|
# unjwt
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<!-- automd:badges color=yellow -->
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://npmjs.com/package/unjwt)
|
|
6
|
+
[](https://npm.chart.dev/unjwt)
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
<!-- /automd -->
|
|
8
9
|
|
|
9
|
-
-
|
|
10
|
-
- 💨 Zero dependencies
|
|
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
|
-
|
|
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.
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
Install the package:
|
|
15
19
|
|
|
16
|
-
```
|
|
17
|
-
# pnpm
|
|
18
|
-
|
|
20
|
+
```sh
|
|
21
|
+
# ✨ Auto-detect (supports npm, yarn, pnpm, deno and bun)
|
|
22
|
+
npx nypm install unjwt
|
|
23
|
+
```
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
npm install unjwt
|
|
25
|
+
Import:
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
**ESM** (Node.js, Bun, Deno)
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
import { jws, jwe, jwk } from "unjwt";
|
|
31
|
+
// JWS functions
|
|
32
|
+
import { sign, verify } from "unjwt/jws";
|
|
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";
|
|
25
46
|
```
|
|
26
47
|
|
|
27
|
-
|
|
48
|
+
**CDN** (Deno, Bun and Browsers)
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
// JWS functions
|
|
52
|
+
import { jws, jwe, jwk } from "https://esm.sh/unjwt";
|
|
53
|
+
import { sign, verify } from "https://esm.sh/unjwt/jws";
|
|
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";
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### JWS (JSON Web Signature, [RFC 7515](https://datatracker.ietf.org/doc/html/rfc7515))
|
|
74
|
+
|
|
75
|
+
Functions to sign and verify data according to the JWS specification.
|
|
76
|
+
|
|
77
|
+
#### `sign(payload, key, options)`
|
|
78
|
+
|
|
79
|
+
Creates a JWS token.
|
|
80
|
+
|
|
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).
|
|
86
|
+
|
|
87
|
+
Returns a `Promise<string>` resolving to the JWS token in Compact Serialization format.
|
|
88
|
+
|
|
89
|
+
**Example (HS256 with string secret):**
|
|
28
90
|
|
|
29
91
|
```ts
|
|
30
|
-
import {
|
|
92
|
+
import { sign } from "unjwt/jws";
|
|
31
93
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
94
|
+
const payload = { message: "My important data" }; // Object payload
|
|
95
|
+
const secret = "supersecretkey"; // String secret, will be imported (length depends on choosen alg)
|
|
35
96
|
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
97
|
+
const token = await sign(payload, secret, { alg: "HS256" });
|
|
98
|
+
|
|
99
|
+
console.log(token);
|
|
100
|
+
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Example (RS256 with CryptoKey):**
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
import { sign } from "unjwt/jws";
|
|
107
|
+
import { generateKey } from "unjwt/jwk";
|
|
108
|
+
|
|
109
|
+
const payload = { userId: 123, permissions: ["read"] };
|
|
110
|
+
const { privateKey } = await generateKey("RS256"); // Generates a CryptoKeyPair
|
|
111
|
+
|
|
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`.
|
|
125
|
+
- `options` (optional):
|
|
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.
|
|
128
|
+
|
|
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.
|
|
131
|
+
|
|
132
|
+
**Example (HS256):**
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import { verify } from "unjwt/jws";
|
|
136
|
+
|
|
137
|
+
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."; // From HS256 sign example
|
|
138
|
+
const secret = "supersecretkey";
|
|
139
|
+
|
|
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):**
|
|
146
|
+
|
|
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"] }
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### JWE (JSON Web Encryption, [RFC 7516](https://datatracker.ietf.org/doc/html/rfc7516))
|
|
168
|
+
|
|
169
|
+
Functions to encrypt and decrypt data according to the JWE specification.
|
|
170
|
+
|
|
171
|
+
#### `encrypt(payload, key, options)`
|
|
172
|
+
|
|
173
|
+
Encrypts data to produce a JWE token.
|
|
174
|
+
|
|
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`.
|
|
184
|
+
|
|
185
|
+
Returns a `Promise<string>` resolving to the JWE token in Compact Serialization format.
|
|
186
|
+
|
|
187
|
+
**Example (PBES2 password-based encryption):**
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
import { encrypt } from "unjwt/jwe";
|
|
191
|
+
|
|
192
|
+
const plaintext = "Secret message for password protection";
|
|
193
|
+
const password = "myVeryStrongPassword123!";
|
|
194
|
+
|
|
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...
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Example (A128KW with A128GCM):**
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import { encrypt } from "unjwt/jwe";
|
|
205
|
+
import { generateKey } from "unjwt/jwk";
|
|
206
|
+
|
|
207
|
+
const payload = { data: "sensitive information" };
|
|
208
|
+
const kek = await generateKey("A128KW"); // AES Key Wrap key
|
|
209
|
+
|
|
210
|
+
const jweToken = await encrypt(payload, kek, {
|
|
211
|
+
alg: "A128KW",
|
|
212
|
+
enc: "A128GCM",
|
|
213
|
+
protectedHeader: { kid: "aes-key-1" },
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
console.log(jweToken);
|
|
217
|
+
// eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4R0NNIiwia2lkIjoiYWVzLWtleS0xIn0...
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
#### `decrypt(jwe, key, options?)`
|
|
221
|
+
|
|
222
|
+
Decrypts a JWE token.
|
|
223
|
+
|
|
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`.
|
|
227
|
+
- `options` (optional):
|
|
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.
|
|
233
|
+
|
|
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`.
|
|
236
|
+
|
|
237
|
+
**Example (PBES2 password-based decryption):**
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
import { decrypt } from "unjwt/jwe";
|
|
241
|
+
// const jweToken = ...; // From PBES2 encrypt example
|
|
242
|
+
// const password = "myVeryStrongPassword123!";
|
|
243
|
+
|
|
244
|
+
const { payload } = await decrypt(jweToken, password);
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Example (A128KW with A128GCM):**
|
|
248
|
+
|
|
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
|
+
}
|
|
61
266
|
}
|
|
267
|
+
```
|
|
268
|
+
|
|
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?)`
|
|
62
274
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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:**
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
import { generateKey } from "unjwt/jwk";
|
|
291
|
+
|
|
292
|
+
// Generate an HS256 CryptoKey
|
|
293
|
+
const hmacKey = await generateKey("HS256");
|
|
294
|
+
console.log(hmacKey); // CryptoKey
|
|
295
|
+
|
|
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
|
|
300
|
+
|
|
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
|
|
305
|
+
|
|
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)
|
|
309
|
+
|
|
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)`
|
|
316
|
+
|
|
317
|
+
Derives a key from a password using PBKDF2 for PBES2 algorithms.
|
|
318
|
+
|
|
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"]`.
|
|
327
|
+
|
|
328
|
+
Returns a `Promise` resolving to `CryptoKey` or `JWK_oct`.
|
|
329
|
+
|
|
330
|
+
**Example:**
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
import { deriveKeyFromPassword } from "unjwt/jwk";
|
|
334
|
+
import { randomBytes, textEncoder } from "unjwt/utils";
|
|
335
|
+
|
|
336
|
+
const password = "mySecretPassword";
|
|
337
|
+
const salt = randomBytes(16);
|
|
338
|
+
const iterations = 4096;
|
|
339
|
+
|
|
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
|
|
66
397
|
```
|
|
67
398
|
|
|
399
|
+
#### `exportKey(key, jwk?)`
|
|
400
|
+
|
|
401
|
+
Exports a `CryptoKey` to JWK format.
|
|
402
|
+
|
|
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`).
|
|
405
|
+
|
|
406
|
+
Returns a `Promise<JWK>`.
|
|
407
|
+
|
|
408
|
+
**Example:**
|
|
409
|
+
|
|
410
|
+
```ts
|
|
411
|
+
import { generateKey, exportKey } from "unjwt/jwk";
|
|
412
|
+
|
|
413
|
+
const { publicKey } = await generateKey("ES256"); // Generates an extractable CryptoKey
|
|
414
|
+
|
|
415
|
+
const jwk = await exportKey(publicKey, { kid: "ec-key-001", use: "sig" });
|
|
416
|
+
console.log(jwk);
|
|
417
|
+
// {
|
|
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'
|
|
426
|
+
// }
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
#### `wrapKey(alg, keyToWrap, wrappingKey, options?)`
|
|
430
|
+
|
|
431
|
+
Wraps a Content Encryption Key (CEK).
|
|
432
|
+
|
|
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).
|
|
437
|
+
|
|
438
|
+
Returns a `Promise<WrapKeyResult>` containing `encryptedKey` and other parameters like `iv`, `tag`, `epk`, `p2s`, `p2c` as needed by the algorithm.
|
|
439
|
+
|
|
440
|
+
**Example (AES Key Wrap):**
|
|
441
|
+
|
|
442
|
+
```ts
|
|
443
|
+
import { wrapKey, generateKey } from "unjwt/jwk";
|
|
444
|
+
import { randomBytes } from "unjwt/utils";
|
|
445
|
+
|
|
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
|
|
448
|
+
|
|
449
|
+
const { encryptedKey } = await wrapKey("A128KW", cekToWrap, kek);
|
|
450
|
+
console.log("Wrapped CEK:", encryptedKey); // Uint8Array
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
#### `unwrapKey(alg, wrappedKey, unwrappingKey, options?)`
|
|
454
|
+
|
|
455
|
+
Unwraps a Content Encryption Key (CEK).
|
|
456
|
+
|
|
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`).
|
|
466
|
+
|
|
467
|
+
Returns a `Promise` resolving to the unwrapped CEK as `CryptoKey` or `Uint8Array`.
|
|
468
|
+
|
|
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
|
+
}
|
|
489
|
+
```
|
|
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
|
+
|
|
502
|
+
## Development
|
|
503
|
+
|
|
504
|
+
<details>
|
|
505
|
+
|
|
506
|
+
<summary>local development</summary>
|
|
507
|
+
|
|
508
|
+
- Clone this repository
|
|
509
|
+
- Install latest LTS version of [Node.js](https://nodejs.org/en/)
|
|
510
|
+
- Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable`
|
|
511
|
+
- Install dependencies using `pnpm install`
|
|
512
|
+
- Run interactive tests using `pnpm dev`
|
|
513
|
+
|
|
514
|
+
</details>
|
|
515
|
+
|
|
516
|
+
## Credits
|
|
517
|
+
|
|
518
|
+
Originally developed by [Johann Schopplich](https://github.com/johannschopplich/unjwt).
|
|
519
|
+
Heavily inspired by [Filip Skokan's work](https://github.com/panva/jose).
|
|
520
|
+
|
|
68
521
|
## License
|
|
69
522
|
|
|
70
|
-
|
|
523
|
+
<!-- automd:contributors license=MIT -->
|
|
524
|
+
|
|
525
|
+
Published under the [MIT](https://github.com/sandros94/unjwt/blob/main/LICENSE) license.
|
|
526
|
+
Made by [community](https://github.com/sandros94/unjwt/graphs/contributors) 💛
|
|
527
|
+
<br><br>
|
|
528
|
+
<a href="https://github.com/sandros94/unjwt/graphs/contributors">
|
|
529
|
+
<img src="https://contrib.rocks/image?repo=sandros94/unjwt" />
|
|
530
|
+
</a>
|
|
531
|
+
|
|
532
|
+
<!-- /automd -->
|
|
533
|
+
|
|
534
|
+
<!-- automd:with-automd -->
|
|
535
|
+
|
|
536
|
+
---
|
|
537
|
+
|
|
538
|
+
_🤖 auto updated with [automd](https://automd.unjs.io)_
|
|
539
|
+
|
|
540
|
+
<!-- /automd -->
|