123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643 |
- /*
- * Copyright 2018 Scytl Secure Electronic Voting SA
- *
- * All rights reserved
- *
- * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
- */
- cryptolib.modules.asymmetric = cryptolib.modules.asymmetric || {};
- /**
- * Defines the asymmetric signers.
- *
- * @namespace asymmetric/xmlsigner
- */
- cryptolib.modules.asymmetric.xmlsigner = function(box) {
- 'use strict';
- box.asymmetric = box.asymmetric || {};
- box.asymmetric.xmlsigner = {};
- /**
- * A module that holds certificates functionalities.
- *
- * @exports asymmetric/xmlsigner/factory
- */
- box.asymmetric.xmlsigner.factory = {};
- var policies = {
- algorithm: box.policies.asymmetric.signer.algorithm,
- provider: box.policies.asymmetric.signer.provider,
- hash: box.policies.asymmetric.signer.hash
- };
- var utils, exceptions, converters, parsers, stringUtils;
- var f = function(box) {
- exceptions = box.commons.exceptions;
- utils = box.commons.utils;
- converters = new box.commons.utils.Converters();
- parsers = new utils.Parsers();
- stringUtils = new utils.StringUtils();
- };
- cryptolib('commons', f);
- /**
- * A factory class for creating an XML digital signature verifier.
- *
- * @class
- */
- box.asymmetric.xmlsigner.factory.XmlSignerFactory = function() {};
- box.asymmetric.xmlsigner.factory.XmlSignerFactory.prototype = {
- /**
- * Gets a {@link asymmetric/xmlsigner.CryptoForgeXmlSigner}.
- *
- * @function
- * @return {asymmetric/xmlsigner.CryptoForgeXmlSigner}
- */
- getCryptoXmlSigner: function() {
- try {
- if (policies.provider === Config.asymmetric.signer.provider.FORGE) {
- return new CryptoForgeXmlSigner();
- } else {
- throw new exceptions.CryptoLibException(
- 'No suitable provider for the signer was provided.');
- }
- } catch (error) {
- throw new exceptions.CryptoLibException(
- 'A CryptoXmlSigner could not be obtained.', error);
- }
- }
- };
- /**
- * Holds xml signature properties.
- *
- * @class
- * @memberof asymmetric/xmlsigner
- */
- function XmlSignature(signatureNode) {
- // go through all the nodes to load all relevant information
- var signedInfoNode;
- var signatureValueNode;
- var canonicalizationMethodNode;
- var signatureMethodNode;
- var referenceNode;
- var digestMethodNode;
- var digestValueNode;
- var transformsNode;
- try {
- signedInfoNode = new utils.XPath(signatureNode.getXml(), 'SignedInfo');
- signatureValueNode =
- new utils.XPath(signatureNode.getXml(), 'SignatureValue');
- canonicalizationMethodNode =
- new utils.XPath(signedInfoNode.getXml(), 'CanonicalizationMethod');
- signatureMethodNode =
- new utils.XPath(signedInfoNode.getXml(), 'SignatureMethod');
- referenceNode = new utils.XPath(signedInfoNode.getXml(), 'Reference');
- digestMethodNode =
- new utils.XPath(referenceNode.getXml(), 'DigestMethod');
- digestValueNode = new utils.XPath(referenceNode.getXml(), 'DigestValue');
- transformsNode = new utils.XPath(referenceNode.getXml(), 'Transforms');
- } catch (error) {
- throw new exceptions.CryptoLibException(
- 'Could not parse signed XML file', error);
- }
- // there are signatures without key, so we ignore them
- var rsaKeyValueNode;
- var modulusNode;
- var exponentNode;
- try {
- rsaKeyValueNode = new utils.XPath(
- signatureNode.getXml(), 'KeyInfo/KeyValue/RSAKeyValue');
- modulusNode = new utils.XPath(rsaKeyValueNode.getXml(), 'Modulus');
- exponentNode = new utils.XPath(rsaKeyValueNode.getXml(), 'Exponent');
- } catch (error) {
- // ignore if this part fails
- }
- // build the object structure
- /** @property {object} */
- this.info = {
- method: signatureMethodNode.getAttribute('Algorithm'),
- value: signatureValueNode.getValue(),
- meta: signatureNode.getAttribute('xmlns')
- };
- /** @property {object} */
- this.canonicalization = {
- method: canonicalizationMethodNode.getAttribute('Algorithm')
- };
- /** @property {object} */
- this.reference = {
- uri: referenceNode.getAttribute('URI'),
- transforms: [],
- digest: {
- method: digestMethodNode.getAttribute('Algorithm'),
- value: digestValueNode.getValue()
- }
- };
- /** @property {object} */
- this.rsakey = {
- modulus: modulusNode ? modulusNode.getValue() : '',
- exponent: exponentNode ? exponentNode.getValue() : ''
- };
- // update the transforms references
- var transforms = transformsNode.getChildren();
- for (var i = 0; i < transforms.length; i++) {
- if (transforms[i].nodeType === Node.ELEMENT_NODE) {
- var transformNode = new utils.XPath(transforms[i]);
- this.reference.transforms.push(transformNode.getAttribute('Algorithm'));
- }
- }
- }
- /**
- * @class
- * @memberof asymmetric/xmlsigner
- */
- function ExclusiveCanonicalization() {}
- /**
- * Sorts the attributes
- *
- * @function
- * @param xmlNode
- * xml node, as a DOM.
- */
- ExclusiveCanonicalization.prototype.sortAttributes = function(xmlNode) {
- // gather all attributes and remove them
- var list = [];
- var attr;
- if (xmlNode.attributes) {
- for (var i = 0; i < xmlNode.attributes.length; i++) {
- attr = xmlNode.attributes[i];
- list.push({id: attr.name, val: xmlNode.getAttribute(attr.name)});
- xmlNode.removeAttribute(attr.name);
- i--;
- }
- }
- // sort the attributes
- list.sort(function(a, b) {
- if (a.id < b.id) {
- return -1;
- }
- if (a.id > b.id) {
- return 1;
- }
- return 0;
- });
- // reinsert the attributes
- for (var j = 0; j < list.length; j++) {
- attr = list[j];
- xmlNode.setAttribute(attr.id, attr.val);
- }
- };
- /**
- * Inits the process of canonicalization.
- *
- * @function
- * @param xmlNode
- * xml node, as a DOM.
- *
- * @return xml node processed, as DOM.
- */
- ExclusiveCanonicalization.prototype.process = function(xml) {
- // traverse the tree through all the children
- for (var i = 0; i < xml.childNodes.length; i++) {
- var child = xml.childNodes[i];
- if (child.nodeType === Node.ELEMENT_NODE) {
- this.process(child);
- }
- // if are comments or other stuff, remove them
- else if (child.nodeType !== Node.TEXT_NODE) {
- child.parentNode.removeChild(child);
- --i;
- }
- }
- // sort the attributes
- this.sortAttributes(xml);
- // return the final object
- return xml;
- };
- /**
- * An xml signer.
- *
- * @class
- * @memberof asymmetric/xmlsigner
- */
- function CryptoForgeXmlSigner() {}
- CryptoForgeXmlSigner.prototype = {
- /**
- * Verifies the digital signature of a selfsigned xml.
- *
- * @function
- * @param publicKey
- * Public key, as an object.
- * @param signedXml
- * XML data, as a string
- * @param signatureParentNode
- * The node where the signature is, as a string.
- *
- * @returns Boolean indicating whether signature verification was
- * successful.
- */
- verify: function(publicKey, signedXml, signatureParentNode) {
- // clean the comments in the xml
- signedXml = this._removeXmlStringComments(signedXml);
- // loads the text to an xml
- var xml = parsers.xmlFromString(signedXml);
- // loads the xml signature structure to json
- var signature = this._loadSignature(xml, signatureParentNode);
- // check all the algorithms are the expected ones
- this._verifyAlgorithmsInSignature(signature);
- // check the public key is the expected one
- this._verifyPublicKeyInSignature(signature, publicKey);
- // check the references are well encoded
- this._validateReferences(signature, signedXml);
- // validates the signatures are right
- this._validateSignature(signature, publicKey);
- // if not exception raised, all went good
- return true;
- },
- /**
- * Gets the XML signature node from the main XML node
- *
- * @function
- * @private
- * @param xml
- * XML where the signature is, as a Document Object
- * @param signatureParentNode
- * The node where the signature is, as a string.
- *
- * @returns Object with the DOM node of the signature and and the
- * signature in json.
- */
- _loadSignature: function(xml, signatureParentNode) {
- // access to the node where the signature is, and take the methods
- try {
- // get the signature node
- var xmlNode = new utils.XPath(xml, signatureParentNode);
- // create signature object to load the data from the signature
- var data = new XmlSignature(xmlNode);
- // return the main object
- return {xmlNode: xmlNode, data: data};
- } catch (error) {
- throw new exceptions.CryptoLibException(
- 'Could not load the signature from the XML', error);
- }
- },
- /**
- * Verifies that the method algorithms in the EML and the properties
- * match.
- *
- * @function
- * @private
- * @param signature
- * Signature, as a Javascript Object
- */
- _verifyAlgorithmsInSignature: function(signature) {
- try {
- if (signature.data.canonicalization.method !==
- 'http://www.w3.org/2001/10/xml-exc-c14n#') {
- throw new exceptions.CryptoLibException(
- 'Invalid canonicalization algorithm');
- }
- if (!stringUtils.containsSubString(
- signature.data.info.method, policies.algorithm) &&
- !stringUtils.containsSubString(
- signature.data.info.method, policies.hash)) {
- throw new exceptions.CryptoLibException(
- 'Invalid signature algorithm');
- }
- if (!stringUtils.containsSubString(
- signature.data.reference.digest.method, policies.hash)) {
- throw new exceptions.CryptoLibException('Invalid digest algorithm');
- }
- } catch (error) {
- throw new exceptions.CryptoLibException(
- 'Could not verify the data in the XML.', error);
- }
- },
- /**
- * Verifies the public key is the same than the one in the EML.
- *
- * @function
- * @private
- * @param signature
- * Signature, as a Javascript Object
- * @param publicKey
- * Public key, as an object.
- */
- _verifyPublicKeyInSignature: function(signature, publicKey) {
- // it has no key
- if (!signature.data.rsakey.exponent || !signature.data.rsakey.modulus) {
- return;
- }
- // if it has key, we validate it
- try {
- var signatureExponent =
- converters.base64ToBigInteger(signature.data.rsakey.exponent);
- var signatureModulus =
- converters.base64ToBigInteger(signature.data.rsakey.modulus);
- if (publicKey.e.compareTo(signatureExponent) !== 0 ||
- publicKey.n.compareTo(signatureModulus) !== 0) {
- throw new exceptions.CryptoLibException('Invalid public key');
- }
- } catch (error) {
- throw new exceptions.CryptoLibException(
- 'Could not verify the data in the XML.', error);
- }
- },
- /**
- * Verifies the references of a selfsigned xml.
- *
- * @function
- * @private
- * @param signature
- * Signature, as a Javascript Object
- */
- _validateReferences: function(signature, xmlString) {
- try {
- var xmlCanonString;
- // The canonicalization transformations are performed here.
- // Due to differences in the behaviour of Internet Explorer,
- // these transformations must be applied manually in the case
- // of that browser. For all other browsers, the transformations
- // are performed by applying a canonicalization algorithm.
- if (parsers.isIE()) {
- xmlCanonString = this._removeXmlStringHeader(xmlString);
- xmlCanonString = this._removeXmlStringSelfClosingTags(xmlCanonString);
- xmlCanonString = this._removeXmlStringSignature(xmlCanonString);
- xmlCanonString = this._removeXmlStringFirstCharacter(xmlCanonString);
- } else {
- var xmlCanon = null;
- // apply all transforms to the xml
- for (var i = 0; i < signature.data.reference.transforms.length; i++) {
- // get the algorithm name
- var algorithm = signature.data.reference.transforms[i];
- // consume it if it exists
- if (algorithm in this._transformAlgorithmMap) {
- xmlCanon = this._transformAlgorithmMap[algorithm](
- signature.xmlNode.getXml());
- }
- }
- xmlCanon = this._removeXmlHeader(xmlCanon);
- xmlCanonString = parsers.xmlToString(xmlCanon, true);
- }
- var digester =
- this._getHashMethod(signature.data.reference.digest.method);
- digester.start();
- digester.update(
- parsers.removeCarriageReturnChars(xmlCanonString), 'utf8');
- var messageDigest = digester.digest();
- // check if the result is valid
- var encodedMessage = forge.util.encode64(messageDigest.getBytes());
- if (encodedMessage !== signature.data.reference.digest.value) {
- throw new exceptions.CryptoLibException(
- 'Invalid reference: \'' + encodedMessage +
- '\' when was expected \'' +
- signature.data.reference.digest.value + '\'');
- }
- } catch (error) {
- throw new exceptions.CryptoLibException(
- 'Could not validate references.', error);
- }
- },
- /**
- * Verifies the digital signature of a selfsigned xml.
- *
- * @function
- * @private
- * @param signature
- * Signature, as a Javascript Object
- * @param publicKey
- * Public key, as an object.
- */
- _validateSignature: function(signature, publicKey) {
- try {
- var signedInfoNode =
- new utils.XPath(signature.xmlNode.getXml(), '/SignedInfo');
- var algorithm = signature.data.canonicalization.method;
- var xmlCanon = null;
- if (algorithm in this._transformAlgorithmMap) {
- xmlCanon =
- this._transformAlgorithmMap[algorithm](signedInfoNode.getXml());
- }
- var digester = this._getHashMethod(signature.data.info.method);
- digester.start();
- digester.update(parsers.xmlToString(xmlCanon, true), 'utf8');
- var signatureValue = forge.util.decode64(signature.data.info.value);
- var result =
- publicKey.verify(digester.digest().getBytes(), signatureValue);
- if (!result) {
- throw new exceptions.CryptoLibException(
- 'Could not validate the signature.');
- }
- } catch (error) {
- throw new exceptions.CryptoLibException(
- 'Could not validate signatures.', error);
- }
- },
- /**
- * Creates the hash method
- *
- * @function
- * @private
- * @param method
- * method type, as string.
- *
- * @return Object, the disgester of that method.
- */
- _getHashMethod: function(method) {
- if (stringUtils.containsSubString(method, 'sha256')) {
- return forge.md.sha256.create();
- }
- },
- /**
- * Map with all tarnsform algorithms
- *
- * @function
- * @private
- */
- _transformAlgorithmMap: {
- /**
- * Removes the signature from a signed XML
- *
- * @param xmlSignature
- * the xml signature, as DOM.
- *
- * @return the XML top document, as DOM
- */
- 'http://www.w3.org/2000/09/xmldsig#enveloped-signature': function(
- xmlSignature) {
- var topElement = xmlSignature;
- // get the top document
- while (topElement.parentNode) {
- topElement = topElement.parentNode;
- }
- // remove the signature from the node
- xmlSignature.parentNode.removeChild(xmlSignature);
- return topElement;
- },
- /**
- * c14n the XML
- *
- * @param xmlNode
- * the xml node, as DOM.
- *
- * @return the XML top document, as DOM
- */
- 'http://www.w3.org/2001/10/xml-exc-c14n#': function(xmlNode) {
- var transform = new ExclusiveCanonicalization();
- return transform.process(xmlNode);
- }
- },
- /**
- * Removes the comments in the xml
- *
- * @function
- * @private
- * @param xml
- * the xml, as string.
- *
- * @return {string} the xml without comments
- */
- _removeXmlStringComments: function(xml) {
- return xml.replace(/<!--[\s\S]*?-->/g, '');
- },
- /**
- * Removes the header in the xml structure. <?xml version="1.0"
- * encoding="UTF-8"?>
- *
- * @function
- * @private
- * @param {object}
- * xmlDocument the xml, as DOM.
- *
- * @returns {object} the xml, as DOM without the header
- */
- _removeXmlHeader: function(xmlDocument) {
- return xmlDocument.documentElement ? xmlDocument.documentElement :
- xmlDocument;
- },
- /**
- * Removes the header in the XML string.
- *
- * @param xmlString
- * the xml, as a string.
- *
- * @return the xml string without the header
- */
- _removeXmlStringHeader: function(xmlString) {
- return parsers.removeXmlHeaderFromString(xmlString);
- },
- /**
- * Removes the signature node in the XML string.
- *
- * @param xmlString
- * the xml, as a string.
- *
- * @return the xml string without the signature tag
- */
- _removeXmlStringSignature: function(xmlString) {
- return parsers.removeXmlSignature(xmlString);
- },
- /**
- * Converts any self-closing tags to opening and closing tags, within an
- * XML string.
- *
- * @param xmlString
- * the xml, as a string.
- *
- * @return the xml string with self-closing tags converted to opening
- * and closing tags
- */
- _removeXmlStringSelfClosingTags: function(xmlString) {
- return parsers.xmlRemoveSelfClosingTags(xmlString);
- },
- /**
- * Removes the first character from an XML string if that character is
- * not the expected initial character.
- *
- * @param xmlString
- * the xml, as a string.
- *
- * @return the xml string with the initial character removed if it is
- * not wanted
- */
- _removeXmlStringFirstCharacter: function(xmlString) {
- if (xmlString[0] !== '<') {
- return xmlString.slice(1, xmlString.length);
- } else {
- return xmlString;
- }
- }
- };
- };
|