Skip to content

Commit

Permalink
Fix canon xml being computed differently when signing, than when veri…
Browse files Browse the repository at this point in the history
…fying (#183)

When computing the signature, ancestor namespaces of SignedInfo and
reference node were not passed into the 'getCanonXml' method like
they were during signature verification

This attempts to fix #164
  • Loading branch information
vadimavdeev authored and LoneRifle committed Apr 26, 2019
1 parent 71adf90 commit 6c30b52
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 54 deletions.
97 changes: 59 additions & 38 deletions lib/signed-xml.js
Original file line number Diff line number Diff line change
Expand Up @@ -357,35 +357,63 @@ SignedXml.prototype.checkSignature = function(xml) {
return true
}

SignedXml.prototype.validateSignatureValue = function(doc) {
SignedXml.prototype.getCanonSignedInfoXml = function(doc) {
var signedInfo = utils.findChilds(this.signatureNode, "SignedInfo")
if (signedInfo.length==0) throw new Error("could not find SignedInfo element in the message")

if(this.canonicalizationAlgorithm === "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
|| this.canonicalizationAlgorithm === "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments")
{
if(!doc || typeof(doc) !== "object"){
throw new Error("When canonicalization method is non-exclusive, whole xml dom must be provided as an argument");
throw new Error(
"When canonicalization method is non-exclusive, whole xml dom must be provided as an argument"
);
}
}

/**
* Search for ancestor namespaces before validating signature.
* Search for ancestor namespaces before canonicalization.
*/
var ancestorNamespaces = [];
ancestorNamespaces = findAncestorNs(doc, "//*[local-name()='SignedInfo']");

var c14nOptions = {
ancestorNamespaces: ancestorNamespaces
};
var signedInfoCanon = this.getCanonXml([this.canonicalizationAlgorithm], signedInfo[0], c14nOptions)
return this.getCanonXml([this.canonicalizationAlgorithm], signedInfo[0], c14nOptions)
}

SignedXml.prototype.getCanonReferenceXml = function(doc, ref, node) {
/**
* Search for ancestor namespaces before canonicalization.
*/
if(Array.isArray(ref.transforms)){
ref.ancestorNamespaces = findAncestorNs(doc, ref.xpath)
}

var c14nOptions = {
inclusiveNamespacesPrefixList: ref.inclusiveNamespacesPrefixList,
ancestorNamespaces: ref.ancestorNamespaces
}

return this.getCanonXml(ref.transforms, node, c14nOptions)
}

SignedXml.prototype.validateSignatureValue = function(doc) {
var signedInfoCanon = this.getCanonSignedInfoXml(doc)
var signer = this.findSignatureAlgorithm(this.signatureAlgorithm)
var res = signer.verifySignature(signedInfoCanon, this.signingKey, this.signatureValue)
if (!res) this.validationErrors.push("invalid signature: the signature value " +
this.signatureValue + " is incorrect")
return res
}

SignedXml.prototype.calculateSignatureValue = function(doc) {
var signedInfoCanon = this.getCanonSignedInfoXml(doc)
var signer = this.findSignatureAlgorithm(this.signatureAlgorithm)
this.signatureValue = signer.getSignature(signedInfoCanon, this.signingKey)
}

SignedXml.prototype.findSignatureAlgorithm = function(name) {
var algo = SignedXml.SignatureAlgorithms[name]
if (algo) return new algo()
Expand Down Expand Up @@ -413,7 +441,6 @@ SignedXml.prototype.validateReferences = function(doc) {

var uri = ref.uri[0]=="#" ? ref.uri.substring(1) : ref.uri
var elem = [];
var elemXpath;

if (uri=="") {
elem = xpath.select("//*", doc)
Expand All @@ -423,6 +450,7 @@ SignedXml.prototype.validateReferences = function(doc) {
throw new Error("Cannot validate a uri with quotes inside it");
}
else {
var elemXpath;
var num_elements_for_id = 0;
for (var index in this.idAttributes) {
if (!this.idAttributes.hasOwnProperty(index)) continue;
Expand All @@ -439,6 +467,8 @@ SignedXml.prototype.validateReferences = function(doc) {
'same value for the ID / Id / Id attributes, in order to prevent ' +
'signature wrapping attack.');
}

ref.xpath = elemXpath;
}

if (elem.length==0) {
Expand All @@ -447,19 +477,7 @@ SignedXml.prototype.validateReferences = function(doc) {
return false
}

/**
* Search for ancestor namespaces before validating references.
*/
if(Array.isArray(ref.transforms)){
ref.ancestorNamespaces = findAncestorNs(doc, elemXpath);
}

var c14nOptions = {
inclusiveNamespacesPrefixList: ref.inclusiveNamespacesPrefixList,
ancestorNamespaces: ref.ancestorNamespaces
};

var canonXml = this.getCanonXml(ref.transforms, elem[0], c14nOptions);
var canonXml = this.getCanonReferenceXml(doc, ref, elem[0])

var hash = this.findHashAlgorithm(ref.digestAlgorithm)
var digest = hash.getHash(canonXml)
Expand Down Expand Up @@ -679,13 +697,11 @@ SignedXml.prototype.computeSignature = function(xml, opts) {
// add the xml namespace attribute
signatureAttrs.push(xmlNsAttr + "=\"http://www.w3.org/2000/09/xmldsig#\"");

this.signatureXml = "<" + currentPrefix + "Signature " + signatureAttrs.join(" ") + ">"
var signatureXml = "<" + currentPrefix + "Signature " + signatureAttrs.join(" ") + ">"

var signedInfo = this.createSignedInfo(doc, prefix);
this.signatureXml += signedInfo;
this.signatureXml += this.createSignature(signedInfo, prefix);
this.signatureXml += this.getKeyInfo(prefix)
this.signatureXml += "</" + currentPrefix + "Signature>"
signatureXml += this.createSignedInfo(doc, prefix);
signatureXml += this.getKeyInfo(prefix)
signatureXml += "</" + currentPrefix + "Signature>"

this.originalXmlWithIds = doc.toString()

Expand All @@ -696,7 +712,7 @@ SignedXml.prototype.computeSignature = function(xml, opts) {

// A trick to remove the namespaces that already exist in the xml
// This only works if the prefix and namespace match with those in te xml
var dummySignatureWrapper = "<Dummy " + existingPrefixesString + ">" + this.signatureXml + "</Dummy>"
var dummySignatureWrapper = "<Dummy " + existingPrefixesString + ">" + signatureXml + "</Dummy>"
var xml = new Dom().parseFromString(dummySignatureWrapper)
var signatureDoc = xml.documentElement.firstChild;

Expand All @@ -718,6 +734,16 @@ SignedXml.prototype.computeSignature = function(xml, opts) {
referenceNode.parentNode.insertBefore(signatureDoc, referenceNode.nextSibling);
}

this.signatureNode = signatureDoc
this.calculateSignatureValue(doc)

var signedInfoNode = utils.findChilds(this.signatureNode, "SignedInfo")
if (signedInfoNode.length==0) throw new Error("could not find SignedInfo element in the message")

signedInfoNode = signedInfoNode[0];
signatureDoc.insertBefore(this.createSignature(prefix), signedInfoNode.nextSibling)

this.signatureXml = signatureDoc.toString()
this.signedXml = doc.toString()
}

Expand Down Expand Up @@ -778,7 +804,7 @@ SignedXml.prototype.createReferences = function(doc, prefix) {
res += "<" + prefix + "Transform Algorithm=\"" + transform.getAlgorithmName() + "\" />"
}

var canonXml = this.getCanonXml(ref.transforms, node)
var canonXml = this.getCanonReferenceXml(doc, ref, node)

var digestAlgorithm = this.findHashAlgorithm(ref.digestAlgorithm)
res += "</" + prefix + "Transforms>"+
Expand Down Expand Up @@ -880,7 +906,7 @@ SignedXml.prototype.createSignedInfo = function(doc, prefix) {
* Create the Signature element
*
*/
SignedXml.prototype.createSignature = function(signedInfo, prefix) {
SignedXml.prototype.createSignature = function(prefix) {
var xmlNsAttr = 'xmlns'

if (prefix) {
Expand All @@ -890,20 +916,15 @@ SignedXml.prototype.createSignature = function(signedInfo, prefix) {
prefix = '';
}

var signatureValueXml = "<" + prefix + "SignatureValue>" + this.signatureValue + "</" + prefix + "SignatureValue>"
//the canonicalization requires to get a valid xml node.
//we need to wrap the info in a dummy signature since it contains the default namespace.
var dummySignatureWrapper = "<" + prefix + "Signature " + xmlNsAttr + "=\"http://www.w3.org/2000/09/xmldsig#\">" +
signedInfo +
"</" + prefix + "Signature>"
signatureValueXml +
"</" + prefix + "Signature>"

var xml = new Dom().parseFromString(dummySignatureWrapper)
//get the signedInfo
var node = xml.documentElement.firstChild;
var canAlgorithm = new this.findCanonicalizationAlgorithm(this.canonicalizationAlgorithm)
var canonizedSignedInfo = canAlgorithm.process(node)
var signatureAlgorithm = this.findSignatureAlgorithm(this.signatureAlgorithm)
this.signatureValue = signatureAlgorithm.getSignature(canonizedSignedInfo, this.signingKey)
return "<" + prefix + "SignatureValue>" + this.signatureValue + "</" + prefix + "SignatureValue>"
var doc = new Dom().parseFromString(dummySignatureWrapper)
return doc.documentElement.firstChild;
}


Expand All @@ -917,4 +938,4 @@ SignedXml.prototype.getOriginalXmlWithIds = function() {

SignedXml.prototype.getSignedXml = function() {
return this.signedXml
}
}
32 changes: 16 additions & 16 deletions test/signature-unit-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,27 +194,27 @@ module.exports = {
var signature = sig.getSignatureXml()
var expected = "<Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">"+
"<SignedInfo>"+
"<CanonicalizationMethod Algorithm=\"dummy canonicalization\" />"+
"<SignatureMethod Algorithm=\"dummy algorithm\" />"+
"<CanonicalizationMethod Algorithm=\"dummy canonicalization\"/>"+
"<SignatureMethod Algorithm=\"dummy algorithm\"/>"+
"<Reference URI=\"#_0\">"+
"<Transforms>"+
"<Transform Algorithm=\"dummy transformation\" />"+
"<Transform Algorithm=\"dummy transformation\"/>"+
"</Transforms>"+
"<DigestMethod Algorithm=\"dummy digest algorithm\" />"+
"<DigestMethod Algorithm=\"dummy digest algorithm\"/>"+
"<DigestValue>dummy digest</DigestValue>"+
"</Reference>"+
"<Reference URI=\"#_1\">"+
"<Transforms>"+
"<Transform Algorithm=\"dummy transformation\" />"+
"<Transform Algorithm=\"dummy transformation\"/>"+
"</Transforms>"+
"<DigestMethod Algorithm=\"dummy digest algorithm\" />"+
"<DigestMethod Algorithm=\"dummy digest algorithm\"/>"+
"<DigestValue>dummy digest</DigestValue>"+
"</Reference>"+
"<Reference URI=\"#_2\">"+
"<Transforms>"+
"<Transform Algorithm=\"dummy transformation\" />"+
"<Transform Algorithm=\"dummy transformation\"/>"+
"</Transforms>"+
"<DigestMethod Algorithm=\"dummy digest algorithm\" />"+
"<DigestMethod Algorithm=\"dummy digest algorithm\"/>"+
"<DigestValue>dummy digest</DigestValue>"+
"</Reference>"+
"</SignedInfo>"+
Expand Down Expand Up @@ -347,27 +347,27 @@ module.exports = {

var expected = "<ds:Signature xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">"+
"<ds:SignedInfo>"+
"<ds:CanonicalizationMethod Algorithm=\"dummy canonicalization\" />"+
"<ds:SignatureMethod Algorithm=\"dummy algorithm\" />"+
"<ds:CanonicalizationMethod Algorithm=\"dummy canonicalization\"/>"+
"<ds:SignatureMethod Algorithm=\"dummy algorithm\"/>"+
"<ds:Reference URI=\"#_0\">"+
"<ds:Transforms>"+
"<ds:Transform Algorithm=\"dummy transformation\" />"+
"<ds:Transform Algorithm=\"dummy transformation\"/>"+
"</ds:Transforms>"+
"<ds:DigestMethod Algorithm=\"dummy digest algorithm\" />"+
"<ds:DigestMethod Algorithm=\"dummy digest algorithm\"/>"+
"<ds:DigestValue>dummy digest</ds:DigestValue>"+
"</ds:Reference>"+
"<ds:Reference URI=\"#_1\">"+
"<ds:Transforms>"+
"<ds:Transform Algorithm=\"dummy transformation\" />"+
"<ds:Transform Algorithm=\"dummy transformation\"/>"+
"</ds:Transforms>"+
"<ds:DigestMethod Algorithm=\"dummy digest algorithm\" />"+
"<ds:DigestMethod Algorithm=\"dummy digest algorithm\"/>"+
"<ds:DigestValue>dummy digest</ds:DigestValue>"+
"</ds:Reference>"+
"<ds:Reference URI=\"#_2\">"+
"<ds:Transforms>"+
"<ds:Transform Algorithm=\"dummy transformation\" />"+
"<ds:Transform Algorithm=\"dummy transformation\"/>"+
"</ds:Transforms>"+
"<ds:DigestMethod Algorithm=\"dummy digest algorithm\" />"+
"<ds:DigestMethod Algorithm=\"dummy digest algorithm\"/>"+
"<ds:DigestValue>dummy digest</ds:DigestValue>"+
"</ds:Reference>"+
"</ds:SignedInfo>"+
Expand Down

0 comments on commit 6c30b52

Please sign in to comment.