xml-crypto
Advanced tools
Comparing version 3.0.1 to 3.1.0
10
index.js
@@ -1,6 +0,6 @@ | ||
var select = require('xpath').select | ||
var select = require("xpath").select; | ||
module.exports = require('./lib/signed-xml') | ||
module.exports.xpath = function(node, xpath) { | ||
return select(xpath, node) | ||
} | ||
module.exports = require("./lib/signed-xml"); | ||
module.exports.xpath = function (node, xpath) { | ||
return select(xpath, node); | ||
}; |
/* jshint laxcomma: true */ | ||
var utils = require('./utils'); | ||
var utils = require("./utils"); | ||
@@ -8,31 +8,40 @@ exports.C14nCanonicalization = C14nCanonicalization; | ||
function C14nCanonicalization() { | ||
this.includeComments = false; | ||
}; | ||
this.includeComments = false; | ||
} | ||
C14nCanonicalization.prototype.attrCompare = function(a,b) { | ||
if (!a.namespaceURI && b.namespaceURI) { return -1; } | ||
if (!b.namespaceURI && a.namespaceURI) { return 1; } | ||
C14nCanonicalization.prototype.attrCompare = function (a, b) { | ||
if (!a.namespaceURI && b.namespaceURI) { | ||
return -1; | ||
} | ||
if (!b.namespaceURI && a.namespaceURI) { | ||
return 1; | ||
} | ||
var left = a.namespaceURI + a.localName | ||
var right = b.namespaceURI + b.localName | ||
var left = a.namespaceURI + a.localName; | ||
var right = b.namespaceURI + b.localName; | ||
if (left===right) return 0 | ||
else if (left<right) return -1 | ||
else return 1 | ||
if (left === right) return 0; | ||
else if (left < right) return -1; | ||
else return 1; | ||
}; | ||
C14nCanonicalization.prototype.nsCompare = function(a,b) { | ||
C14nCanonicalization.prototype.nsCompare = function (a, b) { | ||
var attr1 = a.prefix; | ||
var attr2 = b.prefix; | ||
if (attr1 == attr2) { return 0; } | ||
if (attr1 == attr2) { | ||
return 0; | ||
} | ||
return attr1.localeCompare(attr2); | ||
}; | ||
C14nCanonicalization.prototype.renderAttrs = function(node, defaultNS) { | ||
var a, i, attr | ||
, res = [] | ||
, attrListToRender = []; | ||
C14nCanonicalization.prototype.renderAttrs = function (node, defaultNS) { | ||
var a, | ||
i, | ||
attr, | ||
res = [], | ||
attrListToRender = []; | ||
if (node.nodeType===8) { return this.renderComment(node); } | ||
if (node.nodeType === 8) { | ||
return this.renderComment(node); | ||
} | ||
@@ -43,3 +52,5 @@ if (node.attributes) { | ||
//ignore namespace definition attributes | ||
if (attr.name.indexOf("xmlns") === 0) { continue; } | ||
if (attr.name.indexOf("xmlns") === 0) { | ||
continue; | ||
} | ||
attrListToRender.push(attr); | ||
@@ -52,3 +63,5 @@ } | ||
for (a in attrListToRender) { | ||
if (!attrListToRender.hasOwnProperty(a)) { continue; } | ||
if (!attrListToRender.hasOwnProperty(a)) { | ||
continue; | ||
} | ||
@@ -62,3 +75,2 @@ attr = attrListToRender[a]; | ||
/** | ||
@@ -76,21 +88,32 @@ * Create the string of all namespace declarations that should appear on this element | ||
*/ | ||
C14nCanonicalization.prototype.renderNs = function(node, prefixesInScope, defaultNs, defaultNsForPrefix, ancestorNamespaces) { | ||
var a, i, p, attr | ||
, res = [] | ||
, newDefaultNs = defaultNs | ||
, nsListToRender = [] | ||
, currNs = node.namespaceURI || ""; | ||
C14nCanonicalization.prototype.renderNs = function ( | ||
node, | ||
prefixesInScope, | ||
defaultNs, | ||
defaultNsForPrefix, | ||
ancestorNamespaces | ||
) { | ||
var a, | ||
i, | ||
p, | ||
attr, | ||
res = [], | ||
newDefaultNs = defaultNs, | ||
nsListToRender = [], | ||
currNs = node.namespaceURI || ""; | ||
//handle the namespaceof the node itself | ||
if (node.prefix) { | ||
if (prefixesInScope.indexOf(node.prefix)==-1) { | ||
nsListToRender.push({"prefix": node.prefix, "namespaceURI": node.namespaceURI || defaultNsForPrefix[node.prefix]}); | ||
if (prefixesInScope.indexOf(node.prefix) == -1) { | ||
nsListToRender.push({ | ||
prefix: node.prefix, | ||
namespaceURI: node.namespaceURI || defaultNsForPrefix[node.prefix], | ||
}); | ||
prefixesInScope.push(node.prefix); | ||
} | ||
} else if (defaultNs != currNs) { | ||
//new default ns | ||
newDefaultNs = node.namespaceURI; | ||
res.push(' xmlns="', newDefaultNs, '"'); | ||
} | ||
else if (defaultNs!=currNs) { | ||
//new default ns | ||
newDefaultNs = node.namespaceURI; | ||
res.push(' xmlns="', newDefaultNs, '"'); | ||
} | ||
@@ -105,3 +128,3 @@ //handle the attributes namespace | ||
if (attr.prefix === "xmlns" && prefixesInScope.indexOf(attr.localName) === -1) { | ||
nsListToRender.push({"prefix": attr.localName, "namespaceURI": attr.value}); | ||
nsListToRender.push({ prefix: attr.localName, namespaceURI: attr.value }); | ||
prefixesInScope.push(attr.localName); | ||
@@ -112,4 +135,9 @@ } | ||
//the prefix is not defined already | ||
if (attr.prefix && prefixesInScope.indexOf(attr.prefix)==-1 && attr.prefix!="xmlns" && attr.prefix!="xml") { | ||
nsListToRender.push({"prefix": attr.prefix, "namespaceURI": attr.namespaceURI}); | ||
if ( | ||
attr.prefix && | ||
prefixesInScope.indexOf(attr.prefix) == -1 && | ||
attr.prefix != "xmlns" && | ||
attr.prefix != "xml" | ||
) { | ||
nsListToRender.push({ prefix: attr.prefix, namespaceURI: attr.namespaceURI }); | ||
prefixesInScope.push(attr.prefix); | ||
@@ -119,17 +147,18 @@ } | ||
} | ||
if(Array.isArray(ancestorNamespaces) && ancestorNamespaces.length > 0){ | ||
if (Array.isArray(ancestorNamespaces) && ancestorNamespaces.length > 0) { | ||
// Remove namespaces which are already present in nsListToRender | ||
for(var p1 in ancestorNamespaces){ | ||
if(!ancestorNamespaces.hasOwnProperty(p1)) continue; | ||
for (var p1 in ancestorNamespaces) { | ||
if (!ancestorNamespaces.hasOwnProperty(p1)) continue; | ||
var alreadyListed = false; | ||
for(var p2 in nsListToRender){ | ||
if(nsListToRender[p2].prefix === ancestorNamespaces[p1].prefix | ||
&& nsListToRender[p2].namespaceURI === ancestorNamespaces[p1].namespaceURI) | ||
{ | ||
for (var p2 in nsListToRender) { | ||
if ( | ||
nsListToRender[p2].prefix === ancestorNamespaces[p1].prefix && | ||
nsListToRender[p2].namespaceURI === ancestorNamespaces[p1].namespaceURI | ||
) { | ||
alreadyListed = true; | ||
} | ||
} | ||
if(!alreadyListed){ | ||
if (!alreadyListed) { | ||
nsListToRender.push(ancestorNamespaces[p1]); | ||
@@ -144,3 +173,5 @@ } | ||
for (a in nsListToRender) { | ||
if (!nsListToRender.hasOwnProperty(a)) { continue; } | ||
if (!nsListToRender.hasOwnProperty(a)) { | ||
continue; | ||
} | ||
@@ -151,17 +182,29 @@ p = nsListToRender[a]; | ||
return {"rendered": res.join(""), "newDefaultNs": newDefaultNs}; | ||
return { rendered: res.join(""), newDefaultNs: newDefaultNs }; | ||
}; | ||
C14nCanonicalization.prototype.processInner = function(node, prefixesInScope, defaultNs, defaultNsForPrefix, ancestorNamespaces) { | ||
C14nCanonicalization.prototype.processInner = function ( | ||
node, | ||
prefixesInScope, | ||
defaultNs, | ||
defaultNsForPrefix, | ||
ancestorNamespaces | ||
) { | ||
if (node.nodeType === 8) { | ||
return this.renderComment(node); | ||
} | ||
if (node.data) { | ||
return utils.encodeSpecialCharactersInText(node.data); | ||
} | ||
if (node.nodeType === 8) { return this.renderComment(node); } | ||
if (node.data) { return utils.encodeSpecialCharactersInText(node.data); } | ||
var i, | ||
pfxCopy, | ||
ns = this.renderNs(node, prefixesInScope, defaultNs, defaultNsForPrefix, ancestorNamespaces), | ||
res = ["<", node.tagName, ns.rendered, this.renderAttrs(node, ns.newDefaultNs), ">"]; | ||
var i, pfxCopy | ||
, ns = this.renderNs(node, prefixesInScope, defaultNs, defaultNsForPrefix, ancestorNamespaces) | ||
, res = ["<", node.tagName, ns.rendered, this.renderAttrs(node, ns.newDefaultNs), ">"]; | ||
for (i = 0; i < node.childNodes.length; ++i) { | ||
pfxCopy = prefixesInScope.slice(0); | ||
res.push(this.processInner(node.childNodes[i], pfxCopy, ns.newDefaultNs, defaultNsForPrefix, [])); | ||
res.push( | ||
this.processInner(node.childNodes[i], pfxCopy, ns.newDefaultNs, defaultNsForPrefix, []) | ||
); | ||
} | ||
@@ -175,12 +218,13 @@ | ||
C14nCanonicalization.prototype.renderComment = function (node) { | ||
if (!this.includeComments) { | ||
return ""; | ||
} | ||
if (!this.includeComments) { return ""; } | ||
var isOutsideDocument = node.ownerDocument === node.parentNode, | ||
isBeforeDocument = null, | ||
isAfterDocument = null; | ||
var isOutsideDocument = (node.ownerDocument === node.parentNode), | ||
isBeforeDocument = null, | ||
isAfterDocument = null; | ||
if (isOutsideDocument) { | ||
var nextNode = node, | ||
previousNode = node; | ||
previousNode = node; | ||
@@ -206,3 +250,9 @@ while (nextNode !== null) { | ||
return (isAfterDocument ? "\n" : "") + "<!--" + utils.encodeSpecialCharactersInText(node.data) + "-->" + (isBeforeDocument ? "\n" : ""); | ||
return ( | ||
(isAfterDocument ? "\n" : "") + | ||
"<!--" + | ||
utils.encodeSpecialCharactersInText(node.data) + | ||
"-->" + | ||
(isBeforeDocument ? "\n" : "") | ||
); | ||
}; | ||
@@ -217,3 +267,3 @@ | ||
*/ | ||
C14nCanonicalization.prototype.process = function(node, options) { | ||
C14nCanonicalization.prototype.process = function (node, options) { | ||
options = options || {}; | ||
@@ -229,7 +279,13 @@ var defaultNs = options.defaultNs || ""; | ||
var res = this.processInner(node, prefixesInScope, defaultNs, defaultNsForPrefix, ancestorNamespaces); | ||
var res = this.processInner( | ||
node, | ||
prefixesInScope, | ||
defaultNs, | ||
defaultNsForPrefix, | ||
ancestorNamespaces | ||
); | ||
return res; | ||
}; | ||
C14nCanonicalization.prototype.getAlgorithmName = function() { | ||
C14nCanonicalization.prototype.getAlgorithmName = function () { | ||
return "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"; | ||
@@ -242,5 +298,5 @@ }; | ||
function C14nCanonicalizationWithComments() { | ||
C14nCanonicalization.call(this); | ||
this.includeComments = true; | ||
}; | ||
C14nCanonicalization.call(this); | ||
this.includeComments = true; | ||
} | ||
@@ -251,4 +307,4 @@ C14nCanonicalizationWithComments.prototype = Object.create(C14nCanonicalization.prototype); | ||
C14nCanonicalizationWithComments.prototype.getAlgorithmName = function() { | ||
C14nCanonicalizationWithComments.prototype.getAlgorithmName = function () { | ||
return "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"; | ||
}; |
@@ -1,8 +0,7 @@ | ||
var xpath = require('xpath'); | ||
var utils = require('./utils'); | ||
var xpath = require("xpath"); | ||
var utils = require("./utils"); | ||
exports.EnvelopedSignature = EnvelopedSignature; | ||
function EnvelopedSignature() { | ||
} | ||
function EnvelopedSignature() {} | ||
@@ -12,3 +11,6 @@ EnvelopedSignature.prototype.process = function (node, options) { | ||
// leave this for the moment... | ||
var signature = xpath.select("./*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", node)[0]; | ||
var signature = xpath.select( | ||
"./*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", | ||
node | ||
)[0]; | ||
if (signature) signature.parentNode.removeChild(signature); | ||
@@ -18,10 +20,19 @@ return node; | ||
var signatureNode = options.signatureNode; | ||
var expectedSignatureValue = utils.findFirst(signatureNode, ".//*[local-name(.)='SignatureValue']/text()").data; | ||
var signatures = xpath.select(".//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", node); | ||
var expectedSignatureValue = utils.findFirst( | ||
signatureNode, | ||
".//*[local-name(.)='SignatureValue']/text()" | ||
).data; | ||
var signatures = xpath.select( | ||
".//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", | ||
node | ||
); | ||
for (var h in signatures) { | ||
if (!signatures.hasOwnProperty(h)) continue; | ||
var signature = signatures[h]; | ||
var signatureValue = utils.findFirst(signature, ".//*[local-name(.)='SignatureValue']/text()").data; | ||
var nodeSignature = signatures[h]; | ||
var signatureValue = utils.findFirst( | ||
nodeSignature, | ||
".//*[local-name(.)='SignatureValue']/text()" | ||
).data; | ||
if (expectedSignatureValue === signatureValue) { | ||
signature.parentNode.removeChild(signature); | ||
nodeSignature.parentNode.removeChild(nodeSignature); | ||
} | ||
@@ -28,0 +39,0 @@ } |
/* jshint laxcomma: true */ | ||
var utils = require('./utils'); | ||
var utils = require("./utils"); | ||
@@ -8,31 +8,40 @@ exports.ExclusiveCanonicalization = ExclusiveCanonicalization; | ||
function ExclusiveCanonicalization() { | ||
this.includeComments = false; | ||
}; | ||
this.includeComments = false; | ||
} | ||
ExclusiveCanonicalization.prototype.attrCompare = function(a,b) { | ||
if (!a.namespaceURI && b.namespaceURI) { return -1; } | ||
if (!b.namespaceURI && a.namespaceURI) { return 1; } | ||
ExclusiveCanonicalization.prototype.attrCompare = function (a, b) { | ||
if (!a.namespaceURI && b.namespaceURI) { | ||
return -1; | ||
} | ||
if (!b.namespaceURI && a.namespaceURI) { | ||
return 1; | ||
} | ||
var left = a.namespaceURI + a.localName | ||
var right = b.namespaceURI + b.localName | ||
var left = a.namespaceURI + a.localName; | ||
var right = b.namespaceURI + b.localName; | ||
if (left===right) return 0 | ||
else if (left<right) return -1 | ||
else return 1 | ||
if (left === right) return 0; | ||
else if (left < right) return -1; | ||
else return 1; | ||
}; | ||
ExclusiveCanonicalization.prototype.nsCompare = function(a,b) { | ||
ExclusiveCanonicalization.prototype.nsCompare = function (a, b) { | ||
var attr1 = a.prefix; | ||
var attr2 = b.prefix; | ||
if (attr1 == attr2) { return 0; } | ||
if (attr1 == attr2) { | ||
return 0; | ||
} | ||
return attr1.localeCompare(attr2); | ||
}; | ||
ExclusiveCanonicalization.prototype.renderAttrs = function(node, defaultNS) { | ||
var a, i, attr | ||
, res = [] | ||
, attrListToRender = []; | ||
ExclusiveCanonicalization.prototype.renderAttrs = function (node, defaultNS) { | ||
var a, | ||
i, | ||
attr, | ||
res = [], | ||
attrListToRender = []; | ||
if (node.nodeType===8) { return this.renderComment(node); } | ||
if (node.nodeType === 8) { | ||
return this.renderComment(node); | ||
} | ||
@@ -43,3 +52,5 @@ if (node.attributes) { | ||
//ignore namespace definition attributes | ||
if (attr.name.indexOf("xmlns") === 0) { continue; } | ||
if (attr.name.indexOf("xmlns") === 0) { | ||
continue; | ||
} | ||
attrListToRender.push(attr); | ||
@@ -52,3 +63,5 @@ } | ||
for (a in attrListToRender) { | ||
if (!attrListToRender.hasOwnProperty(a)) { continue; } | ||
if (!attrListToRender.hasOwnProperty(a)) { | ||
continue; | ||
} | ||
@@ -62,4 +75,3 @@ attr = attrListToRender[a]; | ||
function isPrefixInScope(prefixesInScope, prefix, namespaceURI) | ||
{ | ||
function isPrefixInScope(prefixesInScope, prefix, namespaceURI) { | ||
var ret = false; | ||
@@ -70,3 +82,3 @@ prefixesInScope.forEach(function (pf) { | ||
} | ||
}) | ||
}); | ||
@@ -86,21 +98,41 @@ return ret; | ||
*/ | ||
ExclusiveCanonicalization.prototype.renderNs = function(node, prefixesInScope, defaultNs, defaultNsForPrefix, inclusiveNamespacesPrefixList) { | ||
var a, i, p, attr | ||
, res = [] | ||
, newDefaultNs = defaultNs | ||
, nsListToRender = [] | ||
, currNs = node.namespaceURI || ""; | ||
ExclusiveCanonicalization.prototype.renderNs = function ( | ||
node, | ||
prefixesInScope, | ||
defaultNs, | ||
defaultNsForPrefix, | ||
inclusiveNamespacesPrefixList | ||
) { | ||
var a, | ||
i, | ||
p, | ||
attr, | ||
res = [], | ||
newDefaultNs = defaultNs, | ||
nsListToRender = [], | ||
currNs = node.namespaceURI || ""; | ||
//handle the namespaceof the node itself | ||
if (node.prefix) { | ||
if (!isPrefixInScope(prefixesInScope, node.prefix, node.namespaceURI || defaultNsForPrefix[node.prefix])) { | ||
nsListToRender.push({"prefix": node.prefix, "namespaceURI": node.namespaceURI || defaultNsForPrefix[node.prefix]}); | ||
prefixesInScope.push({"prefix": node.prefix, "namespaceURI": node.namespaceURI || defaultNsForPrefix[node.prefix]}); | ||
if ( | ||
!isPrefixInScope( | ||
prefixesInScope, | ||
node.prefix, | ||
node.namespaceURI || defaultNsForPrefix[node.prefix] | ||
) | ||
) { | ||
nsListToRender.push({ | ||
prefix: node.prefix, | ||
namespaceURI: node.namespaceURI || defaultNsForPrefix[node.prefix], | ||
}); | ||
prefixesInScope.push({ | ||
prefix: node.prefix, | ||
namespaceURI: node.namespaceURI || defaultNsForPrefix[node.prefix], | ||
}); | ||
} | ||
} else if (defaultNs != currNs) { | ||
//new default ns | ||
newDefaultNs = node.namespaceURI; | ||
res.push(' xmlns="', newDefaultNs, '"'); | ||
} | ||
else if (defaultNs!=currNs) { | ||
//new default ns | ||
newDefaultNs = node.namespaceURI; | ||
res.push(' xmlns="', newDefaultNs, '"'); | ||
} | ||
@@ -114,5 +146,9 @@ //handle the attributes namespace | ||
//the prefix is not defined already | ||
if (attr.prefix && !isPrefixInScope(prefixesInScope, attr.localName, attr.value) && inclusiveNamespacesPrefixList.indexOf(attr.localName) >= 0) { | ||
nsListToRender.push({"prefix": attr.localName, "namespaceURI": attr.value}); | ||
prefixesInScope.push({"prefix": attr.localName, "namespaceURI": attr.value}); | ||
if ( | ||
attr.prefix && | ||
!isPrefixInScope(prefixesInScope, attr.localName, attr.value) && | ||
inclusiveNamespacesPrefixList.indexOf(attr.localName) >= 0 | ||
) { | ||
nsListToRender.push({ prefix: attr.localName, namespaceURI: attr.value }); | ||
prefixesInScope.push({ prefix: attr.localName, namespaceURI: attr.value }); | ||
} | ||
@@ -122,5 +158,10 @@ | ||
//the prefix is not defined already | ||
if (attr.prefix && !isPrefixInScope(prefixesInScope, attr.prefix, attr.namespaceURI) && attr.prefix!="xmlns" && attr.prefix!="xml") { | ||
nsListToRender.push({"prefix": attr.prefix, "namespaceURI": attr.namespaceURI}); | ||
prefixesInScope.push({"prefix": attr.prefix, "namespaceURI": attr.namespaceURI}); | ||
if ( | ||
attr.prefix && | ||
!isPrefixInScope(prefixesInScope, attr.prefix, attr.namespaceURI) && | ||
attr.prefix != "xmlns" && | ||
attr.prefix != "xml" | ||
) { | ||
nsListToRender.push({ prefix: attr.prefix, namespaceURI: attr.namespaceURI }); | ||
prefixesInScope.push({ prefix: attr.prefix, namespaceURI: attr.namespaceURI }); | ||
} | ||
@@ -134,3 +175,5 @@ } | ||
for (a in nsListToRender) { | ||
if (!nsListToRender.hasOwnProperty(a)) { continue; } | ||
if (!nsListToRender.hasOwnProperty(a)) { | ||
continue; | ||
} | ||
@@ -141,17 +184,41 @@ p = nsListToRender[a]; | ||
return {"rendered": res.join(""), "newDefaultNs": newDefaultNs}; | ||
return { rendered: res.join(""), newDefaultNs: newDefaultNs }; | ||
}; | ||
ExclusiveCanonicalization.prototype.processInner = function(node, prefixesInScope, defaultNs, defaultNsForPrefix, inclusiveNamespacesPrefixList) { | ||
ExclusiveCanonicalization.prototype.processInner = function ( | ||
node, | ||
prefixesInScope, | ||
defaultNs, | ||
defaultNsForPrefix, | ||
inclusiveNamespacesPrefixList | ||
) { | ||
if (node.nodeType === 8) { | ||
return this.renderComment(node); | ||
} | ||
if (node.data) { | ||
return utils.encodeSpecialCharactersInText(node.data); | ||
} | ||
if (node.nodeType === 8) { return this.renderComment(node); } | ||
if (node.data) { return utils.encodeSpecialCharactersInText(node.data); } | ||
var i, | ||
pfxCopy, | ||
ns = this.renderNs( | ||
node, | ||
prefixesInScope, | ||
defaultNs, | ||
defaultNsForPrefix, | ||
inclusiveNamespacesPrefixList | ||
), | ||
res = ["<", node.tagName, ns.rendered, this.renderAttrs(node, ns.newDefaultNs), ">"]; | ||
var i, pfxCopy | ||
, ns = this.renderNs(node, prefixesInScope, defaultNs, defaultNsForPrefix, inclusiveNamespacesPrefixList) | ||
, res = ["<", node.tagName, ns.rendered, this.renderAttrs(node, ns.newDefaultNs), ">"]; | ||
for (i = 0; i < node.childNodes.length; ++i) { | ||
pfxCopy = prefixesInScope.slice(0); | ||
res.push(this.processInner(node.childNodes[i], pfxCopy, ns.newDefaultNs, defaultNsForPrefix, inclusiveNamespacesPrefixList)); | ||
res.push( | ||
this.processInner( | ||
node.childNodes[i], | ||
pfxCopy, | ||
ns.newDefaultNs, | ||
defaultNsForPrefix, | ||
inclusiveNamespacesPrefixList | ||
) | ||
); | ||
} | ||
@@ -165,12 +232,13 @@ | ||
ExclusiveCanonicalization.prototype.renderComment = function (node) { | ||
if (!this.includeComments) { | ||
return ""; | ||
} | ||
if (!this.includeComments) { return ""; } | ||
var isOutsideDocument = node.ownerDocument === node.parentNode, | ||
isBeforeDocument = null, | ||
isAfterDocument = null; | ||
var isOutsideDocument = (node.ownerDocument === node.parentNode), | ||
isBeforeDocument = null, | ||
isAfterDocument = null; | ||
if (isOutsideDocument) { | ||
var nextNode = node, | ||
previousNode = node; | ||
previousNode = node; | ||
@@ -196,3 +264,9 @@ while (nextNode !== null) { | ||
return (isAfterDocument ? "\n" : "") + "<!--" + utils.encodeSpecialCharactersInText(node.data) + "-->" + (isBeforeDocument ? "\n" : ""); | ||
return ( | ||
(isAfterDocument ? "\n" : "") + | ||
"<!--" + | ||
utils.encodeSpecialCharactersInText(node.data) + | ||
"-->" + | ||
(isBeforeDocument ? "\n" : "") | ||
); | ||
}; | ||
@@ -207,3 +281,3 @@ | ||
*/ | ||
ExclusiveCanonicalization.prototype.process = function(node, options) { | ||
ExclusiveCanonicalization.prototype.process = function (node, options) { | ||
options = options || {}; | ||
@@ -213,19 +287,23 @@ var inclusiveNamespacesPrefixList = options.inclusiveNamespacesPrefixList || []; | ||
var defaultNsForPrefix = options.defaultNsForPrefix || {}; | ||
if (!(inclusiveNamespacesPrefixList instanceof Array)) { inclusiveNamespacesPrefixList = inclusiveNamespacesPrefixList.split(' '); } | ||
if (!(inclusiveNamespacesPrefixList instanceof Array)) { | ||
inclusiveNamespacesPrefixList = inclusiveNamespacesPrefixList.split(" "); | ||
} | ||
var ancestorNamespaces = options.ancestorNamespaces || []; | ||
/** | ||
* If the inclusiveNamespacesPrefixList has not been explicitly provided then look it up in CanonicalizationMethod/InclusiveNamespaces | ||
*/ | ||
*/ | ||
if (inclusiveNamespacesPrefixList.length == 0) { | ||
var CanonicalizationMethod = utils.findChilds(node, "CanonicalizationMethod") | ||
var CanonicalizationMethod = utils.findChilds(node, "CanonicalizationMethod"); | ||
if (CanonicalizationMethod.length != 0) { | ||
var inclusiveNamespaces = utils.findChilds(CanonicalizationMethod[0], "InclusiveNamespaces") | ||
var inclusiveNamespaces = utils.findChilds(CanonicalizationMethod[0], "InclusiveNamespaces"); | ||
if (inclusiveNamespaces.length != 0) { | ||
inclusiveNamespacesPrefixList = inclusiveNamespaces[0].getAttribute('PrefixList').split(" "); | ||
inclusiveNamespacesPrefixList = inclusiveNamespaces[0] | ||
.getAttribute("PrefixList") | ||
.split(" "); | ||
} | ||
} | ||
} | ||
/** | ||
@@ -235,19 +313,32 @@ * If you have a PrefixList then use it and the ancestors to add the necessary namespaces | ||
if (inclusiveNamespacesPrefixList) { | ||
var prefixList = inclusiveNamespacesPrefixList instanceof Array ? inclusiveNamespacesPrefixList : inclusiveNamespacesPrefixList.split(' '); | ||
var prefixList = | ||
inclusiveNamespacesPrefixList instanceof Array | ||
? inclusiveNamespacesPrefixList | ||
: inclusiveNamespacesPrefixList.split(" "); | ||
prefixList.forEach(function (prefix) { | ||
if (ancestorNamespaces) { | ||
if (ancestorNamespaces) { | ||
ancestorNamespaces.forEach(function (ancestorNamespace) { | ||
if (prefix == ancestorNamespace.prefix) { | ||
node.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:' + prefix, ancestorNamespace.namespaceURI); | ||
node.setAttributeNS( | ||
"http://www.w3.org/2000/xmlns/", | ||
"xmlns:" + prefix, | ||
ancestorNamespace.namespaceURI | ||
); | ||
} | ||
}) | ||
}); | ||
} | ||
}) | ||
}); | ||
} | ||
var res = this.processInner(node, [], defaultNs, defaultNsForPrefix, inclusiveNamespacesPrefixList); | ||
var res = this.processInner( | ||
node, | ||
[], | ||
defaultNs, | ||
defaultNsForPrefix, | ||
inclusiveNamespacesPrefixList | ||
); | ||
return res; | ||
}; | ||
ExclusiveCanonicalization.prototype.getAlgorithmName = function() { | ||
ExclusiveCanonicalization.prototype.getAlgorithmName = function () { | ||
return "http://www.w3.org/2001/10/xml-exc-c14n#"; | ||
@@ -260,12 +351,14 @@ }; | ||
function ExclusiveCanonicalizationWithComments() { | ||
ExclusiveCanonicalization.call(this); | ||
this.includeComments = true; | ||
}; | ||
ExclusiveCanonicalization.call(this); | ||
this.includeComments = true; | ||
} | ||
ExclusiveCanonicalizationWithComments.prototype = Object.create(ExclusiveCanonicalization.prototype); | ||
ExclusiveCanonicalizationWithComments.prototype = Object.create( | ||
ExclusiveCanonicalization.prototype | ||
); | ||
ExclusiveCanonicalizationWithComments.prototype.constructor = ExclusiveCanonicalizationWithComments; | ||
ExclusiveCanonicalizationWithComments.prototype.getAlgorithmName = function() { | ||
ExclusiveCanonicalizationWithComments.prototype.getAlgorithmName = function () { | ||
return "http://www.w3.org/2001/10/xml-exc-c14n#WithComments"; | ||
}; |
@@ -1,32 +0,16 @@ | ||
var xpath = require('xpath') | ||
, Dom = require('@xmldom/xmldom').DOMParser | ||
, utils = require('./utils') | ||
, c14n = require('./c14n-canonicalization') | ||
, execC14n = require('./exclusive-canonicalization') | ||
, EnvelopedSignature = require('./enveloped-signature').EnvelopedSignature | ||
, crypto = require('crypto') | ||
, fs = require('fs') | ||
var xpath = require("xpath"), | ||
Dom = require("@xmldom/xmldom").DOMParser, | ||
utils = require("./utils"), | ||
c14n = require("./c14n-canonicalization"), | ||
execC14n = require("./exclusive-canonicalization"), | ||
EnvelopedSignature = require("./enveloped-signature").EnvelopedSignature, | ||
StringKeyInfo = require("./string-key-info"), | ||
FileKeyInfo = require("./file-key-info"), | ||
crypto = require("crypto"); | ||
exports.SignedXml = SignedXml | ||
exports.FileKeyInfo = FileKeyInfo | ||
exports.SignedXml = SignedXml; | ||
exports.StringKeyInfo = StringKeyInfo; | ||
exports.FileKeyInfo = FileKeyInfo; | ||
/** | ||
* A key info provider implementation | ||
* | ||
*/ | ||
function FileKeyInfo(file) { | ||
this.file = file | ||
this.getKeyInfo = function(key, prefix) { | ||
prefix = prefix || '' | ||
prefix = prefix ? prefix + ':' : prefix | ||
return "<" + prefix + "X509Data></" + prefix + "X509Data>" | ||
} | ||
this.getKey = function(keyInfo) { | ||
return fs.readFileSync(this.file) | ||
} | ||
} | ||
/** | ||
* Hash algorithm implementation | ||
@@ -36,44 +20,40 @@ * | ||
function SHA1() { | ||
this.getHash = function (xml) { | ||
var shasum = crypto.createHash("sha1"); | ||
shasum.update(xml, "utf8"); | ||
var res = shasum.digest("base64"); | ||
return res; | ||
}; | ||
this.getHash = function(xml) { | ||
var shasum = crypto.createHash('sha1') | ||
shasum.update(xml, 'utf8') | ||
var res = shasum.digest('base64') | ||
return res | ||
} | ||
this.getAlgorithmName = function() { | ||
return "http://www.w3.org/2000/09/xmldsig#sha1" | ||
} | ||
this.getAlgorithmName = function () { | ||
return "http://www.w3.org/2000/09/xmldsig#sha1"; | ||
}; | ||
} | ||
function SHA256() { | ||
this.getHash = function (xml) { | ||
var shasum = crypto.createHash("sha256"); | ||
shasum.update(xml, "utf8"); | ||
var res = shasum.digest("base64"); | ||
return res; | ||
}; | ||
this.getHash = function(xml) { | ||
var shasum = crypto.createHash('sha256') | ||
shasum.update(xml, 'utf8') | ||
var res = shasum.digest('base64') | ||
return res | ||
} | ||
this.getAlgorithmName = function() { | ||
return "http://www.w3.org/2001/04/xmlenc#sha256" | ||
} | ||
this.getAlgorithmName = function () { | ||
return "http://www.w3.org/2001/04/xmlenc#sha256"; | ||
}; | ||
} | ||
function SHA512() { | ||
this.getHash = function (xml) { | ||
var shasum = crypto.createHash("sha512"); | ||
shasum.update(xml, "utf8"); | ||
var res = shasum.digest("base64"); | ||
return res; | ||
}; | ||
this.getHash = function(xml) { | ||
var shasum = crypto.createHash('sha512') | ||
shasum.update(xml, 'utf8') | ||
var res = shasum.digest('base64') | ||
return res | ||
} | ||
this.getAlgorithmName = function() { | ||
return "http://www.w3.org/2001/04/xmlenc#sha512" | ||
} | ||
this.getAlgorithmName = function () { | ||
return "http://www.w3.org/2001/04/xmlenc#sha512"; | ||
}; | ||
} | ||
/** | ||
@@ -84,34 +64,31 @@ * Signature algorithm implementation | ||
function RSASHA1() { | ||
/** | ||
* Sign the given string using the given key | ||
* | ||
*/ | ||
this.getSignature = function(signedInfo, signingKey, callback) { | ||
var signer = crypto.createSign("RSA-SHA1") | ||
signer.update(signedInfo) | ||
var res = signer.sign(signingKey, 'base64') | ||
if (callback) callback(null, res) | ||
return res | ||
} | ||
* Sign the given string using the given key | ||
* | ||
*/ | ||
this.getSignature = function (signedInfo, signingKey, callback) { | ||
var signer = crypto.createSign("RSA-SHA1"); | ||
signer.update(signedInfo); | ||
var res = signer.sign(signingKey, "base64"); | ||
if (callback) callback(null, res); | ||
return res; | ||
}; | ||
/** | ||
* Verify the given signature of the given string using key | ||
* | ||
*/ | ||
this.verifySignature = function(str, key, signatureValue, callback) { | ||
var verifier = crypto.createVerify("RSA-SHA1") | ||
verifier.update(str) | ||
var res = verifier.verify(key, signatureValue, 'base64') | ||
if (callback) callback(null, res) | ||
return res | ||
} | ||
* Verify the given signature of the given string using key | ||
* | ||
*/ | ||
this.verifySignature = function (str, key, signatureValue, callback) { | ||
var verifier = crypto.createVerify("RSA-SHA1"); | ||
verifier.update(str); | ||
var res = verifier.verify(key, signatureValue, "base64"); | ||
if (callback) callback(null, res); | ||
return res; | ||
}; | ||
this.getAlgorithmName = function() { | ||
return "http://www.w3.org/2000/09/xmldsig#rsa-sha1" | ||
} | ||
this.getAlgorithmName = function () { | ||
return "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; | ||
}; | ||
} | ||
/** | ||
@@ -122,31 +99,29 @@ * Signature algorithm implementation | ||
function RSASHA256() { | ||
/** | ||
* Sign the given string using the given key | ||
* | ||
*/ | ||
this.getSignature = function(signedInfo, signingKey, callback) { | ||
var signer = crypto.createSign("RSA-SHA256") | ||
signer.update(signedInfo) | ||
var res = signer.sign(signingKey, 'base64') | ||
if (callback) callback(null, res) | ||
return res | ||
} | ||
* Sign the given string using the given key | ||
* | ||
*/ | ||
this.getSignature = function (signedInfo, signingKey, callback) { | ||
var signer = crypto.createSign("RSA-SHA256"); | ||
signer.update(signedInfo); | ||
var res = signer.sign(signingKey, "base64"); | ||
if (callback) callback(null, res); | ||
return res; | ||
}; | ||
/** | ||
* Verify the given signature of the given string using key | ||
* | ||
*/ | ||
this.verifySignature = function(str, key, signatureValue, callback) { | ||
var verifier = crypto.createVerify("RSA-SHA256") | ||
verifier.update(str) | ||
var res = verifier.verify(key, signatureValue, 'base64') | ||
if (callback) callback(null, res) | ||
return res | ||
} | ||
* Verify the given signature of the given string using key | ||
* | ||
*/ | ||
this.verifySignature = function (str, key, signatureValue, callback) { | ||
var verifier = crypto.createVerify("RSA-SHA256"); | ||
verifier.update(str); | ||
var res = verifier.verify(key, signatureValue, "base64"); | ||
if (callback) callback(null, res); | ||
return res; | ||
}; | ||
this.getAlgorithmName = function() { | ||
return "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" | ||
} | ||
this.getAlgorithmName = function () { | ||
return "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"; | ||
}; | ||
} | ||
@@ -159,54 +134,51 @@ | ||
function RSASHA512() { | ||
/** | ||
* Sign the given string using the given key | ||
* | ||
*/ | ||
this.getSignature = function(signedInfo, signingKey, callback) { | ||
var signer = crypto.createSign("RSA-SHA512") | ||
signer.update(signedInfo) | ||
var res = signer.sign(signingKey, 'base64') | ||
if (callback) callback(null, res) | ||
return res | ||
} | ||
* Sign the given string using the given key | ||
* | ||
*/ | ||
this.getSignature = function (signedInfo, signingKey, callback) { | ||
var signer = crypto.createSign("RSA-SHA512"); | ||
signer.update(signedInfo); | ||
var res = signer.sign(signingKey, "base64"); | ||
if (callback) callback(null, res); | ||
return res; | ||
}; | ||
/** | ||
* Verify the given signature of the given string using key | ||
* | ||
*/ | ||
this.verifySignature = function(str, key, signatureValue, callback) { | ||
var verifier = crypto.createVerify("RSA-SHA512") | ||
verifier.update(str) | ||
var res = verifier.verify(key, signatureValue, 'base64') | ||
if (callback) callback(null, res) | ||
return res | ||
} | ||
* Verify the given signature of the given string using key | ||
* | ||
*/ | ||
this.verifySignature = function (str, key, signatureValue, callback) { | ||
var verifier = crypto.createVerify("RSA-SHA512"); | ||
verifier.update(str); | ||
var res = verifier.verify(key, signatureValue, "base64"); | ||
if (callback) callback(null, res); | ||
return res; | ||
}; | ||
this.getAlgorithmName = function() { | ||
return "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512" | ||
} | ||
this.getAlgorithmName = function () { | ||
return "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"; | ||
}; | ||
} | ||
function HMACSHA1() { | ||
this.verifySignature = function(str, key, signatureValue) { | ||
var verifier = crypto.createHmac("SHA1", key); | ||
verifier.update(str); | ||
var res = verifier.digest('base64'); | ||
return res === signatureValue; | ||
}; | ||
this.verifySignature = function (str, key, signatureValue) { | ||
var verifier = crypto.createHmac("SHA1", key); | ||
verifier.update(str); | ||
var res = verifier.digest("base64"); | ||
return res === signatureValue; | ||
}; | ||
this.getAlgorithmName = function() { | ||
return "http://www.w3.org/2000/09/xmldsig#hmac-sha1"; | ||
}; | ||
this.getAlgorithmName = function () { | ||
return "http://www.w3.org/2000/09/xmldsig#hmac-sha1"; | ||
}; | ||
this.getSignature = function(signedInfo, signingKey) { | ||
var verifier = crypto.createHmac("SHA1", signingKey); | ||
verifier.update(signedInfo); | ||
var res = verifier.digest('base64'); | ||
return res; | ||
}; | ||
this.getSignature = function (signedInfo, signingKey) { | ||
var verifier = crypto.createHmac("SHA1", signingKey); | ||
verifier.update(signedInfo); | ||
var res = verifier.digest("base64"); | ||
return res; | ||
}; | ||
} | ||
/** | ||
@@ -221,16 +193,16 @@ * Extract ancestor namespaces in order to import it to root of document subset | ||
*/ | ||
function findAncestorNs(doc, docSubsetXpath, namespaceResolver){ | ||
function findAncestorNs(doc, docSubsetXpath, namespaceResolver) { | ||
var docSubset = xpath.selectWithResolver(docSubsetXpath, doc, namespaceResolver); | ||
if(!Array.isArray(docSubset) || docSubset.length < 1){ | ||
if (!Array.isArray(docSubset) || docSubset.length < 1) { | ||
return []; | ||
} | ||
// Remove duplicate on ancestor namespace | ||
var ancestorNs = collectAncestorNamespaces(docSubset[0]); | ||
var ancestorNsWithoutDuplicate = []; | ||
for(var i=0;i<ancestorNs.length;i++){ | ||
for (var i = 0; i < ancestorNs.length; i++) { | ||
var notOnTheList = true; | ||
for(var v in ancestorNsWithoutDuplicate){ | ||
if(ancestorNsWithoutDuplicate[v].prefix === ancestorNs[i].prefix){ | ||
for (var v in ancestorNsWithoutDuplicate) { | ||
if (ancestorNsWithoutDuplicate[v].prefix === ancestorNs[i].prefix) { | ||
notOnTheList = false; | ||
@@ -240,18 +212,18 @@ break; | ||
} | ||
if(notOnTheList){ | ||
if (notOnTheList) { | ||
ancestorNsWithoutDuplicate.push(ancestorNs[i]); | ||
} | ||
} | ||
// Remove namespaces which are already declared in the subset with the same prefix | ||
var returningNs = []; | ||
var subsetAttributes = docSubset[0].attributes; | ||
for(var j=0;j<ancestorNsWithoutDuplicate.length;j++){ | ||
for (var j = 0; j < ancestorNsWithoutDuplicate.length; j++) { | ||
var isUnique = true; | ||
for(var k=0;k<subsetAttributes.length;k++){ | ||
for (var k = 0; k < subsetAttributes.length; k++) { | ||
var nodeName = subsetAttributes[k].nodeName; | ||
if(nodeName.search(/^xmlns:/) === -1) continue; | ||
if (nodeName.search(/^xmlns:/) === -1) continue; | ||
var prefix = nodeName.replace(/^xmlns:/, ""); | ||
if(ancestorNsWithoutDuplicate[j].prefix === prefix){ | ||
if (ancestorNsWithoutDuplicate[j].prefix === prefix) { | ||
isUnique = false; | ||
@@ -261,33 +233,34 @@ break; | ||
} | ||
if(isUnique){ | ||
if (isUnique) { | ||
returningNs.push(ancestorNsWithoutDuplicate[j]); | ||
} | ||
} | ||
return returningNs; | ||
} | ||
function collectAncestorNamespaces(node, nsArray){ | ||
if(!nsArray){ | ||
function collectAncestorNamespaces(node, nsArray) { | ||
if (!nsArray) { | ||
nsArray = []; | ||
} | ||
var parent = node.parentNode; | ||
if(!parent){ | ||
if (!parent) { | ||
return nsArray; | ||
} | ||
if(parent.attributes && parent.attributes.length > 0){ | ||
for(var i=0;i<parent.attributes.length;i++){ | ||
if (parent.attributes && parent.attributes.length > 0) { | ||
for (var i = 0; i < parent.attributes.length; i++) { | ||
var attr = parent.attributes[i]; | ||
if(attr && attr.nodeName && attr.nodeName.search(/^xmlns:/) !== -1){ | ||
nsArray.push({prefix: attr.nodeName.replace(/^xmlns:/, ""), namespaceURI: attr.nodeValue}) | ||
if (attr && attr.nodeName && attr.nodeName.search(/^xmlns:/) !== -1) { | ||
nsArray.push({ | ||
prefix: attr.nodeName.replace(/^xmlns:/, ""), | ||
namespaceURI: attr.nodeValue, | ||
}); | ||
} | ||
} | ||
} | ||
return collectAncestorNamespaces(parent, nsArray); | ||
@@ -297,24 +270,28 @@ } | ||
/** | ||
* Xml signature implementation | ||
* | ||
* @param {string} idMode. Value of "wssecurity" will create/validate id's with the ws-security namespace | ||
* @param {object} options. Initial configurations | ||
*/ | ||
* Xml signature implementation | ||
* | ||
* @param {string} idMode. Value of "wssecurity" will create/validate id's with the ws-security namespace | ||
* @param {object} options. Initial configurations | ||
*/ | ||
function SignedXml(idMode, options) { | ||
this.options = options || {}; | ||
this.idMode = idMode | ||
this.references = [] | ||
this.id = 0 | ||
this.signingKey = null | ||
this.signatureAlgorithm = this.options.signatureAlgorithm || "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; | ||
this.keyInfoProvider = null | ||
this.canonicalizationAlgorithm = this.options.canonicalizationAlgorithm || "http://www.w3.org/2001/10/xml-exc-c14n#" | ||
this.signedXml = "" | ||
this.signatureXml = "" | ||
this.signatureNode = null | ||
this.signatureValue = "" | ||
this.originalXmlWithIds = "" | ||
this.validationErrors = [] | ||
this.keyInfo = null | ||
this.idAttributes = [ 'Id', 'ID', 'id' ]; | ||
this.idMode = idMode; | ||
this.references = []; | ||
this.id = 0; | ||
this.signingKey = null; | ||
this.signingCert = null; | ||
this.signatureAlgorithm = | ||
this.options.signatureAlgorithm || "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; | ||
this.keyInfoProvider = null; | ||
this.canonicalizationAlgorithm = | ||
this.options.canonicalizationAlgorithm || "http://www.w3.org/2001/10/xml-exc-c14n#"; | ||
this.inclusiveNamespacesPrefixList = this.options.inclusiveNamespacesPrefixList || ""; | ||
this.signedXml = ""; | ||
this.signatureXml = ""; | ||
this.signatureNode = null; | ||
this.signatureValue = ""; | ||
this.originalXmlWithIds = ""; | ||
this.validationErrors = []; | ||
this.keyInfo = null; | ||
this.idAttributes = ["Id", "ID", "id"]; | ||
if (this.options.idAttribute) this.idAttributes.splice(0, 0, this.options.idAttribute); | ||
@@ -325,22 +302,24 @@ this.implicitTransforms = this.options.implicitTransforms || []; | ||
SignedXml.CanonicalizationAlgorithms = { | ||
'http://www.w3.org/TR/2001/REC-xml-c14n-20010315': c14n.C14nCanonicalization, | ||
'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments': c14n.C14nCanonicalizationWithComments, | ||
'http://www.w3.org/2001/10/xml-exc-c14n#': execC14n.ExclusiveCanonicalization, | ||
'http://www.w3.org/2001/10/xml-exc-c14n#WithComments': execC14n.ExclusiveCanonicalizationWithComments, | ||
'http://www.w3.org/2000/09/xmldsig#enveloped-signature': EnvelopedSignature | ||
} | ||
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315": c14n.C14nCanonicalization, | ||
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments": | ||
c14n.C14nCanonicalizationWithComments, | ||
"http://www.w3.org/2001/10/xml-exc-c14n#": execC14n.ExclusiveCanonicalization, | ||
"http://www.w3.org/2001/10/xml-exc-c14n#WithComments": | ||
execC14n.ExclusiveCanonicalizationWithComments, | ||
"http://www.w3.org/2000/09/xmldsig#enveloped-signature": EnvelopedSignature, | ||
}; | ||
SignedXml.HashAlgorithms = { | ||
'http://www.w3.org/2000/09/xmldsig#sha1': SHA1, | ||
'http://www.w3.org/2001/04/xmlenc#sha256': SHA256, | ||
'http://www.w3.org/2001/04/xmlenc#sha512': SHA512 | ||
} | ||
"http://www.w3.org/2000/09/xmldsig#sha1": SHA1, | ||
"http://www.w3.org/2001/04/xmlenc#sha256": SHA256, | ||
"http://www.w3.org/2001/04/xmlenc#sha512": SHA512, | ||
}; | ||
SignedXml.SignatureAlgorithms = { | ||
'http://www.w3.org/2000/09/xmldsig#rsa-sha1': RSASHA1, | ||
'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256': RSASHA256, | ||
'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512': RSASHA512, | ||
"http://www.w3.org/2000/09/xmldsig#rsa-sha1": RSASHA1, | ||
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256": RSASHA256, | ||
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha512": RSASHA512, | ||
// Disabled by default due to key confusion concerns. | ||
// 'http://www.w3.org/2000/09/xmldsig#hmac-sha1': HMACSHA1 | ||
} | ||
}; | ||
@@ -354,8 +333,8 @@ /** | ||
SignedXml.SignatureAlgorithms = { | ||
'http://www.w3.org/2000/09/xmldsig#hmac-sha1': HMACSHA1 | ||
} | ||
} | ||
"http://www.w3.org/2000/09/xmldsig#hmac-sha1": HMACSHA1, | ||
}; | ||
}; | ||
SignedXml.defaultNsForPrefix = { | ||
ds: 'http://www.w3.org/2000/09/xmldsig#' | ||
ds: "http://www.w3.org/2000/09/xmldsig#", | ||
}; | ||
@@ -365,32 +344,32 @@ | ||
SignedXml.prototype.checkSignature = function(xml, callback) { | ||
if (callback != null && typeof callback !== 'function') { | ||
throw new Error("Last paramater must be a callback function") | ||
SignedXml.prototype.checkSignature = function (xml, callback) { | ||
if (callback != null && typeof callback !== "function") { | ||
throw new Error("Last parameter must be a callback function"); | ||
} | ||
this.validationErrors = [] | ||
this.signedXml = xml | ||
this.validationErrors = []; | ||
this.signedXml = xml; | ||
if (!this.keyInfoProvider) { | ||
var err = new Error("cannot validate signature since no key info resolver was provided") | ||
var err = new Error("cannot validate signature since no key info resolver was provided"); | ||
if (!callback) { | ||
throw err | ||
throw err; | ||
} else { | ||
callback(err) | ||
return | ||
callback(err); | ||
return; | ||
} | ||
} | ||
this.signingKey = this.keyInfoProvider.getKey(this.keyInfo) | ||
this.signingKey = this.keyInfoProvider.getKey(this.keyInfo); | ||
if (!this.signingKey) { | ||
var err = new Error("key info provider could not resolve key info " + this.keyInfo) | ||
var err2 = new Error("key info provider could not resolve key info " + this.keyInfo); | ||
if (!callback) { | ||
throw err | ||
throw err2; | ||
} else { | ||
callback(err) | ||
return | ||
callback(err2); | ||
return; | ||
} | ||
} | ||
var doc = new Dom().parseFromString(xml) | ||
var doc = new Dom().parseFromString(xml); | ||
@@ -401,4 +380,4 @@ if (!this.validateReferences(doc)) { | ||
} else { | ||
callback(new Error('Could not validate references')) | ||
return | ||
callback(new Error("Could not validate references")); | ||
return; | ||
} | ||
@@ -408,29 +387,32 @@ } | ||
if (!callback) { | ||
//Syncronous flow | ||
// Synchronous flow | ||
if (!this.validateSignatureValue(doc)) { | ||
return false | ||
return false; | ||
} | ||
return true | ||
return true; | ||
} else { | ||
//Asyncronous flow | ||
// Asynchronous flow | ||
this.validateSignatureValue(doc, function (err, isValidSignature) { | ||
if (err) { | ||
this.validationErrors.push("invalid signature: the signature value " + | ||
this.signatureValue + " is incorrect") | ||
callback(err) | ||
this.validationErrors.push( | ||
"invalid signature: the signature value " + this.signatureValue + " is incorrect" | ||
); | ||
callback(err); | ||
} else { | ||
callback(null, isValidSignature) | ||
callback(null, isValidSignature); | ||
} | ||
}) | ||
}); | ||
} | ||
} | ||
}; | ||
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"){ | ||
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( | ||
@@ -441,3 +423,3 @@ "When canonicalization method is non-exclusive, whole xml dom must be provided as an argument" | ||
} | ||
/** | ||
@@ -448,15 +430,15 @@ * Search for ancestor namespaces before canonicalization. | ||
ancestorNamespaces = findAncestorNs(doc, "//*[local-name()='SignedInfo']"); | ||
var c14nOptions = { | ||
ancestorNamespaces: ancestorNamespaces | ||
ancestorNamespaces: ancestorNamespaces, | ||
}; | ||
return this.getCanonXml([this.canonicalizationAlgorithm], signedInfo[0], c14nOptions) | ||
} | ||
return this.getCanonXml([this.canonicalizationAlgorithm], signedInfo[0], c14nOptions); | ||
}; | ||
SignedXml.prototype.getCanonReferenceXml = function(doc, ref, node) { | ||
SignedXml.prototype.getCanonReferenceXml = function (doc, ref, node) { | ||
/** | ||
* Search for ancestor namespaces before canonicalization. | ||
*/ | ||
if(Array.isArray(ref.transforms)){ | ||
ref.ancestorNamespaces = findAncestorNs(doc, ref.xpath, this.namespaceResolver) | ||
if (Array.isArray(ref.transforms)) { | ||
ref.ancestorNamespaces = findAncestorNs(doc, ref.xpath, this.namespaceResolver); | ||
} | ||
@@ -466,59 +448,58 @@ | ||
inclusiveNamespacesPrefixList: ref.inclusiveNamespacesPrefixList, | ||
ancestorNamespaces: ref.ancestorNamespaces | ||
} | ||
ancestorNamespaces: ref.ancestorNamespaces, | ||
}; | ||
return this.getCanonXml(ref.transforms, node, c14nOptions) | ||
} | ||
return this.getCanonXml(ref.transforms, node, c14nOptions); | ||
}; | ||
SignedXml.prototype.validateSignatureValue = function(doc, callback) { | ||
var signedInfoCanon = this.getCanonSignedInfoXml(doc) | ||
var signer = this.findSignatureAlgorithm(this.signatureAlgorithm) | ||
var res = signer.verifySignature(signedInfoCanon, this.signingKey, this.signatureValue, callback) | ||
if (!res && !callback) this.validationErrors.push("invalid signature: the signature value " + | ||
this.signatureValue + " is incorrect") | ||
return res | ||
} | ||
SignedXml.prototype.validateSignatureValue = function (doc, callback) { | ||
var signedInfoCanon = this.getCanonSignedInfoXml(doc); | ||
var signer = this.findSignatureAlgorithm(this.signatureAlgorithm); | ||
var res = signer.verifySignature(signedInfoCanon, this.signingKey, this.signatureValue, callback); | ||
if (!res && !callback) | ||
this.validationErrors.push( | ||
"invalid signature: the signature value " + this.signatureValue + " is incorrect" | ||
); | ||
return res; | ||
}; | ||
SignedXml.prototype.calculateSignatureValue = function(doc, callback) { | ||
var signedInfoCanon = this.getCanonSignedInfoXml(doc) | ||
var signer = this.findSignatureAlgorithm(this.signatureAlgorithm) | ||
this.signatureValue = signer.getSignature(signedInfoCanon, this.signingKey, callback) | ||
} | ||
SignedXml.prototype.calculateSignatureValue = function (doc, callback) { | ||
var signedInfoCanon = this.getCanonSignedInfoXml(doc); | ||
var signer = this.findSignatureAlgorithm(this.signatureAlgorithm); | ||
this.signatureValue = signer.getSignature(signedInfoCanon, this.signingKey, callback); | ||
}; | ||
SignedXml.prototype.findSignatureAlgorithm = function(name) { | ||
var algo = SignedXml.SignatureAlgorithms[name] | ||
if (algo) return new algo() | ||
SignedXml.prototype.findSignatureAlgorithm = function (name) { | ||
var algo = SignedXml.SignatureAlgorithms[name]; | ||
if (algo) return new algo(); | ||
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() | ||
SignedXml.prototype.findCanonicalizationAlgorithm = function (name) { | ||
var algo = SignedXml.CanonicalizationAlgorithms[name]; | ||
if (algo) return new algo(); | ||
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() | ||
SignedXml.prototype.findHashAlgorithm = function (name) { | ||
var algo = SignedXml.HashAlgorithms[name]; | ||
if (algo) return new algo(); | ||
else throw new Error("hash algorithm '" + name + "' is not supported"); | ||
} | ||
}; | ||
SignedXml.prototype.validateReferences = function(doc) { | ||
SignedXml.prototype.validateReferences = function (doc) { | ||
for (var r in this.references) { | ||
if (!this.references.hasOwnProperty(r)) continue; | ||
var ref = this.references[r] | ||
var ref = this.references[r]; | ||
var uri = ref.uri[0]=="#" ? ref.uri.substring(1) : ref.uri | ||
var uri = ref.uri[0] == "#" ? ref.uri.substring(1) : ref.uri; | ||
var elem = []; | ||
if (uri=="") { | ||
elem = xpath.select("//*", doc) | ||
} | ||
else if (uri.indexOf("'") != -1) { | ||
if (uri == "") { | ||
elem = xpath.select("//*", doc); | ||
} else if (uri.indexOf("'") != -1) { | ||
// xpath injection | ||
throw new Error("Cannot validate a uri with quotes inside it"); | ||
} | ||
else { | ||
} else { | ||
var elemXpath; | ||
@@ -528,4 +509,5 @@ var num_elements_for_id = 0; | ||
if (!this.idAttributes.hasOwnProperty(index)) continue; | ||
var tmp_elemXpath = "//*[@*[local-name(.)='" + this.idAttributes[index] + "']='" + uri + "']"; | ||
var tmp_elem = xpath.select(tmp_elemXpath, doc) | ||
var tmp_elemXpath = | ||
"//*[@*[local-name(.)='" + this.idAttributes[index] + "']='" + uri + "']"; | ||
var tmp_elem = xpath.select(tmp_elemXpath, doc); | ||
num_elements_for_id += tmp_elem.length; | ||
@@ -538,5 +520,7 @@ if (tmp_elem.length > 0) { | ||
if (num_elements_for_id > 1) { | ||
throw new Error('Cannot validate a document which contains multiple elements with the ' + | ||
'same value for the ID / Id / Id attributes, in order to prevent ' + | ||
'signature wrapping attack.'); | ||
throw new Error( | ||
"Cannot validate a document which contains multiple elements with the " + | ||
"same value for the ID / Id / Id attributes, in order to prevent " + | ||
"signature wrapping attack." | ||
); | ||
} | ||
@@ -547,23 +531,31 @@ | ||
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 | ||
if (elem.length == 0) { | ||
this.validationErrors.push( | ||
"invalid signature: the signature references an element with uri " + | ||
ref.uri + | ||
" but could not find such element in the xml" | ||
); | ||
return false; | ||
} | ||
var canonXml = this.getCanonReferenceXml(doc, ref, elem[0]) | ||
var hash = this.findHashAlgorithm(ref.digestAlgorithm) | ||
var digest = hash.getHash(canonXml) | ||
var canonXml = this.getCanonReferenceXml(doc, ref, elem[0]); | ||
var hash = this.findHashAlgorithm(ref.digestAlgorithm); | ||
var digest = hash.getHash(canonXml); | ||
if (!validateDigestValue(digest, ref.digestValue)) { | ||
this.validationErrors.push("invalid signature: for uri " + ref.uri + | ||
" calculated digest is " + digest + | ||
" but the xml to validate supplies digest " + ref.digestValue) | ||
this.validationErrors.push( | ||
"invalid signature: for uri " + | ||
ref.uri + | ||
" calculated digest is " + | ||
digest + | ||
" but the xml to validate supplies digest " + | ||
ref.digestValue | ||
); | ||
return false | ||
return false; | ||
} | ||
} | ||
return true | ||
} | ||
return true; | ||
}; | ||
@@ -576,11 +568,11 @@ function validateDigestValue(digest, expectedDigest) { | ||
if (+majorVersion >= 6) { | ||
buffer = Buffer.from(digest, 'base64'); | ||
expectedBuffer = Buffer.from(expectedDigest, 'base64'); | ||
buffer = Buffer.from(digest, "base64"); | ||
expectedBuffer = Buffer.from(expectedDigest, "base64"); | ||
} else { | ||
// Compatibility with Node < 5.10.0 | ||
buffer = new Buffer(digest, 'base64'); | ||
expectedBuffer = new Buffer(expectedDigest, 'base64'); | ||
buffer = new Buffer(digest, "base64"); | ||
expectedBuffer = new Buffer(expectedDigest, "base64"); | ||
} | ||
if (typeof buffer.equals === 'function') { | ||
if (typeof buffer.equals === "function") { | ||
return buffer.equals(expectedBuffer); | ||
@@ -603,4 +595,4 @@ } | ||
SignedXml.prototype.loadSignature = function(signatureNode) { | ||
if (typeof signatureNode === 'string') { | ||
SignedXml.prototype.loadSignature = function (signatureNode) { | ||
if (typeof signatureNode === "string") { | ||
this.signatureNode = signatureNode = new Dom().parseFromString(signatureNode); | ||
@@ -613,12 +605,21 @@ } else { | ||
var nodes = xpath.select(".//*[local-name(.)='CanonicalizationMethod']/@Algorithm", signatureNode) | ||
if (nodes.length==0) throw new Error("could not find CanonicalizationMethod/@Algorithm element") | ||
this.canonicalizationAlgorithm = nodes[0].value | ||
var nodes = xpath.select( | ||
".//*[local-name(.)='CanonicalizationMethod']/@Algorithm", | ||
signatureNode | ||
); | ||
if (nodes.length == 0) | ||
throw new Error("could not find CanonicalizationMethod/@Algorithm element"); | ||
this.canonicalizationAlgorithm = nodes[0].value; | ||
this.signatureAlgorithm = | ||
utils.findFirst(signatureNode, ".//*[local-name(.)='SignatureMethod']/@Algorithm").value | ||
this.signatureAlgorithm = utils.findFirst( | ||
signatureNode, | ||
".//*[local-name(.)='SignatureMethod']/@Algorithm" | ||
).value; | ||
this.references = [] | ||
var references = xpath.select(".//*[local-name(.)='SignedInfo']/*[local-name(.)='Reference']", signatureNode) | ||
if (references.length == 0) throw new Error("could not find any Reference elements") | ||
this.references = []; | ||
var references = xpath.select( | ||
".//*[local-name(.)='SignedInfo']/*[local-name(.)='Reference']", | ||
signatureNode | ||
); | ||
if (references.length == 0) throw new Error("could not find any Reference elements"); | ||
@@ -628,10 +629,11 @@ for (var i in references) { | ||
this.loadReference(references[i]) | ||
this.loadReference(references[i]); | ||
} | ||
this.signatureValue = | ||
utils.findFirst(signatureNode, ".//*[local-name(.)='SignatureValue']/text()").data.replace(/\r?\n/g, '') | ||
this.signatureValue = utils | ||
.findFirst(signatureNode, ".//*[local-name(.)='SignatureValue']/text()") | ||
.data.replace(/\r?\n/g, ""); | ||
this.keyInfo = xpath.select(".//*[local-name(.)='KeyInfo']", signatureNode) | ||
} | ||
this.keyInfo = xpath.select(".//*[local-name(.)='KeyInfo']", signatureNode); | ||
}; | ||
@@ -642,40 +644,43 @@ /** | ||
*/ | ||
SignedXml.prototype.loadReference = function(ref) { | ||
var nodes = utils.findChilds(ref, "DigestMethod") | ||
if (nodes.length==0) throw new Error("could not find DigestMethod in reference " + ref.toString()) | ||
var digestAlgoNode = nodes[0] | ||
SignedXml.prototype.loadReference = function (ref) { | ||
var nodes = utils.findChilds(ref, "DigestMethod"); | ||
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 new Error("could not find Algorithm attribute in node " + digestAlgoNode.toString()) | ||
var digestAlgo = attr.value | ||
var attr = utils.findAttr(digestAlgoNode, "Algorithm"); | ||
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 new Error("could not find DigestValue node in reference " + ref.toString()) | ||
if (nodes[0].childNodes.length==0 || !nodes[0].firstChild.data) | ||
{ | ||
throw new Error("could not find the value of DigestValue in " + nodes[0].toString()) | ||
nodes = utils.findChilds(ref, "DigestValue"); | ||
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 new Error("could not find the value of DigestValue in " + nodes[0].toString()); | ||
} | ||
var digestValue = nodes[0].firstChild.data | ||
var digestValue = nodes[0].firstChild.data; | ||
var transforms = [] | ||
var transforms = []; | ||
var inclusiveNamespacesPrefixList; | ||
nodes = utils.findChilds(ref, "Transforms") | ||
if (nodes.length!=0) { | ||
var transformsNode = nodes[0] | ||
var transformsAll = utils.findChilds(transformsNode, "Transform") | ||
nodes = utils.findChilds(ref, "Transforms"); | ||
if (nodes.length != 0) { | ||
var transformsNode = nodes[0]; | ||
var transformsAll = utils.findChilds(transformsNode, "Transform"); | ||
for (var t in transformsAll) { | ||
if (!transformsAll.hasOwnProperty(t)) continue; | ||
var trans = transformsAll[t] | ||
transforms.push(utils.findAttr(trans, "Algorithm").value) | ||
var trans = transformsAll[t]; | ||
transforms.push(utils.findAttr(trans, "Algorithm").value); | ||
} | ||
var inclusiveNamespaces = utils.findChilds(trans, "InclusiveNamespaces") | ||
var inclusiveNamespaces = utils.findChilds(trans, "InclusiveNamespaces"); | ||
if (inclusiveNamespaces.length > 0) { | ||
//Should really only be one prefix list, but maybe there's some circumstances where more than one to lets handle it | ||
for (var i = 0; i<inclusiveNamespaces.length; i++) { | ||
for (var i = 0; i < inclusiveNamespaces.length; i++) { | ||
if (inclusiveNamespacesPrefixList) { | ||
inclusiveNamespacesPrefixList = inclusiveNamespacesPrefixList + " " + inclusiveNamespaces[i].getAttribute('PrefixList'); | ||
inclusiveNamespacesPrefixList = | ||
inclusiveNamespacesPrefixList + " " + inclusiveNamespaces[i].getAttribute("PrefixList"); | ||
} else { | ||
inclusiveNamespacesPrefixList = inclusiveNamespaces[i].getAttribute('PrefixList'); | ||
inclusiveNamespacesPrefixList = inclusiveNamespaces[i].getAttribute("PrefixList"); | ||
} | ||
@@ -686,39 +691,59 @@ } | ||
var hasImplicitTransforms = (Array.isArray(this.implicitTransforms) && this.implicitTransforms.length > 0); | ||
if(hasImplicitTransforms){ | ||
this.implicitTransforms.forEach(function(t){ | ||
var hasImplicitTransforms = | ||
Array.isArray(this.implicitTransforms) && this.implicitTransforms.length > 0; | ||
if (hasImplicitTransforms) { | ||
this.implicitTransforms.forEach(function (t) { | ||
transforms.push(t); | ||
}); | ||
} | ||
/** | ||
* DigestMethods take an octet stream rather than a node set. If the output of the last transform is a node set, we | ||
* need to canonicalize the node set to an octet stream using non-exclusive canonicalization. If there are no | ||
* transforms, we need to canonicalize because URI dereferencing for a same-document reference will return a node-set. | ||
* See: | ||
* https://www.w3.org/TR/xmldsig-core1/#sec-DigestMethod | ||
* https://www.w3.org/TR/xmldsig-core1/#sec-ReferenceProcessingModel | ||
* https://www.w3.org/TR/xmldsig-core1/#sec-Same-Document | ||
*/ | ||
if (transforms.length === 0 || transforms[transforms.length - 1] === "http://www.w3.org/2000/09/xmldsig#enveloped-signature") { | ||
transforms.push("http://www.w3.org/TR/2001/REC-xml-c14n-20010315") | ||
/** | ||
* DigestMethods take an octet stream rather than a node set. If the output of the last transform is a node set, we | ||
* need to canonicalize the node set to an octet stream using non-exclusive canonicalization. If there are no | ||
* transforms, we need to canonicalize because URI dereferencing for a same-document reference will return a node-set. | ||
* See: | ||
* https://www.w3.org/TR/xmldsig-core1/#sec-DigestMethod | ||
* https://www.w3.org/TR/xmldsig-core1/#sec-ReferenceProcessingModel | ||
* https://www.w3.org/TR/xmldsig-core1/#sec-Same-Document | ||
*/ | ||
if ( | ||
transforms.length === 0 || | ||
transforms[transforms.length - 1] === "http://www.w3.org/2000/09/xmldsig#enveloped-signature" | ||
) { | ||
transforms.push("http://www.w3.org/TR/2001/REC-xml-c14n-20010315"); | ||
} | ||
this.addReference(null, transforms, digestAlgo, utils.findAttr(ref, "URI").value, digestValue, inclusiveNamespacesPrefixList, false) | ||
} | ||
this.addReference( | ||
null, | ||
transforms, | ||
digestAlgo, | ||
utils.findAttr(ref, "URI").value, | ||
digestValue, | ||
inclusiveNamespacesPrefixList, | ||
false | ||
); | ||
}; | ||
SignedXml.prototype.addReference = function(xpath, transforms, digestAlgorithm, uri, digestValue, inclusiveNamespacesPrefixList, isEmptyUri) { | ||
SignedXml.prototype.addReference = function ( | ||
xpath, | ||
transforms, | ||
digestAlgorithm, | ||
uri, | ||
digestValue, | ||
inclusiveNamespacesPrefixList, | ||
isEmptyUri | ||
) { | ||
this.references.push({ | ||
"xpath": xpath, | ||
"transforms": transforms ? transforms : ["http://www.w3.org/2001/10/xml-exc-c14n#"] , | ||
"digestAlgorithm": digestAlgorithm ? digestAlgorithm : "http://www.w3.org/2000/09/xmldsig#sha1", | ||
"uri": uri, | ||
"digestValue": digestValue, | ||
"inclusiveNamespacesPrefixList": inclusiveNamespacesPrefixList, | ||
"isEmptyUri": isEmptyUri | ||
xpath: xpath, | ||
transforms: transforms ? transforms : ["http://www.w3.org/2001/10/xml-exc-c14n#"], | ||
digestAlgorithm: digestAlgorithm ? digestAlgorithm : "http://www.w3.org/2000/09/xmldsig#sha1", | ||
uri: uri, | ||
digestValue: digestValue, | ||
inclusiveNamespacesPrefixList: inclusiveNamespacesPrefixList, | ||
isEmptyUri: isEmptyUri, | ||
}); | ||
} | ||
}; | ||
/** | ||
* Compute the signature of the given xml (usign the already defined settings) | ||
* Compute the signature of the given xml (using the already defined settings) | ||
* | ||
@@ -737,18 +762,18 @@ * Options: | ||
*/ | ||
SignedXml.prototype.computeSignature = function(xml, opts, callback) { | ||
if (typeof opts === 'function' && callback == null) { | ||
callback = opts | ||
SignedXml.prototype.computeSignature = function (xml, opts, callback) { | ||
if (typeof opts === "function" && callback == null) { | ||
callback = opts; | ||
} | ||
if (callback != null && typeof callback !== 'function') { | ||
throw new Error("Last paramater must be a callback function") | ||
if (callback != null && typeof callback !== "function") { | ||
throw new Error("Last parameter must be a callback function"); | ||
} | ||
var doc = new Dom().parseFromString(xml), | ||
xmlNsAttr = "xmlns", | ||
signatureAttrs = [], | ||
location, | ||
attrs, | ||
prefix, | ||
currentPrefix; | ||
xmlNsAttr = "xmlns", | ||
signatureAttrs = [], | ||
location, | ||
attrs, | ||
prefix, | ||
currentPrefix; | ||
@@ -764,6 +789,6 @@ var validActions = ["append", "prepend", "before", "after"]; | ||
this.namespaceResolver = { | ||
lookupNamespaceURI: function(prefix) { | ||
lookupNamespaceURI: function (prefix) { | ||
return existingPrefixes[prefix]; | ||
} | ||
} | ||
}, | ||
}; | ||
@@ -776,9 +801,13 @@ // defaults to the root node | ||
if (validActions.indexOf(location.action) === -1) { | ||
var err = new Error("location.action option has an invalid action: " + location.action + | ||
", must be any of the following values: " + validActions.join(", ")); | ||
var err = new Error( | ||
"location.action option has an invalid action: " + | ||
location.action + | ||
", must be any of the following values: " + | ||
validActions.join(", ") | ||
); | ||
if (!callback) { | ||
throw err; | ||
} else { | ||
callback(err, null) | ||
return | ||
callback(err, null); | ||
return; | ||
} | ||
@@ -795,5 +824,5 @@ } | ||
Object.keys(attrs).forEach(function(name) { | ||
Object.keys(attrs).forEach(function (name) { | ||
if (name !== "xmlns" && name !== xmlNsAttr) { | ||
signatureAttrs.push(name + "=\"" + attrs[name] + "\""); | ||
signatureAttrs.push(name + '="' + attrs[name] + '"'); | ||
} | ||
@@ -803,15 +832,15 @@ }); | ||
// add the xml namespace attribute | ||
signatureAttrs.push(xmlNsAttr + "=\"http://www.w3.org/2000/09/xmldsig#\""); | ||
signatureAttrs.push(xmlNsAttr + '="http://www.w3.org/2000/09/xmldsig#"'); | ||
var signatureXml = "<" + currentPrefix + "Signature " + signatureAttrs.join(" ") + ">" | ||
var signatureXml = "<" + currentPrefix + "Signature " + signatureAttrs.join(" ") + ">"; | ||
signatureXml += this.createSignedInfo(doc, prefix); | ||
signatureXml += this.getKeyInfo(prefix) | ||
signatureXml += "</" + currentPrefix + "Signature>" | ||
signatureXml += this.getKeyInfo(prefix); | ||
signatureXml += "</" + currentPrefix + "Signature>"; | ||
this.originalXmlWithIds = doc.toString() | ||
this.originalXmlWithIds = doc.toString(); | ||
var existingPrefixesString = "" | ||
Object.keys(existingPrefixes).forEach(function(key) { | ||
existingPrefixesString += "xmlns:" + key + '="' + existingPrefixes[key] + '" ' | ||
var existingPrefixesString = ""; | ||
Object.keys(existingPrefixes).forEach(function (key) { | ||
existingPrefixesString += "xmlns:" + key + '="' + existingPrefixes[key] + '" '; | ||
}); | ||
@@ -821,5 +850,5 @@ | ||
// This only works if the prefix and namespace match with those in te xml | ||
var dummySignatureWrapper = "<Dummy " + existingPrefixesString + ">" + signatureXml + "</Dummy>" | ||
var xml = new Dom().parseFromString(dummySignatureWrapper) | ||
var signatureDoc = xml.documentElement.firstChild; | ||
var dummySignatureWrapper = "<Dummy " + existingPrefixesString + ">" + signatureXml + "</Dummy>"; | ||
var nodeXml = new Dom().parseFromString(dummySignatureWrapper); | ||
var signatureDoc = nodeXml.documentElement.firstChild; | ||
@@ -829,8 +858,10 @@ var referenceNode = xpath.select(location.reference, doc); | ||
if (!referenceNode || referenceNode.length === 0) { | ||
var err = new Error("the following xpath cannot be used because it was not found: " + location.reference); | ||
var err2 = new Error( | ||
"the following xpath cannot be used because it was not found: " + location.reference | ||
); | ||
if (!callback) { | ||
throw err | ||
throw err2; | ||
} else { | ||
callback(err, null) | ||
return | ||
callback(err2, null); | ||
return; | ||
} | ||
@@ -851,11 +882,11 @@ } | ||
this.signatureNode = signatureDoc | ||
var signedInfoNode = utils.findChilds(this.signatureNode, "SignedInfo") | ||
this.signatureNode = signatureDoc; | ||
var signedInfoNode = utils.findChilds(this.signatureNode, "SignedInfo"); | ||
if (signedInfoNode.length == 0) { | ||
var err = new Error("could not find SignedInfo element in the message") | ||
var err3 = new Error("could not find SignedInfo element in the message"); | ||
if (!callback) { | ||
throw err | ||
throw err3; | ||
} else { | ||
callback(err) | ||
return | ||
callback(err3); | ||
return; | ||
} | ||
@@ -867,37 +898,43 @@ } | ||
//Synchronous flow | ||
this.calculateSignatureValue(doc) | ||
signatureDoc.insertBefore(this.createSignature(prefix), signedInfoNode.nextSibling) | ||
this.signatureXml = signatureDoc.toString() | ||
this.signedXml = doc.toString() | ||
this.calculateSignatureValue(doc); | ||
signatureDoc.insertBefore(this.createSignature(prefix), signedInfoNode.nextSibling); | ||
this.signatureXml = signatureDoc.toString(); | ||
this.signedXml = doc.toString(); | ||
} else { | ||
var self = this | ||
var self = this; | ||
//Asynchronous flow | ||
this.calculateSignatureValue(doc, function(err, signature) { | ||
this.calculateSignatureValue(doc, function (err, signature) { | ||
if (err) { | ||
callback(err) | ||
callback(err); | ||
} else { | ||
self.signatureValue = signature | ||
signatureDoc.insertBefore(self.createSignature(prefix), signedInfoNode.nextSibling) | ||
self.signatureXml = signatureDoc.toString() | ||
self.signedXml = doc.toString() | ||
callback(null, self) | ||
self.signatureValue = signature; | ||
signatureDoc.insertBefore(self.createSignature(prefix), signedInfoNode.nextSibling); | ||
self.signatureXml = signatureDoc.toString(); | ||
self.signedXml = doc.toString(); | ||
callback(null, self); | ||
} | ||
}) | ||
}); | ||
} | ||
} | ||
}; | ||
SignedXml.prototype.getKeyInfo = function(prefix) { | ||
var res = "" | ||
var currentPrefix | ||
SignedXml.prototype.getKeyInfo = function (prefix) { | ||
var res = ""; | ||
var currentPrefix; | ||
currentPrefix = prefix || '' | ||
currentPrefix = currentPrefix ? currentPrefix + ':' : currentPrefix | ||
currentPrefix = prefix || ""; | ||
currentPrefix = currentPrefix ? currentPrefix + ":" : currentPrefix; | ||
if (this.keyInfoProvider) { | ||
res += "<" + currentPrefix + "KeyInfo>" | ||
res += this.keyInfoProvider.getKeyInfo(this.signingKey, prefix) | ||
res += "</" + currentPrefix + "KeyInfo>" | ||
var keyInfoAttrs = ""; | ||
if (this.keyInfoProvider.attrs) { | ||
Object.keys(this.keyInfoProvider.attrs).forEach((name) => { | ||
keyInfoAttrs += " " + name + '="' + this.keyInfoProvider.attrs[name] + '"'; | ||
}); | ||
} | ||
res += "<" + currentPrefix + "KeyInfo" + keyInfoAttrs + ">"; | ||
res += this.keyInfoProvider.getKeyInfo(this.signingCert || this.signingKey, prefix); | ||
res += "</" + currentPrefix + "KeyInfo>"; | ||
} | ||
return res | ||
} | ||
return res; | ||
}; | ||
@@ -908,17 +945,18 @@ /** | ||
*/ | ||
SignedXml.prototype.createReferences = function(doc, prefix) { | ||
SignedXml.prototype.createReferences = function (doc, prefix) { | ||
var res = ""; | ||
var res = "" | ||
prefix = prefix || ""; | ||
prefix = prefix ? prefix + ":" : prefix; | ||
prefix = prefix || '' | ||
prefix = prefix ? prefix + ':' : prefix | ||
for (var n in this.references) { | ||
if (!this.references.hasOwnProperty(n)) continue; | ||
var ref = this.references[n] | ||
, nodes = xpath.selectWithResolver(ref.xpath, doc, this.namespaceResolver) | ||
var ref = this.references[n], | ||
nodes = xpath.selectWithResolver(ref.xpath, doc, this.namespaceResolver); | ||
if (nodes.length==0) { | ||
throw new Error('the following xpath cannot be signed because it was not found: ' + ref.xpath) | ||
if (nodes.length == 0) { | ||
throw new Error( | ||
"the following xpath cannot be signed because it was not found: " + ref.xpath | ||
); | ||
} | ||
@@ -929,34 +967,60 @@ | ||
var node = nodes[h] | ||
var node = nodes[h]; | ||
if (ref.isEmptyUri) { | ||
res += "<" + prefix + "Reference URI=\"\">" | ||
} | ||
else { | ||
res += "<" + prefix + 'Reference URI="">'; | ||
} else { | ||
var id = this.ensureHasId(node); | ||
ref.uri = id | ||
res += "<" + prefix + "Reference URI=\"#" + id + "\">" | ||
ref.uri = id; | ||
res += "<" + prefix + 'Reference URI="#' + id + '">'; | ||
} | ||
res += "<" + prefix + "Transforms>" | ||
res += "<" + prefix + "Transforms>"; | ||
for (var t in ref.transforms) { | ||
if (!ref.transforms.hasOwnProperty(t)) continue; | ||
var trans = ref.transforms[t] | ||
var transform = this.findCanonicalizationAlgorithm(trans) | ||
res += "<" + prefix + "Transform Algorithm=\"" + transform.getAlgorithmName() + "\" />" | ||
var trans = ref.transforms[t]; | ||
var transform = this.findCanonicalizationAlgorithm(trans); | ||
res += "<" + prefix + 'Transform Algorithm="' + transform.getAlgorithmName() + '"'; | ||
if (ref.inclusiveNamespacesPrefixList) { | ||
res += ">"; | ||
res += | ||
'<InclusiveNamespaces PrefixList="' + | ||
ref.inclusiveNamespacesPrefixList + | ||
'" xmlns="' + | ||
transform.getAlgorithmName() + | ||
'"/>'; | ||
res += "</" + prefix + "Transform>"; | ||
} else { | ||
res += " />"; | ||
} | ||
} | ||
var canonXml = this.getCanonReferenceXml(doc, ref, node) | ||
var canonXml = this.getCanonReferenceXml(doc, ref, node); | ||
var digestAlgorithm = this.findHashAlgorithm(ref.digestAlgorithm) | ||
res += "</" + prefix + "Transforms>"+ | ||
"<" + prefix + "DigestMethod Algorithm=\"" + digestAlgorithm.getAlgorithmName() + "\" />"+ | ||
"<" + prefix + "DigestValue>" + digestAlgorithm.getHash(canonXml) + "</" + prefix + "DigestValue>"+ | ||
"</" + prefix + "Reference>" | ||
var digestAlgorithm = this.findHashAlgorithm(ref.digestAlgorithm); | ||
res += | ||
"</" + | ||
prefix + | ||
"Transforms>" + | ||
"<" + | ||
prefix + | ||
'DigestMethod Algorithm="' + | ||
digestAlgorithm.getAlgorithmName() + | ||
'" />' + | ||
"<" + | ||
prefix + | ||
"DigestValue>" + | ||
digestAlgorithm.getHash(canonXml) + | ||
"</" + | ||
prefix + | ||
"DigestValue>" + | ||
"</" + | ||
prefix + | ||
"Reference>"; | ||
} | ||
} | ||
return res | ||
} | ||
return res; | ||
}; | ||
SignedXml.prototype.getCanonXml = function(transforms, node, options) { | ||
SignedXml.prototype.getCanonXml = function (transforms, node, options) { | ||
options = options || {}; | ||
@@ -966,3 +1030,3 @@ options.defaultNsForPrefix = options.defaultNsForPrefix || SignedXml.defaultNsForPrefix; | ||
var canonXml = node.cloneNode(true) // Deep clone | ||
var canonXml = node.cloneNode(true); // Deep clone | ||
@@ -972,7 +1036,7 @@ for (var t in transforms) { | ||
var transform = this.findCanonicalizationAlgorithm(transforms[t]) | ||
var transform = this.findCanonicalizationAlgorithm(transforms[t]); | ||
canonXml = transform.process(canonXml, options); | ||
//TODO: currently transform.process may return either Node or String value (enveloped transformation returns Node, exclusive-canonicalization returns String). | ||
//This eitehr needs to be more explicit in the API, or all should return the same. | ||
//exclusive-canonicalization returns String since it builds the Xml by hand. If it had used xmldom it would inccorectly minimize empty tags | ||
//This either needs to be more explicit in the API, or all should return the same. | ||
//exclusive-canonicalization returns String since it builds the Xml by hand. If it had used xmldom it would incorrectly minimize empty tags | ||
//to <x/> instead of <x></x> and also incorrectly handle some delicate line break issues. | ||
@@ -983,4 +1047,4 @@ //enveloped transformation returns Node since if it would return String consider this case: | ||
} | ||
return canonXml.toString() | ||
} | ||
return canonXml.toString(); | ||
}; | ||
@@ -991,11 +1055,12 @@ /** | ||
*/ | ||
SignedXml.prototype.ensureHasId = function(node) { | ||
var attr | ||
SignedXml.prototype.ensureHasId = function (node) { | ||
var attr; | ||
if (this.idMode=="wssecurity") { | ||
attr = utils.findAttr(node, | ||
if (this.idMode == "wssecurity") { | ||
attr = utils.findAttr( | ||
node, | ||
"Id", | ||
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd") | ||
} | ||
else { | ||
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" | ||
); | ||
} else { | ||
for (var index in this.idAttributes) { | ||
@@ -1009,21 +1074,24 @@ if (!this.idAttributes.hasOwnProperty(index)) continue; | ||
if (attr) return attr.value | ||
if (attr) return attr.value; | ||
//add the attribute | ||
var id = "_" + this.id++ | ||
var id = "_" + this.id++; | ||
if (this.idMode=="wssecurity") { | ||
node.setAttributeNS("http://www.w3.org/2000/xmlns/", | ||
if (this.idMode == "wssecurity") { | ||
node.setAttributeNS( | ||
"http://www.w3.org/2000/xmlns/", | ||
"xmlns:wsu", | ||
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd") | ||
node.setAttributeNS("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", | ||
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" | ||
); | ||
node.setAttributeNS( | ||
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", | ||
"wsu:Id", | ||
id) | ||
id | ||
); | ||
} else { | ||
node.setAttribute("Id", id); | ||
} | ||
else { | ||
node.setAttribute("Id", id) | ||
} | ||
return id | ||
} | ||
return id; | ||
}; | ||
@@ -1034,18 +1102,31 @@ /** | ||
*/ | ||
SignedXml.prototype.createSignedInfo = function(doc, prefix) { | ||
var transform = this.findCanonicalizationAlgorithm(this.canonicalizationAlgorithm) | ||
var algo = this.findSignatureAlgorithm(this.signatureAlgorithm) | ||
var currentPrefix | ||
SignedXml.prototype.createSignedInfo = function (doc, prefix) { | ||
var transform = this.findCanonicalizationAlgorithm(this.canonicalizationAlgorithm); | ||
var algo = this.findSignatureAlgorithm(this.signatureAlgorithm); | ||
var currentPrefix; | ||
currentPrefix = prefix || '' | ||
currentPrefix = currentPrefix ? currentPrefix + ':' : currentPrefix | ||
currentPrefix = prefix || ""; | ||
currentPrefix = currentPrefix ? currentPrefix + ":" : currentPrefix; | ||
var res = "<" + currentPrefix + "SignedInfo>" | ||
res += "<" + currentPrefix + "CanonicalizationMethod Algorithm=\"" + transform.getAlgorithmName() + "\" />" + | ||
"<" + currentPrefix + "SignatureMethod Algorithm=\"" + algo.getAlgorithmName() + "\" />" | ||
var res = "<" + currentPrefix + "SignedInfo>"; | ||
res += | ||
"<" + currentPrefix + 'CanonicalizationMethod Algorithm="' + transform.getAlgorithmName() + '"'; | ||
if (this.inclusiveNamespacesPrefixList) { | ||
res += ">"; | ||
res += | ||
'<InclusiveNamespaces PrefixList="' + | ||
this.inclusiveNamespacesPrefixList + | ||
'" xmlns="' + | ||
transform.getAlgorithmName() + | ||
'"/>'; | ||
res += "</" + currentPrefix + "CanonicalizationMethod>"; | ||
} else { | ||
res += " />"; | ||
} | ||
res += "<" + currentPrefix + 'SignatureMethod Algorithm="' + algo.getAlgorithmName() + '" />'; | ||
res += this.createReferences(doc, prefix) | ||
res += "</" + currentPrefix + "SignedInfo>" | ||
return res | ||
} | ||
res += this.createReferences(doc, prefix); | ||
res += "</" + currentPrefix + "SignedInfo>"; | ||
return res; | ||
}; | ||
@@ -1056,34 +1137,41 @@ /** | ||
*/ | ||
SignedXml.prototype.createSignature = function(prefix) { | ||
var xmlNsAttr = 'xmlns' | ||
SignedXml.prototype.createSignature = function (prefix) { | ||
var xmlNsAttr = "xmlns"; | ||
if (prefix) { | ||
xmlNsAttr += ':' + prefix; | ||
prefix += ':'; | ||
xmlNsAttr += ":" + prefix; | ||
prefix += ":"; | ||
} else { | ||
prefix = ''; | ||
prefix = ""; | ||
} | ||
var signatureValueXml = "<" + prefix + "SignatureValue>" + this.signatureValue + "</" + prefix + "SignatureValue>" | ||
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#\">" + | ||
signatureValueXml + | ||
"</" + prefix + "Signature>" | ||
var dummySignatureWrapper = | ||
"<" + | ||
prefix + | ||
"Signature " + | ||
xmlNsAttr + | ||
'="http://www.w3.org/2000/09/xmldsig#">' + | ||
signatureValueXml + | ||
"</" + | ||
prefix + | ||
"Signature>"; | ||
var doc = new Dom().parseFromString(dummySignatureWrapper) | ||
var doc = new Dom().parseFromString(dummySignatureWrapper); | ||
return doc.documentElement.firstChild; | ||
} | ||
}; | ||
SignedXml.prototype.getSignatureXml = function () { | ||
return this.signatureXml; | ||
}; | ||
SignedXml.prototype.getSignatureXml = function() { | ||
return this.signatureXml | ||
} | ||
SignedXml.prototype.getOriginalXmlWithIds = function () { | ||
return this.originalXmlWithIds; | ||
}; | ||
SignedXml.prototype.getOriginalXmlWithIds = function() { | ||
return this.originalXmlWithIds | ||
} | ||
SignedXml.prototype.getSignedXml = function() { | ||
return this.signedXml | ||
} | ||
SignedXml.prototype.getSignedXml = function () { | ||
return this.signedXml; | ||
}; |
109
lib/utils.js
@@ -1,18 +0,21 @@ | ||
var select = require('xpath').select | ||
var select = require("xpath").select; | ||
function findAttr(node, localName, namespace) { | ||
for (var i = 0; i<node.attributes.length; i++) { | ||
var attr = node.attributes[i] | ||
for (var i = 0; i < node.attributes.length; i++) { | ||
var attr = node.attributes[i]; | ||
if (attrEqualsExplicitly(attr, localName, namespace) || attrEqualsImplicitly(attr, localName, namespace, node)) { | ||
return attr | ||
} | ||
if ( | ||
attrEqualsExplicitly(attr, localName, namespace) || | ||
attrEqualsImplicitly(attr, localName, namespace, node) | ||
) { | ||
return attr; | ||
} | ||
} | ||
return null | ||
return null; | ||
} | ||
function findFirst(doc, xpath) { | ||
var nodes = select(xpath, doc) | ||
if (nodes.length==0) throw "could not find xpath " + xpath | ||
return nodes[0] | ||
function findFirst(doc, xpath) { | ||
var nodes = select(xpath, doc); | ||
if (nodes.length == 0) throw "could not find xpath " + xpath; | ||
return nodes[0]; | ||
} | ||
@@ -22,61 +25,61 @@ | ||
node = node.documentElement || node; | ||
var res = [] | ||
for (var i = 0; i<node.childNodes.length; i++) { | ||
var child = node.childNodes[i] | ||
if (child.localName==localName && (child.namespaceURI==namespace || !namespace)) { | ||
res.push(child) | ||
var res = []; | ||
for (var i = 0; i < node.childNodes.length; i++) { | ||
var child = node.childNodes[i]; | ||
if (child.localName == localName && (child.namespaceURI == namespace || !namespace)) { | ||
res.push(child); | ||
} | ||
} | ||
return res | ||
return res; | ||
} | ||
function attrEqualsExplicitly(attr, localName, namespace) { | ||
return attr.localName==localName && (attr.namespaceURI==namespace || !namespace) | ||
return attr.localName == localName && (attr.namespaceURI == namespace || !namespace); | ||
} | ||
function attrEqualsImplicitly(attr, localName, namespace, node) { | ||
return attr.localName==localName && ((!attr.namespaceURI && node.namespaceURI==namespace) || !namespace) | ||
return ( | ||
attr.localName == localName && | ||
((!attr.namespaceURI && node.namespaceURI == namespace) || !namespace) | ||
); | ||
} | ||
var xml_special_to_encoded_attribute = { | ||
'&': '&', | ||
'<': '<', | ||
'"': '"', | ||
'\r': '
', | ||
'\n': '
', | ||
'\t': '	' | ||
} | ||
"&": "&", | ||
"<": "<", | ||
'"': """, | ||
"\r": "
", | ||
"\n": "
", | ||
"\t": "	", | ||
}; | ||
var xml_special_to_encoded_text = { | ||
'&': '&', | ||
'<': '<', | ||
'>': '>', | ||
'\r': '
' | ||
} | ||
"&": "&", | ||
"<": "<", | ||
">": ">", | ||
"\r": "
", | ||
}; | ||
function encodeSpecialCharactersInAttribute(attributeValue){ | ||
return attributeValue | ||
.replace(/([&<"\r\n\t])/g, function(str, item){ | ||
// Special character normalization. See: | ||
// - https://www.w3.org/TR/xml-c14n#ProcessingModel (Attribute Nodes) | ||
// - https://www.w3.org/TR/xml-c14n#Example-Chars | ||
return xml_special_to_encoded_attribute[item] | ||
}) | ||
function encodeSpecialCharactersInAttribute(attributeValue) { | ||
return attributeValue.replace(/([&<"\r\n\t])/g, function (str, item) { | ||
// Special character normalization. See: | ||
// - https://www.w3.org/TR/xml-c14n#ProcessingModel (Attribute Nodes) | ||
// - https://www.w3.org/TR/xml-c14n#Example-Chars | ||
return xml_special_to_encoded_attribute[item]; | ||
}); | ||
} | ||
function encodeSpecialCharactersInText(text){ | ||
return text | ||
.replace(/([&<>\r])/g, function(str, item){ | ||
// Special character normalization. See: | ||
// - https://www.w3.org/TR/xml-c14n#ProcessingModel (Text Nodes) | ||
// - https://www.w3.org/TR/xml-c14n#Example-Chars | ||
return xml_special_to_encoded_text[item] | ||
}) | ||
function encodeSpecialCharactersInText(text) { | ||
return text.replace(/([&<>\r])/g, function (str, item) { | ||
// Special character normalization. See: | ||
// - https://www.w3.org/TR/xml-c14n#ProcessingModel (Text Nodes) | ||
// - https://www.w3.org/TR/xml-c14n#Example-Chars | ||
return xml_special_to_encoded_text[item]; | ||
}); | ||
} | ||
exports.findAttr = findAttr | ||
exports.findChilds = findChilds | ||
exports.encodeSpecialCharactersInAttribute = encodeSpecialCharactersInAttribute | ||
exports.encodeSpecialCharactersInText = encodeSpecialCharactersInText | ||
exports.findFirst = findFirst | ||
exports.findAttr = findAttr; | ||
exports.findChilds = findChilds; | ||
exports.encodeSpecialCharactersInAttribute = encodeSpecialCharactersInAttribute; | ||
exports.encodeSpecialCharactersInText = encodeSpecialCharactersInText; | ||
exports.findFirst = findFirst; |
{ | ||
"name": "xml-crypto", | ||
"version": "3.0.1", | ||
"version": "3.1.0", | ||
"private": false, | ||
"description": "Xml digital signature and encryption library for Node.js", | ||
"engines": { | ||
"node": ">=0.4.0" | ||
"keywords": [ | ||
"xml", | ||
"digital signature", | ||
"xml encryption", | ||
"x.509 certificate" | ||
], | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/node-saml/xml-crypto.git" | ||
}, | ||
"license": "MIT", | ||
"author": "Yaron Naveh <yaronn01@gmail.com> (http://webservices20.blogspot.com/)", | ||
"contributors": [ | ||
"LoneRifle <LoneRifle@users.noreply.github.com>" | ||
"LoneRifle <LoneRifle@users.noreply.github.com>", | ||
"Chris Barth <chrisjbarth@hotmail.com>" | ||
], | ||
"dependencies": { | ||
"@xmldom/xmldom": "^0.8.5", | ||
"xpath": "0.0.32" | ||
}, | ||
"devDependencies": { | ||
"ejs": "3.1.7", | ||
"nodeunit": "^0.11.3" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/yaronn/xml-crypto.git" | ||
}, | ||
"main": "./index.js", | ||
"types": "./index.d.ts", | ||
"directories": { | ||
"lib": "./lib" | ||
}, | ||
"keywords": [ | ||
"xml", | ||
"digital signature", | ||
"xml encryption", | ||
"x.509 certificate" | ||
], | ||
"license": "MIT", | ||
"scripts": { | ||
"changelog": "gren changelog --override --generate --head master", | ||
"lint": "eslint --ext .js \"**/*.js\" --cache && npm run prettier-check", | ||
"lint:fix": "eslint --ext .js --fix \"**/*.js\" && npm run prettier-format", | ||
"prettier-check": "prettier --config .prettierrc.json --check .", | ||
"prettier-format": "prettier --config .prettierrc.json --write .", | ||
"prerelease": "git clean -xfd && npm ci && npm test", | ||
"release": "release-it", | ||
"test": "nodeunit ./test/canonicalization-unit-tests.js ./test/c14nWithComments-unit-tests.js ./test/signature-unit-tests.js ./test/saml-response-test.js ./test/signature-integration-tests.js ./test/document-test.js ./test/wsfed-metadata-test.js ./test/hmac-tests.js ./test/c14n-non-exclusive-unit-test.js" | ||
}, | ||
"dependencies": { | ||
"@xmldom/xmldom": "0.8.7", | ||
"xpath": "0.0.32" | ||
}, | ||
"devDependencies": { | ||
"@cjbarth/github-release-notes": "^4.0.0", | ||
"@prettier/plugin-xml": "^2.2.0", | ||
"ejs": "3.1.9", | ||
"eslint": "^8.42.0", | ||
"eslint-config-prettier": "^8.8.0", | ||
"nodeunit": "^0.11.3", | ||
"prettier": "^2.8.8", | ||
"prettier-plugin-packagejson": "^2.4.3", | ||
"release-it": "^15.11.0" | ||
}, | ||
"engines": { | ||
"node": ">=4.0.0" | ||
} | ||
} |
522
README.md
@@ -1,4 +0,4 @@ | ||
## xml-crypto | ||
# xml-crypto | ||
![Build](https://github.com/yaronn/xml-crypto/actions/workflows/ci.yml/badge.svg) | ||
![Build](https://github.com/node-saml/xml-crypto/actions/workflows/ci.yml/badge.svg) | ||
[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/from-referrer/) | ||
@@ -11,5 +11,8 @@ | ||
## Install | ||
Install with [npm](http://github.com/isaacs/npm): | ||
npm install xml-crypto | ||
```shell | ||
npm install xml-crypto | ||
``` | ||
@@ -22,27 +25,30 @@ A pre requisite it to have [openssl](http://www.openssl.org/) installed and its /bin to be on the system path. I used version 1.0.1c but it should work on older versions too. | ||
* Canonicalization http://www.w3.org/TR/2001/REC-xml-c14n-20010315 | ||
* Canonicalization with comments http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments | ||
* Exclusive Canonicalization http://www.w3.org/2001/10/xml-exc-c14n# | ||
* Exclusive Canonicalization with comments http://www.w3.org/2001/10/xml-exc-c14n#WithComments | ||
* Enveloped Signature transform http://www.w3.org/2000/09/xmldsig#enveloped-signature | ||
- Canonicalization http://www.w3.org/TR/2001/REC-xml-c14n-20010315 | ||
- Canonicalization with comments http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments | ||
- Exclusive Canonicalization http://www.w3.org/2001/10/xml-exc-c14n# | ||
- Exclusive Canonicalization with comments http://www.w3.org/2001/10/xml-exc-c14n#WithComments | ||
- Enveloped Signature transform http://www.w3.org/2000/09/xmldsig#enveloped-signature | ||
### Hashing Algorithms | ||
* SHA1 digests http://www.w3.org/2000/09/xmldsig#sha1 | ||
* SHA256 digests http://www.w3.org/2001/04/xmlenc#sha256 | ||
* SHA512 digests http://www.w3.org/2001/04/xmlenc#sha512 | ||
- SHA1 digests http://www.w3.org/2000/09/xmldsig#sha1 | ||
- SHA256 digests http://www.w3.org/2001/04/xmlenc#sha256 | ||
- SHA512 digests http://www.w3.org/2001/04/xmlenc#sha512 | ||
### Signature Algorithms | ||
* RSA-SHA1 http://www.w3.org/2000/09/xmldsig#rsa-sha1 | ||
* RSA-SHA256 http://www.w3.org/2001/04/xmldsig-more#rsa-sha256 | ||
* RSA-SHA512 http://www.w3.org/2001/04/xmldsig-more#rsa-sha512 | ||
- RSA-SHA1 http://www.w3.org/2000/09/xmldsig#rsa-sha1 | ||
- RSA-SHA256 http://www.w3.org/2001/04/xmldsig-more#rsa-sha256 | ||
- RSA-SHA512 http://www.w3.org/2001/04/xmldsig-more#rsa-sha512 | ||
HMAC-SHA1 is also available but it is disabled by default | ||
* HMAC-SHA1 http://www.w3.org/2000/09/xmldsig#hmac-sha1 | ||
- HMAC-SHA1 http://www.w3.org/2000/09/xmldsig#hmac-sha1 | ||
to enable HMAC-SHA1, do: | ||
```javascript | ||
require( 'xml-crypto' ).SignedXml.enableHMAC(); | ||
require("xml-crypto").SignedXml.enableHMAC(); | ||
``` | ||
This will enable HMAC and disable digital signature algorithms. Due to key | ||
@@ -54,11 +60,10 @@ confusion issues, it is risky to have both HMAC-based and public key digital | ||
*Canonicalization/Transformation Algorithm:* Exclusive Canonicalization http://www.w3.org/2001/10/xml-exc-c14n# | ||
_Canonicalization/Transformation Algorithm:_ Exclusive Canonicalization http://www.w3.org/2001/10/xml-exc-c14n# | ||
*Hashing Algorithm:* SHA1 digest http://www.w3.org/2000/09/xmldsig#sha1 | ||
_Hashing Algorithm:_ SHA1 digest http://www.w3.org/2000/09/xmldsig#sha1 | ||
*Signature Algorithm:* RSA-SHA1 http://www.w3.org/2000/09/xmldsig#rsa-sha1 | ||
_Signature Algorithm:_ RSA-SHA1 http://www.w3.org/2000/09/xmldsig#rsa-sha1 | ||
[You are able to extend xml-crypto with custom algorithms.](#customizing-algorithms) | ||
## Signing Xml documents | ||
@@ -75,45 +80,39 @@ | ||
`````javascript | ||
var SignedXml = require('xml-crypto').SignedXml | ||
, fs = require('fs') | ||
```javascript | ||
var SignedXml = require("xml-crypto").SignedXml, | ||
fs = require("fs"); | ||
var xml = "<library>" + | ||
"<book>" + | ||
"<name>Harry Potter</name>" + | ||
"</book>" + | ||
"</library>" | ||
var xml = "<library>" + "<book>" + "<name>Harry Potter</name>" + "</book>" + "</library>"; | ||
var sig = new SignedXml() | ||
sig.addReference("//*[local-name(.)='book']") | ||
sig.signingKey = fs.readFileSync("client.pem") | ||
sig.computeSignature(xml) | ||
fs.writeFileSync("signed.xml", sig.getSignedXml()) | ||
var sig = new SignedXml(); | ||
sig.addReference("//*[local-name(.)='book']"); | ||
sig.signingKey = fs.readFileSync("client.pem"); | ||
sig.computeSignature(xml); | ||
fs.writeFileSync("signed.xml", sig.getSignedXml()); | ||
``` | ||
````` | ||
The result will be: | ||
```xml | ||
<library> | ||
<book Id="_0"> | ||
<name>Harry Potter</name> | ||
</book> | ||
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#"> | ||
<SignedInfo> | ||
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /> | ||
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" /> | ||
<Reference URI="#_0"> | ||
<Transforms> | ||
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /> | ||
</Transforms> | ||
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /> | ||
<DigestValue>cdiS43aFDQMnb3X8yaIUej3+z9Q=</DigestValue> | ||
</Reference> | ||
</SignedInfo> | ||
<SignatureValue>vhWzpQyIYuncHUZV9W...[long base64 removed]...</SignatureValue> | ||
</Signature> | ||
</library> | ||
``` | ||
`````xml | ||
<library> | ||
<book Id="_0"> | ||
<name>Harry Potter</name> | ||
</book> | ||
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#"> | ||
<SignedInfo> | ||
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /> | ||
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" /> | ||
<Reference URI="#_0"> | ||
<Transforms> | ||
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" /> | ||
</Transforms> | ||
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /> | ||
<DigestValue>cdiS43aFDQMnb3X8yaIUej3+z9Q=</DigestValue> | ||
</Reference> | ||
</SignedInfo> | ||
<SignatureValue>vhWzpQyIYuncHUZV9W...[long base64 removed]...</SignatureValue> | ||
</Signature> | ||
</library> | ||
````` | ||
Note: | ||
@@ -131,23 +130,28 @@ | ||
npm install xmldom | ||
```shell | ||
npm install xmldom | ||
``` | ||
Example: | ||
`````javascript | ||
var select = require('xml-crypto').xpath | ||
, dom = require('@xmldom/xmldom').DOMParser | ||
, SignedXml = require('xml-crypto').SignedXml | ||
, FileKeyInfo = require('xml-crypto').FileKeyInfo | ||
, fs = require('fs') | ||
```javascript | ||
var select = require("xml-crypto").xpath, | ||
dom = require("@xmldom/xmldom").DOMParser, | ||
SignedXml = require("xml-crypto").SignedXml, | ||
FileKeyInfo = require("xml-crypto").FileKeyInfo, | ||
fs = require("fs"); | ||
var xml = fs.readFileSync("signed.xml").toString() | ||
var doc = new dom().parseFromString(xml) | ||
var xml = fs.readFileSync("signed.xml").toString(); | ||
var doc = new dom().parseFromString(xml); | ||
var signature = select(doc, "//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0] | ||
var sig = new SignedXml() | ||
sig.keyInfoProvider = new FileKeyInfo("client_public.pem") | ||
sig.loadSignature(signature) | ||
var res = sig.checkSignature(xml) | ||
if (!res) console.log(sig.validationErrors) | ||
````` | ||
var signature = select( | ||
doc, | ||
"//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']" | ||
)[0]; | ||
var sig = new SignedXml(); | ||
sig.keyInfoProvider = new FileKeyInfo("client_public.pem"); | ||
sig.loadSignature(signature); | ||
var res = sig.checkSignature(xml); | ||
if (!res) console.log(sig.validationErrors); | ||
``` | ||
@@ -157,10 +161,11 @@ if the verification process fails `sig.validationErrors` will have the errors. | ||
In order to protect from some attacks we must check the content we want to use is the one that has been signed: | ||
`````javascript | ||
var elem = select(doc, "/xpath_to_interesting_element"); | ||
var uri = sig.references[0].uri; // might not be 0 - depending on the document you verify | ||
var id = (uri[0] === '#') ? uri.substring(1) : uri; | ||
if (elem.getAttribute('ID') != id && elem.getAttribute('Id') != id && elem.getAttribute('id') != id) | ||
throw new Error('the interesting element was not the one verified by the signature') | ||
````` | ||
```javascript | ||
var elem = select(doc, "/xpath_to_interesting_element"); | ||
var uri = sig.references[0].uri; // might not be 0 - depending on the document you verify | ||
var id = uri[0] === "#" ? uri.substring(1) : uri; | ||
if (elem.getAttribute("ID") != id && elem.getAttribute("Id") != id && elem.getAttribute("id") != id) | ||
throw new Error("the interesting element was not the one verified by the signature"); | ||
``` | ||
Note: | ||
@@ -170,4 +175,4 @@ | ||
### Caring for Implicit transform | ||
### Caring for Implicit transform | ||
If you fail to verify signed XML, then one possible cause is that there are some hidden implicit transforms(#). | ||
@@ -182,7 +187,7 @@ (#) Normalizing XML document to be verified. i.e. remove extra space within a tag, sorting attributes, importing namespace declared in ancestor nodes, etc. | ||
```javascript | ||
var option = {implicitTransforms: ["http://www.w3.org/TR/2001/REC-xml-c14n-20010315"]} | ||
var sig = new SignedXml(null, option) | ||
sig.keyInfoProvider = new FileKeyInfo("client_public.pem") | ||
sig.loadSignature(signature) | ||
var res = sig.checkSignature(xml) | ||
var option = { implicitTransforms: ["http://www.w3.org/TR/2001/REC-xml-c14n-20010315"] }; | ||
var sig = new SignedXml(null, option); | ||
sig.keyInfoProvider = new FileKeyInfo("client_public.pem"); | ||
sig.loadSignature(signature); | ||
var res = sig.checkSignature(xml); | ||
``` | ||
@@ -210,3 +215,3 @@ | ||
*API* | ||
#### API | ||
@@ -218,12 +223,12 @@ A `SignedXml` object provides the following methods: | ||
- `addReference(xpath, [transforms], [digestAlgorithm])` - adds a reference to a xml element where: | ||
- `xpath` - a string containing a XPath expression referencing a xml element | ||
- `transforms` - an array of [transform algorithms](#canonicalization-and-transformation-algorithms), the referenced element will be transformed for each value in the array | ||
- `digestAlgorithm` - one of the supported [hashing algorithms](#hashing-algorithms) | ||
- `xpath` - a string containing a XPath expression referencing a xml element | ||
- `transforms` - an array of [transform algorithms](#canonicalization-and-transformation-algorithms), the referenced element will be transformed for each value in the array | ||
- `digestAlgorithm` - one of the supported [hashing algorithms](#hashing-algorithms) | ||
- `computeSignature(xml, [options])` - compute the signature of the given xml where: | ||
- `xml` - a string containing a xml document | ||
- `options` - an object with the following properties: | ||
- `prefix` - adds this value as a prefix for the generated signature tags | ||
- `attrs` - a hash of attributes and values `attrName: value` to add to the signature root node | ||
- `location` - customize the location of the signature, pass an object with a `reference` key which should contain a XPath expression to a reference node, an `action` key which should contain one of the following values: `append`, `prepend`, `before`, `after` | ||
- `existingPrefixes` - A hash of prefixes and namespaces `prefix: namespace` that shouldn't be in the signature because they already exist in the xml | ||
- `xml` - a string containing a xml document | ||
- `options` - an object with the following properties: | ||
- `prefix` - adds this value as a prefix for the generated signature tags | ||
- `attrs` - a hash of attributes and values `attrName: value` to add to the signature root node | ||
- `location` - customize the location of the signature, pass an object with a `reference` key which should contain a XPath expression to a reference node, an `action` key which should contain one of the following values: `append`, `prepend`, `before`, `after` | ||
- `existingPrefixes` - A hash of prefixes and namespaces `prefix: namespace` that shouldn't be in the signature because they already exist in the xml | ||
- `getSignedXml()` - returns the original xml document with the signature in it, **must be called only after `computeSignature`** | ||
@@ -236,7 +241,6 @@ - `getSignatureXml()` - returns just the signature part, **must be called only after `computeSignature`** | ||
- `loadSignature(signatureXml)` - loads the signature where: | ||
- `signatureXml` - a string or node object (like an [xml-dom](https://github.com/jindw/xmldom) node) containing the xml representation of the signature | ||
- `signatureXml` - a string or node object (like an [xml-dom](https://github.com/jindw/xmldom) node) containing the xml representation of the signature | ||
- `checkSignature(xml)` - validates the given xml document and returns true if the validation was successful, `sig.validationErrors` will have the validation errors if any, where: | ||
- `xml` - a string containing a xml document | ||
- `xml` - a string containing a xml document | ||
### FileKeyInfo | ||
@@ -250,4 +254,4 @@ | ||
## Customizing Algorithms | ||
## Customizing Algorithms | ||
The following sample shows how to sign a message using custom algorithms. | ||
@@ -257,8 +261,7 @@ | ||
`````javascript | ||
var SignedXml = require('xml-crypto').SignedXml | ||
, fs = require('fs') | ||
````` | ||
```javascript | ||
var SignedXml = require("xml-crypto").SignedXml, | ||
fs = require("fs"); | ||
``` | ||
Now define the extension point you want to implement. You can choose one or more. | ||
@@ -268,123 +271,116 @@ | ||
Implement it if you want to create a signature with a KeyInfo section, or you want to read your key in a different way then the default file read option. | ||
`````javascript | ||
/**/ | ||
function MyKeyInfo() { | ||
this.getKeyInfo = function(key, prefix) { | ||
prefix = prefix || '' | ||
prefix = prefix ? prefix + ':' : prefix | ||
return "<" + prefix + "X509Data></" + prefix + "X509Data>" | ||
} | ||
this.getKey = function(keyInfo) { | ||
//you can use the keyInfo parameter to extract the key in any way you want | ||
return fs.readFileSync("key.pem") | ||
} | ||
} | ||
````` | ||
```javascript | ||
function MyKeyInfo() { | ||
this.getKeyInfo = function (key, prefix) { | ||
prefix = prefix || ""; | ||
prefix = prefix ? prefix + ":" : prefix; | ||
return "<" + prefix + "X509Data></" + prefix + "X509Data>"; | ||
}; | ||
this.getKey = function (keyInfo) { | ||
//you can use the keyInfo parameter to extract the key in any way you want | ||
return fs.readFileSync("key.pem"); | ||
}; | ||
} | ||
``` | ||
A custom hash algorithm is used to calculate digests. Implement it if you want a hash other than the default SHA1. | ||
`````javascript | ||
function MyDigest() { | ||
```javascript | ||
function MyDigest() { | ||
this.getHash = function (xml) { | ||
return "the base64 hash representation of the given xml string"; | ||
}; | ||
this.getAlgorithmName = function () { | ||
return "http://myDigestAlgorithm"; | ||
}; | ||
} | ||
``` | ||
this.getHash = function(xml) { | ||
return "the base64 hash representation of the given xml string" | ||
} | ||
this.getAlgorithmName = function() { | ||
return "http://myDigestAlgorithm" | ||
} | ||
} | ||
````` | ||
A custom signing algorithm. The default is RSA-SHA1 | ||
`````javascript | ||
function MySignatureAlgorithm() { | ||
/*sign the given SignedInfo using the key. return base64 signature value*/ | ||
this.getSignature = function(signedInfo, signingKey) { | ||
return "signature of signedInfo as base64..." | ||
} | ||
```javascript | ||
function MySignatureAlgorithm() { | ||
/*sign the given SignedInfo using the key. return base64 signature value*/ | ||
this.getSignature = function (signedInfo, signingKey) { | ||
return "signature of signedInfo as base64..."; | ||
}; | ||
this.getAlgorithmName = function() { | ||
return "http://mySigningAlgorithm" | ||
} | ||
this.getAlgorithmName = function () { | ||
return "http://mySigningAlgorithm"; | ||
}; | ||
} | ||
``` | ||
} | ||
````` | ||
Custom transformation algorithm. The default is exclusive canonicalization. | ||
`````javascript | ||
function MyTransformation() { | ||
```javascript | ||
function MyTransformation() { | ||
/*given a node (from the xmldom module) return its canonical representation (as string)*/ | ||
this.process = function (node) { | ||
//you should apply your transformation before returning | ||
return node.toString(); | ||
}; | ||
/*given a node (from the xmldom module) return its canonical representation (as string)*/ | ||
this.process = function(node) { | ||
//you should apply your transformation before returning | ||
return node.toString() | ||
} | ||
this.getAlgorithmName = function () { | ||
return "http://myTransformation"; | ||
}; | ||
} | ||
``` | ||
this.getAlgorithmName = function() { | ||
return "http://myTransformation" | ||
} | ||
} | ||
````` | ||
Custom canonicalization is actually the same as custom transformation. It is applied on the SignedInfo rather than on references. | ||
`````javascript | ||
function MyCanonicalization() { | ||
```javascript | ||
function MyCanonicalization() { | ||
/*given a node (from the xmldom module) return its canonical representation (as string)*/ | ||
this.process = function (node) { | ||
//you should apply your transformation before returning | ||
return "< x/>"; | ||
}; | ||
/*given a node (from the xmldom module) return its canonical representation (as string)*/ | ||
this.process = function(node) { | ||
//you should apply your transformation before returning | ||
return "< x/>" | ||
} | ||
this.getAlgorithmName = function () { | ||
return "http://myCanonicalization"; | ||
}; | ||
} | ||
``` | ||
this.getAlgorithmName = function() { | ||
return "http://myCanonicalization" | ||
} | ||
} | ||
````` | ||
Now you need to register the new algorithms: | ||
`````javascript | ||
/*register all the custom algorithms*/ | ||
```javascript | ||
/*register all the custom algorithms*/ | ||
SignedXml.CanonicalizationAlgorithms["http://MyTransformation"] = MyTransformation | ||
SignedXml.CanonicalizationAlgorithms["http://MyCanonicalization"] = MyCanonicalization | ||
SignedXml.HashAlgorithms["http://myDigestAlgorithm"] = MyDigest | ||
SignedXml.SignatureAlgorithms["http://mySigningAlgorithm"] = MySignatureAlgorithm | ||
````` | ||
SignedXml.CanonicalizationAlgorithms["http://MyTransformation"] = MyTransformation; | ||
SignedXml.CanonicalizationAlgorithms["http://MyCanonicalization"] = MyCanonicalization; | ||
SignedXml.HashAlgorithms["http://myDigestAlgorithm"] = MyDigest; | ||
SignedXml.SignatureAlgorithms["http://mySigningAlgorithm"] = MySignatureAlgorithm; | ||
``` | ||
Now do the signing. Note how we configure the signature to use the above algorithms: | ||
`````javascript | ||
function signXml(xml, xpath, key, dest) | ||
{ | ||
var sig = new SignedXml() | ||
```javascript | ||
function signXml(xml, xpath, key, dest) { | ||
var sig = new SignedXml(); | ||
/*configure the signature object to use the custom algorithms*/ | ||
sig.signatureAlgorithm = "http://mySignatureAlgorithm" | ||
sig.keyInfoProvider = new MyKeyInfo() | ||
sig.canonicalizationAlgorithm = "http://MyCanonicalization" | ||
sig.addReference("//*[local-name(.)='x']", ["http://MyTransformation"], "http://myDigestAlgorithm") | ||
/*configure the signature object to use the custom algorithms*/ | ||
sig.signatureAlgorithm = "http://mySignatureAlgorithm"; | ||
sig.keyInfoProvider = new MyKeyInfo(); | ||
sig.canonicalizationAlgorithm = "http://MyCanonicalization"; | ||
sig.addReference( | ||
"//*[local-name(.)='x']", | ||
["http://MyTransformation"], | ||
"http://myDigestAlgorithm" | ||
); | ||
sig.signingKey = fs.readFileSync(key) | ||
sig.addReference(xpath) | ||
sig.computeSignature(xml) | ||
fs.writeFileSync(dest, sig.getSignedXml()) | ||
} | ||
sig.signingKey = fs.readFileSync(key); | ||
sig.addReference(xpath); | ||
sig.computeSignature(xml); | ||
fs.writeFileSync(dest, sig.getSignedXml()); | ||
} | ||
var xml = "<library>" + | ||
"<book>" + | ||
"<name>Harry Potter</name>" + | ||
"</book>" | ||
"</library>" | ||
var xml = "<library>" + "<book>" + "<name>Harry Potter</name>" + "</book>"; | ||
("</library>"); | ||
signXml(xml, | ||
"//*[local-name(.)='book']", | ||
"client.pem", | ||
"result.xml") | ||
````` | ||
signXml(xml, "//*[local-name(.)='book']", "client.pem", "result.xml"); | ||
``` | ||
@@ -397,23 +393,23 @@ You can always look at the actual code as a sample (or drop me a [mail](mailto:yaronn01@gmail.com)). | ||
`````javascript | ||
function AsyncSignatureAlgorithm() { | ||
this.getSignature = function (signedInfo, signingKey, callback) { | ||
var signer = crypto.createSign("RSA-SHA1") | ||
signer.update(signedInfo) | ||
var res = signer.sign(signingKey, 'base64') | ||
//Do some asynchronous things here | ||
callback(null, res) | ||
} | ||
this.getAlgorithmName = function () { | ||
return "http://www.w3.org/2000/09/xmldsig#rsa-sha1" | ||
} | ||
} | ||
```javascript | ||
function AsyncSignatureAlgorithm() { | ||
this.getSignature = function (signedInfo, signingKey, callback) { | ||
var signer = crypto.createSign("RSA-SHA1"); | ||
signer.update(signedInfo); | ||
var res = signer.sign(signingKey, "base64"); | ||
//Do some asynchronous things here | ||
callback(null, res); | ||
}; | ||
this.getAlgorithmName = function () { | ||
return "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; | ||
}; | ||
} | ||
SignedXml.SignatureAlgorithms["http://asyncSignatureAlgorithm"] = AsyncSignatureAlgorithm | ||
var sig = new SignedXml() | ||
sig.signatureAlgorithm = "http://asyncSignatureAlgorithm" | ||
sig.computeSignature(xml, opts, function(err){ | ||
var signedResponse = sig.getSignedXml() | ||
}) | ||
````` | ||
SignedXml.SignatureAlgorithms["http://asyncSignatureAlgorithm"] = AsyncSignatureAlgorithm; | ||
var sig = new SignedXml(); | ||
sig.signatureAlgorithm = "http://asyncSignatureAlgorithm"; | ||
sig.computeSignature(xml, opts, function (err) { | ||
var signedResponse = sig.getSignedXml(); | ||
}); | ||
``` | ||
@@ -423,49 +419,56 @@ The function `sig.checkSignature` may also use a callback if asynchronous verification is needed. | ||
## X.509 / Key formats | ||
Xml-Crypto internally relies on node's crypto module. This means pem encoded certificates are supported. So to sign an xml use key.pem that looks like this (only the begining of the key content is shown): | ||
-----BEGIN PRIVATE KEY----- | ||
MIICdwIBADANBgkqhkiG9w0... | ||
-----END PRIVATE KEY----- | ||
Xml-Crypto internally relies on node's crypto module. This means pem encoded certificates are supported. So to sign an xml use key.pem that looks like this (only the beginning of the key content is shown): | ||
```text | ||
-----BEGIN PRIVATE KEY----- | ||
MIICdwIBADANBgkqhkiG9w0... | ||
-----END PRIVATE KEY----- | ||
``` | ||
And for verification use key_public.pem: | ||
-----BEGIN CERTIFICATE----- | ||
MIIBxDCCAW6gAwIBAgIQxUSX... | ||
-----END CERTIFICATE----- | ||
```text | ||
-----BEGIN CERTIFICATE----- | ||
MIIBxDCCAW6gAwIBAgIQxUSX... | ||
-----END CERTIFICATE----- | ||
``` | ||
**Converting .pfx certificates to pem** | ||
### Converting .pfx certificates to pem | ||
If you have .pfx certificates you can convert them to .pem using [openssl](http://www.openssl.org/): | ||
openssl pkcs12 -in c:\certs\yourcert.pfx -out c:\certs\cag.pem | ||
```shell | ||
openssl pkcs12 -in c:\certs\yourcert.pfx -out c:\certs\cag.pem | ||
``` | ||
Then you could use the result as is for the purpose of signing. For the purpose of validation open the resulting .pem with a text editor and copy from -----BEGIN CERTIFICATE----- to -----END CERTIFICATE----- (including) to a new text file and save it as .pem. | ||
Then you could use the result as is for the purpose of signing. For the purpose of validation open the resulting .pem with a text editor and copy from -----BEGIN CERTIFICATE----- to -----END CERTIFICATE----- (including) to a new text file and save it as .pem. | ||
## Examples | ||
- [how to sign a root node](#) *coming soon* | ||
### how to sign a root node (_coming soon_) | ||
###how to add a prefix for the signature### | ||
Use the `prefix` option when calling `computeSignature` to add a prefix to the signature. | ||
`````javascript | ||
var SignedXml = require('xml-crypto').SignedXml | ||
, fs = require('fs'); | ||
### how to add a prefix for the signature | ||
var xml = "<library>" + | ||
"<book>" + | ||
"<name>Harry Potter</name>" + | ||
"</book>" + | ||
"</library>"; | ||
Use the `prefix` option when calling `computeSignature` to add a prefix to the signature. | ||
```javascript | ||
var SignedXml = require("xml-crypto").SignedXml, | ||
fs = require("fs"); | ||
var xml = "<library>" + "<book>" + "<name>Harry Potter</name>" + "</book>" + "</library>"; | ||
var sig = new SignedXml(); | ||
sig.addReference("//*[local-name(.)='book']"); | ||
sig.signingKey = fs.readFileSync("client.pem"); | ||
sig.computeSignature(xml,{ | ||
prefix: 'ds' | ||
sig.computeSignature(xml, { | ||
prefix: "ds", | ||
}); | ||
````` | ||
``` | ||
###how to specify the location of the signature### | ||
Use the `location` option when calling `computeSignature` to move the signature around. | ||
Set `action` to one of the following: | ||
### how to specify the location of the signature | ||
Use the `location` option when calling `computeSignature` to move the signature around. | ||
Set `action` to one of the following: | ||
- append(default) - append to the end of the xml document | ||
@@ -476,11 +479,7 @@ - prepend - prepend to the xml document | ||
`````javascript | ||
var SignedXml = require('xml-crypto').SignedXml | ||
, fs = require('fs'); | ||
```javascript | ||
var SignedXml = require("xml-crypto").SignedXml, | ||
fs = require("fs"); | ||
var xml = "<library>" + | ||
"<book>" + | ||
"<name>Harry Potter</name>" + | ||
"</book>" + | ||
"</library>"; | ||
var xml = "<library>" + "<book>" + "<name>Harry Potter</name>" + "</book>" + "</library>"; | ||
@@ -490,18 +489,21 @@ var sig = new SignedXml(); | ||
sig.signingKey = fs.readFileSync("client.pem"); | ||
sig.computeSignature(xml,{ | ||
location: { reference: "//*[local-name(.)='book']", action: "after" } //This will place the signature after the book element | ||
sig.computeSignature(xml, { | ||
location: { reference: "//*[local-name(.)='book']", action: "after" }, //This will place the signature after the book element | ||
}); | ||
``` | ||
````` | ||
*more examples coming soon* | ||
### more examples (_coming soon_) | ||
## Development | ||
The test framework is [nodeunit](https://github.com/caolan/nodeunit). To run tests use: | ||
$> npm test | ||
```shell | ||
npm test | ||
``` | ||
## More information | ||
Visit my [blog](http://webservices20.blogspot.com/) or my [twitter](http://twitter.com/#!/YaronNaveh) | ||
[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/yaronn/xml-crypto/trend.png)](https://bitdeli.com/free "Bitdeli Badge") | ||
@@ -508,0 +510,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
100692
25
1896
496
9
2
+ Added@xmldom/xmldom@0.8.7(transitive)
- Removed@xmldom/xmldom@0.8.10(transitive)
Updated@xmldom/xmldom@0.8.7