rittenhop-dev/versions/5.94.2/node_modules/node-jose/lib/jws/sign.js
2024-09-23 19:40:12 -04:00

372 lines
10 KiB
JavaScript

/*!
* jws/sign.js - Sign to JWS
*
* Copyright (c) 2015 Cisco Systems, Inc. See LICENSE file.
*/
"use strict";
var merge = require("../util/merge"),
util = require("../util"),
JWK = require("../jwk"),
slice = require("./helpers").slice;
var clone = require("lodash/clone");
var uniq = require("lodash/uniq");
var DEFAULTS = require("./defaults");
/**
* @class JWS.Signer
* @classdesc Generator of signed content.
*
* @description
* **NOTE:** this class cannot be instantiated directly. Instead call {@link
* JWS.createSign}.
*/
var JWSSigner = function(cfg, signatories) {
var finalized = false,
format = cfg.format || "general",
content = Buffer.alloc(0);
/**
* @member {Boolean} JWS.Signer#compact
* @description
* Indicates whether the outuput of this signature generator is using
* the Compact serialization (`true`) or the JSON serialization
* (`false`).
*/
Object.defineProperty(this, "compact", {
get: function() {
return "compact" === format;
},
enumerable: true
});
Object.defineProperty(this, "format", {
get: function() {
return format;
},
enumerable: true
});
/**
* @method JWS.Signer#update
* @description
* Updates the signing content for this signature content. The content
* is appended to the end of any other content 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 data to sign.
* @param {String} [encoding="binary"] The encoding of {data}.
* @returns {JWS.Signer} This signature generator.
* @throws {Error} If a signature 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 JWS.Signer#final
* @description
* Finishes the signature operation.
*
* The returned Promise, when fulfilled, is the JSON Web Signature (JWS)
* object, either in the Compact (if {@link JWS.Signer#format} is
* `"compact"`), the flattened JSON (if {@link JWS.Signer#format} is
* "flattened"), or the general JSON serialization.
*
* @param {Buffer|String} [data] The final content to apply.
* @param {String} [encoding="binary"] The encoding of the final content
* (if any).
* @returns {Promise} The promise for the signatures
* @throws {Error} If a signature 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;
// map signatory promises to just signatories
promise = Promise.all(signatories);
promise = promise.then(function(sigs) {
// prepare content
content = util.base64url.encode(content);
sigs = sigs.map(function(s) {
// prepare protected
var protect = {},
lenProtect = 0,
unprotect = clone(s.header),
lenUnprotect = Object.keys(unprotect).length;
s.protected.forEach(function(h) {
if (!(h in unprotect)) {
return;
}
protect[h] = unprotect[h];
lenProtect++;
delete unprotect[h];
lenUnprotect--;
});
if (lenProtect > 0) {
protect = JSON.stringify(protect);
protect = util.base64url.encode(protect);
} else {
protect = "";
}
// signit!
var data = Buffer.from(protect + "." + content, "ascii");
s = s.key.sign(s.header.alg, data, s.header);
s = s.then(function(result) {
var sig = {};
if (0 < lenProtect) {
sig.protected = protect;
}
if (0 < lenUnprotect) {
sig.header = unprotect;
}
sig.signature = util.base64url.encode(result.mac);
return sig;
});
return s;
});
sigs = [Promise.resolve(content)].concat(sigs);
return Promise.all(sigs);
});
promise = promise.then(function(results) {
var content = results[0];
return {
payload: content,
signatures: results.slice(1)
};
});
switch (format) {
case "compact":
promise = promise.then(function(jws) {
var compact = [
jws.signatures[0].protected,
jws.payload,
jws.signatures[0].signature
];
compact = compact.join(".");
return compact;
});
break;
case "flattened":
promise = promise.then(function(jws) {
var flattened = {};
flattened.payload = jws.payload;
var sig = jws.signatures[0];
if (sig.protected) {
flattened.protected = sig.protected;
}
if (sig.header) {
flattened.header = sig.header;
}
flattened.signature = sig.signature;
return flattened;
});
break;
}
return promise;
}
});
};
/**
* @description
* Creates a new JWS.Signer with the given options and signatories.
*
* @param {Object} [opts] The signing options
* @param {Boolean} [opts.compact] Use compact serialization?
* @param {String} [opts.format] The serialization format to use ("compact",
* "flattened", "general")
* @param {Object} [opts.fields] Additional header fields
* @param {JWK.Key[]|Object[]} [signs] Signatories, either as an array of
* JWK.Key instances; or an array of objects, each with the following
* properties
* @param {JWK.Key} signs.key Key used to sign content
* @param {Object} [signs.header] Per-signatory header fields
* @param {String} [signs.reference] Reference field to identify the key
* @param {String[]|String} [signs.protect] List of fields to integrity
* protect ("*" to protect all fields)
* @returns {JWS.Signer} The signature generator.
* @throws {Error} If Compact serialization is requested but there are
* multiple signatories
*/
function createSign(opts, signs) {
// fixup signatories
var options = opts,
signStart = 1,
signList = signs;
if (arguments.length === 0) {
throw new Error("at least one signatory must be provided");
}
if (arguments.length === 1) {
signList = opts;
signStart = 0;
options = {};
} else if (JWK.isKey(opts) ||
(opts && "kty" in opts) ||
(opts && "key" in opts &&
(JWK.isKey(opts.key) || "kty" in opts.key))) {
signList = opts;
signStart = 0;
options = {};
} else {
options = clone(opts);
}
if (!Array.isArray(signList)) {
signList = slice(arguments, signStart);
}
// fixup options
options = merge(clone(DEFAULTS), options);
// setup header fields
var allFields = options.fields || {};
// setup serialization format
var format = options.format;
if (!format) {
format = options.compact ? "compact" : "general";
}
if (("compact" === format || "flattened" === format) && 1 < signList.length) {
throw new Error("too many signatories for compact or flattened JSON serialization");
}
// note protected fields (globally)
// protected fields are per signature
var protectAll = ("*" === options.protect);
if (options.compact) {
protectAll = true;
}
signList = signList.map(function(s, idx) {
var p;
// resolve a key
if (s && "kty" in s) {
p = JWK.asKey(s);
p = p.then(function(k) {
return {
key: k
};
});
} else if (s) {
p = JWK.asKey(s.key);
p = p.then(function(k) {
return {
header: s.header,
reference: s.reference,
protect: s.protect,
key: k
};
});
} else {
p = Promise.reject(new Error("missing key for signatory " + idx));
}
// resolve the complete signatory
p = p.then(function(signatory) {
var key = signatory.key;
// make sure there is a header
var header = signatory.header || {};
header = merge(merge({}, allFields), header);
signatory.header = header;
// ensure an algorithm
if (!header.alg) {
header.alg = key.algorithms(JWK.MODE_SIGN)[0] || "";
}
// determine the key reference
var ref = signatory.reference;
delete signatory.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];
}
}
// determine protected fields
var protect = signatory.protect;
if (protectAll || "*" === protect) {
protect = Object.keys(header);
} else if ("string" === protect) {
protect = [protect];
} else if (Array.isArray(protect)) {
protect = protect.concat();
} else if (!protect) {
protect = [];
} else {
return Promise.reject(new Error("protect must be a list of fields"));
}
protect = uniq(protect);
signatory.protected = protect;
// freeze signatory
signatory = Object.freeze(signatory);
return signatory;
});
return p;
});
var cfg = {
format: format
};
return new JWSSigner(cfg,
signList);
}
module.exports = {
signer: JWSSigner,
createSign: createSign
};