170 lines
6.4 KiB
JavaScript
170 lines
6.4 KiB
JavaScript
import { encode as b64u } from '../../util/base64url.js';
|
|
import { unprotected } from '../../lib/private_symbols.js';
|
|
import { encrypt } from '../../lib/encrypt.js';
|
|
import { encryptKeyManagement } from '../../lib/encrypt_key_management.js';
|
|
import { JOSENotSupported, JWEInvalid } from '../../util/errors.js';
|
|
import { isDisjoint } from '../../lib/is_disjoint.js';
|
|
import { concat, encode } from '../../lib/buffer_utils.js';
|
|
import { validateCrit } from '../../lib/validate_crit.js';
|
|
import { normalizeKey } from '../../lib/normalize_key.js';
|
|
import { checkKeyType } from '../../lib/check_key_type.js';
|
|
export class FlattenedEncrypt {
|
|
#plaintext;
|
|
#protectedHeader;
|
|
#sharedUnprotectedHeader;
|
|
#unprotectedHeader;
|
|
#aad;
|
|
#cek;
|
|
#iv;
|
|
#keyManagementParameters;
|
|
constructor(plaintext) {
|
|
if (!(plaintext instanceof Uint8Array)) {
|
|
throw new TypeError('plaintext must be an instance of Uint8Array');
|
|
}
|
|
this.#plaintext = plaintext;
|
|
}
|
|
setKeyManagementParameters(parameters) {
|
|
if (this.#keyManagementParameters) {
|
|
throw new TypeError('setKeyManagementParameters can only be called once');
|
|
}
|
|
this.#keyManagementParameters = parameters;
|
|
return this;
|
|
}
|
|
setProtectedHeader(protectedHeader) {
|
|
if (this.#protectedHeader) {
|
|
throw new TypeError('setProtectedHeader can only be called once');
|
|
}
|
|
this.#protectedHeader = protectedHeader;
|
|
return this;
|
|
}
|
|
setSharedUnprotectedHeader(sharedUnprotectedHeader) {
|
|
if (this.#sharedUnprotectedHeader) {
|
|
throw new TypeError('setSharedUnprotectedHeader can only be called once');
|
|
}
|
|
this.#sharedUnprotectedHeader = sharedUnprotectedHeader;
|
|
return this;
|
|
}
|
|
setUnprotectedHeader(unprotectedHeader) {
|
|
if (this.#unprotectedHeader) {
|
|
throw new TypeError('setUnprotectedHeader can only be called once');
|
|
}
|
|
this.#unprotectedHeader = unprotectedHeader;
|
|
return this;
|
|
}
|
|
setAdditionalAuthenticatedData(aad) {
|
|
this.#aad = aad;
|
|
return this;
|
|
}
|
|
setContentEncryptionKey(cek) {
|
|
if (this.#cek) {
|
|
throw new TypeError('setContentEncryptionKey can only be called once');
|
|
}
|
|
this.#cek = cek;
|
|
return this;
|
|
}
|
|
setInitializationVector(iv) {
|
|
if (this.#iv) {
|
|
throw new TypeError('setInitializationVector can only be called once');
|
|
}
|
|
this.#iv = iv;
|
|
return this;
|
|
}
|
|
async encrypt(key, options) {
|
|
if (!this.#protectedHeader && !this.#unprotectedHeader && !this.#sharedUnprotectedHeader) {
|
|
throw new JWEInvalid('either setProtectedHeader, setUnprotectedHeader, or sharedUnprotectedHeader must be called before #encrypt()');
|
|
}
|
|
if (!isDisjoint(this.#protectedHeader, this.#unprotectedHeader, this.#sharedUnprotectedHeader)) {
|
|
throw new JWEInvalid('JWE Protected, JWE Shared Unprotected and JWE Per-Recipient Header Parameter names must be disjoint');
|
|
}
|
|
const joseHeader = {
|
|
...this.#protectedHeader,
|
|
...this.#unprotectedHeader,
|
|
...this.#sharedUnprotectedHeader,
|
|
};
|
|
validateCrit(JWEInvalid, new Map(), options?.crit, this.#protectedHeader, joseHeader);
|
|
if (joseHeader.zip !== undefined) {
|
|
throw new JOSENotSupported('JWE "zip" (Compression Algorithm) Header Parameter is not supported.');
|
|
}
|
|
const { alg, enc } = joseHeader;
|
|
if (typeof alg !== 'string' || !alg) {
|
|
throw new JWEInvalid('JWE "alg" (Algorithm) Header Parameter missing or invalid');
|
|
}
|
|
if (typeof enc !== 'string' || !enc) {
|
|
throw new JWEInvalid('JWE "enc" (Encryption Algorithm) Header Parameter missing or invalid');
|
|
}
|
|
let encryptedKey;
|
|
if (this.#cek && (alg === 'dir' || alg === 'ECDH-ES')) {
|
|
throw new TypeError(`setContentEncryptionKey cannot be called with JWE "alg" (Algorithm) Header ${alg}`);
|
|
}
|
|
checkKeyType(alg === 'dir' ? enc : alg, key, 'encrypt');
|
|
let cek;
|
|
{
|
|
let parameters;
|
|
const k = await normalizeKey(key, alg);
|
|
({ cek, encryptedKey, parameters } = await encryptKeyManagement(alg, enc, k, this.#cek, this.#keyManagementParameters));
|
|
if (parameters) {
|
|
if (options && unprotected in options) {
|
|
if (!this.#unprotectedHeader) {
|
|
this.setUnprotectedHeader(parameters);
|
|
}
|
|
else {
|
|
this.#unprotectedHeader = { ...this.#unprotectedHeader, ...parameters };
|
|
}
|
|
}
|
|
else if (!this.#protectedHeader) {
|
|
this.setProtectedHeader(parameters);
|
|
}
|
|
else {
|
|
this.#protectedHeader = { ...this.#protectedHeader, ...parameters };
|
|
}
|
|
}
|
|
}
|
|
let additionalData;
|
|
let protectedHeaderS;
|
|
let protectedHeaderB;
|
|
let aadMember;
|
|
if (this.#protectedHeader) {
|
|
protectedHeaderS = b64u(JSON.stringify(this.#protectedHeader));
|
|
protectedHeaderB = encode(protectedHeaderS);
|
|
}
|
|
else {
|
|
protectedHeaderS = '';
|
|
protectedHeaderB = new Uint8Array();
|
|
}
|
|
if (this.#aad) {
|
|
aadMember = b64u(this.#aad);
|
|
const aadMemberBytes = encode(aadMember);
|
|
additionalData = concat(protectedHeaderB, encode('.'), aadMemberBytes);
|
|
}
|
|
else {
|
|
additionalData = protectedHeaderB;
|
|
}
|
|
const { ciphertext, tag, iv } = await encrypt(enc, this.#plaintext, cek, this.#iv, additionalData);
|
|
const jwe = {
|
|
ciphertext: b64u(ciphertext),
|
|
};
|
|
if (iv) {
|
|
jwe.iv = b64u(iv);
|
|
}
|
|
if (tag) {
|
|
jwe.tag = b64u(tag);
|
|
}
|
|
if (encryptedKey) {
|
|
jwe.encrypted_key = b64u(encryptedKey);
|
|
}
|
|
if (aadMember) {
|
|
jwe.aad = aadMember;
|
|
}
|
|
if (this.#protectedHeader) {
|
|
jwe.protected = protectedHeaderS;
|
|
}
|
|
if (this.#sharedUnprotectedHeader) {
|
|
jwe.unprotected = this.#sharedUnprotectedHeader;
|
|
}
|
|
if (this.#unprotectedHeader) {
|
|
jwe.header = this.#unprotectedHeader;
|
|
}
|
|
return jwe;
|
|
}
|
|
}
|