@oari/jose 0.0.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.md +21 -0
- package/README.md +150 -0
- package/dist/types/index.d.ts +55 -0
- package/dist/types/jwe/compact/decrypt.d.ts +43 -0
- package/dist/types/jwe/compact/encrypt.d.ts +76 -0
- package/dist/types/jwe/flattened/decrypt.d.ts +53 -0
- package/dist/types/jwe/flattened/encrypt.d.ts +95 -0
- package/dist/types/jwe/general/decrypt.d.ts +64 -0
- package/dist/types/jwe/general/encrypt.d.ts +89 -0
- package/dist/types/jwk/embedded.d.ts +31 -0
- package/dist/types/jwk/thumbprint.d.ts +60 -0
- package/dist/types/jwks/local.d.ts +90 -0
- package/dist/types/jwks/remote.d.ts +306 -0
- package/dist/types/jws/compact/sign.d.ts +47 -0
- package/dist/types/jws/compact/verify.d.ts +45 -0
- package/dist/types/jws/flattened/sign.d.ts +53 -0
- package/dist/types/jws/flattened/verify.d.ts +50 -0
- package/dist/types/jws/general/sign.d.ts +67 -0
- package/dist/types/jws/general/verify.d.ts +61 -0
- package/dist/types/jwt/decrypt.d.ts +51 -0
- package/dist/types/jwt/encrypt.d.ts +105 -0
- package/dist/types/jwt/sign.d.ts +140 -0
- package/dist/types/jwt/unsecured.d.ts +70 -0
- package/dist/types/jwt/verify.d.ts +124 -0
- package/dist/types/key/export.d.ts +59 -0
- package/dist/types/key/generate_key_pair.d.ts +64 -0
- package/dist/types/key/generate_secret.d.ts +42 -0
- package/dist/types/key/import.d.ts +146 -0
- package/dist/types/types.d.ts +869 -0
- package/dist/types/util/base64url.d.ts +9 -0
- package/dist/types/util/decode_jwt.d.ts +25 -0
- package/dist/types/util/decode_protected_header.d.ts +24 -0
- package/dist/types/util/errors.d.ts +488 -0
- package/dist/webapi/index.js +32 -0
- package/dist/webapi/jwe/compact/decrypt.js +27 -0
- package/dist/webapi/jwe/compact/encrypt.js +27 -0
- package/dist/webapi/jwe/flattened/decrypt.js +159 -0
- package/dist/webapi/jwe/flattened/encrypt.js +167 -0
- package/dist/webapi/jwe/general/decrypt.js +31 -0
- package/dist/webapi/jwe/general/encrypt.js +182 -0
- package/dist/webapi/jwk/embedded.js +17 -0
- package/dist/webapi/jwk/thumbprint.js +68 -0
- package/dist/webapi/jwks/local.js +119 -0
- package/dist/webapi/jwks/remote.js +179 -0
- package/dist/webapi/jws/compact/sign.js +18 -0
- package/dist/webapi/jws/compact/verify.js +21 -0
- package/dist/webapi/jws/flattened/sign.js +87 -0
- package/dist/webapi/jws/flattened/verify.js +110 -0
- package/dist/webapi/jws/general/sign.js +70 -0
- package/dist/webapi/jws/general/verify.js +24 -0
- package/dist/webapi/jwt/decrypt.js +23 -0
- package/dist/webapi/jwt/encrypt.js +101 -0
- package/dist/webapi/jwt/sign.js +52 -0
- package/dist/webapi/jwt/unsecured.js +63 -0
- package/dist/webapi/jwt/verify.js +15 -0
- package/dist/webapi/key/export.js +11 -0
- package/dist/webapi/key/generate_key_pair.js +97 -0
- package/dist/webapi/key/generate_secret.js +40 -0
- package/dist/webapi/key/import.js +57 -0
- package/dist/webapi/lib/aesgcmkw.js +15 -0
- package/dist/webapi/lib/aeskw.js +25 -0
- package/dist/webapi/lib/asn1.js +243 -0
- package/dist/webapi/lib/base64.js +22 -0
- package/dist/webapi/lib/buffer_utils.js +43 -0
- package/dist/webapi/lib/check_key_type.js +127 -0
- package/dist/webapi/lib/content_encryption.js +217 -0
- package/dist/webapi/lib/crypto_key.js +136 -0
- package/dist/webapi/lib/deflate.js +44 -0
- package/dist/webapi/lib/ecdhes.js +52 -0
- package/dist/webapi/lib/helpers.js +19 -0
- package/dist/webapi/lib/invalid_key_input.js +27 -0
- package/dist/webapi/lib/is_key_like.js +17 -0
- package/dist/webapi/lib/jwk_to_key.js +107 -0
- package/dist/webapi/lib/jwt_claims_set.js +238 -0
- package/dist/webapi/lib/key_management.js +186 -0
- package/dist/webapi/lib/key_to_jwk.js +31 -0
- package/dist/webapi/lib/normalize_key.js +166 -0
- package/dist/webapi/lib/pbes2kw.js +42 -0
- package/dist/webapi/lib/rsaes.js +24 -0
- package/dist/webapi/lib/signing.js +74 -0
- package/dist/webapi/lib/type_checks.js +41 -0
- package/dist/webapi/lib/validate_algorithms.js +10 -0
- package/dist/webapi/lib/validate_crit.js +33 -0
- package/dist/webapi/util/base64url.js +30 -0
- package/dist/webapi/util/decode_jwt.js +32 -0
- package/dist/webapi/util/decode_protected_header.js +34 -0
- package/dist/webapi/util/errors.js +99 -0
- package/package.json +195 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import * as aeskw from './aeskw.js';
|
|
2
|
+
import * as ecdhes from './ecdhes.js';
|
|
3
|
+
import * as pbes2kw from './pbes2kw.js';
|
|
4
|
+
import * as rsaes from './rsaes.js';
|
|
5
|
+
import { encode as b64u } from '../util/base64url.js';
|
|
6
|
+
import { normalizeKey } from './normalize_key.js';
|
|
7
|
+
import { JOSENotSupported, JWEInvalid } from '../util/errors.js';
|
|
8
|
+
import { decodeBase64url } from './helpers.js';
|
|
9
|
+
import { generateCek, cekLength } from './content_encryption.js';
|
|
10
|
+
import { importJWK } from '../key/import.js';
|
|
11
|
+
import { exportJWK } from '../key/export.js';
|
|
12
|
+
import { isObject } from './type_checks.js';
|
|
13
|
+
import { wrap as aesGcmKwWrap, unwrap as aesGcmKwUnwrap } from './aesgcmkw.js';
|
|
14
|
+
import { assertCryptoKey } from './is_key_like.js';
|
|
15
|
+
const unsupportedAlgHeader = 'Invalid or unsupported "alg" (JWE Algorithm) header value';
|
|
16
|
+
function assertEncryptedKey(encryptedKey) {
|
|
17
|
+
if (encryptedKey === undefined)
|
|
18
|
+
throw new JWEInvalid('JWE Encrypted Key missing');
|
|
19
|
+
}
|
|
20
|
+
export async function decryptKeyManagement(alg, key, encryptedKey, joseHeader, options) {
|
|
21
|
+
switch (alg) {
|
|
22
|
+
case 'dir': {
|
|
23
|
+
if (encryptedKey !== undefined)
|
|
24
|
+
throw new JWEInvalid('Encountered unexpected JWE Encrypted Key');
|
|
25
|
+
return key;
|
|
26
|
+
}
|
|
27
|
+
case 'ECDH-ES':
|
|
28
|
+
if (encryptedKey !== undefined)
|
|
29
|
+
throw new JWEInvalid('Encountered unexpected JWE Encrypted Key');
|
|
30
|
+
case 'ECDH-ES+A128KW':
|
|
31
|
+
case 'ECDH-ES+A192KW':
|
|
32
|
+
case 'ECDH-ES+A256KW': {
|
|
33
|
+
if (!isObject(joseHeader.epk))
|
|
34
|
+
throw new JWEInvalid(`JOSE Header "epk" (Ephemeral Public Key) missing or invalid`);
|
|
35
|
+
assertCryptoKey(key);
|
|
36
|
+
if (!ecdhes.allowed(key))
|
|
37
|
+
throw new JOSENotSupported('ECDH with the provided key is not allowed or not supported by your javascript runtime');
|
|
38
|
+
const epk = await importJWK(joseHeader.epk, alg);
|
|
39
|
+
assertCryptoKey(epk);
|
|
40
|
+
let partyUInfo;
|
|
41
|
+
let partyVInfo;
|
|
42
|
+
if (joseHeader.apu !== undefined) {
|
|
43
|
+
if (typeof joseHeader.apu !== 'string')
|
|
44
|
+
throw new JWEInvalid(`JOSE Header "apu" (Agreement PartyUInfo) invalid`);
|
|
45
|
+
partyUInfo = decodeBase64url(joseHeader.apu, 'apu', JWEInvalid);
|
|
46
|
+
}
|
|
47
|
+
if (joseHeader.apv !== undefined) {
|
|
48
|
+
if (typeof joseHeader.apv !== 'string')
|
|
49
|
+
throw new JWEInvalid(`JOSE Header "apv" (Agreement PartyVInfo) invalid`);
|
|
50
|
+
partyVInfo = decodeBase64url(joseHeader.apv, 'apv', JWEInvalid);
|
|
51
|
+
}
|
|
52
|
+
const sharedSecret = await ecdhes.deriveKey(epk, key, alg === 'ECDH-ES' ? joseHeader.enc : alg, alg === 'ECDH-ES' ? cekLength(joseHeader.enc) : parseInt(alg.slice(-5, -2), 10), partyUInfo, partyVInfo);
|
|
53
|
+
if (alg === 'ECDH-ES')
|
|
54
|
+
return sharedSecret;
|
|
55
|
+
assertEncryptedKey(encryptedKey);
|
|
56
|
+
return aeskw.unwrap(alg.slice(-6), sharedSecret, encryptedKey);
|
|
57
|
+
}
|
|
58
|
+
case 'RSA-OAEP':
|
|
59
|
+
case 'RSA-OAEP-256':
|
|
60
|
+
case 'RSA-OAEP-384':
|
|
61
|
+
case 'RSA-OAEP-512': {
|
|
62
|
+
assertEncryptedKey(encryptedKey);
|
|
63
|
+
assertCryptoKey(key);
|
|
64
|
+
return rsaes.decrypt(alg, key, encryptedKey);
|
|
65
|
+
}
|
|
66
|
+
case 'PBES2-HS256+A128KW':
|
|
67
|
+
case 'PBES2-HS384+A192KW':
|
|
68
|
+
case 'PBES2-HS512+A256KW': {
|
|
69
|
+
assertEncryptedKey(encryptedKey);
|
|
70
|
+
if (typeof joseHeader.p2c !== 'number')
|
|
71
|
+
throw new JWEInvalid(`JOSE Header "p2c" (PBES2 Count) missing or invalid`);
|
|
72
|
+
const p2cLimit = options?.maxPBES2Count || 10_000;
|
|
73
|
+
if (joseHeader.p2c > p2cLimit)
|
|
74
|
+
throw new JWEInvalid(`JOSE Header "p2c" (PBES2 Count) out is of acceptable bounds`);
|
|
75
|
+
if (typeof joseHeader.p2s !== 'string')
|
|
76
|
+
throw new JWEInvalid(`JOSE Header "p2s" (PBES2 Salt) missing or invalid`);
|
|
77
|
+
let p2s;
|
|
78
|
+
p2s = decodeBase64url(joseHeader.p2s, 'p2s', JWEInvalid);
|
|
79
|
+
return pbes2kw.unwrap(alg, key, encryptedKey, joseHeader.p2c, p2s);
|
|
80
|
+
}
|
|
81
|
+
case 'A128KW':
|
|
82
|
+
case 'A192KW':
|
|
83
|
+
case 'A256KW': {
|
|
84
|
+
assertEncryptedKey(encryptedKey);
|
|
85
|
+
return aeskw.unwrap(alg, key, encryptedKey);
|
|
86
|
+
}
|
|
87
|
+
case 'A128GCMKW':
|
|
88
|
+
case 'A192GCMKW':
|
|
89
|
+
case 'A256GCMKW': {
|
|
90
|
+
assertEncryptedKey(encryptedKey);
|
|
91
|
+
if (typeof joseHeader.iv !== 'string')
|
|
92
|
+
throw new JWEInvalid(`JOSE Header "iv" (Initialization Vector) missing or invalid`);
|
|
93
|
+
if (typeof joseHeader.tag !== 'string')
|
|
94
|
+
throw new JWEInvalid(`JOSE Header "tag" (Authentication Tag) missing or invalid`);
|
|
95
|
+
let iv;
|
|
96
|
+
iv = decodeBase64url(joseHeader.iv, 'iv', JWEInvalid);
|
|
97
|
+
let tag;
|
|
98
|
+
tag = decodeBase64url(joseHeader.tag, 'tag', JWEInvalid);
|
|
99
|
+
return aesGcmKwUnwrap(alg, key, encryptedKey, iv, tag);
|
|
100
|
+
}
|
|
101
|
+
default: {
|
|
102
|
+
throw new JOSENotSupported(unsupportedAlgHeader);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
export async function encryptKeyManagement(alg, enc, key, providedCek, providedParameters = {}) {
|
|
107
|
+
let encryptedKey;
|
|
108
|
+
let parameters;
|
|
109
|
+
let cek;
|
|
110
|
+
switch (alg) {
|
|
111
|
+
case 'dir': {
|
|
112
|
+
cek = key;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
case 'ECDH-ES':
|
|
116
|
+
case 'ECDH-ES+A128KW':
|
|
117
|
+
case 'ECDH-ES+A192KW':
|
|
118
|
+
case 'ECDH-ES+A256KW': {
|
|
119
|
+
assertCryptoKey(key);
|
|
120
|
+
if (!ecdhes.allowed(key)) {
|
|
121
|
+
throw new JOSENotSupported('ECDH with the provided key is not allowed or not supported by your javascript runtime');
|
|
122
|
+
}
|
|
123
|
+
const { apu, apv } = providedParameters;
|
|
124
|
+
let ephemeralKey;
|
|
125
|
+
if (providedParameters.epk) {
|
|
126
|
+
ephemeralKey = (await normalizeKey(providedParameters.epk, alg));
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
ephemeralKey = (await crypto.subtle.generateKey(key.algorithm, true, ['deriveBits'])).privateKey;
|
|
130
|
+
}
|
|
131
|
+
const { x, y, crv, kty } = await exportJWK(ephemeralKey);
|
|
132
|
+
const sharedSecret = await ecdhes.deriveKey(key, ephemeralKey, alg === 'ECDH-ES' ? enc : alg, alg === 'ECDH-ES' ? cekLength(enc) : parseInt(alg.slice(-5, -2), 10), apu, apv);
|
|
133
|
+
parameters = { epk: { x, crv, kty } };
|
|
134
|
+
if (kty === 'EC')
|
|
135
|
+
parameters.epk.y = y;
|
|
136
|
+
if (apu)
|
|
137
|
+
parameters.apu = b64u(apu);
|
|
138
|
+
if (apv)
|
|
139
|
+
parameters.apv = b64u(apv);
|
|
140
|
+
if (alg === 'ECDH-ES') {
|
|
141
|
+
cek = sharedSecret;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
cek = providedCek || generateCek(enc);
|
|
145
|
+
const kwAlg = alg.slice(-6);
|
|
146
|
+
encryptedKey = await aeskw.wrap(kwAlg, sharedSecret, cek);
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
case 'RSA-OAEP':
|
|
150
|
+
case 'RSA-OAEP-256':
|
|
151
|
+
case 'RSA-OAEP-384':
|
|
152
|
+
case 'RSA-OAEP-512': {
|
|
153
|
+
cek = providedCek || generateCek(enc);
|
|
154
|
+
assertCryptoKey(key);
|
|
155
|
+
encryptedKey = await rsaes.encrypt(alg, key, cek);
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
case 'PBES2-HS256+A128KW':
|
|
159
|
+
case 'PBES2-HS384+A192KW':
|
|
160
|
+
case 'PBES2-HS512+A256KW': {
|
|
161
|
+
cek = providedCek || generateCek(enc);
|
|
162
|
+
const { p2c, p2s } = providedParameters;
|
|
163
|
+
({ encryptedKey, ...parameters } = await pbes2kw.wrap(alg, key, cek, p2c, p2s));
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case 'A128KW':
|
|
167
|
+
case 'A192KW':
|
|
168
|
+
case 'A256KW': {
|
|
169
|
+
cek = providedCek || generateCek(enc);
|
|
170
|
+
encryptedKey = await aeskw.wrap(alg, key, cek);
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
case 'A128GCMKW':
|
|
174
|
+
case 'A192GCMKW':
|
|
175
|
+
case 'A256GCMKW': {
|
|
176
|
+
cek = providedCek || generateCek(enc);
|
|
177
|
+
const { iv } = providedParameters;
|
|
178
|
+
({ encryptedKey, ...parameters } = await aesGcmKwWrap(alg, key, cek, iv));
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
default: {
|
|
182
|
+
throw new JOSENotSupported(unsupportedAlgHeader);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return { cek, encryptedKey, parameters };
|
|
186
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { invalidKeyInput } from './invalid_key_input.js';
|
|
2
|
+
import { encode as b64u } from '../util/base64url.js';
|
|
3
|
+
import { isCryptoKey, isKeyObject } from './is_key_like.js';
|
|
4
|
+
export async function keyToJWK(key) {
|
|
5
|
+
if (isKeyObject(key)) {
|
|
6
|
+
if (key.type === 'secret') {
|
|
7
|
+
key = key.export();
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
return key.export({ format: 'jwk' });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
if (key instanceof Uint8Array) {
|
|
14
|
+
return {
|
|
15
|
+
kty: 'oct',
|
|
16
|
+
k: b64u(key),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
if (!isCryptoKey(key)) {
|
|
20
|
+
throw new TypeError(invalidKeyInput(key, 'CryptoKey', 'KeyObject', 'Uint8Array'));
|
|
21
|
+
}
|
|
22
|
+
if (!key.extractable) {
|
|
23
|
+
throw new TypeError('non-extractable CryptoKey cannot be exported as a JWK');
|
|
24
|
+
}
|
|
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
|
+
}
|
|
30
|
+
return jwk;
|
|
31
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { isJWK } from './type_checks.js';
|
|
2
|
+
import { decode } from '../util/base64url.js';
|
|
3
|
+
import { jwkToKey } from './jwk_to_key.js';
|
|
4
|
+
import { isCryptoKey, isKeyObject } from './is_key_like.js';
|
|
5
|
+
const unusableForAlg = 'given KeyObject instance cannot be used for this algorithm';
|
|
6
|
+
let cache;
|
|
7
|
+
const handleJWK = async (key, jwk, alg, freeze = false) => {
|
|
8
|
+
cache ||= new WeakMap();
|
|
9
|
+
let cached = cache.get(key);
|
|
10
|
+
if (cached?.[alg]) {
|
|
11
|
+
return cached[alg];
|
|
12
|
+
}
|
|
13
|
+
const cryptoKey = await jwkToKey({ ...jwk, alg });
|
|
14
|
+
if (freeze)
|
|
15
|
+
Object.freeze(key);
|
|
16
|
+
if (!cached) {
|
|
17
|
+
cache.set(key, { [alg]: cryptoKey });
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
cached[alg] = cryptoKey;
|
|
21
|
+
}
|
|
22
|
+
return cryptoKey;
|
|
23
|
+
};
|
|
24
|
+
const handleKeyObject = (keyObject, alg) => {
|
|
25
|
+
cache ||= new WeakMap();
|
|
26
|
+
let cached = cache.get(keyObject);
|
|
27
|
+
if (cached?.[alg]) {
|
|
28
|
+
return cached[alg];
|
|
29
|
+
}
|
|
30
|
+
const isPublic = keyObject.type === 'public';
|
|
31
|
+
const extractable = isPublic ? true : false;
|
|
32
|
+
let cryptoKey;
|
|
33
|
+
if (keyObject.asymmetricKeyType === 'x25519') {
|
|
34
|
+
switch (alg) {
|
|
35
|
+
case 'ECDH-ES':
|
|
36
|
+
case 'ECDH-ES+A128KW':
|
|
37
|
+
case 'ECDH-ES+A192KW':
|
|
38
|
+
case 'ECDH-ES+A256KW':
|
|
39
|
+
break;
|
|
40
|
+
default:
|
|
41
|
+
throw new TypeError(unusableForAlg);
|
|
42
|
+
}
|
|
43
|
+
cryptoKey = keyObject.toCryptoKey(keyObject.asymmetricKeyType, extractable, isPublic ? [] : ['deriveBits']);
|
|
44
|
+
}
|
|
45
|
+
if (keyObject.asymmetricKeyType === 'ed25519') {
|
|
46
|
+
if (alg !== 'EdDSA' && alg !== 'Ed25519') {
|
|
47
|
+
throw new TypeError(unusableForAlg);
|
|
48
|
+
}
|
|
49
|
+
cryptoKey = keyObject.toCryptoKey(keyObject.asymmetricKeyType, extractable, [
|
|
50
|
+
isPublic ? 'verify' : 'sign',
|
|
51
|
+
]);
|
|
52
|
+
}
|
|
53
|
+
switch (keyObject.asymmetricKeyType) {
|
|
54
|
+
case 'ml-dsa-44':
|
|
55
|
+
case 'ml-dsa-65':
|
|
56
|
+
case 'ml-dsa-87': {
|
|
57
|
+
if (alg !== keyObject.asymmetricKeyType.toUpperCase()) {
|
|
58
|
+
throw new TypeError(unusableForAlg);
|
|
59
|
+
}
|
|
60
|
+
cryptoKey = keyObject.toCryptoKey(keyObject.asymmetricKeyType, extractable, [
|
|
61
|
+
isPublic ? 'verify' : 'sign',
|
|
62
|
+
]);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (keyObject.asymmetricKeyType === 'rsa') {
|
|
66
|
+
let hash;
|
|
67
|
+
switch (alg) {
|
|
68
|
+
case 'RSA-OAEP':
|
|
69
|
+
hash = 'SHA-1';
|
|
70
|
+
break;
|
|
71
|
+
case 'RS256':
|
|
72
|
+
case 'PS256':
|
|
73
|
+
case 'RSA-OAEP-256':
|
|
74
|
+
hash = 'SHA-256';
|
|
75
|
+
break;
|
|
76
|
+
case 'RS384':
|
|
77
|
+
case 'PS384':
|
|
78
|
+
case 'RSA-OAEP-384':
|
|
79
|
+
hash = 'SHA-384';
|
|
80
|
+
break;
|
|
81
|
+
case 'RS512':
|
|
82
|
+
case 'PS512':
|
|
83
|
+
case 'RSA-OAEP-512':
|
|
84
|
+
hash = 'SHA-512';
|
|
85
|
+
break;
|
|
86
|
+
default:
|
|
87
|
+
throw new TypeError(unusableForAlg);
|
|
88
|
+
}
|
|
89
|
+
if (alg.startsWith('RSA-OAEP')) {
|
|
90
|
+
return keyObject.toCryptoKey({
|
|
91
|
+
name: 'RSA-OAEP',
|
|
92
|
+
hash,
|
|
93
|
+
}, extractable, isPublic ? ['encrypt'] : ['decrypt']);
|
|
94
|
+
}
|
|
95
|
+
cryptoKey = keyObject.toCryptoKey({
|
|
96
|
+
name: alg.startsWith('PS') ? 'RSA-PSS' : 'RSASSA-PKCS1-v1_5',
|
|
97
|
+
hash,
|
|
98
|
+
}, extractable, [isPublic ? 'verify' : 'sign']);
|
|
99
|
+
}
|
|
100
|
+
if (keyObject.asymmetricKeyType === 'ec') {
|
|
101
|
+
const nist = new Map([
|
|
102
|
+
['prime256v1', 'P-256'],
|
|
103
|
+
['secp384r1', 'P-384'],
|
|
104
|
+
['secp521r1', 'P-521'],
|
|
105
|
+
]);
|
|
106
|
+
const namedCurve = nist.get(keyObject.asymmetricKeyDetails?.namedCurve);
|
|
107
|
+
if (!namedCurve) {
|
|
108
|
+
throw new TypeError(unusableForAlg);
|
|
109
|
+
}
|
|
110
|
+
const expectedCurve = { ES256: 'P-256', ES384: 'P-384', ES512: 'P-521' };
|
|
111
|
+
if (expectedCurve[alg] && namedCurve === expectedCurve[alg]) {
|
|
112
|
+
cryptoKey = keyObject.toCryptoKey({
|
|
113
|
+
name: 'ECDSA',
|
|
114
|
+
namedCurve,
|
|
115
|
+
}, extractable, [isPublic ? 'verify' : 'sign']);
|
|
116
|
+
}
|
|
117
|
+
if (alg.startsWith('ECDH-ES')) {
|
|
118
|
+
cryptoKey = keyObject.toCryptoKey({
|
|
119
|
+
name: 'ECDH',
|
|
120
|
+
namedCurve,
|
|
121
|
+
}, extractable, isPublic ? [] : ['deriveBits']);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (!cryptoKey) {
|
|
125
|
+
throw new TypeError(unusableForAlg);
|
|
126
|
+
}
|
|
127
|
+
if (!cached) {
|
|
128
|
+
cache.set(keyObject, { [alg]: cryptoKey });
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
cached[alg] = cryptoKey;
|
|
132
|
+
}
|
|
133
|
+
return cryptoKey;
|
|
134
|
+
};
|
|
135
|
+
export async function normalizeKey(key, alg) {
|
|
136
|
+
if (key instanceof Uint8Array) {
|
|
137
|
+
return key;
|
|
138
|
+
}
|
|
139
|
+
if (isCryptoKey(key)) {
|
|
140
|
+
return key;
|
|
141
|
+
}
|
|
142
|
+
if (isKeyObject(key)) {
|
|
143
|
+
if (key.type === 'secret') {
|
|
144
|
+
return key.export();
|
|
145
|
+
}
|
|
146
|
+
if ('toCryptoKey' in key && typeof key.toCryptoKey === 'function') {
|
|
147
|
+
try {
|
|
148
|
+
return handleKeyObject(key, alg);
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
if (err instanceof TypeError) {
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
let jwk = key.export({ format: 'jwk' });
|
|
157
|
+
return handleJWK(key, jwk, alg);
|
|
158
|
+
}
|
|
159
|
+
if (isJWK(key)) {
|
|
160
|
+
if (key.k) {
|
|
161
|
+
return decode(key.k);
|
|
162
|
+
}
|
|
163
|
+
return handleJWK(key, key, alg, true);
|
|
164
|
+
}
|
|
165
|
+
throw new Error('unreachable');
|
|
166
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { encode as b64u } from '../util/base64url.js';
|
|
2
|
+
import * as aeskw from './aeskw.js';
|
|
3
|
+
import { checkEncCryptoKey } from './crypto_key.js';
|
|
4
|
+
import { concat, encode } from './buffer_utils.js';
|
|
5
|
+
import { JWEInvalid } from '../util/errors.js';
|
|
6
|
+
function getCryptoKey(key, alg) {
|
|
7
|
+
if (key instanceof Uint8Array) {
|
|
8
|
+
return crypto.subtle.importKey('raw', key, 'PBKDF2', false, [
|
|
9
|
+
'deriveBits',
|
|
10
|
+
]);
|
|
11
|
+
}
|
|
12
|
+
checkEncCryptoKey(key, alg, 'deriveBits');
|
|
13
|
+
return key;
|
|
14
|
+
}
|
|
15
|
+
const concatSalt = (alg, p2sInput) => concat(encode(alg), Uint8Array.of(0x00), p2sInput);
|
|
16
|
+
async function deriveKey(p2s, alg, p2c, key) {
|
|
17
|
+
if (!(p2s instanceof Uint8Array) || p2s.length < 8) {
|
|
18
|
+
throw new JWEInvalid('PBES2 Salt Input must be 8 or more octets');
|
|
19
|
+
}
|
|
20
|
+
if (!Number.isSafeInteger(p2c) || Math.sign(p2c) !== 1) {
|
|
21
|
+
throw new JWEInvalid('PBES2 Count Input must be a positive integer');
|
|
22
|
+
}
|
|
23
|
+
const salt = concatSalt(alg, p2s);
|
|
24
|
+
const keylen = parseInt(alg.slice(13, 16), 10);
|
|
25
|
+
const subtleAlg = {
|
|
26
|
+
hash: `SHA-${alg.slice(8, 11)}`,
|
|
27
|
+
iterations: p2c,
|
|
28
|
+
name: 'PBKDF2',
|
|
29
|
+
salt,
|
|
30
|
+
};
|
|
31
|
+
const cryptoKey = await getCryptoKey(key, alg);
|
|
32
|
+
return new Uint8Array(await crypto.subtle.deriveBits(subtleAlg, cryptoKey, keylen));
|
|
33
|
+
}
|
|
34
|
+
export async function wrap(alg, key, cek, p2c = 2048, p2s = crypto.getRandomValues(new Uint8Array(16))) {
|
|
35
|
+
const derived = await deriveKey(p2s, alg, p2c, key);
|
|
36
|
+
const encryptedKey = await aeskw.wrap(alg.slice(-6), derived, cek);
|
|
37
|
+
return { encryptedKey, p2c, p2s: b64u(p2s) };
|
|
38
|
+
}
|
|
39
|
+
export async function unwrap(alg, key, encryptedKey, p2c, p2s) {
|
|
40
|
+
const derived = await deriveKey(p2s, alg, p2c, key);
|
|
41
|
+
return aeskw.unwrap(alg.slice(-6), derived, encryptedKey);
|
|
42
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { checkEncCryptoKey } from './crypto_key.js';
|
|
2
|
+
import { checkKeyLength } from './signing.js';
|
|
3
|
+
import { JOSENotSupported } from '../util/errors.js';
|
|
4
|
+
const subtleAlgorithm = (alg) => {
|
|
5
|
+
switch (alg) {
|
|
6
|
+
case 'RSA-OAEP':
|
|
7
|
+
case 'RSA-OAEP-256':
|
|
8
|
+
case 'RSA-OAEP-384':
|
|
9
|
+
case 'RSA-OAEP-512':
|
|
10
|
+
return 'RSA-OAEP';
|
|
11
|
+
default:
|
|
12
|
+
throw new JOSENotSupported(`alg ${alg} is not supported either by JOSE or your javascript runtime`);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
export async function encrypt(alg, key, cek) {
|
|
16
|
+
checkEncCryptoKey(key, alg, 'encrypt');
|
|
17
|
+
checkKeyLength(alg, key);
|
|
18
|
+
return new Uint8Array(await crypto.subtle.encrypt(subtleAlgorithm(alg), key, cek));
|
|
19
|
+
}
|
|
20
|
+
export async function decrypt(alg, key, encryptedKey) {
|
|
21
|
+
checkEncCryptoKey(key, alg, 'decrypt');
|
|
22
|
+
checkKeyLength(alg, key);
|
|
23
|
+
return new Uint8Array(await crypto.subtle.decrypt(subtleAlgorithm(alg), key, encryptedKey));
|
|
24
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { JOSENotSupported } from '../util/errors.js';
|
|
2
|
+
import { checkSigCryptoKey } from './crypto_key.js';
|
|
3
|
+
import { invalidKeyInput } from './invalid_key_input.js';
|
|
4
|
+
import { normalizeKey } from './normalize_key.js';
|
|
5
|
+
import { isHardwareJWK } from './type_checks.js';
|
|
6
|
+
export function checkKeyLength(alg, key) {
|
|
7
|
+
if (alg.startsWith('RS') || alg.startsWith('PS')) {
|
|
8
|
+
const { modulusLength } = key.algorithm;
|
|
9
|
+
if (typeof modulusLength !== 'number' || modulusLength < 2048) {
|
|
10
|
+
throw new TypeError(`${alg} requires key modulusLength to be 2048 bits or larger`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function subtleAlgorithm(alg, algorithm) {
|
|
15
|
+
const hash = `SHA-${alg.slice(-3)}`;
|
|
16
|
+
switch (alg) {
|
|
17
|
+
case 'HS256':
|
|
18
|
+
case 'HS384':
|
|
19
|
+
case 'HS512':
|
|
20
|
+
return { hash, name: 'HMAC' };
|
|
21
|
+
case 'PS256':
|
|
22
|
+
case 'PS384':
|
|
23
|
+
case 'PS512':
|
|
24
|
+
return { hash, name: 'RSA-PSS', saltLength: parseInt(alg.slice(-3), 10) >> 3 };
|
|
25
|
+
case 'RS256':
|
|
26
|
+
case 'RS384':
|
|
27
|
+
case 'RS512':
|
|
28
|
+
return { hash, name: 'RSASSA-PKCS1-v1_5' };
|
|
29
|
+
case 'ES256':
|
|
30
|
+
case 'ES384':
|
|
31
|
+
case 'ES512':
|
|
32
|
+
return { hash, name: 'ECDSA', namedCurve: algorithm.namedCurve };
|
|
33
|
+
case 'Ed25519':
|
|
34
|
+
case 'EdDSA':
|
|
35
|
+
return { name: 'Ed25519' };
|
|
36
|
+
case 'ML-DSA-44':
|
|
37
|
+
case 'ML-DSA-65':
|
|
38
|
+
case 'ML-DSA-87':
|
|
39
|
+
return { name: alg };
|
|
40
|
+
default:
|
|
41
|
+
throw new JOSENotSupported(`alg ${alg} is not supported either by JOSE or your javascript runtime`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function getSigKey(alg, key, usage) {
|
|
45
|
+
if (key instanceof Uint8Array) {
|
|
46
|
+
if (!alg.startsWith('HS')) {
|
|
47
|
+
throw new TypeError(invalidKeyInput(key, 'CryptoKey', 'KeyObject', 'JSON Web Key'));
|
|
48
|
+
}
|
|
49
|
+
return crypto.subtle.importKey('raw', key, { hash: `SHA-${alg.slice(-3)}`, name: 'HMAC' }, false, [usage]);
|
|
50
|
+
}
|
|
51
|
+
checkSigCryptoKey(key, alg, usage);
|
|
52
|
+
return key;
|
|
53
|
+
}
|
|
54
|
+
export async function sign(alg, key, data) {
|
|
55
|
+
if (isHardwareJWK(key)) {
|
|
56
|
+
return key.sign(alg, data);
|
|
57
|
+
}
|
|
58
|
+
const k = await normalizeKey(key, alg);
|
|
59
|
+
const cryptoKey = await getSigKey(alg, k, 'sign');
|
|
60
|
+
checkKeyLength(alg, cryptoKey);
|
|
61
|
+
const signature = await crypto.subtle.sign(subtleAlgorithm(alg, cryptoKey.algorithm), cryptoKey, data);
|
|
62
|
+
return new Uint8Array(signature);
|
|
63
|
+
}
|
|
64
|
+
export async function verify(alg, key, signature, data) {
|
|
65
|
+
const cryptoKey = await getSigKey(alg, key, 'verify');
|
|
66
|
+
checkKeyLength(alg, cryptoKey);
|
|
67
|
+
const algorithm = subtleAlgorithm(alg, cryptoKey.algorithm);
|
|
68
|
+
try {
|
|
69
|
+
return await crypto.subtle.verify(algorithm, cryptoKey, signature, data);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const isObjectLike = (value) => typeof value === 'object' && value !== null;
|
|
2
|
+
export function isObject(input) {
|
|
3
|
+
if (!isObjectLike(input) || Object.prototype.toString.call(input) !== '[object Object]') {
|
|
4
|
+
return false;
|
|
5
|
+
}
|
|
6
|
+
if (Object.getPrototypeOf(input) === null) {
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
let proto = input;
|
|
10
|
+
while (Object.getPrototypeOf(proto) !== null) {
|
|
11
|
+
proto = Object.getPrototypeOf(proto);
|
|
12
|
+
}
|
|
13
|
+
return Object.getPrototypeOf(input) === proto;
|
|
14
|
+
}
|
|
15
|
+
export function isDisjoint(...headers) {
|
|
16
|
+
const sources = headers.filter(Boolean);
|
|
17
|
+
if (sources.length === 0 || sources.length === 1) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
let acc;
|
|
21
|
+
for (const header of sources) {
|
|
22
|
+
const parameters = Object.keys(header);
|
|
23
|
+
if (!acc || acc.size === 0) {
|
|
24
|
+
acc = new Set(parameters);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
for (const parameter of parameters) {
|
|
28
|
+
if (acc.has(parameter)) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
acc.add(parameter);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
export const isJWK = (key) => isObject(key) && typeof key.kty === 'string';
|
|
37
|
+
export const isHardwareJWK = (key) => isJWK(key) && typeof key.sign === 'function';
|
|
38
|
+
export const isPrivateJWK = (key) => key.kty !== 'oct' &&
|
|
39
|
+
((key.kty === 'AKP' && typeof key.priv === 'string') || typeof key.d === 'string');
|
|
40
|
+
export const isPublicJWK = (key) => key.kty !== 'oct' && key.d === undefined && key.priv === undefined;
|
|
41
|
+
export const isSecretJWK = (key) => key.kty === 'oct' && typeof key.k === 'string';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function validateAlgorithms(option, algorithms) {
|
|
2
|
+
if (algorithms !== undefined &&
|
|
3
|
+
(!Array.isArray(algorithms) || algorithms.some((s) => typeof s !== 'string'))) {
|
|
4
|
+
throw new TypeError(`"${option}" option must be an array of strings`);
|
|
5
|
+
}
|
|
6
|
+
if (!algorithms) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
return new Set(algorithms);
|
|
10
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { JOSENotSupported, JWEInvalid, JWSInvalid } from '../util/errors.js';
|
|
2
|
+
export function validateCrit(Err, recognizedDefault, recognizedOption, protectedHeader, joseHeader) {
|
|
3
|
+
if (joseHeader.crit !== undefined && protectedHeader?.crit === undefined) {
|
|
4
|
+
throw new Err('"crit" (Critical) Header Parameter MUST be integrity protected');
|
|
5
|
+
}
|
|
6
|
+
if (!protectedHeader || protectedHeader.crit === undefined) {
|
|
7
|
+
return new Set();
|
|
8
|
+
}
|
|
9
|
+
if (!Array.isArray(protectedHeader.crit) ||
|
|
10
|
+
protectedHeader.crit.length === 0 ||
|
|
11
|
+
protectedHeader.crit.some((input) => typeof input !== 'string' || input.length === 0)) {
|
|
12
|
+
throw new Err('"crit" (Critical) Header Parameter MUST be an array of non-empty strings when present');
|
|
13
|
+
}
|
|
14
|
+
let recognized;
|
|
15
|
+
if (recognizedOption !== undefined) {
|
|
16
|
+
recognized = new Map([...Object.entries(recognizedOption), ...recognizedDefault.entries()]);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
recognized = recognizedDefault;
|
|
20
|
+
}
|
|
21
|
+
for (const parameter of protectedHeader.crit) {
|
|
22
|
+
if (!recognized.has(parameter)) {
|
|
23
|
+
throw new JOSENotSupported(`Extension Header Parameter "${parameter}" is not recognized`);
|
|
24
|
+
}
|
|
25
|
+
if (joseHeader[parameter] === undefined) {
|
|
26
|
+
throw new Err(`Extension Header Parameter "${parameter}" is missing`);
|
|
27
|
+
}
|
|
28
|
+
if (recognized.get(parameter) && protectedHeader[parameter] === undefined) {
|
|
29
|
+
throw new Err(`Extension Header Parameter "${parameter}" MUST be integrity protected`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return new Set(protectedHeader.crit);
|
|
33
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { encoder, decoder } from '../lib/buffer_utils.js';
|
|
2
|
+
import { encodeBase64, decodeBase64 } from '../lib/base64.js';
|
|
3
|
+
export function decode(input) {
|
|
4
|
+
if (Uint8Array.fromBase64) {
|
|
5
|
+
return Uint8Array.fromBase64(typeof input === 'string' ? input : decoder.decode(input), {
|
|
6
|
+
alphabet: 'base64url',
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
let encoded = input;
|
|
10
|
+
if (encoded instanceof Uint8Array) {
|
|
11
|
+
encoded = decoder.decode(encoded);
|
|
12
|
+
}
|
|
13
|
+
encoded = encoded.replace(/-/g, '+').replace(/_/g, '/');
|
|
14
|
+
try {
|
|
15
|
+
return decodeBase64(encoded);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
throw new TypeError('The input to be decoded is not correctly encoded.');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function encode(input) {
|
|
22
|
+
let unencoded = input;
|
|
23
|
+
if (typeof unencoded === 'string') {
|
|
24
|
+
unencoded = encoder.encode(unencoded);
|
|
25
|
+
}
|
|
26
|
+
if (Uint8Array.prototype.toBase64) {
|
|
27
|
+
return unencoded.toBase64({ alphabet: 'base64url', omitPadding: true });
|
|
28
|
+
}
|
|
29
|
+
return encodeBase64(unencoded).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
|
|
30
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { decode as b64u } from './base64url.js';
|
|
2
|
+
import { decoder } from '../lib/buffer_utils.js';
|
|
3
|
+
import { isObject } from '../lib/type_checks.js';
|
|
4
|
+
import { JWTInvalid } from './errors.js';
|
|
5
|
+
export function decodeJwt(jwt) {
|
|
6
|
+
if (typeof jwt !== 'string')
|
|
7
|
+
throw new JWTInvalid('JWTs must use Compact JWS serialization, JWT must be a string');
|
|
8
|
+
const { 1: payload, length } = jwt.split('.');
|
|
9
|
+
if (length === 5)
|
|
10
|
+
throw new JWTInvalid('Only JWTs using Compact JWS serialization can be decoded');
|
|
11
|
+
if (length !== 3)
|
|
12
|
+
throw new JWTInvalid('Invalid JWT');
|
|
13
|
+
if (!payload)
|
|
14
|
+
throw new JWTInvalid('JWTs must contain a payload');
|
|
15
|
+
let decoded;
|
|
16
|
+
try {
|
|
17
|
+
decoded = b64u(payload);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
throw new JWTInvalid('Failed to base64url decode the payload');
|
|
21
|
+
}
|
|
22
|
+
let result;
|
|
23
|
+
try {
|
|
24
|
+
result = JSON.parse(decoder.decode(decoded));
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
throw new JWTInvalid('Failed to parse the decoded payload as JSON');
|
|
28
|
+
}
|
|
29
|
+
if (!isObject(result))
|
|
30
|
+
throw new JWTInvalid('Invalid JWT Claims Set');
|
|
31
|
+
return result;
|
|
32
|
+
}
|