Skip to content
Merged
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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
Expand Down
41 changes: 40 additions & 1 deletion test/signature-unit-tests.spec.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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("<root><x /></root>");
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");
});
});
Loading