jose 6.0.12 → 6.0.13
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/dist/webapi/jwks/remote.js +1 -1
- package/dist/webapi/lib/asn1.js +138 -68
- package/dist/webapi/lib/ecdhes.js +28 -22
- package/package.json +1 -1
|
@@ -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.13';
|
|
13
13
|
USER_AGENT = `${NAME}/${VERSION}`;
|
|
14
14
|
}
|
|
15
15
|
export const customFetch = Symbol();
|
package/dist/webapi/lib/asn1.js
CHANGED
|
@@ -30,42 +30,112 @@ export const toSPKI = (key) => {
|
|
|
30
30
|
export const toPKCS8 = (key) => {
|
|
31
31
|
return genericExport('private', 'pkcs8', key);
|
|
32
32
|
};
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
33
|
+
const bytesEqual = (a, b) => {
|
|
34
|
+
if (a.byteLength !== b.length)
|
|
35
|
+
return false;
|
|
36
|
+
for (let i = 0; i < a.byteLength; i++) {
|
|
37
|
+
if (a[i] !== b[i])
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
return true;
|
|
41
|
+
};
|
|
42
|
+
const createASN1State = (data) => ({ data, pos: 0 });
|
|
43
|
+
const parseLength = (state) => {
|
|
44
|
+
const first = state.data[state.pos++];
|
|
45
|
+
if (first & 0x80) {
|
|
46
|
+
const lengthOfLen = first & 0x7f;
|
|
47
|
+
let length = 0;
|
|
48
|
+
for (let i = 0; i < lengthOfLen; i++) {
|
|
49
|
+
length = (length << 8) | state.data[state.pos++];
|
|
50
|
+
}
|
|
51
|
+
return length;
|
|
52
|
+
}
|
|
53
|
+
return first;
|
|
54
|
+
};
|
|
55
|
+
const skipElement = (state, count = 1) => {
|
|
56
|
+
if (count <= 0)
|
|
57
|
+
return;
|
|
58
|
+
state.pos++;
|
|
59
|
+
const length = parseLength(state);
|
|
60
|
+
state.pos += length;
|
|
61
|
+
if (count > 1) {
|
|
62
|
+
skipElement(state, count - 1);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const expectTag = (state, expectedTag, errorMessage) => {
|
|
66
|
+
if (state.data[state.pos++] !== expectedTag) {
|
|
67
|
+
throw new Error(errorMessage);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const getSubarray = (state, length) => {
|
|
71
|
+
const result = state.data.subarray(state.pos, state.pos + length);
|
|
72
|
+
state.pos += length;
|
|
73
|
+
return result;
|
|
74
|
+
};
|
|
75
|
+
const parseAlgorithmOID = (state) => {
|
|
76
|
+
expectTag(state, 0x06, 'Expected algorithm OID');
|
|
77
|
+
const oidLen = parseLength(state);
|
|
78
|
+
return getSubarray(state, oidLen);
|
|
79
|
+
};
|
|
80
|
+
function parsePKCS8Header(state) {
|
|
81
|
+
expectTag(state, 0x30, 'Invalid PKCS#8 structure');
|
|
82
|
+
parseLength(state);
|
|
83
|
+
expectTag(state, 0x02, 'Expected version field');
|
|
84
|
+
const verLen = parseLength(state);
|
|
85
|
+
state.pos += verLen;
|
|
86
|
+
expectTag(state, 0x30, 'Expected algorithm identifier');
|
|
87
|
+
const algIdLen = parseLength(state);
|
|
88
|
+
const algIdStart = state.pos;
|
|
89
|
+
return { algIdStart, algIdLength: algIdLen };
|
|
90
|
+
}
|
|
91
|
+
function parseSPKIHeader(state) {
|
|
92
|
+
expectTag(state, 0x30, 'Invalid SPKI structure');
|
|
93
|
+
parseLength(state);
|
|
94
|
+
expectTag(state, 0x30, 'Expected algorithm identifier');
|
|
95
|
+
const algIdLen = parseLength(state);
|
|
96
|
+
const algIdStart = state.pos;
|
|
97
|
+
return { algIdStart, algIdLength: algIdLen };
|
|
98
|
+
}
|
|
99
|
+
const parseECAlgorithmIdentifier = (state) => {
|
|
100
|
+
const algOid = parseAlgorithmOID(state);
|
|
101
|
+
if (bytesEqual(algOid, [0x2b, 0x65, 0x6e])) {
|
|
102
|
+
return 'X25519';
|
|
103
|
+
}
|
|
104
|
+
if (!bytesEqual(algOid, [0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01])) {
|
|
105
|
+
throw new Error('Unsupported key algorithm');
|
|
106
|
+
}
|
|
107
|
+
expectTag(state, 0x06, 'Expected curve OID');
|
|
108
|
+
const curveOidLen = parseLength(state);
|
|
109
|
+
const curveOid = getSubarray(state, curveOidLen);
|
|
110
|
+
for (const { name, oid } of [
|
|
111
|
+
{ name: 'P-256', oid: [0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07] },
|
|
112
|
+
{ name: 'P-384', oid: [0x2b, 0x81, 0x04, 0x00, 0x22] },
|
|
113
|
+
{ name: 'P-521', oid: [0x2b, 0x81, 0x04, 0x00, 0x23] },
|
|
114
|
+
]) {
|
|
115
|
+
if (bytesEqual(curveOid, oid)) {
|
|
116
|
+
return name;
|
|
47
117
|
}
|
|
48
118
|
}
|
|
49
|
-
|
|
119
|
+
throw new Error('Unsupported named curve');
|
|
50
120
|
};
|
|
51
121
|
const genericImport = async (keyFormat, keyData, alg, options) => {
|
|
52
122
|
let algorithm;
|
|
53
123
|
let keyUsages;
|
|
54
124
|
const isPublic = keyFormat === 'spki';
|
|
55
|
-
const
|
|
56
|
-
const
|
|
125
|
+
const getSigUsages = () => (isPublic ? ['verify'] : ['sign']);
|
|
126
|
+
const getEncUsages = () => isPublic ? ['encrypt', 'wrapKey'] : ['decrypt', 'unwrapKey'];
|
|
57
127
|
switch (alg) {
|
|
58
128
|
case 'PS256':
|
|
59
129
|
case 'PS384':
|
|
60
130
|
case 'PS512':
|
|
61
131
|
algorithm = { name: 'RSA-PSS', hash: `SHA-${alg.slice(-3)}` };
|
|
62
|
-
keyUsages =
|
|
132
|
+
keyUsages = getSigUsages();
|
|
63
133
|
break;
|
|
64
134
|
case 'RS256':
|
|
65
135
|
case 'RS384':
|
|
66
136
|
case 'RS512':
|
|
67
137
|
algorithm = { name: 'RSASSA-PKCS1-v1_5', hash: `SHA-${alg.slice(-3)}` };
|
|
68
|
-
keyUsages =
|
|
138
|
+
keyUsages = getSigUsages();
|
|
69
139
|
break;
|
|
70
140
|
case 'RSA-OAEP':
|
|
71
141
|
case 'RSA-OAEP-256':
|
|
@@ -75,88 +145,88 @@ const genericImport = async (keyFormat, keyData, alg, options) => {
|
|
|
75
145
|
name: 'RSA-OAEP',
|
|
76
146
|
hash: `SHA-${parseInt(alg.slice(-3), 10) || 1}`,
|
|
77
147
|
};
|
|
78
|
-
keyUsages =
|
|
148
|
+
keyUsages = getEncUsages();
|
|
79
149
|
break;
|
|
80
150
|
case 'ES256':
|
|
81
151
|
case 'ES384':
|
|
82
152
|
case 'ES512': {
|
|
83
153
|
const curveMap = { ES256: 'P-256', ES384: 'P-384', ES512: 'P-521' };
|
|
84
154
|
algorithm = { name: 'ECDSA', namedCurve: curveMap[alg] };
|
|
85
|
-
keyUsages =
|
|
155
|
+
keyUsages = getSigUsages();
|
|
86
156
|
break;
|
|
87
157
|
}
|
|
88
158
|
case 'ECDH-ES':
|
|
89
159
|
case 'ECDH-ES+A128KW':
|
|
90
160
|
case 'ECDH-ES+A192KW':
|
|
91
161
|
case 'ECDH-ES+A256KW': {
|
|
92
|
-
|
|
93
|
-
|
|
162
|
+
try {
|
|
163
|
+
const namedCurve = options.getNamedCurve(keyData);
|
|
164
|
+
algorithm = namedCurve === 'X25519' ? { name: 'X25519' } : { name: 'ECDH', namedCurve };
|
|
165
|
+
}
|
|
166
|
+
catch (cause) {
|
|
167
|
+
throw new JOSENotSupported('Invalid or unsupported key format');
|
|
168
|
+
}
|
|
94
169
|
keyUsages = isPublic ? [] : ['deriveBits'];
|
|
95
170
|
break;
|
|
96
171
|
}
|
|
97
172
|
case 'Ed25519':
|
|
98
173
|
case 'EdDSA':
|
|
99
174
|
algorithm = { name: 'Ed25519' };
|
|
100
|
-
keyUsages =
|
|
175
|
+
keyUsages = getSigUsages();
|
|
101
176
|
break;
|
|
102
177
|
default:
|
|
103
178
|
throw new JOSENotSupported('Invalid or unsupported "alg" (Algorithm) value');
|
|
104
179
|
}
|
|
105
180
|
return crypto.subtle.importKey(keyFormat, keyData, algorithm, options?.extractable ?? (isPublic ? true : false), keyUsages);
|
|
106
181
|
};
|
|
182
|
+
const processPEMData = (pem, pattern) => {
|
|
183
|
+
return decodeBase64(pem.replace(pattern, ''));
|
|
184
|
+
};
|
|
107
185
|
export const fromPKCS8 = (pem, alg, options) => {
|
|
108
|
-
const keyData =
|
|
109
|
-
|
|
186
|
+
const keyData = processPEMData(pem, /(?:-----(?:BEGIN|END) PRIVATE KEY-----|\s)/g);
|
|
187
|
+
let opts = options;
|
|
188
|
+
if (alg?.startsWith?.('ECDH-ES')) {
|
|
189
|
+
opts ||= {};
|
|
190
|
+
opts.getNamedCurve = (keyData) => {
|
|
191
|
+
const state = createASN1State(keyData);
|
|
192
|
+
parsePKCS8Header(state);
|
|
193
|
+
return parseECAlgorithmIdentifier(state);
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
return genericImport('pkcs8', keyData, alg, opts);
|
|
110
197
|
};
|
|
111
198
|
export const fromSPKI = (pem, alg, options) => {
|
|
112
|
-
const keyData =
|
|
113
|
-
|
|
199
|
+
const keyData = processPEMData(pem, /(?:-----(?:BEGIN|END) PUBLIC KEY-----|\s)/g);
|
|
200
|
+
let opts = options;
|
|
201
|
+
if (alg?.startsWith?.('ECDH-ES')) {
|
|
202
|
+
opts ||= {};
|
|
203
|
+
opts.getNamedCurve = (keyData) => {
|
|
204
|
+
const state = createASN1State(keyData);
|
|
205
|
+
parseSPKIHeader(state);
|
|
206
|
+
return parseECAlgorithmIdentifier(state);
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
return genericImport('spki', keyData, alg, opts);
|
|
114
210
|
};
|
|
115
211
|
function spkiFromX509(buf) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
length = (length << 8) | buf[pos++];
|
|
124
|
-
}
|
|
125
|
-
return length;
|
|
126
|
-
}
|
|
127
|
-
return first;
|
|
128
|
-
};
|
|
129
|
-
const skipElement = (count = 1) => {
|
|
130
|
-
if (count <= 0)
|
|
131
|
-
return;
|
|
132
|
-
pos++;
|
|
133
|
-
const length = parseLength();
|
|
134
|
-
pos += length;
|
|
135
|
-
if (count > 1) {
|
|
136
|
-
skipElement(count - 1);
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
|
-
if (buf[pos++] !== 0x30)
|
|
140
|
-
throw new Error('Invalid certificate structure');
|
|
141
|
-
parseLength();
|
|
142
|
-
if (buf[pos++] !== 0x30)
|
|
143
|
-
throw new Error('Invalid tbsCertificate structure');
|
|
144
|
-
parseLength();
|
|
145
|
-
if (buf[pos] === 0xa0) {
|
|
146
|
-
skipElement(6);
|
|
212
|
+
const state = createASN1State(buf);
|
|
213
|
+
expectTag(state, 0x30, 'Invalid certificate structure');
|
|
214
|
+
parseLength(state);
|
|
215
|
+
expectTag(state, 0x30, 'Invalid tbsCertificate structure');
|
|
216
|
+
parseLength(state);
|
|
217
|
+
if (buf[state.pos] === 0xa0) {
|
|
218
|
+
skipElement(state, 6);
|
|
147
219
|
}
|
|
148
220
|
else {
|
|
149
|
-
skipElement(5);
|
|
221
|
+
skipElement(state, 5);
|
|
150
222
|
}
|
|
151
|
-
const spkiStart = pos;
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return buf.subarray(spkiStart, spkiStart + spkiContentLength + (pos - spkiStart));
|
|
223
|
+
const spkiStart = state.pos;
|
|
224
|
+
expectTag(state, 0x30, 'Invalid SPKI structure');
|
|
225
|
+
const spkiContentLen = parseLength(state);
|
|
226
|
+
return buf.subarray(spkiStart, spkiStart + spkiContentLen + (state.pos - spkiStart));
|
|
156
227
|
}
|
|
157
228
|
function extractX509SPKI(x509) {
|
|
158
|
-
const
|
|
159
|
-
const derBytes = decodeBase64(base64Content);
|
|
229
|
+
const derBytes = processPEMData(x509, /(?:-----(?:BEGIN|END) CERTIFICATE-----|\s)/g);
|
|
160
230
|
return spkiFromX509(derBytes);
|
|
161
231
|
}
|
|
162
232
|
export const fromX509 = (pem, alg, options) => {
|
|
@@ -167,5 +237,5 @@ export const fromX509 = (pem, alg, options) => {
|
|
|
167
237
|
catch (cause) {
|
|
168
238
|
throw new TypeError('Failed to parse the X.509 certificate', { cause });
|
|
169
239
|
}
|
|
170
|
-
return
|
|
240
|
+
return fromSPKI(formatPEM(encodeBase64(spki), 'PUBLIC KEY'), alg, options);
|
|
171
241
|
};
|
|
@@ -4,35 +4,41 @@ import digest from './digest.js';
|
|
|
4
4
|
function lengthAndInput(input) {
|
|
5
5
|
return concat(uint32be(input.length), input);
|
|
6
6
|
}
|
|
7
|
-
async function concatKdf(
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
async function concatKdf(Z, L, OtherInfo) {
|
|
8
|
+
const dkLen = L >> 3;
|
|
9
|
+
const hashLen = 32;
|
|
10
|
+
const reps = Math.ceil(dkLen / hashLen);
|
|
11
|
+
const dk = new Uint8Array(reps * hashLen);
|
|
12
|
+
for (let i = 1; i <= reps; i++) {
|
|
13
|
+
const hashInput = new Uint8Array(4 + Z.length + OtherInfo.length);
|
|
14
|
+
hashInput.set(uint32be(i), 0);
|
|
15
|
+
hashInput.set(Z, 4);
|
|
16
|
+
hashInput.set(OtherInfo, 4 + Z.length);
|
|
17
|
+
const hashResult = await digest('sha256', hashInput);
|
|
18
|
+
dk.set(hashResult, (i - 1) * hashLen);
|
|
16
19
|
}
|
|
17
|
-
return
|
|
20
|
+
return dk.slice(0, dkLen);
|
|
18
21
|
}
|
|
19
22
|
export async function deriveKey(publicKey, privateKey, algorithm, keyLength, apu = new Uint8Array(0), apv = new Uint8Array(0)) {
|
|
20
23
|
checkEncCryptoKey(publicKey, 'ECDH');
|
|
21
24
|
checkEncCryptoKey(privateKey, 'ECDH', 'deriveBits');
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Math.ceil(parseInt(publicKey.algorithm.namedCurve.slice(-3), 10) / 8) << 3;
|
|
30
|
-
}
|
|
31
|
-
const sharedSecret = new Uint8Array(await crypto.subtle.deriveBits({
|
|
25
|
+
const algorithmID = lengthAndInput(encoder.encode(algorithm));
|
|
26
|
+
const partyUInfo = lengthAndInput(apu);
|
|
27
|
+
const partyVInfo = lengthAndInput(apv);
|
|
28
|
+
const suppPubInfo = uint32be(keyLength);
|
|
29
|
+
const suppPrivInfo = new Uint8Array(0);
|
|
30
|
+
const otherInfo = concat(algorithmID, partyUInfo, partyVInfo, suppPubInfo, suppPrivInfo);
|
|
31
|
+
const Z = new Uint8Array(await crypto.subtle.deriveBits({
|
|
32
32
|
name: publicKey.algorithm.name,
|
|
33
33
|
public: publicKey,
|
|
34
|
-
}, privateKey,
|
|
35
|
-
return concatKdf(
|
|
34
|
+
}, privateKey, getEcdhBitLength(publicKey)));
|
|
35
|
+
return concatKdf(Z, keyLength, otherInfo);
|
|
36
|
+
}
|
|
37
|
+
function getEcdhBitLength(publicKey) {
|
|
38
|
+
if (publicKey.algorithm.name === 'X25519') {
|
|
39
|
+
return 256;
|
|
40
|
+
}
|
|
41
|
+
return (Math.ceil(parseInt(publicKey.algorithm.namedCurve.slice(-3), 10) / 8) << 3);
|
|
36
42
|
}
|
|
37
43
|
export function allowed(key) {
|
|
38
44
|
switch (key.algorithm.namedCurve) {
|