Commit inicial: fuentes MBS ERP (Genero 6) + .gitignore + docs/SETUP_PC_MBS.md

This commit is contained in:
2026-08-18 20:59:52 -04:00
commit 454973e269
5978 changed files with 2664094 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
(The MIT License)
Copyright (c) Yaron Naveh <yaronn01@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+557
View File
@@ -0,0 +1,557 @@
# xml-crypto
![Build](https://github.com/node-saml/xml-crypto/actions/workflows/ci.yml/badge.svg)
[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/from-referrer/)
---
# Upgrading
The `.getReferences()` AND the `.references` APIs are deprecated.
Please do not attempt to access them. The content in them should be treated as unsigned.
Instead, we strongly encourage users to migrate to the `.getSignedReferences()` API. See the [Verifying XML document](#verifying-xml-documents) section
We understand that this may take a lot of efforts to migrate, feel free to ask for help.
This will help prevent future XML signature wrapping attacks.
---
## Install
Install with [npm](http://github.com/isaacs/npm):
```shell
npm install xml-crypto
```
A pre requisite it to have [openssl](http://www.openssl.org/) installed and its /bin to be on the system path. I used version 1.0.1c but it should work on older versions too.
## Supported Algorithms
### Canonicalization and Transformation Algorithms
- 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>
### 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>
HMAC-SHA1 is also available but it is disabled by default
- HMAC-SHA1 <http://www.w3.org/2000/09/xmldsig#hmac-sha1>
to enable HMAC-SHA1, call `enableHMAC()` on your instance of `SignedXml`.
This will enable HMAC and disable digital signature algorithms. Due to key
confusion issues, it is risky to have both HMAC-based and public key digital
signature algorithms enabled at same time.
[You are able to extend xml-crypto with custom algorithms.](#customizing-algorithms)
## Signing Xml documents
When signing a xml document you can pass the following options to the `SignedXml` constructor to customize the signature process:
- `privateKey` - **[required]** a `Buffer` or pem encoded `String` containing your private key
- `publicCert` - **[optional]** a `Buffer` or pem encoded `String` containing your public key
- `signatureAlgorithm` - **[required]** one of the supported [signature algorithms](#signature-algorithms). Ex: `sign.signatureAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"`
- `canonicalizationAlgorithm` - **[required]** one of the supported [canonicalization algorithms](#canonicalization-and-transformation-algorithms). Ex: `sign.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#WithComments"`
Use this code:
```javascript
var SignedXml = require("xml-crypto").SignedXml,
fs = require("fs");
var xml = "<library>" + "<book>" + "<name>Harry Potter</name>" + "</book>" + "</library>";
var sig = new SignedXml({ privateKey: fs.readFileSync("client.pem") });
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);
fs.writeFileSync("signed.xml", sig.getSignedXml());
```
The result will be:
```xml
<library>
<book Id="_0">
<name>Harry Potter</name>
</book>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
<Reference URI="#_0">
<Transforms>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>cdiS43aFDQMnb3X8yaIUej3+z9Q=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>vhWzpQyIYuncHUZV9W...[long base64 removed]...</SignatureValue>
</Signature>
</library>
```
Note:
If you set the `publicCert` and the `getKeyInfoContent` properties, a `<KeyInfo></KeyInfo>` element with the public certificate will be generated in the signature:
```xml
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
...[signature info removed]...
</SignedInfo>
<SignatureValue>vhWzpQyIYuncHUZV9W...[long base64 removed]...</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>MIIGYjCCBJagACCBN...[long base64 removed]...</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>
```
For `getKeyInfoContent`, a default implementation `SignedXml.getKeyInfoContent` is available.
To customize this see [customizing algorithms](#customizing-algorithms) for an example.
## Verifying Xml documents
When verifying a xml document you can pass the following options to the `SignedXml` constructor to customize the verify process:
- `publicCert` - **[optional]** your certificate as a string, a string of multiple certs in PEM format, or a Buffer
- `privateKey` - **[optional]** your private key as a string or a Buffer - used for verifying symmetrical signatures (HMAC)
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:
```shell
npm install @xmldom/xmldom
```
Example:
```javascript
var select = require("xml-crypto").xpath,
dom = require("@xmldom/xmldom").DOMParser,
SignedXml = require("xml-crypto").SignedXml,
fs = require("fs");
var xml = fs.readFileSync("signed.xml").toString();
var doc = new dom().parseFromString(xml);
// DO NOT attempt to parse whatever data object you have here in `doc`
// and then use it to verify the signature. This can lead to security issues.
// i.e. BAD: parseAssertion(doc),
// good: see below
var signature = select(
doc,
"//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']",
)[0];
var sig = new SignedXml({ publicCert: fs.readFileSync("client_public.pem") });
sig.loadSignature(signature);
try {
var res = sig.checkSignature(xml);
} catch (ex) {
console.log(ex);
}
```
In order to protect from some attacks we must check the content we want to use is the one that has been signed:
```javascript
if (!res) {
throw "Invalid Signature";
}
// good: The XML Signature has been verified, meaning some subset of XML is verified.
var signedBytes = sig.getSignedReferences();
var authenticatedDoc = new dom().parseFromString(signedBytes[0]); // Take the first signed reference
// It is now safe to load SAML, obtain the assertion XML, or do whatever else is needed.
// Be sure to only use authenticated data.
let signedAssertionNode = extractAssertion(authenticatedDoc);
let parsedAssertion = parseAssertion(signedAssertionNode);
return parsedAssertion; // This the correctly verified signed Assertion
// BAD example: DO not use the .getReferences() API.
```
Note:
The xml-crypto api requires you to supply it separately the xml signature ("&lt;Signature&gt;...&lt;/Signature&gt;", in loadSignature) and the signed xml (in checkSignature). The signed xml may or may not contain the signature in it, but you are still required to supply the signature separately.
### Caring for Implicit transform
If you fail to verify signed XML, then one possible cause is that there are some hidden implicit transforms(#).
(#) Normalizing XML document to be verified. i.e. remove extra space within a tag, sorting attributes, importing namespace declared in ancestor nodes, etc.
The reason for these implicit transform might come from [complex xml signature specification](https://www.w3.org/TR/2002/REC-xmldsig-core-20020212),
which makes XML developers confused and then leads to incorrect implementation for signing XML document.
If you keep failing verification, it is worth trying to guess such a hidden transform and specify it to the option as below:
```javascript
var options = {
implicitTransforms: ["http://www.w3.org/TR/2001/REC-xml-c14n-20010315"],
publicCert: fs.readFileSync("client_public.pem"),
};
var sig = new SignedXml(options);
sig.loadSignature(signature);
var res = sig.checkSignature(xml);
```
You might find it difficult to guess such transforms, but there are typical transforms you can try.
- <http://www.w3.org/TR/2001/REC-xml-c14n-20010315>
- <http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments>
- <http://www.w3.org/2001/10/xml-exc-c14n#>
- <http://www.w3.org/2001/10/xml-exc-c14n#WithComments>
## API
### xpath
See [xpath.js](https://github.com/yaronn/xpath.js) for usage. Note that this is actually using
[another library](https://github.com/goto100/xpath) as the underlying implementation.
### SignedXml
The `SignedXml` constructor provides an abstraction for sign and verify xml documents. The object is constructed using `new SignedXml(options?: SignedXmlOptions)` where the possible options are:
- `idMode` - default `null` - if the value of `wssecurity` is passed it will create/validate id's with the ws-security namespace.
- `idAttribute` - string - default `Id` or `ID` or `id` - the name of the attribute that contains the id of the element
- `privateKey` - string or Buffer - default `null` - the private key to use for signing
- `publicCert` - string or Buffer - default `null` - the public certificate to use for verifying
- `signatureAlgorithm` - string - 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 `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
#### API
A `SignedXml` object provides the following methods:
To sign xml documents:
- `addReference(xpath, transforms, digestAlgorithm)` - adds a reference to a xml element where:
- `xpath` - a string containing a XPath expression referencing a xml element
- `transforms` - an array of [transform algorithms](#canonicalization-and-transformation-algorithms), the referenced element will be transformed for each value in the array
- `digestAlgorithm` - one of the supported [hashing algorithms](#hashing-algorithms)
- `computeSignature(xml, [options])` - compute the signature of the given xml where:
- `xml` - a string containing a xml document
- `options` - an object with the following properties:
- `prefix` - adds this value as a prefix for the generated signature tags
- `attrs` - a hash of attributes and values `attrName: value` to add to the signature root node
- `location` - customize the location of the signature, pass an object with a `reference` key which should contain a XPath expression to a reference node, an `action` key which should contain one of the following values: `append`, `prepend`, `before`, `after`
- `existingPrefixes` - A hash of prefixes and namespaces `prefix: namespace` that shouldn't be in the signature because they already exist in the xml
- `getSignedXml()` - returns the original xml document with the signature in it, **must be called only after `computeSignature`**
- `getSignatureXml()` - returns just the signature part, **must be called only after `computeSignature`**
- `getOriginalXmlWithIds()` - returns the original xml with Id attributes added on relevant elements (required for validation), **must be called only after `computeSignature`**
To verify xml documents:
- `loadSignature(signatureXml)` - loads the signature where:
- `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
## Customizing Algorithms
The following sample shows how to sign a message using custom algorithms.
First import some modules:
```javascript
var SignedXml = require("xml-crypto").SignedXml,
fs = require("fs");
```
Now define the extension point you want to implement. You can choose one or more.
To determine the inclusion and contents of a `<KeyInfo />` element, the function
`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
generated XML.
To specify custom attributes on `<KeyInfo />`, add the properties to the `.keyInfoAttributes` property.
A custom hash algorithm is used to calculate digests. Implement it if you want a hash other than the built-in methods.
```javascript
function MyDigest() {
this.getHash = function (xml) {
return "the base64 hash representation of the given xml string";
};
this.getAlgorithmName = function () {
return "http://myDigestAlgorithm";
};
}
```
A custom signing algorithm.
```javascript
function MySignatureAlgorithm() {
/*sign the given SignedInfo using the key. return base64 signature value*/
this.getSignature = function (signedInfo, privateKey) {
return "signature of signedInfo as base64...";
};
this.getAlgorithmName = function () {
return "http://mySigningAlgorithm";
};
}
```
Custom transformation algorithm.
```javascript
function MyTransformation() {
/*given a node (from the xmldom module) return its canonical representation (as string)*/
this.process = function (node) {
//you should apply your transformation before returning
return node.toString();
};
this.getAlgorithmName = function () {
return "http://myTransformation";
};
}
```
Custom canonicalization is actually the same as custom transformation. It is applied on the SignedInfo rather than on references.
```javascript
function MyCanonicalization() {
/*given a node (from the xmldom module) return its canonical representation (as string)*/
this.process = function (node) {
//you should apply your transformation before returning
return "< x/>";
};
this.getAlgorithmName = function () {
return "http://myCanonicalization";
};
}
```
Now you need to register the new algorithms:
```javascript
/*register all the custom algorithms*/
signedXml.CanonicalizationAlgorithms["http://MyTransformation"] = MyTransformation;
signedXml.CanonicalizationAlgorithms["http://MyCanonicalization"] = MyCanonicalization;
signedXml.HashAlgorithms["http://myDigestAlgorithm"] = MyDigest;
signedXml.SignatureAlgorithms["http://mySigningAlgorithm"] = MySignatureAlgorithm;
```
Now do the signing. Note how we configure the signature to use the above algorithms:
```javascript
function signXml(xml, xpath, key, dest) {
var options = {
publicCert: fs.readFileSync("my_public_cert.pem", "latin1"),
privateKey: fs.readFileSync(key),
/*configure the signature object to use the custom algorithms*/
signatureAlgorithm: "http://mySignatureAlgorithm",
canonicalizationAlgorithm: "http://MyCanonicalization",
};
var sig = new SignedXml(options);
sig.addReference({
xpath: "//*[local-name(.)='x']",
transforms: ["http://MyTransformation"],
digestAlgorithm: "http://myDigestAlgorithm",
});
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);
fs.writeFileSync(dest, sig.getSignedXml());
}
var xml = "<library>" + "<book>" + "<name>Harry Potter</name>" + "</book>";
("</library>");
signXml(xml, "//*[local-name(.)='book']", "client.pem", "result.xml");
```
You can always look at the actual code as a sample.
## Asynchronous signing and verification
If the private key is not stored locally, and you wish to use a signing server or Hardware Security Module (HSM) to sign documents, you can create a custom signing algorithm that uses an asynchronous callback.
```javascript
function AsyncSignatureAlgorithm() {
this.getSignature = function (signedInfo, privateKey, callback) {
var signer = crypto.createSign("RSA-SHA1");
signer.update(signedInfo);
var res = signer.sign(privateKey, "base64");
//Do some asynchronous things here
callback(null, res);
};
this.getAlgorithmName = function () {
return "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
};
}
var sig = new SignedXml({ signatureAlgorithm: "http://asyncSignatureAlgorithm" });
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) {
var signedResponse = sig.getSignedXml();
});
```
The function `sig.checkSignature` may also use a callback if asynchronous verification is needed.
## X.509 / Key formats
Xml-Crypto internally relies on node's crypto module. This means pem encoded certificates are supported. So to sign an xml use key.pem that looks like this (only the beginning of the key content is shown):
```text
-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0...
-----END PRIVATE KEY-----
```
And for verification use key_public.pem:
```text
-----BEGIN CERTIFICATE-----
MIIBxDCCAW6gAwIBAgIQxUSX...
-----END CERTIFICATE-----
```
### Converting .pfx certificates to pem
If you have .pfx certificates you can convert them to .pem using [openssl](http://www.openssl.org/):
```shell
openssl pkcs12 -in c:\certs\yourcert.pfx -out c:\certs\cag.pem
```
Then you could use the result as is for the purpose of signing. For the purpose of validation open the resulting .pem with a text editor and copy from -----BEGIN CERTIFICATE----- to -----END CERTIFICATE----- (including) to a new text file and save it as .pem.
## Examples
### how to sign a root node (_coming soon_)
### how to add a prefix for the signature
Use the `prefix` option when calling `computeSignature` to add a prefix to the signature.
```javascript
var SignedXml = require("xml-crypto").SignedXml,
fs = require("fs");
var xml = "<library>" + "<book>" + "<name>Harry Potter</name>" + "</book>" + "</library>";
var sig = new SignedXml({ privateKey: fs.readFileSync("client.pem") });
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, {
prefix: "ds",
});
```
### how to specify the location of the signature
Use the `location` option when calling `computeSignature` to move the signature around.
Set `action` to one of the following:
- append(default) - append to the end of the xml document
- prepend - prepend to the xml document
- before - prepend to a specific node (use the `referenceNode` property)
- after - append to specific node (use the `referenceNode` property)
```javascript
var SignedXml = require("xml-crypto").SignedXml,
fs = require("fs");
var xml = "<library>" + "<book>" + "<name>Harry Potter</name>" + "</book>" + "</library>";
var sig = new SignedXml({ privateKey: fs.readFileSync("client.pem") });
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, {
location: { reference: "//*[local-name(.)='book']", action: "after" }, //This will place the signature after the book element
});
```
### more examples (_coming soon_)
## Development
The testing framework we use is [Mocha](https://github.com/mochajs/mocha) with [Chai](https://github.com/chaijs/chai) as the assertion framework.
To run tests use:
```shell
npm test
```
## More information
Visit my [blog](http://webservices20.blogspot.com/) or my [twitter](http://twitter.com/#!/YaronNaveh)
[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/yaronn/xml-crypto/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
## License
This project is licensed under the [MIT License](http://opensource.org/licenses/MIT). See the [LICENSE](LICENSE) file for more info.
@@ -0,0 +1,39 @@
import type { CanonicalizationOrTransformationAlgorithm, CanonicalizationOrTransformationAlgorithmProcessOptions, NamespacePrefix, RenderedNamespace } from "./types";
export declare class C14nCanonicalization implements CanonicalizationOrTransformationAlgorithm {
protected includeComments: boolean;
constructor();
attrCompare(a: any, b: any): 1 | 0 | -1;
nsCompare(a: any, b: any): any;
renderAttrs(node: any): string;
/**
* Create the string of all namespace declarations that should appear on this element
*
* @param node The node we now render
* @param prefixesInScope The prefixes defined on this node parents which are a part of the output set
* @param defaultNs The current default namespace
* @param defaultNsForPrefix
* @param ancestorNamespaces Import ancestor namespaces if it is specified
* @api private
*/
renderNs(node: Element, prefixesInScope: string[], defaultNs: string, defaultNsForPrefix: string, ancestorNamespaces: NamespacePrefix[]): RenderedNamespace;
/**
* @param node Node
*/
processInner(node: any, prefixesInScope: any, defaultNs: any, defaultNsForPrefix: any, ancestorNamespaces: any): string;
renderComment(node: Comment): string;
/**
* Perform canonicalization of the given node
*
* @param node
* @api public
*/
process(node: Node, options: CanonicalizationOrTransformationAlgorithmProcessOptions): string;
getAlgorithmName(): string;
}
/**
* Add c14n#WithComments here (very simple subclass)
*/
export declare class C14nCanonicalizationWithComments extends C14nCanonicalization {
constructor();
getAlgorithmName(): string;
}
@@ -0,0 +1,230 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.C14nCanonicalizationWithComments = exports.C14nCanonicalization = void 0;
const utils = require("./utils");
const isDomNode = require("@xmldom/is-dom-node");
class C14nCanonicalization {
constructor() {
this.includeComments = false;
this.includeComments = false;
}
attrCompare(a, b) {
if (!a.namespaceURI && b.namespaceURI) {
return -1;
}
if (!b.namespaceURI && a.namespaceURI) {
return 1;
}
const left = a.namespaceURI + a.localName;
const right = b.namespaceURI + b.localName;
if (left === right) {
return 0;
}
else if (left < right) {
return -1;
}
else {
return 1;
}
}
nsCompare(a, b) {
const attr1 = a.prefix;
const attr2 = b.prefix;
if (attr1 === attr2) {
return 0;
}
return attr1.localeCompare(attr2);
}
renderAttrs(node) {
let i;
let attr;
const attrListToRender = [];
if (isDomNode.isCommentNode(node)) {
return this.renderComment(node);
}
if (node.attributes) {
for (i = 0; i < node.attributes.length; ++i) {
attr = node.attributes[i];
//ignore namespace definition attributes
if (attr.name.indexOf("xmlns") === 0) {
continue;
}
attrListToRender.push(attr);
}
}
attrListToRender.sort(this.attrCompare);
const res = attrListToRender.map((attr) => {
return ` ${attr.name}="${utils.encodeSpecialCharactersInAttribute(attr.value)}"`;
});
return res.join("");
}
/**
* Create the string of all namespace declarations that should appear on this element
*
* @param node The node we now render
* @param prefixesInScope The prefixes defined on this node parents which are a part of the output set
* @param defaultNs The current default namespace
* @param defaultNsForPrefix
* @param ancestorNamespaces Import ancestor namespaces if it is specified
* @api private
*/
renderNs(node, prefixesInScope, defaultNs, defaultNsForPrefix, ancestorNamespaces) {
let i;
let attr;
const res = [];
let newDefaultNs = defaultNs;
const nsListToRender = [];
const currNs = node.namespaceURI || "";
//handle the namespace of the node itself
if (node.prefix) {
if (prefixesInScope.indexOf(node.prefix) === -1) {
nsListToRender.push({
prefix: node.prefix,
namespaceURI: node.namespaceURI || defaultNsForPrefix[node.prefix],
});
prefixesInScope.push(node.prefix);
}
}
else if (defaultNs !== currNs) {
//new default ns
newDefaultNs = node.namespaceURI || "";
res.push(' xmlns="', newDefaultNs, '"');
}
//handle the attributes namespace
if (node.attributes) {
for (i = 0; i < node.attributes.length; ++i) {
attr = node.attributes[i];
//handle all prefixed attributes that are included in the prefix list and where
//the prefix is not defined already. New prefixes can only be defined by `xmlns:`.
if (attr.prefix === "xmlns" && prefixesInScope.indexOf(attr.localName) === -1) {
nsListToRender.push({ prefix: attr.localName, namespaceURI: attr.value });
prefixesInScope.push(attr.localName);
}
//handle all prefixed attributes that are not xmlns definitions and where
//the prefix is not defined already
if (attr.prefix &&
prefixesInScope.indexOf(attr.prefix) === -1 &&
attr.prefix !== "xmlns" &&
attr.prefix !== "xml") {
nsListToRender.push({ prefix: attr.prefix, namespaceURI: attr.namespaceURI });
prefixesInScope.push(attr.prefix);
}
}
}
if (utils.isArrayHasLength(ancestorNamespaces)) {
// Remove namespaces which are already present in nsListToRender
for (const ancestorNamespace of ancestorNamespaces) {
let alreadyListed = false;
for (const nsToRender of nsListToRender) {
if (nsToRender.prefix === ancestorNamespace.prefix &&
nsToRender.namespaceURI === ancestorNamespace.namespaceURI) {
alreadyListed = true;
}
}
if (!alreadyListed) {
nsListToRender.push(ancestorNamespace);
}
}
}
nsListToRender.sort(this.nsCompare);
//render namespaces
res.push(...nsListToRender.map((attr) => {
if (attr.prefix) {
return ` xmlns:${attr.prefix}="${attr.namespaceURI}"`;
}
return ` xmlns="${attr.namespaceURI}"`;
}));
return { rendered: res.join(""), newDefaultNs };
}
/**
* @param node Node
*/
processInner(node, prefixesInScope, defaultNs, defaultNsForPrefix, ancestorNamespaces) {
if (isDomNode.isCommentNode(node)) {
return this.renderComment(node);
}
if (node.data) {
return utils.encodeSpecialCharactersInText(node.data);
}
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("");
}
throw new Error(`Unable to canonicalize node type: ${node.nodeType}`);
}
// Thanks to deoxxa/xml-c14n for comment renderer
renderComment(node) {
if (!this.includeComments) {
return "";
}
const isOutsideDocument = node.ownerDocument === node.parentNode;
let isBeforeDocument = false;
let isAfterDocument = false;
if (isOutsideDocument) {
let nextNode = node;
let previousNode = node;
while (nextNode !== null) {
if (nextNode === node.ownerDocument.documentElement) {
isBeforeDocument = true;
break;
}
nextNode = nextNode.nextSibling;
}
while (previousNode !== null) {
if (previousNode === node.ownerDocument.documentElement) {
isAfterDocument = true;
break;
}
previousNode = previousNode.previousSibling;
}
}
const afterDocument = isAfterDocument ? "\n" : "";
const beforeDocument = isBeforeDocument ? "\n" : "";
const encodedText = utils.encodeSpecialCharactersInText(node.data);
return `${afterDocument}<!--${encodedText}-->${beforeDocument}`;
}
/**
* Perform canonicalization of the given node
*
* @param node
* @api public
*/
process(node, options) {
options = options || {};
const defaultNs = options.defaultNs || "";
const defaultNsForPrefix = options.defaultNsForPrefix || {};
const ancestorNamespaces = options.ancestorNamespaces || [];
const prefixesInScope = [];
for (let i = 0; i < ancestorNamespaces.length; i++) {
prefixesInScope.push(ancestorNamespaces[i].prefix);
}
const res = this.processInner(node, prefixesInScope, defaultNs, defaultNsForPrefix, ancestorNamespaces);
return res;
}
getAlgorithmName() {
return "http://www.w3.org/TR/2001/REC-xml-c14n-20010315";
}
}
exports.C14nCanonicalization = C14nCanonicalization;
/**
* Add c14n#WithComments here (very simple subclass)
*/
class C14nCanonicalizationWithComments extends C14nCanonicalization {
constructor() {
super();
this.includeComments = true;
}
getAlgorithmName() {
return "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments";
}
}
exports.C14nCanonicalizationWithComments = C14nCanonicalizationWithComments;
//# sourceMappingURL=c14n-canonicalization.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,7 @@
import type { CanonicalizationOrTransformationAlgorithm, CanonicalizationOrTransformationAlgorithmProcessOptions, CanonicalizationOrTransformAlgorithmType } from "./types";
export declare class EnvelopedSignature implements CanonicalizationOrTransformationAlgorithm {
protected includeComments: boolean;
constructor();
process(node: Node, options: CanonicalizationOrTransformationAlgorithmProcessOptions): Node;
getAlgorithmName(): CanonicalizationOrTransformAlgorithmType;
}
@@ -0,0 +1,43 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EnvelopedSignature = void 0;
const xpath = require("xpath");
const isDomNode = require("@xmldom/is-dom-node");
class EnvelopedSignature {
constructor() {
this.includeComments = false;
this.includeComments = false;
}
process(node, options) {
if (null == options.signatureNode) {
const signature = xpath.select1("./*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", node);
if (isDomNode.isNodeLike(signature) && signature.parentNode) {
signature.parentNode.removeChild(signature);
}
return node;
}
const signatureNode = options.signatureNode;
const expectedSignatureValue = xpath.select1(".//*[local-name(.)='SignatureValue']/text()", signatureNode);
if (isDomNode.isTextNode(expectedSignatureValue)) {
const expectedSignatureValueData = expectedSignatureValue.data;
const signatures = xpath.select(".//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", node);
for (const nodeSignature of Array.isArray(signatures) ? signatures : []) {
const signatureValue = xpath.select1(".//*[local-name(.)='SignatureValue']/text()", nodeSignature);
if (isDomNode.isTextNode(signatureValue)) {
const signatureValueData = signatureValue.data;
if (expectedSignatureValueData === signatureValueData) {
if (nodeSignature.parentNode) {
nodeSignature.parentNode.removeChild(nodeSignature);
}
}
}
}
}
return node;
}
getAlgorithmName() {
return "http://www.w3.org/2000/09/xmldsig#enveloped-signature";
}
}
exports.EnvelopedSignature = EnvelopedSignature;
//# sourceMappingURL=enveloped-signature.js.map
@@ -0,0 +1 @@
{"version":3,"file":"enveloped-signature.js","sourceRoot":"","sources":["../src/enveloped-signature.ts"],"names":[],"mappings":";;;AAAA,+BAA+B;AAC/B,iDAAiD;AAQjD,MAAa,kBAAkB;IAG7B;QAFU,oBAAe,GAAG,KAAK,CAAC;QAGhC,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC;IAC/B,CAAC;IAED,OAAO,CAAC,IAAU,EAAE,OAAgE;QAClF,IAAI,IAAI,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;YAClC,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAC7B,0FAA0F,EAC1F,IAAI,CACL,CAAC;YACF,IAAI,SAAS,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,SAAS,CAAC,UAAU,EAAE,CAAC;gBAC5D,SAAS,CAAC,UAAU,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;YAC9C,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;QAC5C,MAAM,sBAAsB,GAAG,KAAK,CAAC,OAAO,CAC1C,6CAA6C,EAC7C,aAAa,CACd,CAAC;QACF,IAAI,SAAS,CAAC,UAAU,CAAC,sBAAsB,CAAC,EAAE,CAAC;YACjD,MAAM,0BAA0B,GAAG,sBAAsB,CAAC,IAAI,CAAC;YAE/D,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAC7B,2FAA2F,EAC3F,IAAI,CACL,CAAC;YACF,KAAK,MAAM,aAAa,IAAI,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;gBACxE,MAAM,cAAc,GAAG,KAAK,CAAC,OAAO,CAClC,6CAA6C,EAC7C,aAAa,CACd,CAAC;gBACF,IAAI,SAAS,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;oBACzC,MAAM,kBAAkB,GAAG,cAAc,CAAC,IAAI,CAAC;oBAC/C,IAAI,0BAA0B,KAAK,kBAAkB,EAAE,CAAC;wBACtD,IAAI,aAAa,CAAC,UAAU,EAAE,CAAC;4BAC7B,aAAa,CAAC,UAAU,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC;wBACtD,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,gBAAgB;QACd,OAAO,uDAAuD,CAAC;IACjE,CAAC;CACF;AAnDD,gDAmDC","sourcesContent":["import * as xpath from \"xpath\";\nimport * as isDomNode from \"@xmldom/is-dom-node\";\n\nimport type {\n CanonicalizationOrTransformationAlgorithm,\n CanonicalizationOrTransformationAlgorithmProcessOptions,\n CanonicalizationOrTransformAlgorithmType,\n} from \"./types\";\n\nexport class EnvelopedSignature implements CanonicalizationOrTransformationAlgorithm {\n protected includeComments = false;\n\n constructor() {\n this.includeComments = false;\n }\n\n process(node: Node, options: CanonicalizationOrTransformationAlgorithmProcessOptions): Node {\n if (null == options.signatureNode) {\n const signature = xpath.select1(\n \"./*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']\",\n node,\n );\n if (isDomNode.isNodeLike(signature) && signature.parentNode) {\n signature.parentNode.removeChild(signature);\n }\n return node;\n }\n const signatureNode = options.signatureNode;\n const expectedSignatureValue = xpath.select1(\n \".//*[local-name(.)='SignatureValue']/text()\",\n signatureNode,\n );\n if (isDomNode.isTextNode(expectedSignatureValue)) {\n const expectedSignatureValueData = expectedSignatureValue.data;\n\n const signatures = xpath.select(\n \".//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']\",\n node,\n );\n for (const nodeSignature of Array.isArray(signatures) ? signatures : []) {\n const signatureValue = xpath.select1(\n \".//*[local-name(.)='SignatureValue']/text()\",\n nodeSignature,\n );\n if (isDomNode.isTextNode(signatureValue)) {\n const signatureValueData = signatureValue.data;\n if (expectedSignatureValueData === signatureValueData) {\n if (nodeSignature.parentNode) {\n nodeSignature.parentNode.removeChild(nodeSignature);\n }\n }\n }\n }\n }\n return node;\n }\n\n getAlgorithmName(): CanonicalizationOrTransformAlgorithmType {\n return \"http://www.w3.org/2000/09/xmldsig#enveloped-signature\";\n }\n}\n"]}
@@ -0,0 +1,38 @@
import type { CanonicalizationOrTransformationAlgorithm, CanonicalizationOrTransformationAlgorithmProcessOptions } from "./types";
export declare class ExclusiveCanonicalization implements CanonicalizationOrTransformationAlgorithm {
protected includeComments: boolean;
constructor();
attrCompare(a: any, b: any): 1 | 0 | -1;
nsCompare(a: any, b: any): any;
renderAttrs(node: any): string;
/**
* Create the string of all namespace declarations that should appear on this element
*
* @param {Node} node. The node we now render
* @param {Array} prefixesInScope. The prefixes defined on this node
* parents which are a part of the output set
* @param {String} defaultNs. The current default namespace
* @return {String}
* @api private
*/
renderNs(node: any, prefixesInScope: any, defaultNs: any, defaultNsForPrefix: any, inclusiveNamespacesPrefixList: string[]): {
rendered: string;
newDefaultNs: 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 element node
*
* @api public
*/
process(elem: Element, options: CanonicalizationOrTransformationAlgorithmProcessOptions): string;
getAlgorithmName(): string;
}
export declare class ExclusiveCanonicalizationWithComments extends ExclusiveCanonicalization {
constructor();
getAlgorithmName(): string;
}
@@ -0,0 +1,246 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExclusiveCanonicalizationWithComments = exports.ExclusiveCanonicalization = void 0;
const utils = require("./utils");
const isDomNode = require("@xmldom/is-dom-node");
function isPrefixInScope(prefixesInScope, prefix, namespaceURI) {
let ret = false;
prefixesInScope.forEach(function (pf) {
if (pf.prefix === prefix && pf.namespaceURI === namespaceURI) {
ret = true;
}
});
return ret;
}
class ExclusiveCanonicalization {
constructor() {
this.includeComments = false;
this.includeComments = false;
}
attrCompare(a, b) {
if (!a.namespaceURI && b.namespaceURI) {
return -1;
}
if (!b.namespaceURI && a.namespaceURI) {
return 1;
}
const left = a.namespaceURI + a.localName;
const right = b.namespaceURI + b.localName;
if (left === right) {
return 0;
}
else if (left < right) {
return -1;
}
else {
return 1;
}
}
nsCompare(a, b) {
const attr1 = a.prefix;
const attr2 = b.prefix;
if (attr1 === attr2) {
return 0;
}
return attr1.localeCompare(attr2);
}
renderAttrs(node) {
let i;
let attr;
const res = [];
const attrListToRender = [];
if (isDomNode.isCommentNode(node)) {
return this.renderComment(node);
}
if (node.attributes) {
for (i = 0; i < node.attributes.length; ++i) {
attr = node.attributes[i];
//ignore namespace definition attributes
if (attr.name.indexOf("xmlns") === 0) {
continue;
}
attrListToRender.push(attr);
}
}
attrListToRender.sort(this.attrCompare);
for (attr of attrListToRender) {
res.push(" ", attr.name, '="', utils.encodeSpecialCharactersInAttribute(attr.value), '"');
}
return res.join("");
}
/**
* Create the string of all namespace declarations that should appear on this element
*
* @param {Node} node. The node we now render
* @param {Array} prefixesInScope. The prefixes defined on this node
* parents which are a part of the output set
* @param {String} defaultNs. The current default namespace
* @return {String}
* @api private
*/
renderNs(node, prefixesInScope, defaultNs, defaultNsForPrefix, inclusiveNamespacesPrefixList) {
let i;
let attr;
const res = [];
let newDefaultNs = defaultNs;
const nsListToRender = [];
const currNs = node.namespaceURI || "";
//handle the namespaceof the node itself
if (node.prefix) {
if (!isPrefixInScope(prefixesInScope, node.prefix, node.namespaceURI || defaultNsForPrefix[node.prefix])) {
nsListToRender.push({
prefix: node.prefix,
namespaceURI: node.namespaceURI || defaultNsForPrefix[node.prefix],
});
prefixesInScope.push({
prefix: node.prefix,
namespaceURI: node.namespaceURI || defaultNsForPrefix[node.prefix],
});
}
}
else if (defaultNs !== currNs) {
//new default ns
newDefaultNs = node.namespaceURI;
res.push(' xmlns="', newDefaultNs, '"');
}
//handle the attributes namespace
if (node.attributes) {
for (i = 0; i < node.attributes.length; ++i) {
attr = node.attributes[i];
//handle all prefixed attributes that are included in the prefix list and where
//the prefix is not defined already
if (attr.prefix &&
!isPrefixInScope(prefixesInScope, attr.localName, attr.value) &&
inclusiveNamespacesPrefixList.indexOf(attr.localName) >= 0) {
nsListToRender.push({ prefix: attr.localName, namespaceURI: attr.value });
prefixesInScope.push({ prefix: attr.localName, namespaceURI: attr.value });
}
//handle all prefixed attributes that are not xmlns definitions and where
//the prefix is not defined already
if (attr.prefix &&
!isPrefixInScope(prefixesInScope, attr.prefix, attr.namespaceURI) &&
attr.prefix !== "xmlns" &&
attr.prefix !== "xml") {
nsListToRender.push({ prefix: attr.prefix, namespaceURI: attr.namespaceURI });
prefixesInScope.push({ prefix: attr.prefix, namespaceURI: attr.namespaceURI });
}
}
}
nsListToRender.sort(this.nsCompare);
//render namespaces
for (const p of nsListToRender) {
res.push(" xmlns:", p.prefix, '="', p.namespaceURI, '"');
}
return { rendered: res.join(""), newDefaultNs: newDefaultNs };
}
/**
* @param node Node
*/
processInner(node, prefixesInScope, defaultNs, defaultNsForPrefix, inclusiveNamespacesPrefixList) {
if (isDomNode.isCommentNode(node)) {
return this.renderComment(node);
}
if (node.data) {
return utils.encodeSpecialCharactersInText(node.data);
}
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("");
}
throw new Error(`Unable to exclusive canonicalize node type: ${node.nodeType}`);
}
// Thanks to deoxxa/xml-c14n for comment renderer
renderComment(node) {
if (!this.includeComments) {
return "";
}
const isOutsideDocument = node.ownerDocument === node.parentNode;
let isBeforeDocument = false;
let isAfterDocument = false;
if (isOutsideDocument) {
let nextNode = node;
let previousNode = node;
while (nextNode != null) {
if (nextNode === node.ownerDocument.documentElement) {
isBeforeDocument = true;
break;
}
nextNode = nextNode.nextSibling;
}
while (previousNode != null) {
if (previousNode === node.ownerDocument.documentElement) {
isAfterDocument = true;
break;
}
previousNode = previousNode.previousSibling;
}
}
const afterDocument = isAfterDocument ? "\n" : "";
const beforeDocument = isBeforeDocument ? "\n" : "";
const encodedText = utils.encodeSpecialCharactersInText(node.data);
return `${afterDocument}<!--${encodedText}-->${beforeDocument}`;
}
/**
* Perform canonicalization of the given element node
*
* @api public
*/
process(elem, options) {
options = options || {};
let inclusiveNamespacesPrefixList = options.inclusiveNamespacesPrefixList || [];
const defaultNs = options.defaultNs || "";
const defaultNsForPrefix = options.defaultNsForPrefix || {};
const ancestorNamespaces = options.ancestorNamespaces || [];
/**
* If the inclusiveNamespacesPrefixList has not been explicitly provided then look it up in CanonicalizationMethod/InclusiveNamespaces
*/
if (!utils.isArrayHasLength(inclusiveNamespacesPrefixList)) {
const CanonicalizationMethod = utils.findChildren(elem, "CanonicalizationMethod");
if (CanonicalizationMethod.length !== 0) {
const inclusiveNamespaces = utils.findChildren(CanonicalizationMethod[0], "InclusiveNamespaces");
if (inclusiveNamespaces.length !== 0) {
inclusiveNamespacesPrefixList = (inclusiveNamespaces[0].getAttribute("PrefixList") || "").split(" ");
}
}
}
/**
* If you have a PrefixList then use it and the ancestors to add the necessary namespaces
*/
if (utils.isArrayHasLength(inclusiveNamespacesPrefixList)) {
inclusiveNamespacesPrefixList.forEach(function (prefix) {
if (ancestorNamespaces) {
ancestorNamespaces.forEach(function (ancestorNamespace) {
if (prefix === ancestorNamespace.prefix) {
elem.setAttributeNS("http://www.w3.org/2000/xmlns/", `xmlns:${prefix}`, ancestorNamespace.namespaceURI);
}
});
}
});
}
const res = this.processInner(elem, [], defaultNs, defaultNsForPrefix, inclusiveNamespacesPrefixList);
return res;
}
getAlgorithmName() {
return "http://www.w3.org/2001/10/xml-exc-c14n#";
}
}
exports.ExclusiveCanonicalization = ExclusiveCanonicalization;
class ExclusiveCanonicalizationWithComments extends ExclusiveCanonicalization {
constructor() {
super();
this.includeComments = true;
}
getAlgorithmName() {
return "http://www.w3.org/2001/10/xml-exc-c14n#WithComments";
}
}
exports.ExclusiveCanonicalizationWithComments = ExclusiveCanonicalizationWithComments;
//# sourceMappingURL=exclusive-canonicalization.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,13 @@
import type { HashAlgorithm } from "./types";
export declare class Sha1 implements HashAlgorithm {
getHash: (xml: any) => string;
getAlgorithmName: () => string;
}
export declare class Sha256 implements HashAlgorithm {
getHash: (xml: any) => string;
getAlgorithmName: () => string;
}
export declare class Sha512 implements HashAlgorithm {
getHash: (xml: any) => string;
getAlgorithmName: () => string;
}
@@ -0,0 +1,47 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Sha512 = exports.Sha256 = exports.Sha1 = void 0;
const crypto = require("crypto");
class Sha1 {
constructor() {
this.getHash = function (xml) {
const shasum = crypto.createHash("sha1");
shasum.update(xml, "utf8");
const res = shasum.digest("base64");
return res;
};
this.getAlgorithmName = function () {
return "http://www.w3.org/2000/09/xmldsig#sha1";
};
}
}
exports.Sha1 = Sha1;
class Sha256 {
constructor() {
this.getHash = function (xml) {
const shasum = crypto.createHash("sha256");
shasum.update(xml, "utf8");
const res = shasum.digest("base64");
return res;
};
this.getAlgorithmName = function () {
return "http://www.w3.org/2001/04/xmlenc#sha256";
};
}
}
exports.Sha256 = Sha256;
class Sha512 {
constructor() {
this.getHash = function (xml) {
const shasum = crypto.createHash("sha512");
shasum.update(xml, "utf8");
const res = shasum.digest("base64");
return res;
};
this.getAlgorithmName = function () {
return "http://www.w3.org/2001/04/xmlenc#sha512";
};
}
}
exports.Sha512 = Sha512;
//# sourceMappingURL=hash-algorithms.js.map
@@ -0,0 +1 @@
{"version":3,"file":"hash-algorithms.js","sourceRoot":"","sources":["../src/hash-algorithms.ts"],"names":[],"mappings":";;;AAAA,iCAAiC;AAGjC,MAAa,IAAI;IAAjB;QACE,YAAO,GAAG,UAAU,GAAG;YACrB,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;YACzC,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAC3B,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YACpC,OAAO,GAAG,CAAC;QACb,CAAC,CAAC;QAEF,qBAAgB,GAAG;YACjB,OAAO,wCAAwC,CAAC;QAClD,CAAC,CAAC;IACJ,CAAC;CAAA;AAXD,oBAWC;AAED,MAAa,MAAM;IAAnB;QACE,YAAO,GAAG,UAAU,GAAG;YACrB,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;YAC3C,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAC3B,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YACpC,OAAO,GAAG,CAAC;QACb,CAAC,CAAC;QAEF,qBAAgB,GAAG;YACjB,OAAO,yCAAyC,CAAC;QACnD,CAAC,CAAC;IACJ,CAAC;CAAA;AAXD,wBAWC;AAED,MAAa,MAAM;IAAnB;QACE,YAAO,GAAG,UAAU,GAAG;YACrB,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;YAC3C,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAC3B,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YACpC,OAAO,GAAG,CAAC;QACb,CAAC,CAAC;QAEF,qBAAgB,GAAG;YACjB,OAAO,yCAAyC,CAAC;QACnD,CAAC,CAAC;IACJ,CAAC;CAAA;AAXD,wBAWC","sourcesContent":["import * as crypto from \"crypto\";\nimport type { HashAlgorithm } from \"./types\";\n\nexport class Sha1 implements HashAlgorithm {\n getHash = function (xml) {\n const shasum = crypto.createHash(\"sha1\");\n shasum.update(xml, \"utf8\");\n const res = shasum.digest(\"base64\");\n return res;\n };\n\n getAlgorithmName = function () {\n return \"http://www.w3.org/2000/09/xmldsig#sha1\";\n };\n}\n\nexport class Sha256 implements HashAlgorithm {\n getHash = function (xml) {\n const shasum = crypto.createHash(\"sha256\");\n shasum.update(xml, \"utf8\");\n const res = shasum.digest(\"base64\");\n return res;\n };\n\n getAlgorithmName = function () {\n return \"http://www.w3.org/2001/04/xmlenc#sha256\";\n };\n}\n\nexport class Sha512 implements HashAlgorithm {\n getHash = function (xml) {\n const shasum = crypto.createHash(\"sha512\");\n shasum.update(xml, \"utf8\");\n const res = shasum.digest(\"base64\");\n return res;\n };\n\n getAlgorithmName = function () {\n return \"http://www.w3.org/2001/04/xmlenc#sha512\";\n };\n}\n"]}
+5
View File
@@ -0,0 +1,5 @@
export { C14nCanonicalization, C14nCanonicalizationWithComments } from "./c14n-canonicalization";
export { ExclusiveCanonicalization, ExclusiveCanonicalizationWithComments, } from "./exclusive-canonicalization";
export { SignedXml } from "./signed-xml";
export * from "./types";
export * from "./utils";
+28
View File
@@ -0,0 +1,28 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SignedXml = exports.ExclusiveCanonicalizationWithComments = exports.ExclusiveCanonicalization = exports.C14nCanonicalizationWithComments = exports.C14nCanonicalization = void 0;
var c14n_canonicalization_1 = require("./c14n-canonicalization");
Object.defineProperty(exports, "C14nCanonicalization", { enumerable: true, get: function () { return c14n_canonicalization_1.C14nCanonicalization; } });
Object.defineProperty(exports, "C14nCanonicalizationWithComments", { enumerable: true, get: function () { return c14n_canonicalization_1.C14nCanonicalizationWithComments; } });
var exclusive_canonicalization_1 = require("./exclusive-canonicalization");
Object.defineProperty(exports, "ExclusiveCanonicalization", { enumerable: true, get: function () { return exclusive_canonicalization_1.ExclusiveCanonicalization; } });
Object.defineProperty(exports, "ExclusiveCanonicalizationWithComments", { enumerable: true, get: function () { return exclusive_canonicalization_1.ExclusiveCanonicalizationWithComments; } });
var signed_xml_1 = require("./signed-xml");
Object.defineProperty(exports, "SignedXml", { enumerable: true, get: function () { return signed_xml_1.SignedXml; } });
__exportStar(require("./types"), exports);
__exportStar(require("./utils"), exports);
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,iEAAiG;AAAxF,6HAAA,oBAAoB,OAAA;AAAE,yIAAA,gCAAgC,OAAA;AAC/D,2EAGsC;AAFpC,uIAAA,yBAAyB,OAAA;AACzB,mJAAA,qCAAqC,OAAA;AAEvC,2CAAyC;AAAhC,uGAAA,SAAS,OAAA;AAClB,0CAAwB;AACxB,0CAAwB","sourcesContent":["export { C14nCanonicalization, C14nCanonicalizationWithComments } from \"./c14n-canonicalization\";\nexport {\n ExclusiveCanonicalization,\n ExclusiveCanonicalizationWithComments,\n} from \"./exclusive-canonicalization\";\nexport { SignedXml } from \"./signed-xml\";\nexport * from \"./types\";\nexport * from \"./utils\";\n"]}
@@ -0,0 +1,47 @@
/// <reference types="node" />
import * as crypto from "crypto";
import { type SignatureAlgorithm } from "./types";
export declare class RsaSha1 implements SignatureAlgorithm {
getSignature: {
(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike): string;
(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike, args_2: import("./types").ErrorFirstCallback<string>): void;
};
verifySignature: {
(material: string, key: crypto.KeyLike, signatureValue: string): boolean;
(material: string, key: crypto.KeyLike, signatureValue: string, args_3: import("./types").ErrorFirstCallback<boolean>): void;
};
getAlgorithmName: () => string;
}
export declare class RsaSha256 implements SignatureAlgorithm {
getSignature: {
(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike): string;
(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike, args_2: import("./types").ErrorFirstCallback<string>): void;
};
verifySignature: {
(material: string, key: crypto.KeyLike, signatureValue: string): boolean;
(material: string, key: crypto.KeyLike, signatureValue: string, args_3: import("./types").ErrorFirstCallback<boolean>): void;
};
getAlgorithmName: () => string;
}
export declare class RsaSha512 implements SignatureAlgorithm {
getSignature: {
(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike): string;
(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike, args_2: import("./types").ErrorFirstCallback<string>): void;
};
verifySignature: {
(material: string, key: crypto.KeyLike, signatureValue: string): boolean;
(material: string, key: crypto.KeyLike, signatureValue: string, args_3: import("./types").ErrorFirstCallback<boolean>): void;
};
getAlgorithmName: () => string;
}
export declare class HmacSha1 implements SignatureAlgorithm {
getSignature: {
(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike): string;
(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike, args_2: import("./types").ErrorFirstCallback<string>): void;
};
verifySignature: {
(material: string, key: crypto.KeyLike, signatureValue: string): boolean;
(material: string, key: crypto.KeyLike, signatureValue: string, args_3: import("./types").ErrorFirstCallback<boolean>): void;
};
getAlgorithmName: () => string;
}
@@ -0,0 +1,86 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HmacSha1 = exports.RsaSha512 = exports.RsaSha256 = exports.RsaSha1 = void 0;
const crypto = require("crypto");
const types_1 = require("./types");
class RsaSha1 {
constructor() {
this.getSignature = (0, types_1.createOptionalCallbackFunction)((signedInfo, privateKey) => {
const signer = crypto.createSign("RSA-SHA1");
signer.update(signedInfo);
const res = signer.sign(privateKey, "base64");
return res;
});
this.verifySignature = (0, types_1.createOptionalCallbackFunction)((material, key, signatureValue) => {
const verifier = crypto.createVerify("RSA-SHA1");
verifier.update(material);
const res = verifier.verify(key, signatureValue, "base64");
return res;
});
this.getAlgorithmName = () => {
return "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
};
}
}
exports.RsaSha1 = RsaSha1;
class RsaSha256 {
constructor() {
this.getSignature = (0, types_1.createOptionalCallbackFunction)((signedInfo, privateKey) => {
const signer = crypto.createSign("RSA-SHA256");
signer.update(signedInfo);
const res = signer.sign(privateKey, "base64");
return res;
});
this.verifySignature = (0, types_1.createOptionalCallbackFunction)((material, key, signatureValue) => {
const verifier = crypto.createVerify("RSA-SHA256");
verifier.update(material);
const res = verifier.verify(key, signatureValue, "base64");
return res;
});
this.getAlgorithmName = () => {
return "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
};
}
}
exports.RsaSha256 = RsaSha256;
class RsaSha512 {
constructor() {
this.getSignature = (0, types_1.createOptionalCallbackFunction)((signedInfo, privateKey) => {
const signer = crypto.createSign("RSA-SHA512");
signer.update(signedInfo);
const res = signer.sign(privateKey, "base64");
return res;
});
this.verifySignature = (0, types_1.createOptionalCallbackFunction)((material, key, signatureValue) => {
const verifier = crypto.createVerify("RSA-SHA512");
verifier.update(material);
const res = verifier.verify(key, signatureValue, "base64");
return res;
});
this.getAlgorithmName = () => {
return "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512";
};
}
}
exports.RsaSha512 = RsaSha512;
class HmacSha1 {
constructor() {
this.getSignature = (0, types_1.createOptionalCallbackFunction)((signedInfo, privateKey) => {
const signer = crypto.createHmac("SHA1", privateKey);
signer.update(signedInfo);
const res = signer.digest("base64");
return res;
});
this.verifySignature = (0, types_1.createOptionalCallbackFunction)((material, key, signatureValue) => {
const verifier = crypto.createHmac("SHA1", key);
verifier.update(material);
const res = verifier.digest("base64");
return res === signatureValue;
});
this.getAlgorithmName = () => {
return "http://www.w3.org/2000/09/xmldsig#hmac-sha1";
};
}
}
exports.HmacSha1 = HmacSha1;
//# sourceMappingURL=signature-algorithms.js.map
File diff suppressed because one or more lines are too long
+225
View File
@@ -0,0 +1,225 @@
/// <reference types="node" />
import type { CanonicalizationAlgorithmType, CanonicalizationOrTransformAlgorithmType, CanonicalizationOrTransformationAlgorithm, CanonicalizationOrTransformationAlgorithmProcessOptions, ComputeSignatureOptions, ErrorFirstCallback, GetKeyInfoContentArgs, HashAlgorithm, HashAlgorithmType, Reference, SignatureAlgorithm, SignatureAlgorithmType, SignedXmlOptions } from "./types";
import * as crypto from "crypto";
export declare class SignedXml {
idMode?: "wssecurity";
idAttributes: string[];
/**
* A {@link Buffer} or pem encoded {@link String} containing your private key
*/
privateKey?: crypto.KeyLike;
publicCert?: crypto.KeyLike;
/**
* One of the supported signature algorithms.
* @see {@link SignatureAlgorithmType}
*/
signatureAlgorithm?: SignatureAlgorithmType;
/**
* Rules used to convert an XML document into its canonical form.
*/
canonicalizationAlgorithm?: CanonicalizationAlgorithmType;
/**
* It specifies a list of namespace prefixes that should be considered "inclusive" during the canonicalization process.
*/
inclusiveNamespacesPrefixList: string[];
namespaceResolver: XPathNSResolver;
implicitTransforms: ReadonlyArray<CanonicalizationOrTransformAlgorithmType>;
keyInfoAttributes: {
[attrName: string]: string;
};
getKeyInfoContent: typeof SignedXml.getKeyInfoContent;
getCertFromKeyInfo: typeof SignedXml.getCertFromKeyInfo;
private id;
private signedXml;
private signatureXml;
private signatureNode;
private signatureValue;
private originalXmlWithIds;
private keyInfo;
/**
* Contains the references that were signed.
* @see {@link Reference}
*/
private 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.
*/
private 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}
*/
CanonicalizationAlgorithms: Record<CanonicalizationOrTransformAlgorithmType, new () => CanonicalizationOrTransformationAlgorithm>;
/**
* 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}
*/
HashAlgorithms: Record<HashAlgorithmType, new () => HashAlgorithm>;
/**
* 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}
*/
SignatureAlgorithms: Record<SignatureAlgorithmType, new () => SignatureAlgorithm>;
static defaultNsForPrefix: {
ds: string;
};
static noop: () => null;
/**
* The SignedXml constructor provides an abstraction for sign and verify xml documents. The object is constructed using
* @param options {@link SignedXmlOptions}
*/
constructor(options?: SignedXmlOptions);
/**
* 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(): void;
/**
* 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 }: GetKeyInfoContentArgs): string | null;
/**
* 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?: Node | null): string | null;
/**
* Validates the signature of the provided XML document synchronously using the configured key info provider.
*
* @param xml The XML document containing the signature to be validated.
* @returns `true` if the signature is valid
* @throws Error if no key info resolver is provided.
*/
checkSignature(xml: string): boolean;
/**
* Validates the signature of the provided XML document synchronously using the configured key info provider.
*
* @param xml The XML document containing the signature to be validated.
* @param callback Callback function to handle the validation result asynchronously.
* @throws Error if the last parameter is provided and is not a function, or if no key info resolver is provided.
*/
checkSignature(xml: string, callback: (error: Error | null, isValid?: boolean) => void): void;
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[];
/**
* Loads the signature information from the provided XML node or string.
*
* @param signatureNode The XML node or string representing the signature.
*/
loadSignature(signatureNode: Node | string): void;
/**
* Load the reference xml node to a model
*
*/
private loadReference;
/**
* 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, }: Partial<Reference> & Pick<Reference, "xpath">): void;
/**
* Returns the list of references.
*/
getReferences(): Reference[];
getSignedReferences(): string[];
/**
* Compute the signature of the given XML (using the already defined settings).
*
* @param xml The XML to compute the signature for.
* @param callback A callback function to handle the signature computation asynchronously.
* @returns void
* @throws TypeError If the xml can not be parsed.
*/
computeSignature(xml: string): void;
/**
* Compute the signature of the given XML (using the already defined settings).
*
* @param xml The XML to compute the signature for.
* @param callback A callback function to handle the signature computation asynchronously.
* @returns void
* @throws TypeError If the xml can not be parsed.
*/
computeSignature(xml: string, callback: ErrorFirstCallback<SignedXml>): void;
/**
* Compute the signature of the given XML (using the already defined settings).
*
* @param xml The XML to compute the signature for.
* @param opts An object containing options for the signature computation.
* @returns If no callback is provided, returns `this` (the instance of SignedXml).
* @throws TypeError If the xml can not be parsed, or Error if there were invalid options passed.
*/
computeSignature(xml: string, options: ComputeSignatureOptions): void;
/**
* Compute the signature of the given XML (using the already defined settings).
*
* @param xml The XML to compute the signature for.
* @param opts An object containing options for the signature computation.
* @param callback A callback function to handle the signature computation asynchronously.
* @returns void
* @throws TypeError If the xml can not be parsed, or Error if there were invalid options passed.
*/
computeSignature(xml: string, options: ComputeSignatureOptions, callback: ErrorFirstCallback<SignedXml>): void;
private getKeyInfo;
/**
* Generate the Reference nodes (as part of the signature process)
*
*/
private createReferences;
getCanonXml(transforms: Reference["transforms"], node: Node, options?: CanonicalizationOrTransformationAlgorithmProcessOptions): string;
/**
* Ensure an element has Id attribute. If not create it with unique value.
* Work with both normal and wssecurity Id flavour
*/
private ensureHasId;
/**
* Create the SignedInfo element
*
*/
private createSignedInfo;
/**
* Create the Signature element
*
*/
private createSignature;
/**
* Returns just the signature part, must be called only after {@link computeSignature}
*
* @returns The signature XML.
*/
getSignatureXml(): string;
/**
* 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(): string;
/**
* Returns the original xml document with the signature in it, must be called only after {@link computeSignature}
*
* @returns The signed XML.
*/
getSignedXml(): string;
}
+960
View File
@@ -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
File diff suppressed because one or more lines are too long
+125
View File
@@ -0,0 +1,125 @@
/// <reference types="node" />
import * as crypto from "crypto";
export type ErrorFirstCallback<T> = (err: Error | null, result?: T) => void;
export type CanonicalizationAlgorithmType = "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" | string;
export type CanonicalizationOrTransformAlgorithmType = CanonicalizationAlgorithmType | "http://www.w3.org/2000/09/xmldsig#enveloped-signature";
export type HashAlgorithmType = "http://www.w3.org/2000/09/xmldsig#sha1" | "http://www.w3.org/2001/04/xmlenc#sha256" | "http://www.w3.org/2001/04/xmlenc#sha512" | string;
export type SignatureAlgorithmType = "http://www.w3.org/2000/09/xmldsig#rsa-sha1" | "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" | "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512" | "http://www.w3.org/2000/09/xmldsig#hmac-sha1" | string;
/**
* @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
*/
export interface GetKeyInfoContentArgs {
publicCert?: crypto.KeyLike;
prefix?: string | null;
}
/**
* Options for the SignedXml constructor.
*/
export interface SignedXmlOptions {
idMode?: "wssecurity";
idAttribute?: string;
privateKey?: crypto.KeyLike;
publicCert?: crypto.KeyLike;
signatureAlgorithm?: SignatureAlgorithmType;
canonicalizationAlgorithm?: CanonicalizationAlgorithmType;
inclusiveNamespacesPrefixList?: string | string[];
implicitTransforms?: ReadonlyArray<CanonicalizationOrTransformAlgorithmType>;
keyInfoAttributes?: Record<string, string>;
getKeyInfoContent?(args?: GetKeyInfoContentArgs): string | null;
getCertFromKeyInfo?(keyInfo?: Node | null): string | null;
}
export interface NamespacePrefix {
prefix: string;
namespaceURI: string;
}
export interface RenderedNamespace {
rendered: string;
newDefaultNs: string;
}
export interface CanonicalizationOrTransformationAlgorithmProcessOptions {
defaultNs?: string;
defaultNsForPrefix?: Record<string, string>;
ancestorNamespaces?: NamespacePrefix[];
signatureNode?: Node | null;
inclusiveNamespacesPrefixList?: string[];
}
export interface ComputeSignatureOptionsLocation {
reference?: string;
action?: "append" | "prepend" | "before" | "after";
}
/**
* Options for the computeSignature method.
*
* - `prefix` {String} Adds a prefix for the generated signature tags
* - `attrs` {Object} A hash of attributes and values `attrName: value` to add to the signature root node
* - `location` {{ reference: String, action: String }}
* - `existingPrefixes` {Object} A hash of prefixes and namespaces `prefix: namespace` already in the xml
* An object with a `reference` key which should
* contain a XPath expression, an `action` key which
* should contain one of the following values:
* `append`, `prepend`, `before`, `after`
*/
export interface ComputeSignatureOptions {
prefix?: string;
attrs?: Record<string, string>;
location?: ComputeSignatureOptionsLocation;
existingPrefixes?: Record<string, string>;
}
/**
* Represents a reference node for XML digital signature.
*/
export interface Reference {
xpath?: string;
transforms: ReadonlyArray<CanonicalizationOrTransformAlgorithmType>;
digestAlgorithm: HashAlgorithmType;
uri: string;
digestValue?: unknown;
inclusiveNamespacesPrefixList: string[];
isEmptyUri: boolean;
ancestorNamespaces?: NamespacePrefix[];
validationError?: Error;
getValidatedNode(xpathSelector?: string): Node | null;
signedReference?: string;
}
/** Implement this to create a new CanonicalizationOrTransformationAlgorithm */
export interface CanonicalizationOrTransformationAlgorithm {
process(node: Node, options: CanonicalizationOrTransformationAlgorithmProcessOptions): Node | string;
getAlgorithmName(): CanonicalizationOrTransformAlgorithmType;
}
/** Implement this to create a new HashAlgorithm */
export interface HashAlgorithm {
getAlgorithmName(): HashAlgorithmType;
getHash(xml: string): string;
}
/** Extend this to create a new SignatureAlgorithm */
export interface SignatureAlgorithm {
/**
* Sign the given string using the given key
*/
getSignature(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike): string;
getSignature(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike, callback?: ErrorFirstCallback<string>): void;
/**
* Verify the given signature of the given string using key
*
* @param key a public cert, public key, or private key can be passed here
*/
verifySignature(material: string, key: crypto.KeyLike, signatureValue: string): boolean;
verifySignature(material: string, key: crypto.KeyLike, signatureValue: string, callback?: ErrorFirstCallback<boolean>): void;
getAlgorithmName(): SignatureAlgorithmType;
}
/** Implement this to create a new TransformAlgorithm */
export interface TransformAlgorithm {
getAlgorithmName(): CanonicalizationOrTransformAlgorithmType;
process(node: Node): string;
}
/**
* This function will add a callback version of a sync function.
*
* This follows the factory pattern.
* Just call this function, passing the function that you'd like to add a callback version of.
*/
export declare function createOptionalCallbackFunction<T, A extends unknown[]>(syncVersion: (...args: A) => T): {
(...args: A): T;
(...args: [...A, ErrorFirstCallback<T>]): void;
};
+57
View File
@@ -0,0 +1,57 @@
"use strict";
/* eslint-disable no-unused-vars */
// Type definitions for @node-saml/xml-crypto
// Project: https://github.com/node-saml/xml-crypto#readme
// Original definitions by: Eric Heikes <https://github.com/eheikes>
// Max Chehab <https://github.com/maxchehab>
Object.defineProperty(exports, "__esModule", { value: true });
exports.createOptionalCallbackFunction = void 0;
/**
* ### Sign
* #### Properties
* - {@link SignedXml#privateKey} [required]
* - {@link SignedXml#publicCert} [optional]
* - {@link SignedXml#signatureAlgorithm} [optional]
* - {@link SignedXml#canonicalizationAlgorithm} [optional]
* #### Api
* - {@link SignedXml#addReference}
* - {@link SignedXml#computeSignature}
* - {@link SignedXml#getSignedXml}
* - {@link SignedXml#getSignatureXml}
* - {@link SignedXml#getOriginalXmlWithIds}
*
* ### Verify
* #### Properties
* - {@link SignedXml#publicCert} [optional]
* #### Api
* - {@link SignedXml#loadSignature}
* - {@link SignedXml#checkSignature}
*/
function isErrorFirstCallback(possibleCallback) {
return typeof possibleCallback === "function";
}
/**
* This function will add a callback version of a sync function.
*
* This follows the factory pattern.
* Just call this function, passing the function that you'd like to add a callback version of.
*/
function createOptionalCallbackFunction(syncVersion) {
return ((...args) => {
const possibleCallback = args[args.length - 1];
if (isErrorFirstCallback(possibleCallback)) {
try {
const result = syncVersion(...args.slice(0, -1));
possibleCallback(null, result);
}
catch (err) {
possibleCallback(err instanceof Error ? err : new Error("Unknown error"));
}
}
else {
return syncVersion(...args);
}
});
}
exports.createOptionalCallbackFunction = createOptionalCallbackFunction;
//# sourceMappingURL=types.js.map
File diff suppressed because one or more lines are too long
+65
View File
@@ -0,0 +1,65 @@
/// <reference types="node" />
import type { NamespacePrefix } from "./types";
export declare function isArrayHasLength(array: unknown): array is unknown[];
export declare function findAttr(element: Element, localName: string, namespace?: string): Attr | null;
export declare function findChildren(node: Node | Document, localName: string, namespace?: string): Element[];
/** @deprecated */
export declare function findChilds(node: Node | Document, localName: string, namespace?: string): Element[];
export declare function encodeSpecialCharactersInAttribute(attributeValue: any): any;
export declare function encodeSpecialCharactersInText(text: string): string;
/**
* PEM format has wide range of usages, but this library
* is enforcing RFC7468 which focuses on PKIX, PKCS and CMS.
*
* https://www.rfc-editor.org/rfc/rfc7468
*
* PEM_FORMAT_REGEX is validating given PEM file against RFC7468 'stricttextualmsg' definition.
*
* With few exceptions;
* - 'posteb' MAY have 'eol', but it is not mandatory.
* - 'preeb' and 'posteb' lines are limited to 64 characters, but
* should not cause any issues in context of PKIX, PKCS and CMS.
*/
export declare const PEM_FORMAT_REGEX: RegExp;
export declare const EXTRACT_X509_CERTS: RegExp;
export declare const BASE64_REGEX: RegExp;
/**
* -----BEGIN [LABEL]-----
* base64([DATA])
* -----END [LABEL]-----
*
* Above is shown what PEM file looks like. As can be seen, base64 data
* can be in single line or multiple lines.
*
* This function normalizes PEM presentation to;
* - contain PEM header and footer as they are given
* - normalize line endings to '\n'
* - normalize line length to maximum of 64 characters
* - ensure that 'preeb' has line ending '\n'
*
* With a couple of notes:
* - 'eol' is normalized to '\n'
*
* @param pem The PEM string to normalize to RFC7468 'stricttextualmsg' definition
*/
export declare function normalizePem(pem: string): string;
/**
* @param pem The PEM-encoded base64 certificate to strip headers from
*/
export declare function pemToDer(pem: string): Buffer;
/**
* @param der The DER-encoded base64 certificate to add PEM headers too
* @param pemLabel The label of the header and footer to add
*/
export declare function derToPem(der: string | Buffer, pemLabel?: "CERTIFICATE" | "PRIVATE KEY" | "RSA PUBLIC KEY"): string;
/**
* Extract ancestor namespaces in order to import it to root of document subset
* which is being canonicalized for non-exclusive c14n.
*
* @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: Document, docSubsetXpath?: string, namespaceResolver?: XPathNSResolver): NamespacePrefix[];
export declare function validateDigestValue(digest: any, expectedDigest: any): boolean;
+256
View File
@@ -0,0 +1,256 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateDigestValue = exports.findAncestorNs = exports.derToPem = exports.pemToDer = exports.normalizePem = exports.BASE64_REGEX = exports.EXTRACT_X509_CERTS = exports.PEM_FORMAT_REGEX = exports.encodeSpecialCharactersInText = exports.encodeSpecialCharactersInAttribute = exports.findChilds = exports.findChildren = exports.findAttr = exports.isArrayHasLength = void 0;
const xpath = require("xpath");
const isDomNode = require("@xmldom/is-dom-node");
function isArrayHasLength(array) {
return Array.isArray(array) && array.length > 0;
}
exports.isArrayHasLength = isArrayHasLength;
function attrEqualsExplicitly(attr, localName, namespace) {
return attr.localName === localName && (attr.namespaceURI === namespace || namespace == null);
}
function attrEqualsImplicitly(attr, localName, namespace, node) {
return (attr.localName === localName &&
((!attr.namespaceURI && node?.namespaceURI === namespace) || namespace == null));
}
function findAttr(element, localName, namespace) {
for (let i = 0; i < element.attributes.length; i++) {
const attr = element.attributes[i];
if (attrEqualsExplicitly(attr, localName, namespace) ||
attrEqualsImplicitly(attr, localName, namespace, element)) {
return attr;
}
}
return null;
}
exports.findAttr = findAttr;
function findChildren(node, localName, namespace) {
const element = node.documentElement ?? node;
const res = [];
for (let i = 0; i < element.childNodes.length; i++) {
const child = element.childNodes[i];
if (isDomNode.isElementNode(child) &&
child.localName === localName &&
(child.namespaceURI === namespace || namespace == null)) {
res.push(child);
}
}
return res;
}
exports.findChildren = findChildren;
/** @deprecated */
function findChilds(node, localName, namespace) {
return findChildren(node, localName, namespace);
}
exports.findChilds = findChilds;
const xml_special_to_encoded_attribute = {
"&": "&amp;",
"<": "&lt;",
'"': "&quot;",
"\r": "&#xD;",
"\n": "&#xA;",
"\t": "&#x9;",
};
const xml_special_to_encoded_text = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
"\r": "&#xD;",
};
function encodeSpecialCharactersInAttribute(attributeValue) {
return attributeValue.replace(/([&<"\r\n\t])/g, function (str, item) {
/** Special character normalization.
* @see:
* - https://www.w3.org/TR/xml-c14n#ProcessingModel (Attribute Nodes)
* - https://www.w3.org/TR/xml-c14n#Example-Chars
*/
return xml_special_to_encoded_attribute[item];
});
}
exports.encodeSpecialCharactersInAttribute = encodeSpecialCharactersInAttribute;
function encodeSpecialCharactersInText(text) {
return text.replace(/([&<>\r])/g, function (str, item) {
/** Special character normalization.
* @see:
* - https://www.w3.org/TR/xml-c14n#ProcessingModel (Text Nodes)
* - https://www.w3.org/TR/xml-c14n#Example-Chars
*/
return xml_special_to_encoded_text[item];
});
}
exports.encodeSpecialCharactersInText = encodeSpecialCharactersInText;
/**
* PEM format has wide range of usages, but this library
* is enforcing RFC7468 which focuses on PKIX, PKCS and CMS.
*
* https://www.rfc-editor.org/rfc/rfc7468
*
* PEM_FORMAT_REGEX is validating given PEM file against RFC7468 'stricttextualmsg' definition.
*
* With few exceptions;
* - 'posteb' MAY have 'eol', but it is not mandatory.
* - 'preeb' and 'posteb' lines are limited to 64 characters, but
* should not cause any issues in context of PKIX, PKCS and CMS.
*/
exports.PEM_FORMAT_REGEX = new RegExp("^-----BEGIN [A-Z\x20]{1,48}-----([^-]*)-----END [A-Z\x20]{1,48}-----$", "s");
exports.EXTRACT_X509_CERTS = new RegExp("-----BEGIN CERTIFICATE-----[^-]*-----END CERTIFICATE-----", "g");
exports.BASE64_REGEX = new RegExp("^(?:[A-Za-z0-9\\+\\/]{4}\\n{0,1})*(?:[A-Za-z0-9\\+\\/]{2}==|[A-Za-z0-9\\+\\/]{3}=)?$", "s");
/**
* -----BEGIN [LABEL]-----
* base64([DATA])
* -----END [LABEL]-----
*
* Above is shown what PEM file looks like. As can be seen, base64 data
* can be in single line or multiple lines.
*
* This function normalizes PEM presentation to;
* - contain PEM header and footer as they are given
* - normalize line endings to '\n'
* - normalize line length to maximum of 64 characters
* - ensure that 'preeb' has line ending '\n'
*
* With a couple of notes:
* - 'eol' is normalized to '\n'
*
* @param pem The PEM string to normalize to RFC7468 'stricttextualmsg' definition
*/
function normalizePem(pem) {
return `${(pem
.trim()
.replace(/(\r\n|\r)/g, "\n")
.match(/.{1,64}/g) ?? []).join("\n")}\n`;
}
exports.normalizePem = normalizePem;
/**
* @param pem The PEM-encoded base64 certificate to strip headers from
*/
function pemToDer(pem) {
if (!exports.PEM_FORMAT_REGEX.test(pem.trim())) {
throw new Error("Invalid PEM format.");
}
return Buffer.from(pem
.replace(/(\r\n|\r)/g, "")
.replace(/-----BEGIN [A-Z\x20]{1,48}-----\n?/, "")
.replace(/-----END [A-Z\x20]{1,48}-----\n?/, ""), "base64");
}
exports.pemToDer = pemToDer;
/**
* @param der The DER-encoded base64 certificate to add PEM headers too
* @param pemLabel The label of the header and footer to add
*/
function derToPem(der, pemLabel) {
const base64Der = Buffer.isBuffer(der)
? der.toString("base64").trim()
: der.replace(/(\r\n|\r)/g, "").trim();
if (exports.PEM_FORMAT_REGEX.test(base64Der)) {
return normalizePem(base64Der);
}
if (exports.BASE64_REGEX.test(base64Der.replace(/ /g, ""))) {
if (pemLabel == null) {
throw new Error("PEM label is required when DER is given.");
}
const pem = `-----BEGIN ${pemLabel}-----\n${base64Der.replace(/ /g, "")}\n-----END ${pemLabel}-----`;
return normalizePem(pem);
}
throw new Error("Unknown DER format.");
}
exports.derToPem = derToPem;
function collectAncestorNamespaces(node, nsArray = []) {
if (!isDomNode.isElementNode(node.parentNode)) {
return nsArray;
}
const parent = node.parentNode;
if (!parent) {
return nsArray;
}
if (parent.attributes && parent.attributes.length > 0) {
for (let i = 0; i < parent.attributes.length; i++) {
const attr = parent.attributes[i];
if (attr && attr.nodeName && attr.nodeName.search(/^xmlns:?/) !== -1) {
nsArray.push({
prefix: attr.nodeName.replace(/^xmlns:?/, ""),
namespaceURI: attr.nodeValue || "",
});
}
}
}
return collectAncestorNamespaces(parent, nsArray);
}
function findNSPrefix(subset) {
const subsetAttributes = subset.attributes;
for (let k = 0; k < subsetAttributes.length; k++) {
const nodeName = subsetAttributes[k].nodeName;
if (nodeName.search(/^xmlns:?/) !== -1) {
return nodeName.replace(/^xmlns:?/, "");
}
}
return subset.prefix || "";
}
function isElementSubset(docSubset) {
return docSubset.every((node) => isDomNode.isElementNode(node));
}
/**
* Extract ancestor namespaces in order to import it to root of document subset
* which is being canonicalized for non-exclusive c14n.
*
* @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);
if (!isArrayHasLength(docSubset)) {
return [];
}
if (!isElementSubset(docSubset)) {
throw new Error("Document subset must be list of elements");
}
// Remove duplicate on ancestor namespace
const ancestorNs = collectAncestorNamespaces(docSubset[0]);
const ancestorNsWithoutDuplicate = [];
for (let i = 0; i < ancestorNs.length; i++) {
let notOnTheList = true;
for (const v in ancestorNsWithoutDuplicate) {
if (ancestorNsWithoutDuplicate[v].prefix === ancestorNs[i].prefix) {
notOnTheList = false;
break;
}
}
if (notOnTheList) {
ancestorNsWithoutDuplicate.push(ancestorNs[i]);
}
}
// Remove namespaces which are already declared in the subset with the same prefix
const returningNs = [];
const subsetNsPrefix = findNSPrefix(docSubset[0]);
for (const ancestorNs of ancestorNsWithoutDuplicate) {
if (ancestorNs.prefix !== subsetNsPrefix) {
returningNs.push(ancestorNs);
}
}
return returningNs;
}
exports.findAncestorNs = findAncestorNs;
function validateDigestValue(digest, expectedDigest) {
const buffer = Buffer.from(digest, "base64");
const expectedBuffer = Buffer.from(expectedDigest, "base64");
if (typeof buffer.equals === "function") {
return buffer.equals(expectedBuffer);
}
if (buffer.length !== expectedBuffer.length) {
return false;
}
for (let i = 0; i < buffer.length; i++) {
if (buffer[i] !== expectedBuffer[i]) {
return false;
}
}
return true;
}
exports.validateDigestValue = validateDigestValue;
//# sourceMappingURL=utils.js.map
File diff suppressed because one or more lines are too long
+73
View File
@@ -0,0 +1,73 @@
{
"name": "xml-crypto",
"version": "6.1.2",
"private": false,
"description": "Xml digital signature and encryption library for Node.js",
"keywords": [
"xml",
"digital signature",
"xml encryption",
"x.509 certificate"
],
"repository": {
"type": "git",
"url": "https://github.com/node-saml/xml-crypto.git"
},
"license": "MIT",
"author": "Yaron Naveh <yaronn01@gmail.com> (http://webservices20.blogspot.com/)",
"contributors": [
"LoneRifle <LoneRifle@users.noreply.github.com>",
"Chris Barth <chrisjbarth@hotmail.com>"
],
"main": "./lib",
"files": [
"lib",
"LICENSE",
"README.md"
],
"scripts": {
"build": "npx tsc",
"changelog": "gren changelog --override --generate",
"lint": "eslint \"{src,test}/*.ts\" --cache && npm run prettier-check",
"lint:fix": "eslint --fix \"{src,test}/*.ts\" && npm run prettier-format",
"prepare": "tsc",
"prettier-check": "prettier --config .prettierrc.json --check .",
"prettier-format": "prettier --config .prettierrc.json --write .",
"prerelease": "git clean -xfd && npm ci && npm test",
"release": "release-it",
"test": "nyc mocha"
},
"dependencies": {
"@xmldom/is-dom-node": "^1.0.1",
"@xmldom/xmldom": "^0.8.10",
"xpath": "^0.0.33"
},
"devDependencies": {
"@cjbarth/github-release-notes": "^4.2.0",
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@prettier/plugin-xml": "^3.2.2",
"@types/chai": "^4.3.11",
"@types/mocha": "^10.0.6",
"@types/node": "^16.18.69",
"@typescript-eslint/eslint-plugin": "^6.18.1",
"@typescript-eslint/parser": "^6.18.1",
"chai": "^4.3.10",
"choma": "^1.2.1",
"ejs": "^3.1.9",
"eslint": "^8.56.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.1.0",
"prettier-plugin-packagejson": "^2.4.6",
"release-it": "^16.3.0",
"source-map-support": "^0.5.21",
"ts-node": "^10.9.1",
"typescript": "^5.3.2"
},
"engines": {
"node": ">=16"
}
}