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>"; - }; - 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}`; +}; + +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 += ""; + 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>"; -}; - -/** - * 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);