diff --git a/.eslintrc.json b/.eslintrc.json
index fcce24d1..15e31a56 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -6,7 +6,7 @@
},
"root": true,
"parserOptions": {
- "ecmaVersion": 6
+ "ecmaVersion": 2020
},
"extends": ["eslint:recommended", "prettier"],
"rules": {
diff --git a/README.md b/README.md
index 8f743d00..bcd9e77c 100644
--- a/README.md
+++ b/README.md
@@ -68,7 +68,6 @@ _Signature Algorithm:_ RSA-SHA1 http://www.w3.org/2000/09/xmldsig#rsa-sha1
When signing a xml document you can specify the following properties on a `SignedXml` instance to customize the signature process:
- `sign.signingKey` - **[required]** a `Buffer` or pem encoded `String` containing your private key
-- `sign.keyInfoProvider` - **[optional]** a key info provider instance, see [customizing algorithms](#customizing-algorithms) for an implementation example
- `sign.signatureAlgorithm` - **[optional]** one of the supported [signature algorithms](#signature-algorithms). Ex: `sign.signatureAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"`
- `sign.canonicalizationAlgorithm` - **[optional]** one of the supported [canonicalization algorithms](#canonicalization-and-transformation-algorithms). Ex: `sign.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#WithComments"`
@@ -119,7 +118,9 @@ To generate a `` element in the signature you must provide
When verifying a xml document you must specify the following properties on a ``SignedXml` instance:
-- `sign.keyInfoProvider` - **[required]** a key info provider instance containing your certificate, see [customizing algorithms](#customizing-algorithms) for an implementation example
+- `sign.signingCert` - **[optional]** your certificate as a string, a string of multiple certs in PEM format, or a Buffer, see [customizing algorithms](#customizing-algorithms) for an implementation example
+
+The certificate that will be used to check the signature will first be determined by calling `.getCertFromKeyInfo()`, which function you can customize as you see fit. If that returns `null`, then `.signingCert` is used. If that is `null`, then `.signingKey` is used (for symmetrical signing applications).
You can use any dom parser you want in your code (or none, depending on your usage). This sample uses [xmldom](https://github.com/jindw/xmldom) so you should install it first:
@@ -133,7 +134,6 @@ Example:
var select = require("xml-crypto").xpath,
dom = require("@xmldom/xmldom").DOMParser,
SignedXml = require("xml-crypto").SignedXml,
- FileKeyInfo = require("xml-crypto").FileKeyInfo,
fs = require("fs");
var xml = fs.readFileSync("signed.xml").toString();
@@ -144,7 +144,7 @@ var signature = select(
"//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']"
)[0];
var sig = new SignedXml();
-sig.keyInfoProvider = new FileKeyInfo("client_public.pem");
+sig.signingCert = new FileKeyInfo("client_public.pem");
sig.loadSignature(signature);
var res = sig.checkSignature(xml);
if (!res) console.log(sig.validationErrors);
@@ -179,7 +179,7 @@ If you keep failing verification, it is worth trying to guess such a hidden tran
```javascript
var option = { implicitTransforms: ["http://www.w3.org/TR/2001/REC-xml-c14n-20010315"] };
var sig = new SignedXml(null, option);
-sig.keyInfoProvider = new FileKeyInfo("client_public.pem");
+sig.signingCert = new FileKeyInfo("client_public.pem");
sig.loadSignature(signature);
var res = sig.checkSignature(xml);
```
@@ -232,14 +232,6 @@ To verify xml documents:
- `checkSignature(xml)` - validates the given xml document and returns true if the validation was successful, `sig.validationErrors` will have the validation errors if any, where:
- `xml` - a string containing a xml document
-### FileKeyInfo
-
-A basic key info provider implementation using `fs.readFileSync(file)`, is constructed using `new FileKeyInfo([file])` where:
-
-- `file` - a path to a pem encoded certificate
-
-See [verifying xml documents](#verifying-xml-documents) for an example usage
-
## Customizing Algorithms
The following sample shows how to sign a message using custom algorithms.
@@ -253,24 +245,15 @@ var SignedXml = require("xml-crypto").SignedXml,
Now define the extension point you want to implement. You can choose one or more.
-A key info provider is used to extract and construct the key and the KeyInfo xml section.
-Implement it if you want to create a signature with a KeyInfo section, or you want to read your key in a different way then the default file read option.
+To determine the inclusion and contents of a `` element, the function
+`getKeyInfoContent()` is called. There is a default implementation of this. If you wish to change
+this implementation, provide your own function assigned to the property `.getKeyInfoContent`. If
+there are no attributes and no contents to the `` element, it won't be included in the
+generated XML.
-```javascript
-function MyKeyInfo() {
- this.getKeyInfo = function (key, prefix) {
- prefix = prefix || "";
- prefix = prefix ? prefix + ":" : prefix;
- return "<" + prefix + "X509Data>" + prefix + "X509Data>";
- };
- this.getKey = function (keyInfo) {
- //you can use the keyInfo parameter to extract the key in any way you want
- return fs.readFileSync("key.pem");
- };
-}
-```
+To specify custom attributes on ``, add the properties to the `.keyInfoAttributes` property.
-A custom hash algorithm is used to calculate digests. Implement it if you want a hash other than the default SHA1.
+A custom hash algorithm is used to calculate digests. Implement it if you want a hash other than the built-in methods.
```javascript
function MyDigest() {
@@ -284,7 +267,7 @@ function MyDigest() {
}
```
-A custom signing algorithm. The default is RSA-SHA1
+A custom signing algorithm. The default is RSA-SHA1.
```javascript
function MySignatureAlgorithm() {
@@ -350,7 +333,7 @@ function signXml(xml, xpath, key, dest) {
/*configure the signature object to use the custom algorithms*/
sig.signatureAlgorithm = "http://mySignatureAlgorithm";
- sig.keyInfoProvider = new MyKeyInfo();
+ sig.signingCert = fs.readFileSync("my_public_cert.pem", "latin1");
sig.canonicalizationAlgorithm = "http://MyCanonicalization";
sig.addReference(
"//*[local-name(.)='x']",
@@ -370,7 +353,7 @@ var xml = "" + "" + "Harry Potter" + "";
signXml(xml, "//*[local-name(.)='book']", "client.pem", "result.xml");
```
-You can always look at the actual code as a sample (or drop me a [mail](mailto:yaronn01@gmail.com)).
+You can always look at the actual code as a sample.
## Asynchronous signing and verification
diff --git a/example/example.js b/example/example.js
index 9a815248..ae539ba4 100644
--- a/example/example.js
+++ b/example/example.js
@@ -3,7 +3,6 @@
const select = require("xml-crypto").xpath;
const dom = require("@xmldom/xmldom").DOMParser;
const SignedXml = require("xml-crypto").SignedXml;
-const FileKeyInfo = require("xml-crypto").FileKeyInfo;
const fs = require("fs");
function signXml(xml, xpath, key, dest) {
@@ -21,7 +20,7 @@ function validateXml(xml, key) {
doc
)[0];
const sig = new SignedXml();
- sig.keyInfoProvider = new FileKeyInfo(key);
+ sig.signingCert = key;
sig.loadSignature(signature.toString());
const res = sig.checkSignature(xml);
if (!res) {
diff --git a/index.d.ts b/index.d.ts
index 736977d1..32861375 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -129,15 +129,25 @@ export interface TransformAlgorithm {
* - {@link SignedXml#checkSignature}
* - {@link SignedXml#validationErrors}
*/
+
+/**
+ * @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 {
+ cert: string | string[] | Buffer;
+ prefix: string;
+}
+
export class SignedXml {
// To add a new transformation algorithm create a new class that implements the {@link TransformationAlgorithm} interface, and register it here. More info: {@link https://github.com/node-saml/xml-crypto#customizing-algorithms|Customizing Algorithms}
- static CanonicalizationAlgorithms: {
+ CanonicalizationAlgorithms: {
[uri in TransformAlgorithmType]: new () => TransformAlgorithm;
};
// 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}
- static HashAlgorithms: { [uri in HashAlgorithmType]: new () => HashAlgorithm };
+ HashAlgorithms: { [uri in 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}
- static SignatureAlgorithms: { [uri in SignatureAlgorithmType]: new () => SignatureAlgorithm };
+ SignatureAlgorithms: { [uri in SignatureAlgorithmType]: new () => SignatureAlgorithm };
// Rules used to convert an XML document into its canonical form.
canonicalizationAlgorithm: TransformAlgorithmType;
// It specifies a list of namespace prefixes that should be considered "inclusive" during the canonicalization process.
@@ -149,7 +159,7 @@ export class SignedXml {
// One of the supported signature algorithms. See {@link SignatureAlgorithmType}
signatureAlgorithm: SignatureAlgorithmType;
// A {@link Buffer} or pem encoded {@link String} containing your private key
- signingKey: Buffer | string;
+ privateKey: Buffer | string;
// Contains validation errors (if any) after {@link checkSignature} method is called
validationErrors: string[];
@@ -278,115 +288,79 @@ export class SignedXml {
* @returns The signed XML.
*/
getSignedXml(): string;
-}
-/**
- * KeyInfoProvider interface represents the structure for managing keys
- * and KeyInfo section in XML data when dealing with XML digital signatures.
- */
-export interface KeyInfoProvider {
/**
- * Method to return the key based on the contents of the specified KeyInfo.
+ * Builds the contents of a KeyInfo element as an XML string.
*
- * @param keyInfo - An optional array of XML Nodes.
- * @return A string or Buffer representing the key.
- */
- getKey(keyInfo?: Node[]): string | Buffer;
-
- /**
- * Method to return an XML string representing the contents of a KeyInfo element.
+ * For example, if the value of the prefix argument is 'foo', then
+ * the resultant XML string will be ""
*
- * @param key - An optional string representing the key.
- * @param prefix - An optional string representing the namespace alias.
- * @return An XML string representation of the contents of a KeyInfo element.
+ * @return an XML string representation of the contents of a KeyInfo element, or `null` if no `KeyInfo` element should be included
*/
- getKeyInfo(key?: string, prefix?: string): string;
+ getKeyInfoContent(args: GetKeyInfoContentArgs): string | null;
/**
- * An optional dictionary of attributes which will be added to the KeyInfo element.
+ * Returns the value of the signing certificate based on the contents of the
+ * specified KeyInfo.
+ *
+ * @param keyInfo an array with exactly one 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
*/
- attrs?: { [key: string]: string };
+ getCertFromKeyInfo(keyInfo: string): string | null;
}
-/**
- * The FileKeyInfo class loads the certificate from the file provided in the constructor.
- */
-export class FileKeyInfo implements KeyInfoProvider {
+export interface Utils {
/**
- * The path to the file from which the certificate is to be read.
+ * @param pem The PEM-encoded base64 certificate to strip headers from
*/
- file: string;
+ static pemToDer(pem: string): string;
/**
- * Initializes a new instance of the FileKeyInfo class.
- *
- * @param file - An optional string representing the file path of the certificate.
+ * @param der The DER-encoded base64 certificate to add PEM headers too
+ * @param pemLabel The label of the header and footer to add
*/
- constructor(file?: string);
+ static derToPem(
+ der: string,
+ pemLabel: ["CERTIFICATE" | "PRIVATE KEY" | "RSA PUBLIC KEY"]
+ ): string;
/**
- * Return the loaded certificate. The certificate is read from the file specified in the constructor.
- * The keyInfo parameter is ignored. (not implemented)
+ * -----BEGIN [LABEL]-----
+ * base64([DATA])
+ * -----END [LABEL]-----
*
- * @param keyInfo - (not used) An optional array of XML Elements.
- * @return A Buffer representing the certificate.
- */
- getKey(keyInfo?: Node[]): Buffer;
-
- /**
- * Builds the contents of a KeyInfo element as an XML string.
+ * Above is shown what PEM file looks like. As can be seen, base64 data
+ * can be in single line or multiple lines.
*
- * Currently, this returns exactly one empty X509Data element
- * (e.g. ""). The resultant X509Data element will be
- * prefaced with a namespace alias if a value for the prefix argument
- * is provided. In example, if the value of the prefix argument is 'foo', then
- * the resultant XML string will be ""
+ * 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'
*
- * @param key (not used) the signing/private key as a string
- * @param prefix an optional namespace alias to be used for the generated XML
- * @return an XML string representation of the contents of a KeyInfo element
- */
- getKeyInfo(key?: string, prefix?: string): string;
-}
-
-/**
- * The StringKeyInfo class loads the certificate from the string provided in the constructor.
- */
-export class StringKeyInfo implements KeyInfoProvider {
- /**
- * The certificate in string form.
- */
- key: string;
-
- /**
- * Initializes a new instance of the StringKeyInfo class.
- * @param key - An optional string representing the certificate.
- */
- constructor(key?: string);
-
- /**
- * Returns the certificate loaded in the constructor.
- * The keyInfo parameter is ignored. (not implemented)
+ * With couple of notes:
+ * - 'eol' is normalized to '\n'
*
- * @param keyInfo (not used) an array with exactly one KeyInfo element
- * @return the signing certificate as a string
+ * @param pem The PEM string to normalize to RFC7468 'stricttextualmsg' definition
*/
- getKey(keyInfo?: Node[]): string;
+ static normalizePem(pem: string): string;
/**
- * Builds the contents of a KeyInfo element as an XML string.
+ * PEM format has wide range of usages, but this library
+ * is enforcing RFC7468 which focuses on PKIX, PKCS and CMS.
*
- * Currently, this returns exactly one empty X509Data element
- * (e.g. ""). The resultant X509Data element will be
- * prefaced with a namespace alias if a value for the prefix argument
- * is provided. In example, if the value of the prefix argument is 'foo', then
- * the resultant XML string will be ""
+ * https://www.rfc-editor.org/rfc/rfc7468
+ *
+ * PEM_FORMAT_REGEX is validating given PEM file against RFC7468 'stricttextualmsg' definition.
*
- * @param key (not used) the signing/private key as a string
- * @param prefix an optional namespace alias to be used for the generated XML
- * @return an XML string representation of the contents of a KeyInfo element
+ * 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.
*/
- getKeyInfo(key?: string, prefix?: string): string;
+ PEM_FORMAT_REGEX: RegExp;
+ EXTRACT_X509_CERTS: RegExp;
+ BASE64_REGEX: RegExp;
}
/**
diff --git a/lib/file-key-info.js b/lib/file-key-info.js
deleted file mode 100644
index ef77f6b6..00000000
--- a/lib/file-key-info.js
+++ /dev/null
@@ -1,17 +0,0 @@
-const StringKeyInfo = require("./string-key-info");
-const fs = require("fs");
-
-/**
- * A key info provider implementation
- *
- * @param {string} file path to public certificate
- */
-function FileKeyInfo(file) {
- const key = fs.readFileSync(file);
- StringKeyInfo.apply(this, [key]);
-}
-
-FileKeyInfo.prototype = StringKeyInfo.prototype;
-FileKeyInfo.prototype.constructor = FileKeyInfo;
-
-module.exports = FileKeyInfo;
diff --git a/lib/signed-xml.js b/lib/signed-xml.js
index bc5e6d58..158e8c44 100644
--- a/lib/signed-xml.js
+++ b/lib/signed-xml.js
@@ -4,8 +4,6 @@ const utils = require("./utils");
const c14n = require("./c14n-canonicalization");
const execC14n = require("./exclusive-canonicalization");
const EnvelopedSignature = require("./enveloped-signature").EnvelopedSignature;
-const StringKeyInfo = require("./string-key-info");
-const FileKeyInfo = require("./file-key-info");
const crypto = require("crypto");
/**
@@ -317,7 +315,6 @@ function SignedXml(idMode, options) {
this.signingCert = null;
this.signatureAlgorithm =
this.options.signatureAlgorithm || "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
- this.keyInfoProvider = null;
this.canonicalizationAlgorithm =
this.options.canonicalizationAlgorithm || "http://www.w3.org/2001/10/xml-exc-c14n#";
this.inclusiveNamespacesPrefixList = this.options.inclusiveNamespacesPrefixList || "";
@@ -333,9 +330,12 @@ function SignedXml(idMode, options) {
this.idAttributes.splice(0, 0, this.options.idAttribute);
}
this.implicitTransforms = this.options.implicitTransforms || [];
+ this.getKeyInfoContent = SignedXml.getKeyInfoContent;
+ this.getCertFromKeyInfo = SignedXml.getCertFromKeyInfo;
+ this.keyInfoAttributes = {};
}
-SignedXml.CanonicalizationAlgorithms = {
+SignedXml.prototype.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,
@@ -345,13 +345,13 @@ SignedXml.CanonicalizationAlgorithms = {
"http://www.w3.org/2000/09/xmldsig#enveloped-signature": EnvelopedSignature,
};
-SignedXml.HashAlgorithms = {
+SignedXml.prototype.HashAlgorithms = {
"http://www.w3.org/2000/09/xmldsig#sha1": SHA1,
"http://www.w3.org/2001/04/xmlenc#sha256": SHA256,
"http://www.w3.org/2001/04/xmlenc#sha512": SHA512,
};
-SignedXml.SignatureAlgorithms = {
+SignedXml.prototype.SignatureAlgorithms = {
"http://www.w3.org/2000/09/xmldsig#rsa-sha1": RSASHA1,
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256": RSASHA256,
"http://www.w3.org/2001/04/xmldsig-more#rsa-sha512": RSASHA512,
@@ -364,10 +364,11 @@ SignedXml.SignatureAlgorithms = {
* and digital signature algos enabled at the same time.
* This enables HMAC and disables other signing algos.
*/
-SignedXml.enableHMAC = function () {
- SignedXml.SignatureAlgorithms = {
+SignedXml.prototype.enableHMAC = function () {
+ this.SignatureAlgorithms = {
"http://www.w3.org/2000/09/xmldsig#hmac-sha1": HMACSHA1,
};
+ this.getKeyInfoContent = () => null;
};
SignedXml.defaultNsForPrefix = {
@@ -376,35 +377,50 @@ SignedXml.defaultNsForPrefix = {
SignedXml.findAncestorNs = findAncestorNs;
-SignedXml.prototype.checkSignature = function (xml, callback) {
- if (callback != null && typeof callback !== "function") {
- throw new Error("Last parameter must be a callback function");
+SignedXml.getKeyInfoContent = function ({ publicCert = null, prefix = null } = {}) {
+ if (publicCert == null) {
+ return null;
}
- this.validationErrors = [];
- this.signedXml = xml;
+ prefix = prefix ? prefix + ":" : "";
- if (!this.keyInfoProvider) {
- const err = new Error("cannot validate signature since no key info resolver was provided");
- if (!callback) {
- throw err;
- } else {
- callback(err);
- return;
- }
+ let x509Certs = "";
+ if (Buffer.isBuffer(publicCert)) {
+ publicCert = publicCert.toString("latin1");
}
- this.signingKey = this.keyInfoProvider.getKey(this.keyInfo);
- if (!this.signingKey) {
- const err2 = new Error("key info provider could not resolve key info " + this.keyInfo);
- if (!callback) {
- throw err2;
- } else {
- callback(err2);
- return;
+ if (typeof publicCert === "string") {
+ publicCert = publicCert.match(utils.EXTRACT_X509_CERTS);
+ }
+
+ if (Array.isArray(publicCert)) {
+ x509Certs = publicCert
+ .map((c) => `${utils.pemToDer(c)}`)
+ .join("");
+ }
+
+ return `<${prefix}X509Data>${x509Certs}${prefix}X509Data>`;
+};
+
+SignedXml.getCertFromKeyInfo = function (keyInfo) {
+ if (keyInfo != null && keyInfo.length > 0) {
+ const certs = xpath.select(".//*[local-name(.)='X509Certificate']", keyInfo[0]);
+ if (certs.length > 0) {
+ return utils.derToPem(certs[0].textContent.trim(), "CERTIFICATE");
}
}
+ return null;
+};
+
+SignedXml.prototype.checkSignature = function (xml, callback) {
+ if (callback != null && typeof callback !== "function") {
+ throw new Error("Last parameter must be a callback function");
+ }
+
+ this.validationErrors = [];
+ this.signedXml = xml;
+
const doc = new Dom().parseFromString(xml);
if (!this.validateReferences(doc)) {
@@ -488,7 +504,7 @@ SignedXml.prototype.validateSignatureValue = function (doc, callback) {
const signer = this.findSignatureAlgorithm(this.signatureAlgorithm);
const res = signer.verifySignature(
signedInfoCanon,
- this.signingKey,
+ this.getCertFromKeyInfo(this.keyInfo) || this.signingCert || this.signingKey,
this.signatureValue,
callback
);
@@ -507,7 +523,7 @@ SignedXml.prototype.calculateSignatureValue = function (doc, callback) {
};
SignedXml.prototype.findSignatureAlgorithm = function (name) {
- const algo = SignedXml.SignatureAlgorithms[name];
+ const algo = this.SignatureAlgorithms[name];
if (algo) {
return new algo();
} else {
@@ -516,7 +532,7 @@ SignedXml.prototype.findSignatureAlgorithm = function (name) {
};
SignedXml.prototype.findCanonicalizationAlgorithm = function (name) {
- const algo = SignedXml.CanonicalizationAlgorithms[name];
+ const algo = this.CanonicalizationAlgorithms[name];
if (algo) {
return new algo();
} else {
@@ -525,7 +541,7 @@ SignedXml.prototype.findCanonicalizationAlgorithm = function (name) {
};
SignedXml.prototype.findHashAlgorithm = function (name) {
- const algo = SignedXml.HashAlgorithms[name];
+ const algo = this.HashAlgorithms[name];
if (algo) {
return new algo();
} else {
@@ -932,18 +948,21 @@ SignedXml.prototype.getKeyInfo = function (prefix) {
currentPrefix = prefix || "";
currentPrefix = currentPrefix ? currentPrefix + ":" : currentPrefix;
- if (this.keyInfoProvider) {
- let keyInfoAttrs = "";
- if (this.keyInfoProvider.attrs) {
- Object.keys(this.keyInfoProvider.attrs).forEach((name) => {
- keyInfoAttrs += " " + name + '="' + this.keyInfoProvider.attrs[name] + '"';
- });
- }
+ let keyInfoAttrs = "";
+ if (this.keyInfoAttributes) {
+ Object.keys(this.keyInfoAttributes).forEach((name) => {
+ keyInfoAttrs += " " + name + '="' + this.keyInfoAttributes[name] + '"';
+ });
+ }
+ const keyInfoContent = this.getKeyInfoContent({ publicCert: this.signingCert, prefix });
+ if (keyInfoAttrs !== "" || keyInfoContent != null) {
res += "<" + currentPrefix + "KeyInfo" + keyInfoAttrs + ">";
- res += this.keyInfoProvider.getKeyInfo(this.signingCert || this.signingKey, prefix);
+ res += keyInfoContent;
res += "" + currentPrefix + "KeyInfo>";
+ return res;
+ } else {
+ return "";
}
- return res;
};
/**
@@ -1190,5 +1209,3 @@ SignedXml.prototype.getSignedXml = function () {
};
exports.SignedXml = SignedXml;
-exports.StringKeyInfo = StringKeyInfo;
-exports.FileKeyInfo = FileKeyInfo;
diff --git a/lib/string-key-info.js b/lib/string-key-info.js
deleted file mode 100644
index 2387f0bc..00000000
--- a/lib/string-key-info.js
+++ /dev/null
@@ -1,40 +0,0 @@
-/**
- * A basic string based implementation of a FileInfoProvider
- *
- * @param {string} key the string contents of a public certificate
- */
-function StringKeyInfo(key) {
- this.key = key;
-}
-
-/**
- * Builds the contents of a KeyInfo element as an XML string.
- *
- * Currently, this returns exactly one empty X509Data element
- * (e.g. ""). The resultant X509Data element will be
- * prefaced with a namespace alias if a value for the prefix argument
- * is provided. In example, if the value of the prefix argument is 'foo', then
- * the resultant XML string will be ""
- *
- * @param key (not used) the signing/private key as a string
- * @param prefix an optional namespace alias to be used for the generated XML
- * @return an XML string representation of the contents of a KeyInfo element
- */
-StringKeyInfo.prototype.getKeyInfo = function (key, prefix) {
- prefix = prefix || "";
- prefix = prefix ? prefix + ":" : prefix;
- return "<" + prefix + "X509Data>" + prefix + "X509Data>";
-};
-
-/**
- * Returns the value of the signing certificate based on the contents of the
- * specified KeyInfo.
- *
- * @param keyInfo (not used) an array with exactly one KeyInfo element
- * @return the signing certificate as a string
- */
-StringKeyInfo.prototype.getKey = function (keyInfo) {
- return this.key;
-};
-
-module.exports = StringKeyInfo;
diff --git a/lib/utils.js b/lib/utils.js
index 84c5c4ab..30b87c15 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -79,8 +79,59 @@ function encodeSpecialCharactersInText(text) {
});
}
+const EXTRACT_X509_CERTS = new RegExp(
+ "-----BEGIN CERTIFICATE-----[^-]*-----END CERTIFICATE-----",
+ "g"
+);
+const PEM_FORMAT_REGEX = new RegExp(
+ "^-----BEGIN [A-Z\x20]{1,48}-----([^-]*)-----END [A-Z\x20]{1,48}-----$",
+ "s"
+);
+const BASE64_REGEX = new RegExp(
+ "^(?:[A-Za-z0-9\\+\\/]{4}\\n{0,1})*(?:[A-Za-z0-9\\+\\/]{2}==|[A-Za-z0-9\\+\\/]{3}=)?$",
+ "s"
+);
+
+function normalizePem(pem) {
+ return `${(
+ pem
+ .trim()
+ .replace(/(\r\n|\r)/g, "\n")
+ .match(/.{1,64}/g) ?? []
+ ).join("\n")}\n`;
+}
+
+function pemToDer(pem) {
+ return pem
+ .replace(/(\r\n|\r)/g, "\n")
+ .replace(/-----BEGIN [A-Z\x20]{1,48}-----\n?/, "")
+ .replace(/-----END [A-Z\x20]{1,48}-----\n?/, "");
+}
+
+function derToPem(der, pemLabel) {
+ const base64Der = Buffer.isBuffer(der) ? der.toString("latin1").trim() : der.trim();
+
+ if (PEM_FORMAT_REGEX.test(base64Der)) {
+ return normalizePem(base64Der);
+ }
+
+ if (BASE64_REGEX.test(base64Der)) {
+ const pem = `-----BEGIN ${pemLabel}-----\n${base64Der}\n-----END ${pemLabel}-----`;
+
+ return normalizePem(pem);
+ }
+
+ throw new Error("Unknown DER format.");
+}
+
exports.findAttr = findAttr;
exports.findChilds = findChilds;
exports.encodeSpecialCharactersInAttribute = encodeSpecialCharactersInAttribute;
exports.encodeSpecialCharactersInText = encodeSpecialCharactersInText;
exports.findFirst = findFirst;
+exports.EXTRACT_X509_CERTS = EXTRACT_X509_CERTS;
+exports.PEM_FORMAT_REGEX = PEM_FORMAT_REGEX;
+exports.BASE64_REGEX = BASE64_REGEX;
+exports.pemToDer = pemToDer;
+exports.derToPem = derToPem;
+exports.normalizePem = normalizePem;
diff --git a/test/document-test.js b/test/document-test.js
index 390a50cb..bade7613 100644
--- a/test/document-test.js
+++ b/test/document-test.js
@@ -17,7 +17,7 @@ describe("Document tests", function () {
.toString()
);
const sig = new crypto.SignedXml();
- sig.keyInfoProvider = new crypto.FileKeyInfo("./test/static/feide_public.pem");
+ sig.signingCert = fs.readFileSync("./test/static/feide_public.pem");
sig.loadSignature(signature);
const result = sig.checkSignature(xml);
@@ -37,7 +37,7 @@ describe("Document tests", function () {
);
const sig = new crypto.SignedXml();
const feidePublicCert = fs.readFileSync("./test/static/feide_public.pem");
- sig.keyInfoProvider = new crypto.StringKeyInfo(feidePublicCert);
+ sig.signingCert = feidePublicCert;
sig.loadSignature(signature);
const result = sig.checkSignature(xml);
diff --git a/test/hmac-tests.js b/test/hmac-tests.js
index 28863cb1..f1128509 100644
--- a/test/hmac-tests.js
+++ b/test/hmac-tests.js
@@ -2,20 +2,10 @@ const crypto = require("../index");
const xpath = require("xpath");
const xmldom = require("@xmldom/xmldom");
const fs = require("fs");
+const { sign } = require("crypto");
const expect = require("chai").expect;
-let sigAlgs;
-
describe("HMAC tests", function () {
- beforeEach(function () {
- sigAlgs = crypto.SignedXml.SignatureAlgorithms;
- crypto.SignedXml.enableHMAC();
- });
-
- afterEach(function () {
- crypto.SignedXml.SignatureAlgorithms = sigAlgs;
- });
-
it("test validating HMAC signature", function () {
const xml = fs.readFileSync("./test/static/hmac_signature.xml", "utf-8");
const doc = new xmldom.DOMParser().parseFromString(xml);
@@ -24,7 +14,8 @@ describe("HMAC tests", function () {
doc
)[0];
const sig = new crypto.SignedXml();
- sig.keyInfoProvider = new crypto.FileKeyInfo("./test/static/hmac.key");
+ sig.enableHMAC();
+ sig.signingCert = fs.readFileSync("./test/static/hmac.key");
sig.loadSignature(signature);
const result = sig.checkSignature(xml);
@@ -39,7 +30,8 @@ describe("HMAC tests", function () {
doc
)[0];
const sig = new crypto.SignedXml();
- sig.keyInfoProvider = new crypto.FileKeyInfo("./test/static/hmac-foobar.key");
+ sig.enableHMAC();
+ sig.signingCert = fs.readFileSync("./test/static/hmac-foobar.key");
sig.loadSignature(signature);
const result = sig.checkSignature(xml);
@@ -49,6 +41,7 @@ describe("HMAC tests", function () {
it("test create and validate HMAC signature", function () {
const xml = "" + "" + "Harry Potter" + "" + "";
const sig = new crypto.SignedXml();
+ sig.enableHMAC();
sig.signingKey = fs.readFileSync("./test/static/hmac.key");
sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#hmac-sha1";
sig.addReference("//*[local-name(.)='book']");
@@ -60,7 +53,8 @@ describe("HMAC tests", function () {
doc
)[0];
const verify = new crypto.SignedXml();
- verify.keyInfoProvider = new crypto.FileKeyInfo("./test/static/hmac.key");
+ verify.enableHMAC();
+ verify.signingCert = fs.readFileSync("./test/static/hmac.key");
verify.loadSignature(signature);
const result = verify.checkSignature(sig.getSignedXml());
diff --git a/test/key-info-tests.js b/test/key-info-tests.js
new file mode 100644
index 00000000..1fe58435
--- /dev/null
+++ b/test/key-info-tests.js
@@ -0,0 +1,38 @@
+const select = require("xpath").select;
+const xmldom = require("@xmldom/xmldom");
+const SignedXml = require("../lib/signed-xml.js").SignedXml;
+const fs = require("fs");
+const xpath = require("xpath");
+const crypto = require("../index.js");
+const expect = require("chai").expect;
+
+describe("KeyInfo tests", function () {
+ it("adds X509Certificate element during signature", function () {
+ const xml = "";
+ const sig = new SignedXml();
+ sig.signingKey = fs.readFileSync("./test/static/client.pem");
+ sig.signingCert = fs.readFileSync("./test/static/client_public.pem");
+ sig.computeSignature(xml);
+ const signedXml = sig.getSignedXml();
+ const doc = new xmldom.DOMParser().parseFromString(signedXml);
+ const x509 = select("//*[local-name(.)='X509Certificate']", doc.documentElement);
+ expect(x509.length, "X509Certificate element should exist").to.equal(1);
+ });
+
+ it("make sure private hmac key is not leaked due to key confusion", function () {
+ const xml = "" + "" + "Harry Potter" + "" + "";
+ const sig = new crypto.SignedXml();
+ sig.signingKey = fs.readFileSync("./test/static/hmac.key");
+ sig.signingCert = fs.readFileSync("./test/static/hmac.key");
+ sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#hmac-sha1";
+ sig.enableHMAC();
+ sig.addReference("//*[local-name(.)='book']");
+ sig.computeSignature(xml);
+
+ const doc = new xmldom.DOMParser().parseFromString(sig.getSignedXml());
+
+ const keyInfo = xpath.select("//*[local-name(.)='KeyInfo']", doc)[0];
+
+ expect(keyInfo).to.be.undefined;
+ });
+});
diff --git a/test/saml-response-test.js b/test/saml-response-test.js
index 88116fa0..1be09d8d 100644
--- a/test/saml-response-test.js
+++ b/test/saml-response-test.js
@@ -13,7 +13,7 @@ describe("SAML response tests", function () {
doc
)[0];
const sig = new crypto.SignedXml();
- sig.keyInfoProvider = new crypto.FileKeyInfo("./test/static/feide_public.pem");
+ sig.signingCert = fs.readFileSync("./test/static/feide_public.pem");
sig.loadSignature(signature);
const result = sig.checkSignature(xml);
@@ -29,7 +29,7 @@ describe("SAML response tests", function () {
assertion
)[0];
const sig = new crypto.SignedXml();
- sig.keyInfoProvider = new crypto.FileKeyInfo("./test/static/feide_public.pem");
+ sig.signingCert = fs.readFileSync("./test/static/feide_public.pem");
sig.loadSignature(signature);
expect(function () {
sig.checkSignature(xml);
@@ -46,7 +46,7 @@ describe("SAML response tests", function () {
doc
)[0];
const sig = new crypto.SignedXml();
- sig.keyInfoProvider = new crypto.FileKeyInfo("./test/static/saml_external_ns.pem");
+ sig.signingCert = fs.readFileSync("./test/static/saml_external_ns.pem");
sig.loadSignature(signature);
const result = sig.checkSignature(xml);
expect(result).to.be.true;
@@ -61,7 +61,7 @@ describe("SAML response tests", function () {
assertion
)[0];
const sig = new crypto.SignedXml();
- sig.keyInfoProvider = new crypto.FileKeyInfo("./test/static/feide_public.pem");
+ sig.signingCert = fs.readFileSync("./test/static/feide_public.pem");
sig.loadSignature(signature);
expect(function () {
sig.checkSignature(xml);
@@ -76,7 +76,7 @@ describe("SAML response tests", function () {
doc
)[0];
const sig = new crypto.SignedXml();
- sig.keyInfoProvider = new crypto.FileKeyInfo("./test/static/feide_public.pem");
+ sig.signingCert = fs.readFileSync("./test/static/feide_public.pem");
sig.loadSignature(signature);
const result = sig.checkSignature(xml);
// This doesn't matter, just want to make sure that we don't fail due to unknown algorithm
diff --git a/test/signature-integration-tests.js b/test/signature-integration-tests.js
index 6ea47629..80d77bc5 100644
--- a/test/signature-integration-tests.js
+++ b/test/signature-integration-tests.js
@@ -87,7 +87,7 @@ describe("Signature integration tests", function () {
"";
const sig = new crypto.SignedXml();
- sig.keyInfoProvider = new crypto.FileKeyInfo("./test/static/client_public.pem");
+ sig.signingCert = fs.readFileSync("./test/static/client_public.pem");
sig.loadSignature(signature);
const result = sig.checkSignature(xml);
@@ -111,7 +111,7 @@ describe("Signature integration tests", function () {
doc
)[0];
const sig = new crypto.SignedXml();
- sig.keyInfoProvider = new crypto.FileKeyInfo("./test/static/windows_store_certificate.pem");
+ sig.signingCert = fs.readFileSync("./test/static/windows_store_certificate.pem");
sig.loadSignature(signature);
const result = sig.checkSignature(xml);
@@ -128,9 +128,7 @@ describe("Signature integration tests", function () {
doc
)[0];
const sig = new crypto.SignedXml();
- sig.keyInfoProvider = new crypto.FileKeyInfo(
- "./test/static/signature_with_inclusivenamespaces.pem"
- );
+ sig.signingCert = fs.readFileSync("./test/static/signature_with_inclusivenamespaces.pem");
sig.loadSignature(signature);
const result = sig.checkSignature(xml);
@@ -150,9 +148,7 @@ describe("Signature integration tests", function () {
doc
)[0];
const sig = new crypto.SignedXml();
- sig.keyInfoProvider = new crypto.FileKeyInfo(
- "./test/static/signature_with_inclusivenamespaces.pem"
- );
+ sig.signingCert = fs.readFileSync("./test/static/signature_with_inclusivenamespaces.pem");
sig.loadSignature(signature);
const result = sig.checkSignature(xml);
@@ -172,9 +168,7 @@ describe("Signature integration tests", function () {
doc
)[0];
const sig = new crypto.SignedXml();
- sig.keyInfoProvider = new crypto.FileKeyInfo(
- "./test/static/signature_with_inclusivenamespaces.pem"
- );
+ sig.signingCert = fs.readFileSync("./test/static/signature_with_inclusivenamespaces.pem");
sig.loadSignature(signature);
const result = sig.checkSignature(xml);
diff --git a/test/signature-unit-tests.js b/test/signature-unit-tests.js
index 6e2d3f93..f1190729 100644
--- a/test/signature-unit-tests.js
+++ b/test/signature-unit-tests.js
@@ -1,7 +1,6 @@
const select = require("xpath").select;
const dom = require("@xmldom/xmldom").DOMParser;
const SignedXml = require("../lib/signed-xml.js").SignedXml;
-const FileKeyInfo = require("../lib/signed-xml.js").FileKeyInfo;
const fs = require("fs");
const crypto = require("crypto");
const expect = require("chai").expect;
@@ -15,11 +14,15 @@ describe("Signature unit tests", function () {
)[0];
const sig = new SignedXml(mode);
- sig.keyInfoProvider = new FileKeyInfo("./test/static/client_public.pem");
+ sig.signingCert = fs.readFileSync("./test/static/client_public.pem");
sig.loadSignature(node);
- const res = sig.checkSignature(xml);
+ try {
+ const res = sig.checkSignature(xml);
- return res;
+ return res;
+ } catch (e) {
+ return false;
+ }
}
function passValidSignature(file, mode) {
@@ -319,12 +322,6 @@ describe("Signature unit tests", function () {
});
it("signer creates signature with correct structure", function () {
- function DummyKeyInfo() {
- this.getKeyInfo = function () {
- return "dummy key info";
- };
- }
-
function DummyDigest() {
this.getHash = function () {
return "dummy digest";
@@ -368,13 +365,15 @@ describe("Signature unit tests", function () {
const xml = '';
const sig = new SignedXml();
- SignedXml.CanonicalizationAlgorithms["http://DummyTransformation"] = DummyTransformation;
- SignedXml.CanonicalizationAlgorithms["http://DummyCanonicalization"] = DummyCanonicalization;
- SignedXml.HashAlgorithms["http://dummyDigest"] = DummyDigest;
- SignedXml.SignatureAlgorithms["http://dummySignatureAlgorithm"] = DummySignatureAlgorithm;
+ sig.CanonicalizationAlgorithms["http://DummyTransformation"] = DummyTransformation;
+ sig.CanonicalizationAlgorithms["http://DummyCanonicalization"] = DummyCanonicalization;
+ sig.HashAlgorithms["http://dummyDigest"] = DummyDigest;
+ sig.SignatureAlgorithms["http://dummySignatureAlgorithm"] = DummySignatureAlgorithm;
sig.signatureAlgorithm = "http://dummySignatureAlgorithm";
- sig.keyInfoProvider = new DummyKeyInfo();
+ sig.getKeyInfoContent = function () {
+ return "dummy key info";
+ };
sig.canonicalizationAlgorithm = "http://DummyCanonicalization";
sig.addReference(
@@ -477,12 +476,6 @@ describe("Signature unit tests", function () {
it("signer creates signature with correct structure (with prefix)", function () {
const prefix = "ds";
- function DummyKeyInfo() {
- this.getKeyInfo = function () {
- return "dummy key info";
- };
- }
-
function DummyDigest() {
this.getHash = function () {
return "dummy digest";
@@ -526,13 +519,15 @@ describe("Signature unit tests", function () {
const xml = '';
const sig = new SignedXml();
- SignedXml.CanonicalizationAlgorithms["http://DummyTransformation"] = DummyTransformation;
- SignedXml.CanonicalizationAlgorithms["http://DummyCanonicalization"] = DummyCanonicalization;
- SignedXml.HashAlgorithms["http://dummyDigest"] = DummyDigest;
- SignedXml.SignatureAlgorithms["http://dummySignatureAlgorithm"] = DummySignatureAlgorithm;
+ sig.CanonicalizationAlgorithms["http://DummyTransformation"] = DummyTransformation;
+ sig.CanonicalizationAlgorithms["http://DummyCanonicalization"] = DummyCanonicalization;
+ sig.HashAlgorithms["http://dummyDigest"] = DummyDigest;
+ sig.SignatureAlgorithms["http://dummySignatureAlgorithm"] = DummySignatureAlgorithm;
sig.signatureAlgorithm = "http://dummySignatureAlgorithm";
- sig.keyInfoProvider = new DummyKeyInfo();
+ sig.getKeyInfoContent = function () {
+ return "dummy key info";
+ };
sig.canonicalizationAlgorithm = "http://DummyCanonicalization";
sig.addReference(
@@ -638,7 +633,7 @@ describe("Signature unit tests", function () {
'';
const sig = new SignedXml();
sig.signingKey = fs.readFileSync("./test/static/client.pem");
- sig.keyInfoProvider = null;
+ sig.signingCert = null;
sig.addReference("//*[local-name(.)='x']");
sig.addReference("//*[local-name(.)='y']");
@@ -696,11 +691,11 @@ describe("Signature unit tests", function () {
const xml =
'';
- SignedXml.SignatureAlgorithms["http://dummySignatureAlgorithmAsync"] = DummySignatureAlgorithm;
const sig = new SignedXml();
+ sig.SignatureAlgorithms["http://dummySignatureAlgorithmAsync"] = DummySignatureAlgorithm;
sig.signatureAlgorithm = "http://dummySignatureAlgorithmAsync";
sig.signingKey = fs.readFileSync("./test/static/client.pem");
- sig.keyInfoProvider = null;
+ sig.signingCert = null;
sig.addReference("//*[local-name(.)='x']");
sig.addReference("//*[local-name(.)='y']");
@@ -783,7 +778,7 @@ describe("Signature unit tests", function () {
const xml = "";
const sig = new SignedXml();
sig.signingKey = fs.readFileSync("./test/static/client.pem");
- sig.keyInfoProvider = null;
+ sig.signingCert = null;
sig.addReference(
"//*[local-name(.)='root']",
@@ -823,17 +818,15 @@ describe("Signature unit tests", function () {
});
it("signer adds existing prefixes", function () {
- function AssertionKeyInfo(assertionId) {
- this.getKeyInfo = function () {
- return (
- ' ' +
- '' +
- assertionId +
- "" +
- ""
- );
- };
+ function getKeyInfoContentWithAssertionId({ assertionId }) {
+ return (
+ ' ' +
+ '' +
+ assertionId +
+ "" +
+ ""
+ );
}
const xml =
@@ -848,7 +841,8 @@ describe("Signature unit tests", function () {
"";
const sig = new SignedXml();
- sig.keyInfoProvider = new AssertionKeyInfo("_81d5fba5c807be9e9cf60c58566349b1");
+ const assertionId = "_81d5fba5c807be9e9cf60c58566349b1";
+ sig.getKeyInfoContent = getKeyInfoContentWithAssertionId.bind(this, { assertionId });
sig.signingKey = fs.readFileSync("./test/static/client.pem");
sig.computeSignature(xml, {
prefix: "ds",
@@ -864,13 +858,14 @@ describe("Signature unit tests", function () {
const result = sig.getSignedXml();
expect((result.match(/xmlns:wsu=/g) || []).length).to.equal(1);
expect((result.match(/xmlns:wsse=/g) || []).length).to.equal(1);
+ expect(result.includes(assertionId)).to.be.true;
});
it("creates InclusiveNamespaces element when inclusiveNamespacesPrefixList is set on Reference", function () {
const xml = "";
const sig = new SignedXml();
sig.signingKey = fs.readFileSync("./test/static/client.pem");
- sig.keyInfoProvider = null;
+ sig.signingCert = null;
sig.addReference(
"//*[local-name(.)='root']",
@@ -902,7 +897,7 @@ describe("Signature unit tests", function () {
const xml = "";
const sig = new SignedXml();
sig.signingKey = fs.readFileSync("./test/static/client.pem");
- sig.keyInfoProvider = null;
+ sig.signingCert = null;
sig.addReference(
"//*[local-name(.)='root']",
@@ -929,7 +924,7 @@ describe("Signature unit tests", function () {
const xml = "";
const sig = new SignedXml(null, { inclusiveNamespacesPrefixList: "prefix1 prefix2" });
sig.signingKey = fs.readFileSync("./test/static/client.pem");
- sig.keyInfoProvider = null;
+ sig.signingCert = null;
sig.addReference(
"//*[local-name(.)='root']",
@@ -962,7 +957,7 @@ describe("Signature unit tests", function () {
const xml = "";
const sig = new SignedXml(null); // Omit inclusiveNamespacesPrefixList property
sig.signingKey = fs.readFileSync("./test/static/client.pem");
- sig.keyInfoProvider = null;
+ sig.signingCert = null;
sig.addReference(
"//*[local-name(.)='root']",
@@ -989,15 +984,11 @@ describe("Signature unit tests", function () {
const xml = "";
const sig = new SignedXml();
sig.signingKey = fs.readFileSync("./test/static/client.pem");
- sig.keyInfoProvider = {
- attrs: {
- CustomUri: "http://www.example.com/keyinfo",
- CustomAttribute: "custom-value",
- },
- getKeyInfo: function () {
- return "";
- },
+ sig.keyInfoAttributes = {
+ CustomUri: "http://www.example.com/keyinfo",
+ CustomAttribute: "custom-value",
};
+ sig.getKeyInfoContent = () => "";
sig.computeSignature(xml);
const signedXml = sig.getSignedXml();
@@ -1018,4 +1009,36 @@ describe("Signature unit tests", function () {
"KeyInfo element should have the correct CustomAttribute attribute value"
).to.equal("custom-value");
});
+
+ it("adds all certificates and does not add private keys to KeyInfo element", function () {
+ const xml = "";
+ const sig = new SignedXml();
+ const pemBuffer = fs.readFileSync("./test/static/client_bundle.pem");
+ sig.signingKey = pemBuffer;
+ sig.signingCert = pemBuffer;
+ sig.computeSignature(xml);
+ const signedXml = sig.getSignedXml();
+
+ const doc = new dom().parseFromString(signedXml);
+
+ const x509certificates = select("//*[local-name(.)='X509Certificate']", doc.documentElement);
+ expect(x509certificates.length, "There should be exactly two certificates").to.equal(2);
+
+ const cert1 = x509certificates[0];
+ const cert2 = x509certificates[1];
+ expect(cert1.textContent, "X509Certificate[0] TextContent does not exist").to.exist;
+ expect(cert2.textContent, "X509Certificate[1] TextContent does not exist").to.exist;
+
+ const trimmedTextContent1 = cert1.textContent.trim();
+ const trimmedTextContent2 = cert2.textContent.trim();
+ expect(trimmedTextContent1, "Empty certificate added [0]").to.not.be.empty;
+ expect(trimmedTextContent2, "Empty certificate added [1]").to.not.be.empty;
+
+ expect(trimmedTextContent1.substring(0, 5), "Incorrect value for X509Certificate[0]").to.equal(
+ "MIIDC"
+ );
+ expect(trimmedTextContent2.substring(0, 5), "Incorrect value for X509Certificate[1]").to.equal(
+ "MIIDZ"
+ );
+ });
});
diff --git a/test/static/client_bundle.pem b/test/static/client_bundle.pem
new file mode 100644
index 00000000..bd784097
--- /dev/null
+++ b/test/static/client_bundle.pem
@@ -0,0 +1,68 @@
+-----BEGIN CERTIFICATE-----
+MIIDCzCCAfMCFDdl3bSiEFLCBC3akD+sPuSbRKnyMA0GCSqGSIb3DQEBCwUAMEIx
+CzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0Rl
+ZmF1bHQgQ29tcGFueSBMdGQwHhcNMjMwNjE1MTAyNjMwWhcNMzMwNjEyMTAyNjMw
+WjBCMQswCQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQK
+DBNEZWZhdWx0IENvbXBhbnkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEArMLLZkjvJ/Kr3rfhR/77nJdjPumutJ7lJoDgQAwG2qBmse4oJmBDB6fY
+XFTrwVH4DKYnJFOaPBAqp+BGpFEjKo/zghEcGxidnuM5Hc6NAfnK3YEmbspc1DGX
+cLCfv0Mw3VV+XvDxfLpQdfTA4CM/lgPmO6lUF6er/WaLsLMfJc2+jLXYkIlj+x6b
+KVNHC7SG/HkD0WSZAAsfW1RCOQgsgVi/b+TEPR7MqcXzS3R1WWd8dB9EC8VwpU3o
+KBZ4EaYYvbEH+z2YW24jl+vxGHM9+UZaoYMzkBnDs+gtmpH35S/+YFbrro+qbRAs
+Hy5FhhfQ0ZWbe9nAFUaID0CkemnQOwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBf
+gb5/f8Jv+zR5yD2VhaqZPgIc3lekCi1UxOrmSfnFZ1osSlgenf1dvJeCX9QEh2Lv
+FHmp0TflcJ12qHsWdfZSSantFhG5jMFxYD9uARyTHCWtRtdfO0P/KeuORleDN5lE
+p7wBCy6JpE5INQxoHYnhO0ujfo9SvZVxpBHRpdSnHrkKn+6UOr6HVFQ4RVyEns7B
+oZ/GQ7HWj4qpRF98MUmtwtCCemWPnNSjSAAWuJZ8e4JStjFcx8Vw3xIZbNGCZflw
+ECjO3qQDUQmzySBub8FaDZkG1d2ODZsL221ETto4c5DXlesgYBVcPIkQAy53IVoP
+hupmcqhjnIejGHsgFAdF
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDZTCCAk2gAwIBAgIUD2FdlpUMPuX6W1A0OiMKu6g0XuYwDQYJKoZIhvcNAQEL
+BQAwQjELMAkGA1UEBhMCWFgxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTEcMBoGA1UE
+CgwTRGVmYXVsdCBDb21wYW55IEx0ZDAeFw0yMzA2MTUxMDI2MDNaFw0zMzA2MTIx
+MDI2MDNaMEIxCzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAa
+BgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQD/emWytGY9zUAJ8Jq++GgTieFkmdgwPq8QcHzfIhqs0n5Y28cS
+CExFwoNZaJQiA1lobD2bgwAJPUb4j1zUmnaKeDuc2dq3RhcctJ2kbReqJVwzPW19
+DxWsvADYrjzE6UdgyWZfoanp7IBKjEj3xF13w0rYm1D3lrT7mE5roEA10oOVwErl
+HgRcCO8nbWMxy6HnZmMiTY815xdWXVKZpbjNJaVybEEnW128BFafAy24XmMg5PLx
+YGLEVExO2RHjEOibDb08/L91wJA8N8rSDoG8Akl1UesdH95VBcMvK4lA2e4Nn2Lu
+vFkqtsey6YrNw4OD1uAnQ0hinuo7OlVMYiRLAgMBAAGjUzBRMB0GA1UdDgQWBBTD
+b17H/nXO5ZUh74YhBn/X7zZuxTAfBgNVHSMEGDAWgBTDb17H/nXO5ZUh74YhBn/X
+7zZuxTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBsq1PBuJtg
+t+EFpbGngOmlMW4YaI77N8+H8Tk9+AIfvv+Awya+8T8ToMyEd7WZXuCabpzFwc5u
+TaZiHV0oyHNLLMt+QKrBZ9Ybu/RFJo0kjvbO2FEvtIiz9qqRqPUwD804HpXcyBvP
+lcQvJXRUSsaoTmcLtaA6TOWUbzxgHiXIDyiNVaM9B8hgKdqKCDqwtKYSAd9dpQVt
+Yq2yEXxLKrJjZRrRF0d5CesA4rOoUiRK7VnZSIo8aO8BtLH7UjOrFg5WFial/1kh
+SEmjbsPp6oLNyDtg4eTWkS82polPsCWihm9gEu3+plBQNXH7X6xrPEsn5ZilscCc
++lxT6hClXBXJ
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCswstmSO8n8qve
+t+FH/vucl2M+6a60nuUmgOBADAbaoGax7igmYEMHp9hcVOvBUfgMpickU5o8ECqn
+4EakUSMqj/OCERwbGJ2e4zkdzo0B+crdgSZuylzUMZdwsJ+/QzDdVX5e8PF8ulB1
+9MDgIz+WA+Y7qVQXp6v9Zouwsx8lzb6MtdiQiWP7HpspU0cLtIb8eQPRZJkACx9b
+VEI5CCyBWL9v5MQ9HsypxfNLdHVZZ3x0H0QLxXClTegoFngRphi9sQf7PZhbbiOX
+6/EYcz35RlqhgzOQGcOz6C2akfflL/5gVuuuj6ptECwfLkWGF9DRlZt72cAVRogP
+QKR6adA7AgMBAAECggEAQVcvfNEq+u3yiTr8zrEm0vQDCmFxvUi3nJdzuWWTFg9C
+qBtOPi18TKHz2AAaZrSs34PcHAYuuHbY20OdFDrH1So6zD/SZIEr5FNGX/qmJFAo
+pRxav95zu6HCCFIVKU6tZZkXQatZenYxRlu6s0tBmmiBJKGHd6boCuBFByDIMBB/
+P509g1TrRB70vAL8hqyd5wHJNhuwUvOOfLMfAMOXa5aAtwQlc0PXOUbUxwS3C4NW
+acqraGpFFWAOwg+J5eBi5jfXHDyiGhS5p8T8HkcoVyI5WrEQJES3fPlmA2IM8CXj
+4ipS329zJNM8SUJuluo1KIJeFMNN5cF1DZqtREb38QKBgQDywatu2mOIAoN+cpwf
+VtTDH1qsGYxuCka+7tipt+DxTxIJB/1KTQRwdWb1leqszO3nNS6Q/UIDFMV+oszG
+3UuoriOw+xuvYy/PrFdrDki3droipOEllSGmbXGk7rBalAbgswyF21ebgZi4moZr
+YjqdQl+R+XN1YjqnOn39njyeKQKBgQC2L5F90HBBhp+1qU1hbEGof5oMkp+Thjx/
+PWbJsqt6s41yIemug3MP8QLlUOMG5X+QTCN4RNTmf60V5McF2TSuT8jbT8jby0w+
+ClnZ9lgGMSL7UjI26CHkw36xcDH2hzgXWGRxzttzlXttJqnbeATHC811yWaLDXGU
+ecC7bG9/wwKBgQChgq8fgtdjv2BjObebtja6V1sJU7o14EpvcBPg30Ee65+xOIqR
+66n/dGz7CjJno7TI9n4z4vwPdrtrZL9ftA5JfQqsDnW9+/zsa9qBlLBWt/xhXleZ
+nJ4Vz40j0datfP0SdK3pRSUFhnTopY63VVRwGp/hTBlASQmDB4yZt7TW+QKBgF73
+eM2ug3WEqWfWcsGf3rHoofJ/07LgvFRPO29UNVLmmYqu5tLTLn1W0n2apl0H8HDV
+X3/n0Vq9nwnUkXIZAP8EE91OP5Ni68FDQAcABG5l2qhK9mXspw5KYZY4t7KcVb7F
+ksZIX9hmSUpiZxRCAauIGXeWnl9JiLUuqiqIoa5lAoGAMS2HObavBhdTPkN998lq
+fnInmg5M466+PiPYZuQZh+Ea+8Gs0wv2wpFXw7Ds7hjo0hmiYGV/yw8etn45lpfA
+buAIy50HQABwOWxH74AzddemsUSFEnRO4VgQ2Cu2dDaDGRjZbXcggRtOZ+ynuUJi
+G9/7qOuw5oPSq0v7tEDbqpI=
+-----END PRIVATE KEY-----
diff --git a/test/static/client_public.pem b/test/static/client_public.pem
index 430f46fa..d61698bd 100644
--- a/test/static/client_public.pem
+++ b/test/static/client_public.pem
@@ -1,12 +1,12 @@
------BEGIN CERTIFICATE-----
-MIIBxDCCAW6gAwIBAgIQxUSXFzWJYYtOZnmmuOMKkjANBgkqhkiG9w0BAQQFADAW
-MRQwEgYDVQQDEwtSb290IEFnZW5jeTAeFw0wMzA3MDgxODQ3NTlaFw0zOTEyMzEy
-MzU5NTlaMB8xHTAbBgNVBAMTFFdTRTJRdWlja1N0YXJ0Q2xpZW50MIGfMA0GCSqG
-SIb3DQEBAQUAA4GNADCBiQKBgQC+L6aB9x928noY4+0QBsXnxkQE4quJl7c3PUPd
-Vu7k9A02hRG481XIfWhrDY5i7OEB7KGW7qFJotLLeMec/UkKUwCgv3VvJrs2nE9x
-O3SSWIdNzADukYh+Cxt+FUU6tUkDeqg7dqwivOXhuOTRyOI3HqbWTbumaLdc8juf
-z2LhaQIDAQABo0swSTBHBgNVHQEEQDA+gBAS5AktBh0dTwCNYSHcFmRjoRgwFjEU
-MBIGA1UEAxMLUm9vdCBBZ2VuY3mCEAY3bACqAGSKEc+41KpcNfQwDQYJKoZIhvcN
-AQEEBQADQQAfIbnMPVYkNNfX1tG1F+qfLhHwJdfDUZuPyRPucWF5qkh6sSdWVBY5
-sT/txBnVJGziyO8DPYdu2fPMER8ajJfl
------END CERTIFICATE-----
\ No newline at end of file
+-----BEGIN CERTIFICATE-----
+MIIBxDCCAW6gAwIBAgIQxUSXFzWJYYtOZnmmuOMKkjANBgkqhkiG9w0BAQQFADAW
+MRQwEgYDVQQDEwtSb290IEFnZW5jeTAeFw0wMzA3MDgxODQ3NTlaFw0zOTEyMzEy
+MzU5NTlaMB8xHTAbBgNVBAMTFFdTRTJRdWlja1N0YXJ0Q2xpZW50MIGfMA0GCSqG
+SIb3DQEBAQUAA4GNADCBiQKBgQC+L6aB9x928noY4+0QBsXnxkQE4quJl7c3PUPd
+Vu7k9A02hRG481XIfWhrDY5i7OEB7KGW7qFJotLLeMec/UkKUwCgv3VvJrs2nE9x
+O3SSWIdNzADukYh+Cxt+FUU6tUkDeqg7dqwivOXhuOTRyOI3HqbWTbumaLdc8juf
+z2LhaQIDAQABo0swSTBHBgNVHQEEQDA+gBAS5AktBh0dTwCNYSHcFmRjoRgwFjEU
+MBIGA1UEAxMLUm9vdCBBZ2VuY3mCEAY3bACqAGSKEc+41KpcNfQwDQYJKoZIhvcN
+AQEEBQADQQAfIbnMPVYkNNfX1tG1F+qfLhHwJdfDUZuPyRPucWF5qkh6sSdWVBY5
+sT/txBnVJGziyO8DPYdu2fPMER8ajJfl
+-----END CERTIFICATE-----
diff --git a/test/utils-tests.js b/test/utils-tests.js
new file mode 100644
index 00000000..b28ca25f
--- /dev/null
+++ b/test/utils-tests.js
@@ -0,0 +1,29 @@
+const fs = require("fs");
+const utils = require("../lib/utils");
+const expect = require("chai").expect;
+
+describe("Utils tests", function () {
+ describe("derToPem", function () {
+ it("will return a normalized PEM format when given an non-normalized PEM format", function () {
+ const normalizedPem = fs.readFileSync("./test/static/client_public.pem", "latin1");
+ const pemAsArray = normalizedPem.trim().split("\n");
+ const base64String = pemAsArray.slice(1, -1).join("");
+ const nonNormalizedPem =
+ pemAsArray[0] + "\n" + base64String + "\n" + pemAsArray[pemAsArray.length - 1];
+
+ expect(utils.derToPem(nonNormalizedPem)).to.equal(normalizedPem);
+ });
+
+ it("will return a normalized PEM format when given a base64 string", function () {
+ const normalizedPem = fs.readFileSync("./test/static/client_public.pem", "latin1");
+ const pemAsArray = normalizedPem.trim().split("\n");
+ const base64String = pemAsArray.slice(1, -1).join("");
+
+ expect(utils.derToPem(base64String, "CERTIFICATE")).to.equal(normalizedPem);
+ });
+
+ it("will throw if the format is neither PEM nor DER", function () {
+ expect(() => utils.derToPem("not a pem")).to.throw();
+ });
+ });
+});
diff --git a/test/validators/XmlCryptoUtilities/XmlCryptoUtilities/Program.cs b/test/validators/XmlCryptoUtilities/XmlCryptoUtilities/Program.cs
index 4b226b3d..6c4c48e6 100644
--- a/test/validators/XmlCryptoUtilities/XmlCryptoUtilities/Program.cs
+++ b/test/validators/XmlCryptoUtilities/XmlCryptoUtilities/Program.cs
@@ -1,4 +1,4 @@
-//
+//
// This example signs an XML file using an
// envelope signature. It then verifies the
// signed XML.
diff --git a/test/validators/XmlCryptoUtilities/XmlCryptoUtilities/utilities.cs b/test/validators/XmlCryptoUtilities/XmlCryptoUtilities/utilities.cs
index 75796a44..5ed16f80 100644
--- a/test/validators/XmlCryptoUtilities/XmlCryptoUtilities/utilities.cs
+++ b/test/validators/XmlCryptoUtilities/XmlCryptoUtilities/utilities.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
diff --git a/test/wsfed-metadata-test.js b/test/wsfed-metadata-test.js
index 8001a201..7ea0f954 100644
--- a/test/wsfed-metadata-test.js
+++ b/test/wsfed-metadata-test.js
@@ -13,7 +13,7 @@ describe("WS-Fed Metadata tests", function () {
doc
)[0];
const sig = new crypto.SignedXml();
- sig.keyInfoProvider = new crypto.FileKeyInfo("./test/static/wsfederation_metadata.pem");
+ sig.signingCert = fs.readFileSync("./test/static/wsfederation_metadata.pem");
sig.loadSignature(signature);
const result = sig.checkSignature(xml);