176 lines
6.9 KiB
JavaScript
176 lines
6.9 KiB
JavaScript
|
import { encode as base64url } from '../../runtime/base64url.js';
|
||
|
import encrypt from '../../runtime/encrypt.js';
|
||
|
import { deflate } from '../../runtime/zlib.js';
|
||
|
import generateIv from '../../lib/iv.js';
|
||
|
import encryptKeyManagement from '../../lib/encrypt_key_management.js';
|
||
|
import { JOSENotSupported, JWEInvalid } from '../../util/errors.js';
|
||
|
import isDisjoint from '../../lib/is_disjoint.js';
|
||
|
import { encoder, decoder, concat } from '../../lib/buffer_utils.js';
|
||
|
import validateCrit from '../../lib/validate_crit.js';
|
||
|
export const unprotected = Symbol();
|
||
|
export class FlattenedEncrypt {
|
||
|
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 === null || options === void 0 ? void 0 : options.crit, this._protectedHeader, joseHeader);
|
||
|
if (joseHeader.zip !== undefined) {
|
||
|
if (!this._protectedHeader || !this._protectedHeader.zip) {
|
||
|
throw new JWEInvalid('JWE "zip" (Compression Algorithm) Header MUST be integrity protected');
|
||
|
}
|
||
|
if (joseHeader.zip !== 'DEF') {
|
||
|
throw new JOSENotSupported('Unsupported JWE "zip" (Compression Algorithm) Header Parameter value');
|
||
|
}
|
||
|
}
|
||
|
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 (alg === 'dir') {
|
||
|
if (this._cek) {
|
||
|
throw new TypeError('setContentEncryptionKey cannot be called when using Direct Encryption');
|
||
|
}
|
||
|
}
|
||
|
else if (alg === 'ECDH-ES') {
|
||
|
if (this._cek) {
|
||
|
throw new TypeError('setContentEncryptionKey cannot be called when using Direct Key Agreement');
|
||
|
}
|
||
|
}
|
||
|
let cek;
|
||
|
{
|
||
|
let parameters;
|
||
|
({ cek, encryptedKey, parameters } = await encryptKeyManagement(alg, enc, key, 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 };
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
this._iv || (this._iv = generateIv(enc));
|
||
|
let additionalData;
|
||
|
let protectedHeader;
|
||
|
let aadMember;
|
||
|
if (this._protectedHeader) {
|
||
|
protectedHeader = encoder.encode(base64url(JSON.stringify(this._protectedHeader)));
|
||
|
}
|
||
|
else {
|
||
|
protectedHeader = encoder.encode('');
|
||
|
}
|
||
|
if (this._aad) {
|
||
|
aadMember = base64url(this._aad);
|
||
|
additionalData = concat(protectedHeader, encoder.encode('.'), encoder.encode(aadMember));
|
||
|
}
|
||
|
else {
|
||
|
additionalData = protectedHeader;
|
||
|
}
|
||
|
let ciphertext;
|
||
|
let tag;
|
||
|
if (joseHeader.zip === 'DEF') {
|
||
|
const deflated = await ((options === null || options === void 0 ? void 0 : options.deflateRaw) || deflate)(this._plaintext);
|
||
|
({ ciphertext, tag } = await encrypt(enc, deflated, cek, this._iv, additionalData));
|
||
|
}
|
||
|
else {
|
||
|
;
|
||
|
({ ciphertext, tag } = await encrypt(enc, this._plaintext, cek, this._iv, additionalData));
|
||
|
}
|
||
|
const jwe = {
|
||
|
ciphertext: base64url(ciphertext),
|
||
|
iv: base64url(this._iv),
|
||
|
tag: base64url(tag),
|
||
|
};
|
||
|
if (encryptedKey) {
|
||
|
jwe.encrypted_key = base64url(encryptedKey);
|
||
|
}
|
||
|
if (aadMember) {
|
||
|
jwe.aad = aadMember;
|
||
|
}
|
||
|
if (this._protectedHeader) {
|
||
|
jwe.protected = decoder.decode(protectedHeader);
|
||
|
}
|
||
|
if (this._sharedUnprotectedHeader) {
|
||
|
jwe.unprotected = this._sharedUnprotectedHeader;
|
||
|
}
|
||
|
if (this._unprotectedHeader) {
|
||
|
jwe.header = this._unprotectedHeader;
|
||
|
}
|
||
|
return jwe;
|
||
|
}
|
||
|
}
|