astro-ghostcms/.pnpm-store/v3/files/dc/7f7ca3b4e1956a75d2c34e8b0d2...

182 lines
7.3 KiB
Plaintext

import { FlattenedEncrypt, unprotected } from '../flattened/encrypt.js';
import { JOSENotSupported, JWEInvalid } from '../../util/errors.js';
import generateCek from '../../lib/cek.js';
import isDisjoint from '../../lib/is_disjoint.js';
import encryptKeyManagement from '../../lib/encrypt_key_management.js';
import { encode as base64url } from '../../runtime/base64url.js';
import validateCrit from '../../lib/validate_crit.js';
class IndividualRecipient {
parent;
unprotectedHeader;
key;
options;
constructor(enc, key, options) {
this.parent = enc;
this.key = key;
this.options = options;
}
setUnprotectedHeader(unprotectedHeader) {
if (this.unprotectedHeader) {
throw new TypeError('setUnprotectedHeader can only be called once');
}
this.unprotectedHeader = unprotectedHeader;
return this;
}
addRecipient(...args) {
return this.parent.addRecipient(...args);
}
encrypt(...args) {
return this.parent.encrypt(...args);
}
done() {
return this.parent;
}
}
export class GeneralEncrypt {
_plaintext;
_recipients = [];
_protectedHeader;
_unprotectedHeader;
_aad;
constructor(plaintext) {
this._plaintext = plaintext;
}
addRecipient(key, options) {
const recipient = new IndividualRecipient(this, key, { crit: options?.crit });
this._recipients.push(recipient);
return recipient;
}
setProtectedHeader(protectedHeader) {
if (this._protectedHeader) {
throw new TypeError('setProtectedHeader can only be called once');
}
this._protectedHeader = protectedHeader;
return this;
}
setSharedUnprotectedHeader(sharedUnprotectedHeader) {
if (this._unprotectedHeader) {
throw new TypeError('setSharedUnprotectedHeader can only be called once');
}
this._unprotectedHeader = sharedUnprotectedHeader;
return this;
}
setAdditionalAuthenticatedData(aad) {
this._aad = aad;
return this;
}
async encrypt() {
if (!this._recipients.length) {
throw new JWEInvalid('at least one recipient must be added');
}
if (this._recipients.length === 1) {
const [recipient] = this._recipients;
const flattened = await new FlattenedEncrypt(this._plaintext)
.setAdditionalAuthenticatedData(this._aad)
.setProtectedHeader(this._protectedHeader)
.setSharedUnprotectedHeader(this._unprotectedHeader)
.setUnprotectedHeader(recipient.unprotectedHeader)
.encrypt(recipient.key, { ...recipient.options });
const jwe = {
ciphertext: flattened.ciphertext,
iv: flattened.iv,
recipients: [{}],
tag: flattened.tag,
};
if (flattened.aad)
jwe.aad = flattened.aad;
if (flattened.protected)
jwe.protected = flattened.protected;
if (flattened.unprotected)
jwe.unprotected = flattened.unprotected;
if (flattened.encrypted_key)
jwe.recipients[0].encrypted_key = flattened.encrypted_key;
if (flattened.header)
jwe.recipients[0].header = flattened.header;
return jwe;
}
let enc;
for (let i = 0; i < this._recipients.length; i++) {
const recipient = this._recipients[i];
if (!isDisjoint(this._protectedHeader, this._unprotectedHeader, recipient.unprotectedHeader)) {
throw new JWEInvalid('JWE Protected, JWE Shared Unprotected and JWE Per-Recipient Header Parameter names must be disjoint');
}
const joseHeader = {
...this._protectedHeader,
...this._unprotectedHeader,
...recipient.unprotectedHeader,
};
const { alg } = joseHeader;
if (typeof alg !== 'string' || !alg) {
throw new JWEInvalid('JWE "alg" (Algorithm) Header Parameter missing or invalid');
}
if (alg === 'dir' || alg === 'ECDH-ES') {
throw new JWEInvalid('"dir" and "ECDH-ES" alg may only be used with a single recipient');
}
if (typeof joseHeader.enc !== 'string' || !joseHeader.enc) {
throw new JWEInvalid('JWE "enc" (Encryption Algorithm) Header Parameter missing or invalid');
}
if (!enc) {
enc = joseHeader.enc;
}
else if (enc !== joseHeader.enc) {
throw new JWEInvalid('JWE "enc" (Encryption Algorithm) Header Parameter must be the same for all recipients');
}
validateCrit(JWEInvalid, new Map(), recipient.options.crit, this._protectedHeader, joseHeader);
if (joseHeader.zip !== undefined) {
throw new JOSENotSupported('JWE "zip" (Compression Algorithm) Header Parameter is not supported.');
}
}
const cek = generateCek(enc);
const jwe = {
ciphertext: '',
iv: '',
recipients: [],
tag: '',
};
for (let i = 0; i < this._recipients.length; i++) {
const recipient = this._recipients[i];
const target = {};
jwe.recipients.push(target);
const joseHeader = {
...this._protectedHeader,
...this._unprotectedHeader,
...recipient.unprotectedHeader,
};
const p2c = joseHeader.alg.startsWith('PBES2') ? 2048 + i : undefined;
if (i === 0) {
const flattened = await new FlattenedEncrypt(this._plaintext)
.setAdditionalAuthenticatedData(this._aad)
.setContentEncryptionKey(cek)
.setProtectedHeader(this._protectedHeader)
.setSharedUnprotectedHeader(this._unprotectedHeader)
.setUnprotectedHeader(recipient.unprotectedHeader)
.setKeyManagementParameters({ p2c })
.encrypt(recipient.key, {
...recipient.options,
[unprotected]: true,
});
jwe.ciphertext = flattened.ciphertext;
jwe.iv = flattened.iv;
jwe.tag = flattened.tag;
if (flattened.aad)
jwe.aad = flattened.aad;
if (flattened.protected)
jwe.protected = flattened.protected;
if (flattened.unprotected)
jwe.unprotected = flattened.unprotected;
target.encrypted_key = flattened.encrypted_key;
if (flattened.header)
target.header = flattened.header;
continue;
}
const { encryptedKey, parameters } = await encryptKeyManagement(recipient.unprotectedHeader?.alg ||
this._protectedHeader?.alg ||
this._unprotectedHeader?.alg, enc, recipient.key, cek, { p2c });
target.encrypted_key = base64url(encryptedKey);
if (recipient.unprotectedHeader || parameters)
target.header = { ...recipient.unprotectedHeader, ...parameters };
}
return jwe;
}
}