Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

xml-crypto

Package Overview
Dependencies
Maintainers
2
Versions
80
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

xml-crypto - npm Package Compare versions

Comparing version 0.10.1 to 1.0.0

36

lib/c14n-canonicalization.js

@@ -67,6 +67,8 @@ /* jshint laxcomma: true */

* @param {String} defaultNs. The current default namespace
* @param {String} defaultNsForPrefix.
* @param {String} ancestorNamespaces - Import ancestor namespaces if it is specified
* @return {String}
* @api private
*/
C14nCanonicalization.prototype.renderNs = function(node, prefixesInScope, defaultNs, defaultNsForPrefix) {
C14nCanonicalization.prototype.renderNs = function(node, prefixesInScope, defaultNs, defaultNsForPrefix, ancestorNamespaces) {
var a, i, p, attr

@@ -97,4 +99,4 @@ , res = []

//handle all prefixed attributes that are included in the prefix list and where
//the prefix is not defined already
if (attr.prefix && prefixesInScope.indexOf(attr.localName) === -1) {
//the prefix is not defined already. New prefixes can only be defined by `xmlns:`.
if (attr.prefix === "xmlns" && prefixesInScope.indexOf(attr.localName) === -1) {
nsListToRender.push({"prefix": attr.localName, "namespaceURI": attr.value});

@@ -112,2 +114,21 @@ prefixesInScope.push(attr.localName);

}
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;
var alreadyListed = false;
for(var p2 in nsListToRender){
if(nsListToRender[p2].prefix === ancestorNamespaces[p1].prefix
&& nsListToRender[p2].namespaceURI === ancestorNamespaces[p1].namespaceURI)
{
alreadyListed = true;
}
}
if(!alreadyListed){
nsListToRender.push(ancestorNamespaces[p1]);
}
}
}

@@ -127,3 +148,3 @@ nsListToRender.sort(this.nsCompare);

C14nCanonicalization.prototype.processInner = function(node, prefixesInScope, defaultNs, defaultNsForPrefix) {
C14nCanonicalization.prototype.processInner = function(node, prefixesInScope, defaultNs, defaultNsForPrefix, ancestorNamespaces) {

@@ -134,3 +155,3 @@ if (node.nodeType === 8) { return this.renderComment(node); }

var i, pfxCopy
, ns = this.renderNs(node, prefixesInScope, defaultNs, defaultNsForPrefix)
, ns = this.renderNs(node, prefixesInScope, defaultNs, defaultNsForPrefix, ancestorNamespaces)
, res = ["<", node.tagName, ns.rendered, this.renderAttrs(node, ns.newDefaultNs), ">"];

@@ -140,3 +161,3 @@

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, []));
}

@@ -194,4 +215,5 @@

var defaultNsForPrefix = options.defaultNsForPrefix || {};
var ancestorNamespaces = options.ancestorNamespaces || [];
var res = this.processInner(node, [], defaultNs, defaultNsForPrefix);
var res = this.processInner(node, [], defaultNs, defaultNsForPrefix, ancestorNamespaces);
return res;

@@ -198,0 +220,0 @@ };

2

lib/enveloped-signature.js

@@ -9,3 +9,3 @@ var xpath = require('xpath.js');

EnvelopedSignature.prototype.process = function (node) {
var signature = xpath(node, ".//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0];
var signature = xpath(node, "./*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']")[0];
if (signature) signature.parentNode.removeChild(signature);

@@ -12,0 +12,0 @@ return node;

@@ -9,2 +9,3 @@ var select = require('xpath.js')

, fs = require('fs')
, xpath = require('xpath.js')

@@ -201,3 +202,85 @@ exports.SignedXml = SignedXml

/**
* Extract ancestor namespaces in order to import it to root of document subset
* which is being canonicalized for non-exclusive c14n.
*
* @param {object} doc - Usually a product from `new DOMParser().parseFromString()`
* @param {string} docSubsetXpath - xpath query to get document subset being canonicalized
* @returns {Array} i.e. [{prefix: "saml", namespaceURI: "urn:oasis:names:tc:SAML:2.0:assertion"}]
*/
function findAncestorNs(doc, docSubsetXpath){
var docSubset = xpath(doc, docSubsetXpath);
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++){
var notOnTheList = true;
for(var v in ancestorNsWithoutDuplicate){
if(ancestorNsWithoutDuplicate[v].prefix === ancestorNs[i].prefix){
notOnTheList = false;
break;
}
}
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++){
var isUnique = true;
for(var k=0;k<subsetAttributes.length;k++){
var nodeName = subsetAttributes[k].nodeName;
if(nodeName.search(/^xmlns:/) === -1) continue;
var prefix = nodeName.replace(/^xmlns:/, "");
if(ancestorNsWithoutDuplicate[j].prefix === prefix){
isUnique = false;
break;
}
}
if(isUnique){
returningNs.push(ancestorNsWithoutDuplicate[j]);
}
}
return returningNs;
}
function collectAncestorNamespaces(node, nsArray){
if(!nsArray){
nsArray = [];
}
var parent = node.parentNode;
if(!parent){
return nsArray;
}
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})
}
}
}
return collectAncestorNamespaces(parent, nsArray);
}
/**
* Xml signature implementation

@@ -226,2 +309,3 @@ *

if (this.options.idAttribute) this.idAttributes.splice(0, 0, this.options.idAttribute);
this.implicitTransforms = this.options.implicitTransforms || [];
}

@@ -254,2 +338,4 @@

SignedXml.findAncestorNs = findAncestorNs;
SignedXml.prototype.checkSignature = function(xml) {

@@ -271,4 +357,4 @@ this.validationErrors = []

}
if (!this.validateSignatureValue()) {
if (!this.validateSignatureValue(doc)) {
return false;

@@ -280,6 +366,25 @@ }

SignedXml.prototype.validateSignatureValue = function() {
SignedXml.prototype.validateSignatureValue = function(doc) {
var signedInfo = utils.findChilds(this.signatureNode, "SignedInfo")
if (signedInfo.length==0) throw new Error("could not find SignedInfo element in the message")
var signedInfoCanon = this.getCanonXml([this.canonicalizationAlgorithm], signedInfo[0])
/**
* When canonicalization algorithm is non-exclusive, search for ancestor namespaces
* before validating signature.
*/
var ancestorNamespaces = [];
if(this.canonicalizationAlgorithm === "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
|| this.canonicalizationAlgorithm === "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments")
{
if(!doc || typeof(doc) !== "object"){
throw new Error("When canonicalization method is non-exclusive, whole xml dom must be provided as an argument");
}
ancestorNamespaces = findAncestorNs(doc, "//*[local-name()='SignedInfo']");
}
var c14nOptions = {
ancestorNamespaces: ancestorNamespaces
};
var signedInfoCanon = this.getCanonXml([this.canonicalizationAlgorithm], signedInfo[0], c14nOptions)
var signer = this.findSignatureAlgorithm(this.signatureAlgorithm)

@@ -319,2 +424,3 @@ var res = signer.verifySignature(signedInfoCanon, this.signingKey, this.signatureValue)

var elem = [];
var elemXpath;

@@ -332,7 +438,9 @@ if (uri=="") {

if (!this.idAttributes.hasOwnProperty(index)) continue;
var tmp_elem = select(doc, "//*[@*[local-name(.)='" + this.idAttributes[index] + "']='" + uri + "']")
var tmp_elemXpath = "//*[@*[local-name(.)='" + this.idAttributes[index] + "']='" + uri + "']";
var tmp_elem = select(doc, tmp_elemXpath)
num_elements_for_id += tmp_elem.length;
if (tmp_elem.length > 0) {
elem = tmp_elem;
};
elemXpath = tmp_elemXpath;
}
}

@@ -351,3 +459,30 @@ if (num_elements_for_id > 1) {

}
var canonXml = this.getCanonXml(ref.transforms, elem[0], { inclusiveNamespacesPrefixList: ref.inclusiveNamespacesPrefixList });
/**
* When canonicalization algorithm is non-exclusive, search for ancestor namespaces
* before validating references.
*/
if(Array.isArray(ref.transforms)){
var hasNonExcC14nTransform = false;
for(var t in ref.transforms){
if(!ref.transforms.hasOwnProperty(t)) continue;
if(ref.transforms[t] === "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
|| ref.transforms[t] === "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments")
{
hasNonExcC14nTransform = true;
break;
}
}
if(hasNonExcC14nTransform){
ref.ancestorNamespaces = findAncestorNs(doc, elemXpath);
}
}
var c14nOptions = {
inclusiveNamespacesPrefixList: ref.inclusiveNamespacesPrefixList,
ancestorNamespaces: ref.ancestorNamespaces
};
var canonXml = this.getCanonXml(ref.transforms, elem[0], c14nOptions);

@@ -357,3 +492,3 @@ var hash = this.findHashAlgorithm(ref.digestAlgorithm)

if (digest!=ref.digestValue) {
if (!validateDigestValue(digest, ref.digestValue)) {
this.validationErrors.push("invalid signature: for uri " + ref.uri +

@@ -369,2 +504,32 @@ " calculated digest is " + digest +

function validateDigestValue(digest, expectedDigest) {
var buffer, expectedBuffer;
if (typeof Buffer.from === 'function') {
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');
}
if (typeof buffer.equals === 'function') {
return buffer.equals(expectedBuffer);
}
// Compatibility with Node < 0.11.13
if (buffer.length !== expectedBuffer.length) {
return false;
}
for (var i = 0; i < buffer.length; i++) {
if (buffer[i] !== expectedBuffer[i]) {
return false;
}
}
return true;
}
SignedXml.prototype.loadSignature = function(signatureNode) {

@@ -442,5 +607,21 @@ if (typeof signatureNode === 'string') {

//***workaround for validating windows mobile store signatures - it uses c14n but does not state it in the transforms
if (transforms.length==1 && transforms[0]=="http://www.w3.org/2000/09/xmldsig#enveloped-signature")
transforms.push("http://www.w3.org/2001/10/xml-exc-c14n#")
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")
}

@@ -625,3 +806,3 @@ this.addReference(null, transforms, digestAlgo, utils.findAttr(ref, "URI").value, digestValue, inclusiveNamespacesPrefixList, false)

var canonXml = node
var canonXml = node.cloneNode(true) // Deep clone

@@ -628,0 +809,0 @@ for (var t in transforms) {

{
"name": "xml-crypto",
"version": "0.10.1",
"version": "1.0.0",
"description": "Xml digital signature and encryption library for Node.js",

@@ -10,7 +10,7 @@ "engines": {

"dependencies": {
"xmldom": "=0.1.19",
"xmldom": "0.1.27",
"xpath.js": ">=0.0.3"
},
"devDependencies": {
"nodeunit": ">=0.6.4"
"nodeunit": "^0.11.3"
},

@@ -33,4 +33,4 @@ "repository": {

"scripts": {
"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": "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"
}
}

@@ -135,3 +135,3 @@ ## xml-crypto

var res = sig.checkSignature(xml)
if (!res) console.log(sig.validationErrors)
if (!res) console.log(sig.validationErrors)
`````

@@ -141,2 +141,11 @@

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')
`````
Note:

@@ -147,2 +156,26 @@

### Caring for Implicit transform
If you fail to verify signed XML, then one possible cause is that there are some hidden implicit transforms(#).
(#) Normalizing XML document to be verified. i.e. remove extra space within a tag, sorting attributes, importing namespace declared in ancestor nodes, etc.
The reason for these implicit transform might come from [complex xml signature specification](https://www.w3.org/TR/2002/REC-xmldsig-core-20020212),
which makes XML developers confused and then leads to incorrect implementation for signing XML document.
If you keep failing verification, it is worth trying to guess such a hidden transform and specify it to the option as below:
```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)
```
You might find it difficult to guess such transforms, but there are typical transforms you can try.
- http://www.w3.org/TR/2001/REC-xml-c14n-20010315
- http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments
- http://www.w3.org/2001/10/xml-exc-c14n#
- http://www.w3.org/2001/10/xml-exc-c14n#WithComments
## API

@@ -149,0 +182,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc