From 7bab299d55c91b52c08727d8b47433d7035e4f8c Mon Sep 17 00:00:00 2001 From: Henri Bergius Date: Tue, 3 Jul 2012 14:39:44 +0200 Subject: [PATCH 1/7] Add 'npm test' command --- package.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 6ec92728..dc0f4622 100644 --- a/package.json +++ b/package.json @@ -18,5 +18,8 @@ "keywords": ["xml", "digital signature", "xml encryption", "x.509 certificate"], "licenses": [{ "type" : "MIT License", - "url" : "http://www.opensource.org/licenses/mit-license.php" }] -} \ No newline at end of file + "url" : "http://www.opensource.org/licenses/mit-license.php" }], + "scripts": { + "test": "./node_modules/nodeunit/bin/nodeunit test" + } +} From e3308a74c8b2cdc37299e2e92e18d81fa457e211 Mon Sep 17 00:00:00 2001 From: Henri Bergius Date: Tue, 3 Jul 2012 14:41:39 +0200 Subject: [PATCH 2/7] Integrate with Travis CI --- .travis.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..88d956c9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +language: node_js +node_js: + - 0.6 + +before_script: + - npm install + +script: npm test From 464b13f44f3ddb0fa07706c2555b805251be7ba3 Mon Sep 17 00:00:00 2001 From: Henri Bergius Date: Tue, 3 Jul 2012 14:46:23 +0200 Subject: [PATCH 3/7] Skip Windows-specific test if not on Win32 --- test/signature-integration-tests.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/signature-integration-tests.js b/test/signature-integration-tests.js index e0589ab6..779df7f9 100644 --- a/test/signature-integration-tests.js +++ b/test/signature-integration-tests.js @@ -30,6 +30,10 @@ module.exports = { } function verifySignature(test, xml, xpath) { + if (process.platform !== 'win32') { + test.done(); + return; + } var sig = new SignedXml() sig.signingKey = fs.readFileSync("./test/static/client.pem") sig.keyInfoCaluse = null @@ -57,4 +61,4 @@ function verifySignature(test, xml, xpath) { test.done() }); -} \ No newline at end of file +} From f8160597c01a1bdba1c5d19e5be97b694a2896c7 Mon Sep 17 00:00:00 2001 From: Henri Bergius Date: Tue, 3 Jul 2012 14:48:32 +0200 Subject: [PATCH 4/7] Don't run this as test directly --- test/xml-assert.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/xml-assert.js b/test/xml-assert.js index d2c7bd6b..95209bdc 100644 --- a/test/xml-assert.js +++ b/test/xml-assert.js @@ -3,8 +3,12 @@ var select = require('../lib/xpath.js').SelectNodes function nodeExists(test, doc, xpath) { + if (!doc && !xpath) { + test.done(); + return; + } var node = select(doc, xpath) test.ok(node.length==1, "xpath " + xpath + " not found") } -exports.nodeExists = nodeExists \ No newline at end of file +exports.nodeExists = nodeExists From 911921109890bb5707823fa66838f97ee1cf5cf4 Mon Sep 17 00:00:00 2001 From: Henri Bergius Date: Tue, 3 Jul 2012 15:03:32 +0200 Subject: [PATCH 5/7] Throw errors instead of strings --- lib/signed-xml.js | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/lib/signed-xml.js b/lib/signed-xml.js index d3a8ca82..224eebae 100644 --- a/lib/signed-xml.js +++ b/lib/signed-xml.js @@ -116,11 +116,11 @@ SignedXml.prototype.checkSignature = function(xml) { this.signedXml = xml if (!this.keyInfoProvider) { - throw "cannot validate signature since no key info resolver was provided" + throw new Error("cannot validate signature since no key info resolver was provided") } this.signingKey = this.keyInfoProvider.getKey(this.keyInfo) - if (!this.signingKey) throw "key info provider could not resolve key info " + this.keyInfo + if (!this.signingKey) throw new Error("key info provider could not resolve key info " + this.keyInfo) var doc = new Dom().parseFromString(xml) @@ -135,7 +135,7 @@ SignedXml.prototype.checkSignature = function(xml) { SignedXml.prototype.validateSignatureValue = function(doc) { var signedInfo = utils.findChilds(this.signatureXmlDoc.documentElement, "SignedInfo") - if (signedInfo.length==0) throw "could not find SignedInfo element in the message" + if (signedInfo.length==0) throw new Error("could not find SignedInfo element in the message") var signedInfoCanon = this.getCanonXml([this.canonicalizationAlgorithm], signedInfo[0]) var signer = this.findSignatureAlgorithm(this.signatureAlgorithm) var res = signer.verifySignature(signedInfoCanon, this.signingKey, this.signatureValue) @@ -147,19 +147,19 @@ SignedXml.prototype.validateSignatureValue = function(doc) { SignedXml.prototype.findSignatureAlgorithm = function(name) { var algo = SignedXml.SignatureAlgorithms[name] if (algo) return new algo() - else throw "signature algorithm '" + name + "' is not supported" + else throw new Error("signature algorithm '" + name + "' is not supported"); } SignedXml.prototype.findCanonicalizationAlgorithm = function(name) { var algo = SignedXml.CanonicalizationAlgorithms[name] if (algo) return new algo() - else throw "canonicalization algorithm '" + name + "' is not supported" + else throw new Error("canonicalization algorithm '" + name + "' is not supported"); } SignedXml.prototype.findHashAlgorithm = function(name) { var algo = SignedXml.HashAlgorithms[name] if (algo) return new algo() - else throw "hash algorithm '" + name + "' is not supported" + else throw new Error("hash algorithm '" + name + "' is not supported"); } @@ -170,9 +170,12 @@ SignedXml.prototype.validateReferences = function(doc) { var uri = ref.uri[0]=="#" ? ref.uri.substring(1) : ref.uri var elem = select(doc, "//*[@*[local-name(.)='Id']='" + uri + "']") if (elem.length==0) { - this.validationErrors.push("invalid signature: the signature refernces an element with uri "+ + elem = select(doc, "//*[@*[local-name(.)='ID']='" + uri + "']") + if (elem.length==0) { + this.validationErrors.push("invalid signature: the signature refernces an element with uri "+ ref.uri + " but could not find such element in the xml") - return false + return false + } } var canonXml = this.getCanonXml(ref.transforms, elem[0]) @@ -196,7 +199,7 @@ SignedXml.prototype.loadSignature = function(signatureXml) { this.signatureXmlDoc = doc var nodes = select(doc, "//*[local-name(.)='CanonicalizationMethod']/@Algorithm") - if (nodes.length==0) throw "could not find CanonicalizationMethod/@Algorithm element" + if (nodes.length==0) throw new Error("could not find CanonicalizationMethod/@Algorithm element") this.canonicalizationAlgorithm = nodes[0].value this.signatureAlgorithm = @@ -204,7 +207,7 @@ SignedXml.prototype.loadSignature = function(signatureXml) { this.references = [] var references = select(doc, "//*[local-name(.)='SignedInfo']/*[local-name(.)='Reference' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']") - if (references.length == 0) throw "could not find any Reference elements" + if (references.length == 0) throw new Error("could not find any Reference elements") for (var i in references) { this.loadReference(references[i]) @@ -222,18 +225,18 @@ SignedXml.prototype.loadSignature = function(signatureXml) { */ SignedXml.prototype.loadReference = function(ref) { var nodes = utils.findChilds(ref, "DigestMethod") - if (nodes.length==0) throw "could not find DigestMethod in reference " + ref.toString() + if (nodes.length==0) throw new Error("could not find DigestMethod in reference " + ref.toString()) var digestAlgoNode = nodes[0] var attr = utils.findAttr(digestAlgoNode, "Algorithm") - if (!attr) throw "could not find Algorithm attribute in node " + digestAlgoNode.toString() + if (!attr) throw new Error("could not find Algorithm attribute in node " + digestAlgoNode.toString()) var digestAlgo = attr.value nodes = utils.findChilds(ref, "DigestValue") - if (nodes.length==0) throw "could not find DigestValue node in reference " + ref.toString() + if (nodes.length==0) throw new Error("could not find DigestValue node in reference " + ref.toString()) if (nodes[0].childNodes.length==0 || !nodes[0].firstChild.data) { - throw "could not find the value of DigestValue in " + nodes[0].toString() + throw new Error("could not find the value of DigestValue in " + nodes[0].toString()) } var digestValue = nodes[0].firstChild.data From 9a584428344cbd7e124f6e63462a9fc04e619f50 Mon Sep 17 00:00:00 2001 From: Henri Bergius Date: Tue, 3 Jul 2012 15:50:11 +0200 Subject: [PATCH 6/7] Initial enveloped signature support --- lib/enveloped-signature.js | 20 ++++++++++++++++++++ lib/signed-xml.js | 14 +++++++++----- test/saml-response-test.js | 15 +++++++++++++++ test/static/feide_public.pem | 16 ++++++++++++++++ test/static/valid_saml.xml | 9 +++++++++ 5 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 lib/enveloped-signature.js create mode 100644 test/saml-response-test.js create mode 100644 test/static/feide_public.pem create mode 100644 test/static/valid_saml.xml diff --git a/lib/enveloped-signature.js b/lib/enveloped-signature.js new file mode 100644 index 00000000..3c93e939 --- /dev/null +++ b/lib/enveloped-signature.js @@ -0,0 +1,20 @@ +var xpath = require('./xpath'); + +exports.EnvelopedSignature = EnvelopedSignature; + +function EnvelopedSignature() { +} + +EnvelopedSignature.prototype.process = function (node) { + var signature = xpath.SelectNodes(node.ownerDocument, "/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0]; + + var xml = "" + node; + var sigXml = "" + signature; + + var cleaned = xml.replace(sigXml, ''); + return cleaned; +}; + +EnvelopedSignature.prototype.getAlgorithmName = function () { + return "http://www.w3.org/2000/09/xmldsig#enveloped-signature"; +}; diff --git a/lib/signed-xml.js b/lib/signed-xml.js index 224eebae..8ffe36dc 100644 --- a/lib/signed-xml.js +++ b/lib/signed-xml.js @@ -2,6 +2,7 @@ var select = require('./xpath.js').SelectNodes , Dom = require('xmldom').DOMParser , utils = require('./utils') , ExclusiveCanonicalization = require('./exclusive-canonicalization').ExclusiveCanonicalization + , EnvelopedSignature = require('./enveloped-signature').EnvelopedSignature , crypto = require('crypto') , fs = require('fs') @@ -100,7 +101,8 @@ function SignedXml(idMode) { } SignedXml.CanonicalizationAlgorithms = { - 'http://www.w3.org/2001/10/xml-exc-c14n#': ExclusiveCanonicalization + 'http://www.w3.org/2001/10/xml-exc-c14n#': ExclusiveCanonicalization, + 'http://www.w3.org/2000/09/xmldsig#enveloped-signature': EnvelopedSignature } SignedXml.HashAlgorithms = { @@ -124,11 +126,13 @@ SignedXml.prototype.checkSignature = function(xml) { var doc = new Dom().parseFromString(xml) - if (!this.validateReferences(doc)) - return false + if (!this.validateReferences(doc)) { + return false; + } - if (!this.validateSignatureValue(doc)) - return false + if (!this.validateSignatureValue(doc)) { + return false; + } return true } diff --git a/test/saml-response-test.js b/test/saml-response-test.js new file mode 100644 index 00000000..db0b3bbf --- /dev/null +++ b/test/saml-response-test.js @@ -0,0 +1,15 @@ +var crypto = require('../index'); +var xmldom = require('xmldom'); +var fs = require('fs'); + +exports['test validating SAML response'] = function (test) { + var xml = fs.readFileSync('./test/static/valid_saml.xml', 'utf-8'); + var doc = new xmldom.DOMParser().parseFromString(xml); + var signature = crypto.xpath.SelectNodes(doc, "/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0]; + var sig = new crypto.SignedXml(); + sig.keyInfoProvider = new crypto.FileKeyInfo("./test/static/feide_public.pem"); + sig.loadSignature(signature.toString()); + var result = sig.checkSignature(xml); + test.equal(result, true); + test.done(); +}; diff --git a/test/static/feide_public.pem b/test/static/feide_public.pem new file mode 100644 index 00000000..801dcf77 --- /dev/null +++ b/test/static/feide_public.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICizCCAfQCCQCY8tKaMc0BMjANBgkqhkiG9w0BAQUFADCBiTELMAkGA1UEBhMC +Tk8xEjAQBgNVBAgTCVRyb25kaGVpbTEQMA4GA1UEChMHVU5JTkVUVDEOMAwGA1UE +CxMFRmVpZGUxGTAXBgNVBAMTEG9wZW5pZHAuZmVpZGUubm8xKTAnBgkqhkiG9w0B +CQEWGmFuZHJlYXMuc29sYmVyZ0B1bmluZXR0Lm5vMB4XDTA4MDUwODA5MjI0OFoX +DTM1MDkyMzA5MjI0OFowgYkxCzAJBgNVBAYTAk5PMRIwEAYDVQQIEwlUcm9uZGhl +aW0xEDAOBgNVBAoTB1VOSU5FVFQxDjAMBgNVBAsTBUZlaWRlMRkwFwYDVQQDExBv +cGVuaWRwLmZlaWRlLm5vMSkwJwYJKoZIhvcNAQkBFhphbmRyZWFzLnNvbGJlcmdA +dW5pbmV0dC5ubzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAt8jLoqI1VTlx +AZ2axiDIThWcAOXdu8KkVUWaN/SooO9O0QQ7KRUjSGKN9JK65AFRDXQkWPAu4Hln +O4noYlFSLnYyDxI66LCr71x4lgFJjqLeAvB/GqBqFfIZ3YK/NrhnUqFwZu63nLrZ +jcUZxNaPjOOSRSDaXpv1kb5k3jOiSGECAwEAATANBgkqhkiG9w0BAQUFAAOBgQBQ +Yj4cAafWaYfjBU2zi1ElwStIaJ5nyp/s/8B8SAPK2T79McMyccP3wSW13LHkmM1j +wKe3ACFXBvqGQN0IbcH49hu0FKhYFM/GPDJcIHFBsiyMBXChpye9vBaTNEBCtU3K +jjyG0hRT2mAQ9h+bkPmOvlEo/aH0xR68Z9hw4PF13w== +-----END CERTIFICATE----- diff --git a/test/static/valid_saml.xml b/test/static/valid_saml.xml new file mode 100644 index 00000000..afbfa14f --- /dev/null +++ b/test/static/valid_saml.xml @@ -0,0 +1,9 @@ +https://openidp.feide.no + + + fc21hh1bKZpaMNjx9HfOfVelfWw=dkONrkxW+LSuDvnNMG/mWYFa47d2WGyapLhXSTYqrlT9Td+tT7ciojNJ55WTaPaCMt7IrGtIxxskPAZIjdIn5pRyDxHr0joWxzZ7oZHCOI1CnQV5HjOq+rzzmEN2LctCZ6S4hbL7SQ1qJ3vp2BCXAygy4tmJOURQdnk0KLwwRS8= +MIICizCCAfQCCQCY8tKaMc0BMjANBgkqhkiG9w0BAQUFADCBiTELMAkGA1UEBhMCTk8xEjAQBgNVBAgTCVRyb25kaGVpbTEQMA4GA1UEChMHVU5JTkVUVDEOMAwGA1UECxMFRmVpZGUxGTAXBgNVBAMTEG9wZW5pZHAuZmVpZGUubm8xKTAnBgkqhkiG9w0BCQEWGmFuZHJlYXMuc29sYmVyZ0B1bmluZXR0Lm5vMB4XDTA4MDUwODA5MjI0OFoXDTM1MDkyMzA5MjI0OFowgYkxCzAJBgNVBAYTAk5PMRIwEAYDVQQIEwlUcm9uZGhlaW0xEDAOBgNVBAoTB1VOSU5FVFQxDjAMBgNVBAsTBUZlaWRlMRkwFwYDVQQDExBvcGVuaWRwLmZlaWRlLm5vMSkwJwYJKoZIhvcNAQkBFhphbmRyZWFzLnNvbGJlcmdAdW5pbmV0dC5ubzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAt8jLoqI1VTlxAZ2axiDIThWcAOXdu8KkVUWaN/SooO9O0QQ7KRUjSGKN9JK65AFRDXQkWPAu4HlnO4noYlFSLnYyDxI66LCr71x4lgFJjqLeAvB/GqBqFfIZ3YK/NrhnUqFwZu63nLrZjcUZxNaPjOOSRSDaXpv1kb5k3jOiSGECAwEAATANBgkqhkiG9w0BAQUFAAOBgQBQYj4cAafWaYfjBU2zi1ElwStIaJ5nyp/s/8B8SAPK2T79McMyccP3wSW13LHkmM1jwKe3ACFXBvqGQN0IbcH49hu0FKhYFM/GPDJcIHFBsiyMBXChpye9vBaTNEBCtU3KjjyG0hRT2mAQ9h+bkPmOvlEo/aH0xR68Z9hw4PF13w==https://openidp.feide.no + + + RnNjoyUguwze5w2R+cboyTHlkQk=aw5711jKP7xragunjRRCAD4mT4xKHc37iohBpQDbdSomD3ksOSB96UZQp0MtaC3xlVSkMtYw85Om96T2q2xrxLLYVA50eFJEMMF7SCVPStWTVjBlaCuOPEQxIaHyJs9Sy3MCEfbBh4Pqn9IJBd1kzwdlCrWWjAmksbFFg5wHQJA= +MIICizCCAfQCCQCY8tKaMc0BMjANBgkqhkiG9w0BAQUFADCBiTELMAkGA1UEBhMCTk8xEjAQBgNVBAgTCVRyb25kaGVpbTEQMA4GA1UEChMHVU5JTkVUVDEOMAwGA1UECxMFRmVpZGUxGTAXBgNVBAMTEG9wZW5pZHAuZmVpZGUubm8xKTAnBgkqhkiG9w0BCQEWGmFuZHJlYXMuc29sYmVyZ0B1bmluZXR0Lm5vMB4XDTA4MDUwODA5MjI0OFoXDTM1MDkyMzA5MjI0OFowgYkxCzAJBgNVBAYTAk5PMRIwEAYDVQQIEwlUcm9uZGhlaW0xEDAOBgNVBAoTB1VOSU5FVFQxDjAMBgNVBAsTBUZlaWRlMRkwFwYDVQQDExBvcGVuaWRwLmZlaWRlLm5vMSkwJwYJKoZIhvcNAQkBFhphbmRyZWFzLnNvbGJlcmdAdW5pbmV0dC5ubzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAt8jLoqI1VTlxAZ2axiDIThWcAOXdu8KkVUWaN/SooO9O0QQ7KRUjSGKN9JK65AFRDXQkWPAu4HlnO4noYlFSLnYyDxI66LCr71x4lgFJjqLeAvB/GqBqFfIZ3YK/NrhnUqFwZu63nLrZjcUZxNaPjOOSRSDaXpv1kb5k3jOiSGECAwEAATANBgkqhkiG9w0BAQUFAAOBgQBQYj4cAafWaYfjBU2zi1ElwStIaJ5nyp/s/8B8SAPK2T79McMyccP3wSW13LHkmM1jwKe3ACFXBvqGQN0IbcH49hu0FKhYFM/GPDJcIHFBsiyMBXChpye9vBaTNEBCtU3KjjyG0hRT2mAQ9h+bkPmOvlEo/aH0xR68Z9hw4PF13w==_6c5dcaa3053321ff4d63785fbc3f67c59a129cde82passport-samlurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordbergieHenriBergiusHenri Bergiushenri.bergius@nemein.combergie@rnd.feide.no8216c78fe244502efa13f62e6615c94acb7bdf3ebergieHenriBergiusHenri Bergiushenri.bergius@nemein.combergie@rnd.feide.no8216c78fe244502efa13f62e6615c94acb7bdf3e From cb8be357c57c2a1ab8e36a69c4132f35978f5c25 Mon Sep 17 00:00:00 2001 From: Henri Bergius Date: Tue, 3 Jul 2012 17:01:55 +0200 Subject: [PATCH 7/7] Support uppercase IDs as well --- lib/signed-xml.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/signed-xml.js b/lib/signed-xml.js index 8ffe36dc..b3786d19 100644 --- a/lib/signed-xml.js +++ b/lib/signed-xml.js @@ -365,8 +365,11 @@ SignedXml.prototype.ensureHasId = function(node) { } else { attr = utils.findAttr(node, "Id", null) + if (!attr) { + attr = utils.findAttr(node, "ID", null) + } } - + if (attr) return attr.value //add the attribute