jose 4.3.8 → 4.5.1

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
@@ -42,10 +42,10 @@ import * as jose from 'https://deno.land/x/jose/index.ts'
42
42
 
43
43
  - JSON Web Tokens (JWT)
44
44
  - [Signing](docs/classes/jwt_sign.SignJWT.md#readme)
45
- - [Verification & Claims Set Validation](docs/functions/jwt_verify.jwtVerify.md#readme)
45
+ - [Verification & JWT Claims Set Validation](docs/functions/jwt_verify.jwtVerify.md#readme)
46
46
  - Encrypted JSON Web Tokens
47
47
  - [Encryption](docs/classes/jwt_encrypt.EncryptJWT.md#readme)
48
- - [Decryption & Claims Set Validation](docs/functions/jwt_decrypt.jwtDecrypt.md#readme)
48
+ - [Decryption & JWT Claims Set Validation](docs/functions/jwt_decrypt.jwtDecrypt.md#readme)
49
49
  - Key Import
50
50
  - [JWK Import](docs/functions/key_import.importJWK.md#readme)
51
51
  - [Public Key Import (SPKI)](docs/functions/key_import.importSPKI.md#readme)
@@ -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)
@@ -71,6 +72,7 @@ import * as jose from 'https://deno.land/x/jose/index.ts'
71
72
  - [Public Key Export](docs/functions/key_export.exportSPKI.md#readme)
72
73
  - Utilities
73
74
  - [Decoding Token's Protected Header](docs/functions/util_decode_protected_header.decodeProtectedHeader.md#readme)
75
+ - [Decoding JWT Claims Set](docs/functions/util_decode_jwt.decodeJwt.md#readme)
74
76
  - [Unsecured JWT](docs/classes/jwt_unsecured.UnsecuredJWT.md#readme)
75
77
  - [JOSE Errors](docs/modules/util_errors.md#readme)
76
78
 
@@ -89,7 +91,7 @@ import * as jose from 'https://deno.land/x/jose/index.ts'
89
91
 
90
92
  | Version | Security Fixes 🔑 | Other Bug Fixes 🐞 | New Features ⭐ |
91
93
  | ------- | --------- | -------- | -------- |
92
- | [4.x.x](https://github.com/panva/jose) | ✅ | ✅ | ✅ |
94
+ | [4.x.x](https://github.com/panva/jose/tree/v4.x) | ✅ | ✅ | ✅ |
93
95
  | [3.x.x](https://github.com/panva/jose/tree/v3.x) | ✅ | ✅ until 2022-04-30 | ❌ |
94
96
  | [2.x.x](https://github.com/panva/jose/tree/v2.x) | ✅ | ✅ until 2022-04-30 | ❌ |
95
97
  | [1.x.x](https://github.com/panva/jose/tree/v1.x) | ✅ | ❌ | ❌ |
@@ -134,16 +136,16 @@ install size should not be a cause for concern.
134
136
 
135
137
  [documentation]: /docs/README.md
136
138
  [node-jose]: https://github.com/cisco/node-jose
137
- [spec-b64]: https://tools.ietf.org/html/rfc7797
138
- [spec-cookbook]: https://tools.ietf.org/html/rfc7520
139
- [spec-jwa]: https://tools.ietf.org/html/rfc7518
140
- [spec-jwe]: https://tools.ietf.org/html/rfc7516
141
- [spec-jwk]: https://tools.ietf.org/html/rfc7517
142
- [spec-jws]: https://tools.ietf.org/html/rfc7515
143
- [spec-jwt]: https://tools.ietf.org/html/rfc7519
144
- [spec-okp]: https://tools.ietf.org/html/rfc8037
145
- [spec-secp256k1]: https://tools.ietf.org/html/rfc8812
146
- [spec-thumbprint]: https://tools.ietf.org/html/rfc7638
139
+ [spec-b64]: https://www.rfc-editor.org/rfc/rfc7797
140
+ [spec-cookbook]: https://www.rfc-editor.org/rfc/rfc7520
141
+ [spec-jwa]: https://www.rfc-editor.org/rfc/rfc7518
142
+ [spec-jwe]: https://www.rfc-editor.org/rfc/rfc7516
143
+ [spec-jwk]: https://www.rfc-editor.org/rfc/rfc7517
144
+ [spec-jws]: https://www.rfc-editor.org/rfc/rfc7515
145
+ [spec-jwt]: https://www.rfc-editor.org/rfc/rfc7519
146
+ [spec-okp]: https://www.rfc-editor.org/rfc/rfc8037
147
+ [spec-secp256k1]: https://www.rfc-editor.org/rfc/rfc8812
148
+ [spec-thumbprint]: https://www.rfc-editor.org/rfc/rfc7638
147
149
  [support-sponsor]: https://github.com/sponsors/panva
148
150
  [conditional-exports]: https://nodejs.org/api/packages.html#packages_conditional_exports
149
151
  [webcrypto]: https://www.w3.org/TR/WebCryptoAPI/
@@ -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
  import * as errors_1 from './util/errors.js';
25
27
  export { errors_1 as errors };
26
28
  export { generateKeyPair } from './key/generate_key_pair.js';
@@ -12,6 +12,11 @@ export async function calculateJwkThumbprint(jwk, digestAlgorithm = 'sha256') {
12
12
  if (!isObject(jwk)) {
13
13
  throw new TypeError('JWK must be an object');
14
14
  }
15
+ if (digestAlgorithm !== 'sha256' &&
16
+ digestAlgorithm !== 'sha384' &&
17
+ digestAlgorithm !== 'sha512') {
18
+ throw new TypeError('digestAlgorithm must one of "sha256", "sha384", or "sha512"');
19
+ }
15
20
  let components;
16
21
  switch (jwk.kty) {
17
22
  case 'EC':
@@ -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 { 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 { 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
- this._cached = new WeakMap();
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
- 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();
29
+ try {
30
+ return await super.getKey(protectedHeader, token);
94
31
  }
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');
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
- cached[joseHeader.alg] = keyObject;
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 (typeof json !== 'object' ||
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 };
@@ -1,3 +1,4 @@
1
+ import digest from '../runtime/digest.js';
1
2
  export const encoder = new TextEncoder();
2
3
  export const decoder = new TextDecoder();
3
4
  const MAX_INT32 = 2 ** 32;
@@ -36,7 +37,7 @@ export function uint32be(value) {
36
37
  export function lengthAndInput(input) {
37
38
  return concat(uint32be(input.length), input);
38
39
  }
39
- export async function concatKdf(digest, secret, bits, value) {
40
+ export async function concatKdf(secret, bits, value) {
40
41
  const iterations = Math.ceil((bits >> 3) / 32);
41
42
  let res;
42
43
  for (let iter = 1; iter <= iterations; iter++) {
@@ -30,7 +30,9 @@ async function encryptKeyManagement(alg, enc, key, providedCek, providedParamete
30
30
  ephemeralKey || (ephemeralKey = (await ECDH.generateEpk(key)).privateKey);
31
31
  const { x, y, crv, kty } = await exportJWK(ephemeralKey);
32
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, y, crv, kty } };
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)
@@ -1,7 +1,6 @@
1
1
  import { encoder, concat, uint32be, lengthAndInput, concatKdf } from '../lib/buffer_utils.js';
2
2
  import crypto, { isCryptoKey } from './webcrypto.js';
3
3
  import { checkEncCryptoKey } from '../lib/crypto_key.js';
4
- import digest from './digest.js';
5
4
  import invalidKeyInput from '../lib/invalid_key_input.js';
6
5
  import { types } from './is_key_like.js';
7
6
  export async function deriveKey(publicKey, privateKey, algorithm, keyLength, apu = new Uint8Array(0), apv = new Uint8Array(0)) {
@@ -21,7 +20,7 @@ export async function deriveKey(publicKey, privateKey, algorithm, keyLength, apu
21
20
  name: 'ECDH',
22
21
  public: publicKey,
23
22
  }, privateKey, Math.ceil(parseInt(privateKey.algorithm.namedCurve.slice(-3), 10) / 8) << 3));
24
- return concatKdf(digest, sharedSecret, keyLength, value);
23
+ return concatKdf(sharedSecret, keyLength, value);
25
24
  }
26
25
  export async function generateEpk(key) {
27
26
  if (!isCryptoKey(key)) {
@@ -0,0 +1,32 @@
1
+ import { decode as base64url } from './base64url.js';
2
+ import { decoder } from '../lib/buffer_utils.js';
3
+ import isObject from '../lib/is_object.js';
4
+ import { JWTInvalid } from './errors.js';
5
+ export function decodeJwt(jwt) {
6
+ if (typeof jwt !== 'string')
7
+ throw new JWTInvalid('JWTs must use Compact JWS serialization, JWT must be a string');
8
+ const { 1: payload, length } = jwt.split('.');
9
+ if (length === 5)
10
+ throw new JWTInvalid('Only JWTs using Compact JWS serialization can be decoded');
11
+ if (length !== 3)
12
+ throw new JWTInvalid('Invalid JWT');
13
+ if (!payload)
14
+ throw new JWTInvalid('JWTs must contain a payload');
15
+ let decoded;
16
+ try {
17
+ decoded = base64url(payload);
18
+ }
19
+ catch (_a) {
20
+ throw new JWTInvalid('Failed to parse the base64url encoded payload');
21
+ }
22
+ let result;
23
+ try {
24
+ result = JSON.parse(decoder.decode(decoded));
25
+ }
26
+ catch (_b) {
27
+ throw new JWTInvalid('Failed to parse the decoded payload as JSON');
28
+ }
29
+ if (!isObject(result))
30
+ throw new JWTInvalid('Invalid JWT Claims Set');
31
+ return result;
32
+ }
@@ -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.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.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");
@@ -52,6 +54,8 @@ Object.defineProperty(exports, "importX509", { enumerable: true, get: function (
52
54
  Object.defineProperty(exports, "importJWK", { enumerable: true, get: function () { return import_js_1.importJWK; } });
53
55
  var decode_protected_header_js_1 = require("./util/decode_protected_header.js");
54
56
  Object.defineProperty(exports, "decodeProtectedHeader", { enumerable: true, get: function () { return decode_protected_header_js_1.decodeProtectedHeader; } });
57
+ var decode_jwt_js_1 = require("./util/decode_jwt.js");
58
+ Object.defineProperty(exports, "decodeJwt", { enumerable: true, get: function () { return decode_jwt_js_1.decodeJwt; } });
55
59
  exports.errors = require("./util/errors.js");
56
60
  var generate_key_pair_js_1 = require("./key/generate_key_pair.js");
57
61
  Object.defineProperty(exports, "generateKeyPair", { enumerable: true, get: function () { return generate_key_pair_js_1.generateKeyPair; } });
@@ -15,6 +15,11 @@ async function calculateJwkThumbprint(jwk, digestAlgorithm = 'sha256') {
15
15
  if (!(0, is_object_js_1.default)(jwk)) {
16
16
  throw new TypeError('JWK must be an object');
17
17
  }
18
+ if (digestAlgorithm !== 'sha256' &&
19
+ digestAlgorithm !== 'sha384' &&
20
+ digestAlgorithm !== 'sha512') {
21
+ throw new TypeError('digestAlgorithm must one of "sha256", "sha384", or "sha512"');
22
+ }
18
23
  let components;
19
24
  switch (jwk.kty) {
20
25
  case 'EC':
@@ -0,0 +1,103 @@
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 { alg, kid } = { ...protectedHeader, ...token.header };
46
+ const candidates = this._jwks.keys.filter((jwk) => {
47
+ let candidate = jwk.kty === getKtyFromAlg(alg);
48
+ if (candidate && typeof kid === 'string') {
49
+ candidate = kid === jwk.kid;
50
+ }
51
+ if (candidate && typeof jwk.alg === 'string') {
52
+ candidate = alg === jwk.alg;
53
+ }
54
+ if (candidate && typeof jwk.use === 'string') {
55
+ candidate = jwk.use === 'sig';
56
+ }
57
+ if (candidate && Array.isArray(jwk.key_ops)) {
58
+ candidate = jwk.key_ops.includes('verify');
59
+ }
60
+ if (candidate && alg === 'EdDSA') {
61
+ candidate = jwk.crv === 'Ed25519' || jwk.crv === 'Ed448';
62
+ }
63
+ if (candidate) {
64
+ switch (alg) {
65
+ case 'ES256':
66
+ candidate = jwk.crv === 'P-256';
67
+ break;
68
+ case 'ES256K':
69
+ candidate = jwk.crv === 'secp256k1';
70
+ break;
71
+ case 'ES384':
72
+ candidate = jwk.crv === 'P-384';
73
+ break;
74
+ case 'ES512':
75
+ candidate = jwk.crv === 'P-521';
76
+ break;
77
+ }
78
+ }
79
+ return candidate;
80
+ });
81
+ const { 0: jwk, length } = candidates;
82
+ if (length === 0) {
83
+ throw new errors_js_1.JWKSNoMatchingKey();
84
+ }
85
+ else if (length !== 1) {
86
+ throw new errors_js_1.JWKSMultipleMatchingKeys();
87
+ }
88
+ const cached = this._cached.get(jwk) || this._cached.set(jwk, {}).get(jwk);
89
+ if (cached[alg] === undefined) {
90
+ const keyObject = await (0, import_js_1.importJWK)({ ...jwk, ext: true }, alg);
91
+ if (keyObject instanceof Uint8Array || keyObject.type !== 'public') {
92
+ throw new errors_js_1.JWKSInvalid('JSON Web Key Set members must be public keys');
93
+ }
94
+ cached[alg] = keyObject;
95
+ }
96
+ return cached[alg];
97
+ }
98
+ }
99
+ exports.LocalJWKSet = LocalJWKSet;
100
+ function createLocalJWKSet(jwks) {
101
+ return LocalJWKSet.prototype.getKey.bind(new LocalJWKSet(jwks));
102
+ }
103
+ exports.createLocalJWKSet = createLocalJWKSet;
@@ -2,28 +2,13 @@
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");
5
+ const env_js_1 = require("../runtime/env.js");
6
6
  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 {
7
+ const local_js_1 = require("./local.js");
8
+ class RemoteJWKSet extends local_js_1.LocalJWKSet {
25
9
  constructor(url, options) {
26
- this._cached = new WeakMap();
10
+ super({ keys: [] });
11
+ this._jwks = undefined;
27
12
  if (!(url instanceof URL)) {
28
13
  throw new TypeError('url must be an instance of URL');
29
14
  }
@@ -41,78 +26,40 @@ class RemoteJWKSet {
41
26
  return Date.now() < this._cooldownStarted + this._cooldownDuration;
42
27
  }
43
28
  async getKey(protectedHeader, token) {
44
- const joseHeader = {
45
- ...protectedHeader,
46
- ...token.header,
47
- };
48
29
  if (!this._jwks) {
49
30
  await this.reload();
50
31
  }
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();
32
+ try {
33
+ return await super.getKey(protectedHeader, token);
97
34
  }
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');
35
+ catch (err) {
36
+ if (err instanceof errors_js_1.JWKSNoMatchingKey) {
37
+ if (this.coolingDown() === false) {
38
+ await this.reload();
39
+ return super.getKey(protectedHeader, token);
40
+ }
103
41
  }
104
- cached[joseHeader.alg] = keyObject;
42
+ throw err;
105
43
  }
106
- return cached[joseHeader.alg];
107
44
  }
108
45
  async reload() {
46
+ if (this._pendingFetch && (0, env_js_1.isCloudflareWorkers)()) {
47
+ return new Promise((resolve) => {
48
+ const isDone = () => {
49
+ if (this._pendingFetch === undefined) {
50
+ resolve();
51
+ }
52
+ else {
53
+ setTimeout(isDone, 5);
54
+ }
55
+ };
56
+ isDone();
57
+ });
58
+ }
109
59
  if (!this._pendingFetch) {
110
60
  this._pendingFetch = (0, fetch_jwks_js_1.default)(this._url, this._timeoutDuration, this._options)
111
61
  .then((json) => {
112
- if (typeof json !== 'object' ||
113
- !json ||
114
- !Array.isArray(json.keys) ||
115
- !json.keys.every(isJWKLike)) {
62
+ if (!(0, local_js_1.isJWKSLike)(json)) {
116
63
  throw new errors_js_1.JWKSInvalid('JSON Web Key Set malformed');
117
64
  }
118
65
  this._jwks = { keys: json.keys };
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.concatKdf = exports.lengthAndInput = exports.uint32be = exports.uint64be = exports.p2s = exports.concat = exports.decoder = exports.encoder = void 0;
4
+ const digest_js_1 = require("../runtime/digest.js");
4
5
  exports.encoder = new TextEncoder();
5
6
  exports.decoder = new TextDecoder();
6
7
  const MAX_INT32 = 2 ** 32;
@@ -44,7 +45,7 @@ function lengthAndInput(input) {
44
45
  return concat(uint32be(input.length), input);
45
46
  }
46
47
  exports.lengthAndInput = lengthAndInput;
47
- async function concatKdf(digest, secret, bits, value) {
48
+ async function concatKdf(secret, bits, value) {
48
49
  const iterations = Math.ceil((bits >> 3) / 32);
49
50
  let res;
50
51
  for (let iter = 1; iter <= iterations; iter++) {
@@ -53,10 +54,10 @@ async function concatKdf(digest, secret, bits, value) {
53
54
  buf.set(secret, 4);
54
55
  buf.set(value, 4 + secret.length);
55
56
  if (!res) {
56
- res = await digest('sha256', buf);
57
+ res = await (0, digest_js_1.default)('sha256', buf);
57
58
  }
58
59
  else {
59
- res = concat(res, await digest('sha256', buf));
60
+ res = concat(res, await (0, digest_js_1.default)('sha256', buf));
60
61
  }
61
62
  }
62
63
  res = res.slice(0, bits >> 3);
@@ -32,7 +32,9 @@ async function encryptKeyManagement(alg, enc, key, providedCek, providedParamete
32
32
  ephemeralKey || (ephemeralKey = (await ECDH.generateEpk(key)).privateKey);
33
33
  const { x, y, crv, kty } = await (0, export_js_1.exportJWK)(ephemeralKey);
34
34
  const sharedSecret = await ECDH.deriveKey(key, ephemeralKey, alg === 'ECDH-ES' ? enc : alg, alg === 'ECDH-ES' ? (0, cek_js_1.bitLength)(enc) : parseInt(alg.slice(-5, -2), 10), apu, apv);
35
- parameters = { epk: { x, y, crv, kty } };
35
+ parameters = { epk: { x, crv, kty } };
36
+ if (kty === 'EC')
37
+ parameters.epk.y = y;
36
38
  if (apu)
37
39
  parameters.apu = (0, base64url_js_1.encode)(apu);
38
40
  if (apv)
@@ -3,7 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.decode = exports.encode = exports.encodeBase64 = exports.decodeBase64 = void 0;
4
4
  const buffer_1 = require("buffer");
5
5
  const buffer_utils_js_1 = require("../lib/buffer_utils.js");
6
- let encodeImpl;
6
+ let encode;
7
+ exports.encode = encode;
7
8
  function normalize(input) {
8
9
  let encoded = input;
9
10
  if (encoded instanceof Uint8Array) {
@@ -12,15 +13,14 @@ function normalize(input) {
12
13
  return encoded;
13
14
  }
14
15
  if (buffer_1.Buffer.isEncoding('base64url')) {
15
- encodeImpl = (input) => buffer_1.Buffer.from(input).toString('base64url');
16
+ exports.encode = encode = (input) => buffer_1.Buffer.from(input).toString('base64url');
16
17
  }
17
18
  else {
18
- encodeImpl = (input) => buffer_1.Buffer.from(input).toString('base64').replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
19
+ exports.encode = encode = (input) => buffer_1.Buffer.from(input).toString('base64').replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
19
20
  }
20
21
  const decodeBase64 = (input) => buffer_1.Buffer.from(input, 'base64');
21
22
  exports.decodeBase64 = decodeBase64;
22
23
  const encodeBase64 = (input) => buffer_1.Buffer.from(input).toString('base64');
23
24
  exports.encodeBase64 = encodeBase64;
24
- exports.encode = encodeImpl;
25
25
  const decode = (input) => buffer_1.Buffer.from(normalize(input), 'base64');
26
26
  exports.decode = decode;
@@ -5,7 +5,6 @@ const crypto_1 = require("crypto");
5
5
  const util_1 = require("util");
6
6
  const get_named_curve_js_1 = require("./get_named_curve.js");
7
7
  const buffer_utils_js_1 = require("../lib/buffer_utils.js");
8
- const digest_js_1 = require("./digest.js");
9
8
  const errors_js_1 = require("../util/errors.js");
10
9
  const webcrypto_js_1 = require("./webcrypto.js");
11
10
  const crypto_key_js_1 = require("../lib/crypto_key.js");
@@ -38,7 +37,7 @@ async function deriveKey(publicKee, privateKee, algorithm, keyLength, apu = new
38
37
  }
39
38
  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
39
  const sharedSecret = (0, crypto_1.diffieHellman)({ privateKey, publicKey });
41
- return (0, buffer_utils_js_1.concatKdf)(digest_js_1.default, sharedSecret, keyLength, value);
40
+ return (0, buffer_utils_js_1.concatKdf)(sharedSecret, keyLength, value);
42
41
  }
43
42
  exports.deriveKey = deriveKey;
44
43
  async function generateEpk(kee) {
@@ -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;
@@ -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';
@@ -12,6 +12,11 @@ export async function calculateJwkThumbprint(jwk, digestAlgorithm = 'sha256') {
12
12
  if (!isObject(jwk)) {
13
13
  throw new TypeError('JWK must be an object');
14
14
  }
15
+ if (digestAlgorithm !== 'sha256' &&
16
+ digestAlgorithm !== 'sha384' &&
17
+ digestAlgorithm !== 'sha512') {
18
+ throw new TypeError('digestAlgorithm must one of "sha256", "sha384", or "sha512"');
19
+ }
15
20
  let components;
16
21
  switch (jwk.kty) {
17
22
  case 'EC':
@@ -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 { 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 { 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
- this._cached = new WeakMap();
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
- 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();
29
+ try {
30
+ return await super.getKey(protectedHeader, token);
94
31
  }
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');
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
- cached[joseHeader.alg] = keyObject;
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 (typeof json !== 'object' ||
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 };
@@ -1,3 +1,4 @@
1
+ import digest from '../runtime/digest.js';
1
2
  export const encoder = new TextEncoder();
2
3
  export const decoder = new TextDecoder();
3
4
  const MAX_INT32 = 2 ** 32;
@@ -36,7 +37,7 @@ export function uint32be(value) {
36
37
  export function lengthAndInput(input) {
37
38
  return concat(uint32be(input.length), input);
38
39
  }
39
- export async function concatKdf(digest, secret, bits, value) {
40
+ export async function concatKdf(secret, bits, value) {
40
41
  const iterations = Math.ceil((bits >> 3) / 32);
41
42
  let res;
42
43
  for (let iter = 1; iter <= iterations; iter++) {
@@ -30,7 +30,9 @@ async function encryptKeyManagement(alg, enc, key, providedCek, providedParamete
30
30
  ephemeralKey || (ephemeralKey = (await ECDH.generateEpk(key)).privateKey);
31
31
  const { x, y, crv, kty } = await exportJWK(ephemeralKey);
32
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, y, crv, kty } };
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)
@@ -1,6 +1,6 @@
1
1
  import { Buffer } from 'buffer';
2
2
  import { decoder } from '../lib/buffer_utils.js';
3
- let encodeImpl;
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
- encodeImpl = (input) => Buffer.from(input).toString('base64url');
12
+ encode = (input) => Buffer.from(input).toString('base64url');
13
13
  }
14
14
  else {
15
- encodeImpl = (input) => Buffer.from(input).toString('base64').replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
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 const encode = encodeImpl;
19
+ export { encode };
20
20
  export const decode = (input) => Buffer.from(normalize(input), 'base64');
@@ -2,7 +2,6 @@ import { diffieHellman, generateKeyPair as generateKeyPairCb, KeyObject } from '
2
2
  import { promisify } from 'util';
3
3
  import getNamedCurve from './get_named_curve.js';
4
4
  import { encoder, concat, uint32be, lengthAndInput, concatKdf } from '../lib/buffer_utils.js';
5
- import digest from './digest.js';
6
5
  import { JOSENotSupported } from '../util/errors.js';
7
6
  import { isCryptoKey } from './webcrypto.js';
8
7
  import { checkEncCryptoKey } from '../lib/crypto_key.js';
@@ -35,7 +34,7 @@ export async function deriveKey(publicKee, privateKee, algorithm, keyLength, apu
35
34
  }
36
35
  const value = concat(lengthAndInput(encoder.encode(algorithm)), lengthAndInput(apu), lengthAndInput(apv), uint32be(keyLength));
37
36
  const sharedSecret = diffieHellman({ privateKey, publicKey });
38
- return concatKdf(digest, sharedSecret, keyLength, value);
37
+ return concatKdf(sharedSecret, keyLength, value);
39
38
  }
40
39
  export async function generateEpk(kee) {
41
40
  let key;
@@ -0,0 +1,32 @@
1
+ import { decode as base64url } from './base64url.js';
2
+ import { decoder } from '../lib/buffer_utils.js';
3
+ import isObject from '../lib/is_object.js';
4
+ import { JWTInvalid } from './errors.js';
5
+ export function decodeJwt(jwt) {
6
+ if (typeof jwt !== 'string')
7
+ throw new JWTInvalid('JWTs must use Compact JWS serialization, JWT must be a string');
8
+ const { 1: payload, length } = jwt.split('.');
9
+ if (length === 5)
10
+ throw new JWTInvalid('Only JWTs using Compact JWS serialization can be decoded');
11
+ if (length !== 3)
12
+ throw new JWTInvalid('Invalid JWT');
13
+ if (!payload)
14
+ throw new JWTInvalid('JWTs must contain a payload');
15
+ let decoded;
16
+ try {
17
+ decoded = base64url(payload);
18
+ }
19
+ catch {
20
+ throw new JWTInvalid('Failed to parse the base64url encoded payload');
21
+ }
22
+ let result;
23
+ try {
24
+ result = JSON.parse(decoder.decode(decoded));
25
+ }
26
+ catch {
27
+ throw new JWTInvalid('Failed to parse the decoded payload as JSON');
28
+ }
29
+ if (!isObject(result))
30
+ throw new JWTInvalid('Invalid JWT Claims Set');
31
+ return result;
32
+ }
@@ -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';
@@ -35,6 +36,7 @@ export { exportPKCS8, exportSPKI, exportJWK } from './key/export';
35
36
  export { importSPKI, importPKCS8, importX509, importJWK } from './key/import';
36
37
  export type { PEMImportOptions } from './key/import';
37
38
  export { decodeProtectedHeader } from './util/decode_protected_header';
39
+ export { decodeJwt } from './util/decode_jwt';
38
40
  export type { ProtectedHeaderParameters } from './util/decode_protected_header';
39
41
  export * as errors from './util/errors';
40
42
  export { generateKeyPair } from './key/generate_key_pair';
@@ -42,4 +44,4 @@ export type { GenerateKeyPairResult, GenerateKeyPairOptions } from './key/genera
42
44
  export { generateSecret } from './key/generate_secret';
43
45
  export type { GenerateSecretOptions } from './key/generate_secret';
44
46
  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';
47
+ 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>;
@@ -118,7 +118,7 @@ export interface JWK {
118
118
  [propName: string]: unknown
119
119
  }
120
120
  export interface GetKeyFunction<T, T2> {
121
- (protectedHeader: T, token: T2): Promise<KeyLike | Uint8Array>
121
+ (protectedHeader: T, token: T2): Promise<KeyLike | Uint8Array> | KeyLike | Uint8Array
122
122
  }
123
123
  export interface FlattenedJWSInput {
124
124
  header?: JWSHeaderParameters
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ import type { JWTPayload } from '../types';
2
+ export declare function decodeJwt(jwt: string): JWTPayload;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jose",
3
- "version": "4.3.8",
3
+ "version": "4.5.1",
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",