jose 6.0.7 → 6.0.9
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 +1 -1
- package/dist/types/index.d.ts +3 -4
- package/dist/types/jwe/compact/encrypt.d.ts +6 -2
- package/dist/types/jwe/flattened/encrypt.d.ts +6 -9
- package/dist/types/jwe/general/encrypt.d.ts +9 -9
- package/dist/types/jwks/remote.d.ts +10 -1
- package/dist/types/jws/compact/sign.d.ts +6 -2
- package/dist/types/jws/flattened/sign.d.ts +6 -4
- package/dist/types/jws/general/sign.d.ts +9 -6
- package/dist/types/jwt/encrypt.d.ts +15 -9
- package/dist/types/jwt/produce.d.ts +17 -101
- package/dist/types/jwt/sign.d.ts +15 -3
- package/dist/types/jwt/unsecured.d.ts +15 -2
- package/dist/types/types.d.ts +107 -0
- package/dist/types/util/errors.d.ts +15 -1
- package/dist/webapi/index.js +1 -1
- package/dist/webapi/jwe/compact/encrypt.js +7 -7
- package/dist/webapi/jwe/flattened/encrypt.js +44 -44
- package/dist/webapi/jwe/general/encrypt.js +40 -40
- package/dist/webapi/jwks/local.js +11 -14
- package/dist/webapi/jwks/remote.js +52 -46
- package/dist/webapi/jws/compact/sign.js +4 -4
- package/dist/webapi/jws/flattened/sign.js +20 -20
- package/dist/webapi/jws/general/sign.js +13 -13
- package/dist/webapi/jwt/encrypt.js +69 -36
- package/dist/webapi/jwt/produce.js +57 -42
- package/dist/webapi/jwt/sign.js +40 -9
- package/dist/webapi/jwt/unsecured.js +34 -2
- package/package.json +1 -1
package/dist/webapi/index.js
CHANGED
|
@@ -17,7 +17,7 @@ export { EncryptJWT } from './jwt/encrypt.js';
|
|
|
17
17
|
export { calculateJwkThumbprint, calculateJwkThumbprintUri } from './jwk/thumbprint.js';
|
|
18
18
|
export { EmbeddedJWK } from './jwk/embedded.js';
|
|
19
19
|
export { createLocalJWKSet } from './jwks/local.js';
|
|
20
|
-
export { createRemoteJWKSet, jwksCache } from './jwks/remote.js';
|
|
20
|
+
export { createRemoteJWKSet, jwksCache, customFetch } from './jwks/remote.js';
|
|
21
21
|
export { UnsecuredJWT } from './jwt/unsecured.js';
|
|
22
22
|
export { exportPKCS8, exportSPKI, exportJWK } from './key/export.js';
|
|
23
23
|
export { importSPKI, importPKCS8, importX509, importJWK } from './key/import.js';
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
import { FlattenedEncrypt } from '../flattened/encrypt.js';
|
|
2
2
|
export class CompactEncrypt {
|
|
3
|
-
|
|
3
|
+
#flattened;
|
|
4
4
|
constructor(plaintext) {
|
|
5
|
-
this
|
|
5
|
+
this.#flattened = new FlattenedEncrypt(plaintext);
|
|
6
6
|
}
|
|
7
7
|
setContentEncryptionKey(cek) {
|
|
8
|
-
this.
|
|
8
|
+
this.#flattened.setContentEncryptionKey(cek);
|
|
9
9
|
return this;
|
|
10
10
|
}
|
|
11
11
|
setInitializationVector(iv) {
|
|
12
|
-
this.
|
|
12
|
+
this.#flattened.setInitializationVector(iv);
|
|
13
13
|
return this;
|
|
14
14
|
}
|
|
15
15
|
setProtectedHeader(protectedHeader) {
|
|
16
|
-
this.
|
|
16
|
+
this.#flattened.setProtectedHeader(protectedHeader);
|
|
17
17
|
return this;
|
|
18
18
|
}
|
|
19
19
|
setKeyManagementParameters(parameters) {
|
|
20
|
-
this.
|
|
20
|
+
this.#flattened.setKeyManagementParameters(parameters);
|
|
21
21
|
return this;
|
|
22
22
|
}
|
|
23
23
|
async encrypt(key, options) {
|
|
24
|
-
const jwe = await this.
|
|
24
|
+
const jwe = await this.#flattened.encrypt(key, options);
|
|
25
25
|
return [jwe.protected, jwe.encrypted_key, jwe.iv, jwe.ciphertext, jwe.tag].join('.');
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -9,79 +9,79 @@ import validateCrit from '../../lib/validate_crit.js';
|
|
|
9
9
|
import normalizeKey from '../../lib/normalize_key.js';
|
|
10
10
|
import checkKeyType from '../../lib/check_key_type.js';
|
|
11
11
|
export class FlattenedEncrypt {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
#plaintext;
|
|
13
|
+
#protectedHeader;
|
|
14
|
+
#sharedUnprotectedHeader;
|
|
15
|
+
#unprotectedHeader;
|
|
16
|
+
#aad;
|
|
17
|
+
#cek;
|
|
18
|
+
#iv;
|
|
19
|
+
#keyManagementParameters;
|
|
20
20
|
constructor(plaintext) {
|
|
21
21
|
if (!(plaintext instanceof Uint8Array)) {
|
|
22
22
|
throw new TypeError('plaintext must be an instance of Uint8Array');
|
|
23
23
|
}
|
|
24
|
-
this
|
|
24
|
+
this.#plaintext = plaintext;
|
|
25
25
|
}
|
|
26
26
|
setKeyManagementParameters(parameters) {
|
|
27
|
-
if (this
|
|
27
|
+
if (this.#keyManagementParameters) {
|
|
28
28
|
throw new TypeError('setKeyManagementParameters can only be called once');
|
|
29
29
|
}
|
|
30
|
-
this
|
|
30
|
+
this.#keyManagementParameters = parameters;
|
|
31
31
|
return this;
|
|
32
32
|
}
|
|
33
33
|
setProtectedHeader(protectedHeader) {
|
|
34
|
-
if (this
|
|
34
|
+
if (this.#protectedHeader) {
|
|
35
35
|
throw new TypeError('setProtectedHeader can only be called once');
|
|
36
36
|
}
|
|
37
|
-
this
|
|
37
|
+
this.#protectedHeader = protectedHeader;
|
|
38
38
|
return this;
|
|
39
39
|
}
|
|
40
40
|
setSharedUnprotectedHeader(sharedUnprotectedHeader) {
|
|
41
|
-
if (this
|
|
41
|
+
if (this.#sharedUnprotectedHeader) {
|
|
42
42
|
throw new TypeError('setSharedUnprotectedHeader can only be called once');
|
|
43
43
|
}
|
|
44
|
-
this
|
|
44
|
+
this.#sharedUnprotectedHeader = sharedUnprotectedHeader;
|
|
45
45
|
return this;
|
|
46
46
|
}
|
|
47
47
|
setUnprotectedHeader(unprotectedHeader) {
|
|
48
|
-
if (this
|
|
48
|
+
if (this.#unprotectedHeader) {
|
|
49
49
|
throw new TypeError('setUnprotectedHeader can only be called once');
|
|
50
50
|
}
|
|
51
|
-
this
|
|
51
|
+
this.#unprotectedHeader = unprotectedHeader;
|
|
52
52
|
return this;
|
|
53
53
|
}
|
|
54
54
|
setAdditionalAuthenticatedData(aad) {
|
|
55
|
-
this
|
|
55
|
+
this.#aad = aad;
|
|
56
56
|
return this;
|
|
57
57
|
}
|
|
58
58
|
setContentEncryptionKey(cek) {
|
|
59
|
-
if (this
|
|
59
|
+
if (this.#cek) {
|
|
60
60
|
throw new TypeError('setContentEncryptionKey can only be called once');
|
|
61
61
|
}
|
|
62
|
-
this
|
|
62
|
+
this.#cek = cek;
|
|
63
63
|
return this;
|
|
64
64
|
}
|
|
65
65
|
setInitializationVector(iv) {
|
|
66
|
-
if (this
|
|
66
|
+
if (this.#iv) {
|
|
67
67
|
throw new TypeError('setInitializationVector can only be called once');
|
|
68
68
|
}
|
|
69
|
-
this
|
|
69
|
+
this.#iv = iv;
|
|
70
70
|
return this;
|
|
71
71
|
}
|
|
72
72
|
async encrypt(key, options) {
|
|
73
|
-
if (!this
|
|
73
|
+
if (!this.#protectedHeader && !this.#unprotectedHeader && !this.#sharedUnprotectedHeader) {
|
|
74
74
|
throw new JWEInvalid('either setProtectedHeader, setUnprotectedHeader, or sharedUnprotectedHeader must be called before #encrypt()');
|
|
75
75
|
}
|
|
76
|
-
if (!isDisjoint(this
|
|
76
|
+
if (!isDisjoint(this.#protectedHeader, this.#unprotectedHeader, this.#sharedUnprotectedHeader)) {
|
|
77
77
|
throw new JWEInvalid('JWE Protected, JWE Shared Unprotected and JWE Per-Recipient Header Parameter names must be disjoint');
|
|
78
78
|
}
|
|
79
79
|
const joseHeader = {
|
|
80
|
-
...this
|
|
81
|
-
...this
|
|
82
|
-
...this
|
|
80
|
+
...this.#protectedHeader,
|
|
81
|
+
...this.#unprotectedHeader,
|
|
82
|
+
...this.#sharedUnprotectedHeader,
|
|
83
83
|
};
|
|
84
|
-
validateCrit(JWEInvalid, new Map(), options?.crit, this
|
|
84
|
+
validateCrit(JWEInvalid, new Map(), options?.crit, this.#protectedHeader, joseHeader);
|
|
85
85
|
if (joseHeader.zip !== undefined) {
|
|
86
86
|
throw new JOSENotSupported('JWE "zip" (Compression Algorithm) Header Parameter is not supported.');
|
|
87
87
|
}
|
|
@@ -93,7 +93,7 @@ export class FlattenedEncrypt {
|
|
|
93
93
|
throw new JWEInvalid('JWE "enc" (Encryption Algorithm) Header Parameter missing or invalid');
|
|
94
94
|
}
|
|
95
95
|
let encryptedKey;
|
|
96
|
-
if (this
|
|
96
|
+
if (this.#cek && (alg === 'dir' || alg === 'ECDH-ES')) {
|
|
97
97
|
throw new TypeError(`setContentEncryptionKey cannot be called with JWE "alg" (Algorithm) Header ${alg}`);
|
|
98
98
|
}
|
|
99
99
|
checkKeyType(alg === 'dir' ? enc : alg, key, 'encrypt');
|
|
@@ -101,41 +101,41 @@ export class FlattenedEncrypt {
|
|
|
101
101
|
{
|
|
102
102
|
let parameters;
|
|
103
103
|
const k = await normalizeKey(key, alg);
|
|
104
|
-
({ cek, encryptedKey, parameters } = await encryptKeyManagement(alg, enc, k, this
|
|
104
|
+
({ cek, encryptedKey, parameters } = await encryptKeyManagement(alg, enc, k, this.#cek, this.#keyManagementParameters));
|
|
105
105
|
if (parameters) {
|
|
106
106
|
if (options && unprotected in options) {
|
|
107
|
-
if (!this
|
|
107
|
+
if (!this.#unprotectedHeader) {
|
|
108
108
|
this.setUnprotectedHeader(parameters);
|
|
109
109
|
}
|
|
110
110
|
else {
|
|
111
|
-
this
|
|
111
|
+
this.#unprotectedHeader = { ...this.#unprotectedHeader, ...parameters };
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
|
-
else if (!this
|
|
114
|
+
else if (!this.#protectedHeader) {
|
|
115
115
|
this.setProtectedHeader(parameters);
|
|
116
116
|
}
|
|
117
117
|
else {
|
|
118
|
-
this
|
|
118
|
+
this.#protectedHeader = { ...this.#protectedHeader, ...parameters };
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
let additionalData;
|
|
123
123
|
let protectedHeader;
|
|
124
124
|
let aadMember;
|
|
125
|
-
if (this
|
|
126
|
-
protectedHeader = encoder.encode(b64u(JSON.stringify(this
|
|
125
|
+
if (this.#protectedHeader) {
|
|
126
|
+
protectedHeader = encoder.encode(b64u(JSON.stringify(this.#protectedHeader)));
|
|
127
127
|
}
|
|
128
128
|
else {
|
|
129
129
|
protectedHeader = encoder.encode('');
|
|
130
130
|
}
|
|
131
|
-
if (this
|
|
132
|
-
aadMember = b64u(this
|
|
131
|
+
if (this.#aad) {
|
|
132
|
+
aadMember = b64u(this.#aad);
|
|
133
133
|
additionalData = concat(protectedHeader, encoder.encode('.'), encoder.encode(aadMember));
|
|
134
134
|
}
|
|
135
135
|
else {
|
|
136
136
|
additionalData = protectedHeader;
|
|
137
137
|
}
|
|
138
|
-
const { ciphertext, tag, iv } = await encrypt(enc, this
|
|
138
|
+
const { ciphertext, tag, iv } = await encrypt(enc, this.#plaintext, cek, this.#iv, additionalData);
|
|
139
139
|
const jwe = {
|
|
140
140
|
ciphertext: b64u(ciphertext),
|
|
141
141
|
};
|
|
@@ -151,14 +151,14 @@ export class FlattenedEncrypt {
|
|
|
151
151
|
if (aadMember) {
|
|
152
152
|
jwe.aad = aadMember;
|
|
153
153
|
}
|
|
154
|
-
if (this
|
|
154
|
+
if (this.#protectedHeader) {
|
|
155
155
|
jwe.protected = decoder.decode(protectedHeader);
|
|
156
156
|
}
|
|
157
|
-
if (this
|
|
158
|
-
jwe.unprotected = this
|
|
157
|
+
if (this.#sharedUnprotectedHeader) {
|
|
158
|
+
jwe.unprotected = this.#sharedUnprotectedHeader;
|
|
159
159
|
}
|
|
160
|
-
if (this
|
|
161
|
-
jwe.header = this
|
|
160
|
+
if (this.#unprotectedHeader) {
|
|
161
|
+
jwe.header = this.#unprotectedHeader;
|
|
162
162
|
}
|
|
163
163
|
return jwe;
|
|
164
164
|
}
|
|
@@ -9,12 +9,12 @@ import validateCrit from '../../lib/validate_crit.js';
|
|
|
9
9
|
import normalizeKey from '../../lib/normalize_key.js';
|
|
10
10
|
import checkKeyType from '../../lib/check_key_type.js';
|
|
11
11
|
class IndividualRecipient {
|
|
12
|
-
parent;
|
|
12
|
+
#parent;
|
|
13
13
|
unprotectedHeader;
|
|
14
14
|
key;
|
|
15
15
|
options;
|
|
16
16
|
constructor(enc, key, options) {
|
|
17
|
-
this
|
|
17
|
+
this.#parent = enc;
|
|
18
18
|
this.key = key;
|
|
19
19
|
this.options = options;
|
|
20
20
|
}
|
|
@@ -26,57 +26,57 @@ class IndividualRecipient {
|
|
|
26
26
|
return this;
|
|
27
27
|
}
|
|
28
28
|
addRecipient(...args) {
|
|
29
|
-
return this
|
|
29
|
+
return this.#parent.addRecipient(...args);
|
|
30
30
|
}
|
|
31
31
|
encrypt(...args) {
|
|
32
|
-
return this
|
|
32
|
+
return this.#parent.encrypt(...args);
|
|
33
33
|
}
|
|
34
34
|
done() {
|
|
35
|
-
return this
|
|
35
|
+
return this.#parent;
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
export class GeneralEncrypt {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
#plaintext;
|
|
40
|
+
#recipients = [];
|
|
41
|
+
#protectedHeader;
|
|
42
|
+
#unprotectedHeader;
|
|
43
|
+
#aad;
|
|
44
44
|
constructor(plaintext) {
|
|
45
|
-
this
|
|
45
|
+
this.#plaintext = plaintext;
|
|
46
46
|
}
|
|
47
47
|
addRecipient(key, options) {
|
|
48
48
|
const recipient = new IndividualRecipient(this, key, { crit: options?.crit });
|
|
49
|
-
this.
|
|
49
|
+
this.#recipients.push(recipient);
|
|
50
50
|
return recipient;
|
|
51
51
|
}
|
|
52
52
|
setProtectedHeader(protectedHeader) {
|
|
53
|
-
if (this
|
|
53
|
+
if (this.#protectedHeader) {
|
|
54
54
|
throw new TypeError('setProtectedHeader can only be called once');
|
|
55
55
|
}
|
|
56
|
-
this
|
|
56
|
+
this.#protectedHeader = protectedHeader;
|
|
57
57
|
return this;
|
|
58
58
|
}
|
|
59
59
|
setSharedUnprotectedHeader(sharedUnprotectedHeader) {
|
|
60
|
-
if (this
|
|
60
|
+
if (this.#unprotectedHeader) {
|
|
61
61
|
throw new TypeError('setSharedUnprotectedHeader can only be called once');
|
|
62
62
|
}
|
|
63
|
-
this
|
|
63
|
+
this.#unprotectedHeader = sharedUnprotectedHeader;
|
|
64
64
|
return this;
|
|
65
65
|
}
|
|
66
66
|
setAdditionalAuthenticatedData(aad) {
|
|
67
|
-
this
|
|
67
|
+
this.#aad = aad;
|
|
68
68
|
return this;
|
|
69
69
|
}
|
|
70
70
|
async encrypt() {
|
|
71
|
-
if (!this.
|
|
71
|
+
if (!this.#recipients.length) {
|
|
72
72
|
throw new JWEInvalid('at least one recipient must be added');
|
|
73
73
|
}
|
|
74
|
-
if (this.
|
|
75
|
-
const [recipient] = this
|
|
76
|
-
const flattened = await new FlattenedEncrypt(this
|
|
77
|
-
.setAdditionalAuthenticatedData(this
|
|
78
|
-
.setProtectedHeader(this
|
|
79
|
-
.setSharedUnprotectedHeader(this
|
|
74
|
+
if (this.#recipients.length === 1) {
|
|
75
|
+
const [recipient] = this.#recipients;
|
|
76
|
+
const flattened = await new FlattenedEncrypt(this.#plaintext)
|
|
77
|
+
.setAdditionalAuthenticatedData(this.#aad)
|
|
78
|
+
.setProtectedHeader(this.#protectedHeader)
|
|
79
|
+
.setSharedUnprotectedHeader(this.#unprotectedHeader)
|
|
80
80
|
.setUnprotectedHeader(recipient.unprotectedHeader)
|
|
81
81
|
.encrypt(recipient.key, { ...recipient.options });
|
|
82
82
|
const jwe = {
|
|
@@ -98,14 +98,14 @@ export class GeneralEncrypt {
|
|
|
98
98
|
return jwe;
|
|
99
99
|
}
|
|
100
100
|
let enc;
|
|
101
|
-
for (let i = 0; i < this.
|
|
102
|
-
const recipient = this
|
|
103
|
-
if (!isDisjoint(this
|
|
101
|
+
for (let i = 0; i < this.#recipients.length; i++) {
|
|
102
|
+
const recipient = this.#recipients[i];
|
|
103
|
+
if (!isDisjoint(this.#protectedHeader, this.#unprotectedHeader, recipient.unprotectedHeader)) {
|
|
104
104
|
throw new JWEInvalid('JWE Protected, JWE Shared Unprotected and JWE Per-Recipient Header Parameter names must be disjoint');
|
|
105
105
|
}
|
|
106
106
|
const joseHeader = {
|
|
107
|
-
...this
|
|
108
|
-
...this
|
|
107
|
+
...this.#protectedHeader,
|
|
108
|
+
...this.#unprotectedHeader,
|
|
109
109
|
...recipient.unprotectedHeader,
|
|
110
110
|
};
|
|
111
111
|
const { alg } = joseHeader;
|
|
@@ -124,7 +124,7 @@ export class GeneralEncrypt {
|
|
|
124
124
|
else if (enc !== joseHeader.enc) {
|
|
125
125
|
throw new JWEInvalid('JWE "enc" (Encryption Algorithm) Header Parameter must be the same for all recipients');
|
|
126
126
|
}
|
|
127
|
-
validateCrit(JWEInvalid, new Map(), recipient.options.crit, this
|
|
127
|
+
validateCrit(JWEInvalid, new Map(), recipient.options.crit, this.#protectedHeader, joseHeader);
|
|
128
128
|
if (joseHeader.zip !== undefined) {
|
|
129
129
|
throw new JOSENotSupported('JWE "zip" (Compression Algorithm) Header Parameter is not supported.');
|
|
130
130
|
}
|
|
@@ -136,22 +136,22 @@ export class GeneralEncrypt {
|
|
|
136
136
|
recipients: [],
|
|
137
137
|
tag: '',
|
|
138
138
|
};
|
|
139
|
-
for (let i = 0; i < this.
|
|
140
|
-
const recipient = this
|
|
139
|
+
for (let i = 0; i < this.#recipients.length; i++) {
|
|
140
|
+
const recipient = this.#recipients[i];
|
|
141
141
|
const target = {};
|
|
142
142
|
jwe.recipients.push(target);
|
|
143
143
|
const joseHeader = {
|
|
144
|
-
...this
|
|
145
|
-
...this
|
|
144
|
+
...this.#protectedHeader,
|
|
145
|
+
...this.#unprotectedHeader,
|
|
146
146
|
...recipient.unprotectedHeader,
|
|
147
147
|
};
|
|
148
148
|
const p2c = joseHeader.alg.startsWith('PBES2') ? 2048 + i : undefined;
|
|
149
149
|
if (i === 0) {
|
|
150
|
-
const flattened = await new FlattenedEncrypt(this
|
|
151
|
-
.setAdditionalAuthenticatedData(this
|
|
150
|
+
const flattened = await new FlattenedEncrypt(this.#plaintext)
|
|
151
|
+
.setAdditionalAuthenticatedData(this.#aad)
|
|
152
152
|
.setContentEncryptionKey(cek)
|
|
153
|
-
.setProtectedHeader(this
|
|
154
|
-
.setSharedUnprotectedHeader(this
|
|
153
|
+
.setProtectedHeader(this.#protectedHeader)
|
|
154
|
+
.setSharedUnprotectedHeader(this.#unprotectedHeader)
|
|
155
155
|
.setUnprotectedHeader(recipient.unprotectedHeader)
|
|
156
156
|
.setKeyManagementParameters({ p2c })
|
|
157
157
|
.encrypt(recipient.key, {
|
|
@@ -173,8 +173,8 @@ export class GeneralEncrypt {
|
|
|
173
173
|
continue;
|
|
174
174
|
}
|
|
175
175
|
const alg = recipient.unprotectedHeader?.alg ||
|
|
176
|
-
this
|
|
177
|
-
this
|
|
176
|
+
this.#protectedHeader?.alg ||
|
|
177
|
+
this.#unprotectedHeader?.alg;
|
|
178
178
|
checkKeyType(alg === 'dir' ? enc : alg, recipient.key, 'encrypt');
|
|
179
179
|
const k = await normalizeKey(recipient.key, alg);
|
|
180
180
|
const { encryptedKey, parameters } = await encryptKeyManagement(alg, enc, k, cek, { p2c });
|
|
@@ -23,25 +23,22 @@ function isJWKSLike(jwks) {
|
|
|
23
23
|
function isJWKLike(key) {
|
|
24
24
|
return isObject(key);
|
|
25
25
|
}
|
|
26
|
-
function clone(obj) {
|
|
27
|
-
if (typeof structuredClone === 'function') {
|
|
28
|
-
return structuredClone(obj);
|
|
29
|
-
}
|
|
30
|
-
return JSON.parse(JSON.stringify(obj));
|
|
31
|
-
}
|
|
32
26
|
class LocalJWKSet {
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
#jwks;
|
|
28
|
+
#cached = new WeakMap();
|
|
35
29
|
constructor(jwks) {
|
|
36
30
|
if (!isJWKSLike(jwks)) {
|
|
37
31
|
throw new JWKSInvalid('JSON Web Key Set malformed');
|
|
38
32
|
}
|
|
39
|
-
this
|
|
33
|
+
this.#jwks = structuredClone(jwks);
|
|
34
|
+
}
|
|
35
|
+
jwks() {
|
|
36
|
+
return this.#jwks;
|
|
40
37
|
}
|
|
41
38
|
async getKey(protectedHeader, token) {
|
|
42
39
|
const { alg, kid } = { ...protectedHeader, ...token?.header };
|
|
43
40
|
const kty = getKtyFromAlg(alg);
|
|
44
|
-
const candidates = this.
|
|
41
|
+
const candidates = this.#jwks.keys.filter((jwk) => {
|
|
45
42
|
let candidate = kty === jwk.kty;
|
|
46
43
|
if (candidate && typeof kid === 'string') {
|
|
47
44
|
candidate = kid === jwk.kid;
|
|
@@ -80,7 +77,7 @@ class LocalJWKSet {
|
|
|
80
77
|
}
|
|
81
78
|
if (length !== 1) {
|
|
82
79
|
const error = new JWKSMultipleMatchingKeys();
|
|
83
|
-
const
|
|
80
|
+
const _cached = this.#cached;
|
|
84
81
|
error[Symbol.asyncIterator] = async function* () {
|
|
85
82
|
for (const jwk of candidates) {
|
|
86
83
|
try {
|
|
@@ -91,7 +88,7 @@ class LocalJWKSet {
|
|
|
91
88
|
};
|
|
92
89
|
throw error;
|
|
93
90
|
}
|
|
94
|
-
return importWithAlgCache(this
|
|
91
|
+
return importWithAlgCache(this.#cached, jwk, alg);
|
|
95
92
|
}
|
|
96
93
|
}
|
|
97
94
|
async function importWithAlgCache(cache, jwk, alg) {
|
|
@@ -110,8 +107,8 @@ export function createLocalJWKSet(jwks) {
|
|
|
110
107
|
const localJWKSet = async (protectedHeader, token) => set.getKey(protectedHeader, token);
|
|
111
108
|
Object.defineProperties(localJWKSet, {
|
|
112
109
|
jwks: {
|
|
113
|
-
value: () =>
|
|
114
|
-
enumerable:
|
|
110
|
+
value: () => structuredClone(set.jwks()),
|
|
111
|
+
enumerable: false,
|
|
115
112
|
configurable: false,
|
|
116
113
|
writable: false,
|
|
117
114
|
},
|
|
@@ -9,7 +9,7 @@ function isCloudflareWorkers() {
|
|
|
9
9
|
let USER_AGENT;
|
|
10
10
|
if (typeof navigator === 'undefined' || !navigator.userAgent?.startsWith?.('Mozilla/5.0 ')) {
|
|
11
11
|
const NAME = 'jose';
|
|
12
|
-
const VERSION = 'v6.0.
|
|
12
|
+
const VERSION = 'v6.0.9';
|
|
13
13
|
USER_AGENT = `${NAME}/${VERSION}`;
|
|
14
14
|
}
|
|
15
15
|
export const customFetch = Symbol();
|
|
@@ -52,89 +52,95 @@ function isFreshJwksCache(input, cacheMaxAge) {
|
|
|
52
52
|
return true;
|
|
53
53
|
}
|
|
54
54
|
class RemoteJWKSet {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
55
|
+
#url;
|
|
56
|
+
#timeoutDuration;
|
|
57
|
+
#cooldownDuration;
|
|
58
|
+
#cacheMaxAge;
|
|
59
|
+
#jwksTimestamp;
|
|
60
|
+
#pendingFetch;
|
|
61
|
+
#headers;
|
|
62
|
+
#customFetch;
|
|
63
|
+
#local;
|
|
64
|
+
#cache;
|
|
65
65
|
constructor(url, options) {
|
|
66
66
|
if (!(url instanceof URL)) {
|
|
67
67
|
throw new TypeError('url must be an instance of URL');
|
|
68
68
|
}
|
|
69
|
-
this
|
|
70
|
-
this
|
|
69
|
+
this.#url = new URL(url.href);
|
|
70
|
+
this.#timeoutDuration =
|
|
71
71
|
typeof options?.timeoutDuration === 'number' ? options?.timeoutDuration : 5000;
|
|
72
|
-
this
|
|
72
|
+
this.#cooldownDuration =
|
|
73
73
|
typeof options?.cooldownDuration === 'number' ? options?.cooldownDuration : 30000;
|
|
74
|
-
this
|
|
75
|
-
this
|
|
76
|
-
if (USER_AGENT && !this.
|
|
77
|
-
this.
|
|
74
|
+
this.#cacheMaxAge = typeof options?.cacheMaxAge === 'number' ? options?.cacheMaxAge : 600000;
|
|
75
|
+
this.#headers = new Headers(options?.headers);
|
|
76
|
+
if (USER_AGENT && !this.#headers.has('User-Agent')) {
|
|
77
|
+
this.#headers.set('User-Agent', USER_AGENT);
|
|
78
78
|
}
|
|
79
|
-
if (!this.
|
|
80
|
-
this.
|
|
81
|
-
this.
|
|
79
|
+
if (!this.#headers.has('accept')) {
|
|
80
|
+
this.#headers.set('accept', 'application/json');
|
|
81
|
+
this.#headers.append('accept', 'application/jwk-set+json');
|
|
82
82
|
}
|
|
83
|
-
this
|
|
83
|
+
this.#customFetch = options?.[customFetch];
|
|
84
84
|
if (options?.[jwksCache] !== undefined) {
|
|
85
|
-
this
|
|
86
|
-
if (isFreshJwksCache(options?.[jwksCache], this
|
|
87
|
-
this
|
|
88
|
-
this
|
|
85
|
+
this.#cache = options?.[jwksCache];
|
|
86
|
+
if (isFreshJwksCache(options?.[jwksCache], this.#cacheMaxAge)) {
|
|
87
|
+
this.#jwksTimestamp = this.#cache.uat;
|
|
88
|
+
this.#local = createLocalJWKSet(this.#cache.jwks);
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
|
+
pendingFetch() {
|
|
93
|
+
return !!this.#pendingFetch;
|
|
94
|
+
}
|
|
92
95
|
coolingDown() {
|
|
93
|
-
return typeof this
|
|
94
|
-
? Date.now() < this
|
|
96
|
+
return typeof this.#jwksTimestamp === 'number'
|
|
97
|
+
? Date.now() < this.#jwksTimestamp + this.#cooldownDuration
|
|
95
98
|
: false;
|
|
96
99
|
}
|
|
97
100
|
fresh() {
|
|
98
|
-
return typeof this
|
|
99
|
-
? Date.now() < this
|
|
101
|
+
return typeof this.#jwksTimestamp === 'number'
|
|
102
|
+
? Date.now() < this.#jwksTimestamp + this.#cacheMaxAge
|
|
100
103
|
: false;
|
|
101
104
|
}
|
|
105
|
+
jwks() {
|
|
106
|
+
return this.#local?.jwks();
|
|
107
|
+
}
|
|
102
108
|
async getKey(protectedHeader, token) {
|
|
103
|
-
if (!this
|
|
109
|
+
if (!this.#local || !this.fresh()) {
|
|
104
110
|
await this.reload();
|
|
105
111
|
}
|
|
106
112
|
try {
|
|
107
|
-
return await this
|
|
113
|
+
return await this.#local(protectedHeader, token);
|
|
108
114
|
}
|
|
109
115
|
catch (err) {
|
|
110
116
|
if (err instanceof JWKSNoMatchingKey) {
|
|
111
117
|
if (this.coolingDown() === false) {
|
|
112
118
|
await this.reload();
|
|
113
|
-
return this
|
|
119
|
+
return this.#local(protectedHeader, token);
|
|
114
120
|
}
|
|
115
121
|
}
|
|
116
122
|
throw err;
|
|
117
123
|
}
|
|
118
124
|
}
|
|
119
125
|
async reload() {
|
|
120
|
-
if (this
|
|
121
|
-
this
|
|
126
|
+
if (this.#pendingFetch && isCloudflareWorkers()) {
|
|
127
|
+
this.#pendingFetch = undefined;
|
|
122
128
|
}
|
|
123
|
-
this
|
|
129
|
+
this.#pendingFetch ||= fetchJwks(this.#url.href, this.#headers, AbortSignal.timeout(this.#timeoutDuration), this.#customFetch)
|
|
124
130
|
.then((json) => {
|
|
125
|
-
this
|
|
126
|
-
if (this
|
|
127
|
-
this.
|
|
128
|
-
this.
|
|
131
|
+
this.#local = createLocalJWKSet(json);
|
|
132
|
+
if (this.#cache) {
|
|
133
|
+
this.#cache.uat = Date.now();
|
|
134
|
+
this.#cache.jwks = json;
|
|
129
135
|
}
|
|
130
|
-
this
|
|
131
|
-
this
|
|
136
|
+
this.#jwksTimestamp = Date.now();
|
|
137
|
+
this.#pendingFetch = undefined;
|
|
132
138
|
})
|
|
133
139
|
.catch((err) => {
|
|
134
|
-
this
|
|
140
|
+
this.#pendingFetch = undefined;
|
|
135
141
|
throw err;
|
|
136
142
|
});
|
|
137
|
-
await this
|
|
143
|
+
await this.#pendingFetch;
|
|
138
144
|
}
|
|
139
145
|
}
|
|
140
146
|
export function createRemoteJWKSet(url, options) {
|
|
@@ -158,12 +164,12 @@ export function createRemoteJWKSet(url, options) {
|
|
|
158
164
|
writable: false,
|
|
159
165
|
},
|
|
160
166
|
reloading: {
|
|
161
|
-
get: () =>
|
|
167
|
+
get: () => set.pendingFetch(),
|
|
162
168
|
enumerable: true,
|
|
163
169
|
configurable: false,
|
|
164
170
|
},
|
|
165
171
|
jwks: {
|
|
166
|
-
value: () => set.
|
|
172
|
+
value: () => set.jwks(),
|
|
167
173
|
enumerable: true,
|
|
168
174
|
configurable: false,
|
|
169
175
|
writable: false,
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { FlattenedSign } from '../flattened/sign.js';
|
|
2
2
|
export class CompactSign {
|
|
3
|
-
|
|
3
|
+
#flattened;
|
|
4
4
|
constructor(payload) {
|
|
5
|
-
this
|
|
5
|
+
this.#flattened = new FlattenedSign(payload);
|
|
6
6
|
}
|
|
7
7
|
setProtectedHeader(protectedHeader) {
|
|
8
|
-
this.
|
|
8
|
+
this.#flattened.setProtectedHeader(protectedHeader);
|
|
9
9
|
return this;
|
|
10
10
|
}
|
|
11
11
|
async sign(key, options) {
|
|
12
|
-
const jws = await this.
|
|
12
|
+
const jws = await this.#flattened.sign(key, options);
|
|
13
13
|
if (jws.payload === undefined) {
|
|
14
14
|
throw new TypeError('use the flattened module for creating JWS with b64: false');
|
|
15
15
|
}
|