jose 4.3.7 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/dist/browser/index.js +2 -0
- package/dist/browser/jwks/local.js +97 -0
- package/dist/browser/jwks/remote.js +29 -82
- package/dist/browser/lib/aesgcmkw.js +2 -2
- package/dist/browser/lib/crypto_key.js +7 -7
- package/dist/browser/lib/decrypt_key_management.js +2 -2
- package/dist/browser/lib/encrypt_key_management.js +6 -4
- package/dist/browser/runtime/aeskw.js +1 -1
- package/dist/browser/runtime/asn1.js +21 -20
- package/dist/browser/runtime/decrypt.js +3 -3
- package/dist/browser/runtime/digest.js +1 -1
- package/dist/browser/runtime/ecdhes.js +8 -9
- package/dist/browser/runtime/encrypt.js +3 -3
- package/dist/browser/runtime/generate.js +7 -7
- package/dist/browser/runtime/get_sign_verify_key.js +1 -1
- package/dist/browser/runtime/jwk_to_key.js +4 -4
- package/dist/browser/runtime/pbes2kw.js +4 -4
- package/dist/browser/runtime/sign.js +1 -1
- package/dist/browser/runtime/subtle_dsa.js +7 -6
- package/dist/browser/runtime/verify.js +1 -1
- package/dist/browser/util/decode_jwt.js +32 -0
- package/dist/node/cjs/index.js +5 -1
- package/dist/node/cjs/jwks/local.js +103 -0
- package/dist/node/cjs/jwks/remote.js +28 -81
- package/dist/node/cjs/lib/aesgcmkw.js +2 -2
- package/dist/node/cjs/lib/crypto_key.js +7 -7
- package/dist/node/cjs/lib/decrypt_key_management.js +2 -2
- package/dist/node/cjs/lib/encrypt_key_management.js +6 -4
- package/dist/node/cjs/runtime/aeskw.js +3 -3
- package/dist/node/cjs/runtime/base64url.js +4 -4
- package/dist/node/cjs/runtime/check_cek_length.js +2 -2
- package/dist/node/cjs/runtime/decrypt.js +3 -3
- package/dist/node/cjs/runtime/ecdhes.js +7 -7
- package/dist/node/cjs/runtime/encrypt.js +3 -3
- package/dist/node/cjs/runtime/generate.js +2 -2
- package/dist/node/cjs/runtime/get_named_curve.js +2 -2
- package/dist/node/cjs/runtime/jwk_to_key.js +1 -1
- package/dist/node/cjs/runtime/key_to_jwk.js +1 -1
- package/dist/node/cjs/runtime/node_key.js +2 -2
- package/dist/node/cjs/runtime/pbes2kw.js +6 -6
- package/dist/node/cjs/runtime/verify.js +1 -1
- package/dist/node/cjs/util/decode_jwt.js +36 -0
- package/dist/node/esm/index.js +2 -0
- package/dist/node/esm/jwks/local.js +97 -0
- package/dist/node/esm/jwks/remote.js +29 -82
- package/dist/node/esm/lib/aesgcmkw.js +2 -2
- package/dist/node/esm/lib/crypto_key.js +7 -7
- package/dist/node/esm/lib/decrypt_key_management.js +2 -2
- package/dist/node/esm/lib/encrypt_key_management.js +6 -4
- package/dist/node/esm/runtime/aeskw.js +3 -3
- package/dist/node/esm/runtime/base64url.js +4 -4
- package/dist/node/esm/runtime/check_cek_length.js +2 -2
- package/dist/node/esm/runtime/decrypt.js +3 -3
- package/dist/node/esm/runtime/ecdhes.js +7 -7
- package/dist/node/esm/runtime/encrypt.js +3 -3
- package/dist/node/esm/runtime/generate.js +2 -2
- package/dist/node/esm/runtime/get_named_curve.js +2 -2
- package/dist/node/esm/runtime/jwk_to_key.js +1 -1
- package/dist/node/esm/runtime/key_to_jwk.js +1 -1
- package/dist/node/esm/runtime/node_key.js +2 -2
- package/dist/node/esm/runtime/pbes2kw.js +6 -6
- package/dist/node/esm/runtime/verify.js +1 -1
- package/dist/node/esm/util/decode_jwt.js +32 -0
- package/dist/types/index.d.ts +3 -1
- package/dist/types/jwks/local.d.ts +9 -0
- package/dist/types/types.d.ts +3 -0
- package/dist/types/util/decode_jwt.d.ts +2 -0
- package/package.json +1 -1
|
@@ -13,7 +13,7 @@ const is_key_object_js_1 = require("./is_key_object.js");
|
|
|
13
13
|
const invalid_key_input_js_1 = require("../lib/invalid_key_input.js");
|
|
14
14
|
const is_key_like_js_1 = require("./is_key_like.js");
|
|
15
15
|
const generateKeyPair = (0, util_1.promisify)(crypto_1.generateKeyPair);
|
|
16
|
-
|
|
16
|
+
async function deriveKey(publicKee, privateKee, algorithm, keyLength, apu = new Uint8Array(0), apv = new Uint8Array(0)) {
|
|
17
17
|
let publicKey;
|
|
18
18
|
if ((0, webcrypto_js_1.isCryptoKey)(publicKee)) {
|
|
19
19
|
(0, crypto_key_js_1.checkEncCryptoKey)(publicKee, 'ECDH-ES');
|
|
@@ -39,9 +39,9 @@ const deriveKey = (publicKee, privateKee, algorithm, keyLength, apu = new Uint8A
|
|
|
39
39
|
const value = (0, buffer_utils_js_1.concat)((0, buffer_utils_js_1.lengthAndInput)(buffer_utils_js_1.encoder.encode(algorithm)), (0, buffer_utils_js_1.lengthAndInput)(apu), (0, buffer_utils_js_1.lengthAndInput)(apv), (0, buffer_utils_js_1.uint32be)(keyLength));
|
|
40
40
|
const sharedSecret = (0, crypto_1.diffieHellman)({ privateKey, publicKey });
|
|
41
41
|
return (0, buffer_utils_js_1.concatKdf)(digest_js_1.default, sharedSecret, keyLength, value);
|
|
42
|
-
}
|
|
42
|
+
}
|
|
43
43
|
exports.deriveKey = deriveKey;
|
|
44
|
-
|
|
44
|
+
async function generateEpk(kee) {
|
|
45
45
|
let key;
|
|
46
46
|
if ((0, webcrypto_js_1.isCryptoKey)(kee)) {
|
|
47
47
|
key = crypto_1.KeyObject.from(kee);
|
|
@@ -54,18 +54,18 @@ const generateEpk = async (kee) => {
|
|
|
54
54
|
}
|
|
55
55
|
switch (key.asymmetricKeyType) {
|
|
56
56
|
case 'x25519':
|
|
57
|
-
return
|
|
57
|
+
return generateKeyPair('x25519');
|
|
58
58
|
case 'x448': {
|
|
59
|
-
return
|
|
59
|
+
return generateKeyPair('x448');
|
|
60
60
|
}
|
|
61
61
|
case 'ec': {
|
|
62
62
|
const namedCurve = (0, get_named_curve_js_1.default)(key);
|
|
63
|
-
return
|
|
63
|
+
return generateKeyPair('ec', { namedCurve });
|
|
64
64
|
}
|
|
65
65
|
default:
|
|
66
66
|
throw new errors_js_1.JOSENotSupported('Invalid or unsupported EPK');
|
|
67
67
|
}
|
|
68
|
-
}
|
|
68
|
+
}
|
|
69
69
|
exports.generateEpk = generateEpk;
|
|
70
70
|
const ecdhAllowed = (key) => ['P-256', 'P-384', 'P-521', 'X25519', 'X448'].includes((0, get_named_curve_js_1.default)(key));
|
|
71
71
|
exports.ecdhAllowed = ecdhAllowed;
|
|
@@ -13,7 +13,7 @@ const errors_js_1 = require("../util/errors.js");
|
|
|
13
13
|
const ciphers_js_1 = require("./ciphers.js");
|
|
14
14
|
const is_key_like_js_1 = require("./is_key_like.js");
|
|
15
15
|
function cbcEncrypt(enc, plaintext, cek, iv, aad) {
|
|
16
|
-
const keySize = parseInt(enc.
|
|
16
|
+
const keySize = parseInt(enc.slice(1, 4), 10);
|
|
17
17
|
if ((0, is_key_object_js_1.default)(cek)) {
|
|
18
18
|
cek = cek.export();
|
|
19
19
|
}
|
|
@@ -25,12 +25,12 @@ function cbcEncrypt(enc, plaintext, cek, iv, aad) {
|
|
|
25
25
|
}
|
|
26
26
|
const cipher = (0, crypto_1.createCipheriv)(algorithm, encKey, iv);
|
|
27
27
|
const ciphertext = (0, buffer_utils_js_1.concat)(cipher.update(plaintext), cipher.final());
|
|
28
|
-
const macSize = parseInt(enc.
|
|
28
|
+
const macSize = parseInt(enc.slice(-3), 10);
|
|
29
29
|
const tag = (0, cbc_tag_js_1.default)(aad, iv, ciphertext, macSize, macKey, keySize);
|
|
30
30
|
return { ciphertext, tag };
|
|
31
31
|
}
|
|
32
32
|
function gcmEncrypt(enc, plaintext, cek, iv, aad) {
|
|
33
|
-
const keySize = parseInt(enc.
|
|
33
|
+
const keySize = parseInt(enc.slice(1, 4), 10);
|
|
34
34
|
const algorithm = `aes-${keySize}-gcm`;
|
|
35
35
|
if (!(0, ciphers_js_1.default)(algorithm)) {
|
|
36
36
|
throw new errors_js_1.JOSENotSupported(`alg ${enc} is not supported by your javascript runtime`);
|
|
@@ -16,7 +16,7 @@ async function generateSecret(alg, options) {
|
|
|
16
16
|
case 'A128CBC-HS256':
|
|
17
17
|
case 'A192CBC-HS384':
|
|
18
18
|
case 'A256CBC-HS512':
|
|
19
|
-
length = parseInt(alg.
|
|
19
|
+
length = parseInt(alg.slice(-3), 10);
|
|
20
20
|
break;
|
|
21
21
|
case 'A128KW':
|
|
22
22
|
case 'A192KW':
|
|
@@ -27,7 +27,7 @@ async function generateSecret(alg, options) {
|
|
|
27
27
|
case 'A128GCM':
|
|
28
28
|
case 'A192GCM':
|
|
29
29
|
case 'A256GCM':
|
|
30
|
-
length = parseInt(alg.
|
|
30
|
+
length = parseInt(alg.slice(1, 4), 10);
|
|
31
31
|
break;
|
|
32
32
|
default:
|
|
33
33
|
throw new errors_js_1.JOSENotSupported('Invalid or unsupported JWK "alg" (Algorithm) Parameter value');
|
|
@@ -45,10 +45,10 @@ const getNamedCurve = (kee, raw) => {
|
|
|
45
45
|
switch (key.asymmetricKeyType) {
|
|
46
46
|
case 'ed25519':
|
|
47
47
|
case 'ed448':
|
|
48
|
-
return `Ed${key.asymmetricKeyType.
|
|
48
|
+
return `Ed${key.asymmetricKeyType.slice(2)}`;
|
|
49
49
|
case 'x25519':
|
|
50
50
|
case 'x448':
|
|
51
|
-
return `X${key.asymmetricKeyType.
|
|
51
|
+
return `X${key.asymmetricKeyType.slice(1)}`;
|
|
52
52
|
case 'ec': {
|
|
53
53
|
if (exports.weakMap.has(key)) {
|
|
54
54
|
return exports.weakMap.get(key);
|
|
@@ -8,7 +8,7 @@ const get_named_curve_js_1 = require("./get_named_curve.js");
|
|
|
8
8
|
const check_modulus_length_js_1 = require("./check_modulus_length.js");
|
|
9
9
|
const asn1_sequence_encoder_js_1 = require("./asn1_sequence_encoder.js");
|
|
10
10
|
const [major, minor] = process.version
|
|
11
|
-
.
|
|
11
|
+
.slice(1)
|
|
12
12
|
.split('.')
|
|
13
13
|
.map((str) => parseInt(str, 10));
|
|
14
14
|
const jwkImportSupported = major >= 16 || (major === 15 && minor >= 12);
|
|
@@ -10,7 +10,7 @@ const is_key_object_js_1 = require("./is_key_object.js");
|
|
|
10
10
|
const invalid_key_input_js_1 = require("../lib/invalid_key_input.js");
|
|
11
11
|
const is_key_like_js_1 = require("./is_key_like.js");
|
|
12
12
|
const [major, minor] = process.version
|
|
13
|
-
.
|
|
13
|
+
.slice(1)
|
|
14
14
|
.split('.')
|
|
15
15
|
.map((str) => parseInt(str, 10));
|
|
16
16
|
const jwkExportSupported = major >= 16 || (major === 15 && minor >= 9);
|
|
@@ -5,7 +5,7 @@ const get_named_curve_js_1 = require("./get_named_curve.js");
|
|
|
5
5
|
const errors_js_1 = require("../util/errors.js");
|
|
6
6
|
const check_modulus_length_js_1 = require("./check_modulus_length.js");
|
|
7
7
|
const [major, minor] = process.version
|
|
8
|
-
.
|
|
8
|
+
.slice(1)
|
|
9
9
|
.split('.')
|
|
10
10
|
.map((str) => parseInt(str, 10));
|
|
11
11
|
const electron = 'electron' in process.versions;
|
|
@@ -40,7 +40,7 @@ function keyForCrypto(alg, key) {
|
|
|
40
40
|
case rsaPssParams && 'PS512':
|
|
41
41
|
if (key.asymmetricKeyType === 'rsa-pss') {
|
|
42
42
|
const { hashAlgorithm, mgf1HashAlgorithm, saltLength } = key.asymmetricKeyDetails;
|
|
43
|
-
const length = parseInt(alg.
|
|
43
|
+
const length = parseInt(alg.slice(-3), 10);
|
|
44
44
|
if (hashAlgorithm !== undefined &&
|
|
45
45
|
(hashAlgorithm !== `sha${length}` || mgf1HashAlgorithm !== hashAlgorithm)) {
|
|
46
46
|
throw new TypeError(`Invalid key for this operation, its RSA-PSS parameters do not meet the requirements of "alg" ${alg}`);
|
|
@@ -30,19 +30,19 @@ function getPassword(key, alg) {
|
|
|
30
30
|
const encrypt = async (alg, key, cek, p2c = Math.floor(Math.random() * 2049) + 2048, p2s = (0, random_js_1.default)(new Uint8Array(16))) => {
|
|
31
31
|
(0, check_p2s_js_1.default)(p2s);
|
|
32
32
|
const salt = (0, buffer_utils_js_1.p2s)(alg, p2s);
|
|
33
|
-
const keylen = parseInt(alg.
|
|
33
|
+
const keylen = parseInt(alg.slice(13, 16), 10) >> 3;
|
|
34
34
|
const password = getPassword(key, alg);
|
|
35
|
-
const derivedKey = await pbkdf2(password, salt, p2c, keylen, `sha${alg.
|
|
36
|
-
const encryptedKey = await (0, aeskw_js_1.wrap)(alg.
|
|
35
|
+
const derivedKey = await pbkdf2(password, salt, p2c, keylen, `sha${alg.slice(8, 11)}`);
|
|
36
|
+
const encryptedKey = await (0, aeskw_js_1.wrap)(alg.slice(-6), derivedKey, cek);
|
|
37
37
|
return { encryptedKey, p2c, p2s: (0, base64url_js_1.encode)(p2s) };
|
|
38
38
|
};
|
|
39
39
|
exports.encrypt = encrypt;
|
|
40
40
|
const decrypt = async (alg, key, encryptedKey, p2c, p2s) => {
|
|
41
41
|
(0, check_p2s_js_1.default)(p2s);
|
|
42
42
|
const salt = (0, buffer_utils_js_1.p2s)(alg, p2s);
|
|
43
|
-
const keylen = parseInt(alg.
|
|
43
|
+
const keylen = parseInt(alg.slice(13, 16), 10) >> 3;
|
|
44
44
|
const password = getPassword(key, alg);
|
|
45
|
-
const derivedKey = await pbkdf2(password, salt, p2c, keylen, `sha${alg.
|
|
46
|
-
return (0, aeskw_js_1.unwrap)(alg.
|
|
45
|
+
const derivedKey = await pbkdf2(password, salt, p2c, keylen, `sha${alg.slice(8, 11)}`);
|
|
46
|
+
return (0, aeskw_js_1.unwrap)(alg.slice(-6), derivedKey, encryptedKey);
|
|
47
47
|
};
|
|
48
48
|
exports.decrypt = decrypt;
|
|
@@ -7,7 +7,7 @@ const node_key_js_1 = require("./node_key.js");
|
|
|
7
7
|
const sign_js_1 = require("./sign.js");
|
|
8
8
|
const get_sign_verify_key_js_1 = require("./get_sign_verify_key.js");
|
|
9
9
|
const [major, minor] = process.version
|
|
10
|
-
.
|
|
10
|
+
.slice(1)
|
|
11
11
|
.split('.')
|
|
12
12
|
.map((str) => parseInt(str, 10));
|
|
13
13
|
const oneShotCallbackSupported = major >= 16 || (major === 15 && minor >= 13);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.decodeJwt = void 0;
|
|
4
|
+
const base64url_js_1 = require("./base64url.js");
|
|
5
|
+
const buffer_utils_js_1 = require("../lib/buffer_utils.js");
|
|
6
|
+
const is_object_js_1 = require("../lib/is_object.js");
|
|
7
|
+
const errors_js_1 = require("./errors.js");
|
|
8
|
+
function decodeJwt(jwt) {
|
|
9
|
+
if (typeof jwt !== 'string')
|
|
10
|
+
throw new errors_js_1.JWTInvalid('JWTs must use Compact JWS serialization, JWT must be a string');
|
|
11
|
+
const { 1: payload, length } = jwt.split('.');
|
|
12
|
+
if (length === 5)
|
|
13
|
+
throw new errors_js_1.JWTInvalid('Only JWTs using Compact JWS serialization can be decoded');
|
|
14
|
+
if (length !== 3)
|
|
15
|
+
throw new errors_js_1.JWTInvalid('Invalid JWT');
|
|
16
|
+
if (!payload)
|
|
17
|
+
throw new errors_js_1.JWTInvalid('JWTs must contain a payload');
|
|
18
|
+
let decoded;
|
|
19
|
+
try {
|
|
20
|
+
decoded = (0, base64url_js_1.decode)(payload);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
throw new errors_js_1.JWTInvalid('Failed to parse the base64url encoded payload');
|
|
24
|
+
}
|
|
25
|
+
let result;
|
|
26
|
+
try {
|
|
27
|
+
result = JSON.parse(buffer_utils_js_1.decoder.decode(decoded));
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
throw new errors_js_1.JWTInvalid('Failed to parse the decoded payload as JSON');
|
|
31
|
+
}
|
|
32
|
+
if (!(0, is_object_js_1.default)(result))
|
|
33
|
+
throw new errors_js_1.JWTInvalid('Invalid JWT Claims Set');
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
exports.decodeJwt = decodeJwt;
|
package/dist/node/esm/index.js
CHANGED
|
@@ -16,11 +16,13 @@ export { SignJWT } from './jwt/sign.js';
|
|
|
16
16
|
export { EncryptJWT } from './jwt/encrypt.js';
|
|
17
17
|
export { calculateJwkThumbprint } from './jwk/thumbprint.js';
|
|
18
18
|
export { EmbeddedJWK } from './jwk/embedded.js';
|
|
19
|
+
export { createLocalJWKSet } from './jwks/local.js';
|
|
19
20
|
export { createRemoteJWKSet } from './jwks/remote.js';
|
|
20
21
|
export { UnsecuredJWT } from './jwt/unsecured.js';
|
|
21
22
|
export { exportPKCS8, exportSPKI, exportJWK } from './key/export.js';
|
|
22
23
|
export { importSPKI, importPKCS8, importX509, importJWK } from './key/import.js';
|
|
23
24
|
export { decodeProtectedHeader } from './util/decode_protected_header.js';
|
|
25
|
+
export { decodeJwt } from './util/decode_jwt.js';
|
|
24
26
|
export * as errors from './util/errors.js';
|
|
25
27
|
export { generateKeyPair } from './key/generate_key_pair.js';
|
|
26
28
|
export { generateSecret } from './key/generate_secret.js';
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { importJWK } from '../key/import.js';
|
|
2
|
+
import { JWKSInvalid, JOSENotSupported, JWKSNoMatchingKey, JWKSMultipleMatchingKeys, } from '../util/errors.js';
|
|
3
|
+
import isObject from '../lib/is_object.js';
|
|
4
|
+
function getKtyFromAlg(alg) {
|
|
5
|
+
switch (typeof alg === 'string' && alg.slice(0, 2)) {
|
|
6
|
+
case 'RS':
|
|
7
|
+
case 'PS':
|
|
8
|
+
return 'RSA';
|
|
9
|
+
case 'ES':
|
|
10
|
+
return 'EC';
|
|
11
|
+
case 'Ed':
|
|
12
|
+
return 'OKP';
|
|
13
|
+
default:
|
|
14
|
+
throw new JOSENotSupported('Unsupported "alg" value for a JSON Web Key Set');
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function isJWKSLike(jwks) {
|
|
18
|
+
return (jwks &&
|
|
19
|
+
typeof jwks === 'object' &&
|
|
20
|
+
Array.isArray(jwks.keys) &&
|
|
21
|
+
jwks.keys.every(isJWKLike));
|
|
22
|
+
}
|
|
23
|
+
function isJWKLike(key) {
|
|
24
|
+
return isObject(key);
|
|
25
|
+
}
|
|
26
|
+
function clone(obj) {
|
|
27
|
+
if (typeof structuredClone === 'function') {
|
|
28
|
+
return structuredClone(obj);
|
|
29
|
+
}
|
|
30
|
+
return JSON.parse(JSON.stringify(obj));
|
|
31
|
+
}
|
|
32
|
+
export class LocalJWKSet {
|
|
33
|
+
constructor(jwks) {
|
|
34
|
+
this._cached = new WeakMap();
|
|
35
|
+
if (!isJWKSLike(jwks)) {
|
|
36
|
+
throw new JWKSInvalid('JSON Web Key Set malformed');
|
|
37
|
+
}
|
|
38
|
+
this._jwks = clone(jwks);
|
|
39
|
+
}
|
|
40
|
+
async getKey(protectedHeader, token) {
|
|
41
|
+
const { alg, kid } = { ...protectedHeader, ...token.header };
|
|
42
|
+
const candidates = this._jwks.keys.filter((jwk) => {
|
|
43
|
+
let candidate = jwk.kty === getKtyFromAlg(alg);
|
|
44
|
+
if (candidate && typeof kid === 'string') {
|
|
45
|
+
candidate = kid === jwk.kid;
|
|
46
|
+
}
|
|
47
|
+
if (candidate && typeof jwk.alg === 'string') {
|
|
48
|
+
candidate = alg === jwk.alg;
|
|
49
|
+
}
|
|
50
|
+
if (candidate && typeof jwk.use === 'string') {
|
|
51
|
+
candidate = jwk.use === 'sig';
|
|
52
|
+
}
|
|
53
|
+
if (candidate && Array.isArray(jwk.key_ops)) {
|
|
54
|
+
candidate = jwk.key_ops.includes('verify');
|
|
55
|
+
}
|
|
56
|
+
if (candidate && alg === 'EdDSA') {
|
|
57
|
+
candidate = jwk.crv === 'Ed25519' || jwk.crv === 'Ed448';
|
|
58
|
+
}
|
|
59
|
+
if (candidate) {
|
|
60
|
+
switch (alg) {
|
|
61
|
+
case 'ES256':
|
|
62
|
+
candidate = jwk.crv === 'P-256';
|
|
63
|
+
break;
|
|
64
|
+
case 'ES256K':
|
|
65
|
+
candidate = jwk.crv === 'secp256k1';
|
|
66
|
+
break;
|
|
67
|
+
case 'ES384':
|
|
68
|
+
candidate = jwk.crv === 'P-384';
|
|
69
|
+
break;
|
|
70
|
+
case 'ES512':
|
|
71
|
+
candidate = jwk.crv === 'P-521';
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return candidate;
|
|
76
|
+
});
|
|
77
|
+
const { 0: jwk, length } = candidates;
|
|
78
|
+
if (length === 0) {
|
|
79
|
+
throw new JWKSNoMatchingKey();
|
|
80
|
+
}
|
|
81
|
+
else if (length !== 1) {
|
|
82
|
+
throw new JWKSMultipleMatchingKeys();
|
|
83
|
+
}
|
|
84
|
+
const cached = this._cached.get(jwk) || this._cached.set(jwk, {}).get(jwk);
|
|
85
|
+
if (cached[alg] === undefined) {
|
|
86
|
+
const keyObject = await importJWK({ ...jwk, ext: true }, alg);
|
|
87
|
+
if (keyObject instanceof Uint8Array || keyObject.type !== 'public') {
|
|
88
|
+
throw new JWKSInvalid('JSON Web Key Set members must be public keys');
|
|
89
|
+
}
|
|
90
|
+
cached[alg] = keyObject;
|
|
91
|
+
}
|
|
92
|
+
return cached[alg];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export function createLocalJWKSet(jwks) {
|
|
96
|
+
return LocalJWKSet.prototype.getKey.bind(new LocalJWKSet(jwks));
|
|
97
|
+
}
|
|
@@ -1,26 +1,11 @@
|
|
|
1
1
|
import fetchJwks from '../runtime/fetch_jwks.js';
|
|
2
|
-
import {
|
|
3
|
-
import { JWKSInvalid,
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
switch (typeof alg === 'string' && alg.substr(0, 2)) {
|
|
7
|
-
case 'RS':
|
|
8
|
-
case 'PS':
|
|
9
|
-
return 'RSA';
|
|
10
|
-
case 'ES':
|
|
11
|
-
return 'EC';
|
|
12
|
-
case 'Ed':
|
|
13
|
-
return 'OKP';
|
|
14
|
-
default:
|
|
15
|
-
throw new JOSENotSupported('Unsupported "alg" value for a JSON Web Key Set');
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
function isJWKLike(key) {
|
|
19
|
-
return isObject(key);
|
|
20
|
-
}
|
|
21
|
-
class RemoteJWKSet {
|
|
2
|
+
import { isCloudflareWorkers } from '../runtime/env.js';
|
|
3
|
+
import { JWKSInvalid, JWKSNoMatchingKey } from '../util/errors.js';
|
|
4
|
+
import { isJWKSLike, LocalJWKSet } from './local.js';
|
|
5
|
+
class RemoteJWKSet extends LocalJWKSet {
|
|
22
6
|
constructor(url, options) {
|
|
23
|
-
|
|
7
|
+
super({ keys: [] });
|
|
8
|
+
this._jwks = undefined;
|
|
24
9
|
if (!(url instanceof URL)) {
|
|
25
10
|
throw new TypeError('url must be an instance of URL');
|
|
26
11
|
}
|
|
@@ -38,78 +23,40 @@ class RemoteJWKSet {
|
|
|
38
23
|
return Date.now() < this._cooldownStarted + this._cooldownDuration;
|
|
39
24
|
}
|
|
40
25
|
async getKey(protectedHeader, token) {
|
|
41
|
-
const joseHeader = {
|
|
42
|
-
...protectedHeader,
|
|
43
|
-
...token.header,
|
|
44
|
-
};
|
|
45
26
|
if (!this._jwks) {
|
|
46
27
|
await this.reload();
|
|
47
28
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (candidate && typeof joseHeader.kid === 'string') {
|
|
51
|
-
candidate = joseHeader.kid === jwk.kid;
|
|
52
|
-
}
|
|
53
|
-
if (candidate && typeof jwk.alg === 'string') {
|
|
54
|
-
candidate = joseHeader.alg === jwk.alg;
|
|
55
|
-
}
|
|
56
|
-
if (candidate && typeof jwk.use === 'string') {
|
|
57
|
-
candidate = jwk.use === 'sig';
|
|
58
|
-
}
|
|
59
|
-
if (candidate && Array.isArray(jwk.key_ops)) {
|
|
60
|
-
candidate = jwk.key_ops.includes('verify');
|
|
61
|
-
}
|
|
62
|
-
if (candidate && joseHeader.alg === 'EdDSA') {
|
|
63
|
-
candidate = jwk.crv === 'Ed25519' || jwk.crv === 'Ed448';
|
|
64
|
-
}
|
|
65
|
-
if (candidate) {
|
|
66
|
-
switch (joseHeader.alg) {
|
|
67
|
-
case 'ES256':
|
|
68
|
-
candidate = jwk.crv === 'P-256';
|
|
69
|
-
break;
|
|
70
|
-
case 'ES256K':
|
|
71
|
-
candidate = jwk.crv === 'secp256k1';
|
|
72
|
-
break;
|
|
73
|
-
case 'ES384':
|
|
74
|
-
candidate = jwk.crv === 'P-384';
|
|
75
|
-
break;
|
|
76
|
-
case 'ES512':
|
|
77
|
-
candidate = jwk.crv === 'P-521';
|
|
78
|
-
break;
|
|
79
|
-
default:
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return candidate;
|
|
83
|
-
});
|
|
84
|
-
const { 0: jwk, length } = candidates;
|
|
85
|
-
if (length === 0) {
|
|
86
|
-
if (this.coolingDown() === false) {
|
|
87
|
-
await this.reload();
|
|
88
|
-
return this.getKey(protectedHeader, token);
|
|
89
|
-
}
|
|
90
|
-
throw new JWKSNoMatchingKey();
|
|
91
|
-
}
|
|
92
|
-
else if (length !== 1) {
|
|
93
|
-
throw new JWKSMultipleMatchingKeys();
|
|
29
|
+
try {
|
|
30
|
+
return await super.getKey(protectedHeader, token);
|
|
94
31
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
32
|
+
catch (err) {
|
|
33
|
+
if (err instanceof JWKSNoMatchingKey) {
|
|
34
|
+
if (this.coolingDown() === false) {
|
|
35
|
+
await this.reload();
|
|
36
|
+
return super.getKey(protectedHeader, token);
|
|
37
|
+
}
|
|
100
38
|
}
|
|
101
|
-
|
|
39
|
+
throw err;
|
|
102
40
|
}
|
|
103
|
-
return cached[joseHeader.alg];
|
|
104
41
|
}
|
|
105
42
|
async reload() {
|
|
43
|
+
if (this._pendingFetch && isCloudflareWorkers()) {
|
|
44
|
+
return new Promise((resolve) => {
|
|
45
|
+
const isDone = () => {
|
|
46
|
+
if (this._pendingFetch === undefined) {
|
|
47
|
+
resolve();
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
setTimeout(isDone, 5);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
isDone();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
106
56
|
if (!this._pendingFetch) {
|
|
107
57
|
this._pendingFetch = fetchJwks(this._url, this._timeoutDuration, this._options)
|
|
108
58
|
.then((json) => {
|
|
109
|
-
if (
|
|
110
|
-
!json ||
|
|
111
|
-
!Array.isArray(json.keys) ||
|
|
112
|
-
!json.keys.every(isJWKLike)) {
|
|
59
|
+
if (!isJWKSLike(json)) {
|
|
113
60
|
throw new JWKSInvalid('JSON Web Key Set malformed');
|
|
114
61
|
}
|
|
115
62
|
this._jwks = { keys: json.keys };
|
|
@@ -3,12 +3,12 @@ import decrypt from '../runtime/decrypt.js';
|
|
|
3
3
|
import generateIv from './iv.js';
|
|
4
4
|
import { encode as base64url } from '../runtime/base64url.js';
|
|
5
5
|
export async function wrap(alg, key, cek, iv) {
|
|
6
|
-
const jweAlgorithm = alg.
|
|
6
|
+
const jweAlgorithm = alg.slice(0, 7);
|
|
7
7
|
iv || (iv = generateIv(jweAlgorithm));
|
|
8
8
|
const { ciphertext: encryptedKey, tag } = await encrypt(jweAlgorithm, cek, key, iv, new Uint8Array(0));
|
|
9
9
|
return { encryptedKey, iv: base64url(iv), tag: base64url(tag) };
|
|
10
10
|
}
|
|
11
11
|
export async function unwrap(alg, key, encryptedKey, iv, tag) {
|
|
12
|
-
const jweAlgorithm = alg.
|
|
12
|
+
const jweAlgorithm = alg.slice(0, 7);
|
|
13
13
|
return decrypt(jweAlgorithm, key, encryptedKey, iv, tag, new Uint8Array(0));
|
|
14
14
|
}
|
|
@@ -6,7 +6,7 @@ function isAlgorithm(algorithm, name) {
|
|
|
6
6
|
return algorithm.name === name;
|
|
7
7
|
}
|
|
8
8
|
function getHashLength(hash) {
|
|
9
|
-
return parseInt(hash.name.
|
|
9
|
+
return parseInt(hash.name.slice(4), 10);
|
|
10
10
|
}
|
|
11
11
|
function getNamedCurve(alg) {
|
|
12
12
|
switch (alg) {
|
|
@@ -43,7 +43,7 @@ export function checkSigCryptoKey(key, alg, ...usages) {
|
|
|
43
43
|
case 'HS512': {
|
|
44
44
|
if (!isAlgorithm(key.algorithm, 'HMAC'))
|
|
45
45
|
throw unusable('HMAC');
|
|
46
|
-
const expected = parseInt(alg.
|
|
46
|
+
const expected = parseInt(alg.slice(2), 10);
|
|
47
47
|
const actual = getHashLength(key.algorithm.hash);
|
|
48
48
|
if (actual !== expected)
|
|
49
49
|
throw unusable(`SHA-${expected}`, 'algorithm.hash');
|
|
@@ -54,7 +54,7 @@ export function checkSigCryptoKey(key, alg, ...usages) {
|
|
|
54
54
|
case 'RS512': {
|
|
55
55
|
if (!isAlgorithm(key.algorithm, 'RSASSA-PKCS1-v1_5'))
|
|
56
56
|
throw unusable('RSASSA-PKCS1-v1_5');
|
|
57
|
-
const expected = parseInt(alg.
|
|
57
|
+
const expected = parseInt(alg.slice(2), 10);
|
|
58
58
|
const actual = getHashLength(key.algorithm.hash);
|
|
59
59
|
if (actual !== expected)
|
|
60
60
|
throw unusable(`SHA-${expected}`, 'algorithm.hash');
|
|
@@ -65,7 +65,7 @@ export function checkSigCryptoKey(key, alg, ...usages) {
|
|
|
65
65
|
case 'PS512': {
|
|
66
66
|
if (!isAlgorithm(key.algorithm, 'RSA-PSS'))
|
|
67
67
|
throw unusable('RSA-PSS');
|
|
68
|
-
const expected = parseInt(alg.
|
|
68
|
+
const expected = parseInt(alg.slice(2), 10);
|
|
69
69
|
const actual = getHashLength(key.algorithm.hash);
|
|
70
70
|
if (actual !== expected)
|
|
71
71
|
throw unusable(`SHA-${expected}`, 'algorithm.hash');
|
|
@@ -104,7 +104,7 @@ export function checkEncCryptoKey(key, alg, ...usages) {
|
|
|
104
104
|
case 'A256GCM': {
|
|
105
105
|
if (!isAlgorithm(key.algorithm, 'AES-GCM'))
|
|
106
106
|
throw unusable('AES-GCM');
|
|
107
|
-
const expected = parseInt(alg.
|
|
107
|
+
const expected = parseInt(alg.slice(1, 4), 10);
|
|
108
108
|
const actual = key.algorithm.length;
|
|
109
109
|
if (actual !== expected)
|
|
110
110
|
throw unusable(expected, 'algorithm.length');
|
|
@@ -115,7 +115,7 @@ export function checkEncCryptoKey(key, alg, ...usages) {
|
|
|
115
115
|
case 'A256KW': {
|
|
116
116
|
if (!isAlgorithm(key.algorithm, 'AES-KW'))
|
|
117
117
|
throw unusable('AES-KW');
|
|
118
|
-
const expected = parseInt(alg.
|
|
118
|
+
const expected = parseInt(alg.slice(1, 4), 10);
|
|
119
119
|
const actual = key.algorithm.length;
|
|
120
120
|
if (actual !== expected)
|
|
121
121
|
throw unusable(expected, 'algorithm.length');
|
|
@@ -137,7 +137,7 @@ export function checkEncCryptoKey(key, alg, ...usages) {
|
|
|
137
137
|
case 'RSA-OAEP-512': {
|
|
138
138
|
if (!isAlgorithm(key.algorithm, 'RSA-OAEP'))
|
|
139
139
|
throw unusable('RSA-OAEP');
|
|
140
|
-
const expected = parseInt(alg.
|
|
140
|
+
const expected = parseInt(alg.slice(9), 10) || 1;
|
|
141
141
|
const actual = getHashLength(key.algorithm.hash);
|
|
142
142
|
if (actual !== expected)
|
|
143
143
|
throw unusable(`SHA-${expected}`, 'algorithm.hash');
|
|
@@ -40,12 +40,12 @@ async function decryptKeyManagement(alg, key, encryptedKey, joseHeader) {
|
|
|
40
40
|
throw new JWEInvalid(`JOSE Header "apv" (Agreement PartyVInfo) invalid`);
|
|
41
41
|
partyVInfo = base64url(joseHeader.apv);
|
|
42
42
|
}
|
|
43
|
-
const sharedSecret = await ECDH.deriveKey(epk, key, alg === 'ECDH-ES' ? joseHeader.enc : alg, alg === 'ECDH-ES' ? cekLength(joseHeader.enc) : parseInt(alg.
|
|
43
|
+
const sharedSecret = await ECDH.deriveKey(epk, key, alg === 'ECDH-ES' ? joseHeader.enc : alg, alg === 'ECDH-ES' ? cekLength(joseHeader.enc) : parseInt(alg.slice(-5, -2), 10), partyUInfo, partyVInfo);
|
|
44
44
|
if (alg === 'ECDH-ES')
|
|
45
45
|
return sharedSecret;
|
|
46
46
|
if (encryptedKey === undefined)
|
|
47
47
|
throw new JWEInvalid('JWE Encrypted Key missing');
|
|
48
|
-
return aesKw(alg.
|
|
48
|
+
return aesKw(alg.slice(-6), sharedSecret, encryptedKey);
|
|
49
49
|
}
|
|
50
50
|
case 'RSA1_5':
|
|
51
51
|
case 'RSA-OAEP':
|
|
@@ -27,10 +27,12 @@ async function encryptKeyManagement(alg, enc, key, providedCek, providedParamete
|
|
|
27
27
|
}
|
|
28
28
|
const { apu, apv } = providedParameters;
|
|
29
29
|
let { epk: ephemeralKey } = providedParameters;
|
|
30
|
-
ephemeralKey || (ephemeralKey = await ECDH.generateEpk(key));
|
|
30
|
+
ephemeralKey || (ephemeralKey = (await ECDH.generateEpk(key)).privateKey);
|
|
31
31
|
const { x, y, crv, kty } = await exportJWK(ephemeralKey);
|
|
32
|
-
const sharedSecret = await ECDH.deriveKey(key, ephemeralKey, alg === 'ECDH-ES' ? enc : alg, alg === 'ECDH-ES' ? cekLength(enc) : parseInt(alg.
|
|
33
|
-
parameters = { epk: { x,
|
|
32
|
+
const sharedSecret = await ECDH.deriveKey(key, ephemeralKey, alg === 'ECDH-ES' ? enc : alg, alg === 'ECDH-ES' ? cekLength(enc) : parseInt(alg.slice(-5, -2), 10), apu, apv);
|
|
33
|
+
parameters = { epk: { x, crv, kty } };
|
|
34
|
+
if (kty === 'EC')
|
|
35
|
+
parameters.epk.y = y;
|
|
34
36
|
if (apu)
|
|
35
37
|
parameters.apu = base64url(apu);
|
|
36
38
|
if (apv)
|
|
@@ -40,7 +42,7 @@ async function encryptKeyManagement(alg, enc, key, providedCek, providedParamete
|
|
|
40
42
|
break;
|
|
41
43
|
}
|
|
42
44
|
cek = providedCek || generateCek(enc);
|
|
43
|
-
const kwAlg = alg.
|
|
45
|
+
const kwAlg = alg.slice(-6);
|
|
44
46
|
encryptedKey = await aesKw(kwAlg, sharedSecret, cek);
|
|
45
47
|
break;
|
|
46
48
|
}
|
|
@@ -9,7 +9,7 @@ import invalidKeyInput from '../lib/invalid_key_input.js';
|
|
|
9
9
|
import supported from './ciphers.js';
|
|
10
10
|
import { types } from './is_key_like.js';
|
|
11
11
|
function checkKeySize(key, alg) {
|
|
12
|
-
if (key.symmetricKeySize << 3 !== parseInt(alg.
|
|
12
|
+
if (key.symmetricKeySize << 3 !== parseInt(alg.slice(1, 4), 10)) {
|
|
13
13
|
throw new TypeError(`Invalid key size for alg: ${alg}`);
|
|
14
14
|
}
|
|
15
15
|
}
|
|
@@ -27,7 +27,7 @@ function ensureKeyObject(key, alg, usage) {
|
|
|
27
27
|
throw new TypeError(invalidKeyInput(key, ...types, 'Uint8Array'));
|
|
28
28
|
}
|
|
29
29
|
export const wrap = (alg, key, cek) => {
|
|
30
|
-
const size = parseInt(alg.
|
|
30
|
+
const size = parseInt(alg.slice(1, 4), 10);
|
|
31
31
|
const algorithm = `aes${size}-wrap`;
|
|
32
32
|
if (!supported(algorithm)) {
|
|
33
33
|
throw new JOSENotSupported(`alg ${alg} is not supported either by JOSE or your javascript runtime`);
|
|
@@ -38,7 +38,7 @@ export const wrap = (alg, key, cek) => {
|
|
|
38
38
|
return concat(cipher.update(cek), cipher.final());
|
|
39
39
|
};
|
|
40
40
|
export const unwrap = (alg, key, encryptedKey) => {
|
|
41
|
-
const size = parseInt(alg.
|
|
41
|
+
const size = parseInt(alg.slice(1, 4), 10);
|
|
42
42
|
const algorithm = `aes${size}-wrap`;
|
|
43
43
|
if (!supported(algorithm)) {
|
|
44
44
|
throw new JOSENotSupported(`alg ${alg} is not supported either by JOSE or your javascript runtime`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Buffer } from 'buffer';
|
|
2
2
|
import { decoder } from '../lib/buffer_utils.js';
|
|
3
|
-
let
|
|
3
|
+
let encode;
|
|
4
4
|
function normalize(input) {
|
|
5
5
|
let encoded = input;
|
|
6
6
|
if (encoded instanceof Uint8Array) {
|
|
@@ -9,12 +9,12 @@ function normalize(input) {
|
|
|
9
9
|
return encoded;
|
|
10
10
|
}
|
|
11
11
|
if (Buffer.isEncoding('base64url')) {
|
|
12
|
-
|
|
12
|
+
encode = (input) => Buffer.from(input).toString('base64url');
|
|
13
13
|
}
|
|
14
14
|
else {
|
|
15
|
-
|
|
15
|
+
encode = (input) => Buffer.from(input).toString('base64').replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
|
|
16
16
|
}
|
|
17
17
|
export const decodeBase64 = (input) => Buffer.from(input, 'base64');
|
|
18
18
|
export const encodeBase64 = (input) => Buffer.from(input).toString('base64');
|
|
19
|
-
export
|
|
19
|
+
export { encode };
|
|
20
20
|
export const decode = (input) => Buffer.from(normalize(input), 'base64');
|
|
@@ -6,12 +6,12 @@ const checkCekLength = (enc, cek) => {
|
|
|
6
6
|
case 'A128CBC-HS256':
|
|
7
7
|
case 'A192CBC-HS384':
|
|
8
8
|
case 'A256CBC-HS512':
|
|
9
|
-
expected = parseInt(enc.
|
|
9
|
+
expected = parseInt(enc.slice(-3), 10);
|
|
10
10
|
break;
|
|
11
11
|
case 'A128GCM':
|
|
12
12
|
case 'A192GCM':
|
|
13
13
|
case 'A256GCM':
|
|
14
|
-
expected = parseInt(enc.
|
|
14
|
+
expected = parseInt(enc.slice(1, 4), 10);
|
|
15
15
|
break;
|
|
16
16
|
default:
|
|
17
17
|
throw new JOSENotSupported(`Content Encryption Algorithm ${enc} is not supported either by JOSE or your javascript runtime`);
|