jose 6.0.12 → 6.1.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/dist/types/types.d.ts +26 -6
- package/dist/webapi/jwk/thumbprint.js +5 -0
- package/dist/webapi/jwks/local.js +3 -1
- package/dist/webapi/jwks/remote.js +1 -1
- package/dist/webapi/key/generate_key_pair.js +7 -0
- package/dist/webapi/key/import.js +10 -0
- package/dist/webapi/lib/asn1.js +144 -68
- package/dist/webapi/lib/crypto_key.js +7 -0
- package/dist/webapi/lib/ecdhes.js +28 -22
- package/dist/webapi/lib/is_jwk.js +3 -2
- package/dist/webapi/lib/jwk_to_key.js +17 -2
- package/dist/webapi/lib/key_to_jwk.js +4 -0
- package/dist/webapi/lib/normalize_key.js +12 -0
- package/dist/webapi/lib/subtle_dsa.js +4 -0
- package/package.json +4 -2
package/dist/types/types.d.ts
CHANGED
|
@@ -35,11 +35,25 @@ export interface JWK_OKP_Public extends JWKParameters {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/** Convenience interface for Private OKP JSON Web Keys */
|
|
38
|
-
export interface JWK_OKP_Private extends JWK_OKP_Public
|
|
38
|
+
export interface JWK_OKP_Private extends JWK_OKP_Public {
|
|
39
39
|
/** OKP JWK "d" (The Private Key) Parameter */
|
|
40
40
|
d: string
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/** Convenience interface for Public AKP JSON Web Keys */
|
|
44
|
+
export interface JWK_AKP_Public extends JWKParameters {
|
|
45
|
+
/** JWK "alg" (Algorithm) Parameter */
|
|
46
|
+
alg: string
|
|
47
|
+
/** AKP JWK "pub" (The Public key) Parameter */
|
|
48
|
+
pub: string
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Convenience interface for Private AKP JSON Web Keys */
|
|
52
|
+
export interface JWK_AKP_Private extends JWK_AKP_Public {
|
|
53
|
+
/** AKP JWK "priv" (The Private Key) Parameter */
|
|
54
|
+
priv: string
|
|
55
|
+
}
|
|
56
|
+
|
|
43
57
|
/** Convenience interface for Public EC JSON Web Keys */
|
|
44
58
|
export interface JWK_EC_Public extends JWKParameters {
|
|
45
59
|
/** EC JWK "crv" (Curve) Parameter */
|
|
@@ -51,7 +65,7 @@ export interface JWK_EC_Public extends JWKParameters {
|
|
|
51
65
|
}
|
|
52
66
|
|
|
53
67
|
/** Convenience interface for Private EC JSON Web Keys */
|
|
54
|
-
export interface JWK_EC_Private extends JWK_EC_Public
|
|
68
|
+
export interface JWK_EC_Private extends JWK_EC_Public {
|
|
55
69
|
/** EC JWK "d" (ECC Private Key) Parameter */
|
|
56
70
|
d: string
|
|
57
71
|
}
|
|
@@ -65,7 +79,7 @@ export interface JWK_RSA_Public extends JWKParameters {
|
|
|
65
79
|
}
|
|
66
80
|
|
|
67
81
|
/** Convenience interface for Private RSA JSON Web Keys */
|
|
68
|
-
export interface JWK_RSA_Private extends JWK_RSA_Public
|
|
82
|
+
export interface JWK_RSA_Private extends JWK_RSA_Public {
|
|
69
83
|
/** RSA JWK "d" (Private Exponent) Parameter */
|
|
70
84
|
d: string
|
|
71
85
|
/** RSA JWK "dp" (First Factor CRT Exponent) Parameter */
|
|
@@ -87,9 +101,11 @@ export interface JWK_oct extends JWKParameters {
|
|
|
87
101
|
}
|
|
88
102
|
|
|
89
103
|
/**
|
|
90
|
-
* JSON Web Key ({@link https://www.rfc-editor.org/rfc/rfc7517 JWK}). "RSA", "EC", "OKP",
|
|
91
|
-
* key types are supported.
|
|
104
|
+
* JSON Web Key ({@link https://www.rfc-editor.org/rfc/rfc7517 JWK}). "RSA", "EC", "OKP", "AKP", and
|
|
105
|
+
* "oct" key types are supported.
|
|
92
106
|
*
|
|
107
|
+
* @see {@link JWK_AKP_Public}
|
|
108
|
+
* @see {@link JWK_AKP_Private}
|
|
93
109
|
* @see {@link JWK_OKP_Public}
|
|
94
110
|
* @see {@link JWK_OKP_Private}
|
|
95
111
|
* @see {@link JWK_EC_Public}
|
|
@@ -133,6 +149,10 @@ export interface JWK extends JWKParameters {
|
|
|
133
149
|
x?: string
|
|
134
150
|
/** EC JWK "y" (Y Coordinate) Parameter */
|
|
135
151
|
y?: string
|
|
152
|
+
/** AKP JWK "pub" (Public Key) Parameter */
|
|
153
|
+
pub?: string
|
|
154
|
+
/** AKP JWK "priv" (Private key) Parameter */
|
|
155
|
+
priv?: string
|
|
136
156
|
}
|
|
137
157
|
|
|
138
158
|
/**
|
|
@@ -251,7 +271,7 @@ export interface JoseHeaderParameters {
|
|
|
251
271
|
jku?: string
|
|
252
272
|
|
|
253
273
|
/** "jwk" (JSON Web Key) Header Parameter */
|
|
254
|
-
jwk?: Pick<JWK, 'kty' | 'crv' | 'x' | 'y' | 'e' | 'n'>
|
|
274
|
+
jwk?: Pick<JWK, 'kty' | 'crv' | 'x' | 'y' | 'e' | 'n' | 'alg' | 'pub'>
|
|
255
275
|
|
|
256
276
|
/** "typ" (Type) Header Parameter */
|
|
257
277
|
typ?: string
|
|
@@ -30,6 +30,11 @@ export async function calculateJwkThumbprint(key, digestAlgorithm) {
|
|
|
30
30
|
}
|
|
31
31
|
let components;
|
|
32
32
|
switch (jwk.kty) {
|
|
33
|
+
case 'AKP':
|
|
34
|
+
check(jwk.alg, '"alg" (Algorithm) Parameter');
|
|
35
|
+
check(jwk.pub, '"pub" (Public key) Parameter');
|
|
36
|
+
components = { alg: jwk.alg, kty: jwk.kty, pub: jwk.pub };
|
|
37
|
+
break;
|
|
33
38
|
case 'EC':
|
|
34
39
|
check(jwk.crv, '"crv" (Curve) Parameter');
|
|
35
40
|
check(jwk.x, '"x" (X Coordinate) Parameter');
|
|
@@ -10,6 +10,8 @@ function getKtyFromAlg(alg) {
|
|
|
10
10
|
return 'EC';
|
|
11
11
|
case 'Ed':
|
|
12
12
|
return 'OKP';
|
|
13
|
+
case 'ML':
|
|
14
|
+
return 'AKP';
|
|
13
15
|
default:
|
|
14
16
|
throw new JOSENotSupported('Unsupported "alg" value for a JSON Web Key Set');
|
|
15
17
|
}
|
|
@@ -43,7 +45,7 @@ class LocalJWKSet {
|
|
|
43
45
|
if (candidate && typeof kid === 'string') {
|
|
44
46
|
candidate = kid === jwk.kid;
|
|
45
47
|
}
|
|
46
|
-
if (candidate && typeof jwk.alg === 'string') {
|
|
48
|
+
if (candidate && (typeof jwk.alg === 'string' || kty === 'AKP')) {
|
|
47
49
|
candidate = alg === jwk.alg;
|
|
48
50
|
}
|
|
49
51
|
if (candidate && typeof jwk.use === 'string') {
|
|
@@ -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.1.0';
|
|
13
13
|
USER_AGENT = `${NAME}/${VERSION}`;
|
|
14
14
|
}
|
|
15
15
|
export const customFetch = Symbol();
|
|
@@ -62,6 +62,13 @@ export async function generateKeyPair(alg, options) {
|
|
|
62
62
|
algorithm = { name: 'Ed25519' };
|
|
63
63
|
break;
|
|
64
64
|
}
|
|
65
|
+
case 'ML-DSA-44':
|
|
66
|
+
case 'ML-DSA-65':
|
|
67
|
+
case 'ML-DSA-87': {
|
|
68
|
+
keyUsages = ['sign', 'verify'];
|
|
69
|
+
algorithm = { name: alg };
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
65
72
|
case 'ECDH-ES':
|
|
66
73
|
case 'ECDH-ES+A128KW':
|
|
67
74
|
case 'ECDH-ES+A192KW':
|
|
@@ -38,6 +38,16 @@ export async function importJWK(jwk, alg, options) {
|
|
|
38
38
|
if ('oth' in jwk && jwk.oth !== undefined) {
|
|
39
39
|
throw new JOSENotSupported('RSA JWK "oth" (Other Primes Info) Parameter value is not supported');
|
|
40
40
|
}
|
|
41
|
+
return toCryptoKey({ ...jwk, alg, ext });
|
|
42
|
+
case 'AKP': {
|
|
43
|
+
if (typeof jwk.alg !== 'string' || !jwk.alg) {
|
|
44
|
+
throw new TypeError('missing "alg" (Algorithm) Parameter value');
|
|
45
|
+
}
|
|
46
|
+
if (alg !== undefined && alg !== jwk.alg) {
|
|
47
|
+
throw new TypeError('JWK alg and alg option value mismatch');
|
|
48
|
+
}
|
|
49
|
+
return toCryptoKey({ ...jwk, ext });
|
|
50
|
+
}
|
|
41
51
|
case 'EC':
|
|
42
52
|
case 'OKP':
|
|
43
53
|
return toCryptoKey({ ...jwk, alg, ext });
|
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++];
|
|
47
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);
|
|
48
68
|
}
|
|
49
|
-
|
|
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;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
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,94 @@ 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();
|
|
176
|
+
break;
|
|
177
|
+
case 'ML-DSA-44':
|
|
178
|
+
case 'ML-DSA-65':
|
|
179
|
+
case 'ML-DSA-87':
|
|
180
|
+
algorithm = { name: alg };
|
|
181
|
+
keyUsages = getSigUsages();
|
|
101
182
|
break;
|
|
102
183
|
default:
|
|
103
184
|
throw new JOSENotSupported('Invalid or unsupported "alg" (Algorithm) value');
|
|
104
185
|
}
|
|
105
186
|
return crypto.subtle.importKey(keyFormat, keyData, algorithm, options?.extractable ?? (isPublic ? true : false), keyUsages);
|
|
106
187
|
};
|
|
188
|
+
const processPEMData = (pem, pattern) => {
|
|
189
|
+
return decodeBase64(pem.replace(pattern, ''));
|
|
190
|
+
};
|
|
107
191
|
export const fromPKCS8 = (pem, alg, options) => {
|
|
108
|
-
const keyData =
|
|
109
|
-
|
|
192
|
+
const keyData = processPEMData(pem, /(?:-----(?:BEGIN|END) PRIVATE KEY-----|\s)/g);
|
|
193
|
+
let opts = options;
|
|
194
|
+
if (alg?.startsWith?.('ECDH-ES')) {
|
|
195
|
+
opts ||= {};
|
|
196
|
+
opts.getNamedCurve = (keyData) => {
|
|
197
|
+
const state = createASN1State(keyData);
|
|
198
|
+
parsePKCS8Header(state);
|
|
199
|
+
return parseECAlgorithmIdentifier(state);
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return genericImport('pkcs8', keyData, alg, opts);
|
|
110
203
|
};
|
|
111
204
|
export const fromSPKI = (pem, alg, options) => {
|
|
112
|
-
const keyData =
|
|
113
|
-
|
|
205
|
+
const keyData = processPEMData(pem, /(?:-----(?:BEGIN|END) PUBLIC KEY-----|\s)/g);
|
|
206
|
+
let opts = options;
|
|
207
|
+
if (alg?.startsWith?.('ECDH-ES')) {
|
|
208
|
+
opts ||= {};
|
|
209
|
+
opts.getNamedCurve = (keyData) => {
|
|
210
|
+
const state = createASN1State(keyData);
|
|
211
|
+
parseSPKIHeader(state);
|
|
212
|
+
return parseECAlgorithmIdentifier(state);
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
return genericImport('spki', keyData, alg, opts);
|
|
114
216
|
};
|
|
115
217
|
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);
|
|
218
|
+
const state = createASN1State(buf);
|
|
219
|
+
expectTag(state, 0x30, 'Invalid certificate structure');
|
|
220
|
+
parseLength(state);
|
|
221
|
+
expectTag(state, 0x30, 'Invalid tbsCertificate structure');
|
|
222
|
+
parseLength(state);
|
|
223
|
+
if (buf[state.pos] === 0xa0) {
|
|
224
|
+
skipElement(state, 6);
|
|
147
225
|
}
|
|
148
226
|
else {
|
|
149
|
-
skipElement(5);
|
|
227
|
+
skipElement(state, 5);
|
|
150
228
|
}
|
|
151
|
-
const spkiStart = pos;
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return buf.subarray(spkiStart, spkiStart + spkiContentLength + (pos - spkiStart));
|
|
229
|
+
const spkiStart = state.pos;
|
|
230
|
+
expectTag(state, 0x30, 'Invalid SPKI structure');
|
|
231
|
+
const spkiContentLen = parseLength(state);
|
|
232
|
+
return buf.subarray(spkiStart, spkiStart + spkiContentLen + (state.pos - spkiStart));
|
|
156
233
|
}
|
|
157
234
|
function extractX509SPKI(x509) {
|
|
158
|
-
const
|
|
159
|
-
const derBytes = decodeBase64(base64Content);
|
|
235
|
+
const derBytes = processPEMData(x509, /(?:-----(?:BEGIN|END) CERTIFICATE-----|\s)/g);
|
|
160
236
|
return spkiFromX509(derBytes);
|
|
161
237
|
}
|
|
162
238
|
export const fromX509 = (pem, alg, options) => {
|
|
@@ -167,5 +243,5 @@ export const fromX509 = (pem, alg, options) => {
|
|
|
167
243
|
catch (cause) {
|
|
168
244
|
throw new TypeError('Failed to parse the X.509 certificate', { cause });
|
|
169
245
|
}
|
|
170
|
-
return
|
|
246
|
+
return fromSPKI(formatPEM(encodeBase64(spki), 'PUBLIC KEY'), alg, options);
|
|
171
247
|
};
|
|
@@ -65,6 +65,13 @@ export function checkSigCryptoKey(key, alg, usage) {
|
|
|
65
65
|
throw unusable('Ed25519');
|
|
66
66
|
break;
|
|
67
67
|
}
|
|
68
|
+
case 'ML-DSA-44':
|
|
69
|
+
case 'ML-DSA-65':
|
|
70
|
+
case 'ML-DSA-87': {
|
|
71
|
+
if (!isAlgorithm(key.algorithm, alg))
|
|
72
|
+
throw unusable(alg);
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
68
75
|
case 'ES256':
|
|
69
76
|
case 'ES384':
|
|
70
77
|
case 'ES512': {
|
|
@@ -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) {
|
|
@@ -3,10 +3,11 @@ export function isJWK(key) {
|
|
|
3
3
|
return isObject(key) && typeof key.kty === 'string';
|
|
4
4
|
}
|
|
5
5
|
export function isPrivateJWK(key) {
|
|
6
|
-
return key.kty !== 'oct' &&
|
|
6
|
+
return (key.kty !== 'oct' &&
|
|
7
|
+
((key.kty === 'AKP' && typeof key.priv === 'string') || typeof key.d === 'string'));
|
|
7
8
|
}
|
|
8
9
|
export function isPublicJWK(key) {
|
|
9
|
-
return key.kty !== 'oct' && typeof key.d === 'undefined';
|
|
10
|
+
return key.kty !== 'oct' && typeof key.d === 'undefined' && typeof key.priv === 'undefined';
|
|
10
11
|
}
|
|
11
12
|
export function isSecretJWK(key) {
|
|
12
13
|
return key.kty === 'oct' && typeof key.k === 'string';
|
|
@@ -3,6 +3,19 @@ function subtleMapping(jwk) {
|
|
|
3
3
|
let algorithm;
|
|
4
4
|
let keyUsages;
|
|
5
5
|
switch (jwk.kty) {
|
|
6
|
+
case 'AKP': {
|
|
7
|
+
switch (jwk.alg) {
|
|
8
|
+
case 'ML-DSA-44':
|
|
9
|
+
case 'ML-DSA-65':
|
|
10
|
+
case 'ML-DSA-87':
|
|
11
|
+
algorithm = { name: jwk.alg };
|
|
12
|
+
keyUsages = jwk.priv ? ['sign'] : ['verify'];
|
|
13
|
+
break;
|
|
14
|
+
default:
|
|
15
|
+
throw new JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value');
|
|
16
|
+
}
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
6
19
|
case 'RSA': {
|
|
7
20
|
switch (jwk.alg) {
|
|
8
21
|
case 'PS256':
|
|
@@ -88,7 +101,9 @@ export default async (jwk) => {
|
|
|
88
101
|
}
|
|
89
102
|
const { algorithm, keyUsages } = subtleMapping(jwk);
|
|
90
103
|
const keyData = { ...jwk };
|
|
91
|
-
|
|
104
|
+
if (keyData.kty !== 'AKP') {
|
|
105
|
+
delete keyData.alg;
|
|
106
|
+
}
|
|
92
107
|
delete keyData.use;
|
|
93
|
-
return crypto.subtle.importKey('jwk', keyData, algorithm, jwk.ext ?? (jwk.d ? false : true), jwk.key_ops ?? keyUsages);
|
|
108
|
+
return crypto.subtle.importKey('jwk', keyData, algorithm, jwk.ext ?? (jwk.d || jwk.priv ? false : true), jwk.key_ops ?? keyUsages);
|
|
94
109
|
};
|
|
@@ -23,5 +23,9 @@ export default async function keyToJWK(key) {
|
|
|
23
23
|
throw new TypeError('non-extractable CryptoKey cannot be exported as a JWK');
|
|
24
24
|
}
|
|
25
25
|
const { ext, key_ops, alg, use, ...jwk } = await crypto.subtle.exportKey('jwk', key);
|
|
26
|
+
if (jwk.kty === 'AKP') {
|
|
27
|
+
;
|
|
28
|
+
jwk.alg = alg;
|
|
29
|
+
}
|
|
26
30
|
return jwk;
|
|
27
31
|
}
|
|
@@ -49,6 +49,18 @@ const handleKeyObject = (keyObject, alg) => {
|
|
|
49
49
|
isPublic ? 'verify' : 'sign',
|
|
50
50
|
]);
|
|
51
51
|
}
|
|
52
|
+
switch (keyObject.asymmetricKeyType) {
|
|
53
|
+
case 'ml-dsa-44':
|
|
54
|
+
case 'ml-dsa-65':
|
|
55
|
+
case 'ml-dsa-87': {
|
|
56
|
+
if (alg !== keyObject.asymmetricKeyType.toUpperCase()) {
|
|
57
|
+
throw new TypeError('given KeyObject instance cannot be used for this algorithm');
|
|
58
|
+
}
|
|
59
|
+
cryptoKey = keyObject.toCryptoKey(keyObject.asymmetricKeyType, extractable, [
|
|
60
|
+
isPublic ? 'verify' : 'sign',
|
|
61
|
+
]);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
52
64
|
if (keyObject.asymmetricKeyType === 'rsa') {
|
|
53
65
|
let hash;
|
|
54
66
|
switch (alg) {
|
|
@@ -21,6 +21,10 @@ export default (alg, algorithm) => {
|
|
|
21
21
|
case 'Ed25519':
|
|
22
22
|
case 'EdDSA':
|
|
23
23
|
return { name: 'Ed25519' };
|
|
24
|
+
case 'ML-DSA-44':
|
|
25
|
+
case 'ML-DSA-65':
|
|
26
|
+
case 'ML-DSA-87':
|
|
27
|
+
return { name: alg };
|
|
24
28
|
default:
|
|
25
29
|
throw new JOSENotSupported(`alg ${alg} is not supported either by JOSE or your javascript runtime`);
|
|
26
30
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jose",
|
|
3
|
-
"version": "6.0
|
|
3
|
+
"version": "6.1.0",
|
|
4
4
|
"description": "JWA, JWS, JWE, JWT, JWK, JWKS for Node.js, Browser, Cloudflare Workers, Deno, Bun, and other Web-interoperable runtimes",
|
|
5
5
|
"keywords": [
|
|
6
|
+
"akp",
|
|
6
7
|
"browser",
|
|
7
8
|
"bun",
|
|
8
9
|
"cloudflare",
|
|
@@ -29,8 +30,9 @@
|
|
|
29
30
|
"jwk",
|
|
30
31
|
"jwks",
|
|
31
32
|
"jws",
|
|
32
|
-
"jwt",
|
|
33
33
|
"jwt-decode",
|
|
34
|
+
"jwt",
|
|
35
|
+
"ml-dsa",
|
|
34
36
|
"netlify",
|
|
35
37
|
"next",
|
|
36
38
|
"nextjs",
|