@oari/jose 0.0.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/LICENSE.md +21 -0
- package/README.md +150 -0
- package/dist/types/index.d.ts +55 -0
- package/dist/types/jwe/compact/decrypt.d.ts +43 -0
- package/dist/types/jwe/compact/encrypt.d.ts +76 -0
- package/dist/types/jwe/flattened/decrypt.d.ts +53 -0
- package/dist/types/jwe/flattened/encrypt.d.ts +95 -0
- package/dist/types/jwe/general/decrypt.d.ts +64 -0
- package/dist/types/jwe/general/encrypt.d.ts +89 -0
- package/dist/types/jwk/embedded.d.ts +31 -0
- package/dist/types/jwk/thumbprint.d.ts +60 -0
- package/dist/types/jwks/local.d.ts +90 -0
- package/dist/types/jwks/remote.d.ts +306 -0
- package/dist/types/jws/compact/sign.d.ts +47 -0
- package/dist/types/jws/compact/verify.d.ts +45 -0
- package/dist/types/jws/flattened/sign.d.ts +53 -0
- package/dist/types/jws/flattened/verify.d.ts +50 -0
- package/dist/types/jws/general/sign.d.ts +67 -0
- package/dist/types/jws/general/verify.d.ts +61 -0
- package/dist/types/jwt/decrypt.d.ts +51 -0
- package/dist/types/jwt/encrypt.d.ts +105 -0
- package/dist/types/jwt/sign.d.ts +140 -0
- package/dist/types/jwt/unsecured.d.ts +70 -0
- package/dist/types/jwt/verify.d.ts +124 -0
- package/dist/types/key/export.d.ts +59 -0
- package/dist/types/key/generate_key_pair.d.ts +64 -0
- package/dist/types/key/generate_secret.d.ts +42 -0
- package/dist/types/key/import.d.ts +146 -0
- package/dist/types/types.d.ts +869 -0
- package/dist/types/util/base64url.d.ts +9 -0
- package/dist/types/util/decode_jwt.d.ts +25 -0
- package/dist/types/util/decode_protected_header.d.ts +24 -0
- package/dist/types/util/errors.d.ts +488 -0
- package/dist/webapi/index.js +32 -0
- package/dist/webapi/jwe/compact/decrypt.js +27 -0
- package/dist/webapi/jwe/compact/encrypt.js +27 -0
- package/dist/webapi/jwe/flattened/decrypt.js +159 -0
- package/dist/webapi/jwe/flattened/encrypt.js +167 -0
- package/dist/webapi/jwe/general/decrypt.js +31 -0
- package/dist/webapi/jwe/general/encrypt.js +182 -0
- package/dist/webapi/jwk/embedded.js +17 -0
- package/dist/webapi/jwk/thumbprint.js +68 -0
- package/dist/webapi/jwks/local.js +119 -0
- package/dist/webapi/jwks/remote.js +179 -0
- package/dist/webapi/jws/compact/sign.js +18 -0
- package/dist/webapi/jws/compact/verify.js +21 -0
- package/dist/webapi/jws/flattened/sign.js +87 -0
- package/dist/webapi/jws/flattened/verify.js +110 -0
- package/dist/webapi/jws/general/sign.js +70 -0
- package/dist/webapi/jws/general/verify.js +24 -0
- package/dist/webapi/jwt/decrypt.js +23 -0
- package/dist/webapi/jwt/encrypt.js +101 -0
- package/dist/webapi/jwt/sign.js +52 -0
- package/dist/webapi/jwt/unsecured.js +63 -0
- package/dist/webapi/jwt/verify.js +15 -0
- package/dist/webapi/key/export.js +11 -0
- package/dist/webapi/key/generate_key_pair.js +97 -0
- package/dist/webapi/key/generate_secret.js +40 -0
- package/dist/webapi/key/import.js +57 -0
- package/dist/webapi/lib/aesgcmkw.js +15 -0
- package/dist/webapi/lib/aeskw.js +25 -0
- package/dist/webapi/lib/asn1.js +243 -0
- package/dist/webapi/lib/base64.js +22 -0
- package/dist/webapi/lib/buffer_utils.js +43 -0
- package/dist/webapi/lib/check_key_type.js +127 -0
- package/dist/webapi/lib/content_encryption.js +217 -0
- package/dist/webapi/lib/crypto_key.js +136 -0
- package/dist/webapi/lib/deflate.js +44 -0
- package/dist/webapi/lib/ecdhes.js +52 -0
- package/dist/webapi/lib/helpers.js +19 -0
- package/dist/webapi/lib/invalid_key_input.js +27 -0
- package/dist/webapi/lib/is_key_like.js +17 -0
- package/dist/webapi/lib/jwk_to_key.js +107 -0
- package/dist/webapi/lib/jwt_claims_set.js +238 -0
- package/dist/webapi/lib/key_management.js +186 -0
- package/dist/webapi/lib/key_to_jwk.js +31 -0
- package/dist/webapi/lib/normalize_key.js +166 -0
- package/dist/webapi/lib/pbes2kw.js +42 -0
- package/dist/webapi/lib/rsaes.js +24 -0
- package/dist/webapi/lib/signing.js +74 -0
- package/dist/webapi/lib/type_checks.js +41 -0
- package/dist/webapi/lib/validate_algorithms.js +10 -0
- package/dist/webapi/lib/validate_crit.js +33 -0
- package/dist/webapi/util/base64url.js +30 -0
- package/dist/webapi/util/decode_jwt.js +32 -0
- package/dist/webapi/util/decode_protected_header.js +34 -0
- package/dist/webapi/util/errors.js +99 -0
- package/package.json +195 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { decode as b64u } from '../../util/base64url.js';
|
|
2
|
+
import { decrypt } from '../../lib/content_encryption.js';
|
|
3
|
+
import { decodeBase64url } from '../../lib/helpers.js';
|
|
4
|
+
import { JOSEAlgNotAllowed, JOSENotSupported, JWEInvalid } from '../../util/errors.js';
|
|
5
|
+
import { isDisjoint } from '../../lib/type_checks.js';
|
|
6
|
+
import { isObject } from '../../lib/type_checks.js';
|
|
7
|
+
import { decryptKeyManagement } from '../../lib/key_management.js';
|
|
8
|
+
import { decoder, concat, encode } from '../../lib/buffer_utils.js';
|
|
9
|
+
import { generateCek } from '../../lib/content_encryption.js';
|
|
10
|
+
import { validateCrit } from '../../lib/validate_crit.js';
|
|
11
|
+
import { validateAlgorithms } from '../../lib/validate_algorithms.js';
|
|
12
|
+
import { normalizeKey } from '../../lib/normalize_key.js';
|
|
13
|
+
import { checkKeyType } from '../../lib/check_key_type.js';
|
|
14
|
+
import { decompress } from '../../lib/deflate.js';
|
|
15
|
+
export async function flattenedDecrypt(jwe, key, options) {
|
|
16
|
+
if (!isObject(jwe)) {
|
|
17
|
+
throw new JWEInvalid('Flattened JWE must be an object');
|
|
18
|
+
}
|
|
19
|
+
if (jwe.protected === undefined && jwe.header === undefined && jwe.unprotected === undefined) {
|
|
20
|
+
throw new JWEInvalid('JOSE Header missing');
|
|
21
|
+
}
|
|
22
|
+
if (jwe.iv !== undefined && typeof jwe.iv !== 'string') {
|
|
23
|
+
throw new JWEInvalid('JWE Initialization Vector incorrect type');
|
|
24
|
+
}
|
|
25
|
+
if (typeof jwe.ciphertext !== 'string') {
|
|
26
|
+
throw new JWEInvalid('JWE Ciphertext missing or incorrect type');
|
|
27
|
+
}
|
|
28
|
+
if (jwe.tag !== undefined && typeof jwe.tag !== 'string') {
|
|
29
|
+
throw new JWEInvalid('JWE Authentication Tag incorrect type');
|
|
30
|
+
}
|
|
31
|
+
if (jwe.protected !== undefined && typeof jwe.protected !== 'string') {
|
|
32
|
+
throw new JWEInvalid('JWE Protected Header incorrect type');
|
|
33
|
+
}
|
|
34
|
+
if (jwe.encrypted_key !== undefined && typeof jwe.encrypted_key !== 'string') {
|
|
35
|
+
throw new JWEInvalid('JWE Encrypted Key incorrect type');
|
|
36
|
+
}
|
|
37
|
+
if (jwe.aad !== undefined && typeof jwe.aad !== 'string') {
|
|
38
|
+
throw new JWEInvalid('JWE AAD incorrect type');
|
|
39
|
+
}
|
|
40
|
+
if (jwe.header !== undefined && !isObject(jwe.header)) {
|
|
41
|
+
throw new JWEInvalid('JWE Shared Unprotected Header incorrect type');
|
|
42
|
+
}
|
|
43
|
+
if (jwe.unprotected !== undefined && !isObject(jwe.unprotected)) {
|
|
44
|
+
throw new JWEInvalid('JWE Per-Recipient Unprotected Header incorrect type');
|
|
45
|
+
}
|
|
46
|
+
let parsedProt;
|
|
47
|
+
if (jwe.protected) {
|
|
48
|
+
try {
|
|
49
|
+
const protectedHeader = b64u(jwe.protected);
|
|
50
|
+
parsedProt = JSON.parse(decoder.decode(protectedHeader));
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
throw new JWEInvalid('JWE Protected Header is invalid');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (!isDisjoint(parsedProt, jwe.header, jwe.unprotected)) {
|
|
57
|
+
throw new JWEInvalid('JWE Protected, JWE Unprotected Header, and JWE Per-Recipient Unprotected Header Parameter names must be disjoint');
|
|
58
|
+
}
|
|
59
|
+
const joseHeader = {
|
|
60
|
+
...parsedProt,
|
|
61
|
+
...jwe.header,
|
|
62
|
+
...jwe.unprotected,
|
|
63
|
+
};
|
|
64
|
+
validateCrit(JWEInvalid, new Map(), options?.crit, parsedProt, joseHeader);
|
|
65
|
+
if (joseHeader.zip !== undefined && joseHeader.zip !== 'DEF') {
|
|
66
|
+
throw new JOSENotSupported('Unsupported JWE "zip" (Compression Algorithm) Header Parameter value.');
|
|
67
|
+
}
|
|
68
|
+
if (joseHeader.zip !== undefined && !parsedProt?.zip) {
|
|
69
|
+
throw new JWEInvalid('JWE "zip" (Compression Algorithm) Header Parameter MUST be in a protected header.');
|
|
70
|
+
}
|
|
71
|
+
const { alg, enc } = joseHeader;
|
|
72
|
+
if (typeof alg !== 'string' || !alg) {
|
|
73
|
+
throw new JWEInvalid('missing JWE Algorithm (alg) in JWE Header');
|
|
74
|
+
}
|
|
75
|
+
if (typeof enc !== 'string' || !enc) {
|
|
76
|
+
throw new JWEInvalid('missing JWE Encryption Algorithm (enc) in JWE Header');
|
|
77
|
+
}
|
|
78
|
+
const keyManagementAlgorithms = options && validateAlgorithms('keyManagementAlgorithms', options.keyManagementAlgorithms);
|
|
79
|
+
const contentEncryptionAlgorithms = options &&
|
|
80
|
+
validateAlgorithms('contentEncryptionAlgorithms', options.contentEncryptionAlgorithms);
|
|
81
|
+
if ((keyManagementAlgorithms && !keyManagementAlgorithms.has(alg)) ||
|
|
82
|
+
(!keyManagementAlgorithms && alg.startsWith('PBES2'))) {
|
|
83
|
+
throw new JOSEAlgNotAllowed('"alg" (Algorithm) Header Parameter value not allowed');
|
|
84
|
+
}
|
|
85
|
+
if (contentEncryptionAlgorithms && !contentEncryptionAlgorithms.has(enc)) {
|
|
86
|
+
throw new JOSEAlgNotAllowed('"enc" (Encryption Algorithm) Header Parameter value not allowed');
|
|
87
|
+
}
|
|
88
|
+
let encryptedKey;
|
|
89
|
+
if (jwe.encrypted_key !== undefined) {
|
|
90
|
+
encryptedKey = decodeBase64url(jwe.encrypted_key, 'encrypted_key', JWEInvalid);
|
|
91
|
+
}
|
|
92
|
+
let resolvedKey = false;
|
|
93
|
+
if (typeof key === 'function') {
|
|
94
|
+
key = await key(parsedProt, jwe);
|
|
95
|
+
resolvedKey = true;
|
|
96
|
+
}
|
|
97
|
+
checkKeyType(alg === 'dir' ? enc : alg, key, 'decrypt');
|
|
98
|
+
const k = await normalizeKey(key, alg);
|
|
99
|
+
let cek;
|
|
100
|
+
try {
|
|
101
|
+
cek = await decryptKeyManagement(alg, k, encryptedKey, joseHeader, options);
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
if (err instanceof TypeError || err instanceof JWEInvalid || err instanceof JOSENotSupported) {
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
107
|
+
cek = generateCek(enc);
|
|
108
|
+
}
|
|
109
|
+
let iv;
|
|
110
|
+
let tag;
|
|
111
|
+
if (jwe.iv !== undefined) {
|
|
112
|
+
iv = decodeBase64url(jwe.iv, 'iv', JWEInvalid);
|
|
113
|
+
}
|
|
114
|
+
if (jwe.tag !== undefined) {
|
|
115
|
+
tag = decodeBase64url(jwe.tag, 'tag', JWEInvalid);
|
|
116
|
+
}
|
|
117
|
+
const protectedHeader = jwe.protected !== undefined ? encode(jwe.protected) : new Uint8Array();
|
|
118
|
+
let additionalData;
|
|
119
|
+
if (jwe.aad !== undefined) {
|
|
120
|
+
additionalData = concat(protectedHeader, encode('.'), encode(jwe.aad));
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
additionalData = protectedHeader;
|
|
124
|
+
}
|
|
125
|
+
const ciphertext = decodeBase64url(jwe.ciphertext, 'ciphertext', JWEInvalid);
|
|
126
|
+
const plaintext = await decrypt(enc, cek, ciphertext, iv, tag, additionalData);
|
|
127
|
+
const result = { plaintext };
|
|
128
|
+
if (joseHeader.zip === 'DEF') {
|
|
129
|
+
const maxDecompressedLength = options?.maxDecompressedLength ?? 250_000;
|
|
130
|
+
if (maxDecompressedLength === 0) {
|
|
131
|
+
throw new JOSENotSupported('JWE "zip" (Compression Algorithm) Header Parameter is not supported.');
|
|
132
|
+
}
|
|
133
|
+
if (maxDecompressedLength !== Infinity &&
|
|
134
|
+
(!Number.isSafeInteger(maxDecompressedLength) || maxDecompressedLength < 1)) {
|
|
135
|
+
throw new TypeError('maxDecompressedLength must be 0, a positive safe integer, or Infinity');
|
|
136
|
+
}
|
|
137
|
+
result.plaintext = await decompress(plaintext, maxDecompressedLength).catch((cause) => {
|
|
138
|
+
if (cause instanceof JWEInvalid)
|
|
139
|
+
throw cause;
|
|
140
|
+
throw new JWEInvalid('Failed to decompress plaintext', { cause });
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (jwe.protected !== undefined) {
|
|
144
|
+
result.protectedHeader = parsedProt;
|
|
145
|
+
}
|
|
146
|
+
if (jwe.aad !== undefined) {
|
|
147
|
+
result.additionalAuthenticatedData = decodeBase64url(jwe.aad, 'aad', JWEInvalid);
|
|
148
|
+
}
|
|
149
|
+
if (jwe.unprotected !== undefined) {
|
|
150
|
+
result.sharedUnprotectedHeader = jwe.unprotected;
|
|
151
|
+
}
|
|
152
|
+
if (jwe.header !== undefined) {
|
|
153
|
+
result.unprotectedHeader = jwe.header;
|
|
154
|
+
}
|
|
155
|
+
if (resolvedKey) {
|
|
156
|
+
return { ...result, key: k };
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { encode as b64u } from '../../util/base64url.js';
|
|
2
|
+
import { unprotected, assertNotSet } from '../../lib/helpers.js';
|
|
3
|
+
import { encrypt } from '../../lib/content_encryption.js';
|
|
4
|
+
import { encryptKeyManagement } from '../../lib/key_management.js';
|
|
5
|
+
import { JOSENotSupported, JWEInvalid } from '../../util/errors.js';
|
|
6
|
+
import { isDisjoint } from '../../lib/type_checks.js';
|
|
7
|
+
import { concat, encode } from '../../lib/buffer_utils.js';
|
|
8
|
+
import { validateCrit } from '../../lib/validate_crit.js';
|
|
9
|
+
import { normalizeKey } from '../../lib/normalize_key.js';
|
|
10
|
+
import { checkKeyType } from '../../lib/check_key_type.js';
|
|
11
|
+
import { compress } from '../../lib/deflate.js';
|
|
12
|
+
export class FlattenedEncrypt {
|
|
13
|
+
#plaintext;
|
|
14
|
+
#protectedHeader;
|
|
15
|
+
#sharedUnprotectedHeader;
|
|
16
|
+
#unprotectedHeader;
|
|
17
|
+
#aad;
|
|
18
|
+
#cek;
|
|
19
|
+
#iv;
|
|
20
|
+
#keyManagementParameters;
|
|
21
|
+
constructor(plaintext) {
|
|
22
|
+
if (!(plaintext instanceof Uint8Array)) {
|
|
23
|
+
throw new TypeError('plaintext must be an instance of Uint8Array');
|
|
24
|
+
}
|
|
25
|
+
this.#plaintext = plaintext;
|
|
26
|
+
}
|
|
27
|
+
setKeyManagementParameters(parameters) {
|
|
28
|
+
assertNotSet(this.#keyManagementParameters, 'setKeyManagementParameters');
|
|
29
|
+
this.#keyManagementParameters = parameters;
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
setProtectedHeader(protectedHeader) {
|
|
33
|
+
assertNotSet(this.#protectedHeader, 'setProtectedHeader');
|
|
34
|
+
this.#protectedHeader = protectedHeader;
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
setSharedUnprotectedHeader(sharedUnprotectedHeader) {
|
|
38
|
+
assertNotSet(this.#sharedUnprotectedHeader, 'setSharedUnprotectedHeader');
|
|
39
|
+
this.#sharedUnprotectedHeader = sharedUnprotectedHeader;
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
setUnprotectedHeader(unprotectedHeader) {
|
|
43
|
+
assertNotSet(this.#unprotectedHeader, 'setUnprotectedHeader');
|
|
44
|
+
this.#unprotectedHeader = unprotectedHeader;
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
setAdditionalAuthenticatedData(aad) {
|
|
48
|
+
this.#aad = aad;
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
setContentEncryptionKey(cek) {
|
|
52
|
+
assertNotSet(this.#cek, 'setContentEncryptionKey');
|
|
53
|
+
this.#cek = cek;
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
setInitializationVector(iv) {
|
|
57
|
+
assertNotSet(this.#iv, 'setInitializationVector');
|
|
58
|
+
this.#iv = iv;
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
async encrypt(key, options) {
|
|
62
|
+
if (!this.#protectedHeader && !this.#unprotectedHeader && !this.#sharedUnprotectedHeader) {
|
|
63
|
+
throw new JWEInvalid('either setProtectedHeader, setUnprotectedHeader, or sharedUnprotectedHeader must be called before #encrypt()');
|
|
64
|
+
}
|
|
65
|
+
if (!isDisjoint(this.#protectedHeader, this.#unprotectedHeader, this.#sharedUnprotectedHeader)) {
|
|
66
|
+
throw new JWEInvalid('JWE Protected, JWE Shared Unprotected and JWE Per-Recipient Header Parameter names must be disjoint');
|
|
67
|
+
}
|
|
68
|
+
const joseHeader = {
|
|
69
|
+
...this.#protectedHeader,
|
|
70
|
+
...this.#unprotectedHeader,
|
|
71
|
+
...this.#sharedUnprotectedHeader,
|
|
72
|
+
};
|
|
73
|
+
validateCrit(JWEInvalid, new Map(), options?.crit, this.#protectedHeader, joseHeader);
|
|
74
|
+
if (joseHeader.zip !== undefined && joseHeader.zip !== 'DEF') {
|
|
75
|
+
throw new JOSENotSupported('Unsupported JWE "zip" (Compression Algorithm) Header Parameter value.');
|
|
76
|
+
}
|
|
77
|
+
if (joseHeader.zip !== undefined && !this.#protectedHeader?.zip) {
|
|
78
|
+
throw new JWEInvalid('JWE "zip" (Compression Algorithm) Header Parameter MUST be in a protected header.');
|
|
79
|
+
}
|
|
80
|
+
const { alg, enc } = joseHeader;
|
|
81
|
+
if (typeof alg !== 'string' || !alg) {
|
|
82
|
+
throw new JWEInvalid('JWE "alg" (Algorithm) Header Parameter missing or invalid');
|
|
83
|
+
}
|
|
84
|
+
if (typeof enc !== 'string' || !enc) {
|
|
85
|
+
throw new JWEInvalid('JWE "enc" (Encryption Algorithm) Header Parameter missing or invalid');
|
|
86
|
+
}
|
|
87
|
+
let encryptedKey;
|
|
88
|
+
if (this.#cek && (alg === 'dir' || alg === 'ECDH-ES')) {
|
|
89
|
+
throw new TypeError(`setContentEncryptionKey cannot be called with JWE "alg" (Algorithm) Header ${alg}`);
|
|
90
|
+
}
|
|
91
|
+
checkKeyType(alg === 'dir' ? enc : alg, key, 'encrypt');
|
|
92
|
+
let cek;
|
|
93
|
+
{
|
|
94
|
+
let parameters;
|
|
95
|
+
const k = await normalizeKey(key, alg);
|
|
96
|
+
({ cek, encryptedKey, parameters } = await encryptKeyManagement(alg, enc, k, this.#cek, this.#keyManagementParameters));
|
|
97
|
+
if (parameters) {
|
|
98
|
+
if (options && unprotected in options) {
|
|
99
|
+
if (!this.#unprotectedHeader) {
|
|
100
|
+
this.setUnprotectedHeader(parameters);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
this.#unprotectedHeader = { ...this.#unprotectedHeader, ...parameters };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else if (!this.#protectedHeader) {
|
|
107
|
+
this.setProtectedHeader(parameters);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
this.#protectedHeader = { ...this.#protectedHeader, ...parameters };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
let additionalData;
|
|
115
|
+
let protectedHeaderS;
|
|
116
|
+
let protectedHeaderB;
|
|
117
|
+
let aadMember;
|
|
118
|
+
if (this.#protectedHeader) {
|
|
119
|
+
protectedHeaderS = b64u(JSON.stringify(this.#protectedHeader));
|
|
120
|
+
protectedHeaderB = encode(protectedHeaderS);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
protectedHeaderS = '';
|
|
124
|
+
protectedHeaderB = new Uint8Array();
|
|
125
|
+
}
|
|
126
|
+
if (this.#aad) {
|
|
127
|
+
aadMember = b64u(this.#aad);
|
|
128
|
+
const aadMemberBytes = encode(aadMember);
|
|
129
|
+
additionalData = concat(protectedHeaderB, encode('.'), aadMemberBytes);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
additionalData = protectedHeaderB;
|
|
133
|
+
}
|
|
134
|
+
let plaintext = this.#plaintext;
|
|
135
|
+
if (joseHeader.zip === 'DEF') {
|
|
136
|
+
plaintext = await compress(plaintext).catch((cause) => {
|
|
137
|
+
throw new JWEInvalid('Failed to compress plaintext', { cause });
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
const { ciphertext, tag, iv } = await encrypt(enc, plaintext, cek, this.#iv, additionalData);
|
|
141
|
+
const jwe = {
|
|
142
|
+
ciphertext: b64u(ciphertext),
|
|
143
|
+
};
|
|
144
|
+
if (iv) {
|
|
145
|
+
jwe.iv = b64u(iv);
|
|
146
|
+
}
|
|
147
|
+
if (tag) {
|
|
148
|
+
jwe.tag = b64u(tag);
|
|
149
|
+
}
|
|
150
|
+
if (encryptedKey) {
|
|
151
|
+
jwe.encrypted_key = b64u(encryptedKey);
|
|
152
|
+
}
|
|
153
|
+
if (aadMember) {
|
|
154
|
+
jwe.aad = aadMember;
|
|
155
|
+
}
|
|
156
|
+
if (this.#protectedHeader) {
|
|
157
|
+
jwe.protected = protectedHeaderS;
|
|
158
|
+
}
|
|
159
|
+
if (this.#sharedUnprotectedHeader) {
|
|
160
|
+
jwe.unprotected = this.#sharedUnprotectedHeader;
|
|
161
|
+
}
|
|
162
|
+
if (this.#unprotectedHeader) {
|
|
163
|
+
jwe.header = this.#unprotectedHeader;
|
|
164
|
+
}
|
|
165
|
+
return jwe;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { flattenedDecrypt } from '../flattened/decrypt.js';
|
|
2
|
+
import { JWEDecryptionFailed, JWEInvalid } from '../../util/errors.js';
|
|
3
|
+
import { isObject } from '../../lib/type_checks.js';
|
|
4
|
+
export async function generalDecrypt(jwe, key, options) {
|
|
5
|
+
if (!isObject(jwe)) {
|
|
6
|
+
throw new JWEInvalid('General JWE must be an object');
|
|
7
|
+
}
|
|
8
|
+
if (!Array.isArray(jwe.recipients) || !jwe.recipients.every(isObject)) {
|
|
9
|
+
throw new JWEInvalid('JWE Recipients missing or incorrect type');
|
|
10
|
+
}
|
|
11
|
+
if (!jwe.recipients.length) {
|
|
12
|
+
throw new JWEInvalid('JWE Recipients has no members');
|
|
13
|
+
}
|
|
14
|
+
for (const recipient of jwe.recipients) {
|
|
15
|
+
try {
|
|
16
|
+
return await flattenedDecrypt({
|
|
17
|
+
aad: jwe.aad,
|
|
18
|
+
ciphertext: jwe.ciphertext,
|
|
19
|
+
encrypted_key: recipient.encrypted_key,
|
|
20
|
+
header: recipient.header,
|
|
21
|
+
iv: jwe.iv,
|
|
22
|
+
protected: jwe.protected,
|
|
23
|
+
tag: jwe.tag,
|
|
24
|
+
unprotected: jwe.unprotected,
|
|
25
|
+
}, key, options);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
throw new JWEDecryptionFailed();
|
|
31
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { FlattenedEncrypt } from '../flattened/encrypt.js';
|
|
2
|
+
import { unprotected, assertNotSet } from '../../lib/helpers.js';
|
|
3
|
+
import { JOSENotSupported, JWEInvalid } from '../../util/errors.js';
|
|
4
|
+
import { generateCek } from '../../lib/content_encryption.js';
|
|
5
|
+
import { isDisjoint } from '../../lib/type_checks.js';
|
|
6
|
+
import { encryptKeyManagement } from '../../lib/key_management.js';
|
|
7
|
+
import { encode as b64u } from '../../util/base64url.js';
|
|
8
|
+
import { validateCrit } from '../../lib/validate_crit.js';
|
|
9
|
+
import { normalizeKey } from '../../lib/normalize_key.js';
|
|
10
|
+
import { checkKeyType } from '../../lib/check_key_type.js';
|
|
11
|
+
class IndividualRecipient {
|
|
12
|
+
#parent;
|
|
13
|
+
unprotectedHeader;
|
|
14
|
+
keyManagementParameters;
|
|
15
|
+
key;
|
|
16
|
+
options;
|
|
17
|
+
constructor(enc, key, options) {
|
|
18
|
+
this.#parent = enc;
|
|
19
|
+
this.key = key;
|
|
20
|
+
this.options = options;
|
|
21
|
+
}
|
|
22
|
+
setUnprotectedHeader(unprotectedHeader) {
|
|
23
|
+
assertNotSet(this.unprotectedHeader, 'setUnprotectedHeader');
|
|
24
|
+
this.unprotectedHeader = unprotectedHeader;
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
setKeyManagementParameters(parameters) {
|
|
28
|
+
assertNotSet(this.keyManagementParameters, 'setKeyManagementParameters');
|
|
29
|
+
this.keyManagementParameters = parameters;
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
addRecipient(...args) {
|
|
33
|
+
return this.#parent.addRecipient(...args);
|
|
34
|
+
}
|
|
35
|
+
encrypt(...args) {
|
|
36
|
+
return this.#parent.encrypt(...args);
|
|
37
|
+
}
|
|
38
|
+
done() {
|
|
39
|
+
return this.#parent;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export class GeneralEncrypt {
|
|
43
|
+
#plaintext;
|
|
44
|
+
#recipients = [];
|
|
45
|
+
#protectedHeader;
|
|
46
|
+
#unprotectedHeader;
|
|
47
|
+
#aad;
|
|
48
|
+
constructor(plaintext) {
|
|
49
|
+
this.#plaintext = plaintext;
|
|
50
|
+
}
|
|
51
|
+
addRecipient(key, options) {
|
|
52
|
+
const recipient = new IndividualRecipient(this, key, { crit: options?.crit });
|
|
53
|
+
this.#recipients.push(recipient);
|
|
54
|
+
return recipient;
|
|
55
|
+
}
|
|
56
|
+
setProtectedHeader(protectedHeader) {
|
|
57
|
+
assertNotSet(this.#protectedHeader, 'setProtectedHeader');
|
|
58
|
+
this.#protectedHeader = protectedHeader;
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
setSharedUnprotectedHeader(sharedUnprotectedHeader) {
|
|
62
|
+
assertNotSet(this.#unprotectedHeader, 'setSharedUnprotectedHeader');
|
|
63
|
+
this.#unprotectedHeader = sharedUnprotectedHeader;
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
setAdditionalAuthenticatedData(aad) {
|
|
67
|
+
this.#aad = aad;
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
async encrypt() {
|
|
71
|
+
if (!this.#recipients.length) {
|
|
72
|
+
throw new JWEInvalid('at least one recipient must be added');
|
|
73
|
+
}
|
|
74
|
+
if (this.#recipients.length === 1) {
|
|
75
|
+
const [recipient] = this.#recipients;
|
|
76
|
+
const flattened = await new FlattenedEncrypt(this.#plaintext)
|
|
77
|
+
.setAdditionalAuthenticatedData(this.#aad)
|
|
78
|
+
.setProtectedHeader(this.#protectedHeader)
|
|
79
|
+
.setSharedUnprotectedHeader(this.#unprotectedHeader)
|
|
80
|
+
.setUnprotectedHeader(recipient.unprotectedHeader)
|
|
81
|
+
.encrypt(recipient.key, { ...recipient.options });
|
|
82
|
+
const jwe = {
|
|
83
|
+
ciphertext: flattened.ciphertext,
|
|
84
|
+
iv: flattened.iv,
|
|
85
|
+
recipients: [{}],
|
|
86
|
+
tag: flattened.tag,
|
|
87
|
+
};
|
|
88
|
+
if (flattened.aad)
|
|
89
|
+
jwe.aad = flattened.aad;
|
|
90
|
+
if (flattened.protected)
|
|
91
|
+
jwe.protected = flattened.protected;
|
|
92
|
+
if (flattened.unprotected)
|
|
93
|
+
jwe.unprotected = flattened.unprotected;
|
|
94
|
+
if (flattened.encrypted_key)
|
|
95
|
+
jwe.recipients[0].encrypted_key = flattened.encrypted_key;
|
|
96
|
+
if (flattened.header)
|
|
97
|
+
jwe.recipients[0].header = flattened.header;
|
|
98
|
+
return jwe;
|
|
99
|
+
}
|
|
100
|
+
let enc;
|
|
101
|
+
for (let i = 0; i < this.#recipients.length; i++) {
|
|
102
|
+
const recipient = this.#recipients[i];
|
|
103
|
+
if (!isDisjoint(this.#protectedHeader, this.#unprotectedHeader, recipient.unprotectedHeader)) {
|
|
104
|
+
throw new JWEInvalid('JWE Protected, JWE Shared Unprotected and JWE Per-Recipient Header Parameter names must be disjoint');
|
|
105
|
+
}
|
|
106
|
+
const joseHeader = {
|
|
107
|
+
...this.#protectedHeader,
|
|
108
|
+
...this.#unprotectedHeader,
|
|
109
|
+
...recipient.unprotectedHeader,
|
|
110
|
+
};
|
|
111
|
+
const { alg } = joseHeader;
|
|
112
|
+
if (typeof alg !== 'string' || !alg) {
|
|
113
|
+
throw new JWEInvalid('JWE "alg" (Algorithm) Header Parameter missing or invalid');
|
|
114
|
+
}
|
|
115
|
+
if (alg === 'dir' || alg === 'ECDH-ES') {
|
|
116
|
+
throw new JWEInvalid('"dir" and "ECDH-ES" alg may only be used with a single recipient');
|
|
117
|
+
}
|
|
118
|
+
if (typeof joseHeader.enc !== 'string' || !joseHeader.enc) {
|
|
119
|
+
throw new JWEInvalid('JWE "enc" (Encryption Algorithm) Header Parameter missing or invalid');
|
|
120
|
+
}
|
|
121
|
+
if (!enc) {
|
|
122
|
+
enc = joseHeader.enc;
|
|
123
|
+
}
|
|
124
|
+
else if (enc !== joseHeader.enc) {
|
|
125
|
+
throw new JWEInvalid('JWE "enc" (Encryption Algorithm) Header Parameter must be the same for all recipients');
|
|
126
|
+
}
|
|
127
|
+
validateCrit(JWEInvalid, new Map(), recipient.options.crit, this.#protectedHeader, joseHeader);
|
|
128
|
+
if (joseHeader.zip !== undefined && joseHeader.zip !== 'DEF') {
|
|
129
|
+
throw new JOSENotSupported('Unsupported JWE "zip" (Compression Algorithm) Header Parameter value.');
|
|
130
|
+
}
|
|
131
|
+
if (joseHeader.zip !== undefined && !this.#protectedHeader?.zip) {
|
|
132
|
+
throw new JWEInvalid('JWE "zip" (Compression Algorithm) Header Parameter MUST be in a protected header.');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const cek = generateCek(enc);
|
|
136
|
+
const jwe = {
|
|
137
|
+
ciphertext: '',
|
|
138
|
+
recipients: [],
|
|
139
|
+
};
|
|
140
|
+
for (let i = 0; i < this.#recipients.length; i++) {
|
|
141
|
+
const recipient = this.#recipients[i];
|
|
142
|
+
const target = {};
|
|
143
|
+
jwe.recipients.push(target);
|
|
144
|
+
if (i === 0) {
|
|
145
|
+
const flattened = await new FlattenedEncrypt(this.#plaintext)
|
|
146
|
+
.setAdditionalAuthenticatedData(this.#aad)
|
|
147
|
+
.setContentEncryptionKey(cek)
|
|
148
|
+
.setProtectedHeader(this.#protectedHeader)
|
|
149
|
+
.setSharedUnprotectedHeader(this.#unprotectedHeader)
|
|
150
|
+
.setUnprotectedHeader(recipient.unprotectedHeader)
|
|
151
|
+
.setKeyManagementParameters(recipient.keyManagementParameters)
|
|
152
|
+
.encrypt(recipient.key, {
|
|
153
|
+
...recipient.options,
|
|
154
|
+
[unprotected]: true,
|
|
155
|
+
});
|
|
156
|
+
jwe.ciphertext = flattened.ciphertext;
|
|
157
|
+
jwe.iv = flattened.iv;
|
|
158
|
+
jwe.tag = flattened.tag;
|
|
159
|
+
if (flattened.aad)
|
|
160
|
+
jwe.aad = flattened.aad;
|
|
161
|
+
if (flattened.protected)
|
|
162
|
+
jwe.protected = flattened.protected;
|
|
163
|
+
if (flattened.unprotected)
|
|
164
|
+
jwe.unprotected = flattened.unprotected;
|
|
165
|
+
target.encrypted_key = flattened.encrypted_key;
|
|
166
|
+
if (flattened.header)
|
|
167
|
+
target.header = flattened.header;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const alg = recipient.unprotectedHeader?.alg ||
|
|
171
|
+
this.#protectedHeader?.alg ||
|
|
172
|
+
this.#unprotectedHeader?.alg;
|
|
173
|
+
checkKeyType(alg === 'dir' ? enc : alg, recipient.key, 'encrypt');
|
|
174
|
+
const k = await normalizeKey(recipient.key, alg);
|
|
175
|
+
const { encryptedKey, parameters } = await encryptKeyManagement(alg, enc, k, cek, recipient.keyManagementParameters);
|
|
176
|
+
target.encrypted_key = b64u(encryptedKey);
|
|
177
|
+
if (recipient.unprotectedHeader || parameters)
|
|
178
|
+
target.header = { ...recipient.unprotectedHeader, ...parameters };
|
|
179
|
+
}
|
|
180
|
+
return jwe;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { importJWK } from '../key/import.js';
|
|
2
|
+
import { isObject } from '../lib/type_checks.js';
|
|
3
|
+
import { JWSInvalid } from '../util/errors.js';
|
|
4
|
+
export async function EmbeddedJWK(protectedHeader, token) {
|
|
5
|
+
const joseHeader = {
|
|
6
|
+
...protectedHeader,
|
|
7
|
+
...token?.header,
|
|
8
|
+
};
|
|
9
|
+
if (!isObject(joseHeader.jwk)) {
|
|
10
|
+
throw new JWSInvalid('"jwk" (JSON Web Key) Header Parameter must be a JSON object');
|
|
11
|
+
}
|
|
12
|
+
const key = await importJWK({ ...joseHeader.jwk, ext: true }, joseHeader.alg);
|
|
13
|
+
if (key instanceof Uint8Array || key.type !== 'public') {
|
|
14
|
+
throw new JWSInvalid('"jwk" (JSON Web Key) Header Parameter must be a public key');
|
|
15
|
+
}
|
|
16
|
+
return key;
|
|
17
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { digest } from '../lib/helpers.js';
|
|
2
|
+
import { encode as b64u } from '../util/base64url.js';
|
|
3
|
+
import { JOSENotSupported, JWKInvalid } from '../util/errors.js';
|
|
4
|
+
import { encode } from '../lib/buffer_utils.js';
|
|
5
|
+
import { isKeyLike } from '../lib/is_key_like.js';
|
|
6
|
+
import { isJWK } from '../lib/type_checks.js';
|
|
7
|
+
import { exportJWK } from '../key/export.js';
|
|
8
|
+
import { invalidKeyInput } from '../lib/invalid_key_input.js';
|
|
9
|
+
const check = (value, description) => {
|
|
10
|
+
if (typeof value !== 'string' || !value) {
|
|
11
|
+
throw new JWKInvalid(`${description} missing or invalid`);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
export async function calculateJwkThumbprint(key, digestAlgorithm) {
|
|
15
|
+
let jwk;
|
|
16
|
+
if (isJWK(key)) {
|
|
17
|
+
jwk = key;
|
|
18
|
+
}
|
|
19
|
+
else if (isKeyLike(key)) {
|
|
20
|
+
jwk = await exportJWK(key);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
throw new TypeError(invalidKeyInput(key, 'CryptoKey', 'KeyObject', 'JSON Web Key'));
|
|
24
|
+
}
|
|
25
|
+
digestAlgorithm ??= 'sha256';
|
|
26
|
+
if (digestAlgorithm !== 'sha256' &&
|
|
27
|
+
digestAlgorithm !== 'sha384' &&
|
|
28
|
+
digestAlgorithm !== 'sha512') {
|
|
29
|
+
throw new TypeError('digestAlgorithm must one of "sha256", "sha384", or "sha512"');
|
|
30
|
+
}
|
|
31
|
+
let components;
|
|
32
|
+
switch (jwk.kty) {
|
|
33
|
+
case 'AKP':
|
|
34
|
+
check(jwk.alg, '"alg" (Algorithm) Parameter');
|
|
35
|
+
check(jwk.pub, '"pub" (Public key) Parameter');
|
|
36
|
+
components = { alg: jwk.alg, kty: jwk.kty, pub: jwk.pub };
|
|
37
|
+
break;
|
|
38
|
+
case 'EC':
|
|
39
|
+
check(jwk.crv, '"crv" (Curve) Parameter');
|
|
40
|
+
check(jwk.x, '"x" (X Coordinate) Parameter');
|
|
41
|
+
check(jwk.y, '"y" (Y Coordinate) Parameter');
|
|
42
|
+
components = { crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y };
|
|
43
|
+
break;
|
|
44
|
+
case 'OKP':
|
|
45
|
+
check(jwk.crv, '"crv" (Subtype of Key Pair) Parameter');
|
|
46
|
+
check(jwk.x, '"x" (Public Key) Parameter');
|
|
47
|
+
components = { crv: jwk.crv, kty: jwk.kty, x: jwk.x };
|
|
48
|
+
break;
|
|
49
|
+
case 'RSA':
|
|
50
|
+
check(jwk.e, '"e" (Exponent) Parameter');
|
|
51
|
+
check(jwk.n, '"n" (Modulus) Parameter');
|
|
52
|
+
components = { e: jwk.e, kty: jwk.kty, n: jwk.n };
|
|
53
|
+
break;
|
|
54
|
+
case 'oct':
|
|
55
|
+
check(jwk.k, '"k" (Key Value) Parameter');
|
|
56
|
+
components = { k: jwk.k, kty: jwk.kty };
|
|
57
|
+
break;
|
|
58
|
+
default:
|
|
59
|
+
throw new JOSENotSupported('"kty" (Key Type) Parameter missing or unsupported');
|
|
60
|
+
}
|
|
61
|
+
const data = encode(JSON.stringify(components));
|
|
62
|
+
return b64u(await digest(digestAlgorithm, data));
|
|
63
|
+
}
|
|
64
|
+
export async function calculateJwkThumbprintUri(key, digestAlgorithm) {
|
|
65
|
+
digestAlgorithm ??= 'sha256';
|
|
66
|
+
const thumbprint = await calculateJwkThumbprint(key, digestAlgorithm);
|
|
67
|
+
return `urn:ietf:params:oauth:jwk-thumbprint:sha-${digestAlgorithm.slice(-3)}:${thumbprint}`;
|
|
68
|
+
}
|