jose 5.4.1 → 5.6.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.
@@ -17,7 +17,7 @@ export { EncryptJWT } from './jwt/encrypt.js';
17
17
  export { calculateJwkThumbprint, calculateJwkThumbprintUri } from './jwk/thumbprint.js';
18
18
  export { EmbeddedJWK } from './jwk/embedded.js';
19
19
  export { createLocalJWKSet } from './jwks/local.js';
20
- export { createRemoteJWKSet } from './jwks/remote.js';
20
+ export { createRemoteJWKSet, experimental_jwksCache } from './jwks/remote.js';
21
21
  export { UnsecuredJWT } from './jwt/unsecured.js';
22
22
  export { exportPKCS8, exportSPKI, exportJWK } from './key/export.js';
23
23
  export { importSPKI, importPKCS8, importX509, importJWK } from './key/import.js';
@@ -1,6 +1,7 @@
1
1
  import fetchJwks from '../runtime/fetch_jwks.js';
2
2
  import { JWKSNoMatchingKey } from '../util/errors.js';
3
3
  import { createLocalJWKSet } from './local.js';
4
+ import isObject from '../lib/is_object.js';
4
5
  function isCloudflareWorkers() {
5
6
  return (typeof WebSocketPair !== 'undefined' ||
6
7
  (typeof navigator !== 'undefined' && navigator.userAgent === 'Cloudflare-Workers') ||
@@ -9,9 +10,25 @@ function isCloudflareWorkers() {
9
10
  let USER_AGENT;
10
11
  if (typeof navigator === 'undefined' || !navigator.userAgent?.startsWith?.('Mozilla/5.0 ')) {
11
12
  const NAME = 'jose';
12
- const VERSION = 'v5.4.1';
13
+ const VERSION = 'v5.6.0';
13
14
  USER_AGENT = `${NAME}/${VERSION}`;
14
15
  }
16
+ export const experimental_jwksCache = Symbol();
17
+ function isFreshJwksCache(input, cacheMaxAge) {
18
+ if (typeof input !== 'object' || input === null) {
19
+ return false;
20
+ }
21
+ if (!('uat' in input) || typeof input.uat !== 'number' || Date.now() - input.uat >= cacheMaxAge) {
22
+ return false;
23
+ }
24
+ if (!('jwks' in input) ||
25
+ !isObject(input.jwks) ||
26
+ !Array.isArray(input.jwks.keys) ||
27
+ !Array.prototype.every.call(input.jwks.keys, isObject)) {
28
+ return false;
29
+ }
30
+ return true;
31
+ }
15
32
  class RemoteJWKSet {
16
33
  constructor(url, options) {
17
34
  if (!(url instanceof URL)) {
@@ -24,6 +41,13 @@ class RemoteJWKSet {
24
41
  this._cooldownDuration =
25
42
  typeof options?.cooldownDuration === 'number' ? options?.cooldownDuration : 30000;
26
43
  this._cacheMaxAge = typeof options?.cacheMaxAge === 'number' ? options?.cacheMaxAge : 600000;
44
+ if (options?.[experimental_jwksCache] !== undefined) {
45
+ this._cache = options?.[experimental_jwksCache];
46
+ if (isFreshJwksCache(options?.[experimental_jwksCache], this._cacheMaxAge)) {
47
+ this._jwksTimestamp = this._cache.uat;
48
+ this._local = createLocalJWKSet(this._cache.jwks);
49
+ }
50
+ }
27
51
  }
28
52
  coolingDown() {
29
53
  return typeof this._jwksTimestamp === 'number'
@@ -64,6 +88,10 @@ class RemoteJWKSet {
64
88
  this._pendingFetch || (this._pendingFetch = fetchJwks(this._url, this._timeoutDuration, this._options)
65
89
  .then((json) => {
66
90
  this._local = createLocalJWKSet(json);
91
+ if (this._cache) {
92
+ this._cache.uat = Date.now();
93
+ this._cache.jwks = json;
94
+ }
67
95
  this._jwksTimestamp = Date.now();
68
96
  this._pendingFetch = undefined;
69
97
  })
@@ -1,5 +1,6 @@
1
1
  import { withAlg as invalidKeyInput } from './invalid_key_input.js';
2
2
  import isKeyLike, { types } from '../runtime/is_key_like.js';
3
+ const tag = (key) => key?.[Symbol.toStringTag];
3
4
  const symmetricTypeCheck = (alg, key) => {
4
5
  if (key instanceof Uint8Array)
5
6
  return;
@@ -7,7 +8,7 @@ const symmetricTypeCheck = (alg, key) => {
7
8
  throw new TypeError(invalidKeyInput(alg, key, ...types, 'Uint8Array'));
8
9
  }
9
10
  if (key.type !== 'secret') {
10
- throw new TypeError(`${types.join(' or ')} instances for symmetric algorithms must be of type "secret"`);
11
+ throw new TypeError(`${tag(key)} instances for symmetric algorithms must be of type "secret"`);
11
12
  }
12
13
  };
13
14
  const asymmetricTypeCheck = (alg, key, usage) => {
@@ -15,19 +16,19 @@ const asymmetricTypeCheck = (alg, key, usage) => {
15
16
  throw new TypeError(invalidKeyInput(alg, key, ...types));
16
17
  }
17
18
  if (key.type === 'secret') {
18
- throw new TypeError(`${types.join(' or ')} instances for asymmetric algorithms must not be of type "secret"`);
19
+ throw new TypeError(`${tag(key)} instances for asymmetric algorithms must not be of type "secret"`);
19
20
  }
20
21
  if (usage === 'sign' && key.type === 'public') {
21
- throw new TypeError(`${types.join(' or ')} instances for asymmetric algorithm signing must be of type "private"`);
22
+ throw new TypeError(`${tag(key)} instances for asymmetric algorithm signing must be of type "private"`);
22
23
  }
23
24
  if (usage === 'decrypt' && key.type === 'public') {
24
- throw new TypeError(`${types.join(' or ')} instances for asymmetric algorithm decryption must be of type "private"`);
25
+ throw new TypeError(`${tag(key)} instances for asymmetric algorithm decryption must be of type "private"`);
25
26
  }
26
27
  if (key.algorithm && usage === 'verify' && key.type === 'private') {
27
- throw new TypeError(`${types.join(' or ')} instances for asymmetric algorithm verifying must be of type "public"`);
28
+ throw new TypeError(`${tag(key)} instances for asymmetric algorithm verifying must be of type "public"`);
28
29
  }
29
30
  if (key.algorithm && usage === 'encrypt' && key.type === 'private') {
30
- throw new TypeError(`${types.join(' or ')} instances for asymmetric algorithm encryption must be of type "public"`);
31
+ throw new TypeError(`${tag(key)} instances for asymmetric algorithm encryption must be of type "public"`);
31
32
  }
32
33
  };
33
34
  const checkKeyType = (alg, key, usage) => {
@@ -3,6 +3,7 @@ import * as ECDH from '../runtime/ecdhes.js';
3
3
  import { decrypt as pbes2Kw } from '../runtime/pbes2kw.js';
4
4
  import { decrypt as rsaEs } from '../runtime/rsaes.js';
5
5
  import { decode as base64url } from '../runtime/base64url.js';
6
+ import * as normalize from '../runtime/normalize_key.js';
6
7
  import { JOSENotSupported, JWEInvalid } from '../util/errors.js';
7
8
  import { bitLength as cekLength } from '../lib/cek.js';
8
9
  import { importJWK } from '../key/import.js';
@@ -10,6 +11,9 @@ import checkKeyType from './check_key_type.js';
10
11
  import isObject from './is_object.js';
11
12
  import { unwrap as aesGcmKw } from './aesgcmkw.js';
12
13
  async function decryptKeyManagement(alg, key, encryptedKey, joseHeader, options) {
14
+ if (normalize.normalizePrivateKey) {
15
+ key = await normalize.normalizePrivateKey(key, alg);
16
+ }
13
17
  checkKeyType(alg, key, 'decrypt');
14
18
  switch (alg) {
15
19
  case 'dir': {
@@ -3,6 +3,7 @@ import * as ECDH from '../runtime/ecdhes.js';
3
3
  import { encrypt as pbes2Kw } from '../runtime/pbes2kw.js';
4
4
  import { encrypt as rsaEs } from '../runtime/rsaes.js';
5
5
  import { encode as base64url } from '../runtime/base64url.js';
6
+ import * as normalize from '../runtime/normalize_key.js';
6
7
  import generateCek, { bitLength as cekLength } from '../lib/cek.js';
7
8
  import { JOSENotSupported } from '../util/errors.js';
8
9
  import { exportJWK } from '../key/export.js';
@@ -12,6 +13,9 @@ async function encryptKeyManagement(alg, enc, key, providedCek, providedParamete
12
13
  let encryptedKey;
13
14
  let parameters;
14
15
  let cek;
16
+ if (normalize.normalizePublicKey) {
17
+ key = await normalize.normalizePublicKey(key, alg);
18
+ }
15
19
  checkKeyType(alg, key, 'encrypt');
16
20
  switch (alg) {
17
21
  case 'dir': {
@@ -2,7 +2,14 @@ import crypto, { isCryptoKey } from './webcrypto.js';
2
2
  import { checkSigCryptoKey } from '../lib/crypto_key.js';
3
3
  import invalidKeyInput from '../lib/invalid_key_input.js';
4
4
  import { types } from './is_key_like.js';
5
- export default function getCryptoKey(alg, key, usage) {
5
+ import * as normalize from './normalize_key.js';
6
+ export default async function getCryptoKey(alg, key, usage) {
7
+ if (normalize.normalizePrivateKey && usage === 'sign') {
8
+ key = await normalize.normalizePrivateKey(key, alg);
9
+ }
10
+ if (normalize.normalizePublicKey && usage === 'verify') {
11
+ key = await normalize.normalizePublicKey(key, alg);
12
+ }
6
13
  if (isCryptoKey(key)) {
7
14
  checkSigCryptoKey(key, alg, usage);
8
15
  return key;
@@ -1,5 +1,8 @@
1
1
  import { isCryptoKey } from './webcrypto.js';
2
2
  export default (key) => {
3
- return isCryptoKey(key);
3
+ if (isCryptoKey(key)) {
4
+ return true;
5
+ }
6
+ return key?.[Symbol.toStringTag] === 'KeyObject';
4
7
  };
5
8
  export const types = ['CryptoKey'];
@@ -0,0 +1,29 @@
1
+ import { decode } from './base64url.js';
2
+ import importJWK from './jwk_to_key.js';
3
+ const normalizeSecretKey = (k) => decode(k);
4
+ export const normalizePublicKey = async (key, alg) => {
5
+ if (key?.[Symbol.toStringTag] === 'KeyObject') {
6
+ let jwk = key.export({ format: 'jwk' });
7
+ delete jwk.d;
8
+ delete jwk.dp;
9
+ delete jwk.dq;
10
+ delete jwk.p;
11
+ delete jwk.q;
12
+ delete jwk.qi;
13
+ if (jwk.k) {
14
+ return normalizeSecretKey(jwk.k);
15
+ }
16
+ return importJWK({ ...jwk, alg });
17
+ }
18
+ return key;
19
+ };
20
+ export const normalizePrivateKey = async (key, alg) => {
21
+ if (key?.[Symbol.toStringTag] === 'KeyObject') {
22
+ let jwk = key.export({ format: 'jwk' });
23
+ if (jwk.k) {
24
+ return normalizeSecretKey(jwk.k);
25
+ }
26
+ return importJWK({ ...jwk, alg });
27
+ }
28
+ return key;
29
+ };
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.cryptoRuntime = exports.base64url = exports.generateSecret = exports.generateKeyPair = exports.errors = exports.decodeJwt = 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.calculateJwkThumbprintUri = 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.cryptoRuntime = exports.base64url = exports.generateSecret = exports.generateKeyPair = exports.errors = exports.decodeJwt = exports.decodeProtectedHeader = exports.importJWK = exports.importX509 = exports.importPKCS8 = exports.importSPKI = exports.exportJWK = exports.exportSPKI = exports.exportPKCS8 = exports.UnsecuredJWT = exports.experimental_jwksCache = exports.createRemoteJWKSet = exports.createLocalJWKSet = exports.EmbeddedJWK = exports.calculateJwkThumbprintUri = 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");
@@ -42,6 +42,7 @@ var local_js_1 = require("./jwks/local.js");
42
42
  Object.defineProperty(exports, "createLocalJWKSet", { enumerable: true, get: function () { return local_js_1.createLocalJWKSet; } });
43
43
  var remote_js_1 = require("./jwks/remote.js");
44
44
  Object.defineProperty(exports, "createRemoteJWKSet", { enumerable: true, get: function () { return remote_js_1.createRemoteJWKSet; } });
45
+ Object.defineProperty(exports, "experimental_jwksCache", { enumerable: true, get: function () { return remote_js_1.experimental_jwksCache; } });
45
46
  var unsecured_js_1 = require("./jwt/unsecured.js");
46
47
  Object.defineProperty(exports, "UnsecuredJWT", { enumerable: true, get: function () { return unsecured_js_1.UnsecuredJWT; } });
47
48
  var export_js_1 = require("./key/export.js");
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createRemoteJWKSet = void 0;
3
+ exports.createRemoteJWKSet = exports.experimental_jwksCache = void 0;
4
4
  const fetch_jwks_js_1 = require("../runtime/fetch_jwks.js");
5
5
  const errors_js_1 = require("../util/errors.js");
6
6
  const local_js_1 = require("./local.js");
7
+ const is_object_js_1 = require("../lib/is_object.js");
7
8
  function isCloudflareWorkers() {
8
9
  return (typeof WebSocketPair !== 'undefined' ||
9
10
  (typeof navigator !== 'undefined' && navigator.userAgent === 'Cloudflare-Workers') ||
@@ -12,9 +13,25 @@ function isCloudflareWorkers() {
12
13
  let USER_AGENT;
13
14
  if (typeof navigator === 'undefined' || !navigator.userAgent?.startsWith?.('Mozilla/5.0 ')) {
14
15
  const NAME = 'jose';
15
- const VERSION = 'v5.4.1';
16
+ const VERSION = 'v5.6.0';
16
17
  USER_AGENT = `${NAME}/${VERSION}`;
17
18
  }
19
+ exports.experimental_jwksCache = Symbol();
20
+ function isFreshJwksCache(input, cacheMaxAge) {
21
+ if (typeof input !== 'object' || input === null) {
22
+ return false;
23
+ }
24
+ if (!('uat' in input) || typeof input.uat !== 'number' || Date.now() - input.uat >= cacheMaxAge) {
25
+ return false;
26
+ }
27
+ if (!('jwks' in input) ||
28
+ !(0, is_object_js_1.default)(input.jwks) ||
29
+ !Array.isArray(input.jwks.keys) ||
30
+ !Array.prototype.every.call(input.jwks.keys, is_object_js_1.default)) {
31
+ return false;
32
+ }
33
+ return true;
34
+ }
18
35
  class RemoteJWKSet {
19
36
  _url;
20
37
  _timeoutDuration;
@@ -24,6 +41,7 @@ class RemoteJWKSet {
24
41
  _pendingFetch;
25
42
  _options;
26
43
  _local;
44
+ _cache;
27
45
  constructor(url, options) {
28
46
  if (!(url instanceof URL)) {
29
47
  throw new TypeError('url must be an instance of URL');
@@ -35,6 +53,13 @@ class RemoteJWKSet {
35
53
  this._cooldownDuration =
36
54
  typeof options?.cooldownDuration === 'number' ? options?.cooldownDuration : 30000;
37
55
  this._cacheMaxAge = typeof options?.cacheMaxAge === 'number' ? options?.cacheMaxAge : 600000;
56
+ if (options?.[exports.experimental_jwksCache] !== undefined) {
57
+ this._cache = options?.[exports.experimental_jwksCache];
58
+ if (isFreshJwksCache(options?.[exports.experimental_jwksCache], this._cacheMaxAge)) {
59
+ this._jwksTimestamp = this._cache.uat;
60
+ this._local = (0, local_js_1.createLocalJWKSet)(this._cache.jwks);
61
+ }
62
+ }
38
63
  }
39
64
  coolingDown() {
40
65
  return typeof this._jwksTimestamp === 'number'
@@ -75,6 +100,10 @@ class RemoteJWKSet {
75
100
  this._pendingFetch ||= (0, fetch_jwks_js_1.default)(this._url, this._timeoutDuration, this._options)
76
101
  .then((json) => {
77
102
  this._local = (0, local_js_1.createLocalJWKSet)(json);
103
+ if (this._cache) {
104
+ this._cache.uat = Date.now();
105
+ this._cache.jwks = json;
106
+ }
78
107
  this._jwksTimestamp = Date.now();
79
108
  this._pendingFetch = undefined;
80
109
  })
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const invalid_key_input_js_1 = require("./invalid_key_input.js");
4
4
  const is_key_like_js_1 = require("../runtime/is_key_like.js");
5
+ const tag = (key) => key?.[Symbol.toStringTag];
5
6
  const symmetricTypeCheck = (alg, key) => {
6
7
  if (key instanceof Uint8Array)
7
8
  return;
@@ -9,7 +10,7 @@ const symmetricTypeCheck = (alg, key) => {
9
10
  throw new TypeError((0, invalid_key_input_js_1.withAlg)(alg, key, ...is_key_like_js_1.types, 'Uint8Array'));
10
11
  }
11
12
  if (key.type !== 'secret') {
12
- throw new TypeError(`${is_key_like_js_1.types.join(' or ')} instances for symmetric algorithms must be of type "secret"`);
13
+ throw new TypeError(`${tag(key)} instances for symmetric algorithms must be of type "secret"`);
13
14
  }
14
15
  };
15
16
  const asymmetricTypeCheck = (alg, key, usage) => {
@@ -17,19 +18,19 @@ const asymmetricTypeCheck = (alg, key, usage) => {
17
18
  throw new TypeError((0, invalid_key_input_js_1.withAlg)(alg, key, ...is_key_like_js_1.types));
18
19
  }
19
20
  if (key.type === 'secret') {
20
- throw new TypeError(`${is_key_like_js_1.types.join(' or ')} instances for asymmetric algorithms must not be of type "secret"`);
21
+ throw new TypeError(`${tag(key)} instances for asymmetric algorithms must not be of type "secret"`);
21
22
  }
22
23
  if (usage === 'sign' && key.type === 'public') {
23
- throw new TypeError(`${is_key_like_js_1.types.join(' or ')} instances for asymmetric algorithm signing must be of type "private"`);
24
+ throw new TypeError(`${tag(key)} instances for asymmetric algorithm signing must be of type "private"`);
24
25
  }
25
26
  if (usage === 'decrypt' && key.type === 'public') {
26
- throw new TypeError(`${is_key_like_js_1.types.join(' or ')} instances for asymmetric algorithm decryption must be of type "private"`);
27
+ throw new TypeError(`${tag(key)} instances for asymmetric algorithm decryption must be of type "private"`);
27
28
  }
28
29
  if (key.algorithm && usage === 'verify' && key.type === 'private') {
29
- throw new TypeError(`${is_key_like_js_1.types.join(' or ')} instances for asymmetric algorithm verifying must be of type "public"`);
30
+ throw new TypeError(`${tag(key)} instances for asymmetric algorithm verifying must be of type "public"`);
30
31
  }
31
32
  if (key.algorithm && usage === 'encrypt' && key.type === 'private') {
32
- throw new TypeError(`${is_key_like_js_1.types.join(' or ')} instances for asymmetric algorithm encryption must be of type "public"`);
33
+ throw new TypeError(`${tag(key)} instances for asymmetric algorithm encryption must be of type "public"`);
33
34
  }
34
35
  };
35
36
  const checkKeyType = (alg, key, usage) => {
@@ -5,6 +5,7 @@ const ECDH = require("../runtime/ecdhes.js");
5
5
  const pbes2kw_js_1 = require("../runtime/pbes2kw.js");
6
6
  const rsaes_js_1 = require("../runtime/rsaes.js");
7
7
  const base64url_js_1 = require("../runtime/base64url.js");
8
+ const normalize = require("../runtime/normalize_key.js");
8
9
  const errors_js_1 = require("../util/errors.js");
9
10
  const cek_js_1 = require("../lib/cek.js");
10
11
  const import_js_1 = require("../key/import.js");
@@ -12,6 +13,9 @@ const check_key_type_js_1 = require("./check_key_type.js");
12
13
  const is_object_js_1 = require("./is_object.js");
13
14
  const aesgcmkw_js_1 = require("./aesgcmkw.js");
14
15
  async function decryptKeyManagement(alg, key, encryptedKey, joseHeader, options) {
16
+ if (normalize.normalizePrivateKey) {
17
+ key = await normalize.normalizePrivateKey(key, alg);
18
+ }
15
19
  (0, check_key_type_js_1.default)(alg, key, 'decrypt');
16
20
  switch (alg) {
17
21
  case 'dir': {
@@ -5,6 +5,7 @@ const ECDH = require("../runtime/ecdhes.js");
5
5
  const pbes2kw_js_1 = require("../runtime/pbes2kw.js");
6
6
  const rsaes_js_1 = require("../runtime/rsaes.js");
7
7
  const base64url_js_1 = require("../runtime/base64url.js");
8
+ const normalize = require("../runtime/normalize_key.js");
8
9
  const cek_js_1 = require("../lib/cek.js");
9
10
  const errors_js_1 = require("../util/errors.js");
10
11
  const export_js_1 = require("../key/export.js");
@@ -14,6 +15,9 @@ async function encryptKeyManagement(alg, enc, key, providedCek, providedParamete
14
15
  let encryptedKey;
15
16
  let parameters;
16
17
  let cek;
18
+ if (normalize.normalizePublicKey) {
19
+ key = await normalize.normalizePublicKey(key, alg);
20
+ }
17
21
  (0, check_key_type_js_1.default)(alg, key, 'encrypt');
18
22
  switch (alg) {
19
23
  case 'dir': {
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -17,7 +17,7 @@ export { EncryptJWT } from './jwt/encrypt.js';
17
17
  export { calculateJwkThumbprint, calculateJwkThumbprintUri } from './jwk/thumbprint.js';
18
18
  export { EmbeddedJWK } from './jwk/embedded.js';
19
19
  export { createLocalJWKSet } from './jwks/local.js';
20
- export { createRemoteJWKSet } from './jwks/remote.js';
20
+ export { createRemoteJWKSet, experimental_jwksCache } from './jwks/remote.js';
21
21
  export { UnsecuredJWT } from './jwt/unsecured.js';
22
22
  export { exportPKCS8, exportSPKI, exportJWK } from './key/export.js';
23
23
  export { importSPKI, importPKCS8, importX509, importJWK } from './key/import.js';
@@ -1,6 +1,7 @@
1
1
  import fetchJwks from '../runtime/fetch_jwks.js';
2
2
  import { JWKSNoMatchingKey } from '../util/errors.js';
3
3
  import { createLocalJWKSet } from './local.js';
4
+ import isObject from '../lib/is_object.js';
4
5
  function isCloudflareWorkers() {
5
6
  return (typeof WebSocketPair !== 'undefined' ||
6
7
  (typeof navigator !== 'undefined' && navigator.userAgent === 'Cloudflare-Workers') ||
@@ -9,9 +10,25 @@ function isCloudflareWorkers() {
9
10
  let USER_AGENT;
10
11
  if (typeof navigator === 'undefined' || !navigator.userAgent?.startsWith?.('Mozilla/5.0 ')) {
11
12
  const NAME = 'jose';
12
- const VERSION = 'v5.4.1';
13
+ const VERSION = 'v5.6.0';
13
14
  USER_AGENT = `${NAME}/${VERSION}`;
14
15
  }
16
+ export const experimental_jwksCache = Symbol();
17
+ function isFreshJwksCache(input, cacheMaxAge) {
18
+ if (typeof input !== 'object' || input === null) {
19
+ return false;
20
+ }
21
+ if (!('uat' in input) || typeof input.uat !== 'number' || Date.now() - input.uat >= cacheMaxAge) {
22
+ return false;
23
+ }
24
+ if (!('jwks' in input) ||
25
+ !isObject(input.jwks) ||
26
+ !Array.isArray(input.jwks.keys) ||
27
+ !Array.prototype.every.call(input.jwks.keys, isObject)) {
28
+ return false;
29
+ }
30
+ return true;
31
+ }
15
32
  class RemoteJWKSet {
16
33
  _url;
17
34
  _timeoutDuration;
@@ -21,6 +38,7 @@ class RemoteJWKSet {
21
38
  _pendingFetch;
22
39
  _options;
23
40
  _local;
41
+ _cache;
24
42
  constructor(url, options) {
25
43
  if (!(url instanceof URL)) {
26
44
  throw new TypeError('url must be an instance of URL');
@@ -32,6 +50,13 @@ class RemoteJWKSet {
32
50
  this._cooldownDuration =
33
51
  typeof options?.cooldownDuration === 'number' ? options?.cooldownDuration : 30000;
34
52
  this._cacheMaxAge = typeof options?.cacheMaxAge === 'number' ? options?.cacheMaxAge : 600000;
53
+ if (options?.[experimental_jwksCache] !== undefined) {
54
+ this._cache = options?.[experimental_jwksCache];
55
+ if (isFreshJwksCache(options?.[experimental_jwksCache], this._cacheMaxAge)) {
56
+ this._jwksTimestamp = this._cache.uat;
57
+ this._local = createLocalJWKSet(this._cache.jwks);
58
+ }
59
+ }
35
60
  }
36
61
  coolingDown() {
37
62
  return typeof this._jwksTimestamp === 'number'
@@ -72,6 +97,10 @@ class RemoteJWKSet {
72
97
  this._pendingFetch ||= fetchJwks(this._url, this._timeoutDuration, this._options)
73
98
  .then((json) => {
74
99
  this._local = createLocalJWKSet(json);
100
+ if (this._cache) {
101
+ this._cache.uat = Date.now();
102
+ this._cache.jwks = json;
103
+ }
75
104
  this._jwksTimestamp = Date.now();
76
105
  this._pendingFetch = undefined;
77
106
  })
@@ -1,5 +1,6 @@
1
1
  import { withAlg as invalidKeyInput } from './invalid_key_input.js';
2
2
  import isKeyLike, { types } from '../runtime/is_key_like.js';
3
+ const tag = (key) => key?.[Symbol.toStringTag];
3
4
  const symmetricTypeCheck = (alg, key) => {
4
5
  if (key instanceof Uint8Array)
5
6
  return;
@@ -7,7 +8,7 @@ const symmetricTypeCheck = (alg, key) => {
7
8
  throw new TypeError(invalidKeyInput(alg, key, ...types, 'Uint8Array'));
8
9
  }
9
10
  if (key.type !== 'secret') {
10
- throw new TypeError(`${types.join(' or ')} instances for symmetric algorithms must be of type "secret"`);
11
+ throw new TypeError(`${tag(key)} instances for symmetric algorithms must be of type "secret"`);
11
12
  }
12
13
  };
13
14
  const asymmetricTypeCheck = (alg, key, usage) => {
@@ -15,19 +16,19 @@ const asymmetricTypeCheck = (alg, key, usage) => {
15
16
  throw new TypeError(invalidKeyInput(alg, key, ...types));
16
17
  }
17
18
  if (key.type === 'secret') {
18
- throw new TypeError(`${types.join(' or ')} instances for asymmetric algorithms must not be of type "secret"`);
19
+ throw new TypeError(`${tag(key)} instances for asymmetric algorithms must not be of type "secret"`);
19
20
  }
20
21
  if (usage === 'sign' && key.type === 'public') {
21
- throw new TypeError(`${types.join(' or ')} instances for asymmetric algorithm signing must be of type "private"`);
22
+ throw new TypeError(`${tag(key)} instances for asymmetric algorithm signing must be of type "private"`);
22
23
  }
23
24
  if (usage === 'decrypt' && key.type === 'public') {
24
- throw new TypeError(`${types.join(' or ')} instances for asymmetric algorithm decryption must be of type "private"`);
25
+ throw new TypeError(`${tag(key)} instances for asymmetric algorithm decryption must be of type "private"`);
25
26
  }
26
27
  if (key.algorithm && usage === 'verify' && key.type === 'private') {
27
- throw new TypeError(`${types.join(' or ')} instances for asymmetric algorithm verifying must be of type "public"`);
28
+ throw new TypeError(`${tag(key)} instances for asymmetric algorithm verifying must be of type "public"`);
28
29
  }
29
30
  if (key.algorithm && usage === 'encrypt' && key.type === 'private') {
30
- throw new TypeError(`${types.join(' or ')} instances for asymmetric algorithm encryption must be of type "public"`);
31
+ throw new TypeError(`${tag(key)} instances for asymmetric algorithm encryption must be of type "public"`);
31
32
  }
32
33
  };
33
34
  const checkKeyType = (alg, key, usage) => {
@@ -3,6 +3,7 @@ import * as ECDH from '../runtime/ecdhes.js';
3
3
  import { decrypt as pbes2Kw } from '../runtime/pbes2kw.js';
4
4
  import { decrypt as rsaEs } from '../runtime/rsaes.js';
5
5
  import { decode as base64url } from '../runtime/base64url.js';
6
+ import * as normalize from '../runtime/normalize_key.js';
6
7
  import { JOSENotSupported, JWEInvalid } from '../util/errors.js';
7
8
  import { bitLength as cekLength } from '../lib/cek.js';
8
9
  import { importJWK } from '../key/import.js';
@@ -10,6 +11,9 @@ import checkKeyType from './check_key_type.js';
10
11
  import isObject from './is_object.js';
11
12
  import { unwrap as aesGcmKw } from './aesgcmkw.js';
12
13
  async function decryptKeyManagement(alg, key, encryptedKey, joseHeader, options) {
14
+ if (normalize.normalizePrivateKey) {
15
+ key = await normalize.normalizePrivateKey(key, alg);
16
+ }
13
17
  checkKeyType(alg, key, 'decrypt');
14
18
  switch (alg) {
15
19
  case 'dir': {
@@ -3,6 +3,7 @@ import * as ECDH from '../runtime/ecdhes.js';
3
3
  import { encrypt as pbes2Kw } from '../runtime/pbes2kw.js';
4
4
  import { encrypt as rsaEs } from '../runtime/rsaes.js';
5
5
  import { encode as base64url } from '../runtime/base64url.js';
6
+ import * as normalize from '../runtime/normalize_key.js';
6
7
  import generateCek, { bitLength as cekLength } from '../lib/cek.js';
7
8
  import { JOSENotSupported } from '../util/errors.js';
8
9
  import { exportJWK } from '../key/export.js';
@@ -12,6 +13,9 @@ async function encryptKeyManagement(alg, enc, key, providedCek, providedParamete
12
13
  let encryptedKey;
13
14
  let parameters;
14
15
  let cek;
16
+ if (normalize.normalizePublicKey) {
17
+ key = await normalize.normalizePublicKey(key, alg);
18
+ }
15
19
  checkKeyType(alg, key, 'encrypt');
16
20
  switch (alg) {
17
21
  case 'dir': {
@@ -0,0 +1 @@
1
+ export {};
@@ -28,8 +28,8 @@ export { EncryptJWT } from './jwt/encrypt';
28
28
  export { calculateJwkThumbprint, calculateJwkThumbprintUri } from './jwk/thumbprint';
29
29
  export { EmbeddedJWK } from './jwk/embedded';
30
30
  export { createLocalJWKSet } from './jwks/local';
31
- export { createRemoteJWKSet } from './jwks/remote';
32
- export type { RemoteJWKSetOptions } from './jwks/remote';
31
+ export { createRemoteJWKSet, experimental_jwksCache } from './jwks/remote';
32
+ export type { RemoteJWKSetOptions, JWKSCacheInput, ExportedJWKSCache } from './jwks/remote';
33
33
  export { UnsecuredJWT } from './jwt/unsecured';
34
34
  export type { UnsecuredResult } from './jwt/unsecured';
35
35
  export { exportPKCS8, exportSPKI, exportJWK } from './key/export';
@@ -1,4 +1,58 @@
1
1
  import type { KeyLike, JWSHeaderParameters, FlattenedJWSInput, JSONWebKeySet } from '../types';
2
+ /**
3
+ * This is an experimental feature, it is not subject to semantic versioning rules. Non-backward
4
+ * compatible changes or removal may occur in any future release.
5
+ *
6
+ * DANGER ZONE - This option has security implications that must be understood, assessed for
7
+ * applicability, and accepted before use. It is critical that the JSON Web Key Set cache only be
8
+ * writable by your own code.
9
+ *
10
+ * This option is intended for cloud computing runtimes that cannot keep an in memory cache between
11
+ * their code's invocations. Use in runtimes where an in memory cache between requests is available
12
+ * is not desirable.
13
+ *
14
+ * When passed to {@link jwks/remote.createRemoteJWKSet createRemoteJWKSet} this allows the passed in
15
+ * object to:
16
+ *
17
+ * - Serve as an initial value for the JSON Web Key Set that the module would otherwise need to
18
+ * trigger an HTTP request for
19
+ * - Have the JSON Web Key Set the function optionally ended up triggering an HTTP request for
20
+ * assigned to it as properties
21
+ *
22
+ * The intended use pattern is:
23
+ *
24
+ * - Before verifying with {@link jwks/remote.createRemoteJWKSet createRemoteJWKSet} you pull the
25
+ * previously cached object from a low-latency key-value store offered by the cloud computing
26
+ * runtime it is executed on;
27
+ * - Default to an empty object `{}` instead when there's no previously cached value;
28
+ * - Pass it in as {@link RemoteJWKSetOptions[experimental_jwksCache]};
29
+ * - Afterwards, update the key-value storage if the {@link ExportedJWKSCache.uat `uat`} property of
30
+ * the object has changed.
31
+ *
32
+ * import * as jose from 'jose'
33
+ *
34
+ * // Prerequisites
35
+ * let url!: URL
36
+ * let jwt!: string
37
+ *
38
+ * // Load JSON Web Key Set cache
39
+ * const jwksCache: jose.JWKSCacheInput = (await getPreviouslyCachedJWKS()) || {}
40
+ * const { uat } = jwksCache
41
+ *
42
+ * const JWKS = jose.createRemoteJWKSet(url, {
43
+ * [jose.experimental_jwksCache]: jwksCache,
44
+ * })
45
+ *
46
+ * // Use JSON Web Key Set cache
47
+ * await jose.jwtVerify(jwt, JWKS)
48
+ *
49
+ * if (uat !== jwksCache.uat) {
50
+ * // Update JSON Web Key Set cache
51
+ * await storeNewJWKScache(jwksCache)
52
+ * }
53
+ * ```
54
+ */
55
+ export declare const experimental_jwksCache: unique symbol;
2
56
  /** Options for the remote JSON Web Key Set. */
3
57
  export interface RemoteJWKSetOptions {
4
58
  /**
@@ -31,7 +85,14 @@ export interface RemoteJWKSetOptions {
31
85
  * configuration would cause an unnecessary CORS preflight request.
32
86
  */
33
87
  headers?: Record<string, string>;
88
+ /** See {@link experimental_jwksCache}. */
89
+ [experimental_jwksCache]?: JWKSCacheInput;
90
+ }
91
+ export interface ExportedJWKSCache {
92
+ jwks: JSONWebKeySet;
93
+ uat: number;
34
94
  }
95
+ export type JWKSCacheInput = ExportedJWKSCache | Record<string, never>;
35
96
  /**
36
97
  * Returns a function that resolves a JWS JOSE Header to a public key object downloaded from a
37
98
  * remote endpoint returning a JSON Web Key Set, that is, for example, an OAuth 2.0 or OIDC
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jose",
3
- "version": "5.4.1",
3
+ "version": "5.6.0",
4
4
  "description": "JWA, JWS, JWE, JWT, JWK, JWKS for Node.js, Browser, Cloudflare Workers, Deno, Bun, and other Web-interoperable runtimes",
5
5
  "keywords": [
6
6
  "browser",