diff --git a/README.md b/README.md index e6650ef4..96aea9b0 100644 --- a/README.md +++ b/README.md @@ -226,7 +226,7 @@ To customize this see [customizing algorithms](#customizing-algorithms) for an e When verifying a xml document you can pass the following options to the `SignedXml` constructor to customize the verify process: -- `publicCert` - **[optional]** your certificate as a string, a string of multiple certs in PEM format, or a Buffer +- `publicCert` - **[optional]** the certificate or public key to verify with, as a PEM `String` or `Buffer`. Verification uses [one key](#one-key-per-value) from it. - `privateKey` - **[optional]** your private key as a string or a Buffer - used for verifying symmetrical signatures (HMAC) The certificate that will be used to check the signature will first be determined by calling `this.getCertFromKeyInfo()`, which function you can customize as you see fit. If that returns `null`, then `publicCert` is used. If that is `null`, then `privateKey` is used (for symmetrical signing applications). @@ -565,6 +565,18 @@ MIIBxDCCAW6gAwIBAgIQxUSX... -----END CERTIFICATE----- ``` +### One key per value + +`privateKey` when signing, and `publicCert` when verifying, are passed to Node's crypto, which uses +one key from the value. + +- `privateKey` holds one private key. A file that also holds its certificate, or its chain, is + fine. +- Verification takes one key from `publicCert`. From several certificates it takes the first, which + is how a chain given leaf first works, so the chain's issuers are not trusted to sign. +- To trust several independent keys, verify with each in turn, as node-saml does for its `idpCert` + array. + ### What the parser accepts `toPem()`, `pemToDer()` and `pemCertificates()` read diff --git a/test/signature-unit-tests.spec.ts b/test/signature-unit-tests.spec.ts index 4367660b..c6fd483a 100644 --- a/test/signature-unit-tests.spec.ts +++ b/test/signature-unit-tests.spec.ts @@ -1,6 +1,6 @@ import * as xpath from "xpath"; import * as xmldom from "@xmldom/xmldom"; -import { SignedXml, createOptionalCallbackFunction } from "../src/index"; +import { SignedXml, createOptionalCallbackFunction, pemCertificates, toPem } from "../src/index"; import * as fs from "fs"; import * as crypto from "crypto"; import { expect } from "chai"; @@ -1725,4 +1725,43 @@ describe("Signature unit tests", function () { "#unique-id", ); }); + + it("verifies with the first of two certificates, and not the second, which in a chain is the issuer's", function () { + const bundle = fs.readFileSync("./test/static/client_bundle.pem", "latin1"); + const first = fs.readFileSync("./test/static/client_public.pem", "latin1"); + const second = toPem(pemCertificates(bundle)[0], "CERTIFICATE"); + + function checkSignedBy(privateKey: string, publicCert: string) { + const sig = new SignedXml({ + privateKey, + canonicalizationAlgorithm: "http://www.w3.org/2001/10/xml-exc-c14n#", + signatureAlgorithm: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", + }); + sig.addReference({ + xpath: "//*[local-name(.)='x']", + digestAlgorithm: "http://www.w3.org/2001/04/xmlenc#sha256", + transforms: [ + "http://www.w3.org/2000/09/xmldsig#enveloped-signature", + "http://www.w3.org/2001/10/xml-exc-c14n#", + ], + }); + sig.computeSignature(""); + const xml = sig.getSignedXml(); + + const verifier = new SignedXml({ publicCert }); + const signature = xpath.select1( + "//*[local-name(.)='Signature']", + new xmldom.DOMParser().parseFromString(xml), + ); + isDomNode.assertIsNodeLike(signature); + verifier.loadSignature(signature); + + return verifier.checkSignature(xml); + } + + expect(checkSignedBy(fs.readFileSync("./test/static/client.pem", "latin1"), first + second)).to + .be.true; + expect(checkSignedBy(bundle, second)).to.be.true; + expect(() => checkSignedBy(bundle, first + second)).to.throw("invalid signature"); + }); });