jose 4.3.6 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/browser/index.js +1 -0
- package/dist/browser/jwks/local.js +101 -0
- package/dist/browser/jwks/remote.js +15 -82
- package/dist/browser/lib/aesgcmkw.js +2 -2
- package/dist/browser/lib/crypto_key.js +7 -7
- package/dist/browser/lib/decrypt_key_management.js +2 -2
- package/dist/browser/lib/encrypt_key_management.js +6 -4
- package/dist/browser/runtime/aeskw.js +1 -1
- package/dist/browser/runtime/asn1.js +21 -20
- package/dist/browser/runtime/decrypt.js +3 -3
- package/dist/browser/runtime/digest.js +1 -1
- package/dist/browser/runtime/ecdhes.js +8 -9
- package/dist/browser/runtime/encrypt.js +3 -3
- package/dist/browser/runtime/generate.js +7 -7
- package/dist/browser/runtime/get_sign_verify_key.js +1 -1
- package/dist/browser/runtime/jwk_to_key.js +4 -4
- package/dist/browser/runtime/pbes2kw.js +4 -4
- package/dist/browser/runtime/sign.js +1 -1
- package/dist/browser/runtime/subtle_dsa.js +7 -6
- package/dist/browser/runtime/verify.js +1 -1
- package/dist/node/cjs/index.js +3 -1
- package/dist/node/cjs/jwks/local.js +107 -0
- package/dist/node/cjs/jwks/remote.js +14 -81
- package/dist/node/cjs/lib/aesgcmkw.js +2 -2
- package/dist/node/cjs/lib/crypto_key.js +7 -7
- package/dist/node/cjs/lib/decrypt_key_management.js +2 -2
- package/dist/node/cjs/lib/encrypt_key_management.js +6 -4
- package/dist/node/cjs/runtime/aeskw.js +4 -5
- package/dist/node/cjs/runtime/check_cek_length.js +2 -2
- package/dist/node/cjs/runtime/decrypt.js +3 -3
- package/dist/node/cjs/runtime/ecdhes.js +7 -7
- package/dist/node/cjs/runtime/encrypt.js +3 -3
- package/dist/node/cjs/runtime/generate.js +2 -2
- package/dist/node/cjs/runtime/get_named_curve.js +2 -2
- package/dist/node/cjs/runtime/get_sign_verify_key.js +1 -2
- package/dist/node/cjs/runtime/jwk_to_key.js +1 -1
- package/dist/node/cjs/runtime/key_to_jwk.js +1 -1
- package/dist/node/cjs/runtime/node_key.js +2 -2
- package/dist/node/cjs/runtime/pbes2kw.js +6 -6
- package/dist/node/cjs/runtime/verify.js +1 -1
- package/dist/node/esm/index.js +1 -0
- package/dist/node/esm/jwks/local.js +101 -0
- package/dist/node/esm/jwks/remote.js +15 -82
- package/dist/node/esm/lib/aesgcmkw.js +2 -2
- package/dist/node/esm/lib/crypto_key.js +7 -7
- package/dist/node/esm/lib/decrypt_key_management.js +2 -2
- package/dist/node/esm/lib/encrypt_key_management.js +6 -4
- package/dist/node/esm/runtime/aeskw.js +5 -6
- package/dist/node/esm/runtime/check_cek_length.js +2 -2
- package/dist/node/esm/runtime/decrypt.js +3 -3
- package/dist/node/esm/runtime/ecdhes.js +7 -7
- package/dist/node/esm/runtime/encrypt.js +3 -3
- package/dist/node/esm/runtime/generate.js +2 -2
- package/dist/node/esm/runtime/get_named_curve.js +2 -2
- package/dist/node/esm/runtime/get_sign_verify_key.js +2 -3
- package/dist/node/esm/runtime/jwk_to_key.js +1 -1
- package/dist/node/esm/runtime/key_to_jwk.js +1 -1
- package/dist/node/esm/runtime/node_key.js +2 -2
- package/dist/node/esm/runtime/pbes2kw.js +6 -6
- package/dist/node/esm/runtime/verify.js +1 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/jwks/local.d.ts +9 -0
- package/dist/types/types.d.ts +4 -1
- package/package.json +1 -1
- package/dist/node/cjs/runtime/secret_key.js +0 -14
- package/dist/node/esm/runtime/secret_key.js +0 -11
package/README.md
CHANGED
|
@@ -61,6 +61,7 @@ import * as jose from 'https://deno.land/x/jose/index.ts'
|
|
|
61
61
|
- [Thumbprints](docs/functions/jwk_thumbprint.calculateJwkThumbprint.md#readme)
|
|
62
62
|
- [EmbeddedJWK](docs/functions/jwk_embedded.EmbeddedJWK.md#readme)
|
|
63
63
|
- JSON Web Key Set (JWKS)
|
|
64
|
+
- [Verify using a local JWKSet](docs/functions/jwks_local.createLocalJWKSet.md#readme)
|
|
64
65
|
- [Verify using a remote JWKSet](docs/functions/jwks_remote.createRemoteJWKSet.md#readme)
|
|
65
66
|
- Key Pair or Secret Generation
|
|
66
67
|
- [Asymmetric Key Pair Generation](docs/functions/key_generate_key_pair.generateKeyPair.md#readme)
|
package/dist/browser/index.js
CHANGED
|
@@ -16,6 +16,7 @@ export { SignJWT } from './jwt/sign.js';
|
|
|
16
16
|
export { EncryptJWT } from './jwt/encrypt.js';
|
|
17
17
|
export { calculateJwkThumbprint } from './jwk/thumbprint.js';
|
|
18
18
|
export { EmbeddedJWK } from './jwk/embedded.js';
|
|
19
|
+
export { createLocalJWKSet } from './jwks/local.js';
|
|
19
20
|
export { createRemoteJWKSet } from './jwks/remote.js';
|
|
20
21
|
export { UnsecuredJWT } from './jwt/unsecured.js';
|
|
21
22
|
export { exportPKCS8, exportSPKI, exportJWK } from './key/export.js';
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { importJWK } from '../key/import.js';
|
|
2
|
+
import { JWKSInvalid, JOSENotSupported, JWKSNoMatchingKey, JWKSMultipleMatchingKeys, } from '../util/errors.js';
|
|
3
|
+
import isObject from '../lib/is_object.js';
|
|
4
|
+
function getKtyFromAlg(alg) {
|
|
5
|
+
switch (typeof alg === 'string' && alg.slice(0, 2)) {
|
|
6
|
+
case 'RS':
|
|
7
|
+
case 'PS':
|
|
8
|
+
return 'RSA';
|
|
9
|
+
case 'ES':
|
|
10
|
+
return 'EC';
|
|
11
|
+
case 'Ed':
|
|
12
|
+
return 'OKP';
|
|
13
|
+
default:
|
|
14
|
+
throw new JOSENotSupported('Unsupported "alg" value for a JSON Web Key Set');
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function isJWKSLike(jwks) {
|
|
18
|
+
return (jwks &&
|
|
19
|
+
typeof jwks === 'object' &&
|
|
20
|
+
Array.isArray(jwks.keys) &&
|
|
21
|
+
jwks.keys.every(isJWKLike));
|
|
22
|
+
}
|
|
23
|
+
function isJWKLike(key) {
|
|
24
|
+
return isObject(key);
|
|
25
|
+
}
|
|
26
|
+
function clone(obj) {
|
|
27
|
+
if (typeof structuredClone === 'function') {
|
|
28
|
+
return structuredClone(obj);
|
|
29
|
+
}
|
|
30
|
+
return JSON.parse(JSON.stringify(obj));
|
|
31
|
+
}
|
|
32
|
+
export class LocalJWKSet {
|
|
33
|
+
constructor(jwks) {
|
|
34
|
+
this._cached = new WeakMap();
|
|
35
|
+
if (!isJWKSLike(jwks)) {
|
|
36
|
+
throw new JWKSInvalid('JSON Web Key Set malformed');
|
|
37
|
+
}
|
|
38
|
+
this._jwks = clone(jwks);
|
|
39
|
+
}
|
|
40
|
+
async getKey(protectedHeader, token) {
|
|
41
|
+
const joseHeader = {
|
|
42
|
+
...protectedHeader,
|
|
43
|
+
...token.header,
|
|
44
|
+
};
|
|
45
|
+
const candidates = this._jwks.keys.filter((jwk) => {
|
|
46
|
+
let candidate = jwk.kty === getKtyFromAlg(joseHeader.alg);
|
|
47
|
+
if (candidate && typeof joseHeader.kid === 'string') {
|
|
48
|
+
candidate = joseHeader.kid === jwk.kid;
|
|
49
|
+
}
|
|
50
|
+
if (candidate && typeof jwk.alg === 'string') {
|
|
51
|
+
candidate = joseHeader.alg === jwk.alg;
|
|
52
|
+
}
|
|
53
|
+
if (candidate && typeof jwk.use === 'string') {
|
|
54
|
+
candidate = jwk.use === 'sig';
|
|
55
|
+
}
|
|
56
|
+
if (candidate && Array.isArray(jwk.key_ops)) {
|
|
57
|
+
candidate = jwk.key_ops.includes('verify');
|
|
58
|
+
}
|
|
59
|
+
if (candidate && joseHeader.alg === 'EdDSA') {
|
|
60
|
+
candidate = jwk.crv === 'Ed25519' || jwk.crv === 'Ed448';
|
|
61
|
+
}
|
|
62
|
+
if (candidate) {
|
|
63
|
+
switch (joseHeader.alg) {
|
|
64
|
+
case 'ES256':
|
|
65
|
+
candidate = jwk.crv === 'P-256';
|
|
66
|
+
break;
|
|
67
|
+
case 'ES256K':
|
|
68
|
+
candidate = jwk.crv === 'secp256k1';
|
|
69
|
+
break;
|
|
70
|
+
case 'ES384':
|
|
71
|
+
candidate = jwk.crv === 'P-384';
|
|
72
|
+
break;
|
|
73
|
+
case 'ES512':
|
|
74
|
+
candidate = jwk.crv === 'P-521';
|
|
75
|
+
break;
|
|
76
|
+
default:
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return candidate;
|
|
80
|
+
});
|
|
81
|
+
const { 0: jwk, length } = candidates;
|
|
82
|
+
if (length === 0) {
|
|
83
|
+
throw new JWKSNoMatchingKey();
|
|
84
|
+
}
|
|
85
|
+
else if (length !== 1) {
|
|
86
|
+
throw new JWKSMultipleMatchingKeys();
|
|
87
|
+
}
|
|
88
|
+
const cached = this._cached.get(jwk) || this._cached.set(jwk, {}).get(jwk);
|
|
89
|
+
if (cached[joseHeader.alg] === undefined) {
|
|
90
|
+
const keyObject = await importJWK({ ...jwk, ext: true }, joseHeader.alg);
|
|
91
|
+
if (keyObject instanceof Uint8Array || keyObject.type !== 'public') {
|
|
92
|
+
throw new JWKSInvalid('JSON Web Key Set members must be public keys');
|
|
93
|
+
}
|
|
94
|
+
cached[joseHeader.alg] = keyObject;
|
|
95
|
+
}
|
|
96
|
+
return cached[joseHeader.alg];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export function createLocalJWKSet(jwks) {
|
|
100
|
+
return LocalJWKSet.prototype.getKey.bind(new LocalJWKSet(jwks));
|
|
101
|
+
}
|
|
@@ -1,26 +1,10 @@
|
|
|
1
1
|
import fetchJwks from '../runtime/fetch_jwks.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
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 { JWKSInvalid, JWKSNoMatchingKey } from '../util/errors.js';
|
|
3
|
+
import { isJWKSLike, LocalJWKSet } from './local.js';
|
|
4
|
+
class RemoteJWKSet extends LocalJWKSet {
|
|
22
5
|
constructor(url, options) {
|
|
23
|
-
|
|
6
|
+
super({ keys: [] });
|
|
7
|
+
this._jwks = undefined;
|
|
24
8
|
if (!(url instanceof URL)) {
|
|
25
9
|
throw new TypeError('url must be an instance of URL');
|
|
26
10
|
}
|
|
@@ -38,78 +22,27 @@ class RemoteJWKSet {
|
|
|
38
22
|
return Date.now() < this._cooldownStarted + this._cooldownDuration;
|
|
39
23
|
}
|
|
40
24
|
async getKey(protectedHeader, token) {
|
|
41
|
-
const joseHeader = {
|
|
42
|
-
...protectedHeader,
|
|
43
|
-
...token.header,
|
|
44
|
-
};
|
|
45
25
|
if (!this._jwks) {
|
|
46
26
|
await this.reload();
|
|
47
27
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (candidate && typeof joseHeader.kid === 'string') {
|
|
51
|
-
candidate = joseHeader.kid === jwk.kid;
|
|
52
|
-
}
|
|
53
|
-
if (candidate && typeof jwk.alg === 'string') {
|
|
54
|
-
candidate = joseHeader.alg === jwk.alg;
|
|
55
|
-
}
|
|
56
|
-
if (candidate && typeof jwk.use === 'string') {
|
|
57
|
-
candidate = jwk.use === 'sig';
|
|
58
|
-
}
|
|
59
|
-
if (candidate && Array.isArray(jwk.key_ops)) {
|
|
60
|
-
candidate = jwk.key_ops.includes('verify');
|
|
61
|
-
}
|
|
62
|
-
if (candidate && joseHeader.alg === 'EdDSA') {
|
|
63
|
-
candidate = jwk.crv === 'Ed25519' || jwk.crv === 'Ed448';
|
|
64
|
-
}
|
|
65
|
-
if (candidate) {
|
|
66
|
-
switch (joseHeader.alg) {
|
|
67
|
-
case 'ES256':
|
|
68
|
-
candidate = jwk.crv === 'P-256';
|
|
69
|
-
break;
|
|
70
|
-
case 'ES256K':
|
|
71
|
-
candidate = jwk.crv === 'secp256k1';
|
|
72
|
-
break;
|
|
73
|
-
case 'ES384':
|
|
74
|
-
candidate = jwk.crv === 'P-384';
|
|
75
|
-
break;
|
|
76
|
-
case 'ES512':
|
|
77
|
-
candidate = jwk.crv === 'P-521';
|
|
78
|
-
break;
|
|
79
|
-
default:
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return candidate;
|
|
83
|
-
});
|
|
84
|
-
const { 0: jwk, length } = candidates;
|
|
85
|
-
if (length === 0) {
|
|
86
|
-
if (this.coolingDown() === false) {
|
|
87
|
-
await this.reload();
|
|
88
|
-
return this.getKey(protectedHeader, token);
|
|
89
|
-
}
|
|
90
|
-
throw new JWKSNoMatchingKey();
|
|
91
|
-
}
|
|
92
|
-
else if (length !== 1) {
|
|
93
|
-
throw new JWKSMultipleMatchingKeys();
|
|
28
|
+
try {
|
|
29
|
+
return await super.getKey(protectedHeader, token);
|
|
94
30
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
31
|
+
catch (err) {
|
|
32
|
+
if (err instanceof JWKSNoMatchingKey) {
|
|
33
|
+
if (this.coolingDown() === false) {
|
|
34
|
+
await this.reload();
|
|
35
|
+
return super.getKey(protectedHeader, token);
|
|
36
|
+
}
|
|
100
37
|
}
|
|
101
|
-
|
|
38
|
+
throw err;
|
|
102
39
|
}
|
|
103
|
-
return cached[joseHeader.alg];
|
|
104
40
|
}
|
|
105
41
|
async reload() {
|
|
106
42
|
if (!this._pendingFetch) {
|
|
107
43
|
this._pendingFetch = fetchJwks(this._url, this._timeoutDuration, this._options)
|
|
108
44
|
.then((json) => {
|
|
109
|
-
if (
|
|
110
|
-
!json ||
|
|
111
|
-
!Array.isArray(json.keys) ||
|
|
112
|
-
!json.keys.every(isJWKLike)) {
|
|
45
|
+
if (!isJWKSLike(json)) {
|
|
113
46
|
throw new JWKSInvalid('JSON Web Key Set malformed');
|
|
114
47
|
}
|
|
115
48
|
this._jwks = { keys: json.keys };
|
|
@@ -3,12 +3,12 @@ import decrypt from '../runtime/decrypt.js';
|
|
|
3
3
|
import generateIv from './iv.js';
|
|
4
4
|
import { encode as base64url } from '../runtime/base64url.js';
|
|
5
5
|
export async function wrap(alg, key, cek, iv) {
|
|
6
|
-
const jweAlgorithm = alg.
|
|
6
|
+
const jweAlgorithm = alg.slice(0, 7);
|
|
7
7
|
iv || (iv = generateIv(jweAlgorithm));
|
|
8
8
|
const { ciphertext: encryptedKey, tag } = await encrypt(jweAlgorithm, cek, key, iv, new Uint8Array(0));
|
|
9
9
|
return { encryptedKey, iv: base64url(iv), tag: base64url(tag) };
|
|
10
10
|
}
|
|
11
11
|
export async function unwrap(alg, key, encryptedKey, iv, tag) {
|
|
12
|
-
const jweAlgorithm = alg.
|
|
12
|
+
const jweAlgorithm = alg.slice(0, 7);
|
|
13
13
|
return decrypt(jweAlgorithm, key, encryptedKey, iv, tag, new Uint8Array(0));
|
|
14
14
|
}
|
|
@@ -6,7 +6,7 @@ function isAlgorithm(algorithm, name) {
|
|
|
6
6
|
return algorithm.name === name;
|
|
7
7
|
}
|
|
8
8
|
function getHashLength(hash) {
|
|
9
|
-
return parseInt(hash.name.
|
|
9
|
+
return parseInt(hash.name.slice(4), 10);
|
|
10
10
|
}
|
|
11
11
|
function getNamedCurve(alg) {
|
|
12
12
|
switch (alg) {
|
|
@@ -43,7 +43,7 @@ export function checkSigCryptoKey(key, alg, ...usages) {
|
|
|
43
43
|
case 'HS512': {
|
|
44
44
|
if (!isAlgorithm(key.algorithm, 'HMAC'))
|
|
45
45
|
throw unusable('HMAC');
|
|
46
|
-
const expected = parseInt(alg.
|
|
46
|
+
const expected = parseInt(alg.slice(2), 10);
|
|
47
47
|
const actual = getHashLength(key.algorithm.hash);
|
|
48
48
|
if (actual !== expected)
|
|
49
49
|
throw unusable(`SHA-${expected}`, 'algorithm.hash');
|
|
@@ -54,7 +54,7 @@ export function checkSigCryptoKey(key, alg, ...usages) {
|
|
|
54
54
|
case 'RS512': {
|
|
55
55
|
if (!isAlgorithm(key.algorithm, 'RSASSA-PKCS1-v1_5'))
|
|
56
56
|
throw unusable('RSASSA-PKCS1-v1_5');
|
|
57
|
-
const expected = parseInt(alg.
|
|
57
|
+
const expected = parseInt(alg.slice(2), 10);
|
|
58
58
|
const actual = getHashLength(key.algorithm.hash);
|
|
59
59
|
if (actual !== expected)
|
|
60
60
|
throw unusable(`SHA-${expected}`, 'algorithm.hash');
|
|
@@ -65,7 +65,7 @@ export function checkSigCryptoKey(key, alg, ...usages) {
|
|
|
65
65
|
case 'PS512': {
|
|
66
66
|
if (!isAlgorithm(key.algorithm, 'RSA-PSS'))
|
|
67
67
|
throw unusable('RSA-PSS');
|
|
68
|
-
const expected = parseInt(alg.
|
|
68
|
+
const expected = parseInt(alg.slice(2), 10);
|
|
69
69
|
const actual = getHashLength(key.algorithm.hash);
|
|
70
70
|
if (actual !== expected)
|
|
71
71
|
throw unusable(`SHA-${expected}`, 'algorithm.hash');
|
|
@@ -104,7 +104,7 @@ export function checkEncCryptoKey(key, alg, ...usages) {
|
|
|
104
104
|
case 'A256GCM': {
|
|
105
105
|
if (!isAlgorithm(key.algorithm, 'AES-GCM'))
|
|
106
106
|
throw unusable('AES-GCM');
|
|
107
|
-
const expected = parseInt(alg.
|
|
107
|
+
const expected = parseInt(alg.slice(1, 4), 10);
|
|
108
108
|
const actual = key.algorithm.length;
|
|
109
109
|
if (actual !== expected)
|
|
110
110
|
throw unusable(expected, 'algorithm.length');
|
|
@@ -115,7 +115,7 @@ export function checkEncCryptoKey(key, alg, ...usages) {
|
|
|
115
115
|
case 'A256KW': {
|
|
116
116
|
if (!isAlgorithm(key.algorithm, 'AES-KW'))
|
|
117
117
|
throw unusable('AES-KW');
|
|
118
|
-
const expected = parseInt(alg.
|
|
118
|
+
const expected = parseInt(alg.slice(1, 4), 10);
|
|
119
119
|
const actual = key.algorithm.length;
|
|
120
120
|
if (actual !== expected)
|
|
121
121
|
throw unusable(expected, 'algorithm.length');
|
|
@@ -137,7 +137,7 @@ export function checkEncCryptoKey(key, alg, ...usages) {
|
|
|
137
137
|
case 'RSA-OAEP-512': {
|
|
138
138
|
if (!isAlgorithm(key.algorithm, 'RSA-OAEP'))
|
|
139
139
|
throw unusable('RSA-OAEP');
|
|
140
|
-
const expected = parseInt(alg.
|
|
140
|
+
const expected = parseInt(alg.slice(9), 10) || 1;
|
|
141
141
|
const actual = getHashLength(key.algorithm.hash);
|
|
142
142
|
if (actual !== expected)
|
|
143
143
|
throw unusable(`SHA-${expected}`, 'algorithm.hash');
|
|
@@ -40,12 +40,12 @@ async function decryptKeyManagement(alg, key, encryptedKey, joseHeader) {
|
|
|
40
40
|
throw new JWEInvalid(`JOSE Header "apv" (Agreement PartyVInfo) invalid`);
|
|
41
41
|
partyVInfo = base64url(joseHeader.apv);
|
|
42
42
|
}
|
|
43
|
-
const sharedSecret = await ECDH.deriveKey(epk, key, alg === 'ECDH-ES' ? joseHeader.enc : alg, alg === 'ECDH-ES' ? cekLength(joseHeader.enc) : parseInt(alg.
|
|
43
|
+
const sharedSecret = await ECDH.deriveKey(epk, key, alg === 'ECDH-ES' ? joseHeader.enc : alg, alg === 'ECDH-ES' ? cekLength(joseHeader.enc) : parseInt(alg.slice(-5, -2), 10), partyUInfo, partyVInfo);
|
|
44
44
|
if (alg === 'ECDH-ES')
|
|
45
45
|
return sharedSecret;
|
|
46
46
|
if (encryptedKey === undefined)
|
|
47
47
|
throw new JWEInvalid('JWE Encrypted Key missing');
|
|
48
|
-
return aesKw(alg.
|
|
48
|
+
return aesKw(alg.slice(-6), sharedSecret, encryptedKey);
|
|
49
49
|
}
|
|
50
50
|
case 'RSA1_5':
|
|
51
51
|
case 'RSA-OAEP':
|
|
@@ -27,10 +27,12 @@ async function encryptKeyManagement(alg, enc, key, providedCek, providedParamete
|
|
|
27
27
|
}
|
|
28
28
|
const { apu, apv } = providedParameters;
|
|
29
29
|
let { epk: ephemeralKey } = providedParameters;
|
|
30
|
-
ephemeralKey || (ephemeralKey = await ECDH.generateEpk(key));
|
|
30
|
+
ephemeralKey || (ephemeralKey = (await ECDH.generateEpk(key)).privateKey);
|
|
31
31
|
const { x, y, crv, kty } = await exportJWK(ephemeralKey);
|
|
32
|
-
const sharedSecret = await ECDH.deriveKey(key, ephemeralKey, alg === 'ECDH-ES' ? enc : alg, alg === 'ECDH-ES' ? cekLength(enc) : parseInt(alg.
|
|
33
|
-
parameters = { epk: { x,
|
|
32
|
+
const sharedSecret = await ECDH.deriveKey(key, ephemeralKey, alg === 'ECDH-ES' ? enc : alg, alg === 'ECDH-ES' ? cekLength(enc) : parseInt(alg.slice(-5, -2), 10), apu, apv);
|
|
33
|
+
parameters = { epk: { x, crv, kty } };
|
|
34
|
+
if (kty === 'EC')
|
|
35
|
+
parameters.epk.y = y;
|
|
34
36
|
if (apu)
|
|
35
37
|
parameters.apu = base64url(apu);
|
|
36
38
|
if (apv)
|
|
@@ -40,7 +42,7 @@ async function encryptKeyManagement(alg, enc, key, providedCek, providedParamete
|
|
|
40
42
|
break;
|
|
41
43
|
}
|
|
42
44
|
cek = providedCek || generateCek(enc);
|
|
43
|
-
const kwAlg = alg.
|
|
45
|
+
const kwAlg = alg.slice(-6);
|
|
44
46
|
encryptedKey = await aesKw(kwAlg, sharedSecret, cek);
|
|
45
47
|
break;
|
|
46
48
|
}
|
|
@@ -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.
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
25
|
+
}
|
|
26
|
+
export async function generateEpk(key) {
|
|
28
27
|
if (!isCryptoKey(key)) {
|
|
29
28
|
throw new TypeError(invalidKeyInput(key, ...types));
|
|
30
29
|
}
|
|
31
|
-
return
|
|
32
|
-
}
|
|
33
|
-
export
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
}
|
|
@@ -11,7 +11,7 @@ function subtleMapping(jwk) {
|
|
|
11
11
|
case 'HS256':
|
|
12
12
|
case 'HS384':
|
|
13
13
|
case 'HS512':
|
|
14
|
-
algorithm = { name: 'HMAC', hash: `SHA-${jwk.alg.
|
|
14
|
+
algorithm = { name: 'HMAC', hash: `SHA-${jwk.alg.slice(-3)}` };
|
|
15
15
|
keyUsages = ['sign', 'verify'];
|
|
16
16
|
break;
|
|
17
17
|
case 'A128CBC-HS256':
|
|
@@ -49,13 +49,13 @@ function subtleMapping(jwk) {
|
|
|
49
49
|
case 'PS256':
|
|
50
50
|
case 'PS384':
|
|
51
51
|
case 'PS512':
|
|
52
|
-
algorithm = { name: 'RSA-PSS', hash: `SHA-${jwk.alg.
|
|
52
|
+
algorithm = { name: 'RSA-PSS', hash: `SHA-${jwk.alg.slice(-3)}` };
|
|
53
53
|
keyUsages = jwk.d ? ['sign'] : ['verify'];
|
|
54
54
|
break;
|
|
55
55
|
case 'RS256':
|
|
56
56
|
case 'RS384':
|
|
57
57
|
case 'RS512':
|
|
58
|
-
algorithm = { name: 'RSASSA-PKCS1-v1_5', hash: `SHA-${jwk.alg.
|
|
58
|
+
algorithm = { name: 'RSASSA-PKCS1-v1_5', hash: `SHA-${jwk.alg.slice(-3)}` };
|
|
59
59
|
keyUsages = jwk.d ? ['sign'] : ['verify'];
|
|
60
60
|
break;
|
|
61
61
|
case 'RSA-OAEP':
|
|
@@ -64,7 +64,7 @@ function subtleMapping(jwk) {
|
|
|
64
64
|
case 'RSA-OAEP-512':
|
|
65
65
|
algorithm = {
|
|
66
66
|
name: 'RSA-OAEP',
|
|
67
|
-
hash: `SHA-${parseInt(jwk.alg.
|
|
67
|
+
hash: `SHA-${parseInt(jwk.alg.slice(-3), 10) || 1}`,
|
|
68
68
|
};
|
|
69
69
|
keyUsages = jwk.d ? ['decrypt', 'unwrapKey'] : ['encrypt', 'wrapKey'];
|
|
70
70
|
break;
|