Skip to content

Commit dcba12d

Browse files
authored
Merge pull request #179 from bazzadp/xml_enc_c14#_inclusivenamespace_fixes
* Handle enveloped transformations with exclusive canonicalization better * Add unit test for enveloped transformation with inclusivenamespaces * Add test case static file for SOAP-UI style message with transforms with inclusive namespaces * Refactor inclusive namespaces to happen in canonicalization code * Only use the CanonicalizationMethod/InclusiveNameSpace if one was not explicitly provided * Update lib/signed-xml.js * aFix logic for multiple PrefixList + fix some old whitespace issues
2 parents 3516a57 + 9e1c6ea commit dcba12d

File tree

4 files changed

+63
-62
lines changed

4 files changed

+63
-62
lines changed

lib/exclusive-canonicalization.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,37 @@ ExclusiveCanonicalization.prototype.process = function(node, options) {
199199
var defaultNsForPrefix = options.defaultNsForPrefix || {};
200200
if (!(inclusiveNamespacesPrefixList instanceof Array)) { inclusiveNamespacesPrefixList = inclusiveNamespacesPrefixList.split(' '); }
201201

202+
var ancestorNamespaces = options.ancestorNamespaces || [];
203+
204+
/**
205+
* If the inclusiveNamespacesPrefixList has not been explicitly provided then look it up in CanonicalizationMethod/InclusiveNamespaces
206+
*/
207+
if (inclusiveNamespacesPrefixList.length == 0) {
208+
var CanonicalizationMethod = utils.findChilds(node, "CanonicalizationMethod")
209+
if (CanonicalizationMethod.length != 0) {
210+
var inclusiveNamespaces = utils.findChilds(CanonicalizationMethod[0], "InclusiveNamespaces")
211+
if (inclusiveNamespaces.length != 0) {
212+
inclusiveNamespacesPrefixList = inclusiveNamespaces[0].getAttribute('PrefixList').split(" ");
213+
}
214+
}
215+
}
216+
217+
/**
218+
* If you have a PrefixList then use it and the ancestors to add the necessary namespaces
219+
*/
220+
if (inclusiveNamespacesPrefixList) {
221+
var prefixList = inclusiveNamespacesPrefixList instanceof Array ? inclusiveNamespacesPrefixList : inclusiveNamespacesPrefixList.split(' ');
222+
prefixList.forEach(function (prefix) {
223+
if (ancestorNamespaces) {
224+
ancestorNamespaces.forEach(function (ancestorNamespace) {
225+
if (prefix == ancestorNamespace.prefix) {
226+
node.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:' + prefix, ancestorNamespace.namespaceURI);
227+
}
228+
})
229+
}
230+
})
231+
}
232+
202233
var res = this.processInner(node, [], defaultNs, defaultNsForPrefix, inclusiveNamespacesPrefixList);
203234
return res;
204235
};

lib/signed-xml.js

Lines changed: 24 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ function FileKeyInfo(file) {
1818
this.file = file
1919

2020
this.getKeyInfo = function(key, prefix) {
21-
prefix = prefix || ''
22-
prefix = prefix ? prefix + ':' : prefix
21+
prefix = prefix || ''
22+
prefix = prefix ? prefix + ':' : prefix
2323
return "<" + prefix + "X509Data></" + prefix + "X509Data>"
2424
}
2525

@@ -361,21 +361,20 @@ SignedXml.prototype.validateSignatureValue = function(doc) {
361361
var signedInfo = utils.findChilds(this.signatureNode, "SignedInfo")
362362
if (signedInfo.length==0) throw new Error("could not find SignedInfo element in the message")
363363

364-
/**
365-
* When canonicalization algorithm is non-exclusive, search for ancestor namespaces
366-
* before validating signature.
367-
*/
368-
var ancestorNamespaces = [];
369364
if(this.canonicalizationAlgorithm === "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
370365
|| this.canonicalizationAlgorithm === "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments")
371366
{
372367
if(!doc || typeof(doc) !== "object"){
373368
throw new Error("When canonicalization method is non-exclusive, whole xml dom must be provided as an argument");
374369
}
375-
376-
ancestorNamespaces = findAncestorNs(doc, "//*[local-name()='SignedInfo']");
377370
}
378371

372+
/**
373+
* Search for ancestor namespaces before validating signature.
374+
*/
375+
var ancestorNamespaces = [];
376+
ancestorNamespaces = findAncestorNs(doc, "//*[local-name()='SignedInfo']");
377+
379378
var c14nOptions = {
380379
ancestorNamespaces: ancestorNamespaces
381380
};
@@ -449,59 +448,21 @@ SignedXml.prototype.validateReferences = function(doc) {
449448
}
450449

451450
/**
452-
* When canonicalization algorithm is non-exclusive, search for ancestor namespaces
453-
* before validating references.
451+
* Search for ancestor namespaces before validating references.
454452
*/
455-
if(Array.isArray(ref.transforms)){
456-
var hasNonExcC14nTransform = false;
457-
for(var t in ref.transforms){
458-
if(!ref.transforms.hasOwnProperty(t)) continue;
459-
460-
if(ref.transforms[t] === "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
461-
|| ref.transforms[t] === "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments")
462-
{
463-
hasNonExcC14nTransform = true;
464-
break;
465-
}
466-
}
467-
468-
if(hasNonExcC14nTransform){
469-
ref.ancestorNamespaces = findAncestorNs(doc, elemXpath);
470-
}
453+
if(Array.isArray(ref.transforms)){
454+
ref.ancestorNamespaces = findAncestorNs(doc, elemXpath);
471455
}
472456

473457
var c14nOptions = {
474458
inclusiveNamespacesPrefixList: ref.inclusiveNamespacesPrefixList,
475459
ancestorNamespaces: ref.ancestorNamespaces
476460
};
461+
477462
var canonXml = this.getCanonXml(ref.transforms, elem[0], c14nOptions);
478463

479464
var hash = this.findHashAlgorithm(ref.digestAlgorithm)
480465
var digest = hash.getHash(canonXml)
481-
482-
if (!validateDigestValue(digest, ref.digestValue)) {
483-
if (ref.inclusiveNamespacesPrefixList) {
484-
// fallback: apply InclusiveNamespaces workaround (https://github.com/yaronn/xml-crypto/issues/72)
485-
var prefixList = ref.inclusiveNamespacesPrefixList instanceof Array ? ref.inclusiveNamespacesPrefixList : ref.inclusiveNamespacesPrefixList.split(' ');
486-
var supported_definitions = {
487-
'xs': 'http://www.w3.org/2001/XMLSchema',
488-
'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
489-
'saml': 'urn:oasis:names:tc:SAML:2.0:assertion'
490-
}
491-
492-
prefixList.forEach(function (prefix) {
493-
if (supported_definitions[prefix]) {
494-
elem[0].setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:' + prefix, supported_definitions[prefix]);
495-
}
496-
});
497-
498-
canonXml = this.getCanonXml(ref.transforms, elem[0], { inclusiveNamespacesPrefixList: ref.inclusiveNamespacesPrefixList });
499-
digest = hash.getHash(canonXml);
500-
if (digest === ref.digestValue) {
501-
return true;
502-
}
503-
}
504-
}
505466

506467
if (!validateDigestValue(digest, ref.digestValue)) {
507468
this.validationErrors.push("invalid signature: for uri " + ref.uri +
@@ -612,9 +573,16 @@ SignedXml.prototype.loadReference = function(ref) {
612573
transforms.push(utils.findAttr(trans, "Algorithm").value)
613574
}
614575

615-
var inclusiveNamespaces = xpath.select("//*[local-name(.)='InclusiveNamespaces']", transformsNode);
576+
var inclusiveNamespaces = utils.findChilds(trans, "InclusiveNamespaces")
616577
if (inclusiveNamespaces.length > 0) {
617-
inclusiveNamespacesPrefixList = inclusiveNamespaces[0].getAttribute('PrefixList');
578+
//Should really only be one prefix list, but maybe there's some circumstances where more than one to lets handle it
579+
for (var i = 0; i<inclusiveNamespaces.length; i++) {
580+
if (inclusiveNamespacesPrefixList) {
581+
inclusiveNamespacesPrefixList = inclusiveNamespacesPrefixList + " " + inclusiveNamespaces[i].getAttribute('PrefixList');
582+
} else {
583+
inclusiveNamespacesPrefixList = inclusiveNamespaces[i].getAttribute('PrefixList');
584+
}
585+
}
618586
}
619587
}
620588

@@ -916,10 +884,10 @@ SignedXml.prototype.createSignature = function(signedInfo, prefix) {
916884
var xmlNsAttr = 'xmlns'
917885

918886
if (prefix) {
919-
xmlNsAttr += ':' + prefix;
920-
prefix += ':';
887+
xmlNsAttr += ':' + prefix;
888+
prefix += ':';
921889
} else {
922-
prefix = '';
890+
prefix = '';
923891
}
924892

925893
//the canonicalization requires to get a valid xml node.

test/signature-unit-tests.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -474,19 +474,20 @@ module.exports = {
474474
},
475475

476476
"correctly loads signature": function(test) {
477-
passLoadSignature(test, "./test/static/valid_signature.xml");
478-
passLoadSignature(test, "./test/static/valid_signature.xml", true);
479-
passLoadSignature(test, "./test/static/valid_signature_with_root_level_sig_namespace.xml");
477+
passLoadSignature(test, "./test/static/valid_signature.xml")
478+
passLoadSignature(test, "./test/static/valid_signature.xml", true)
479+
passLoadSignature(test, "./test/static/valid_signature_with_root_level_sig_namespace.xml")
480480
test.done()
481481
},
482482

483483
"verify valid signature": function(test) {
484484
passValidSignature(test, "./test/static/valid_signature.xml")
485-
passValidSignature(test, "./test/static/valid_signature_with_lowercase_id_attribute.xml");
485+
passValidSignature(test, "./test/static/valid_signature_with_lowercase_id_attribute.xml")
486486
passValidSignature(test, "./test/static/valid_signature wsu.xml", "wssecurity")
487487
passValidSignature(test, "./test/static/valid_signature_with_reference_keyInfo.xml")
488488
passValidSignature(test, "./test/static/valid_signature_with_whitespace_in_digestvalue.xml")
489489
passValidSignature(test, "./test/static/valid_signature_utf8.xml")
490+
passValidSignature(test, "./test/static/valid_signature_with_unused_prefixes.xml")
490491
test.done()
491492
},
492493

@@ -639,8 +640,8 @@ function failInvalidSignature(test, file, mode) {
639640
function verifySignature(xml, mode) {
640641

641642
var doc = new dom().parseFromString(xml)
642-
var node = select("/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", doc)[0]
643-
643+
var node = select("//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", doc)[0]
644+
644645
var sig = new SignedXml(mode)
645646
sig.keyInfoProvider = new FileKeyInfo("./test/static/client_public.pem")
646647
sig.loadSignature(node)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<soapenv:Envelope xmlns:eg="http://www.example.com" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"><soapenv:Header xmlns:wsa="http://www.w3.org/2005/08/addressing"><wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"><ds:Signature Id="SIG-14" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"><ec:InclusiveNamespaces PrefixList="wsa eg soapenv" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:CanonicalizationMethod><ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><ds:Reference URI="#id-9"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"><ec:InclusiveNamespaces PrefixList="eg" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ds:DigestValue>GAEFzMhRbXD8lZeTd5GwSqkB73Q=</ds:DigestValue></ds:Reference><ds:Reference URI="#id-10"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"><ec:InclusiveNamespaces PrefixList="eg soapenv" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ds:DigestValue>c2bqLRa0Q+/P7+vuXZHO8/iiac8=</ds:DigestValue></ds:Reference><ds:Reference URI="#id-11"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"><ec:InclusiveNamespaces PrefixList="eg soapenv" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ds:DigestValue>95WoCzf9J63UgdLCj/05PYaJIAw=</ds:DigestValue></ds:Reference><ds:Reference URI="#TS-8"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"><ec:InclusiveNamespaces PrefixList="wsse wsa eg soapenv" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ds:DigestValue>pa04vRHbAdvfr6Re5wgx2qFQepE=</ds:DigestValue></ds:Reference><ds:Reference URI="#id-12"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"><ec:InclusiveNamespaces PrefixList="eg soapenv" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ds:DigestValue>YYNZUGNHN8uRGzMqPVC2n5XODP8=</ds:DigestValue></ds:Reference><ds:Reference URI="#id-13"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"><ec:InclusiveNamespaces PrefixList="eg soapenv" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ds:DigestValue>GvVowT6jH/4fExGYfFzDbzhSwTs=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>F8Lm5aUouA5FtxQ1krQ3unE9NFh0QSl+QscEyTcK3FrIpWCB195Z7cbmYa9RsiYMdr2wTHHmou/+wrjpk9pZFJq+b0tpHKCpfj6B302Rexb5f+cDpUjBB/NGb11qaUiM65keVIWzmYnHC0iCxsCaG3lwHMELNr7GxNun1U7LzzI=</ds:SignatureValue><ds:KeyInfo Id="KI-7E559E62741B121FC215529264679845"><wsse:SecurityTokenReference wsu:Id="STR-7E559E62741B121FC215529264679846"><wsse:KeyIdentifier EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3">MIIBxDCCAW6gAwIBAgIQxUSXFzWJYYtOZnmmuOMKkjANBgkqhkiG9w0BAQQFADAWMRQwEgYDVQQDEwtSb290IEFnZW5jeTAeFw0wMzA3MDgxODQ3NTlaFw0zOTEyMzEyMzU5NTlaMB8xHTAbBgNVBAMTFFdTRTJRdWlja1N0YXJ0Q2xpZW50MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+L6aB9x928noY4+0QBsXnxkQE4quJl7c3PUPdVu7k9A02hRG481XIfWhrDY5i7OEB7KGW7qFJotLLeMec/UkKUwCgv3VvJrs2nE9xO3SSWIdNzADukYh+Cxt+FUU6tUkDeqg7dqwivOXhuOTRyOI3HqbWTbumaLdc8jufz2LhaQIDAQABo0swSTBHBgNVHQEEQDA+gBAS5AktBh0dTwCNYSHcFmRjoRgwFjEUMBIGA1UEAxMLUm9vdCBBZ2VuY3mCEAY3bACqAGSKEc+41KpcNfQwDQYJKoZIhvcNAQEEBQADQQAfIbnMPVYkNNfX1tG1F+qfLhHwJdfDUZuPyRPucWF5qkh6sSdWVBY5sT/txBnVJGziyO8DPYdu2fPMER8ajJfl</wsse:KeyIdentifier></wsse:SecurityTokenReference></ds:KeyInfo></ds:Signature><wsu:Timestamp wsu:Id="TS-8"><wsu:Created>2019-03-18T16:27:47.984Z</wsu:Created><wsu:Expires>2019-03-19T04:27:47.984Z</wsu:Expires></wsu:Timestamp></wsse:Security><wsa:Action wsu:Id="id-13" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">http://www.example.com/testAction</wsa:Action><wsa:From wsu:Id="id-12" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"><wsa:Address>client</wsa:Address></wsa:From><wsa:MessageID wsu:Id="id-11" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">uuid:c519e597-0570-4d35-92e5-0df733a17cc1</wsa:MessageID><wsa:To wsu:Id="id-10" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">test</wsa:To></soapenv:Header><soapenv:Body wsu:Id="id-9" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"><eg:node1>testMessage</eg:node1></soapenv:Body></soapenv:Envelope>

0 commit comments

Comments
 (0)