jose 4.3.9 → 4.4.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 CHANGED
@@ -61,6 +61,7 @@ import * as jose from 'https://deno.land/x/jose/index.ts'
61
61
  - [Thumbprints](docs/functions/jwk_thumbprint.calculateJwkThumbprint.md#readme)
62
62
  - [EmbeddedJWK](docs/functions/jwk_embedded.EmbeddedJWK.md#readme)
63
63
  - JSON Web Key Set (JWKS)
64
+ - [Verify using a local JWKSet](docs/functions/jwks_local.createLocalJWKSet.md#readme)
64
65
  - [Verify using a remote JWKSet](docs/functions/jwks_remote.createRemoteJWKSet.md#readme)
65
66
  - Key Pair or Secret Generation
66
67
  - [Asymmetric Key Pair Generation](docs/functions/key_generate_key_pair.generateKeyPair.md#readme)
@@ -16,6 +16,7 @@ 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';
@@ -0,0 +1,101 @@
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 joseHeader = {
42
+ ...protectedHeader,
43
+ ...token.header,
44
+ };
45
+ const candidates = this._jwks.keys.filter((jwk) => {
46
+ let candidate = jwk.kty === getKtyFromAlg(joseHeader.alg);
47
+ if (candidate && typeof joseHeader.kid === 'string') {
48
+ candidate = joseHeader.kid === jwk.kid;
49
+ }
50
+ if (candidate && typeof jwk.alg === 'string') {
51
+ candidate = joseHeader.alg === jwk.alg;
52
+ }
53
+ if (candidate && typeof jwk.use === 'string') {
54
+ candidate = jwk.use === 'sig';
55
+ }
56
+ if (candidate && Array.isArray(jwk.key_ops)) {
57
+ candidate = jwk.key_ops.includes('verify');
58
+ }
59
+ if (candidate && joseHeader.alg === 'EdDSA') {
60
+ candidate = jwk.crv === 'Ed25519' || jwk.crv === 'Ed448';
61
+ }
62
+ if (candidate) {
63
+ switch (joseHeader.alg) {
64
+ case 'ES256':
65
+ candidate = jwk.crv === 'P-256';
66
+ break;
67
+ case 'ES256K':
68
+ candidate = jwk.crv === 'secp256k1';
69
+ break;
70
+ case 'ES384':
71
+ candidate = jwk.crv === 'P-384';
72
+ break;
73
+ case 'ES512':
74
+ candidate = jwk.crv === 'P-521';
75
+ break;
76
+ default:
77
+ }
78
+ }
79
+ return candidate;
80
+ });
81
+ const { 0: jwk, length } = candidates;
82
+ if (length === 0) {
83
+ throw new JWKSNoMatchingKey();
84
+ }
85
+ else if (length !== 1) {
86
+ throw new JWKSMultipleMatchingKeys();
87
+ }
88
+ const cached = this._cached.get(jwk) || this._cached.set(jwk, {}).get(jwk);
89
+ if (cached[joseHeader.alg] === undefined) {
90
+ const keyObject = await importJWK({ ...jwk, ext: true }, joseHeader.alg);
91
+ if (keyObject instanceof Uint8Array || keyObject.type !== 'public') {
92
+ throw new JWKSInvalid('JSON Web Key Set members must be public keys');
93
+ }
94
+ cached[joseHeader.alg] = keyObject;
95
+ }
96
+ return cached[joseHeader.alg];
97
+ }
98
+ }
99
+ export function createLocalJWKSet(jwks) {
100
+ return LocalJWKSet.prototype.getKey.bind(new LocalJWKSet(jwks));
101
+ }
@@ -1,26 +1,10 @@
1
1
  import fetchJwks from '../runtime/fetch_jwks.js';
2
- import { importJWK } from '../key/import.js';
3
- import { JWKSInvalid, JOSENotSupported, JWKSNoMatchingKey, JWKSMultipleMatchingKeys, } from '../util/errors.js';
4
- import isObject from '../lib/is_object.js';
5
- function getKtyFromAlg(alg) {
6
- switch (typeof alg === 'string' && alg.slice(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 { JWKSInvalid, JWKSNoMatchingKey } from '../util/errors.js';
3
+ import { isJWKSLike, LocalJWKSet } from './local.js';
4
+ class RemoteJWKSet extends LocalJWKSet {
22
5
  constructor(url, options) {
23
- this._cached = new WeakMap();
6
+ super({ keys: [] });
7
+ this._jwks = undefined;
24
8
  if (!(url instanceof URL)) {
25
9
  throw new TypeError('url must be an instance of URL');
26
10
  }
@@ -38,78 +22,27 @@ class RemoteJWKSet {
38
22
  return Date.now() < this._cooldownStarted + this._cooldownDuration;
39
23
  }
40
24
  async getKey(protectedHeader, token) {
41
- const joseHeader = {
42
- ...protectedHeader,
43
- ...token.header,
44
- };
45
25
  if (!this._jwks) {
46
26
  await this.reload();
47
27
  }
48
- const candidates = this._jwks.keys.filter((jwk) => {
49
- let candidate = jwk.kty === getKtyFromAlg(joseHeader.alg);
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();
28
+ try {
29
+ return await super.getKey(protectedHeader, token);
94
30
  }
95
- const cached = this._cached.get(jwk) || this._cached.set(jwk, {}).get(jwk);
96
- if (cached[joseHeader.alg] === undefined) {
97
- const keyObject = await importJWK({ ...jwk, ext: true }, joseHeader.alg);
98
- if (keyObject instanceof Uint8Array || keyObject.type !== 'public') {
99
- throw new JWKSInvalid('JSON Web Key Set members must be public keys');
31
+ catch (err) {
32
+ if (err instanceof JWKSNoMatchingKey) {
33
+ if (this.coolingDown() === false) {
34
+ await this.reload();
35
+ return super.getKey(protectedHeader, token);
36
+ }
100
37
  }
101
- cached[joseHeader.alg] = keyObject;
38
+ throw err;
102
39
  }
103
- return cached[joseHeader.alg];
104
40
  }
105
41
  async reload() {
106
42
  if (!this._pendingFetch) {
107
43
  this._pendingFetch = fetchJwks(this._url, this._timeoutDuration, this._options)
108
44
  .then((json) => {
109
- if (typeof json !== 'object' ||
110
- !json ||
111
- !Array.isArray(json.keys) ||
112
- !json.keys.every(isJWKLike)) {
45
+ if (!isJWKSLike(json)) {
113
46
  throw new JWKSInvalid('JSON Web Key Set malformed');
114
47
  }
115
48
  this._jwks = { keys: json.keys };
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.base64url = exports.generateSecret = exports.generateKeyPair = exports.errors = exports.decodeProtectedHeader = exports.importJWK = exports.importX509 = exports.importPKCS8 = exports.importSPKI = exports.exportJWK = exports.exportSPKI = exports.exportPKCS8 = exports.UnsecuredJWT = exports.createRemoteJWKSet = exports.EmbeddedJWK = exports.calculateJwkThumbprint = exports.EncryptJWT = exports.SignJWT = exports.GeneralSign = exports.FlattenedSign = exports.CompactSign = exports.FlattenedEncrypt = exports.CompactEncrypt = exports.jwtDecrypt = exports.jwtVerify = exports.generalVerify = exports.flattenedVerify = exports.compactVerify = exports.GeneralEncrypt = exports.generalDecrypt = exports.flattenedDecrypt = exports.compactDecrypt = void 0;
3
+ exports.base64url = exports.generateSecret = exports.generateKeyPair = exports.errors = exports.decodeProtectedHeader = exports.importJWK = exports.importX509 = exports.importPKCS8 = exports.importSPKI = exports.exportJWK = exports.exportSPKI = exports.exportPKCS8 = exports.UnsecuredJWT = exports.createRemoteJWKSet = exports.createLocalJWKSet = exports.EmbeddedJWK = exports.calculateJwkThumbprint = exports.EncryptJWT = exports.SignJWT = exports.GeneralSign = exports.FlattenedSign = exports.CompactSign = exports.FlattenedEncrypt = exports.CompactEncrypt = exports.jwtDecrypt = exports.jwtVerify = exports.generalVerify = exports.flattenedVerify = exports.compactVerify = exports.GeneralEncrypt = exports.generalDecrypt = exports.flattenedDecrypt = exports.compactDecrypt = void 0;
4
4
  var decrypt_js_1 = require("./jwe/compact/decrypt.js");
5
5
  Object.defineProperty(exports, "compactDecrypt", { enumerable: true, get: function () { return decrypt_js_1.compactDecrypt; } });
6
6
  var decrypt_js_2 = require("./jwe/flattened/decrypt.js");
@@ -37,6 +37,8 @@ var thumbprint_js_1 = require("./jwk/thumbprint.js");
37
37
  Object.defineProperty(exports, "calculateJwkThumbprint", { enumerable: true, get: function () { return thumbprint_js_1.calculateJwkThumbprint; } });
38
38
  var embedded_js_1 = require("./jwk/embedded.js");
39
39
  Object.defineProperty(exports, "EmbeddedJWK", { enumerable: true, get: function () { return embedded_js_1.EmbeddedJWK; } });
40
+ var local_js_1 = require("./jwks/local.js");
41
+ Object.defineProperty(exports, "createLocalJWKSet", { enumerable: true, get: function () { return local_js_1.createLocalJWKSet; } });
40
42
  var remote_js_1 = require("./jwks/remote.js");
41
43
  Object.defineProperty(exports, "createRemoteJWKSet", { enumerable: true, get: function () { return remote_js_1.createRemoteJWKSet; } });
42
44
  var unsecured_js_1 = require("./jwt/unsecured.js");
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createLocalJWKSet = exports.LocalJWKSet = exports.isJWKSLike = void 0;
4
+ const import_js_1 = require("../key/import.js");
5
+ const errors_js_1 = require("../util/errors.js");
6
+ const is_object_js_1 = require("../lib/is_object.js");
7
+ function getKtyFromAlg(alg) {
8
+ switch (typeof alg === 'string' && alg.slice(0, 2)) {
9
+ case 'RS':
10
+ case 'PS':
11
+ return 'RSA';
12
+ case 'ES':
13
+ return 'EC';
14
+ case 'Ed':
15
+ return 'OKP';
16
+ default:
17
+ throw new errors_js_1.JOSENotSupported('Unsupported "alg" value for a JSON Web Key Set');
18
+ }
19
+ }
20
+ function isJWKSLike(jwks) {
21
+ return (jwks &&
22
+ typeof jwks === 'object' &&
23
+ Array.isArray(jwks.keys) &&
24
+ jwks.keys.every(isJWKLike));
25
+ }
26
+ exports.isJWKSLike = isJWKSLike;
27
+ function isJWKLike(key) {
28
+ return (0, is_object_js_1.default)(key);
29
+ }
30
+ function clone(obj) {
31
+ if (typeof structuredClone === 'function') {
32
+ return structuredClone(obj);
33
+ }
34
+ return JSON.parse(JSON.stringify(obj));
35
+ }
36
+ class LocalJWKSet {
37
+ constructor(jwks) {
38
+ this._cached = new WeakMap();
39
+ if (!isJWKSLike(jwks)) {
40
+ throw new errors_js_1.JWKSInvalid('JSON Web Key Set malformed');
41
+ }
42
+ this._jwks = clone(jwks);
43
+ }
44
+ async getKey(protectedHeader, token) {
45
+ const joseHeader = {
46
+ ...protectedHeader,
47
+ ...token.header,
48
+ };
49
+ const candidates = this._jwks.keys.filter((jwk) => {
50
+ let candidate = jwk.kty === getKtyFromAlg(joseHeader.alg);
51
+ if (candidate && typeof joseHeader.kid === 'string') {
52
+ candidate = joseHeader.kid === jwk.kid;
53
+ }
54
+ if (candidate && typeof jwk.alg === 'string') {
55
+ candidate = joseHeader.alg === jwk.alg;
56
+ }
57
+ if (candidate && typeof jwk.use === 'string') {
58
+ candidate = jwk.use === 'sig';
59
+ }
60
+ if (candidate && Array.isArray(jwk.key_ops)) {
61
+ candidate = jwk.key_ops.includes('verify');
62
+ }
63
+ if (candidate && joseHeader.alg === 'EdDSA') {
64
+ candidate = jwk.crv === 'Ed25519' || jwk.crv === 'Ed448';
65
+ }
66
+ if (candidate) {
67
+ switch (joseHeader.alg) {
68
+ case 'ES256':
69
+ candidate = jwk.crv === 'P-256';
70
+ break;
71
+ case 'ES256K':
72
+ candidate = jwk.crv === 'secp256k1';
73
+ break;
74
+ case 'ES384':
75
+ candidate = jwk.crv === 'P-384';
76
+ break;
77
+ case 'ES512':
78
+ candidate = jwk.crv === 'P-521';
79
+ break;
80
+ default:
81
+ }
82
+ }
83
+ return candidate;
84
+ });
85
+ const { 0: jwk, length } = candidates;
86
+ if (length === 0) {
87
+ throw new errors_js_1.JWKSNoMatchingKey();
88
+ }
89
+ else if (length !== 1) {
90
+ throw new errors_js_1.JWKSMultipleMatchingKeys();
91
+ }
92
+ const cached = this._cached.get(jwk) || this._cached.set(jwk, {}).get(jwk);
93
+ if (cached[joseHeader.alg] === undefined) {
94
+ const keyObject = await (0, import_js_1.importJWK)({ ...jwk, ext: true }, joseHeader.alg);
95
+ if (keyObject instanceof Uint8Array || keyObject.type !== 'public') {
96
+ throw new errors_js_1.JWKSInvalid('JSON Web Key Set members must be public keys');
97
+ }
98
+ cached[joseHeader.alg] = keyObject;
99
+ }
100
+ return cached[joseHeader.alg];
101
+ }
102
+ }
103
+ exports.LocalJWKSet = LocalJWKSet;
104
+ function createLocalJWKSet(jwks) {
105
+ return LocalJWKSet.prototype.getKey.bind(new LocalJWKSet(jwks));
106
+ }
107
+ exports.createLocalJWKSet = createLocalJWKSet;
@@ -2,28 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createRemoteJWKSet = void 0;
4
4
  const fetch_jwks_js_1 = require("../runtime/fetch_jwks.js");
5
- const import_js_1 = require("../key/import.js");
6
5
  const errors_js_1 = require("../util/errors.js");
7
- const is_object_js_1 = require("../lib/is_object.js");
8
- function getKtyFromAlg(alg) {
9
- switch (typeof alg === 'string' && alg.slice(0, 2)) {
10
- case 'RS':
11
- case 'PS':
12
- return 'RSA';
13
- case 'ES':
14
- return 'EC';
15
- case 'Ed':
16
- return 'OKP';
17
- default:
18
- throw new errors_js_1.JOSENotSupported('Unsupported "alg" value for a JSON Web Key Set');
19
- }
20
- }
21
- function isJWKLike(key) {
22
- return (0, is_object_js_1.default)(key);
23
- }
24
- class RemoteJWKSet {
6
+ const local_js_1 = require("./local.js");
7
+ class RemoteJWKSet extends local_js_1.LocalJWKSet {
25
8
  constructor(url, options) {
26
- this._cached = new WeakMap();
9
+ super({ keys: [] });
10
+ this._jwks = undefined;
27
11
  if (!(url instanceof URL)) {
28
12
  throw new TypeError('url must be an instance of URL');
29
13
  }
@@ -41,78 +25,27 @@ class RemoteJWKSet {
41
25
  return Date.now() < this._cooldownStarted + this._cooldownDuration;
42
26
  }
43
27
  async getKey(protectedHeader, token) {
44
- const joseHeader = {
45
- ...protectedHeader,
46
- ...token.header,
47
- };
48
28
  if (!this._jwks) {
49
29
  await this.reload();
50
30
  }
51
- const candidates = this._jwks.keys.filter((jwk) => {
52
- let candidate = jwk.kty === getKtyFromAlg(joseHeader.alg);
53
- if (candidate && typeof joseHeader.kid === 'string') {
54
- candidate = joseHeader.kid === jwk.kid;
55
- }
56
- if (candidate && typeof jwk.alg === 'string') {
57
- candidate = joseHeader.alg === jwk.alg;
58
- }
59
- if (candidate && typeof jwk.use === 'string') {
60
- candidate = jwk.use === 'sig';
61
- }
62
- if (candidate && Array.isArray(jwk.key_ops)) {
63
- candidate = jwk.key_ops.includes('verify');
64
- }
65
- if (candidate && joseHeader.alg === 'EdDSA') {
66
- candidate = jwk.crv === 'Ed25519' || jwk.crv === 'Ed448';
67
- }
68
- if (candidate) {
69
- switch (joseHeader.alg) {
70
- case 'ES256':
71
- candidate = jwk.crv === 'P-256';
72
- break;
73
- case 'ES256K':
74
- candidate = jwk.crv === 'secp256k1';
75
- break;
76
- case 'ES384':
77
- candidate = jwk.crv === 'P-384';
78
- break;
79
- case 'ES512':
80
- candidate = jwk.crv === 'P-521';
81
- break;
82
- default:
83
- }
84
- }
85
- return candidate;
86
- });
87
- const { 0: jwk, length } = candidates;
88
- if (length === 0) {
89
- if (this.coolingDown() === false) {
90
- await this.reload();
91
- return this.getKey(protectedHeader, token);
92
- }
93
- throw new errors_js_1.JWKSNoMatchingKey();
94
- }
95
- else if (length !== 1) {
96
- throw new errors_js_1.JWKSMultipleMatchingKeys();
31
+ try {
32
+ return await super.getKey(protectedHeader, token);
97
33
  }
98
- const cached = this._cached.get(jwk) || this._cached.set(jwk, {}).get(jwk);
99
- if (cached[joseHeader.alg] === undefined) {
100
- const keyObject = await (0, import_js_1.importJWK)({ ...jwk, ext: true }, joseHeader.alg);
101
- if (keyObject instanceof Uint8Array || keyObject.type !== 'public') {
102
- throw new errors_js_1.JWKSInvalid('JSON Web Key Set members must be public keys');
34
+ catch (err) {
35
+ if (err instanceof errors_js_1.JWKSNoMatchingKey) {
36
+ if (this.coolingDown() === false) {
37
+ await this.reload();
38
+ return super.getKey(protectedHeader, token);
39
+ }
103
40
  }
104
- cached[joseHeader.alg] = keyObject;
41
+ throw err;
105
42
  }
106
- return cached[joseHeader.alg];
107
43
  }
108
44
  async reload() {
109
45
  if (!this._pendingFetch) {
110
46
  this._pendingFetch = (0, fetch_jwks_js_1.default)(this._url, this._timeoutDuration, this._options)
111
47
  .then((json) => {
112
- if (typeof json !== 'object' ||
113
- !json ||
114
- !Array.isArray(json.keys) ||
115
- !json.keys.every(isJWKLike)) {
48
+ if (!(0, local_js_1.isJWKSLike)(json)) {
116
49
  throw new errors_js_1.JWKSInvalid('JSON Web Key Set malformed');
117
50
  }
118
51
  this._jwks = { keys: json.keys };
@@ -16,6 +16,7 @@ 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';
@@ -0,0 +1,101 @@
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 joseHeader = {
42
+ ...protectedHeader,
43
+ ...token.header,
44
+ };
45
+ const candidates = this._jwks.keys.filter((jwk) => {
46
+ let candidate = jwk.kty === getKtyFromAlg(joseHeader.alg);
47
+ if (candidate && typeof joseHeader.kid === 'string') {
48
+ candidate = joseHeader.kid === jwk.kid;
49
+ }
50
+ if (candidate && typeof jwk.alg === 'string') {
51
+ candidate = joseHeader.alg === jwk.alg;
52
+ }
53
+ if (candidate && typeof jwk.use === 'string') {
54
+ candidate = jwk.use === 'sig';
55
+ }
56
+ if (candidate && Array.isArray(jwk.key_ops)) {
57
+ candidate = jwk.key_ops.includes('verify');
58
+ }
59
+ if (candidate && joseHeader.alg === 'EdDSA') {
60
+ candidate = jwk.crv === 'Ed25519' || jwk.crv === 'Ed448';
61
+ }
62
+ if (candidate) {
63
+ switch (joseHeader.alg) {
64
+ case 'ES256':
65
+ candidate = jwk.crv === 'P-256';
66
+ break;
67
+ case 'ES256K':
68
+ candidate = jwk.crv === 'secp256k1';
69
+ break;
70
+ case 'ES384':
71
+ candidate = jwk.crv === 'P-384';
72
+ break;
73
+ case 'ES512':
74
+ candidate = jwk.crv === 'P-521';
75
+ break;
76
+ default:
77
+ }
78
+ }
79
+ return candidate;
80
+ });
81
+ const { 0: jwk, length } = candidates;
82
+ if (length === 0) {
83
+ throw new JWKSNoMatchingKey();
84
+ }
85
+ else if (length !== 1) {
86
+ throw new JWKSMultipleMatchingKeys();
87
+ }
88
+ const cached = this._cached.get(jwk) || this._cached.set(jwk, {}).get(jwk);
89
+ if (cached[joseHeader.alg] === undefined) {
90
+ const keyObject = await importJWK({ ...jwk, ext: true }, joseHeader.alg);
91
+ if (keyObject instanceof Uint8Array || keyObject.type !== 'public') {
92
+ throw new JWKSInvalid('JSON Web Key Set members must be public keys');
93
+ }
94
+ cached[joseHeader.alg] = keyObject;
95
+ }
96
+ return cached[joseHeader.alg];
97
+ }
98
+ }
99
+ export function createLocalJWKSet(jwks) {
100
+ return LocalJWKSet.prototype.getKey.bind(new LocalJWKSet(jwks));
101
+ }
@@ -1,26 +1,10 @@
1
1
  import fetchJwks from '../runtime/fetch_jwks.js';
2
- import { importJWK } from '../key/import.js';
3
- import { JWKSInvalid, JOSENotSupported, JWKSNoMatchingKey, JWKSMultipleMatchingKeys, } from '../util/errors.js';
4
- import isObject from '../lib/is_object.js';
5
- function getKtyFromAlg(alg) {
6
- switch (typeof alg === 'string' && alg.slice(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 { JWKSInvalid, JWKSNoMatchingKey } from '../util/errors.js';
3
+ import { isJWKSLike, LocalJWKSet } from './local.js';
4
+ class RemoteJWKSet extends LocalJWKSet {
22
5
  constructor(url, options) {
23
- this._cached = new WeakMap();
6
+ super({ keys: [] });
7
+ this._jwks = undefined;
24
8
  if (!(url instanceof URL)) {
25
9
  throw new TypeError('url must be an instance of URL');
26
10
  }
@@ -38,78 +22,27 @@ class RemoteJWKSet {
38
22
  return Date.now() < this._cooldownStarted + this._cooldownDuration;
39
23
  }
40
24
  async getKey(protectedHeader, token) {
41
- const joseHeader = {
42
- ...protectedHeader,
43
- ...token.header,
44
- };
45
25
  if (!this._jwks) {
46
26
  await this.reload();
47
27
  }
48
- const candidates = this._jwks.keys.filter((jwk) => {
49
- let candidate = jwk.kty === getKtyFromAlg(joseHeader.alg);
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();
28
+ try {
29
+ return await super.getKey(protectedHeader, token);
94
30
  }
95
- const cached = this._cached.get(jwk) || this._cached.set(jwk, {}).get(jwk);
96
- if (cached[joseHeader.alg] === undefined) {
97
- const keyObject = await importJWK({ ...jwk, ext: true }, joseHeader.alg);
98
- if (keyObject instanceof Uint8Array || keyObject.type !== 'public') {
99
- throw new JWKSInvalid('JSON Web Key Set members must be public keys');
31
+ catch (err) {
32
+ if (err instanceof JWKSNoMatchingKey) {
33
+ if (this.coolingDown() === false) {
34
+ await this.reload();
35
+ return super.getKey(protectedHeader, token);
36
+ }
100
37
  }
101
- cached[joseHeader.alg] = keyObject;
38
+ throw err;
102
39
  }
103
- return cached[joseHeader.alg];
104
40
  }
105
41
  async reload() {
106
42
  if (!this._pendingFetch) {
107
43
  this._pendingFetch = fetchJwks(this._url, this._timeoutDuration, this._options)
108
44
  .then((json) => {
109
- if (typeof json !== 'object' ||
110
- !json ||
111
- !Array.isArray(json.keys) ||
112
- !json.keys.every(isJWKLike)) {
45
+ if (!isJWKSLike(json)) {
113
46
  throw new JWKSInvalid('JSON Web Key Set malformed');
114
47
  }
115
48
  this._jwks = { keys: json.keys };
@@ -27,6 +27,7 @@ export { SignJWT } from './jwt/sign';
27
27
  export { EncryptJWT } from './jwt/encrypt';
28
28
  export { calculateJwkThumbprint } from './jwk/thumbprint';
29
29
  export { EmbeddedJWK } from './jwk/embedded';
30
+ export { createLocalJWKSet } from './jwks/local';
30
31
  export { createRemoteJWKSet } from './jwks/remote';
31
32
  export type { RemoteJWKSetOptions } from './jwks/remote';
32
33
  export { UnsecuredJWT } from './jwt/unsecured';
@@ -42,4 +43,4 @@ export type { GenerateKeyPairResult, GenerateKeyPairOptions } from './key/genera
42
43
  export { generateSecret } from './key/generate_secret';
43
44
  export type { GenerateSecretOptions } from './key/generate_secret';
44
45
  export * as base64url from './util/base64url';
45
- export type { KeyLike, JWK, FlattenedJWSInput, GeneralJWSInput, FlattenedJWS, GeneralJWS, JoseHeaderParameters, JWSHeaderParameters, JWEKeyManagementHeaderParameters, FlattenedJWE, GeneralJWE, JWEHeaderParameters, CritOption, DeflateOption, DecryptOptions, EncryptOptions, JWTClaimVerificationOptions, VerifyOptions, SignOptions, JWTPayload, DeflateFunction, InflateFunction, FlattenedDecryptResult, GeneralDecryptResult, CompactDecryptResult, FlattenedVerifyResult, GeneralVerifyResult, CompactVerifyResult, JWTVerifyResult, JWTDecryptResult, ResolvedKey, CompactJWEHeaderParameters, CompactJWSHeaderParameters, JWTHeaderParameters, } from './types';
46
+ export type { KeyLike, JWK, FlattenedJWSInput, GeneralJWSInput, FlattenedJWS, GeneralJWS, JoseHeaderParameters, JWSHeaderParameters, JWEKeyManagementHeaderParameters, FlattenedJWE, GeneralJWE, JWEHeaderParameters, CritOption, DeflateOption, DecryptOptions, EncryptOptions, JWTClaimVerificationOptions, VerifyOptions, SignOptions, JWTPayload, DeflateFunction, InflateFunction, FlattenedDecryptResult, GeneralDecryptResult, CompactDecryptResult, FlattenedVerifyResult, GeneralVerifyResult, CompactVerifyResult, JWTVerifyResult, JWTDecryptResult, ResolvedKey, CompactJWEHeaderParameters, CompactJWSHeaderParameters, JWTHeaderParameters, JSONWebKeySet, } from './types';
@@ -0,0 +1,9 @@
1
+ import type { KeyLike, JWSHeaderParameters, JSONWebKeySet, FlattenedJWSInput, GetKeyFunction } from '../types';
2
+ export declare function isJWKSLike(jwks: unknown): jwks is JSONWebKeySet;
3
+ export declare class LocalJWKSet {
4
+ protected _jwks?: JSONWebKeySet;
5
+ private _cached;
6
+ constructor(jwks: unknown);
7
+ getKey(protectedHeader: JWSHeaderParameters, token: FlattenedJWSInput): Promise<KeyLike>;
8
+ }
9
+ export declare function createLocalJWKSet(jwks: JSONWebKeySet): GetKeyFunction<JWSHeaderParameters, FlattenedJWSInput>;
@@ -268,3 +268,6 @@ export interface CompactJWEHeaderParameters extends JWEHeaderParameters {
268
268
  alg: string
269
269
  enc: string
270
270
  }
271
+ export interface JSONWebKeySet {
272
+ keys: JWK[]
273
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jose",
3
- "version": "4.3.9",
3
+ "version": "4.4.0",
4
4
  "description": "'JSON Web Almost Everything' - JWA, JWS, JWE, JWT, JWK, JWKS with no dependencies using runtime's native crypto",
5
5
  "keywords": [
6
6
  "browser",