678 lines
20 KiB
JavaScript
678 lines
20 KiB
JavaScript
|
/*!
|
||
|
* jwe/encrypt.js - Encrypt to a JWE
|
||
|
*
|
||
|
* Copyright (c) 2015 Cisco Systems, Inc. See LICENSE file.
|
||
|
*/
|
||
|
"use strict";
|
||
|
|
||
|
var util = require("../util"),
|
||
|
generateCEK = require("./helpers").generateCEK,
|
||
|
JWK = require("../jwk"),
|
||
|
slice = require("./helpers").slice,
|
||
|
pako = require("pako"),
|
||
|
CONSTANTS = require("../algorithms/constants");
|
||
|
|
||
|
var assign = require("lodash/assign");
|
||
|
var clone = require("lodash/clone");
|
||
|
var DEFAULTS = require("./defaults");
|
||
|
|
||
|
/**
|
||
|
* @class JWE.Encrypter
|
||
|
* @classdesc
|
||
|
* Generator of encrypted data.
|
||
|
*
|
||
|
* @description
|
||
|
* **NOTE:** This class cannot be instantiated directly. Instead call {@link
|
||
|
* JWE.createEncrypt}.
|
||
|
*/
|
||
|
function JWEEncrypter(cfg, fields, recipients) {
|
||
|
var finalized = false,
|
||
|
format = cfg.format || "general",
|
||
|
protectAll = !!cfg.protectAll,
|
||
|
content = Buffer.alloc(0);
|
||
|
|
||
|
/**
|
||
|
* @member {String} JWE.Encrypter#zip
|
||
|
* @readonly
|
||
|
* @description
|
||
|
* Indicates the compression algorithm applied to the plaintext
|
||
|
* before it is encrypted. The possible values are:
|
||
|
*
|
||
|
* + **`"DEF"`**: Compress the plaintext using the DEFLATE algorithm.
|
||
|
* + **`""`**: Do not compress the plaintext.
|
||
|
*/
|
||
|
Object.defineProperty(this, "zip", {
|
||
|
get: function() {
|
||
|
return fields.zip || "";
|
||
|
},
|
||
|
enumerable: true
|
||
|
});
|
||
|
/**
|
||
|
* @member {Boolean} JWE.Encrypter#compact
|
||
|
* @readonly
|
||
|
* @description
|
||
|
* Indicates whether the output of this encryption generator is
|
||
|
* using the Compact serialization (`true`) or the JSON
|
||
|
* serialization (`false`).
|
||
|
*/
|
||
|
Object.defineProperty(this, "compact", {
|
||
|
get: function() { return "compact" === format; },
|
||
|
enumerable: true
|
||
|
});
|
||
|
/**
|
||
|
* @member {String} JWE.Encrypter#format
|
||
|
* @readonly
|
||
|
* @description
|
||
|
* Indicates the format the output of this encryption generator takes.
|
||
|
*/
|
||
|
Object.defineProperty(this, "format", {
|
||
|
get: function() { return format; },
|
||
|
enumerable: true
|
||
|
});
|
||
|
/**
|
||
|
* @member {String[]} JWE.Encrypter#protected
|
||
|
* @readonly
|
||
|
* @description
|
||
|
* The header parameter names that are protected. Protected header fields
|
||
|
* are first serialized to UTF-8 then encoded as util.base64url, then used as
|
||
|
* the additional authenticated data in the encryption operation.
|
||
|
*/
|
||
|
Object.defineProperty(this, "protected", {
|
||
|
get: function() {
|
||
|
return clone(cfg.protect);
|
||
|
},
|
||
|
enumerable: true
|
||
|
});
|
||
|
/**
|
||
|
* @member {Object} JWE.Encrypter#header
|
||
|
* @readonly
|
||
|
* @description
|
||
|
* The global header parameters, both protected and unprotected. Call
|
||
|
* {@link JWE.Encrypter#protected} to determine which parameters will
|
||
|
* be protected.
|
||
|
*/
|
||
|
Object.defineProperty(this, "header", {
|
||
|
get: function() {
|
||
|
return clone(fields);
|
||
|
},
|
||
|
enumerable: true
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* @method JWE.Encrypter#update
|
||
|
* @description
|
||
|
* Updates the plaintext data for the encryption generator. The plaintext
|
||
|
* is appended to the end of any other plaintext already applied.
|
||
|
*
|
||
|
* If {data} is a Buffer, {encoding} is ignored. Otherwise, {data} is
|
||
|
* converted to a Buffer internally to {encoding}.
|
||
|
*
|
||
|
* @param {Buffer|String} [data] The plaintext to apply.
|
||
|
* @param {String} [encoding] The encoding of the plaintext.
|
||
|
* @returns {JWE.Encrypter} This encryption generator.
|
||
|
* @throws {Error} If ciphertext has already been generated.
|
||
|
*/
|
||
|
Object.defineProperty(this, "update", {
|
||
|
value: function(data, encoding) {
|
||
|
if (finalized) {
|
||
|
throw new Error("already final");
|
||
|
}
|
||
|
if (data != null) {
|
||
|
data = util.asBuffer(data, encoding);
|
||
|
if (content.length) {
|
||
|
content = Buffer.concat([content, data],
|
||
|
content.length + data.length);
|
||
|
} else {
|
||
|
content = data;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
});
|
||
|
/**
|
||
|
* @method JWE.Encrypter#final
|
||
|
* @description
|
||
|
* Finishes the encryption operation.
|
||
|
*
|
||
|
* The returned Promise, when fulfilled, is the JSON Web Encryption (JWE)
|
||
|
* object, either in the Compact (if {@link JWE.Encrypter#compact} is
|
||
|
* `true`) or the JSON serialization.
|
||
|
*
|
||
|
* @param {Buffer|String} [data] The final plaintext data to apply.
|
||
|
* @param {String} [encoding] The encoding of the final plaintext data
|
||
|
* (if any).
|
||
|
* @returns {Promise} A promise for the encryption operation.
|
||
|
* @throws {Error} If ciphertext has already been generated.
|
||
|
*/
|
||
|
Object.defineProperty(this, "final", {
|
||
|
value: function(data, encoding) {
|
||
|
if (finalized) {
|
||
|
return Promise.reject(new Error("already final"));
|
||
|
}
|
||
|
|
||
|
// last-minute data
|
||
|
this.update(data, encoding);
|
||
|
|
||
|
// mark as done...ish
|
||
|
finalized = true;
|
||
|
var promise = Promise.resolve({});
|
||
|
|
||
|
// determine CEK and IV
|
||
|
var encAlg = fields.enc;
|
||
|
var encKey;
|
||
|
promise = promise.then(function(jwe) {
|
||
|
if (cfg.cek) {
|
||
|
encKey = JWK.asKey(cfg.cek);
|
||
|
}
|
||
|
return jwe;
|
||
|
});
|
||
|
|
||
|
// process recipients
|
||
|
promise = promise.then(function(jwe) {
|
||
|
var procR = function(r, one) {
|
||
|
var props = {};
|
||
|
props = assign(props, fields);
|
||
|
props = assign(props, r.header);
|
||
|
|
||
|
var algKey = r.key,
|
||
|
algAlg = props.alg;
|
||
|
|
||
|
// generate Ephemeral EC Key
|
||
|
var tks,
|
||
|
rpromise;
|
||
|
if ((props.alg || "").indexOf("ECDH-ES") === 0) {
|
||
|
tks = algKey.keystore.temp();
|
||
|
if (r.epk) {
|
||
|
rpromise = Promise.resolve(r.epk).
|
||
|
then(function(epk) {
|
||
|
r.header.epk = epk.toJSON(false, ["kid"]);
|
||
|
props.epk = epk.toObject(true, ["kid"]);
|
||
|
});
|
||
|
} else {
|
||
|
rpromise = tks.generate("EC", algKey.get("crv")).
|
||
|
then(function(epk) {
|
||
|
r.header.epk = epk.toJSON(false, ["kid"]);
|
||
|
props.epk = epk.toObject(true, ["kid"]);
|
||
|
});
|
||
|
}
|
||
|
} else {
|
||
|
rpromise = Promise.resolve();
|
||
|
}
|
||
|
|
||
|
// encrypt the CEK
|
||
|
rpromise = rpromise.then(function() {
|
||
|
var cek,
|
||
|
p;
|
||
|
// special case 'alg=dir'
|
||
|
if ("dir" === algAlg && one) {
|
||
|
encKey = Promise.resolve(algKey);
|
||
|
p = encKey.then(function(jwk) {
|
||
|
// fixup encAlg
|
||
|
if (!encAlg) {
|
||
|
props.enc = fields.enc = encAlg = jwk.algorithms(JWK.MODE_ENCRYPT)[0];
|
||
|
}
|
||
|
return {
|
||
|
once: true,
|
||
|
direct: true
|
||
|
};
|
||
|
});
|
||
|
} else {
|
||
|
if (!encKey) {
|
||
|
if (!encAlg) {
|
||
|
props.enc = fields.enc = encAlg = cfg.contentAlg;
|
||
|
}
|
||
|
encKey = generateCEK(encAlg);
|
||
|
}
|
||
|
p = encKey.then(function(jwk) {
|
||
|
cek = jwk.get("k", true);
|
||
|
// algKey may or may not be a promise
|
||
|
return algKey;
|
||
|
});
|
||
|
p = p.then(function(algKey) {
|
||
|
return algKey.wrap(algAlg, cek, props);
|
||
|
});
|
||
|
}
|
||
|
return p;
|
||
|
});
|
||
|
rpromise = rpromise.then(function(wrapped) {
|
||
|
if (wrapped.once && !one) {
|
||
|
return Promise.reject(new Error("cannot use 'alg':'" + algAlg + "' with multiple recipients"));
|
||
|
}
|
||
|
|
||
|
var rjwe = {},
|
||
|
cek;
|
||
|
if (wrapped.data) {
|
||
|
cek = wrapped.data;
|
||
|
cek = util.base64url.encode(cek);
|
||
|
}
|
||
|
|
||
|
if (wrapped.direct && cek) {
|
||
|
// replace content key
|
||
|
encKey = JWK.asKey({
|
||
|
kty: "oct",
|
||
|
k: cek
|
||
|
});
|
||
|
} else if (cek) {
|
||
|
/* eslint camelcase: [0] */
|
||
|
rjwe.encrypted_key = cek;
|
||
|
}
|
||
|
|
||
|
if (r.header && Object.keys(r.header).length) {
|
||
|
rjwe.header = clone(r.header || {});
|
||
|
}
|
||
|
if (wrapped.header) {
|
||
|
rjwe.header = assign(rjwe.header || {},
|
||
|
wrapped.header);
|
||
|
}
|
||
|
|
||
|
return rjwe;
|
||
|
});
|
||
|
return rpromise;
|
||
|
};
|
||
|
|
||
|
var p = Promise.all(recipients);
|
||
|
p = p.then(function(rcpts) {
|
||
|
var single = (1 === rcpts.length);
|
||
|
rcpts = rcpts.map(function(r) {
|
||
|
return procR(r, single);
|
||
|
});
|
||
|
return Promise.all(rcpts);
|
||
|
});
|
||
|
p = p.then(function(rcpts) {
|
||
|
jwe.recipients = rcpts.filter(function(r) { return !!r; });
|
||
|
return jwe;
|
||
|
});
|
||
|
return p;
|
||
|
});
|
||
|
|
||
|
// normalize headers
|
||
|
var props = {};
|
||
|
promise = promise.then(function(jwe) {
|
||
|
var protect,
|
||
|
lenProtect,
|
||
|
unprotect,
|
||
|
lenUnprotect;
|
||
|
|
||
|
unprotect = clone(fields);
|
||
|
if ((protectAll && jwe.recipients.length === 1) || "compact" === format) {
|
||
|
// merge single recipient into fields
|
||
|
protect = {};
|
||
|
protect = assign({},
|
||
|
unprotect,
|
||
|
jwe.recipients[0].header);
|
||
|
lenProtect = Object.keys(protect).length;
|
||
|
|
||
|
unprotect = undefined;
|
||
|
lenUnprotect = 0;
|
||
|
|
||
|
delete jwe.recipients[0].header;
|
||
|
if (Object.keys(jwe.recipients[0]).length === 0) {
|
||
|
jwe.recipients.splice(0, 1);
|
||
|
}
|
||
|
} else {
|
||
|
protect = {};
|
||
|
lenProtect = 0;
|
||
|
lenUnprotect = Object.keys(unprotect).length;
|
||
|
cfg.protect.forEach(function(f) {
|
||
|
// remove protected header values from body unprotected header
|
||
|
if (!(f in unprotect)) {
|
||
|
return;
|
||
|
}
|
||
|
protect[f] = unprotect[f];
|
||
|
lenProtect++;
|
||
|
|
||
|
delete unprotect[f];
|
||
|
lenUnprotect--;
|
||
|
});
|
||
|
|
||
|
jwe.recipients = (jwe.recipients || []).map(function(rcpt) {
|
||
|
rcpt = rcpt || {};
|
||
|
var header = rcpt.header;
|
||
|
if (header) {
|
||
|
Object.keys(header).forEach(function (f) {
|
||
|
if (f in protect) { delete header[f]; }
|
||
|
});
|
||
|
if (!Object.keys(header).length) {
|
||
|
delete rcpt.header;
|
||
|
}
|
||
|
}
|
||
|
return rcpt;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
if (!jwe.recipients || jwe.recipients.length === 0) {
|
||
|
delete jwe.recipients;
|
||
|
}
|
||
|
|
||
|
// "serialize" (and setup merged props)
|
||
|
if (unprotect && lenUnprotect > 0) {
|
||
|
props = assign(props, unprotect);
|
||
|
jwe.unprotected = unprotect;
|
||
|
}
|
||
|
if (protect && lenProtect > 0) {
|
||
|
props = assign(props, protect);
|
||
|
protect = JSON.stringify(protect);
|
||
|
jwe.protected = util.base64url.encode(protect, "utf8");
|
||
|
}
|
||
|
|
||
|
return jwe;
|
||
|
});
|
||
|
|
||
|
// (OPTIONAL) compress plaintext
|
||
|
promise = promise.then(function(jwe) {
|
||
|
var pdata = content;
|
||
|
if (!props.zip) {
|
||
|
jwe.plaintext = pdata;
|
||
|
return jwe;
|
||
|
} else if (props.zip === "DEF") {
|
||
|
return new Promise(function(resolve, reject) {
|
||
|
try {
|
||
|
var data = pako.deflateRaw(Buffer.from(pdata, "binary"));
|
||
|
|
||
|
jwe.plaintext = Buffer.from(data);
|
||
|
resolve(jwe);
|
||
|
} catch (error) {
|
||
|
reject(error);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
return Promise.reject(new Error("unsupported 'zip' mode"));
|
||
|
});
|
||
|
|
||
|
// encrypt plaintext
|
||
|
promise = promise.then(function(jwe) {
|
||
|
props.adata = jwe.protected;
|
||
|
if ("aad" in cfg && cfg.aad != null) {
|
||
|
props.adata += "." + cfg.aad;
|
||
|
props.adata = Buffer.from(props.adata, "utf8");
|
||
|
}
|
||
|
// calculate IV
|
||
|
var iv = cfg.iv ||
|
||
|
util.randomBytes(CONSTANTS.NONCELENGTH[encAlg] / 8);
|
||
|
if ("string" === typeof iv) {
|
||
|
iv = util.base64url.decode(iv);
|
||
|
}
|
||
|
props.iv = iv;
|
||
|
|
||
|
if ("recipients" in jwe && jwe.recipients.length === 1) {
|
||
|
props.kdata = jwe.recipients[0].encrypted_key;
|
||
|
}
|
||
|
|
||
|
if ("epu" in cfg && cfg.epu != null) {
|
||
|
props.epu = cfg.epu;
|
||
|
}
|
||
|
|
||
|
if ("epv" in cfg && cfg.epv != null) {
|
||
|
props.epv = cfg.epv;
|
||
|
}
|
||
|
|
||
|
var pdata = jwe.plaintext;
|
||
|
delete jwe.plaintext;
|
||
|
return encKey.then(function(encKey) {
|
||
|
var p = encKey.encrypt(encAlg, pdata, props);
|
||
|
p = p.then(function(result) {
|
||
|
jwe.iv = util.base64url.encode(iv, "binary");
|
||
|
if ("aad" in cfg && cfg.aad != null) {
|
||
|
jwe.aad = cfg.aad;
|
||
|
}
|
||
|
jwe.ciphertext = util.base64url.encode(result.data, "binary");
|
||
|
jwe.tag = util.base64url.encode(result.tag, "binary");
|
||
|
return jwe;
|
||
|
});
|
||
|
return p;
|
||
|
});
|
||
|
});
|
||
|
|
||
|
// (OPTIONAL) compact/flattened results
|
||
|
switch (format) {
|
||
|
case "compact":
|
||
|
promise = promise.then(function(jwe) {
|
||
|
var compact = new Array(5);
|
||
|
|
||
|
compact[0] = jwe.protected;
|
||
|
if (jwe.recipients && jwe.recipients[0]) {
|
||
|
compact[1] = jwe.recipients[0].encrypted_key;
|
||
|
}
|
||
|
|
||
|
compact[2] = jwe.iv;
|
||
|
compact[3] = jwe.ciphertext;
|
||
|
compact[4] = jwe.tag;
|
||
|
compact = compact.join(".");
|
||
|
|
||
|
return compact;
|
||
|
});
|
||
|
break;
|
||
|
case "flattened":
|
||
|
promise = promise.then(function(jwe) {
|
||
|
var flattened = {},
|
||
|
rcpt = jwe.recipients && jwe.recipients[0];
|
||
|
|
||
|
if (jwe.protected) {
|
||
|
flattened.protected = jwe.protected;
|
||
|
}
|
||
|
if (jwe.unprotected) {
|
||
|
flattened.unprotected = jwe.unprotected;
|
||
|
}
|
||
|
["header", "encrypted_key"].forEach(function(f) {
|
||
|
if (!rcpt) { return; }
|
||
|
if (!(f in rcpt)) { return; }
|
||
|
if (!rcpt[f]) { return; }
|
||
|
if ("object" === typeof rcpt[f] && !Object.keys(rcpt[f]).length) { return; }
|
||
|
flattened[f] = rcpt[f];
|
||
|
});
|
||
|
if (jwe.aad) {
|
||
|
flattened.aad = jwe.aad;
|
||
|
}
|
||
|
flattened.iv = jwe.iv;
|
||
|
flattened.ciphertext = jwe.ciphertext;
|
||
|
flattened.tag = jwe.tag;
|
||
|
|
||
|
return flattened;
|
||
|
});
|
||
|
break;
|
||
|
case "general":
|
||
|
promise = promise.then(function(jwe) {
|
||
|
var recipients = jwe.recipients || [];
|
||
|
recipients = recipients.map(function (rcpt) {
|
||
|
if (!Object.keys(rcpt).length) { return undefined; }
|
||
|
return rcpt;
|
||
|
});
|
||
|
recipients = recipients.filter(function (rcpt) { return !!rcpt; });
|
||
|
if (recipients.length) {
|
||
|
jwe.recipients = recipients;
|
||
|
} else {
|
||
|
delete jwe.recipients;
|
||
|
}
|
||
|
|
||
|
return jwe;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
return promise;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function createEncrypt(opts, rcpts) {
|
||
|
// fixup recipients
|
||
|
var options = opts,
|
||
|
rcptStart = 1,
|
||
|
rcptList = rcpts;
|
||
|
|
||
|
if (arguments.length === 0) {
|
||
|
throw new Error("at least one recipient must be provided");
|
||
|
}
|
||
|
if (arguments.length === 1) {
|
||
|
// assume opts is the recipient list
|
||
|
rcptList = opts;
|
||
|
rcptStart = 0;
|
||
|
options = {};
|
||
|
} else if (JWK.isKey(opts) ||
|
||
|
(opts && "kty" in opts) ||
|
||
|
(opts && "key" in opts &&
|
||
|
(JWK.isKey(opts.key) || "kty" in opts.key))) {
|
||
|
rcptList = opts;
|
||
|
rcptStart = 0;
|
||
|
options = {};
|
||
|
} else {
|
||
|
options = clone(opts);
|
||
|
}
|
||
|
if (!Array.isArray(rcptList)) {
|
||
|
rcptList = slice(arguments, rcptStart);
|
||
|
}
|
||
|
|
||
|
// fixup options
|
||
|
options = assign(clone(DEFAULTS), options);
|
||
|
|
||
|
// setup header fields
|
||
|
var fields = clone(options.fields || {});
|
||
|
if (options.zip) {
|
||
|
fields.zip = (typeof options.zip === "boolean") ?
|
||
|
(options.zip ? "DEF" : false) :
|
||
|
options.zip;
|
||
|
}
|
||
|
options.format = (options.compact ? "compact" : options.format) || "general";
|
||
|
switch (options.format) {
|
||
|
case "compact":
|
||
|
if ("aad" in opts) {
|
||
|
throw new Error("additional authenticated data cannot be used for compact serialization");
|
||
|
}
|
||
|
/* eslint no-fallthrough: [0] */
|
||
|
case "flattened":
|
||
|
if (rcptList.length > 1) {
|
||
|
throw new Error("too many recipients for compact serialization");
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// note protected fields (globally)
|
||
|
// protected fields are global only
|
||
|
var protectAll = false;
|
||
|
if ("compact" === options.format || "*" === options.protect) {
|
||
|
protectAll = true;
|
||
|
options.protect = Object.keys(fields).concat("enc");
|
||
|
} else if (typeof options.protect === "string") {
|
||
|
options.protect = [options.protect];
|
||
|
} else if (Array.isArray(options.protect)) {
|
||
|
options.protect = options.protect.concat();
|
||
|
} else if (!options.protect) {
|
||
|
options.protect = [];
|
||
|
} else {
|
||
|
throw new Error("protect must be a list of fields");
|
||
|
}
|
||
|
|
||
|
if (protectAll && 1 < rcptList.length) {
|
||
|
throw new Error("too many recipients to protect all header parameters");
|
||
|
}
|
||
|
|
||
|
rcptList = rcptList.map(function(r, idx) {
|
||
|
var p;
|
||
|
|
||
|
// resolve a key
|
||
|
if (r && "kty" in r) {
|
||
|
p = JWK.asKey(r);
|
||
|
p = p.then(function(k) {
|
||
|
return {
|
||
|
key: k
|
||
|
};
|
||
|
});
|
||
|
} else if (r) {
|
||
|
p = JWK.asKey(r.key);
|
||
|
p = p.then(function(k) {
|
||
|
return {
|
||
|
header: r.header,
|
||
|
reference: r.reference,
|
||
|
key: k
|
||
|
};
|
||
|
});
|
||
|
} else {
|
||
|
p = Promise.reject(new Error("missing key for recipient " + idx));
|
||
|
}
|
||
|
|
||
|
// convert ephemeral key (if present)
|
||
|
if (r.epk) {
|
||
|
p = p.then(function(recipient) {
|
||
|
return JWK.asKey(r.epk).
|
||
|
then(function(epk) {
|
||
|
recipient.epk = epk;
|
||
|
return recipient;
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// resolve the complete recipient
|
||
|
p = p.then(function(recipient) {
|
||
|
var key = recipient.key;
|
||
|
|
||
|
// prepare the recipient header
|
||
|
var header = recipient.header || {};
|
||
|
recipient.header = header;
|
||
|
var props = {};
|
||
|
props = assign(props, fields);
|
||
|
props = assign(props, recipient.header);
|
||
|
|
||
|
// ensure key protection algorithm is set
|
||
|
if (!props.alg) {
|
||
|
props.alg = key.algorithms(JWK.MODE_WRAP)[0];
|
||
|
header.alg = props.alg;
|
||
|
}
|
||
|
if (!props.alg) {
|
||
|
return Promise.reject(new Error("key not valid for encrypting to recipient " + idx));
|
||
|
}
|
||
|
header.alg = props.alg;
|
||
|
|
||
|
// determine the key reference
|
||
|
var ref = recipient.reference;
|
||
|
delete recipient.reference;
|
||
|
if (undefined === ref) {
|
||
|
// header already contains the key reference
|
||
|
ref = ["kid", "jku", "x5c", "x5t", "x5u"].some(function(k) {
|
||
|
return (k in header);
|
||
|
});
|
||
|
ref = !ref ? "kid" : null;
|
||
|
} else if ("boolean" === typeof ref) {
|
||
|
// explicit (positive | negative) request for key reference
|
||
|
ref = ref ? "kid" : null;
|
||
|
}
|
||
|
var jwk;
|
||
|
if (ref) {
|
||
|
jwk = key.toJSON();
|
||
|
if ("jwk" === ref) {
|
||
|
if ("oct" === key.kty) {
|
||
|
return Promise.reject(new Error("cannot embed key"));
|
||
|
}
|
||
|
header.jwk = jwk;
|
||
|
} else if (ref in jwk) {
|
||
|
header[ref] = jwk[ref];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// freeze recipient
|
||
|
recipient = Object.freeze(recipient);
|
||
|
return recipient;
|
||
|
});
|
||
|
|
||
|
return p;
|
||
|
});
|
||
|
|
||
|
// create and configure encryption
|
||
|
var cfg = {
|
||
|
aad: ("aad" in options) ? util.base64url.encode(options.aad || "") : null,
|
||
|
contentAlg: options.contentAlg,
|
||
|
format: options.format,
|
||
|
protect: options.protect,
|
||
|
cek: options.cek,
|
||
|
iv: options.iv,
|
||
|
protectAll: protectAll
|
||
|
};
|
||
|
var enc = new JWEEncrypter(cfg, fields, rcptList);
|
||
|
|
||
|
return enc;
|
||
|
}
|
||
|
|
||
|
module.exports = {
|
||
|
encrypter: JWEEncrypter,
|
||
|
createEncrypt: createEncrypt
|
||
|
};
|