xml-crypto
Advanced tools
Comparing version 4.1.0 to 5.0.0
import type { CanonicalizationOrTransformationAlgorithm, CanonicalizationOrTransformationAlgorithmProcessOptions, NamespacePrefix, RenderedNamespace } from "./types"; | ||
export declare class C14nCanonicalization implements CanonicalizationOrTransformationAlgorithm { | ||
includeComments: boolean; | ||
protected includeComments: boolean; | ||
constructor(); | ||
attrCompare(a: any, b: any): 1 | 0 | -1; | ||
@@ -18,3 +19,6 @@ nsCompare(a: any, b: any): any; | ||
renderNs(node: Element, prefixesInScope: string[], defaultNs: string, defaultNsForPrefix: string, ancestorNamespaces: NamespacePrefix[]): RenderedNamespace; | ||
processInner(node: any, prefixesInScope: any, defaultNs: any, defaultNsForPrefix: any, ancestorNamespaces: any): any; | ||
/** | ||
* @param node Node | ||
*/ | ||
processInner(node: any, prefixesInScope: any, defaultNs: any, defaultNsForPrefix: any, ancestorNamespaces: any): string; | ||
renderComment(node: Comment): string; | ||
@@ -24,7 +28,6 @@ /** | ||
* | ||
* @param {Node} node | ||
* @return {String} | ||
* @param node | ||
* @api public | ||
*/ | ||
process(node: Node, options: CanonicalizationOrTransformationAlgorithmProcessOptions): any; | ||
process(node: Node, options: CanonicalizationOrTransformationAlgorithmProcessOptions): string; | ||
getAlgorithmName(): string; | ||
@@ -31,0 +34,0 @@ } |
@@ -5,6 +5,7 @@ "use strict"; | ||
const utils = require("./utils"); | ||
const xpath = require("xpath"); | ||
const isDomNode = require("@xmldom/is-dom-node"); | ||
class C14nCanonicalization { | ||
constructor() { | ||
this.includeComments = false; | ||
this.includeComments = false; | ||
} | ||
@@ -42,3 +43,3 @@ attrCompare(a, b) { | ||
const attrListToRender = []; | ||
if (xpath.isComment(node)) { | ||
if (isDomNode.isCommentNode(node)) { | ||
return this.renderComment(node); | ||
@@ -140,4 +141,7 @@ } | ||
} | ||
/** | ||
* @param node Node | ||
*/ | ||
processInner(node, prefixesInScope, defaultNs, defaultNsForPrefix, ancestorNamespaces) { | ||
if (xpath.isComment(node)) { | ||
if (isDomNode.isCommentNode(node)) { | ||
return this.renderComment(node); | ||
@@ -148,12 +152,15 @@ } | ||
} | ||
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, [])); | ||
if (isDomNode.isElementNode(node)) { | ||
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(""); | ||
} | ||
res.push("</", node.tagName, ">"); | ||
return res.join(""); | ||
throw new Error(`Unable to canonicalize node type: ${node.nodeType}`); | ||
} | ||
@@ -194,4 +201,3 @@ // Thanks to deoxxa/xml-c14n for comment renderer | ||
* | ||
* @param {Node} node | ||
* @return {String} | ||
* @param node | ||
* @api public | ||
@@ -198,0 +204,0 @@ */ |
import type { CanonicalizationOrTransformationAlgorithm, CanonicalizationOrTransformationAlgorithmProcessOptions, CanonicalizationOrTransformAlgorithmType } from "./types"; | ||
export declare class EnvelopedSignature implements CanonicalizationOrTransformationAlgorithm { | ||
includeComments: boolean; | ||
protected includeComments: boolean; | ||
constructor(); | ||
process(node: Node, options: CanonicalizationOrTransformationAlgorithmProcessOptions): Node; | ||
getAlgorithmName(): CanonicalizationOrTransformAlgorithmType; | ||
} |
@@ -5,5 +5,7 @@ "use strict"; | ||
const xpath = require("xpath"); | ||
const isDomNode = require("@xmldom/is-dom-node"); | ||
class EnvelopedSignature { | ||
constructor() { | ||
this.includeComments = false; | ||
this.includeComments = false; | ||
} | ||
@@ -13,3 +15,3 @@ process(node, options) { | ||
const signature = xpath.select1("./*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", node); | ||
if (xpath.isNodeLike(signature) && signature.parentNode) { | ||
if (isDomNode.isNodeLike(signature) && signature.parentNode) { | ||
signature.parentNode.removeChild(signature); | ||
@@ -21,3 +23,3 @@ } | ||
const expectedSignatureValue = xpath.select1(".//*[local-name(.)='SignatureValue']/text()", signatureNode); | ||
if (xpath.isTextNode(expectedSignatureValue)) { | ||
if (isDomNode.isTextNode(expectedSignatureValue)) { | ||
const expectedSignatureValueData = expectedSignatureValue.data; | ||
@@ -27,3 +29,3 @@ const signatures = xpath.select(".//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", node); | ||
const signatureValue = xpath.select1(".//*[local-name(.)='SignatureValue']/text()", nodeSignature); | ||
if (xpath.isTextNode(signatureValue)) { | ||
if (isDomNode.isTextNode(signatureValue)) { | ||
const signatureValueData = signatureValue.data; | ||
@@ -30,0 +32,0 @@ if (expectedSignatureValueData === signatureValueData) { |
import type { CanonicalizationOrTransformationAlgorithm, CanonicalizationOrTransformationAlgorithmProcessOptions } from "./types"; | ||
export declare class ExclusiveCanonicalization implements CanonicalizationOrTransformationAlgorithm { | ||
includeComments: boolean; | ||
protected includeComments: boolean; | ||
constructor(); | ||
attrCompare(a: any, b: any): 1 | 0 | -1; | ||
@@ -21,12 +22,13 @@ nsCompare(a: any, b: any): any; | ||
}; | ||
processInner(node: any, prefixesInScope: any, defaultNs: any, defaultNsForPrefix: any, inclusiveNamespacesPrefixList: string[]): any; | ||
/** | ||
* @param node Node | ||
*/ | ||
processInner(node: any, prefixesInScope: any, defaultNs: any, defaultNsForPrefix: any, inclusiveNamespacesPrefixList: string[]): string; | ||
renderComment(node: Comment): string; | ||
/** | ||
* Perform canonicalization of the given node | ||
* Perform canonicalization of the given element node | ||
* | ||
* @param {Node} node | ||
* @return {String} | ||
* @api public | ||
*/ | ||
process(node: any, options: CanonicalizationOrTransformationAlgorithmProcessOptions): any; | ||
process(elem: Element, options: CanonicalizationOrTransformationAlgorithmProcessOptions): string; | ||
getAlgorithmName(): string; | ||
@@ -33,0 +35,0 @@ } |
@@ -5,3 +5,3 @@ "use strict"; | ||
const utils = require("./utils"); | ||
const xpath = require("xpath"); | ||
const isDomNode = require("@xmldom/is-dom-node"); | ||
function isPrefixInScope(prefixesInScope, prefix, namespaceURI) { | ||
@@ -19,2 +19,3 @@ let ret = false; | ||
this.includeComments = false; | ||
this.includeComments = false; | ||
} | ||
@@ -53,3 +54,3 @@ attrCompare(a, b) { | ||
const attrListToRender = []; | ||
if (xpath.isComment(node)) { | ||
if (isDomNode.isCommentNode(node)) { | ||
return this.renderComment(node); | ||
@@ -138,4 +139,7 @@ } | ||
} | ||
/** | ||
* @param node Node | ||
*/ | ||
processInner(node, prefixesInScope, defaultNs, defaultNsForPrefix, inclusiveNamespacesPrefixList) { | ||
if (xpath.isComment(node)) { | ||
if (isDomNode.isCommentNode(node)) { | ||
return this.renderComment(node); | ||
@@ -146,12 +150,15 @@ } | ||
} | ||
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)); | ||
if (isDomNode.isElementNode(node)) { | ||
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(""); | ||
} | ||
res.push("</", node.tagName, ">"); | ||
return res.join(""); | ||
throw new Error(`Unable to exclusive canonicalize node type: ${node.nodeType}`); | ||
} | ||
@@ -190,9 +197,7 @@ // Thanks to deoxxa/xml-c14n for comment renderer | ||
/** | ||
* Perform canonicalization of the given node | ||
* Perform canonicalization of the given element node | ||
* | ||
* @param {Node} node | ||
* @return {String} | ||
* @api public | ||
*/ | ||
process(node, options) { | ||
process(elem, options) { | ||
options = options || {}; | ||
@@ -207,3 +212,3 @@ let inclusiveNamespacesPrefixList = options.inclusiveNamespacesPrefixList || []; | ||
if (!utils.isArrayHasLength(inclusiveNamespacesPrefixList)) { | ||
const CanonicalizationMethod = utils.findChildren(node, "CanonicalizationMethod"); | ||
const CanonicalizationMethod = utils.findChildren(elem, "CanonicalizationMethod"); | ||
if (CanonicalizationMethod.length !== 0) { | ||
@@ -224,3 +229,3 @@ const inclusiveNamespaces = utils.findChildren(CanonicalizationMethod[0], "InclusiveNamespaces"); | ||
if (prefix === ancestorNamespace.prefix) { | ||
node.setAttributeNS("http://www.w3.org/2000/xmlns/", `xmlns:${prefix}`, ancestorNamespace.namespaceURI); | ||
elem.setAttributeNS("http://www.w3.org/2000/xmlns/", `xmlns:${prefix}`, ancestorNamespace.namespaceURI); | ||
} | ||
@@ -231,3 +236,3 @@ }); | ||
} | ||
const res = this.processInner(node, [], defaultNs, defaultNsForPrefix, inclusiveNamespacesPrefixList); | ||
const res = this.processInner(elem, [], defaultNs, defaultNsForPrefix, inclusiveNamespacesPrefixList); | ||
return res; | ||
@@ -234,0 +239,0 @@ } |
@@ -7,7 +7,7 @@ /// <reference types="node" /> | ||
(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike): string; | ||
(args_0: crypto.BinaryLike, args_1: crypto.KeyLike, args_2: import("./types").ErrorFirstCallback<string>): void; | ||
(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike, args_2: import("./types").ErrorFirstCallback<string>): void; | ||
}; | ||
verifySignature: { | ||
(material: string, key: crypto.KeyLike, signatureValue: string): boolean; | ||
(args_0: string, args_1: crypto.KeyLike, args_2: string, args_3: import("./types").ErrorFirstCallback<boolean>): void; | ||
(material: string, key: crypto.KeyLike, signatureValue: string, args_3: import("./types").ErrorFirstCallback<boolean>): void; | ||
}; | ||
@@ -19,7 +19,7 @@ getAlgorithmName: () => string; | ||
(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike): string; | ||
(args_0: crypto.BinaryLike, args_1: crypto.KeyLike, args_2: import("./types").ErrorFirstCallback<string>): void; | ||
(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike, args_2: import("./types").ErrorFirstCallback<string>): void; | ||
}; | ||
verifySignature: { | ||
(material: string, key: crypto.KeyLike, signatureValue: string): boolean; | ||
(args_0: string, args_1: crypto.KeyLike, args_2: string, args_3: import("./types").ErrorFirstCallback<boolean>): void; | ||
(material: string, key: crypto.KeyLike, signatureValue: string, args_3: import("./types").ErrorFirstCallback<boolean>): void; | ||
}; | ||
@@ -31,7 +31,7 @@ getAlgorithmName: () => string; | ||
(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike): string; | ||
(args_0: crypto.BinaryLike, args_1: crypto.KeyLike, args_2: import("./types").ErrorFirstCallback<string>): void; | ||
(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike, args_2: import("./types").ErrorFirstCallback<string>): void; | ||
}; | ||
verifySignature: { | ||
(material: string, key: crypto.KeyLike, signatureValue: string): boolean; | ||
(args_0: string, args_1: crypto.KeyLike, args_2: string, args_3: import("./types").ErrorFirstCallback<boolean>): void; | ||
(material: string, key: crypto.KeyLike, signatureValue: string, args_3: import("./types").ErrorFirstCallback<boolean>): void; | ||
}; | ||
@@ -43,9 +43,9 @@ getAlgorithmName: () => string; | ||
(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike): string; | ||
(args_0: crypto.BinaryLike, args_1: crypto.KeyLike, args_2: import("./types").ErrorFirstCallback<string>): void; | ||
(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike, args_2: import("./types").ErrorFirstCallback<string>): void; | ||
}; | ||
verifySignature: { | ||
(material: string, key: crypto.KeyLike, signatureValue: string): boolean; | ||
(args_0: string, args_1: crypto.KeyLike, args_2: string, args_3: import("./types").ErrorFirstCallback<boolean>): void; | ||
(material: string, key: crypto.KeyLike, signatureValue: string, args_3: import("./types").ErrorFirstCallback<boolean>): void; | ||
}; | ||
getAlgorithmName: () => string; | ||
} |
@@ -13,9 +13,10 @@ /// <reference types="node" /> | ||
/** | ||
* One of the supported signature algorithms. See {@link SignatureAlgorithmType} | ||
* One of the supported signature algorithms. | ||
* @see {@link SignatureAlgorithmType} | ||
*/ | ||
signatureAlgorithm: SignatureAlgorithmType; | ||
signatureAlgorithm?: SignatureAlgorithmType; | ||
/** | ||
* Rules used to convert an XML document into its canonical form. | ||
*/ | ||
canonicalizationAlgorithm: CanonicalizationAlgorithmType; | ||
canonicalizationAlgorithm?: CanonicalizationAlgorithmType; | ||
/** | ||
@@ -32,6 +33,2 @@ * It specifies a list of namespace prefixes that should be considered "inclusive" during the canonicalization process. | ||
getCertFromKeyInfo: typeof SignedXml.getCertFromKeyInfo; | ||
/** | ||
* Specifies the data to be signed within an XML document. See {@link Reference} | ||
*/ | ||
private references; | ||
private id; | ||
@@ -43,7 +40,8 @@ private signedXml; | ||
private originalXmlWithIds; | ||
private keyInfo; | ||
/** | ||
* Contains validation errors (if any) after {@link checkSignature} method is called | ||
* Contains the references that were signed. | ||
* @see {@link Reference} | ||
*/ | ||
validationErrors: string[]; | ||
private keyInfo; | ||
private references; | ||
/** | ||
@@ -64,2 +62,3 @@ * 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} | ||
}; | ||
static noop: () => null; | ||
/** | ||
@@ -89,3 +88,3 @@ * The SignedXml constructor provides an abstraction for sign and verify xml documents. The object is constructed using | ||
* | ||
* @param keyInfo KeyInfo element (see https://www.w3.org/TR/2008/REC-xmldsig-core-20080610/#sec-X509Data) | ||
* @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 | ||
@@ -110,11 +109,11 @@ */ | ||
checkSignature(xml: string, callback: (error: Error | null, isValid?: boolean) => void): void; | ||
getCanonSignedInfoXml(doc: Document): any; | ||
getCanonReferenceXml(doc: any, ref: any, node: any): any; | ||
validateSignatureValue(doc: Document): boolean; | ||
validateSignatureValue(doc: Document, callback: ErrorFirstCallback<boolean>): void; | ||
calculateSignatureValue(doc: Document, callback?: ErrorFirstCallback<string>): void; | ||
findSignatureAlgorithm(name: SignatureAlgorithmType): SignatureAlgorithm; | ||
findCanonicalizationAlgorithm(name: CanonicalizationOrTransformAlgorithmType): CanonicalizationOrTransformationAlgorithm; | ||
findHashAlgorithm(name: HashAlgorithmType): HashAlgorithm; | ||
validateReferences(doc: any): boolean; | ||
private getCanonSignedInfoXml; | ||
private getCanonReferenceXml; | ||
private calculateSignatureValue; | ||
private findSignatureAlgorithm; | ||
private findCanonicalizationAlgorithm; | ||
private findHashAlgorithm; | ||
validateElementAgainstReferences(elemOrXpath: Element | string, doc: Document): Reference; | ||
private validateReference; | ||
findSignatures(doc: Node): Node[]; | ||
/** | ||
@@ -130,3 +129,3 @@ * Loads the signature information from the provided XML node or string. | ||
*/ | ||
loadReference(ref: any): void; | ||
private loadReference; | ||
/** | ||
@@ -136,4 +135,4 @@ * 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 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. | ||
@@ -145,2 +144,3 @@ * @param digestValue The expected digest value for the reference. | ||
addReference({ xpath, transforms, digestAlgorithm, uri, digestValue, inclusiveNamespacesPrefixList, isEmptyUri, }: Partial<Reference> & Pick<Reference, "xpath">): void; | ||
getReferences(): Reference[]; | ||
/** | ||
@@ -183,3 +183,3 @@ * Compute the signature of the given XML (using the already defined settings). | ||
computeSignature(xml: string, options: ComputeSignatureOptions, callback: ErrorFirstCallback<SignedXml>): void; | ||
getKeyInfo(prefix: any): string; | ||
private getKeyInfo; | ||
/** | ||
@@ -189,4 +189,4 @@ * Generate the Reference nodes (as part of the signature process) | ||
*/ | ||
createReferences(doc: any, prefix: any): string; | ||
getCanonXml(transforms: CanonicalizationAlgorithmType[], node: any, options: CanonicalizationOrTransformationAlgorithmProcessOptions): any; | ||
private createReferences; | ||
getCanonXml(transforms: Reference["transforms"], node: Node, options?: CanonicalizationOrTransformationAlgorithmProcessOptions): string; | ||
/** | ||
@@ -196,3 +196,3 @@ * Ensure an element has Id attribute. If not create it with unique value. | ||
*/ | ||
ensureHasId(node: any): any; | ||
private ensureHasId; | ||
/** | ||
@@ -202,3 +202,3 @@ * Create the SignedInfo element | ||
*/ | ||
createSignedInfo(doc: any, prefix: any): string; | ||
private createSignedInfo; | ||
/** | ||
@@ -208,3 +208,3 @@ * Create the Signature element | ||
*/ | ||
createSignature(prefix?: string): ChildNode; | ||
private createSignature; | ||
/** | ||
@@ -211,0 +211,0 @@ * Returns just the signature part, must be called only after {@link computeSignature} |
@@ -12,2 +12,3 @@ "use strict"; | ||
const signatureAlgorithms = require("./signature-algorithms"); | ||
const isDomNode = require("@xmldom/is-dom-node"); | ||
class SignedXml { | ||
@@ -20,9 +21,10 @@ /** | ||
/** | ||
* One of the supported signature algorithms. See {@link SignatureAlgorithmType} | ||
* One of the supported signature algorithms. | ||
* @see {@link SignatureAlgorithmType} | ||
*/ | ||
this.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; | ||
this.signatureAlgorithm = undefined; | ||
/** | ||
* Rules used to convert an XML document into its canonical form. | ||
*/ | ||
this.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; | ||
this.canonicalizationAlgorithm = undefined; | ||
/** | ||
@@ -42,6 +44,2 @@ * It specifies a list of namespace prefixes that should be considered "inclusive" during the canonicalization process. | ||
// Internal state | ||
/** | ||
* Specifies the data to be signed within an XML document. See {@link Reference} | ||
*/ | ||
this.references = []; | ||
this.id = 0; | ||
@@ -53,7 +51,8 @@ this.signedXml = ""; | ||
this.originalXmlWithIds = ""; | ||
this.keyInfo = null; | ||
/** | ||
* Contains validation errors (if any) after {@link checkSignature} method is called | ||
* Contains the references that were signed. | ||
* @see {@link Reference} | ||
*/ | ||
this.validationErrors = []; | ||
this.keyInfo = null; | ||
this.references = []; | ||
/** | ||
@@ -96,4 +95,4 @@ * 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.publicCert = publicCert; | ||
this.signatureAlgorithm = signatureAlgorithm !== null && signatureAlgorithm !== void 0 ? signatureAlgorithm : this.signatureAlgorithm; | ||
this.canonicalizationAlgorithm = canonicalizationAlgorithm !== null && canonicalizationAlgorithm !== void 0 ? canonicalizationAlgorithm : this.canonicalizationAlgorithm; | ||
this.signatureAlgorithm = signatureAlgorithm ?? this.signatureAlgorithm; | ||
this.canonicalizationAlgorithm = canonicalizationAlgorithm; | ||
if (typeof inclusiveNamespacesPrefixList === "string") { | ||
@@ -105,6 +104,6 @@ this.inclusiveNamespacesPrefixList = inclusiveNamespacesPrefixList.split(" "); | ||
} | ||
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.implicitTransforms = implicitTransforms ?? this.implicitTransforms; | ||
this.keyInfoAttributes = keyInfoAttributes ?? this.keyInfoAttributes; | ||
this.getKeyInfoContent = getKeyInfoContent ?? SignedXml.noop; | ||
this.getCertFromKeyInfo = getCertFromKeyInfo ?? this.getCertFromKeyInfo; | ||
this.CanonicalizationAlgorithms; | ||
@@ -123,3 +122,3 @@ this.HashAlgorithms; | ||
}; | ||
this.getKeyInfoContent = () => null; | ||
this.getKeyInfoContent = SignedXml.noop; | ||
} | ||
@@ -149,3 +148,5 @@ /** | ||
x509Certs = publicCertMatches | ||
.map((c) => `<X509Certificate>${utils.pemToDer(c).toString("base64")}</X509Certificate>`) | ||
.map((c) => `<${prefix}X509Certificate>${utils | ||
.pemToDer(c) | ||
.toString("base64")}</${prefix}X509Certificate>`) | ||
.join(""); | ||
@@ -159,3 +160,3 @@ } | ||
* | ||
* @param keyInfo KeyInfo element (see https://www.w3.org/TR/2008/REC-xmldsig-core-20080610/#sec-X509Data) | ||
* @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 | ||
@@ -166,3 +167,3 @@ */ | ||
const certs = xpath.select1(".//*[local-name(.)='X509Certificate']", keyInfo); | ||
if (xpath.isNodeLike(certs)) { | ||
if (isDomNode.isNodeLike(certs)) { | ||
return utils.derToPem(certs.textContent || "", "CERTIFICATE"); | ||
@@ -177,33 +178,27 @@ } | ||
} | ||
this.validationErrors = []; | ||
this.signedXml = xml; | ||
const doc = new xmldom.DOMParser().parseFromString(xml); | ||
if (!this.validateReferences(doc)) { | ||
if (!callback) { | ||
return false; | ||
} | ||
else { | ||
callback(new Error("Could not validate references")); | ||
if (!this.getReferences().every((ref) => this.validateReference(ref, doc))) { | ||
if (callback) { | ||
callback(new Error("Could not validate all references")); | ||
return; | ||
} | ||
return false; | ||
} | ||
if (!callback) { | ||
// Synchronous flow | ||
if (!this.validateSignatureValue(doc)) { | ||
return false; | ||
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 (callback) { | ||
signer.verifySignature(signedInfoCanon, key, this.signatureValue, callback); | ||
} | ||
else { | ||
const verified = signer.verifySignature(signedInfoCanon, key, this.signatureValue); | ||
if (verified === false) { | ||
throw new Error("invalid signature: the signature value ${this.signatureValue} is incorrect"); | ||
} | ||
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); | ||
} | ||
}); | ||
} | ||
} | ||
@@ -214,2 +209,5 @@ getCanonSignedInfoXml(doc) { | ||
} | ||
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"); | ||
@@ -248,20 +246,2 @@ if (signedInfo.length === 0) { | ||
} | ||
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; | ||
} | ||
} | ||
calculateSignatureValue(doc, callback) { | ||
@@ -281,2 +261,5 @@ const signedInfoCanon = this.getCanonSignedInfoXml(doc); | ||
findSignatureAlgorithm(name) { | ||
if (name == null) { | ||
throw new Error("signatureAlgorithm is required"); | ||
} | ||
const algo = this.SignatureAlgorithms[name]; | ||
@@ -291,9 +274,9 @@ if (algo) { | ||
findCanonicalizationAlgorithm(name) { | ||
const algo = this.CanonicalizationAlgorithms[name]; | ||
if (algo) { | ||
return new algo(); | ||
if (name != null) { | ||
const algo = this.CanonicalizationAlgorithms[name]; | ||
if (algo) { | ||
return new algo(); | ||
} | ||
} | ||
else { | ||
throw new Error(`canonicalization algorithm '${name}' is not supported`); | ||
} | ||
throw new Error(`canonicalization algorithm '${name}' is not supported`); | ||
} | ||
@@ -309,48 +292,84 @@ findHashAlgorithm(name) { | ||
} | ||
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); | ||
validateElementAgainstReferences(elemOrXpath, doc) { | ||
let elem; | ||
if (typeof elemOrXpath === "string") { | ||
const firstElem = xpath.select1(elemOrXpath, doc); | ||
isDomNode.assertIsElementNode(firstElem); | ||
elem = firstElem; | ||
} | ||
else { | ||
elem = elemOrXpath; | ||
} | ||
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 | ||
} | ||
} | ||
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"); | ||
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; | ||
} | ||
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; | ||
} | ||
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; | ||
} | ||
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; | ||
} | ||
ref.getValidatedNode = (xpathSelector) => { | ||
xpathSelector = xpathSelector || ref.xpath; | ||
if (typeof xpathSelector !== "string" || ref.validationError != null) { | ||
return null; | ||
} | ||
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; | ||
} | ||
const selectedValue = xpath.select1(xpathSelector, doc); | ||
return isDomNode.isNodeLike(selectedValue) ? selectedValue : null; | ||
}; | ||
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; | ||
} | ||
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 : []; | ||
} | ||
/** | ||
@@ -369,11 +388,11 @@ * Loads the signature information from the provided XML node or string. | ||
this.signatureXml = signatureNode.toString(); | ||
const nodes = xpath.select(".//*[local-name(.)='CanonicalizationMethod']/@Algorithm", signatureNode); | ||
if (!utils.isArrayHasLength(nodes)) { | ||
const node = xpath.select1(".//*[local-name(.)='CanonicalizationMethod']/@Algorithm", signatureNode); | ||
if (!isDomNode.isNodeLike(node)) { | ||
throw new Error("could not find CanonicalizationMethod/@Algorithm element"); | ||
} | ||
if (xpath.isAttribute(nodes[0])) { | ||
this.canonicalizationAlgorithm = nodes[0].value; | ||
if (isDomNode.isAttributeNode(node)) { | ||
this.canonicalizationAlgorithm = node.value; | ||
} | ||
const signatureAlgorithm = xpath.select1(".//*[local-name(.)='SignatureMethod']/@Algorithm", signatureNode); | ||
if (xpath.isAttribute(signatureAlgorithm)) { | ||
if (isDomNode.isAttributeNode(signatureAlgorithm)) { | ||
this.signatureAlgorithm = signatureAlgorithm.value; | ||
@@ -390,8 +409,7 @@ } | ||
const signatureValue = xpath.select1(".//*[local-name(.)='SignatureValue']/text()", signatureNode); | ||
if (xpath.isTextNode(signatureValue)) { | ||
if (isDomNode.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)) { | ||
if (isDomNode.isNodeLike(keyInfo)) { | ||
this.keyInfo = keyInfo; | ||
@@ -404,7 +422,6 @@ } | ||
*/ | ||
loadReference(ref) { | ||
var _a; | ||
let nodes = utils.findChildren(ref, "DigestMethod"); | ||
loadReference(refNode) { | ||
let nodes = utils.findChildren(refNode, "DigestMethod"); | ||
if (nodes.length === 0) { | ||
throw new Error(`could not find DigestMethod in reference ${ref.toString()}`); | ||
throw new Error(`could not find DigestMethod in reference ${refNode.toString()}`); | ||
} | ||
@@ -417,5 +434,5 @@ const digestAlgoNode = nodes[0]; | ||
const digestAlgo = attr.value; | ||
nodes = utils.findChildren(ref, "DigestValue"); | ||
nodes = utils.findChildren(refNode, "DigestValue"); | ||
if (nodes.length === 0) { | ||
throw new Error(`could not find DigestValue node in reference ${ref.toString()}`); | ||
throw new Error(`could not find DigestValue node in reference ${refNode.toString()}`); | ||
} | ||
@@ -429,3 +446,3 @@ const firstChild = nodes[0].firstChild; | ||
let inclusiveNamespacesPrefixList = []; | ||
nodes = utils.findChildren(ref, "Transforms"); | ||
nodes = utils.findChildren(refNode, "Transforms"); | ||
if (nodes.length !== 0) { | ||
@@ -445,33 +462,32 @@ const transformsNode = nodes[0]; | ||
inclusiveNamespacesPrefixList = inclusiveNamespaces | ||
.flatMap((namespace) => { var _a; return ((_a = namespace.getAttribute("PrefixList")) !== null && _a !== void 0 ? _a : "").split(" "); }) | ||
.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"); | ||
} | ||
this.addReference({ | ||
transforms, | ||
digestAlgorithm: digestAlgo, | ||
uri: (_a = utils.findAttr(ref, "URI")) === null || _a === void 0 ? void 0 : _a.value, | ||
digestValue, | ||
inclusiveNamespacesPrefixList, | ||
isEmptyUri: false, | ||
} | ||
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: isDomNode.isElementNode(refNode) ? utils.findAttr(refNode, "URI")?.value : undefined, | ||
digestValue, | ||
inclusiveNamespacesPrefixList, | ||
isEmptyUri: false, | ||
}); | ||
} | ||
@@ -482,4 +498,4 @@ /** | ||
* @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 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. | ||
@@ -490,3 +506,9 @@ * @param digestValue The expected digest value for the reference. | ||
*/ | ||
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, }) { | ||
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({ | ||
@@ -500,4 +522,10 @@ xpath, | ||
isEmptyUri, | ||
getValidatedNode: () => { | ||
throw new Error("Reference has not been validated yet; Did you call `sig.checkSignature()`?"); | ||
}, | ||
}); | ||
} | ||
getReferences() { | ||
return this.references; | ||
} | ||
computeSignature(xml, options, callbackParam) { | ||
@@ -511,3 +539,3 @@ let callback; | ||
callback = callbackParam; | ||
options = (options !== null && options !== void 0 ? options : {}); | ||
options = (options ?? {}); | ||
} | ||
@@ -574,3 +602,3 @@ const doc = new xmldom.DOMParser().parseFromString(xml); | ||
const referenceNode = xpath.select1(location.reference, doc); | ||
if (!xpath.isNodeLike(referenceNode)) { | ||
if (!isDomNode.isNodeLike(referenceNode)) { | ||
const err2 = new Error(`the following xpath cannot be used because it was not found: ${location.reference}`); | ||
@@ -658,8 +686,7 @@ if (!callback) { | ||
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); | ||
for (const ref of this.getReferences()) { | ||
const nodes = xpath.selectWithResolver(ref.xpath ?? "", doc, this.namespaceResolver); | ||
if (!utils.isArrayHasLength(nodes)) { | ||
@@ -701,11 +728,13 @@ throw new Error(`the following xpath cannot be signed because it was not found: ${ref.xpath}`); | ||
} | ||
getCanonXml(transforms, node, options) { | ||
var _a; | ||
options = options || {}; | ||
options.defaultNsForPrefix = (_a = options.defaultNsForPrefix) !== null && _a !== void 0 ? _a : SignedXml.defaultNsForPrefix; | ||
getCanonXml(transforms, node, options = {}) { | ||
options.defaultNsForPrefix = options.defaultNsForPrefix ?? SignedXml.defaultNsForPrefix; | ||
options.signatureNode = this.signatureNode; | ||
let canonXml = node.cloneNode(true); // Deep clone | ||
const canonXml = node.cloneNode(true); // Deep clone | ||
let transformedXml = canonXml; | ||
transforms.forEach((transformName) => { | ||
const transform = this.findCanonicalizationAlgorithm(transformName); | ||
canonXml = transform.process(canonXml, options); | ||
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). | ||
@@ -719,3 +748,3 @@ //This either needs to be more explicit in the API, or all should return the same. | ||
}); | ||
return canonXml.toString(); | ||
return transformedXml.toString(); | ||
} | ||
@@ -756,2 +785,5 @@ /** | ||
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); | ||
@@ -828,2 +860,3 @@ const algo = this.findSignatureAlgorithm(this.signatureAlgorithm); | ||
}; | ||
SignedXml.noop = () => null; | ||
//# sourceMappingURL=signed-xml.js.map |
@@ -9,3 +9,3 @@ /// <reference types="node" /> | ||
/** | ||
* @param cert the certificate as a string or array of strings (see https://www.w3.org/TR/2008/REC-xmldsig-core-20080610/#sec-X509Data) | ||
* @param cert the certificate as a string or array of strings (@see https://www.w3.org/TR/2008/REC-xmldsig-core-20080610/#sec-X509Data) | ||
* @param prefix an optional namespace alias to be used for the generated XML | ||
@@ -55,3 +55,3 @@ */ | ||
* | ||
* - `prefix` {String} Adds a prefix for the generated signature tags | ||
* - `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 | ||
@@ -83,2 +83,4 @@ * - `location` {{ reference: String, action: String }} | ||
ancestorNamespaces?: NamespacePrefix[]; | ||
validationError?: Error; | ||
getValidatedNode(xpathSelector?: string): Node | null; | ||
} | ||
@@ -89,3 +91,2 @@ /** Implement this to create a new CanonicalizationOrTransformationAlgorithm */ | ||
getAlgorithmName(): CanonicalizationOrTransformAlgorithmType; | ||
includeComments: boolean; | ||
} | ||
@@ -92,0 +93,0 @@ /** Implement this to create a new HashAlgorithm */ |
@@ -29,3 +29,2 @@ "use strict"; | ||
* - {@link SignedXml#checkSignature} | ||
* - {@link SignedXml#validationErrors} | ||
*/ | ||
@@ -32,0 +31,0 @@ function isErrorFirstCallback(possibleCallback) { |
@@ -9,3 +9,3 @@ /// <reference types="node" /> | ||
export declare function encodeSpecialCharactersInAttribute(attributeValue: any): any; | ||
export declare function encodeSpecialCharactersInText(text: any): any; | ||
export declare function encodeSpecialCharactersInText(text: string): string; | ||
/** | ||
@@ -60,8 +60,8 @@ * PEM format has wide range of usages, but this library | ||
* | ||
* @param {object} doc - Usually a product from `new xmldom.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"}] | ||
* @param doc - Usually a product from `new xmldom.DOMParser().parseFromString()` | ||
* @param docSubsetXpath - xpath query to get document subset being canonicalized | ||
* @param namespaceResolver - xpath namespace resolver | ||
* @returns i.e. [{prefix: "saml", namespaceURI: "urn:oasis:names:tc:SAML:2.0:assertion"}] | ||
*/ | ||
export declare function findAncestorNs(doc: Node, docSubsetXpath: string, namespaceResolver?: XPathNSResolver): NamespacePrefix[]; | ||
export declare function findAncestorNs(doc: Document, docSubsetXpath?: string, namespaceResolver?: XPathNSResolver): NamespacePrefix[]; | ||
export declare function validateDigestValue(digest: any, expectedDigest: any): boolean; |
@@ -5,2 +5,3 @@ "use strict"; | ||
const xpath = require("xpath"); | ||
const isDomNode = require("@xmldom/is-dom-node"); | ||
function isArrayHasLength(array) { | ||
@@ -15,3 +16,3 @@ return Array.isArray(array) && array.length > 0; | ||
return (attr.localName === localName && | ||
((!attr.namespaceURI && (node === null || node === void 0 ? void 0 : node.namespaceURI) === namespace) || namespace == null)); | ||
((!attr.namespaceURI && node?.namespaceURI === namespace) || namespace == null)); | ||
} | ||
@@ -30,8 +31,7 @@ function findAttr(element, localName, namespace) { | ||
function findChildren(node, localName, namespace) { | ||
var _a; | ||
const element = (_a = node.documentElement) !== null && _a !== void 0 ? _a : node; | ||
const element = node.documentElement ?? node; | ||
const res = []; | ||
for (let i = 0; i < element.childNodes.length; i++) { | ||
const child = element.childNodes[i]; | ||
if (xpath.isElement(child) && | ||
if (isDomNode.isElementNode(child) && | ||
child.localName === localName && | ||
@@ -66,5 +66,7 @@ (child.namespaceURI === namespace || namespace == null)) { | ||
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 | ||
/** 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]; | ||
@@ -76,5 +78,7 @@ }); | ||
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 | ||
/** 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]; | ||
@@ -120,7 +124,6 @@ }); | ||
function normalizePem(pem) { | ||
var _a; | ||
return `${((_a = pem | ||
return `${(pem | ||
.trim() | ||
.replace(/(\r\n|\r)/g, "\n") | ||
.match(/.{1,64}/g)) !== null && _a !== void 0 ? _a : []).join("\n")}\n`; | ||
.match(/.{1,64}/g) ?? []).join("\n")}\n`; | ||
} | ||
@@ -163,3 +166,3 @@ exports.normalizePem = normalizePem; | ||
function collectAncestorNamespaces(node, nsArray = []) { | ||
if (!xpath.isElement(node.parentNode)) { | ||
if (!isDomNode.isElementNode(node.parentNode)) { | ||
return nsArray; | ||
@@ -195,3 +198,3 @@ } | ||
function isElementSubset(docSubset) { | ||
return docSubset.every((node) => xpath.isElement(node)); | ||
return docSubset.every((node) => isDomNode.isElementNode(node)); | ||
} | ||
@@ -202,8 +205,11 @@ /** | ||
* | ||
* @param {object} doc - Usually a product from `new xmldom.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"}] | ||
* @param doc - Usually a product from `new xmldom.DOMParser().parseFromString()` | ||
* @param docSubsetXpath - xpath query to get document subset being canonicalized | ||
* @param namespaceResolver - xpath namespace resolver | ||
* @returns i.e. [{prefix: "saml", namespaceURI: "urn:oasis:names:tc:SAML:2.0:assertion"}] | ||
*/ | ||
function findAncestorNs(doc, docSubsetXpath, namespaceResolver) { | ||
if (docSubsetXpath == null) { | ||
return []; | ||
} | ||
const docSubset = xpath.selectWithResolver(docSubsetXpath, doc, namespaceResolver); | ||
@@ -248,3 +254,2 @@ if (!isArrayHasLength(docSubset)) { | ||
} | ||
// Compatibility with Node < 0.11.13 | ||
if (buffer.length !== expectedBuffer.length) { | ||
@@ -251,0 +256,0 @@ return false; |
{ | ||
"name": "xml-crypto", | ||
"version": "4.1.0", | ||
"version": "5.0.0", | ||
"private": false, | ||
@@ -31,4 +31,4 @@ "description": "Xml digital signature and encryption library for Node.js", | ||
"changelog": "gren changelog --override --generate", | ||
"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", | ||
"lint": "eslint \"{src,test}/*.ts\" --cache && npm run prettier-check", | ||
"lint:fix": "eslint --fix \"{src,test}/*.ts\" && npm run prettier-format", | ||
"prepare": "tsc", | ||
@@ -42,33 +42,34 @@ "prettier-check": "prettier --config .prettierrc.json --check .", | ||
"dependencies": { | ||
"@xmldom/is-dom-node": "^1.0.1", | ||
"@xmldom/xmldom": "^0.8.10", | ||
"xpath": "0.0.33" | ||
"xpath": "^0.0.33" | ||
}, | ||
"devDependencies": { | ||
"@cjbarth/github-release-notes": "^4.1.0", | ||
"@cjbarth/github-release-notes": "^4.2.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", | ||
"@prettier/plugin-xml": "^3.2.2", | ||
"@types/chai": "^4.3.11", | ||
"@types/mocha": "^10.0.6", | ||
"@types/node": "^16.18.58", | ||
"@typescript-eslint/eslint-plugin": "^6.13.0", | ||
"@typescript-eslint/parser": "^6.13.0", | ||
"chai": "^4.3.10", | ||
"choma": "^1.2.1", | ||
"ejs": "^3.1.9", | ||
"eslint": "^8.45.0", | ||
"eslint-config-prettier": "^8.8.0", | ||
"eslint-plugin-deprecation": "^1.4.1", | ||
"eslint": "^8.54.0", | ||
"eslint-config-prettier": "^9.0.0", | ||
"eslint-plugin-deprecation": "^2.0.0", | ||
"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", | ||
"prettier": "^3.1.0", | ||
"prettier-plugin-packagejson": "^2.4.6", | ||
"release-it": "^16.2.1", | ||
"source-map-support": "^0.5.21", | ||
"ts-node": "^10.9.1", | ||
"typescript": "^5.1.6" | ||
"typescript": "^5.3.2" | ||
}, | ||
"engines": { | ||
"node": ">=14" | ||
"node": ">=16" | ||
} | ||
} |
153
README.md
@@ -24,23 +24,23 @@ # xml-crypto | ||
- Canonicalization http://www.w3.org/TR/2001/REC-xml-c14n-20010315 | ||
- Canonicalization with comments http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments | ||
- Exclusive Canonicalization http://www.w3.org/2001/10/xml-exc-c14n# | ||
- Exclusive Canonicalization with comments http://www.w3.org/2001/10/xml-exc-c14n#WithComments | ||
- Enveloped Signature transform http://www.w3.org/2000/09/xmldsig#enveloped-signature | ||
- Canonicalization <http://www.w3.org/TR/2001/REC-xml-c14n-20010315> | ||
- Canonicalization with comments <http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments> | ||
- Exclusive Canonicalization <http://www.w3.org/2001/10/xml-exc-c14n#> | ||
- Exclusive Canonicalization with comments <http://www.w3.org/2001/10/xml-exc-c14n#WithComments> | ||
- Enveloped Signature transform <http://www.w3.org/2000/09/xmldsig#enveloped-signature> | ||
### Hashing Algorithms | ||
- SHA1 digests http://www.w3.org/2000/09/xmldsig#sha1 | ||
- SHA256 digests http://www.w3.org/2001/04/xmlenc#sha256 | ||
- SHA512 digests http://www.w3.org/2001/04/xmlenc#sha512 | ||
- SHA1 digests <http://www.w3.org/2000/09/xmldsig#sha1> | ||
- SHA256 digests <http://www.w3.org/2001/04/xmlenc#sha256> | ||
- SHA512 digests <http://www.w3.org/2001/04/xmlenc#sha512> | ||
### Signature Algorithms | ||
- RSA-SHA1 http://www.w3.org/2000/09/xmldsig#rsa-sha1 | ||
- RSA-SHA256 http://www.w3.org/2001/04/xmldsig-more#rsa-sha256 | ||
- RSA-SHA512 http://www.w3.org/2001/04/xmldsig-more#rsa-sha512 | ||
- RSA-SHA1 <http://www.w3.org/2000/09/xmldsig#rsa-sha1> | ||
- RSA-SHA256 <http://www.w3.org/2001/04/xmldsig-more#rsa-sha256> | ||
- RSA-SHA512 <http://www.w3.org/2001/04/xmldsig-more#rsa-sha512> | ||
HMAC-SHA1 is also available but it is disabled by default | ||
- HMAC-SHA1 http://www.w3.org/2000/09/xmldsig#hmac-sha1 | ||
- HMAC-SHA1 <http://www.w3.org/2000/09/xmldsig#hmac-sha1> | ||
@@ -53,10 +53,2 @@ to enable HMAC-SHA1, call `enableHMAC()` on your instance of `SignedXml`. | ||
by default the following algorithms are used: | ||
_Canonicalization/Transformation Algorithm:_ Exclusive Canonicalization http://www.w3.org/2001/10/xml-exc-c14n# | ||
_Hashing Algorithm:_ SHA1 digest http://www.w3.org/2000/09/xmldsig#sha1 | ||
_Signature Algorithm:_ RSA-SHA1 http://www.w3.org/2000/09/xmldsig#rsa-sha1 | ||
[You are able to extend xml-crypto with custom algorithms.](#customizing-algorithms) | ||
@@ -82,3 +74,9 @@ | ||
var sig = new SignedXml({ privateKey: fs.readFileSync("client.pem") }); | ||
sig.addReference({ xpath: "//*[local-name(.)='book']" }); | ||
sig.addReference({ | ||
xpath: "//*[local-name(.)='book']", | ||
digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", | ||
transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], | ||
}); | ||
sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; | ||
sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; | ||
sig.computeSignature(xml); | ||
@@ -124,4 +122,13 @@ fs.writeFileSync("signed.xml", sig.getSignedXml()); | ||
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). | ||
The certificate that will be used to check the signature will first be determined by calling `this.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). | ||
Example: | ||
```javascript | ||
new SignedXml({ | ||
publicCert: client_public_pem, | ||
getCertFromKeyInfo: () => null, | ||
}); | ||
``` | ||
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: | ||
@@ -150,16 +157,45 @@ | ||
sig.loadSignature(signature); | ||
var res = sig.checkSignature(xml); | ||
if (!res) console.log(sig.validationErrors); | ||
try { | ||
var res = sig.checkSignature(xml); | ||
} catch (ex) { | ||
console.log(ex); | ||
} | ||
``` | ||
If the verification process fails `sig.validationErrors` will contain the errors. | ||
In order to protect from some attacks we must check the content we want to use is the one that has been signed: | ||
```javascript | ||
var elem = select(doc, "/xpath_to_interesting_element"); | ||
var uri = sig.references[0].uri; // might not be 0 - depending on the document you verify | ||
var id = uri[0] === "#" ? uri.substring(1) : uri; | ||
if (elem.getAttribute("ID") != id && elem.getAttribute("Id") != id && elem.getAttribute("id") != id) | ||
throw new Error("the interesting element was not the one verified by the signature"); | ||
// Roll your own | ||
const elem = xpath.select("/xpath_to_interesting_element", doc); | ||
const uri = sig.getReferences()[0].uri; // might not be 0; it depends on the document | ||
const id = uri[0] === "#" ? uri.substring(1) : uri; | ||
if ( | ||
elem.getAttribute("ID") != id && | ||
elem.getAttribute("Id") != id && | ||
elem.getAttribute("id") != id | ||
) { | ||
throw new Error("The interesting element was not the one verified by the signature"); | ||
} | ||
// Get the validated element directly from a reference | ||
const elem = sig.references[0].getValidatedElement(); // might not be 0; it depends on the document | ||
const matchingReference = xpath.select1("/xpath_to_interesting_element", elem); | ||
if (!isDomNode.isNodeLike(matchingReference)) { | ||
throw new Error("The interesting element was not the one verified by the signature"); | ||
} | ||
// Use the built-in method | ||
const elem = xpath.select1("/xpath_to_interesting_element", doc); | ||
try { | ||
const matchingReference = sig.validateElementAgainstReferences(elem, doc); | ||
} catch { | ||
throw new Error("The interesting element was not the one verified by the signature"); | ||
} | ||
// Use the built-in method with a an xpath expression | ||
try { | ||
const matchingReference = sig.validateReferenceWithXPath("/xpath_to_interesting_element", doc); | ||
} catch { | ||
throw new Error("The interesting element was not the one verified by the signature"); | ||
} | ||
``` | ||
@@ -193,6 +229,6 @@ | ||
- http://www.w3.org/TR/2001/REC-xml-c14n-20010315 | ||
- http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments | ||
- http://www.w3.org/2001/10/xml-exc-c14n# | ||
- http://www.w3.org/2001/10/xml-exc-c14n#WithComments | ||
- <http://www.w3.org/TR/2001/REC-xml-c14n-20010315> | ||
- <http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments> | ||
- <http://www.w3.org/2001/10/xml-exc-c14n#> | ||
- <http://www.w3.org/2001/10/xml-exc-c14n#WithComments> | ||
@@ -214,9 +250,9 @@ ## API | ||
- `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 | ||
- `signatureAlgorithm` - string - the signature algorithm to use | ||
- `canonicalizationAlgorithm` - string - default `undefined` - 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 | ||
- `getKeyInfoContent` - function - default `noop` - 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 | ||
@@ -229,3 +265,3 @@ #### API | ||
- `addReference(xpath, [transforms], [digestAlgorithm])` - adds a reference to a xml element where: | ||
- `addReference(xpath, transforms, digestAlgorithm)` - adds a reference to a xml element where: | ||
- `xpath` - a string containing a XPath expression referencing a xml element | ||
@@ -249,4 +285,3 @@ - `transforms` - an array of [transform algorithms](#canonicalization-and-transformation-algorithms), the referenced element will be transformed for each value in the array | ||
- `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 | ||
- `checkSignature(xml)` - validates the given xml document and returns `true` if the validation was successful | ||
@@ -267,4 +302,4 @@ ## Customizing Algorithms | ||
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 | ||
`this.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 `this.getKeyInfoContent`. If you prefer to use the default implementation, assign `SignedXml.getKeyInfoContent` to `this.getKeyInfoContent` If | ||
there are no attributes and no contents to the `<KeyInfo />` element, it won't be included in the | ||
@@ -289,3 +324,3 @@ generated XML. | ||
A custom signing algorithm. The default is RSA-SHA1. | ||
A custom signing algorithm. | ||
@@ -305,3 +340,3 @@ ```javascript | ||
Custom transformation algorithm. The default is exclusive canonicalization. | ||
Custom transformation algorithm. | ||
@@ -369,3 +404,9 @@ ```javascript | ||
sig.addReference({ xpath }); | ||
sig.addReference({ | ||
xpath, | ||
transforms: ["http://MyTransformation"], | ||
digestAlgorithm: "http://myDigestAlgorithm", | ||
}); | ||
sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; | ||
sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; | ||
sig.computeSignature(xml); | ||
@@ -403,2 +444,4 @@ fs.writeFileSync(dest, sig.getSignedXml()); | ||
sig.SignatureAlgorithms["http://asyncSignatureAlgorithm"] = AsyncSignatureAlgorithm; | ||
sig.signatureAlgorithm = "http://asyncSignatureAlgorithm"; | ||
sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; | ||
sig.computeSignature(xml, opts, function (err) { | ||
@@ -454,3 +497,9 @@ var signedResponse = sig.getSignedXml(); | ||
var sig = new SignedXml({ privateKey: fs.readFileSync("client.pem") }); | ||
sig.addReference({ xpath: "//*[local-name(.)='book']" }); | ||
sig.addReference({ | ||
xpath: "//*[local-name(.)='book']", | ||
digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", | ||
transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], | ||
}); | ||
sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; | ||
sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; | ||
sig.computeSignature(xml, { | ||
@@ -478,3 +527,9 @@ prefix: "ds", | ||
var sig = new SignedXml({ privateKey: fs.readFileSync("client.pem") }); | ||
sig.addReference({ xpath: "//*[local-name(.)='book']" }); | ||
sig.addReference({ | ||
xpath: "//*[local-name(.)='book']", | ||
digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", | ||
transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], | ||
}); | ||
sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; | ||
sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; | ||
sig.computeSignature(xml, { | ||
@@ -481,0 +536,0 @@ location: { reference: "//*[local-name(.)='book']", action: "after" }, //This will place the signature after the book element |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
265838
2360
545
3
+ Added@xmldom/is-dom-node@^1.0.1
+ Added@xmldom/is-dom-node@1.0.1(transitive)
Updatedxpath@^0.0.33