jose 4.3.7 → 4.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +5 -3
  2. package/dist/browser/index.js +2 -0
  3. package/dist/browser/jwks/local.js +97 -0
  4. package/dist/browser/jwks/remote.js +29 -82
  5. package/dist/browser/lib/aesgcmkw.js +2 -2
  6. package/dist/browser/lib/crypto_key.js +7 -7
  7. package/dist/browser/lib/decrypt_key_management.js +2 -2
  8. package/dist/browser/lib/encrypt_key_management.js +6 -4
  9. package/dist/browser/runtime/aeskw.js +1 -1
  10. package/dist/browser/runtime/asn1.js +21 -20
  11. package/dist/browser/runtime/decrypt.js +3 -3
  12. package/dist/browser/runtime/digest.js +1 -1
  13. package/dist/browser/runtime/ecdhes.js +8 -9
  14. package/dist/browser/runtime/encrypt.js +3 -3
  15. package/dist/browser/runtime/generate.js +7 -7
  16. package/dist/browser/runtime/get_sign_verify_key.js +1 -1
  17. package/dist/browser/runtime/jwk_to_key.js +4 -4
  18. package/dist/browser/runtime/pbes2kw.js +4 -4
  19. package/dist/browser/runtime/sign.js +1 -1
  20. package/dist/browser/runtime/subtle_dsa.js +7 -6
  21. package/dist/browser/runtime/verify.js +1 -1
  22. package/dist/browser/util/decode_jwt.js +32 -0
  23. package/dist/node/cjs/index.js +5 -1
  24. package/dist/node/cjs/jwks/local.js +103 -0
  25. package/dist/node/cjs/jwks/remote.js +28 -81
  26. package/dist/node/cjs/lib/aesgcmkw.js +2 -2
  27. package/dist/node/cjs/lib/crypto_key.js +7 -7
  28. package/dist/node/cjs/lib/decrypt_key_management.js +2 -2
  29. package/dist/node/cjs/lib/encrypt_key_management.js +6 -4
  30. package/dist/node/cjs/runtime/aeskw.js +3 -3
  31. package/dist/node/cjs/runtime/base64url.js +4 -4
  32. package/dist/node/cjs/runtime/check_cek_length.js +2 -2
  33. package/dist/node/cjs/runtime/decrypt.js +3 -3
  34. package/dist/node/cjs/runtime/ecdhes.js +7 -7
  35. package/dist/node/cjs/runtime/encrypt.js +3 -3
  36. package/dist/node/cjs/runtime/generate.js +2 -2
  37. package/dist/node/cjs/runtime/get_named_curve.js +2 -2
  38. package/dist/node/cjs/runtime/jwk_to_key.js +1 -1
  39. package/dist/node/cjs/runtime/key_to_jwk.js +1 -1
  40. package/dist/node/cjs/runtime/node_key.js +2 -2
  41. package/dist/node/cjs/runtime/pbes2kw.js +6 -6
  42. package/dist/node/cjs/runtime/verify.js +1 -1
  43. package/dist/node/cjs/util/decode_jwt.js +36 -0
  44. package/dist/node/esm/index.js +2 -0
  45. package/dist/node/esm/jwks/local.js +97 -0
  46. package/dist/node/esm/jwks/remote.js +29 -82
  47. package/dist/node/esm/lib/aesgcmkw.js +2 -2
  48. package/dist/node/esm/lib/crypto_key.js +7 -7
  49. package/dist/node/esm/lib/decrypt_key_management.js +2 -2
  50. package/dist/node/esm/lib/encrypt_key_management.js +6 -4
  51. package/dist/node/esm/runtime/aeskw.js +3 -3
  52. package/dist/node/esm/runtime/base64url.js +4 -4
  53. package/dist/node/esm/runtime/check_cek_length.js +2 -2
  54. package/dist/node/esm/runtime/decrypt.js +3 -3
  55. package/dist/node/esm/runtime/ecdhes.js +7 -7
  56. package/dist/node/esm/runtime/encrypt.js +3 -3
  57. package/dist/node/esm/runtime/generate.js +2 -2
  58. package/dist/node/esm/runtime/get_named_curve.js +2 -2
  59. package/dist/node/esm/runtime/jwk_to_key.js +1 -1
  60. package/dist/node/esm/runtime/key_to_jwk.js +1 -1
  61. package/dist/node/esm/runtime/node_key.js +2 -2
  62. package/dist/node/esm/runtime/pbes2kw.js +6 -6
  63. package/dist/node/esm/runtime/verify.js +1 -1
  64. package/dist/node/esm/util/decode_jwt.js +32 -0
  65. package/dist/types/index.d.ts +3 -1
  66. package/dist/types/jwks/local.d.ts +9 -0
  67. package/dist/types/types.d.ts +3 -0
  68. package/dist/types/util/decode_jwt.d.ts +2 -0
  69. package/package.json +1 -1
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) | ✅ | ❌ | ❌ |
@@ -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';
@@ -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.substr(0, 2)) {
7
- case 'RS':
8
- case 'PS':
9
- return 'RSA';
10
- case 'ES':
11
- return 'EC';
12
- case 'Ed':
13
- return 'OKP';
14
- default:
15
- throw new JOSENotSupported('Unsupported "alg" value for a JSON Web Key Set');
16
- }
17
- }
18
- function isJWKLike(key) {
19
- return isObject(key);
20
- }
21
- class RemoteJWKSet {
2
+ import { isCloudflareWorkers } from '../runtime/env.js';
3
+ import { JWKSInvalid, JWKSNoMatchingKey } from '../util/errors.js';
4
+ import { isJWKSLike, LocalJWKSet } from './local.js';
5
+ class RemoteJWKSet extends LocalJWKSet {
22
6
  constructor(url, options) {
23
- 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 };
@@ -3,12 +3,12 @@ import decrypt from '../runtime/decrypt.js';
3
3
  import generateIv from './iv.js';
4
4
  import { encode as base64url } from '../runtime/base64url.js';
5
5
  export async function wrap(alg, key, cek, iv) {
6
- const jweAlgorithm = alg.substr(0, 7);
6
+ const jweAlgorithm = alg.slice(0, 7);
7
7
  iv || (iv = generateIv(jweAlgorithm));
8
8
  const { ciphertext: encryptedKey, tag } = await encrypt(jweAlgorithm, cek, key, iv, new Uint8Array(0));
9
9
  return { encryptedKey, iv: base64url(iv), tag: base64url(tag) };
10
10
  }
11
11
  export async function unwrap(alg, key, encryptedKey, iv, tag) {
12
- const jweAlgorithm = alg.substr(0, 7);
12
+ const jweAlgorithm = alg.slice(0, 7);
13
13
  return decrypt(jweAlgorithm, key, encryptedKey, iv, tag, new Uint8Array(0));
14
14
  }
@@ -6,7 +6,7 @@ function isAlgorithm(algorithm, name) {
6
6
  return algorithm.name === name;
7
7
  }
8
8
  function getHashLength(hash) {
9
- return parseInt(hash.name.substr(4), 10);
9
+ return parseInt(hash.name.slice(4), 10);
10
10
  }
11
11
  function getNamedCurve(alg) {
12
12
  switch (alg) {
@@ -43,7 +43,7 @@ export function checkSigCryptoKey(key, alg, ...usages) {
43
43
  case 'HS512': {
44
44
  if (!isAlgorithm(key.algorithm, 'HMAC'))
45
45
  throw unusable('HMAC');
46
- const expected = parseInt(alg.substr(2), 10);
46
+ const expected = parseInt(alg.slice(2), 10);
47
47
  const actual = getHashLength(key.algorithm.hash);
48
48
  if (actual !== expected)
49
49
  throw unusable(`SHA-${expected}`, 'algorithm.hash');
@@ -54,7 +54,7 @@ export function checkSigCryptoKey(key, alg, ...usages) {
54
54
  case 'RS512': {
55
55
  if (!isAlgorithm(key.algorithm, 'RSASSA-PKCS1-v1_5'))
56
56
  throw unusable('RSASSA-PKCS1-v1_5');
57
- const expected = parseInt(alg.substr(2), 10);
57
+ const expected = parseInt(alg.slice(2), 10);
58
58
  const actual = getHashLength(key.algorithm.hash);
59
59
  if (actual !== expected)
60
60
  throw unusable(`SHA-${expected}`, 'algorithm.hash');
@@ -65,7 +65,7 @@ export function checkSigCryptoKey(key, alg, ...usages) {
65
65
  case 'PS512': {
66
66
  if (!isAlgorithm(key.algorithm, 'RSA-PSS'))
67
67
  throw unusable('RSA-PSS');
68
- const expected = parseInt(alg.substr(2), 10);
68
+ const expected = parseInt(alg.slice(2), 10);
69
69
  const actual = getHashLength(key.algorithm.hash);
70
70
  if (actual !== expected)
71
71
  throw unusable(`SHA-${expected}`, 'algorithm.hash');
@@ -104,7 +104,7 @@ export function checkEncCryptoKey(key, alg, ...usages) {
104
104
  case 'A256GCM': {
105
105
  if (!isAlgorithm(key.algorithm, 'AES-GCM'))
106
106
  throw unusable('AES-GCM');
107
- const expected = parseInt(alg.substr(1, 3), 10);
107
+ const expected = parseInt(alg.slice(1, 4), 10);
108
108
  const actual = key.algorithm.length;
109
109
  if (actual !== expected)
110
110
  throw unusable(expected, 'algorithm.length');
@@ -115,7 +115,7 @@ export function checkEncCryptoKey(key, alg, ...usages) {
115
115
  case 'A256KW': {
116
116
  if (!isAlgorithm(key.algorithm, 'AES-KW'))
117
117
  throw unusable('AES-KW');
118
- const expected = parseInt(alg.substr(1, 3), 10);
118
+ const expected = parseInt(alg.slice(1, 4), 10);
119
119
  const actual = key.algorithm.length;
120
120
  if (actual !== expected)
121
121
  throw unusable(expected, 'algorithm.length');
@@ -137,7 +137,7 @@ export function checkEncCryptoKey(key, alg, ...usages) {
137
137
  case 'RSA-OAEP-512': {
138
138
  if (!isAlgorithm(key.algorithm, 'RSA-OAEP'))
139
139
  throw unusable('RSA-OAEP');
140
- const expected = parseInt(alg.substr(9), 10) || 1;
140
+ const expected = parseInt(alg.slice(9), 10) || 1;
141
141
  const actual = getHashLength(key.algorithm.hash);
142
142
  if (actual !== expected)
143
143
  throw unusable(`SHA-${expected}`, 'algorithm.hash');
@@ -40,12 +40,12 @@ async function decryptKeyManagement(alg, key, encryptedKey, joseHeader) {
40
40
  throw new JWEInvalid(`JOSE Header "apv" (Agreement PartyVInfo) invalid`);
41
41
  partyVInfo = base64url(joseHeader.apv);
42
42
  }
43
- const sharedSecret = await ECDH.deriveKey(epk, key, alg === 'ECDH-ES' ? joseHeader.enc : alg, alg === 'ECDH-ES' ? cekLength(joseHeader.enc) : parseInt(alg.substr(-5, 3), 10), partyUInfo, partyVInfo);
43
+ const sharedSecret = await ECDH.deriveKey(epk, key, alg === 'ECDH-ES' ? joseHeader.enc : alg, alg === 'ECDH-ES' ? cekLength(joseHeader.enc) : parseInt(alg.slice(-5, -2), 10), partyUInfo, partyVInfo);
44
44
  if (alg === 'ECDH-ES')
45
45
  return sharedSecret;
46
46
  if (encryptedKey === undefined)
47
47
  throw new JWEInvalid('JWE Encrypted Key missing');
48
- return aesKw(alg.substr(-6), sharedSecret, encryptedKey);
48
+ return aesKw(alg.slice(-6), sharedSecret, encryptedKey);
49
49
  }
50
50
  case 'RSA1_5':
51
51
  case 'RSA-OAEP':
@@ -27,10 +27,12 @@ async function encryptKeyManagement(alg, enc, key, providedCek, providedParamete
27
27
  }
28
28
  const { apu, apv } = providedParameters;
29
29
  let { epk: ephemeralKey } = providedParameters;
30
- ephemeralKey || (ephemeralKey = await ECDH.generateEpk(key));
30
+ ephemeralKey || (ephemeralKey = (await ECDH.generateEpk(key)).privateKey);
31
31
  const { x, y, crv, kty } = await exportJWK(ephemeralKey);
32
- const sharedSecret = await ECDH.deriveKey(key, ephemeralKey, alg === 'ECDH-ES' ? enc : alg, alg === 'ECDH-ES' ? cekLength(enc) : parseInt(alg.substr(-5, 3), 10), apu, apv);
33
- parameters = { epk: { x, y, crv, kty } };
32
+ const sharedSecret = await ECDH.deriveKey(key, ephemeralKey, alg === 'ECDH-ES' ? enc : alg, alg === 'ECDH-ES' ? cekLength(enc) : parseInt(alg.slice(-5, -2), 10), apu, apv);
33
+ parameters = { epk: { x, crv, kty } };
34
+ if (kty === 'EC')
35
+ parameters.epk.y = y;
34
36
  if (apu)
35
37
  parameters.apu = base64url(apu);
36
38
  if (apv)
@@ -40,7 +42,7 @@ async function encryptKeyManagement(alg, enc, key, providedCek, providedParamete
40
42
  break;
41
43
  }
42
44
  cek = providedCek || generateCek(enc);
43
- const kwAlg = alg.substr(-6);
45
+ const kwAlg = alg.slice(-6);
44
46
  encryptedKey = await aesKw(kwAlg, sharedSecret, cek);
45
47
  break;
46
48
  }
@@ -4,7 +4,7 @@ import { checkEncCryptoKey } from '../lib/crypto_key.js';
4
4
  import invalidKeyInput from '../lib/invalid_key_input.js';
5
5
  import { types } from './is_key_like.js';
6
6
  function checkKeySize(key, alg) {
7
- if (key.algorithm.length !== parseInt(alg.substr(1, 3), 10)) {
7
+ if (key.algorithm.length !== parseInt(alg.slice(1, 4), 10)) {
8
8
  throw new TypeError(`Invalid key size for alg: ${alg}`);
9
9
  }
10
10
  }
@@ -23,29 +23,30 @@ export const toSPKI = (key) => {
23
23
  export const toPKCS8 = (key) => {
24
24
  return genericExport('private', 'pkcs8', key);
25
25
  };
26
+ const findOid = (keyData, oid, from = 0) => {
27
+ if (from === 0) {
28
+ oid.unshift(oid.length);
29
+ oid.unshift(0x06);
30
+ }
31
+ let i = keyData.indexOf(oid[0], from);
32
+ if (i === -1)
33
+ return false;
34
+ const sub = keyData.subarray(i, i + oid.length);
35
+ if (sub.length !== oid.length)
36
+ return false;
37
+ return sub.every((value, index) => value === oid[index]) || findOid(keyData, oid, i + 1);
38
+ };
26
39
  const getNamedCurve = (keyData) => {
27
- const keyDataStr = keyData.toString();
28
40
  switch (true) {
29
- case keyDataStr.includes(new Uint8Array([
30
- 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce,
31
- 0x3d, 0x03, 0x01, 0x07,
32
- ]).toString()):
41
+ case findOid(keyData, [0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07]):
33
42
  return 'P-256';
34
- case keyDataStr.includes(new Uint8Array([
35
- 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00,
36
- 0x22,
37
- ]).toString()):
43
+ case findOid(keyData, [0x2b, 0x81, 0x04, 0x00, 0x22]):
38
44
  return 'P-384';
39
- case keyDataStr.includes(new Uint8Array([
40
- 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00,
41
- 0x23,
42
- ]).toString()):
45
+ case findOid(keyData, [0x2b, 0x81, 0x04, 0x00, 0x23]):
43
46
  return 'P-521';
44
- case (isCloudflareWorkers() || isNodeJs()) &&
45
- keyDataStr.includes(new Uint8Array([0x06, 0x03, 0x2b, 0x65, 0x70]).toString()):
47
+ case (isCloudflareWorkers() || isNodeJs()) && findOid(keyData, [0x2b, 0x65, 0x70]):
46
48
  return 'Ed25519';
47
- case isNodeJs() &&
48
- keyDataStr.includes(new Uint8Array([0x06, 0x03, 0x2b, 0x65, 0x71]).toString()):
49
+ case isNodeJs() && findOid(keyData, [0x2b, 0x65, 0x71]):
49
50
  return 'Ed448';
50
51
  default:
51
52
  throw new JOSENotSupported('Invalid or unsupported EC Key Curve or OKP Key Sub Type');
@@ -63,13 +64,13 @@ const genericImport = async (replace, keyFormat, pem, alg, options) => {
63
64
  case 'PS256':
64
65
  case 'PS384':
65
66
  case 'PS512':
66
- algorithm = { name: 'RSA-PSS', hash: `SHA-${alg.substr(-3)}` };
67
+ algorithm = { name: 'RSA-PSS', hash: `SHA-${alg.slice(-3)}` };
67
68
  keyUsages = isPublic ? ['verify'] : ['sign'];
68
69
  break;
69
70
  case 'RS256':
70
71
  case 'RS384':
71
72
  case 'RS512':
72
- algorithm = { name: 'RSASSA-PKCS1-v1_5', hash: `SHA-${alg.substr(-3)}` };
73
+ algorithm = { name: 'RSASSA-PKCS1-v1_5', hash: `SHA-${alg.slice(-3)}` };
73
74
  keyUsages = isPublic ? ['verify'] : ['sign'];
74
75
  break;
75
76
  case 'RSA-OAEP':
@@ -78,7 +79,7 @@ const genericImport = async (replace, keyFormat, pem, alg, options) => {
78
79
  case 'RSA-OAEP-512':
79
80
  algorithm = {
80
81
  name: 'RSA-OAEP',
81
- hash: `SHA-${parseInt(alg.substr(-3), 10) || 1}`,
82
+ hash: `SHA-${parseInt(alg.slice(-3), 10) || 1}`,
82
83
  };
83
84
  keyUsages = isPublic ? ['encrypt', 'wrapKey'] : ['decrypt', 'unwrapKey'];
84
85
  break;
@@ -11,7 +11,7 @@ async function cbcDecrypt(enc, cek, ciphertext, iv, tag, aad) {
11
11
  if (!(cek instanceof Uint8Array)) {
12
12
  throw new TypeError(invalidKeyInput(cek, 'Uint8Array'));
13
13
  }
14
- const keySize = parseInt(enc.substr(1, 3), 10);
14
+ const keySize = parseInt(enc.slice(1, 4), 10);
15
15
  const encKey = await crypto.subtle.importKey('raw', cek.subarray(keySize >> 3), 'AES-CBC', false, ['decrypt']);
16
16
  const macKey = await crypto.subtle.importKey('raw', cek.subarray(0, keySize >> 3), {
17
17
  hash: `SHA-${keySize << 1}`,
@@ -70,13 +70,13 @@ const decrypt = async (enc, cek, ciphertext, iv, tag, aad) => {
70
70
  case 'A192CBC-HS384':
71
71
  case 'A256CBC-HS512':
72
72
  if (cek instanceof Uint8Array)
73
- checkCekLength(cek, parseInt(enc.substr(-3), 10));
73
+ checkCekLength(cek, parseInt(enc.slice(-3), 10));
74
74
  return cbcDecrypt(enc, cek, ciphertext, iv, tag, aad);
75
75
  case 'A128GCM':
76
76
  case 'A192GCM':
77
77
  case 'A256GCM':
78
78
  if (cek instanceof Uint8Array)
79
- checkCekLength(cek, parseInt(enc.substr(1, 3), 10));
79
+ checkCekLength(cek, parseInt(enc.slice(1, 4), 10));
80
80
  return gcmDecrypt(enc, cek, ciphertext, iv, tag, aad);
81
81
  default:
82
82
  throw new JOSENotSupported('Unsupported JWE Content Encryption Algorithm');
@@ -1,6 +1,6 @@
1
1
  import crypto from './webcrypto.js';
2
2
  const digest = async (algorithm, data) => {
3
- const subtleDigest = `SHA-${algorithm.substr(-3)}`;
3
+ const subtleDigest = `SHA-${algorithm.slice(-3)}`;
4
4
  return new Uint8Array(await crypto.subtle.digest(subtleDigest, data));
5
5
  };
6
6
  export default digest;
@@ -4,7 +4,7 @@ import { checkEncCryptoKey } from '../lib/crypto_key.js';
4
4
  import digest from './digest.js';
5
5
  import invalidKeyInput from '../lib/invalid_key_input.js';
6
6
  import { types } from './is_key_like.js';
7
- export const deriveKey = async (publicKey, privateKey, algorithm, keyLength, apu = new Uint8Array(0), apv = new Uint8Array(0)) => {
7
+ export async function deriveKey(publicKey, privateKey, algorithm, keyLength, apu = new Uint8Array(0), apv = new Uint8Array(0)) {
8
8
  if (!isCryptoKey(publicKey)) {
9
9
  throw new TypeError(invalidKeyInput(publicKey, ...types));
10
10
  }
@@ -20,19 +20,18 @@ export const deriveKey = async (publicKey, privateKey, algorithm, keyLength, apu
20
20
  const sharedSecret = new Uint8Array(await crypto.subtle.deriveBits({
21
21
  name: 'ECDH',
22
22
  public: publicKey,
23
- }, privateKey, Math.ceil(parseInt(privateKey.algorithm.namedCurve.substr(-3), 10) / 8) <<
24
- 3));
23
+ }, privateKey, Math.ceil(parseInt(privateKey.algorithm.namedCurve.slice(-3), 10) / 8) << 3));
25
24
  return concatKdf(digest, sharedSecret, keyLength, value);
26
- };
27
- export const generateEpk = async (key) => {
25
+ }
26
+ export async function generateEpk(key) {
28
27
  if (!isCryptoKey(key)) {
29
28
  throw new TypeError(invalidKeyInput(key, ...types));
30
29
  }
31
- return (await crypto.subtle.generateKey({ name: 'ECDH', namedCurve: key.algorithm.namedCurve }, true, ['deriveBits'])).privateKey;
32
- };
33
- export const ecdhAllowed = (key) => {
30
+ return crypto.subtle.generateKey(key.algorithm, true, ['deriveBits']);
31
+ }
32
+ export function ecdhAllowed(key) {
34
33
  if (!isCryptoKey(key)) {
35
34
  throw new TypeError(invalidKeyInput(key, ...types));
36
35
  }
37
36
  return ['P-256', 'P-384', 'P-521'].includes(key.algorithm.namedCurve);
38
- };
37
+ }
@@ -10,7 +10,7 @@ async function cbcEncrypt(enc, plaintext, cek, iv, aad) {
10
10
  if (!(cek instanceof Uint8Array)) {
11
11
  throw new TypeError(invalidKeyInput(cek, 'Uint8Array'));
12
12
  }
13
- const keySize = parseInt(enc.substr(1, 3), 10);
13
+ const keySize = parseInt(enc.slice(1, 4), 10);
14
14
  const encKey = await crypto.subtle.importKey('raw', cek.subarray(keySize >> 3), 'AES-CBC', false, ['encrypt']);
15
15
  const macKey = await crypto.subtle.importKey('raw', cek.subarray(0, keySize >> 3), {
16
16
  hash: `SHA-${keySize << 1}`,
@@ -53,13 +53,13 @@ const encrypt = async (enc, plaintext, cek, iv, aad) => {
53
53
  case 'A192CBC-HS384':
54
54
  case 'A256CBC-HS512':
55
55
  if (cek instanceof Uint8Array)
56
- checkCekLength(cek, parseInt(enc.substr(-3), 10));
56
+ checkCekLength(cek, parseInt(enc.slice(-3), 10));
57
57
  return cbcEncrypt(enc, plaintext, cek, iv, aad);
58
58
  case 'A128GCM':
59
59
  case 'A192GCM':
60
60
  case 'A256GCM':
61
61
  if (cek instanceof Uint8Array)
62
- checkCekLength(cek, parseInt(enc.substr(1, 3), 10));
62
+ checkCekLength(cek, parseInt(enc.slice(1, 4), 10));
63
63
  return gcmEncrypt(enc, plaintext, cek, iv, aad);
64
64
  default:
65
65
  throw new JOSENotSupported('Unsupported JWE Content Encryption Algorithm');
@@ -11,19 +11,19 @@ export async function generateSecret(alg, options) {
11
11
  case 'HS256':
12
12
  case 'HS384':
13
13
  case 'HS512':
14
- length = parseInt(alg.substr(-3), 10);
14
+ length = parseInt(alg.slice(-3), 10);
15
15
  algorithm = { name: 'HMAC', hash: `SHA-${length}`, length };
16
16
  keyUsages = ['sign', 'verify'];
17
17
  break;
18
18
  case 'A128CBC-HS256':
19
19
  case 'A192CBC-HS384':
20
20
  case 'A256CBC-HS512':
21
- length = parseInt(alg.substr(-3), 10);
21
+ length = parseInt(alg.slice(-3), 10);
22
22
  return random(new Uint8Array(length >> 3));
23
23
  case 'A128KW':
24
24
  case 'A192KW':
25
25
  case 'A256KW':
26
- length = parseInt(alg.substring(1, 4), 10);
26
+ length = parseInt(alg.slice(1, 4), 10);
27
27
  algorithm = { name: 'AES-KW', length };
28
28
  keyUsages = ['wrapKey', 'unwrapKey'];
29
29
  break;
@@ -33,7 +33,7 @@ export async function generateSecret(alg, options) {
33
33
  case 'A128GCM':
34
34
  case 'A192GCM':
35
35
  case 'A256GCM':
36
- length = parseInt(alg.substring(1, 4), 10);
36
+ length = parseInt(alg.slice(1, 4), 10);
37
37
  algorithm = { name: 'AES-GCM', length };
38
38
  keyUsages = ['encrypt', 'decrypt'];
39
39
  break;
@@ -60,7 +60,7 @@ export async function generateKeyPair(alg, options) {
60
60
  case 'PS512':
61
61
  algorithm = {
62
62
  name: 'RSA-PSS',
63
- hash: `SHA-${alg.substr(-3)}`,
63
+ hash: `SHA-${alg.slice(-3)}`,
64
64
  publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
65
65
  modulusLength: getModulusLengthOption(options),
66
66
  };
@@ -71,7 +71,7 @@ export async function generateKeyPair(alg, options) {
71
71
  case 'RS512':
72
72
  algorithm = {
73
73
  name: 'RSASSA-PKCS1-v1_5',
74
- hash: `SHA-${alg.substr(-3)}`,
74
+ hash: `SHA-${alg.slice(-3)}`,
75
75
  publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
76
76
  modulusLength: getModulusLengthOption(options),
77
77
  };
@@ -83,7 +83,7 @@ export async function generateKeyPair(alg, options) {
83
83
  case 'RSA-OAEP-512':
84
84
  algorithm = {
85
85
  name: 'RSA-OAEP',
86
- hash: `SHA-${parseInt(alg.substr(-3), 10) || 1}`,
86
+ hash: `SHA-${parseInt(alg.slice(-3), 10) || 1}`,
87
87
  publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
88
88
  modulusLength: getModulusLengthOption(options),
89
89
  };
@@ -11,7 +11,7 @@ export default function getCryptoKey(alg, key, usage) {
11
11
  if (!alg.startsWith('HS')) {
12
12
  throw new TypeError(invalidKeyInput(key, ...types));
13
13
  }
14
- return crypto.subtle.importKey('raw', key, { hash: `SHA-${alg.substr(-3)}`, name: 'HMAC' }, false, [usage]);
14
+ return crypto.subtle.importKey('raw', key, { hash: `SHA-${alg.slice(-3)}`, name: 'HMAC' }, false, [usage]);
15
15
  }
16
16
  throw new TypeError(invalidKeyInput(key, ...types, 'Uint8Array'));
17
17
  }