Commit inicial: fuentes MBS ERP (Genero 6) + .gitignore + docs/SETUP_PC_MBS.md
This commit is contained in:
+960
@@ -0,0 +1,960 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.SignedXml = void 0;
|
||||
const isDomNode = require("@xmldom/is-dom-node");
|
||||
const xmldom = require("@xmldom/xmldom");
|
||||
const util_1 = require("util");
|
||||
const xpath = require("xpath");
|
||||
const c14n = require("./c14n-canonicalization");
|
||||
const envelopedSignatures = require("./enveloped-signature");
|
||||
const execC14n = require("./exclusive-canonicalization");
|
||||
const hashAlgorithms = require("./hash-algorithms");
|
||||
const signatureAlgorithms = require("./signature-algorithms");
|
||||
const utils = require("./utils");
|
||||
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 = undefined;
|
||||
/**
|
||||
* Rules used to convert an XML document into its canonical form.
|
||||
*/
|
||||
this.canonicalizationAlgorithm = undefined;
|
||||
/**
|
||||
* 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
|
||||
this.id = 0;
|
||||
this.signedXml = "";
|
||||
this.signatureXml = "";
|
||||
this.signatureNode = null;
|
||||
this.signatureValue = "";
|
||||
this.originalXmlWithIds = "";
|
||||
this.keyInfo = null;
|
||||
/**
|
||||
* Contains the references that were signed.
|
||||
* @see {@link Reference}
|
||||
*/
|
||||
this.references = [];
|
||||
/**
|
||||
* Contains the canonicalized XML of the references that were validly signed.
|
||||
*
|
||||
* This populates with the canonical XML of the reference only after
|
||||
* verifying the signature is cryptographically authentic.
|
||||
*/
|
||||
this.signedReferences = [];
|
||||
/**
|
||||
* 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,
|
||||
};
|
||||
// TODO: In v7.x we may consider deprecating sha1
|
||||
/**
|
||||
* 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,
|
||||
};
|
||||
// TODO: In v7.x we may consider deprecating sha1
|
||||
/**
|
||||
* 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 ?? this.signatureAlgorithm;
|
||||
this.canonicalizationAlgorithm = canonicalizationAlgorithm;
|
||||
if (typeof inclusiveNamespacesPrefixList === "string") {
|
||||
this.inclusiveNamespacesPrefixList = inclusiveNamespacesPrefixList.split(" ");
|
||||
}
|
||||
else if (utils.isArrayHasLength(inclusiveNamespacesPrefixList)) {
|
||||
this.inclusiveNamespacesPrefixList = inclusiveNamespacesPrefixList;
|
||||
}
|
||||
this.implicitTransforms = implicitTransforms ?? this.implicitTransforms;
|
||||
this.keyInfoAttributes = keyInfoAttributes ?? this.keyInfoAttributes;
|
||||
this.getKeyInfoContent = getKeyInfoContent ?? this.getKeyInfoContent;
|
||||
this.getCertFromKeyInfo = getCertFromKeyInfo ?? SignedXml.noop;
|
||||
this.CanonicalizationAlgorithms;
|
||||
this.HashAlgorithms;
|
||||
this.SignatureAlgorithms;
|
||||
}
|
||||
/**
|
||||
* 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 = SignedXml.noop;
|
||||
}
|
||||
/**
|
||||
* 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) => `<${prefix}X509Certificate>${utils
|
||||
.pemToDer(c)
|
||||
.toString("base64")}</${prefix}X509Certificate>`)
|
||||
.join("");
|
||||
}
|
||||
return `<${prefix}X509Data>${x509Certs}</${prefix}X509Data>`;
|
||||
}
|
||||
/**
|
||||
* 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 cert = xpath.select1(".//*[local-name(.)='X509Certificate']", keyInfo);
|
||||
if (isDomNode.isNodeLike(cert)) {
|
||||
return utils.derToPem(cert.textContent ?? "", "CERTIFICATE");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
checkSignature(xml, callback) {
|
||||
if (callback != null && typeof callback !== "function") {
|
||||
throw new Error("Last parameter must be a callback function");
|
||||
}
|
||||
this.signedXml = xml;
|
||||
const doc = new xmldom.DOMParser().parseFromString(xml);
|
||||
// Reset the references as only references from our re-parsed signedInfo node can be trusted
|
||||
this.references = [];
|
||||
const unverifiedSignedInfoCanon = this.getCanonSignedInfoXml(doc);
|
||||
if (!unverifiedSignedInfoCanon) {
|
||||
if (callback) {
|
||||
callback(new Error("Canonical signed info cannot be empty"), false);
|
||||
return;
|
||||
}
|
||||
throw new Error("Canonical signed info cannot be empty");
|
||||
}
|
||||
// unsigned, verify later to keep with consistent callback behavior
|
||||
const parsedUnverifiedSignedInfo = new xmldom.DOMParser().parseFromString(unverifiedSignedInfoCanon, "text/xml");
|
||||
const unverifiedSignedInfoDoc = parsedUnverifiedSignedInfo.documentElement;
|
||||
if (!unverifiedSignedInfoDoc) {
|
||||
if (callback) {
|
||||
callback(new Error("Could not parse unverifiedSignedInfoCanon into a document"), false);
|
||||
return;
|
||||
}
|
||||
throw new Error("Could not parse unverifiedSignedInfoCanon into a document");
|
||||
}
|
||||
const references = utils.findChildren(unverifiedSignedInfoDoc, "Reference");
|
||||
if (!utils.isArrayHasLength(references)) {
|
||||
if (callback) {
|
||||
callback(new Error("could not find any Reference elements"), false);
|
||||
return;
|
||||
}
|
||||
throw new Error("could not find any Reference elements");
|
||||
}
|
||||
// TODO: In a future release we'd like to load the Signature and its References at the same time,
|
||||
// however, in the `.loadSignature()` method we don't have the entire document,
|
||||
// which we need to to keep the inclusive namespaces
|
||||
for (const reference of references) {
|
||||
this.loadReference(reference);
|
||||
}
|
||||
/* eslint-disable-next-line deprecation/deprecation */
|
||||
if (!this.getReferences().every((ref) => this.validateReference(ref, doc))) {
|
||||
/* Trustworthiness can only be determined if SignedInfo's (which holds References' DigestValue(s)
|
||||
which were validated at this stage) signature is valid. Execution does not proceed to validate
|
||||
signature phase thus each References' DigestValue must be considered to be untrusted (attacker
|
||||
might have injected any data with new new references and/or recalculated new DigestValue for
|
||||
altered Reference(s)). Returning any content via `signedReferences` would give false sense of
|
||||
trustworthiness if/when SignedInfo's (which holds references' DigestValues) signature is not
|
||||
valid(ated). Put simply: if one fails, they are all not trustworthy.
|
||||
*/
|
||||
this.signedReferences = [];
|
||||
this.references.forEach((ref) => {
|
||||
ref.signedReference = undefined;
|
||||
});
|
||||
// TODO: add this breaking change here later on for even more security: `this.references = [];`
|
||||
if (callback) {
|
||||
callback(new Error("Could not validate all references"), false);
|
||||
return;
|
||||
}
|
||||
// We return false because some references validated, but not all
|
||||
// We should actually be throwing an error here, but that would be a breaking change
|
||||
// See https://www.w3.org/TR/xmldsig-core/#sec-CoreValidation
|
||||
return false;
|
||||
}
|
||||
// (Stage B authentication step, show that the `signedInfoCanon` is signed)
|
||||
// First find the key & signature algorithm, these should match
|
||||
// Stage B: Take the signature algorithm and key and verify the `SignatureValue` against the canonicalized `SignedInfo`
|
||||
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");
|
||||
}
|
||||
// Check the signature verification to know whether to reset signature value or not.
|
||||
const sigRes = signer.verifySignature(unverifiedSignedInfoCanon, key, this.signatureValue);
|
||||
if (sigRes === true) {
|
||||
if (callback) {
|
||||
callback(null, true);
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Ideally, we would start by verifying the `signedInfoCanon` first,
|
||||
// but that may cause some breaking changes, so we'll handle that in v7.x.
|
||||
// If we were validating `signedInfoCanon` first, we wouldn't have to reset this array.
|
||||
this.signedReferences = [];
|
||||
this.references.forEach((ref) => {
|
||||
ref.signedReference = undefined;
|
||||
});
|
||||
// TODO: add this breaking change here later on for even more security: `this.references = [];`
|
||||
if (callback) {
|
||||
callback(new Error(`invalid signature: the signature value ${this.signatureValue} is incorrect`));
|
||||
return; // return early
|
||||
}
|
||||
else {
|
||||
throw new Error(`invalid signature: the signature value ${this.signatureValue} is incorrect`);
|
||||
}
|
||||
}
|
||||
}
|
||||
getCanonSignedInfoXml(doc) {
|
||||
if (this.signatureNode == null) {
|
||||
throw new Error("No signature found.");
|
||||
}
|
||||
if (typeof this.canonicalizationAlgorithm !== "string") {
|
||||
throw new Error("Missing canonicalizationAlgorithm when trying to get signed info for XML");
|
||||
}
|
||||
const signedInfo = utils.findChildren(this.signatureNode, "SignedInfo");
|
||||
if (signedInfo.length === 0) {
|
||||
throw new Error("could not find SignedInfo element in the message");
|
||||
}
|
||||
if (signedInfo.length > 1) {
|
||||
throw new Error("could not get canonicalized signed info for a signature that contains multiple SignedInfo nodes");
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
findSignatureAlgorithm(name) {
|
||||
if (name == null) {
|
||||
throw new Error("signatureAlgorithm is required");
|
||||
}
|
||||
const algo = this.SignatureAlgorithms[name];
|
||||
if (algo) {
|
||||
return new algo();
|
||||
}
|
||||
else {
|
||||
throw new Error(`signature algorithm '${name}' is not supported`);
|
||||
}
|
||||
}
|
||||
findCanonicalizationAlgorithm(name) {
|
||||
if (name != null) {
|
||||
const algo = this.CanonicalizationAlgorithms[name];
|
||||
if (algo) {
|
||||
return new algo();
|
||||
}
|
||||
}
|
||||
throw new Error(`canonicalization algorithm '${name}' is not supported`);
|
||||
}
|
||||
findHashAlgorithm(name) {
|
||||
const algo = this.HashAlgorithms[name];
|
||||
if (algo) {
|
||||
return new algo();
|
||||
}
|
||||
else {
|
||||
throw new Error(`hash algorithm '${name}' is not supported`);
|
||||
}
|
||||
}
|
||||
validateElementAgainstReferences(elemOrXpath, doc) {
|
||||
let elem;
|
||||
if (typeof elemOrXpath === "string") {
|
||||
const firstElem = xpath.select1(elemOrXpath, doc);
|
||||
isDomNode.assertIsElementNode(firstElem);
|
||||
elem = firstElem;
|
||||
}
|
||||
else {
|
||||
elem = elemOrXpath;
|
||||
}
|
||||
/* eslint-disable-next-line deprecation/deprecation */
|
||||
for (const ref of this.getReferences()) {
|
||||
const uri = ref.uri?.[0] === "#" ? ref.uri.substring(1) : ref.uri;
|
||||
for (const attr of this.idAttributes) {
|
||||
const elemId = elem.getAttribute(attr);
|
||||
if (uri === elemId) {
|
||||
ref.xpath = `//*[@*[local-name(.)='${attr}']='${uri}']`;
|
||||
break; // found the correct element, no need to check further
|
||||
}
|
||||
}
|
||||
const canonXml = this.getCanonReferenceXml(doc, ref, elem);
|
||||
const hash = this.findHashAlgorithm(ref.digestAlgorithm);
|
||||
const digest = hash.getHash(canonXml);
|
||||
if (utils.validateDigestValue(digest, ref.digestValue)) {
|
||||
return ref;
|
||||
}
|
||||
}
|
||||
throw new Error("No references passed validation");
|
||||
}
|
||||
validateReference(ref, doc) {
|
||||
const uri = ref.uri?.[0] === "#" ? ref.uri.substring(1) : ref.uri;
|
||||
let elem = null;
|
||||
if (uri === "") {
|
||||
elem = xpath.select1("//*", doc);
|
||||
}
|
||||
else if (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;
|
||||
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.");
|
||||
}
|
||||
elem = tmp_elem[0];
|
||||
ref.xpath = tmp_elemXpath;
|
||||
}
|
||||
}
|
||||
}
|
||||
ref.getValidatedNode = (0, util_1.deprecate)((xpathSelector) => {
|
||||
xpathSelector = xpathSelector || ref.xpath;
|
||||
if (typeof xpathSelector !== "string" || ref.validationError != null) {
|
||||
return null;
|
||||
}
|
||||
const selectedValue = xpath.select1(xpathSelector, doc);
|
||||
return isDomNode.isNodeLike(selectedValue) ? selectedValue : null;
|
||||
}, "`ref.getValidatedNode()` is deprecated and insecure. Use `ref.signedReference` or `this.getSignedReferences()` instead.");
|
||||
if (!isDomNode.isNodeLike(elem)) {
|
||||
const validationError = new Error(`invalid signature: the signature references an element with uri ${ref.uri} but could not find such element in the xml`);
|
||||
ref.validationError = validationError;
|
||||
return false;
|
||||
}
|
||||
const canonXml = this.getCanonReferenceXml(doc, ref, elem);
|
||||
const hash = this.findHashAlgorithm(ref.digestAlgorithm);
|
||||
const digest = hash.getHash(canonXml);
|
||||
if (!utils.validateDigestValue(digest, ref.digestValue)) {
|
||||
const validationError = new Error(`invalid signature: for uri ${ref.uri} calculated digest is ${digest} but the xml to validate supplies digest ${ref.digestValue}`);
|
||||
ref.validationError = validationError;
|
||||
return false;
|
||||
}
|
||||
// This step can only be done after we have verified the `signedInfo`.
|
||||
// We verified that they have same hash,
|
||||
// thus the `canonXml` and _only_ the `canonXml` can be trusted.
|
||||
// Append this to `signedReferences`.
|
||||
this.signedReferences.push(canonXml);
|
||||
ref.signedReference = canonXml;
|
||||
return true;
|
||||
}
|
||||
findSignatures(doc) {
|
||||
const nodes = xpath.select("//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", doc);
|
||||
return isDomNode.isArrayOfNodes(nodes) ? nodes : [];
|
||||
}
|
||||
/**
|
||||
* 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.DOMParser().parseFromString(signatureNode);
|
||||
}
|
||||
else {
|
||||
this.signatureNode = signatureNode;
|
||||
}
|
||||
this.signatureXml = signatureNode.toString();
|
||||
const node = xpath.select1(".//*[local-name(.)='CanonicalizationMethod']/@Algorithm", signatureNode);
|
||||
if (!isDomNode.isNodeLike(node)) {
|
||||
throw new Error("could not find CanonicalizationMethod/@Algorithm element");
|
||||
}
|
||||
if (isDomNode.isAttributeNode(node)) {
|
||||
this.canonicalizationAlgorithm = node.value;
|
||||
}
|
||||
const signatureAlgorithm = xpath.select1(".//*[local-name(.)='SignatureMethod']/@Algorithm", signatureNode);
|
||||
if (isDomNode.isAttributeNode(signatureAlgorithm)) {
|
||||
this.signatureAlgorithm = signatureAlgorithm.value;
|
||||
}
|
||||
const signedInfoNodes = utils.findChildren(this.signatureNode, "SignedInfo");
|
||||
if (!utils.isArrayHasLength(signedInfoNodes)) {
|
||||
throw new Error("no signed info node found");
|
||||
}
|
||||
if (signedInfoNodes.length > 1) {
|
||||
throw new Error("could not load signature that contains multiple SignedInfo nodes");
|
||||
}
|
||||
// Try to operate on the c14n version of `signedInfo`. This forces the initial `getReferences()`
|
||||
// API call to always return references that are loaded under the canonical `SignedInfo`
|
||||
// in the case that the client access the `.references` **before** signature verification.
|
||||
// Ensure canonicalization algorithm is exclusive, otherwise we'd need the entire document
|
||||
let canonicalizationAlgorithmForSignedInfo = this.canonicalizationAlgorithm;
|
||||
if (!canonicalizationAlgorithmForSignedInfo ||
|
||||
canonicalizationAlgorithmForSignedInfo ===
|
||||
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315" ||
|
||||
canonicalizationAlgorithmForSignedInfo ===
|
||||
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments") {
|
||||
canonicalizationAlgorithmForSignedInfo = "http://www.w3.org/2001/10/xml-exc-c14n#";
|
||||
}
|
||||
const temporaryCanonSignedInfo = this.getCanonXml([canonicalizationAlgorithmForSignedInfo], signedInfoNodes[0]);
|
||||
const temporaryCanonSignedInfoXml = new xmldom.DOMParser().parseFromString(temporaryCanonSignedInfo, "text/xml");
|
||||
const signedInfoDoc = temporaryCanonSignedInfoXml.documentElement;
|
||||
this.references = [];
|
||||
const references = utils.findChildren(signedInfoDoc, "Reference");
|
||||
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 (isDomNode.isTextNode(signatureValue)) {
|
||||
this.signatureValue = signatureValue.data.replace(/\r?\n/g, "");
|
||||
}
|
||||
const keyInfo = xpath.select1(".//*[local-name(.)='KeyInfo']", signatureNode);
|
||||
if (isDomNode.isNodeLike(keyInfo)) {
|
||||
this.keyInfo = keyInfo;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Load the reference xml node to a model
|
||||
*
|
||||
*/
|
||||
loadReference(refNode) {
|
||||
let nodes = utils.findChildren(refNode, "DigestMethod");
|
||||
if (nodes.length === 0) {
|
||||
throw new Error(`could not find DigestMethod in reference ${refNode.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.findChildren(refNode, "DigestValue");
|
||||
if (nodes.length === 0) {
|
||||
throw new Error(`could not find DigestValue node in reference ${refNode.toString()}`);
|
||||
}
|
||||
if (nodes.length > 1) {
|
||||
throw new Error(`could not load reference for a node that contains multiple DigestValue nodes: ${refNode.toString()}`);
|
||||
}
|
||||
const digestValue = nodes[0].textContent;
|
||||
if (!digestValue) {
|
||||
throw new Error(`could not find the value of DigestValue in ${refNode.toString()}`);
|
||||
}
|
||||
const transforms = [];
|
||||
let inclusiveNamespacesPrefixList = [];
|
||||
nodes = utils.findChildren(refNode, "Transforms");
|
||||
if (nodes.length !== 0) {
|
||||
const transformsNode = nodes[0];
|
||||
const transformsAll = utils.findChildren(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.findChildren(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) => (namespace.getAttribute("PrefixList") ?? "").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");
|
||||
}
|
||||
const refUri = isDomNode.isElementNode(refNode)
|
||||
? refNode.getAttribute("URI") || undefined
|
||||
: undefined;
|
||||
this.addReference({
|
||||
transforms,
|
||||
digestAlgorithm: digestAlgo,
|
||||
uri: refUri,
|
||||
digestValue,
|
||||
inclusiveNamespacesPrefixList,
|
||||
isEmptyUri: false,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 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.
|
||||
* @param digestAlgorithm The digest algorithm to use for computing the digest value.
|
||||
* @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, digestAlgorithm, uri = "", digestValue, inclusiveNamespacesPrefixList = [], isEmptyUri = false, }) {
|
||||
if (digestAlgorithm == null) {
|
||||
throw new Error("digestAlgorithm is required");
|
||||
}
|
||||
if (!utils.isArrayHasLength(transforms)) {
|
||||
throw new Error("transforms must contain at least one transform algorithm");
|
||||
}
|
||||
this.references.push({
|
||||
xpath,
|
||||
transforms,
|
||||
digestAlgorithm,
|
||||
uri,
|
||||
digestValue,
|
||||
inclusiveNamespacesPrefixList,
|
||||
isEmptyUri,
|
||||
getValidatedNode: () => {
|
||||
throw new Error("Reference has not been validated yet; Did you call `sig.checkSignature()`?");
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Returns the list of references.
|
||||
*/
|
||||
getReferences() {
|
||||
// TODO: Refactor once `getValidatedNode` is removed
|
||||
/* Once we completely remove the deprecated `getValidatedNode()` method,
|
||||
we can change this to return a clone to prevent accidental mutations,
|
||||
e.g.:
|
||||
return [...this.references];
|
||||
*/
|
||||
return this.references;
|
||||
}
|
||||
getSignedReferences() {
|
||||
return [...this.signedReferences];
|
||||
}
|
||||
computeSignature(xml, options, callbackParam) {
|
||||
let callback;
|
||||
if (typeof options === "function" && callbackParam == null) {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
else {
|
||||
callback = callbackParam;
|
||||
options = (options ?? {});
|
||||
}
|
||||
const doc = new xmldom.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.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 (!isDomNode.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.findChildren(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();
|
||||
}
|
||||
}
|
||||
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 "";
|
||||
}
|
||||
/**
|
||||
* Generate the Reference nodes (as part of the signature process)
|
||||
*
|
||||
*/
|
||||
createReferences(doc, prefix) {
|
||||
let res = "";
|
||||
prefix = prefix || "";
|
||||
prefix = prefix ? `${prefix}:` : prefix;
|
||||
/* eslint-disable-next-line deprecation/deprecation */
|
||||
for (const ref of this.getReferences()) {
|
||||
const nodes = xpath.selectWithResolver(ref.xpath ?? "", 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;
|
||||
}
|
||||
getCanonXml(transforms, node, options = {}) {
|
||||
options.defaultNsForPrefix = options.defaultNsForPrefix ?? SignedXml.defaultNsForPrefix;
|
||||
options.signatureNode = this.signatureNode;
|
||||
const canonXml = node.cloneNode(true); // Deep clone
|
||||
let transformedXml = canonXml;
|
||||
transforms.forEach((transformName) => {
|
||||
if (isDomNode.isNodeLike(transformedXml)) {
|
||||
// If, after processing, `transformedNode` is a string, we can't do anymore transforms on it
|
||||
const transform = this.findCanonicalizationAlgorithm(transformName);
|
||||
transformedXml = transform.process(transformedXml, 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 transformedXml.toString();
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
/**
|
||||
* Create the SignedInfo element
|
||||
*
|
||||
*/
|
||||
createSignedInfo(doc, prefix) {
|
||||
if (typeof this.canonicalizationAlgorithm !== "string") {
|
||||
throw new Error("Missing canonicalizationAlgorithm when trying to create signed info for XML");
|
||||
}
|
||||
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>`;
|
||||
}
|
||||
else {
|
||||
res += " />";
|
||||
}
|
||||
res += `<${currentPrefix}SignatureMethod Algorithm="${algo.getAlgorithmName()}" />`;
|
||||
res += this.createReferences(doc, prefix);
|
||||
res += `</${currentPrefix}SignedInfo>`;
|
||||
return res;
|
||||
}
|
||||
/**
|
||||
* 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.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;
|
||||
}
|
||||
/**
|
||||
* 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#",
|
||||
};
|
||||
SignedXml.noop = () => null;
|
||||
//# sourceMappingURL=signed-xml.js.map
|
||||
Reference in New Issue
Block a user