Skip to content

Commit a0de707

Browse files
gcharnockLoneRifle
authored andcommitted
Bugfix: ensure inclusive namespaces written on root node (#163)
A namespace in the inclusive namespace list should be treated as if it were handled by the exclusive algorithm and thus written out on the root node of the canonical document. - Always obtain ancestor namespaces to pass into the canonicalization algorithms - Ensure that exclusive canonicalization render namespaces in the inclusive list are in the root node, and avoid re-rendering those namespaces subsequently
1 parent 5c46e4f commit a0de707

File tree

3 files changed

+75
-32
lines changed

3 files changed

+75
-32
lines changed

lib/exclusive-canonicalization.js

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,19 @@ function isPrefixInScope(prefixesInScope, prefix, namespaceURI)
7878
* @return {String}
7979
* @api private
8080
*/
81-
ExclusiveCanonicalization.prototype.renderNs = function(node, prefixesInScope, defaultNs, defaultNsForPrefix, inclusiveNamespacesPrefixList) {
81+
ExclusiveCanonicalization.prototype.renderNs = function(node,
82+
prefixesInScope,
83+
defaultNs,
84+
defaultNsForPrefix,
85+
inclusiveNamespacesPrefixList,
86+
ancestorNamespaces,
87+
topNode
88+
) {
8289
var a, i, p, attr
8390
, res = []
8491
, newDefaultNs = defaultNs
8592
, nsListToRender = []
8693
, currNs = node.namespaceURI || "";
87-
8894
//handle the namespaceof the node itself
8995
if (node.prefix) {
9096
if (!isPrefixInScope(prefixesInScope, node.prefix, node.namespaceURI || defaultNsForPrefix[node.prefix])) {
@@ -98,23 +104,57 @@ ExclusiveCanonicalization.prototype.renderNs = function(node, prefixesInScope, d
98104
res.push(' xmlns="', newDefaultNs, '"');
99105
}
100106

107+
if(topNode) {
108+
for(var j = 0; j < ancestorNamespaces.length; j++) {
109+
var ancestorNs = ancestorNamespaces[j];
110+
for(var k = 0; k < inclusiveNamespacesPrefixList.length; k++) {
111+
var inclusiveNs = inclusiveNamespacesPrefixList[k];
112+
if(ancestorNs.prefix === inclusiveNs && !isPrefixInScope(prefixesInScope, ancestorNs.prefix, ancestorNs.namespaceURI)) {
113+
nsListToRender.push({"prefix": ancestorNs.prefix, "namespaceURI": ancestorNs.namespaceURI});
114+
prefixesInScope.push({"prefix": ancestorNs.prefix, "namespaceURI": ancestorNs.namespaceURI});
115+
}
116+
}
117+
}
118+
}
119+
101120
//handle the attributes namespace
102121
if (node.attributes) {
103122
for (i = 0; i < node.attributes.length; ++i) {
104123
attr = node.attributes[i];
105124

125+
106126
//handle all prefixed attributes that are included in the prefix list and where
107127
//the prefix is not defined already
108-
if (attr.prefix && !isPrefixInScope(prefixesInScope, attr.localName, attr.value) && inclusiveNamespacesPrefixList.indexOf(attr.localName) >= 0) {
128+
if (attr.prefix &&
129+
!isPrefixInScope(prefixesInScope, attr.localName, attr.value) &&
130+
(inclusiveNamespacesPrefixList.indexOf(attr.localName) >= 0)) {
109131
nsListToRender.push({"prefix": attr.localName, "namespaceURI": attr.value});
110132
prefixesInScope.push({"prefix": attr.localName, "namespaceURI": attr.value});
111133
}
112134

113135
//handle all prefixed attributes that are not xmlns definitions and where
114136
//the prefix is not defined already
115-
if (attr.prefix && !isPrefixInScope(prefixesInScope, attr.prefix, attr.namespaceURI) && attr.prefix!="xmlns" && attr.prefix!="xml") {
116-
nsListToRender.push({"prefix": attr.prefix, "namespaceURI": attr.namespaceURI});
117-
prefixesInScope.push({"prefix": attr.prefix, "namespaceURI": attr.namespaceURI});
137+
if (attr.prefix && attr.prefix!=="xmlns" && attr.prefix!=="xml") {
138+
var artificiallyIntroduced = false;
139+
if(attr.namespaceURI === undefined) {
140+
//This could mean that the namespace Uri has been reset to "", or it could mean we have artificially
141+
//introduced it because it was in inclusiveNamespacePrefixList
142+
if(inclusiveNamespacesPrefixList.indexOf(attr.prefix) >= 0) {
143+
for(var j = 0; j < ancestorNamespaces.length; j++) {
144+
var ancestorNs = ancestorNamespaces[j];
145+
if(ancestorNs.prefix === attr.prefix) {
146+
artificiallyIntroduced = true;
147+
break;
148+
}
149+
}
150+
}
151+
}
152+
if(!artificiallyIntroduced) {
153+
if(!isPrefixInScope(prefixesInScope, attr.prefix, attr.namespaceURI)) {
154+
nsListToRender.push({"prefix": attr.prefix, "namespaceURI": attr.namespaceURI|| defaultNsForPrefix[attr.prefix]});
155+
prefixesInScope.push({"prefix": attr.prefix, "namespaceURI": attr.namespaceURI|| defaultNsForPrefix[attr.prefix]});
156+
}
157+
}
118158
}
119159
}
120160
}
@@ -132,18 +172,23 @@ ExclusiveCanonicalization.prototype.renderNs = function(node, prefixesInScope, d
132172
return {"rendered": res.join(""), "newDefaultNs": newDefaultNs};
133173
};
134174

135-
ExclusiveCanonicalization.prototype.processInner = function(node, prefixesInScope, defaultNs, defaultNsForPrefix, inclusiveNamespacesPrefixList) {
136-
175+
ExclusiveCanonicalization.prototype.processInner = function(node,
176+
prefixesInScope,
177+
defaultNs,
178+
defaultNsForPrefix,
179+
inclusiveNamespacesPrefixList,
180+
ancestorNamespaces,
181+
topNode) {
137182
if (node.nodeType === 8) { return this.renderComment(node); }
138183
if (node.data) { return utils.encodeSpecialCharactersInText(node.data); }
139184

140185
var i, pfxCopy
141-
, ns = this.renderNs(node, prefixesInScope, defaultNs, defaultNsForPrefix, inclusiveNamespacesPrefixList)
186+
, ns = this.renderNs(node, prefixesInScope, defaultNs, defaultNsForPrefix, inclusiveNamespacesPrefixList, ancestorNamespaces, topNode)
142187
, res = ["<", node.tagName, ns.rendered, this.renderAttrs(node, ns.newDefaultNs), ">"];
143188

144189
for (i = 0; i < node.childNodes.length; ++i) {
145190
pfxCopy = prefixesInScope.slice(0);
146-
res.push(this.processInner(node.childNodes[i], pfxCopy, ns.newDefaultNs, defaultNsForPrefix, inclusiveNamespacesPrefixList));
191+
res.push(this.processInner(node.childNodes[i], pfxCopy, ns.newDefaultNs, defaultNsForPrefix, inclusiveNamespacesPrefixList, ancestorNamespaces, false));
147192
}
148193

149194
res.push("</", node.tagName, ">");
@@ -197,9 +242,10 @@ ExclusiveCanonicalization.prototype.process = function(node, options) {
197242
var inclusiveNamespacesPrefixList = options.inclusiveNamespacesPrefixList || [];
198243
var defaultNs = options.defaultNs || "";
199244
var defaultNsForPrefix = options.defaultNsForPrefix || {};
245+
var ancestorNamespaces = options.ancestorNamespaces || [];
200246
if (!(inclusiveNamespacesPrefixList instanceof Array)) { inclusiveNamespacesPrefixList = inclusiveNamespacesPrefixList.split(' '); }
201247

202-
var res = this.processInner(node, [], defaultNs, defaultNsForPrefix, inclusiveNamespacesPrefixList);
248+
var res = this.processInner(node, [], defaultNs, defaultNsForPrefix, inclusiveNamespacesPrefixList, ancestorNamespaces, true);
203249
return res;
204250
};
205251

lib/signed-xml.js

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -374,9 +374,9 @@ SignedXml.prototype.validateSignatureValue = function(doc) {
374374
throw new Error("When canonicalization method is non-exclusive, whole xml dom must be provided as an argument");
375375
}
376376

377-
ancestorNamespaces = findAncestorNs(doc, "//*[local-name()='SignedInfo']");
378377
}
379-
378+
ancestorNamespaces = findAncestorNs(doc, "//*[local-name()='SignedInfo']");
379+
380380
var c14nOptions = {
381381
ancestorNamespaces: ancestorNamespaces
382382
};
@@ -450,25 +450,12 @@ SignedXml.prototype.validateReferences = function(doc) {
450450
}
451451

452452
/**
453-
* When canonicalization algorithm is non-exclusive, search for ancestor namespaces
454-
* before validating references.
453+
* Search for ancestor namespaces before validating references. Ancestor namespaces are needed
454+
* even for exclusive canonicalization because they may be needed for namespaces that are on the
455+
* inclusive namespace prefix list.
455456
*/
456457
if(Array.isArray(ref.transforms)){
457-
var hasNonExcC14nTransform = false;
458-
for(var t in ref.transforms){
459-
if(!ref.transforms.hasOwnProperty(t)) continue;
460-
461-
if(ref.transforms[t] === "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
462-
|| ref.transforms[t] === "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments")
463-
{
464-
hasNonExcC14nTransform = true;
465-
break;
466-
}
467-
}
468-
469-
if(hasNonExcC14nTransform){
470-
ref.ancestorNamespaces = findAncestorNs(doc, elemXpath);
471-
}
458+
ref.ancestorNamespaces = findAncestorNs(doc, elemXpath);
472459
}
473460

474461
var c14nOptions = {

test/canonicalization-unit-tests.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ var ExclusiveCanonicalization = require("../lib/exclusive-canonicalization").Exc
44
, SignedXml = require('../lib/signed-xml.js').SignedXml
55

66

7-
var compare = function(test, xml, xpath, expected, inclusiveNamespacesPrefixList, defaultNsForPrefix) {
7+
var compare = function(test, xml, xpath, expected, inclusiveNamespacesPrefixList, defaultNsForPrefix, ancestorNamespaces) {
88
test.expect(1)
99
var doc = new Dom().parseFromString(xml)
1010
var elem = select(doc, xpath)[0]
1111
var can = new ExclusiveCanonicalization()
1212
var result = can.process(elem, {
1313
inclusiveNamespacesPrefixList: inclusiveNamespacesPrefixList,
14-
defaultNsForPrefix: defaultNsForPrefix
14+
defaultNsForPrefix: defaultNsForPrefix,
15+
ancestorNamespaces: ancestorNamespaces
1516
}).toString()
1617

1718
test.equal(expected, result)
@@ -442,5 +443,14 @@ module.exports = {
442443
'//*',
443444
'<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:InclusiveNamespaces xmlns:ds="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:InclusiveNamespaces></ds:Signature>',
444445
'ds')
446+
},
447+
448+
"An ancestor namespace, which is included in the inclusiveNamespacePrefixList should be added to the top level element": function (test) {
449+
compare(test, '<root><child inclusive:attr="value"></child></root>',
450+
'//*',
451+
'<root xmlns:inclusive="urn:inclusive"><child inclusive:attr="value"></child></root>',
452+
"inclusive",
453+
{},
454+
[{"prefix": "inclusive", "namespaceURI": "urn:inclusive"}]);
445455
}
446456
}

0 commit comments

Comments
 (0)