Socket
Socket
Sign inDemoInstall

xml-crypto

Package Overview
Dependencies
2
Maintainers
5
Versions
80
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 3.2.0 to 4.0.0

lib/c14n-canonicalization.d.ts

494

lib/c14n-canonicalization.js

@@ -1,294 +0,224 @@

/* jshint laxcomma: true */
var utils = require("./utils");
exports.C14nCanonicalization = C14nCanonicalization;
exports.C14nCanonicalizationWithComments = C14nCanonicalizationWithComments;
function C14nCanonicalization() {
this.includeComments = false;
}
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;
if (left === right) return 0;
else if (left < right) return -1;
else return 1;
};
C14nCanonicalization.prototype.nsCompare = function (a, b) {
var attr1 = a.prefix;
var attr2 = b.prefix;
if (attr1 == attr2) {
return 0;
}
return attr1.localeCompare(attr2);
};
C14nCanonicalization.prototype.renderAttrs = function (node, defaultNS) {
var a,
i,
attr,
res = [],
attrListToRender = [];
if (node.nodeType === 8) {
return this.renderComment(node);
}
if (node.attributes) {
for (i = 0; i < node.attributes.length; ++i) {
attr = node.attributes[i];
//ignore namespace definition attributes
if (attr.name.indexOf("xmlns") === 0) {
continue;
}
attrListToRender.push(attr);
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.C14nCanonicalizationWithComments = exports.C14nCanonicalization = void 0;
const utils = require("./utils");
const xpath = require("xpath");
class C14nCanonicalization {
constructor() {
this.includeComments = false;
}
}
attrListToRender.sort(this.attrCompare);
for (a in attrListToRender) {
if (!attrListToRender.hasOwnProperty(a)) {
continue;
attrCompare(a, b) {
if (!a.namespaceURI && b.namespaceURI) {
return -1;
}
if (!b.namespaceURI && a.namespaceURI) {
return 1;
}
const left = a.namespaceURI + a.localName;
const right = b.namespaceURI + b.localName;
if (left === right) {
return 0;
}
else if (left < right) {
return -1;
}
else {
return 1;
}
}
attr = attrListToRender[a];
res.push(" ", attr.name, '="', utils.encodeSpecialCharactersInAttribute(attr.value), '"');
}
return res.join("");
};
/**
* Create the string of all namespace declarations that should appear on this element
*
* @param {Node} node. The node we now render
* @param {Array} prefixesInScope. The prefixes defined on this node
* parents which are a part of the output set
* @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,
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],
});
prefixesInScope.push(node.prefix);
nsCompare(a, b) {
const attr1 = a.prefix;
const attr2 = b.prefix;
if (attr1 === attr2) {
return 0;
}
return attr1.localeCompare(attr2);
}
} else if (defaultNs != currNs) {
//new default ns
newDefaultNs = node.namespaceURI;
res.push(' xmlns="', newDefaultNs, '"');
}
//handle the attributes namespace
if (node.attributes) {
for (i = 0; i < node.attributes.length; ++i) {
attr = node.attributes[i];
//handle all prefixed attributes that are included in the prefix list and where
//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 });
prefixesInScope.push(attr.localName);
}
//handle all prefixed attributes that are not xmlns definitions and where
//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 });
prefixesInScope.push(attr.prefix);
}
renderAttrs(node) {
let i;
let attr;
const attrListToRender = [];
if (xpath.isComment(node)) {
return this.renderComment(node);
}
if (node.attributes) {
for (i = 0; i < node.attributes.length; ++i) {
attr = node.attributes[i];
//ignore namespace definition attributes
if (attr.name.indexOf("xmlns") === 0) {
continue;
}
attrListToRender.push(attr);
}
}
attrListToRender.sort(this.attrCompare);
const res = attrListToRender.map((attr) => {
return ` ${attr.name}="${utils.encodeSpecialCharactersInAttribute(attr.value)}"`;
});
return res.join("");
}
}
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;
/**
* Create the string of all namespace declarations that should appear on this element
*
* @param node The node we now render
* @param prefixesInScope The prefixes defined on this node parents which are a part of the output set
* @param defaultNs The current default namespace
* @param defaultNsForPrefix
* @param ancestorNamespaces Import ancestor namespaces if it is specified
* @api private
*/
renderNs(node, prefixesInScope, defaultNs, defaultNsForPrefix, ancestorNamespaces) {
let i;
let attr;
const res = [];
let newDefaultNs = defaultNs;
const nsListToRender = [];
const currNs = node.namespaceURI || "";
//handle the namespace of the node itself
if (node.prefix) {
if (prefixesInScope.indexOf(node.prefix) === -1) {
nsListToRender.push({
prefix: node.prefix,
namespaceURI: node.namespaceURI || defaultNsForPrefix[node.prefix],
});
prefixesInScope.push(node.prefix);
}
}
}
if (!alreadyListed) {
nsListToRender.push(ancestorNamespaces[p1]);
}
else if (defaultNs !== currNs) {
//new default ns
newDefaultNs = node.namespaceURI || "";
res.push(' xmlns="', newDefaultNs, '"');
}
//handle the attributes namespace
if (node.attributes) {
for (i = 0; i < node.attributes.length; ++i) {
attr = node.attributes[i];
//handle all prefixed attributes that are included in the prefix list and where
//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 });
prefixesInScope.push(attr.localName);
}
//handle all prefixed attributes that are not xmlns definitions and where
//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 });
prefixesInScope.push(attr.prefix);
}
}
}
if (utils.isArrayHasLength(ancestorNamespaces)) {
// Remove namespaces which are already present in nsListToRender
for (const ancestorNamespace of ancestorNamespaces) {
let alreadyListed = false;
for (const nsToRender of nsListToRender) {
if (nsToRender.prefix === ancestorNamespace.prefix &&
nsToRender.namespaceURI === ancestorNamespace.namespaceURI) {
alreadyListed = true;
}
}
if (!alreadyListed) {
nsListToRender.push(ancestorNamespace);
}
}
}
nsListToRender.sort(this.nsCompare);
//render namespaces
res.push(...nsListToRender.map((attr) => {
if (attr.prefix) {
return ` xmlns:${attr.prefix}="${attr.namespaceURI}"`;
}
return ` xmlns="${attr.namespaceURI}"`;
}));
return { rendered: res.join(""), newDefaultNs };
}
}
nsListToRender.sort(this.nsCompare);
//render namespaces
for (a in nsListToRender) {
if (!nsListToRender.hasOwnProperty(a)) {
continue;
processInner(node, prefixesInScope, defaultNs, defaultNsForPrefix, ancestorNamespaces) {
if (xpath.isComment(node)) {
return this.renderComment(node);
}
if (node.data) {
return utils.encodeSpecialCharactersInText(node.data);
}
let i;
let pfxCopy;
const ns = this.renderNs(node, prefixesInScope, defaultNs, defaultNsForPrefix, ancestorNamespaces);
const res = ["<", node.tagName, ns.rendered, this.renderAttrs(node), ">"];
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("</", node.tagName, ">");
return res.join("");
}
p = nsListToRender[a];
res.push(" xmlns", p.prefix ? ":" + p.prefix : "", '="', p.namespaceURI, '"');
}
return { rendered: res.join(""), newDefaultNs: newDefaultNs };
};
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);
}
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("</", node.tagName, ">");
return res.join("");
};
// Thanks to deoxxa/xml-c14n for comment renderer
C14nCanonicalization.prototype.renderComment = function (node) {
if (!this.includeComments) {
return "";
}
var isOutsideDocument = node.ownerDocument === node.parentNode,
isBeforeDocument = null,
isAfterDocument = null;
if (isOutsideDocument) {
var nextNode = node,
previousNode = node;
while (nextNode !== null) {
if (nextNode === node.ownerDocument.documentElement) {
isBeforeDocument = true;
break;
}
nextNode = nextNode.nextSibling;
// Thanks to deoxxa/xml-c14n for comment renderer
renderComment(node) {
if (!this.includeComments) {
return "";
}
const isOutsideDocument = node.ownerDocument === node.parentNode;
let isBeforeDocument = false;
let isAfterDocument = false;
if (isOutsideDocument) {
let nextNode = node;
let previousNode = node;
while (nextNode !== null) {
if (nextNode === node.ownerDocument.documentElement) {
isBeforeDocument = true;
break;
}
nextNode = nextNode.nextSibling;
}
while (previousNode !== null) {
if (previousNode === node.ownerDocument.documentElement) {
isAfterDocument = true;
break;
}
previousNode = previousNode.previousSibling;
}
}
const afterDocument = isAfterDocument ? "\n" : "";
const beforeDocument = isBeforeDocument ? "\n" : "";
const encodedText = utils.encodeSpecialCharactersInText(node.data);
return `${afterDocument}<!--${encodedText}-->${beforeDocument}`;
}
while (previousNode !== null) {
if (previousNode === node.ownerDocument.documentElement) {
isAfterDocument = true;
break;
}
previousNode = previousNode.previousSibling;
/**
* Perform canonicalization of the given node
*
* @param {Node} node
* @return {String}
* @api public
*/
process(node, options) {
options = options || {};
const defaultNs = options.defaultNs || "";
const defaultNsForPrefix = options.defaultNsForPrefix || {};
const ancestorNamespaces = options.ancestorNamespaces || [];
const prefixesInScope = [];
for (let i = 0; i < ancestorNamespaces.length; i++) {
prefixesInScope.push(ancestorNamespaces[i].prefix);
}
const res = this.processInner(node, prefixesInScope, defaultNs, defaultNsForPrefix, ancestorNamespaces);
return res;
}
}
return (
(isAfterDocument ? "\n" : "") +
"<!--" +
utils.encodeSpecialCharactersInText(node.data) +
"-->" +
(isBeforeDocument ? "\n" : "")
);
};
getAlgorithmName() {
return "http://www.w3.org/TR/2001/REC-xml-c14n-20010315";
}
}
exports.C14nCanonicalization = C14nCanonicalization;
/**
* Perform canonicalization of the given node
*
* @param {Node} node
* @return {String}
* @api public
* Add c14n#WithComments here (very simple subclass)
*/
C14nCanonicalization.prototype.process = function (node, options) {
options = options || {};
var defaultNs = options.defaultNs || "";
var defaultNsForPrefix = options.defaultNsForPrefix || {};
var ancestorNamespaces = options.ancestorNamespaces || [];
var prefixesInScope = [];
for (var i = 0; i < ancestorNamespaces.length; i++) {
prefixesInScope.push(ancestorNamespaces[i].prefix);
}
var res = this.processInner(
node,
prefixesInScope,
defaultNs,
defaultNsForPrefix,
ancestorNamespaces
);
return res;
};
C14nCanonicalization.prototype.getAlgorithmName = function () {
return "http://www.w3.org/TR/2001/REC-xml-c14n-20010315";
};
// Add c14n#WithComments here (very simple subclass)
class C14nCanonicalizationWithComments extends C14nCanonicalization {
constructor() {
super();
this.includeComments = true;
}
getAlgorithmName() {
return "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments";
}
}
exports.C14nCanonicalizationWithComments = C14nCanonicalizationWithComments;
function C14nCanonicalizationWithComments() {
C14nCanonicalization.call(this);
this.includeComments = true;
}
C14nCanonicalizationWithComments.prototype = Object.create(C14nCanonicalization.prototype);
C14nCanonicalizationWithComments.prototype.constructor = C14nCanonicalizationWithComments;
C14nCanonicalizationWithComments.prototype.getAlgorithmName = function () {
return "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments";
};
//# sourceMappingURL=c14n-canonicalization.js.map

@@ -1,43 +0,41 @@

var xpath = require("xpath");
var utils = require("./utils");
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EnvelopedSignature = void 0;
const xpath = require("xpath");
class EnvelopedSignature {
constructor() {
this.includeComments = false;
}
process(node, options) {
if (null == options.signatureNode) {
const signature = xpath.select1("./*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", node);
if (xpath.isNodeLike(signature) && signature.parentNode) {
signature.parentNode.removeChild(signature);
}
return node;
}
const signatureNode = options.signatureNode;
const expectedSignatureValue = xpath.select1(".//*[local-name(.)='SignatureValue']/text()", signatureNode);
if (xpath.isTextNode(expectedSignatureValue)) {
const expectedSignatureValueData = expectedSignatureValue.data;
const signatures = xpath.select(".//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", node);
for (const nodeSignature of Array.isArray(signatures) ? signatures : []) {
const signatureValue = xpath.select1(".//*[local-name(.)='SignatureValue']/text()", nodeSignature);
if (xpath.isTextNode(signatureValue)) {
const signatureValueData = signatureValue.data;
if (expectedSignatureValueData === signatureValueData) {
if (nodeSignature.parentNode) {
nodeSignature.parentNode.removeChild(nodeSignature);
}
}
}
}
}
return node;
}
getAlgorithmName() {
return "http://www.w3.org/2000/09/xmldsig#enveloped-signature";
}
}
exports.EnvelopedSignature = EnvelopedSignature;
function EnvelopedSignature() {}
EnvelopedSignature.prototype.process = function (node, options) {
if (null == options.signatureNode) {
// leave this for the moment...
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);
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
);
for (var h in signatures) {
if (!signatures.hasOwnProperty(h)) continue;
var nodeSignature = signatures[h];
var signatureValue = utils.findFirst(
nodeSignature,
".//*[local-name(.)='SignatureValue']/text()"
).data;
if (expectedSignatureValue === signatureValue) {
nodeSignature.parentNode.removeChild(nodeSignature);
}
}
return node;
};
EnvelopedSignature.prototype.getAlgorithmName = function () {
return "http://www.w3.org/2000/09/xmldsig#enveloped-signature";
};
//# sourceMappingURL=enveloped-signature.js.map

@@ -1,348 +0,241 @@

/* jshint laxcomma: true */
var utils = require("./utils");
exports.ExclusiveCanonicalization = ExclusiveCanonicalization;
exports.ExclusiveCanonicalizationWithComments = ExclusiveCanonicalizationWithComments;
function ExclusiveCanonicalization() {
this.includeComments = false;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExclusiveCanonicalizationWithComments = exports.ExclusiveCanonicalization = void 0;
const utils = require("./utils");
const xpath = require("xpath");
function isPrefixInScope(prefixesInScope, prefix, namespaceURI) {
let ret = false;
prefixesInScope.forEach(function (pf) {
if (pf.prefix === prefix && pf.namespaceURI === namespaceURI) {
ret = true;
}
});
return ret;
}
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;
if (left === right) return 0;
else if (left < right) return -1;
else return 1;
};
ExclusiveCanonicalization.prototype.nsCompare = function (a, b) {
var attr1 = a.prefix;
var attr2 = b.prefix;
if (attr1 == attr2) {
return 0;
}
return attr1.localeCompare(attr2);
};
ExclusiveCanonicalization.prototype.renderAttrs = function (node, defaultNS) {
var a,
i,
attr,
res = [],
attrListToRender = [];
if (node.nodeType === 8) {
return this.renderComment(node);
}
if (node.attributes) {
for (i = 0; i < node.attributes.length; ++i) {
attr = node.attributes[i];
//ignore namespace definition attributes
if (attr.name.indexOf("xmlns") === 0) {
continue;
}
attrListToRender.push(attr);
class ExclusiveCanonicalization {
constructor() {
this.includeComments = false;
}
}
attrListToRender.sort(this.attrCompare);
for (a in attrListToRender) {
if (!attrListToRender.hasOwnProperty(a)) {
continue;
attrCompare(a, b) {
if (!a.namespaceURI && b.namespaceURI) {
return -1;
}
if (!b.namespaceURI && a.namespaceURI) {
return 1;
}
const left = a.namespaceURI + a.localName;
const right = b.namespaceURI + b.localName;
if (left === right) {
return 0;
}
else if (left < right) {
return -1;
}
else {
return 1;
}
}
attr = attrListToRender[a];
res.push(" ", attr.name, '="', utils.encodeSpecialCharactersInAttribute(attr.value), '"');
}
return res.join("");
};
function isPrefixInScope(prefixesInScope, prefix, namespaceURI) {
var ret = false;
prefixesInScope.forEach(function (pf) {
if (pf.prefix === prefix && pf.namespaceURI === namespaceURI) {
ret = true;
nsCompare(a, b) {
const attr1 = a.prefix;
const attr2 = b.prefix;
if (attr1 === attr2) {
return 0;
}
return attr1.localeCompare(attr2);
}
});
return ret;
}
/**
* Create the string of all namespace declarations that should appear on this element
*
* @param {Node} node. The node we now render
* @param {Array} prefixesInScope. The prefixes defined on this node
* parents which are a part of the output set
* @param {String} defaultNs. The current default namespace
* @return {String}
* @api private
*/
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],
});
renderAttrs(node) {
let i;
let attr;
const res = [];
const attrListToRender = [];
if (xpath.isComment(node)) {
return this.renderComment(node);
}
if (node.attributes) {
for (i = 0; i < node.attributes.length; ++i) {
attr = node.attributes[i];
//ignore namespace definition attributes
if (attr.name.indexOf("xmlns") === 0) {
continue;
}
attrListToRender.push(attr);
}
}
attrListToRender.sort(this.attrCompare);
for (attr of attrListToRender) {
res.push(" ", attr.name, '="', utils.encodeSpecialCharactersInAttribute(attr.value), '"');
}
return res.join("");
}
} else if (defaultNs != currNs) {
//new default ns
newDefaultNs = node.namespaceURI;
res.push(' xmlns="', newDefaultNs, '"');
}
//handle the attributes namespace
if (node.attributes) {
for (i = 0; i < node.attributes.length; ++i) {
attr = node.attributes[i];
//handle all prefixed attributes that are included in the prefix list and where
//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 });
}
//handle all prefixed attributes that are not xmlns definitions and where
//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 });
}
/**
* Create the string of all namespace declarations that should appear on this element
*
* @param {Node} node. The node we now render
* @param {Array} prefixesInScope. The prefixes defined on this node
* parents which are a part of the output set
* @param {String} defaultNs. The current default namespace
* @return {String}
* @api private
*/
renderNs(node, prefixesInScope, defaultNs, defaultNsForPrefix, inclusiveNamespacesPrefixList) {
let i;
let attr;
const res = [];
let newDefaultNs = defaultNs;
const nsListToRender = [];
const 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],
});
}
}
else if (defaultNs !== currNs) {
//new default ns
newDefaultNs = node.namespaceURI;
res.push(' xmlns="', newDefaultNs, '"');
}
//handle the attributes namespace
if (node.attributes) {
for (i = 0; i < node.attributes.length; ++i) {
attr = node.attributes[i];
//handle all prefixed attributes that are included in the prefix list and where
//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 });
}
//handle all prefixed attributes that are not xmlns definitions and where
//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 });
}
}
}
nsListToRender.sort(this.nsCompare);
//render namespaces
for (const p of nsListToRender) {
res.push(" xmlns:", p.prefix, '="', p.namespaceURI, '"');
}
return { rendered: res.join(""), newDefaultNs: newDefaultNs };
}
}
nsListToRender.sort(this.nsCompare);
//render namespaces
for (a in nsListToRender) {
if (!nsListToRender.hasOwnProperty(a)) {
continue;
processInner(node, prefixesInScope, defaultNs, defaultNsForPrefix, inclusiveNamespacesPrefixList) {
if (xpath.isComment(node)) {
return this.renderComment(node);
}
if (node.data) {
return utils.encodeSpecialCharactersInText(node.data);
}
let i;
let pfxCopy;
const ns = this.renderNs(node, prefixesInScope, defaultNs, defaultNsForPrefix, inclusiveNamespacesPrefixList);
const res = ["<", node.tagName, ns.rendered, this.renderAttrs(node), ">"];
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("</", node.tagName, ">");
return res.join("");
}
p = nsListToRender[a];
res.push(" xmlns:", p.prefix, '="', p.namespaceURI, '"');
}
return { rendered: res.join(""), newDefaultNs: newDefaultNs };
};
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);
}
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("</", node.tagName, ">");
return res.join("");
};
// Thanks to deoxxa/xml-c14n for comment renderer
ExclusiveCanonicalization.prototype.renderComment = function (node) {
if (!this.includeComments) {
return "";
}
var isOutsideDocument = node.ownerDocument === node.parentNode,
isBeforeDocument = null,
isAfterDocument = null;
if (isOutsideDocument) {
var nextNode = node,
previousNode = node;
while (nextNode !== null) {
if (nextNode === node.ownerDocument.documentElement) {
isBeforeDocument = true;
break;
}
nextNode = nextNode.nextSibling;
// Thanks to deoxxa/xml-c14n for comment renderer
renderComment(node) {
if (!this.includeComments) {
return "";
}
const isOutsideDocument = node.ownerDocument === node.parentNode;
let isBeforeDocument = false;
let isAfterDocument = false;
if (isOutsideDocument) {
let nextNode = node;
let previousNode = node;
while (nextNode != null) {
if (nextNode === node.ownerDocument.documentElement) {
isBeforeDocument = true;
break;
}
nextNode = nextNode.nextSibling;
}
while (previousNode != null) {
if (previousNode === node.ownerDocument.documentElement) {
isAfterDocument = true;
break;
}
previousNode = previousNode.previousSibling;
}
}
const afterDocument = isAfterDocument ? "\n" : "";
const beforeDocument = isBeforeDocument ? "\n" : "";
const encodedText = utils.encodeSpecialCharactersInText(node.data);
return `${afterDocument}<!--${encodedText}-->${beforeDocument}`;
}
while (previousNode !== null) {
if (previousNode === node.ownerDocument.documentElement) {
isAfterDocument = true;
break;
}
previousNode = previousNode.previousSibling;
/**
* Perform canonicalization of the given node
*
* @param {Node} node
* @return {String}
* @api public
*/
process(node, options) {
options = options || {};
let inclusiveNamespacesPrefixList = options.inclusiveNamespacesPrefixList || [];
const defaultNs = options.defaultNs || "";
const defaultNsForPrefix = options.defaultNsForPrefix || {};
const ancestorNamespaces = options.ancestorNamespaces || [];
/**
* If the inclusiveNamespacesPrefixList has not been explicitly provided then look it up in CanonicalizationMethod/InclusiveNamespaces
*/
if (!utils.isArrayHasLength(inclusiveNamespacesPrefixList)) {
const CanonicalizationMethod = utils.findChilds(node, "CanonicalizationMethod");
if (CanonicalizationMethod.length !== 0) {
const inclusiveNamespaces = utils.findChilds(CanonicalizationMethod[0], "InclusiveNamespaces");
if (inclusiveNamespaces.length !== 0) {
inclusiveNamespacesPrefixList = (inclusiveNamespaces[0].getAttribute("PrefixList") || "").split(" ");
}
}
}
/**
* If you have a PrefixList then use it and the ancestors to add the necessary namespaces
*/
if (utils.isArrayHasLength(inclusiveNamespacesPrefixList)) {
inclusiveNamespacesPrefixList.forEach(function (prefix) {
if (ancestorNamespaces) {
ancestorNamespaces.forEach(function (ancestorNamespace) {
if (prefix === ancestorNamespace.prefix) {
node.setAttributeNS("http://www.w3.org/2000/xmlns/", `xmlns:${prefix}`, ancestorNamespace.namespaceURI);
}
});
}
});
}
const res = this.processInner(node, [], defaultNs, defaultNsForPrefix, inclusiveNamespacesPrefixList);
return res;
}
}
return (
(isAfterDocument ? "\n" : "") +
"<!--" +
utils.encodeSpecialCharactersInText(node.data) +
"-->" +
(isBeforeDocument ? "\n" : "")
);
};
/**
* Perform canonicalization of the given node
*
* @param {Node} node
* @return {String}
* @api public
*/
ExclusiveCanonicalization.prototype.process = function (node, options) {
options = options || {};
var inclusiveNamespacesPrefixList = options.inclusiveNamespacesPrefixList || [];
var defaultNs = options.defaultNs || "";
var defaultNsForPrefix = options.defaultNsForPrefix || {};
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");
if (CanonicalizationMethod.length != 0) {
var inclusiveNamespaces = utils.findChilds(CanonicalizationMethod[0], "InclusiveNamespaces");
if (inclusiveNamespaces.length != 0) {
inclusiveNamespacesPrefixList = inclusiveNamespaces[0]
.getAttribute("PrefixList")
.split(" ");
}
getAlgorithmName() {
return "http://www.w3.org/2001/10/xml-exc-c14n#";
}
}
/**
* 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(" ");
prefixList.forEach(function (prefix) {
if (ancestorNamespaces) {
ancestorNamespaces.forEach(function (ancestorNamespace) {
if (prefix == ancestorNamespace.prefix) {
node.setAttributeNS(
"http://www.w3.org/2000/xmlns/",
"xmlns:" + prefix,
ancestorNamespace.namespaceURI
);
}
});
}
});
}
var res = this.processInner(
node,
[],
defaultNs,
defaultNsForPrefix,
inclusiveNamespacesPrefixList
);
return res;
};
ExclusiveCanonicalization.prototype.getAlgorithmName = function () {
return "http://www.w3.org/2001/10/xml-exc-c14n#";
};
// Add c14n#WithComments here (very simple subclass)
}
exports.ExclusiveCanonicalization = ExclusiveCanonicalization;
class ExclusiveCanonicalizationWithComments extends ExclusiveCanonicalization {
constructor() {
super();
this.includeComments = true;
}
getAlgorithmName() {
return "http://www.w3.org/2001/10/xml-exc-c14n#WithComments";
}
}
exports.ExclusiveCanonicalizationWithComments = ExclusiveCanonicalizationWithComments;
function ExclusiveCanonicalizationWithComments() {
ExclusiveCanonicalization.call(this);
this.includeComments = true;
}
ExclusiveCanonicalizationWithComments.prototype = Object.create(
ExclusiveCanonicalization.prototype
);
ExclusiveCanonicalizationWithComments.prototype.constructor = ExclusiveCanonicalizationWithComments;
ExclusiveCanonicalizationWithComments.prototype.getAlgorithmName = function () {
return "http://www.w3.org/2001/10/xml-exc-c14n#WithComments";
};
//# sourceMappingURL=exclusive-canonicalization.js.map

@@ -1,1134 +0,797 @@

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.StringKeyInfo = StringKeyInfo;
exports.FileKeyInfo = FileKeyInfo;
/**
* Hash algorithm implementation
*
*/
function SHA1() {
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";
};
}
function SHA256() {
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";
};
}
function SHA512() {
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";
};
}
/**
* 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;
};
/**
* 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";
};
}
/**
* 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;
};
/**
* 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";
};
}
/**
* Signature algorithm implementation
*
*/
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;
};
/**
* 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";
};
}
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.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;
};
}
/**
* 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
* @param {object} namespaceResolver - xpath namespace resolver
* @returns {Array} i.e. [{prefix: "saml", namespaceURI: "urn:oasis:names:tc:SAML:2.0:assertion"}]
*/
function findAncestorNs(doc, docSubsetXpath, namespaceResolver) {
var docSubset = xpath.selectWithResolver(docSubsetXpath, doc, namespaceResolver);
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;
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SignedXml = void 0;
const xpath = require("xpath");
const xmldom_1 = require("@xmldom/xmldom");
const utils = require("./utils");
const c14n = require("./c14n-canonicalization");
const execC14n = require("./exclusive-canonicalization");
const envelopedSignatures = require("./enveloped-signature");
const hashAlgorithms = require("./hash-algorithms");
const signatureAlgorithms = require("./signature-algorithms");
class SignedXml {
/**
* The SignedXml constructor provides an abstraction for sign and verify xml documents. The object is constructed using
* @param options {@link SignedXmlOptions}
*/
constructor(options = {}) {
/**
* One of the supported signature algorithms. See {@link SignatureAlgorithmType}
*/
this.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
/**
* Rules used to convert an XML document into its canonical form.
*/
this.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#";
/**
* It specifies a list of namespace prefixes that should be considered "inclusive" during the canonicalization process.
*/
this.inclusiveNamespacesPrefixList = [];
this.namespaceResolver = {
lookupNamespaceURI: function ( /* prefix */) {
throw new Error("Not implemented");
},
};
this.implicitTransforms = [];
this.keyInfoAttributes = {};
this.getKeyInfoContent = SignedXml.getKeyInfoContent;
this.getCertFromKeyInfo = SignedXml.getCertFromKeyInfo;
// Internal state
/**
* Specifies the data to be signed within an XML document. See {@link Reference}
*/
this.references = [];
this.id = 0;
this.signedXml = "";
this.signatureXml = "";
this.signatureNode = null;
this.signatureValue = "";
this.originalXmlWithIds = "";
/**
* Contains validation errors (if any) after {@link checkSignature} method is called
*/
this.validationErrors = [];
this.keyInfo = null;
/**
* To add a new transformation algorithm create a new class that implements the {@link TransformationAlgorithm} interface, and register it here. More info: {@link https://github.com/node-saml/xml-crypto#customizing-algorithms|Customizing Algorithms}
*/
this.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": envelopedSignatures.EnvelopedSignature,
};
/**
* To add a new hash algorithm create a new class that implements the {@link HashAlgorithm} interface, and register it here. More info: {@link https://github.com/node-saml/xml-crypto#customizing-algorithms|Customizing Algorithms}
*/
this.HashAlgorithms = {
"http://www.w3.org/2000/09/xmldsig#sha1": hashAlgorithms.Sha1,
"http://www.w3.org/2001/04/xmlenc#sha256": hashAlgorithms.Sha256,
"http://www.w3.org/2001/04/xmlenc#sha512": hashAlgorithms.Sha512,
};
/**
* To add a new signature algorithm create a new class that implements the {@link SignatureAlgorithm} interface, and register it here. More info: {@link https://github.com/node-saml/xml-crypto#customizing-algorithms|Customizing Algorithms}
*/
this.SignatureAlgorithms = {
"http://www.w3.org/2000/09/xmldsig#rsa-sha1": signatureAlgorithms.RsaSha1,
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256": signatureAlgorithms.RsaSha256,
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha512": signatureAlgorithms.RsaSha512,
// Disabled by default due to key confusion concerns.
// 'http://www.w3.org/2000/09/xmldsig#hmac-sha1': SignatureAlgorithms.HmacSha1
};
const { idMode, idAttribute, privateKey, publicCert, signatureAlgorithm, canonicalizationAlgorithm, inclusiveNamespacesPrefixList, implicitTransforms, keyInfoAttributes, getKeyInfoContent, getCertFromKeyInfo, } = options;
// Options
this.idMode = idMode;
this.idAttributes = ["Id", "ID", "id"];
if (idAttribute) {
this.idAttributes.unshift(idAttribute);
}
this.privateKey = privateKey;
this.publicCert = publicCert;
this.signatureAlgorithm = signatureAlgorithm !== null && signatureAlgorithm !== void 0 ? signatureAlgorithm : this.signatureAlgorithm;
this.canonicalizationAlgorithm = canonicalizationAlgorithm !== null && canonicalizationAlgorithm !== void 0 ? canonicalizationAlgorithm : this.canonicalizationAlgorithm;
if (typeof inclusiveNamespacesPrefixList === "string") {
this.inclusiveNamespacesPrefixList = inclusiveNamespacesPrefixList.split(" ");
}
else if (utils.isArrayHasLength(inclusiveNamespacesPrefixList)) {
this.inclusiveNamespacesPrefixList = inclusiveNamespacesPrefixList;
}
this.implicitTransforms = implicitTransforms !== null && implicitTransforms !== void 0 ? implicitTransforms : this.implicitTransforms;
this.keyInfoAttributes = keyInfoAttributes !== null && keyInfoAttributes !== void 0 ? keyInfoAttributes : this.keyInfoAttributes;
this.getKeyInfoContent = getKeyInfoContent !== null && getKeyInfoContent !== void 0 ? getKeyInfoContent : this.getKeyInfoContent;
this.getCertFromKeyInfo = getCertFromKeyInfo !== null && getCertFromKeyInfo !== void 0 ? getCertFromKeyInfo : this.getCertFromKeyInfo;
this.CanonicalizationAlgorithms;
this.HashAlgorithms;
this.SignatureAlgorithms;
}
if (notOnTheList) {
ancestorNsWithoutDuplicate.push(ancestorNs[i]);
/**
* Due to key-confusion issues, it's risky to have both hmac
* and digital signature algorithms enabled at the same time.
* This enables HMAC and disables other signing algorithms.
*/
enableHMAC() {
this.SignatureAlgorithms = {
"http://www.w3.org/2000/09/xmldsig#hmac-sha1": signatureAlgorithms.HmacSha1,
};
this.getKeyInfoContent = () => null;
}
}
// Remove namespaces which are already declared in the subset with the same prefix
var returningNs = [];
const subsetNsPrefix = findNSPrefix(docSubset[0]);
for (const ancestorNs of ancestorNsWithoutDuplicate) {
if (ancestorNs.prefix !== subsetNsPrefix) {
returningNs.push(ancestorNs);
/**
* Builds the contents of a KeyInfo element as an XML string.
*
* For example, if the value of the prefix argument is 'foo', then
* the resultant XML string will be "<foo:X509Data></foo:X509Data>"
*
* @return an XML string representation of the contents of a KeyInfo element, or `null` if no `KeyInfo` element should be included
*/
static getKeyInfoContent({ publicCert, prefix }) {
if (publicCert == null) {
return null;
}
prefix = prefix ? `${prefix}:` : "";
let x509Certs = "";
if (Buffer.isBuffer(publicCert)) {
publicCert = publicCert.toString("latin1");
}
let publicCertMatches = [];
if (typeof publicCert === "string") {
publicCertMatches = publicCert.match(utils.EXTRACT_X509_CERTS) || [];
}
if (publicCertMatches.length > 0) {
x509Certs = publicCertMatches
.map((c) => `<X509Certificate>${utils.pemToDer(c)}</X509Certificate>`)
.join("");
}
return `<${prefix}X509Data>${x509Certs}</${prefix}X509Data>`;
}
}
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,
});
}
/**
* Returns the value of the signing certificate based on the contents of the
* specified KeyInfo.
*
* @param keyInfo KeyInfo element (see https://www.w3.org/TR/2008/REC-xmldsig-core-20080610/#sec-X509Data)
* @return the signing certificate as a string in PEM format
*/
static getCertFromKeyInfo(keyInfo) {
if (keyInfo != null) {
const certs = xpath.select1(".//*[local-name(.)='X509Certificate']", keyInfo);
if (xpath.isNodeLike(certs)) {
return utils.derToPem(certs.textContent || "", "CERTIFICATE");
}
}
return null;
}
}
return collectAncestorNamespaces(parent, nsArray);
}
function findNSPrefix(subset) {
const subsetAttributes = subset.attributes;
for (let k = 0; k < subsetAttributes.length; k++) {
const nodeName = subsetAttributes[k].nodeName;
if (nodeName.search(/^xmlns:?/) !== -1) {
return nodeName.replace(/^xmlns:?/, "");
checkSignature(xml, callback) {
if (callback != null && typeof callback !== "function") {
throw new Error("Last parameter must be a callback function");
}
this.validationErrors = [];
this.signedXml = xml;
const doc = new xmldom_1.DOMParser().parseFromString(xml);
if (!this.validateReferences(doc)) {
if (!callback) {
return false;
}
else {
callback(new Error("Could not validate references"));
return;
}
}
if (!callback) {
// Synchronous flow
if (!this.validateSignatureValue(doc)) {
return false;
}
return true;
}
else {
// Asynchronous flow
this.validateSignatureValue(doc, (err, isValidSignature) => {
if (err) {
this.validationErrors.push(`invalid signature: the signature value ${this.signatureValue} is incorrect`);
callback(err);
}
else {
callback(null, isValidSignature);
}
});
}
}
}
return subset.prefix || "";
}
/**
* 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.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);
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,
};
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,
};
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,
// Disabled by default due to key confusion concerns.
// 'http://www.w3.org/2000/09/xmldsig#hmac-sha1': HMACSHA1
};
/**
* Due to key-confusion issues, its risky to have both hmac
* and digital signature algos enabled at the same time.
* This enables HMAC and disables other signing algos.
*/
SignedXml.enableHMAC = function () {
SignedXml.SignatureAlgorithms = {
"http://www.w3.org/2000/09/xmldsig#hmac-sha1": HMACSHA1,
};
};
SignedXml.defaultNsForPrefix = {
ds: "http://www.w3.org/2000/09/xmldsig#",
};
SignedXml.findAncestorNs = findAncestorNs;
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;
if (!this.keyInfoProvider) {
var err = new Error("cannot validate signature since no key info resolver was provided");
if (!callback) {
throw err;
} else {
callback(err);
return;
getCanonSignedInfoXml(doc) {
if (this.signatureNode == null) {
throw new Error("No signature found.");
}
const 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("When canonicalization method is non-exclusive, whole xml dom must be provided as an argument");
}
}
/**
* Search for ancestor namespaces before canonicalization.
*/
const ancestorNamespaces = utils.findAncestorNs(doc, "//*[local-name()='SignedInfo']");
const c14nOptions = {
ancestorNamespaces: ancestorNamespaces,
};
return this.getCanonXml([this.canonicalizationAlgorithm], signedInfo[0], c14nOptions);
}
}
this.signingKey = this.keyInfoProvider.getKey(this.keyInfo);
if (!this.signingKey) {
var err2 = new Error("key info provider could not resolve key info " + this.keyInfo);
if (!callback) {
throw err2;
} else {
callback(err2);
return;
getCanonReferenceXml(doc, ref, node) {
/**
* Search for ancestor namespaces before canonicalization.
*/
if (Array.isArray(ref.transforms)) {
ref.ancestorNamespaces = utils.findAncestorNs(doc, ref.xpath, this.namespaceResolver);
}
const c14nOptions = {
inclusiveNamespacesPrefixList: ref.inclusiveNamespacesPrefixList,
ancestorNamespaces: ref.ancestorNamespaces,
};
return this.getCanonXml(ref.transforms, node, c14nOptions);
}
}
var doc = new Dom().parseFromString(xml);
if (!this.validateReferences(doc)) {
if (!callback) {
return false;
} else {
callback(new Error("Could not validate references"));
return;
validateSignatureValue(doc, callback) {
const signedInfoCanon = this.getCanonSignedInfoXml(doc);
const signer = this.findSignatureAlgorithm(this.signatureAlgorithm);
const key = this.getCertFromKeyInfo(this.keyInfo) || this.publicCert || this.privateKey;
if (key == null) {
throw new Error("KeyInfo or publicCert or privateKey is required to validate signature");
}
if (typeof callback === "function") {
signer.verifySignature(signedInfoCanon, key, this.signatureValue, callback);
}
else {
const res = signer.verifySignature(signedInfoCanon, key, this.signatureValue);
if (res === false) {
this.validationErrors.push(`invalid signature: the signature value ${this.signatureValue} is incorrect`);
}
return res;
}
}
}
if (!callback) {
// Synchronous flow
if (!this.validateSignatureValue(doc)) {
return false;
calculateSignatureValue(doc, callback) {
const signedInfoCanon = this.getCanonSignedInfoXml(doc);
const signer = this.findSignatureAlgorithm(this.signatureAlgorithm);
if (this.privateKey == null) {
throw new Error("Private key is required to compute signature");
}
if (typeof callback === "function") {
signer.getSignature(signedInfoCanon, this.privateKey, callback);
}
else {
this.signatureValue = signer.getSignature(signedInfoCanon, this.privateKey);
}
}
return true;
} else {
// Asynchronous flow
this.validateSignatureValue(doc, function (err, isValidSignature) {
if (err) {
this.validationErrors.push(
"invalid signature: the signature value " + this.signatureValue + " is incorrect"
);
callback(err);
} else {
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") {
throw new Error(
"When canonicalization method is non-exclusive, whole xml dom must be provided as an argument"
);
findSignatureAlgorithm(name) {
const algo = this.SignatureAlgorithms[name];
if (algo) {
return new algo();
}
else {
throw new Error(`signature algorithm '${name}' is not supported`);
}
}
}
/**
* Search for ancestor namespaces before canonicalization.
*/
var ancestorNamespaces = [];
ancestorNamespaces = findAncestorNs(doc, "//*[local-name()='SignedInfo']");
var c14nOptions = {
ancestorNamespaces: ancestorNamespaces,
};
return this.getCanonXml([this.canonicalizationAlgorithm], signedInfo[0], c14nOptions);
};
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);
}
var c14nOptions = {
inclusiveNamespacesPrefixList: ref.inclusiveNamespacesPrefixList,
ancestorNamespaces: ref.ancestorNamespaces,
};
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.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();
else throw new Error("signature algorithm '" + name + "' is not supported");
};
SignedXml.prototype.findCanonicalizationAlgorithm = function (name) {
var algo = SignedXml.CanonicalizationAlgorithms[name];
if (algo) return new algo();
else throw new Error("canonicalization algorithm '" + name + "' is not supported");
};
SignedXml.prototype.findHashAlgorithm = function (name) {
var algo = SignedXml.HashAlgorithms[name];
if (algo) return new algo();
else throw new Error("hash algorithm '" + name + "' is not supported");
};
SignedXml.prototype.validateReferences = function (doc) {
for (var r in this.references) {
if (!this.references.hasOwnProperty(r)) continue;
var ref = this.references[r];
var uri = ref.uri[0] == "#" ? ref.uri.substring(1) : ref.uri;
var elem = [];
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 {
var elemXpath;
var num_elements_for_id = 0;
for (var index in this.idAttributes) {
if (!this.idAttributes.hasOwnProperty(index)) continue;
var tmp_elemXpath =
"//*[@*[local-name(.)='" + this.idAttributes[index] + "']='" + uri + "']";
var tmp_elem = xpath.select(tmp_elemXpath, doc);
num_elements_for_id += tmp_elem.length;
if (tmp_elem.length > 0) {
elem = tmp_elem;
elemXpath = tmp_elemXpath;
findCanonicalizationAlgorithm(name) {
const algo = this.CanonicalizationAlgorithms[name];
if (algo) {
return new algo();
}
}
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."
);
}
ref.xpath = elemXpath;
else {
throw new Error(`canonicalization algorithm '${name}' is not supported`);
}
}
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;
findHashAlgorithm(name) {
const algo = this.HashAlgorithms[name];
if (algo) {
return new algo();
}
else {
throw new Error(`hash algorithm '${name}' is not supported`);
}
}
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
);
return false;
validateReferences(doc) {
var _a;
for (const ref of this.references) {
let elemXpath;
const uri = ((_a = ref.uri) === null || _a === void 0 ? void 0 : _a[0]) === "#" ? ref.uri.substring(1) : ref.uri;
let elem = [];
if (uri === "") {
elem = xpath.select("//*", doc);
}
else if ((uri === null || uri === void 0 ? void 0 : uri.indexOf("'")) !== -1) {
// xpath injection
throw new Error("Cannot validate a uri with quotes inside it");
}
else {
let num_elements_for_id = 0;
for (const attr of this.idAttributes) {
const tmp_elemXpath = `//*[@*[local-name(.)='${attr}']='${uri}']`;
const tmp_elem = xpath.select(tmp_elemXpath, doc);
if (utils.isArrayHasLength(tmp_elem)) {
num_elements_for_id += tmp_elem.length;
elem = tmp_elem;
elemXpath = tmp_elemXpath;
}
}
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.");
}
ref.xpath = elemXpath;
}
// Note, we are using the last found element from the loop above
if (!utils.isArrayHasLength(elem)) {
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;
}
const canonXml = this.getCanonReferenceXml(doc, ref, elem[0]);
const hash = this.findHashAlgorithm(ref.digestAlgorithm);
const digest = hash.getHash(canonXml);
if (!utils.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}`);
return false;
}
}
return true;
}
}
return true;
};
function validateDigestValue(digest, expectedDigest) {
var buffer, expectedBuffer;
var majorVersion = /^v(\d+)/.exec(process.version)[1];
if (+majorVersion >= 6) {
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;
/**
* Loads the signature information from the provided XML node or string.
*
* @param signatureNode The XML node or string representing the signature.
*/
loadSignature(signatureNode) {
if (typeof signatureNode === "string") {
this.signatureNode = signatureNode = new xmldom_1.DOMParser().parseFromString(signatureNode);
}
else {
this.signatureNode = signatureNode;
}
this.signatureXml = signatureNode.toString();
const nodes = xpath.select(".//*[local-name(.)='CanonicalizationMethod']/@Algorithm", signatureNode);
if (!utils.isArrayHasLength(nodes)) {
throw new Error("could not find CanonicalizationMethod/@Algorithm element");
}
if (xpath.isAttribute(nodes[0])) {
this.canonicalizationAlgorithm = nodes[0].value;
}
const signatureAlgorithm = xpath.select1(".//*[local-name(.)='SignatureMethod']/@Algorithm", signatureNode);
if (xpath.isAttribute(signatureAlgorithm)) {
this.signatureAlgorithm = signatureAlgorithm.value;
}
this.references = [];
const references = xpath.select(".//*[local-name(.)='SignedInfo']/*[local-name(.)='Reference']", signatureNode);
if (!utils.isArrayHasLength(references)) {
throw new Error("could not find any Reference elements");
}
for (const reference of references) {
this.loadReference(reference);
}
const signatureValue = xpath.select1(".//*[local-name(.)='SignatureValue']/text()", signatureNode);
if (xpath.isTextNode(signatureValue)) {
this.signatureValue = signatureValue.data.replace(/\r?\n/g, "");
}
const keyInfo = xpath.select1(".//*[local-name(.)='KeyInfo']", signatureNode);
// TODO: should this just be a single return instead of an array that we always take the first entry of?
if (xpath.isNodeLike(keyInfo)) {
this.keyInfo = keyInfo;
}
}
}
return true;
}
SignedXml.prototype.loadSignature = function (signatureNode) {
if (typeof signatureNode === "string") {
this.signatureNode = signatureNode = new Dom().parseFromString(signatureNode);
} else {
this.signatureNode = signatureNode;
}
this.signatureXml = signatureNode.toString();
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.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");
for (var i in references) {
if (!references.hasOwnProperty(i)) continue;
this.loadReference(references[i]);
}
this.signatureValue = utils
.findFirst(signatureNode, ".//*[local-name(.)='SignatureValue']/text()")
.data.replace(/\r?\n/g, "");
this.keyInfo = xpath.select(".//*[local-name(.)='KeyInfo']", signatureNode);
};
/**
* Load the reference xml node to a model
*
*/
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;
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 transforms = [];
var inclusiveNamespacesPrefixList;
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 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++) {
if (inclusiveNamespacesPrefixList) {
inclusiveNamespacesPrefixList =
inclusiveNamespacesPrefixList + " " + inclusiveNamespaces[i].getAttribute("PrefixList");
} else {
inclusiveNamespacesPrefixList = inclusiveNamespaces[i].getAttribute("PrefixList");
/**
* Load the reference xml node to a model
*
*/
loadReference(ref) {
var _a;
let nodes = utils.findChilds(ref, "DigestMethod");
if (nodes.length === 0) {
throw new Error(`could not find DigestMethod in reference ${ref.toString()}`);
}
}
const digestAlgoNode = nodes[0];
const attr = utils.findAttr(digestAlgoNode, "Algorithm");
if (!attr) {
throw new Error(`could not find Algorithm attribute in node ${digestAlgoNode.toString()}`);
}
const digestAlgo = attr.value;
nodes = utils.findChilds(ref, "DigestValue");
if (nodes.length === 0) {
throw new Error(`could not find DigestValue node in reference ${ref.toString()}`);
}
const firstChild = nodes[0].firstChild;
if (!firstChild || !("data" in firstChild)) {
throw new Error(`could not find the value of DigestValue in ${nodes[0].toString()}`);
}
const digestValue = firstChild.data;
const transforms = [];
let inclusiveNamespacesPrefixList = [];
nodes = utils.findChilds(ref, "Transforms");
if (nodes.length !== 0) {
const transformsNode = nodes[0];
const transformsAll = utils.findChilds(transformsNode, "Transform");
for (const transform of transformsAll) {
const transformAttr = utils.findAttr(transform, "Algorithm");
if (transformAttr) {
transforms.push(transformAttr.value);
}
}
// This is a little strange, we are looking for children of the last child of `transformsNode`
const inclusiveNamespaces = utils.findChilds(transformsAll[transformsAll.length - 1], "InclusiveNamespaces");
if (utils.isArrayHasLength(inclusiveNamespaces)) {
// Should really only be one prefix list, but maybe there's some circumstances where more than one to let's handle it
inclusiveNamespacesPrefixList = inclusiveNamespaces
.flatMap((namespace) => { var _a; return ((_a = namespace.getAttribute("PrefixList")) !== null && _a !== void 0 ? _a : "").split(" "); })
.filter((value) => value.length > 0);
}
if (utils.isArrayHasLength(this.implicitTransforms)) {
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");
}
this.addReference({
transforms,
digestAlgorithm: digestAlgo,
uri: (_a = utils.findAttr(ref, "URI")) === null || _a === void 0 ? void 0 : _a.value,
digestValue,
inclusiveNamespacesPrefixList,
isEmptyUri: false,
});
}
}
}
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");
}
this.addReference(
null,
transforms,
digestAlgo,
utils.findAttr(ref, "URI").value,
digestValue,
inclusiveNamespacesPrefixList,
false
);
};
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,
});
};
/**
* Compute the signature of the given xml (using the already defined settings)
*
* Options:
*
* - `prefix` {String} Adds a prefix for the generated signature tags
* - `attrs` {Object} A hash of attributes and values `attrName: value` to add to the signature root node
* - `location` {{ reference: String, action: String }}
* - `existingPrefixes` {Object} A hash of prefixes and namespaces `prefix: namespace` already in the xml
* An object with a `reference` key which should
* contain a XPath expression, an `action` key which
* should contain one of the following values:
* `append`, `prepend`, `before`, `after`
*
*/
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 parameter must be a callback function");
}
var doc = new Dom().parseFromString(xml),
xmlNsAttr = "xmlns",
signatureAttrs = [],
location,
attrs,
prefix,
currentPrefix;
var validActions = ["append", "prepend", "before", "after"];
opts = opts || {};
prefix = opts.prefix;
attrs = opts.attrs || {};
location = opts.location || {};
var existingPrefixes = opts.existingPrefixes || {};
this.namespaceResolver = {
lookupNamespaceURI: function (prefix) {
return existingPrefixes[prefix];
},
};
// defaults to the root node
location.reference = location.reference || "/*";
// defaults to append action
location.action = location.action || "append";
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(", ")
);
if (!callback) {
throw err;
} else {
callback(err, null);
return;
/**
* Adds a reference to the signature.
*
* @param xpath The XPath expression to select the XML nodes to be referenced.
* @param transforms An array of transform algorithms to be applied to the selected nodes. Defaults to ["http://www.w3.org/2001/10/xml-exc-c14n#"].
* @param digestAlgorithm The digest algorithm to use for computing the digest value. Defaults to "http://www.w3.org/2000/09/xmldsig#sha1".
* @param uri The URI identifier for the reference. If empty, an empty URI will be used.
* @param digestValue The expected digest value for the reference.
* @param inclusiveNamespacesPrefixList The prefix list for inclusive namespace canonicalization.
* @param isEmptyUri Indicates whether the URI is empty. Defaults to `false`.
*/
addReference({ xpath, transforms = ["http://www.w3.org/2001/10/xml-exc-c14n#"], digestAlgorithm = "http://www.w3.org/2000/09/xmldsig#sha1", uri = "", digestValue, inclusiveNamespacesPrefixList = [], isEmptyUri = false, }) {
this.references.push({
xpath,
transforms,
digestAlgorithm,
uri,
digestValue,
inclusiveNamespacesPrefixList,
isEmptyUri,
});
}
}
// automatic insertion of `:`
if (prefix) {
xmlNsAttr += ":" + prefix;
currentPrefix = prefix + ":";
} else {
currentPrefix = "";
}
Object.keys(attrs).forEach(function (name) {
if (name !== "xmlns" && name !== xmlNsAttr) {
signatureAttrs.push(name + '="' + attrs[name] + '"');
computeSignature(xml, options, callbackParam) {
let callback;
if (typeof options === "function" && callbackParam == null) {
callback = options;
options = {};
}
else {
callback = callbackParam;
options = (options !== null && options !== void 0 ? options : {});
}
const doc = new xmldom_1.DOMParser().parseFromString(xml);
let xmlNsAttr = "xmlns";
const signatureAttrs = [];
let currentPrefix;
const validActions = ["append", "prepend", "before", "after"];
const prefix = options.prefix;
const attrs = options.attrs || {};
const location = options.location || {};
const existingPrefixes = options.existingPrefixes || {};
this.namespaceResolver = {
lookupNamespaceURI: function (prefix) {
return prefix ? existingPrefixes[prefix] : null;
},
};
// defaults to the root node
location.reference = location.reference || "/*";
// defaults to append action
location.action = location.action || "append";
if (validActions.indexOf(location.action) === -1) {
const 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);
return;
}
}
// automatic insertion of `:`
if (prefix) {
xmlNsAttr += `:${prefix}`;
currentPrefix = `${prefix}:`;
}
else {
currentPrefix = "";
}
Object.keys(attrs).forEach(function (name) {
if (name !== "xmlns" && name !== xmlNsAttr) {
signatureAttrs.push(`${name}="${attrs[name]}"`);
}
});
// add the xml namespace attribute
signatureAttrs.push(`${xmlNsAttr}="http://www.w3.org/2000/09/xmldsig#"`);
let signatureXml = `<${currentPrefix}Signature ${signatureAttrs.join(" ")}>`;
signatureXml += this.createSignedInfo(doc, prefix);
signatureXml += this.getKeyInfo(prefix);
signatureXml += `</${currentPrefix}Signature>`;
this.originalXmlWithIds = doc.toString();
let existingPrefixesString = "";
Object.keys(existingPrefixes).forEach(function (key) {
existingPrefixesString += `xmlns:${key}="${existingPrefixes[key]}" `;
});
// A trick to remove the namespaces that already exist in the xml
// This only works if the prefix and namespace match with those in the xml
const dummySignatureWrapper = `<Dummy ${existingPrefixesString}>${signatureXml}</Dummy>`;
const nodeXml = new xmldom_1.DOMParser().parseFromString(dummySignatureWrapper);
// Because we are using a dummy wrapper hack described above, we know there will be a `firstChild`
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const signatureDoc = nodeXml.documentElement.firstChild;
const referenceNode = xpath.select1(location.reference, doc);
if (!xpath.isNodeLike(referenceNode)) {
const err2 = new Error(`the following xpath cannot be used because it was not found: ${location.reference}`);
if (!callback) {
throw err2;
}
else {
callback(err2);
return;
}
}
if (location.action === "append") {
referenceNode.appendChild(signatureDoc);
}
else if (location.action === "prepend") {
referenceNode.insertBefore(signatureDoc, referenceNode.firstChild);
}
else if (location.action === "before") {
if (referenceNode.parentNode == null) {
throw new Error("`location.reference` refers to the root node (by default), so we can't insert `before`");
}
referenceNode.parentNode.insertBefore(signatureDoc, referenceNode);
}
else if (location.action === "after") {
if (referenceNode.parentNode == null) {
throw new Error("`location.reference` refers to the root node (by default), so we can't insert `after`");
}
referenceNode.parentNode.insertBefore(signatureDoc, referenceNode.nextSibling);
}
this.signatureNode = signatureDoc;
const signedInfoNodes = utils.findChilds(this.signatureNode, "SignedInfo");
if (signedInfoNodes.length === 0) {
const err3 = new Error("could not find SignedInfo element in the message");
if (!callback) {
throw err3;
}
else {
callback(err3);
return;
}
}
const signedInfoNode = signedInfoNodes[0];
if (typeof callback === "function") {
// Asynchronous flow
this.calculateSignatureValue(doc, (err, signature) => {
if (err) {
callback(err);
}
else {
this.signatureValue = signature || "";
signatureDoc.insertBefore(this.createSignature(prefix), signedInfoNode.nextSibling);
this.signatureXml = signatureDoc.toString();
this.signedXml = doc.toString();
callback(null, this);
}
});
}
else {
// Synchronous flow
this.calculateSignatureValue(doc);
signatureDoc.insertBefore(this.createSignature(prefix), signedInfoNode.nextSibling);
this.signatureXml = signatureDoc.toString();
this.signedXml = doc.toString();
}
}
});
// add the xml namespace attribute
signatureAttrs.push(xmlNsAttr + '="http://www.w3.org/2000/09/xmldsig#"');
var signatureXml = "<" + currentPrefix + "Signature " + signatureAttrs.join(" ") + ">";
signatureXml += this.createSignedInfo(doc, prefix);
signatureXml += this.getKeyInfo(prefix);
signatureXml += "</" + currentPrefix + "Signature>";
this.originalXmlWithIds = doc.toString();
var existingPrefixesString = "";
Object.keys(existingPrefixes).forEach(function (key) {
existingPrefixesString += "xmlns:" + key + '="' + existingPrefixes[key] + '" ';
});
// A trick to remove the namespaces that already exist in the xml
// This only works if the prefix and namespace match with those in te xml
var dummySignatureWrapper = "<Dummy " + existingPrefixesString + ">" + signatureXml + "</Dummy>";
var nodeXml = new Dom().parseFromString(dummySignatureWrapper);
var signatureDoc = nodeXml.documentElement.firstChild;
var referenceNode = xpath.select(location.reference, doc);
if (!referenceNode || referenceNode.length === 0) {
var err2 = new Error(
"the following xpath cannot be used because it was not found: " + location.reference
);
if (!callback) {
throw err2;
} else {
callback(err2, null);
return;
getKeyInfo(prefix) {
const currentPrefix = prefix ? `${prefix}:` : "";
let keyInfoAttrs = "";
if (this.keyInfoAttributes) {
Object.keys(this.keyInfoAttributes).forEach((name) => {
keyInfoAttrs += ` ${name}="${this.keyInfoAttributes[name]}"`;
});
}
const keyInfoContent = this.getKeyInfoContent({ publicCert: this.publicCert, prefix });
if (keyInfoAttrs || keyInfoContent) {
return `<${currentPrefix}KeyInfo${keyInfoAttrs}>${keyInfoContent}</${currentPrefix}KeyInfo>`;
}
return "";
}
}
referenceNode = referenceNode[0];
if (location.action === "append") {
referenceNode.appendChild(signatureDoc);
} else if (location.action === "prepend") {
referenceNode.insertBefore(signatureDoc, referenceNode.firstChild);
} else if (location.action === "before") {
referenceNode.parentNode.insertBefore(signatureDoc, referenceNode);
} else if (location.action === "after") {
referenceNode.parentNode.insertBefore(signatureDoc, referenceNode.nextSibling);
}
this.signatureNode = signatureDoc;
var signedInfoNode = utils.findChilds(this.signatureNode, "SignedInfo");
if (signedInfoNode.length == 0) {
var err3 = new Error("could not find SignedInfo element in the message");
if (!callback) {
throw err3;
} else {
callback(err3);
return;
/**
* Generate the Reference nodes (as part of the signature process)
*
*/
createReferences(doc, prefix) {
var _a;
let res = "";
prefix = prefix || "";
prefix = prefix ? `${prefix}:` : prefix;
for (const ref of this.references) {
const nodes = xpath.selectWithResolver((_a = ref.xpath) !== null && _a !== void 0 ? _a : "", doc, this.namespaceResolver);
if (!utils.isArrayHasLength(nodes)) {
throw new Error(`the following xpath cannot be signed because it was not found: ${ref.xpath}`);
}
for (const node of nodes) {
if (ref.isEmptyUri) {
res += `<${prefix}Reference URI="">`;
}
else {
const id = this.ensureHasId(node);
ref.uri = id;
res += `<${prefix}Reference URI="#${id}">`;
}
res += `<${prefix}Transforms>`;
for (const trans of ref.transforms || []) {
const transform = this.findCanonicalizationAlgorithm(trans);
res += `<${prefix}Transform Algorithm="${transform.getAlgorithmName()}"`;
if (utils.isArrayHasLength(ref.inclusiveNamespacesPrefixList)) {
res += ">";
res += `<InclusiveNamespaces PrefixList="${ref.inclusiveNamespacesPrefixList.join(" ")}" xmlns="${transform.getAlgorithmName()}"/>`;
res += `</${prefix}Transform>`;
}
else {
res += " />";
}
}
const canonXml = this.getCanonReferenceXml(doc, ref, node);
const digestAlgorithm = this.findHashAlgorithm(ref.digestAlgorithm);
res +=
`</${prefix}Transforms>` +
`<${prefix}DigestMethod Algorithm="${digestAlgorithm.getAlgorithmName()}" />` +
`<${prefix}DigestValue>${digestAlgorithm.getHash(canonXml)}</${prefix}DigestValue>` +
`</${prefix}Reference>`;
}
}
return res;
}
}
signedInfoNode = signedInfoNode[0];
if (!callback) {
//Synchronous flow
this.calculateSignatureValue(doc);
signatureDoc.insertBefore(this.createSignature(prefix), signedInfoNode.nextSibling);
this.signatureXml = signatureDoc.toString();
this.signedXml = doc.toString();
} else {
var self = this;
//Asynchronous flow
this.calculateSignatureValue(doc, function (err, signature) {
if (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);
}
});
}
};
SignedXml.prototype.getKeyInfo = function (prefix) {
var res = "";
var currentPrefix;
currentPrefix = prefix || "";
currentPrefix = currentPrefix ? currentPrefix + ":" : currentPrefix;
if (this.keyInfoProvider) {
var keyInfoAttrs = "";
if (this.keyInfoProvider.attrs) {
Object.keys(this.keyInfoProvider.attrs).forEach((name) => {
keyInfoAttrs += " " + name + '="' + this.keyInfoProvider.attrs[name] + '"';
});
getCanonXml(transforms, node, options) {
var _a;
options = options || {};
options.defaultNsForPrefix = (_a = options.defaultNsForPrefix) !== null && _a !== void 0 ? _a : SignedXml.defaultNsForPrefix;
options.signatureNode = this.signatureNode;
let canonXml = node.cloneNode(true); // Deep clone
transforms.forEach((transformName) => {
const transform = this.findCanonicalizationAlgorithm(transformName);
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 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.
//enveloped transformation returns Node since if it would return String consider this case:
//<x xmlns:p='ns'><p:y/></x>
//if only y is the node to sign then a string would be <p:y/> without the definition of the p namespace. probably xmldom toString() should have added it.
});
return canonXml.toString();
}
res += "<" + currentPrefix + "KeyInfo" + keyInfoAttrs + ">";
res += this.keyInfoProvider.getKeyInfo(this.signingCert || this.signingKey, prefix);
res += "</" + currentPrefix + "KeyInfo>";
}
return res;
};
/**
* Generate the Reference nodes (as part of the signature process)
*
*/
SignedXml.prototype.createReferences = function (doc, prefix) {
var res = "";
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);
if (nodes.length == 0) {
throw new Error(
"the following xpath cannot be signed because it was not found: " + ref.xpath
);
/**
* Ensure an element has Id attribute. If not create it with unique value.
* Work with both normal and wssecurity Id flavour
*/
ensureHasId(node) {
let attr;
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 {
this.idAttributes.some((idAttribute) => {
attr = utils.findAttr(node, idAttribute);
return !!attr; // This will break the loop as soon as a truthy attr is found.
});
}
if (attr) {
return attr.value;
}
//add the attribute
const id = `_${this.id++}`;
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", "wsu:Id", id);
}
else {
node.setAttribute("Id", id);
}
return id;
}
for (var h in nodes) {
if (!nodes.hasOwnProperty(h)) continue;
var node = nodes[h];
if (ref.isEmptyUri) {
res += "<" + prefix + 'Reference URI="">';
} else {
var id = this.ensureHasId(node);
ref.uri = id;
res += "<" + prefix + 'Reference URI="#' + id + '">';
}
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() + '"';
if (ref.inclusiveNamespacesPrefixList) {
res += ">";
res +=
'<InclusiveNamespaces PrefixList="' +
ref.inclusiveNamespacesPrefixList +
'" xmlns="' +
transform.getAlgorithmName() +
'"/>';
res += "</" + prefix + "Transform>";
} else {
res += " />";
/**
* Create the SignedInfo element
*
*/
createSignedInfo(doc, prefix) {
const transform = this.findCanonicalizationAlgorithm(this.canonicalizationAlgorithm);
const algo = this.findSignatureAlgorithm(this.signatureAlgorithm);
let currentPrefix;
currentPrefix = prefix || "";
currentPrefix = currentPrefix ? `${currentPrefix}:` : currentPrefix;
let res = `<${currentPrefix}SignedInfo>`;
res += `<${currentPrefix}CanonicalizationMethod Algorithm="${transform.getAlgorithmName()}"`;
if (utils.isArrayHasLength(this.inclusiveNamespacesPrefixList)) {
res += ">";
res += `<InclusiveNamespaces PrefixList="${this.inclusiveNamespacesPrefixList.join(" ")}" xmlns="${transform.getAlgorithmName()}"/>`;
res += `</${currentPrefix}CanonicalizationMethod>`;
}
}
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>";
else {
res += " />";
}
res += `<${currentPrefix}SignatureMethod Algorithm="${algo.getAlgorithmName()}" />`;
res += this.createReferences(doc, prefix);
res += `</${currentPrefix}SignedInfo>`;
return res;
}
}
return res;
};
SignedXml.prototype.getCanonXml = function (transforms, node, options) {
options = options || {};
options.defaultNsForPrefix = options.defaultNsForPrefix || SignedXml.defaultNsForPrefix;
options.signatureNode = this.signatureNode;
var canonXml = node.cloneNode(true); // Deep clone
for (var t in transforms) {
if (!transforms.hasOwnProperty(t)) continue;
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 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.
//enveloped transformation returns Node since if it would return String consider this case:
//<x xmlns:p='ns'><p:y/></x>
//if only y is the node to sign then a string would be <p:y/> without the definition of the p namespace. probably xmldom toString() should have added it.
}
return canonXml.toString();
};
/**
* Ensure an element has Id attribute. If not create it with unique value.
* Work with both normal and wssecurity Id flavour
*/
SignedXml.prototype.ensureHasId = function (node) {
var attr;
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 {
for (var index in this.idAttributes) {
if (!this.idAttributes.hasOwnProperty(index)) continue;
attr = utils.findAttr(node, this.idAttributes[index], null);
if (attr) break;
/**
* Create the Signature element
*
*/
createSignature(prefix) {
let xmlNsAttr = "xmlns";
if (prefix) {
xmlNsAttr += `:${prefix}`;
prefix += ":";
}
else {
prefix = "";
}
const 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.
const dummySignatureWrapper = `<${prefix}Signature ${xmlNsAttr}="http://www.w3.org/2000/09/xmldsig#">${signatureValueXml}</${prefix}Signature>`;
const doc = new xmldom_1.DOMParser().parseFromString(dummySignatureWrapper);
// Because we are using a dummy wrapper hack described above, we know there will be a `firstChild`
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return doc.documentElement.firstChild;
}
}
if (attr) return attr.value;
//add the attribute
var id = "_" + this.id++;
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",
"wsu:Id",
id
);
} else {
node.setAttribute("Id", id);
}
return id;
/**
* Returns just the signature part, must be called only after {@link computeSignature}
*
* @returns The signature XML.
*/
getSignatureXml() {
return this.signatureXml;
}
/**
* Returns the original xml with Id attributes added on relevant elements (required for validation), must be called only after {@link computeSignature}
*
* @returns The original XML with IDs.
*/
getOriginalXmlWithIds() {
return this.originalXmlWithIds;
}
/**
* Returns the original xml document with the signature in it, must be called only after {@link computeSignature}
*
* @returns The signed XML.
*/
getSignedXml() {
return this.signedXml;
}
}
exports.SignedXml = SignedXml;
SignedXml.defaultNsForPrefix = {
ds: "http://www.w3.org/2000/09/xmldsig#",
};
/**
* Create the SignedInfo element
*
*/
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;
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;
};
/**
* Create the Signature element
*
*/
SignedXml.prototype.createSignature = function (prefix) {
var xmlNsAttr = "xmlns";
if (prefix) {
xmlNsAttr += ":" + prefix;
prefix += ":";
} else {
prefix = "";
}
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 doc = new Dom().parseFromString(dummySignatureWrapper);
return doc.documentElement.firstChild;
};
SignedXml.prototype.getSignatureXml = function () {
return this.signatureXml;
};
SignedXml.prototype.getOriginalXmlWithIds = function () {
return this.originalXmlWithIds;
};
SignedXml.prototype.getSignedXml = function () {
return this.signedXml;
};
//# sourceMappingURL=signed-xml.js.map

@@ -1,84 +0,238 @@

var select = require("xpath").select;
function findAttr(node, localName, namespace) {
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;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateDigestValue = exports.findAncestorNs = exports.derToPem = exports.pemToDer = exports.normalizePem = exports.BASE64_REGEX = exports.EXTRACT_X509_CERTS = exports.PEM_FORMAT_REGEX = exports.encodeSpecialCharactersInText = exports.encodeSpecialCharactersInAttribute = exports.findChilds = exports.findAttr = exports.isArrayHasLength = void 0;
const xpath = require("xpath");
function isArrayHasLength(array) {
return Array.isArray(array) && array.length > 0;
}
exports.isArrayHasLength = isArrayHasLength;
function attrEqualsExplicitly(attr, localName, namespace) {
return attr.localName === localName && (attr.namespaceURI === namespace || namespace == null);
}
function attrEqualsImplicitly(attr, localName, namespace, node) {
return (attr.localName === localName &&
((!attr.namespaceURI && (node === null || node === void 0 ? void 0 : node.namespaceURI) === namespace) || namespace == null));
}
function findAttr(element, localName, namespace) {
for (let i = 0; i < element.attributes.length; i++) {
const attr = element.attributes[i];
if (attrEqualsExplicitly(attr, localName, namespace) ||
attrEqualsImplicitly(attr, localName, namespace, element)) {
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];
}
exports.findAttr = findAttr;
function findChilds(node, localName, namespace) {
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 _a;
const element = (_a = node.documentElement) !== null && _a !== void 0 ? _a : node;
const res = [];
for (let i = 0; i < element.childNodes.length; i++) {
const child = element.childNodes[i];
if (xpath.isElement(child) &&
child.localName === localName &&
(child.namespaceURI === namespace || namespace == null)) {
res.push(child);
}
}
}
return res;
return res;
}
function attrEqualsExplicitly(attr, localName, 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)
);
}
var xml_special_to_encoded_attribute = {
"&": "&amp;",
"<": "&lt;",
'"': "&quot;",
"\r": "&#xD;",
"\n": "&#xA;",
"\t": "&#x9;",
exports.findChilds = findChilds;
const xml_special_to_encoded_attribute = {
"&": "&amp;",
"<": "&lt;",
'"': "&quot;",
"\r": "&#xD;",
"\n": "&#xA;",
"\t": "&#x9;",
};
var xml_special_to_encoded_text = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
"\r": "&#xD;",
const xml_special_to_encoded_text = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
"\r": "&#xD;",
};
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];
});
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];
});
}
exports.encodeSpecialCharactersInAttribute = encodeSpecialCharactersInAttribute;
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];
});
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;
/**
* PEM format has wide range of usages, but this library
* is enforcing RFC7468 which focuses on PKIX, PKCS and CMS.
*
* https://www.rfc-editor.org/rfc/rfc7468
*
* PEM_FORMAT_REGEX is validating given PEM file against RFC7468 'stricttextualmsg' definition.
*
* With few exceptions;
* - 'posteb' MAY have 'eol', but it is not mandatory.
* - 'preeb' and 'posteb' lines are limited to 64 characters, but
* should not cause any issues in context of PKIX, PKCS and CMS.
*/
exports.PEM_FORMAT_REGEX = new RegExp("^-----BEGIN [A-Z\x20]{1,48}-----([^-]*)-----END [A-Z\x20]{1,48}-----$", "s");
exports.EXTRACT_X509_CERTS = new RegExp("-----BEGIN CERTIFICATE-----[^-]*-----END CERTIFICATE-----", "g");
exports.BASE64_REGEX = new RegExp("^(?:[A-Za-z0-9\\+\\/]{4}\\n{0,1})*(?:[A-Za-z0-9\\+\\/]{2}==|[A-Za-z0-9\\+\\/]{3}=)?$", "s");
/**
* -----BEGIN [LABEL]-----
* base64([DATA])
* -----END [LABEL]-----
*
* Above is shown what PEM file looks like. As can be seen, base64 data
* can be in single line or multiple lines.
*
* This function normalizes PEM presentation to;
* - contain PEM header and footer as they are given
* - normalize line endings to '\n'
* - normalize line length to maximum of 64 characters
* - ensure that 'preeb' has line ending '\n'
*
* With a couple of notes:
* - 'eol' is normalized to '\n'
*
* @param pem The PEM string to normalize to RFC7468 'stricttextualmsg' definition
*/
function normalizePem(pem) {
var _a;
return `${((_a = pem
.trim()
.replace(/(\r\n|\r)/g, "\n")
.match(/.{1,64}/g)) !== null && _a !== void 0 ? _a : []).join("\n")}\n`;
}
exports.normalizePem = normalizePem;
/**
* @param pem The PEM-encoded base64 certificate to strip headers from
*/
function pemToDer(pem) {
return pem
.replace(/(\r\n|\r)/g, "\n")
.replace(/-----BEGIN [A-Z\x20]{1,48}-----\n?/, "")
.replace(/-----END [A-Z\x20]{1,48}-----\n?/, "");
}
exports.pemToDer = pemToDer;
/**
* @param der The DER-encoded base64 certificate to add PEM headers too
* @param pemLabel The label of the header and footer to add
*/
function derToPem(der, pemLabel) {
const base64Der = Buffer.isBuffer(der) ? der.toString("latin1").trim() : der.trim();
if (exports.PEM_FORMAT_REGEX.test(base64Der)) {
return normalizePem(base64Der);
}
if (exports.BASE64_REGEX.test(base64Der)) {
const pem = `-----BEGIN ${pemLabel}-----\n${base64Der}\n-----END ${pemLabel}-----`;
return normalizePem(pem);
}
throw new Error("Unknown DER format.");
}
exports.derToPem = derToPem;
function collectAncestorNamespaces(node, nsArray = []) {
if (!xpath.isElement(node.parentNode)) {
return nsArray;
}
const parent = node.parentNode;
if (!parent) {
return nsArray;
}
if (parent.attributes && parent.attributes.length > 0) {
for (let i = 0; i < parent.attributes.length; i++) {
const 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);
}
function findNSPrefix(subset) {
const subsetAttributes = subset.attributes;
for (let k = 0; k < subsetAttributes.length; k++) {
const nodeName = subsetAttributes[k].nodeName;
if (nodeName.search(/^xmlns:?/) !== -1) {
return nodeName.replace(/^xmlns:?/, "");
}
}
return subset.prefix || "";
}
function isElementSubset(docSubset) {
return docSubset.every((node) => xpath.isElement(node));
}
/**
* 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
* @param {object} namespaceResolver - xpath namespace resolver
* @returns {Array} i.e. [{prefix: "saml", namespaceURI: "urn:oasis:names:tc:SAML:2.0:assertion"}]
*/
function findAncestorNs(doc, docSubsetXpath, namespaceResolver) {
const docSubset = xpath.selectWithResolver(docSubsetXpath, doc, namespaceResolver);
if (!isArrayHasLength(docSubset)) {
return [];
}
if (!isElementSubset(docSubset)) {
throw new Error("Document subset must be list of elements");
}
// Remove duplicate on ancestor namespace
const ancestorNs = collectAncestorNamespaces(docSubset[0]);
const ancestorNsWithoutDuplicate = [];
for (let i = 0; i < ancestorNs.length; i++) {
let notOnTheList = true;
for (const 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
const returningNs = [];
const subsetNsPrefix = findNSPrefix(docSubset[0]);
for (const ancestorNs of ancestorNsWithoutDuplicate) {
if (ancestorNs.prefix !== subsetNsPrefix) {
returningNs.push(ancestorNs);
}
}
return returningNs;
}
exports.findAncestorNs = findAncestorNs;
function validateDigestValue(digest, expectedDigest) {
const buffer = Buffer.from(digest, "base64");
const expectedBuffer = Buffer.from(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 (let i = 0; i < buffer.length; i++) {
if (buffer[i] !== expectedBuffer[i]) {
return false;
}
}
return true;
}
exports.validateDigestValue = validateDigestValue;
//# sourceMappingURL=utils.js.map
{
"name": "xml-crypto",
"version": "3.2.0",
"version": "4.0.0",
"private": false,

@@ -22,16 +22,14 @@ "description": "Xml digital signature and encryption library for Node.js",

],
"main": "index.js",
"types": "index.d.ts",
"main": "./lib",
"files": [
"CHANGELOG.md",
"index.d.ts",
"lib"
"lib",
"LICENSE",
"README.md"
],
"directories": {
"lib": "lib"
},
"scripts": {
"build": "npx tsc",
"changelog": "gren changelog --override --generate",
"lint": "eslint --ext .js \"**/*.js\" --cache && npm run prettier-check",
"lint:fix": "eslint --ext .js --fix \"**/*.js\" && npm run prettier-format",
"lint": "eslint --ext .ts \"{src,test}/*.ts\" --cache && npm run prettier-check",
"lint:fix": "eslint --ext .ts --fix \"{src,test}/*.ts\" && npm run prettier-format",
"prepare": "tsc",
"prettier-check": "prettier --config .prettierrc.json --check .",

@@ -41,22 +39,36 @@ "prettier-format": "prettier --config .prettierrc.json --write .",

"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"
"test": "nyc mocha"
},
"dependencies": {
"@xmldom/xmldom": "^0.8.8",
"xpath": "0.0.32"
"@xmldom/xmldom": "^0.8.10",
"xpath": "0.0.33"
},
"devDependencies": {
"@cjbarth/github-release-notes": "^4.1.0",
"@prettier/plugin-xml": "^2.2.0",
"ejs": "3.1.9",
"eslint": "^8.42.0",
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@prettier/plugin-xml": "^3.1.1",
"@types/chai": "^4.3.5",
"@types/mocha": "^10.0.1",
"@types/node": "^20.3.2",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"chai": "^4.3.7",
"choma": "^1.2.1",
"ejs": "^3.1.9",
"eslint": "^8.45.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"
"eslint-plugin-deprecation": "^1.4.1",
"lcov": "^1.16.0",
"mocha": "^10.2.0",
"nyc": "^15.1.0",
"prettier": "^3.0.0",
"prettier-plugin-packagejson": "^2.4.5",
"release-it": "^16.1.3",
"source-map-support": "^0.5.21",
"ts-node": "^10.9.1",
"typescript": "^5.1.6"
},
"engines": {
"node": ">=4.0.0"
"node": ">=14"
}
}

@@ -46,8 +46,4 @@ # xml-crypto

to enable HMAC-SHA1, do:
to enable HMAC-SHA1, call `enableHMAC()` on your instance of `SignedXml`.
```javascript
require("xml-crypto").SignedXml.enableHMAC();
```
This will enable HMAC and disable digital signature algorithms. Due to key

@@ -69,8 +65,8 @@ confusion issues, it is risky to have both HMAC-based and public key digital

When signing a xml document you can specify the following properties on a `SignedXml` instance to customize the signature process:
When signing a xml document you can pass the following options to the `SignedXml` constructor to customize the signature process:
- `sign.signingKey` - **[required]** a `Buffer` or pem encoded `String` containing your private key
- `sign.keyInfoProvider` - **[optional]** a key info provider instance, see [customizing algorithms](#customizing-algorithms) for an implementation example
- `sign.signatureAlgorithm` - **[optional]** one of the supported [signature algorithms](#signature-algorithms). Ex: `sign.signatureAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"`
- `sign.canonicalizationAlgorithm` - **[optional]** one of the supported [canonicalization algorithms](#canonicalization-and-transformation-algorithms). Ex: `sign.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#WithComments"`
- `privateKey` - **[required]** a `Buffer` or pem encoded `String` containing your private key
- `publicCert` - **[optional]** a `Buffer` or pem encoded `String` containing your public key
- `signatureAlgorithm` - **[optional]** one of the supported [signature algorithms](#signature-algorithms). Ex: `sign.signatureAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"`
- `canonicalizationAlgorithm` - **[optional]** one of the supported [canonicalization algorithms](#canonicalization-and-transformation-algorithms). Ex: `sign.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#WithComments"`

@@ -85,5 +81,4 @@ Use this code:

var sig = new SignedXml();
sig.addReference("//*[local-name(.)='book']");
sig.signingKey = fs.readFileSync("client.pem");
var sig = new SignedXml({ privateKey: fs.readFileSync("client.pem") });
sig.addReference({ xpath: "//*[local-name(.)='book']" });
sig.computeSignature(xml);

@@ -119,14 +114,18 @@ fs.writeFileSync("signed.xml", sig.getSignedXml());

To generate a `<X509Data></X509Data>` element in the signature you must provide a key info implementation, see [customizing algorithms](#customizing-algorithms) for an example.
If you set the `publicCert` property, a `<X509Data></X509Data>` element with the public certificate will be generated in the signature.
To customize this see [customizing algorithms](#customizing-algorithms) for an example.
## Verifying Xml documents
When verifying a xml document you must specify the following properties on a ``SignedXml` instance:
When verifying a xml document you can pass the following options to the `SignedXml` constructor to customize the verify process:
- `sign.keyInfoProvider` - **[required]** a key info provider instance containing your certificate, see [customizing algorithms](#customizing-algorithms) for an implementation example
- `publicCert` - **[optional]** your certificate as a string, a string of multiple certs in PEM format, or a Buffer
- `privateKey` - **[optional]** your private key as a string or a Buffer - used for verifying symmetrical signatures (HMAC)
You can use any dom parser you want in your code (or none, depending on your usage). This sample uses [xmldom](https://github.com/jindw/xmldom) so you should install it first:
The certificate that will be used to check the signature will first be determined by calling `.getCertFromKeyInfo()`, which function you can customize as you see fit. If that returns `null`, then `publicCert` is used. If that is `null`, then `privateKey` is used (for symmetrical signing applications).
You can use any dom parser you want in your code (or none, depending on your usage). This sample uses [xmldom](https://github.com/xmldom/xmldom), so you should install it first:
```shell
npm install xmldom
npm install @xmldom/xmldom
```

@@ -140,3 +139,2 @@

SignedXml = require("xml-crypto").SignedXml,
FileKeyInfo = require("xml-crypto").FileKeyInfo,
fs = require("fs");

@@ -149,6 +147,5 @@

doc,
"//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']"
"//*[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");
var sig = new SignedXml({ publicCert: fs.readFileSync("client_public.pem") });
sig.loadSignature(signature);

@@ -159,3 +156,3 @@ var res = sig.checkSignature(xml);

if the verification process fails `sig.validationErrors` will have the errors.
If the verification process fails `sig.validationErrors` will contain the errors.

@@ -187,5 +184,7 @@ 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 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");
var options = {
implicitTransforms: ["http://www.w3.org/TR/2001/REC-xml-c14n-20010315"],
publicCert: fs.readFileSync("client_public.pem"),
};
var sig = new SignedXml(options);
sig.loadSignature(signature);

@@ -211,5 +210,15 @@ var res = sig.checkSignature(xml);

The `SignedXml` constructor provides an abstraction for sign and verify xml documents. The object is constructed using `new SignedXml([idMode])` where:
The `SignedXml` constructor provides an abstraction for sign and verify xml documents. The object is constructed using `new SignedXml(options?: SignedXmlOptions)` where the possible options are:
- `idMode` - if the value of `"wssecurity"` is passed it will create/validate id's with the ws-security namespace.
- `idMode` - default `null` - if the value of `wssecurity` is passed it will create/validate id's with the ws-security namespace.
- `idAttribute` - string - default `Id` or `ID` or `id` - the name of the attribute that contains the id of the element
- `privateKey` - string or Buffer - default `null` - the private key to use for signing
- `publicCert` - string or Buffer - default `null` - the public certificate to use for verifying
- `signatureAlgorithm` - string - default `http://www.w3.org/2000/09/xmldsig#rsa-sha1` - the signature algorithm to use
- `canonicalizationAlgorithm` - string - default `http://www.w3.org/TR/2001/REC-xml-c14n-20010315` - the canonicalization algorithm to use
- `inclusiveNamespacesPrefixList` - string - default `null` - a list of namespace prefixes to include during canonicalization
- `implicitTransforms` - string[] - default `[]` - a list of implicit transforms to use during verification
- `keyInfoAttributes` - object - default `{}` - a hash of attributes and values `attrName: value` to add to the KeyInfo node
- `getKeyInfoContent` - function - default `SignedXml.geTKeyInfoContent` - a function that returns the content of the KeyInfo node
- `getCertFromKeyInfo` - function - default `SignedXml.getCertFromKeyInfo` - a function that returns the certificate from the KeyInfo node

@@ -240,14 +249,6 @@ #### API

- `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 [xmldom](https://github.com/xmldom/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
### FileKeyInfo
A basic key info provider implementation using `fs.readFileSync(file)`, is constructed using `new FileKeyInfo([file])` where:
- `file` - a path to a pem encoded certificate
See [verifying xml documents](#verifying-xml-documents) for an example usage
## Customizing Algorithms

@@ -266,20 +267,11 @@

A key info provider is used to extract and construct the key and the KeyInfo xml section.
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.
To determine the inclusion and contents of a `<KeyInfo />` element, the function
`getKeyInfoContent()` is called. There is a default implementation of this. If you wish to change
this implementation, provide your own function assigned to the property `.getKeyInfoContent`. If
there are no attributes and no contents to the `<KeyInfo />` element, it won't be included in the
generated XML.
```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");
};
}
```
To specify custom attributes on `<KeyInfo />`, add the properties to the `.keyInfoAttributes` property.
A custom hash algorithm is used to calculate digests. Implement it if you want a hash other than the default SHA1.
A custom hash algorithm is used to calculate digests. Implement it if you want a hash other than the built-in methods.

@@ -298,3 +290,3 @@ ```javascript

A custom signing algorithm. The default is RSA-SHA1
A custom signing algorithm. The default is RSA-SHA1.

@@ -304,3 +296,3 @@ ```javascript

/*sign the given SignedInfo using the key. return base64 signature value*/
this.getSignature = function (signedInfo, signingKey) {
this.getSignature = function (signedInfo, privateKey) {
return "signature of signedInfo as base64...";

@@ -352,6 +344,6 @@ };

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;
```

@@ -363,16 +355,19 @@

function signXml(xml, xpath, key, dest) {
var sig = new SignedXml();
var options = {
publicCert: fs.readFileSync("my_public_cert.pem", "latin1"),
privateKey: fs.readFileSync(key),
/*configure the signature object to use the custom algorithms*/
signatureAlgorithm: "http://mySignatureAlgorithm",
canonicalizationAlgorithm: "http://MyCanonicalization",
};
/*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"
);
var sig = new SignedXml(options);
sig.signingKey = fs.readFileSync(key);
sig.addReference(xpath);
sig.addReference({
xpath: "//*[local-name(.)='x']",
transforms: ["http://MyTransformation"],
digestAlgorithm: "http://myDigestAlgorithm",
});
sig.addReference({ xpath });
sig.computeSignature(xml);

@@ -388,14 +383,14 @@ fs.writeFileSync(dest, sig.getSignedXml());

You can always look at the actual code as a sample (or drop me a [mail](mailto:yaronn01@gmail.com)).
You can always look at the actual code as a sample.
## Asynchronous signing and verification
If the private key is not stored locally and you wish to use a signing server or Hardware Security Module (HSM) to sign documents you can create a custom signing algorithm that uses an asynchronous callback.
If the private key is not stored locally, and you wish to use a signing server or Hardware Security Module (HSM) to sign documents, you can create a custom signing algorithm that uses an asynchronous callback.
```javascript
function AsyncSignatureAlgorithm() {
this.getSignature = function (signedInfo, signingKey, callback) {
this.getSignature = function (signedInfo, privateKey, callback) {
var signer = crypto.createSign("RSA-SHA1");
signer.update(signedInfo);
var res = signer.sign(signingKey, "base64");
var res = signer.sign(privateKey, "base64");
//Do some asynchronous things here

@@ -409,5 +404,4 @@ callback(null, res);

SignedXml.SignatureAlgorithms["http://asyncSignatureAlgorithm"] = AsyncSignatureAlgorithm;
var sig = new SignedXml();
sig.signatureAlgorithm = "http://asyncSignatureAlgorithm";
var sig = new SignedXml({ signatureAlgorithm: "http://asyncSignatureAlgorithm" });
sig.SignatureAlgorithms["http://asyncSignatureAlgorithm"] = AsyncSignatureAlgorithm;
sig.computeSignature(xml, opts, function (err) {

@@ -462,5 +456,4 @@ var signedResponse = sig.getSignedXml();

var sig = new SignedXml();
sig.addReference("//*[local-name(.)='book']");
sig.signingKey = fs.readFileSync("client.pem");
var sig = new SignedXml({ privateKey: fs.readFileSync("client.pem") });
sig.addReference({ xpath: "//*[local-name(.)='book']" });
sig.computeSignature(xml, {

@@ -487,5 +480,4 @@ prefix: "ds",

var sig = new SignedXml();
sig.addReference("//*[local-name(.)='book']");
sig.signingKey = fs.readFileSync("client.pem");
var sig = new SignedXml({ privateKey: fs.readFileSync("client.pem") });
sig.addReference({ xpath: "//*[local-name(.)='book']" });
sig.computeSignature(xml, {

@@ -500,4 +492,6 @@ location: { reference: "//*[local-name(.)='book']", action: "after" }, //This will place the signature after the book element

The test framework is [nodeunit](https://github.com/caolan/nodeunit). To run tests use:
The testing framework we use is [Mocha](https://github.com/mochajs/mocha) with [Chai](https://github.com/chaijs/chai) as the assertion framework.
To run tests use:
```shell

@@ -504,0 +498,0 @@ npm test

SocketSocket SOC 2 Logo

Product

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

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc