Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand the options, move idmode into options, fix types #323

Merged
merged 7 commits into from
Jun 28, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 32 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,8 @@ var SignedXml = require("xml-crypto").SignedXml,

var xml = "<library>" + "<book>" + "<name>Harry Potter</name>" + "</book>" + "</library>";

var sig = new SignedXml();
var sig = new SignedXml({ privateKey: fs.readFileSync("client.pem") });
sig.addReference("//*[local-name(.)='book']");
sig.privateKey = fs.readFileSync("client.pem");
sig.computeSignature(xml);
fs.writeFileSync("signed.xml", sig.getSignedXml());
```
Expand Down Expand Up @@ -139,8 +138,7 @@ var signature = select(
doc,
"//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']"
)[0];
var sig = new SignedXml();
sig.publicCert = new FileKeyInfo("client_public.pem");
var sig = new SignedXml({ publicCert: fs.readFileSync("client_public.pem") });
sig.loadSignature(signature);
var res = sig.checkSignature(xml);
if (!res) console.log(sig.validationErrors);
Expand Down Expand Up @@ -173,9 +171,11 @@ which makes XML developers confused and then leads to incorrect implementation f
If you keep failing verification, it is worth trying to guess such a hidden transform and specify it to the option as below:

```javascript
var option = { implicitTransforms: ["http://www.w3.org/TR/2001/REC-xml-c14n-20010315"] };
var sig = new SignedXml(null, option);
sig.publicCert = new FileKeyInfo("client_public.pem");
var options = {
implicitTransforms: ["http://www.w3.org/TR/2001/REC-xml-c14n-20010315"],
publicCert: fs.readFileSync("client_public.pem"),
};
var sig = new SignedXml(options);
sig.loadSignature(signature);
var res = sig.checkSignature(xml);
```
Expand All @@ -196,9 +196,19 @@ See [xpath.js](https://github.com/yaronn/xpath.js) for usage. Note that this is

### SignedXml

The `SignedXml` constructor provides an abstraction for sign and verify xml documents. The object is constructed using `new SignedXml([idMode])` where:
The `SignedXml` constructor provides an abstraction for sign and verify xml documents. The object is constructed using `new SignedXml(options?: SignedXmlOptions)` where the possible options are:

- `idMode` - if the value of `"wssecurity"` is passed it will create/validate id's with the ws-security namespace.
- `idMode` - default `null` - if the value of `wssecurity` is passed it will create/validate id's with the ws-security namespace.
- `idAttribute` - string - default `Id` or `ID` or `id` - the name of the attribute that contains the id of the element
- `privateKey` - string or Buffer - default `null` - the private key to use for signing
- `publicCert` - string or Buffer - default `null` - the public certificate to use for verifying
- `signatureAlgorithm` - string - default `http://www.w3.org/2000/09/xmldsig#rsa-sha1` - the signature algorithm to use
- `canonicalizationAlgorithm` - string - default `http://www.w3.org/TR/2001/REC-xml-c14n-20010315` - the canonicalization algorithm to use
- `inclusiveNamespacesPrefixList` - string - default `null` - a list of namespace prefixes to include during canonicalization
- `implicitTransforms` - string[] - default `[]` - a list of implicit transforms to use during verification
- `keyInfoAttributes` - object - default `{}` - a hash of attributes and values `attrName: value` to add to the KeyInfo node
- `getKeyInfoContent` - function - default `SignedXml.geTKeyInfoContent` - a function that returns the content of the KeyInfo node
- `getCertFromKeyInfo` - function - default `SignedXml.getCertFromKeyInfo` - a function that returns the certificate from the KeyInfo node

#### API

Expand Down Expand Up @@ -325,19 +335,22 @@ Now do the signing. Note how we configure the signature to use the above algorit

```javascript
function signXml(xml, xpath, key, dest) {
var sig = new SignedXml();
var options = {
publicCert: fs.readFileSync("my_public_cert.pem", "latin1"),
privateKey: fs.readFileSync(key),
/*configure the signature object to use the custom algorithms*/
signatureAlgorithm: "http://mySignatureAlgorithm",
canonicalizationAlgorithm: "http://MyCanonicalization",
};

var sig = new SignedXml(options);

/*configure the signature object to use the custom algorithms*/
sig.signatureAlgorithm = "http://mySignatureAlgorithm";
sig.publicCert = fs.readFileSync("my_public_cert.pem", "latin1");
sig.canonicalizationAlgorithm = "http://MyCanonicalization";
sig.addReference(
"//*[local-name(.)='x']",
["http://MyTransformation"],
"http://myDigestAlgorithm"
);

sig.privateKey = fs.readFileSync(key);
sig.addReference(xpath);
sig.computeSignature(xml);
fs.writeFileSync(dest, sig.getSignedXml());
Expand Down Expand Up @@ -369,9 +382,8 @@ function AsyncSignatureAlgorithm() {
};
}

SignedXml.SignatureAlgorithms["http://asyncSignatureAlgorithm"] = AsyncSignatureAlgorithm;
var sig = new SignedXml();
sig.signatureAlgorithm = "http://asyncSignatureAlgorithm";
var sig = new SignedXml({ signatureAlgorithm: "http://asyncSignatureAlgorithm" });
sig.SignatureAlgorithms["http://asyncSignatureAlgorithm"] = AsyncSignatureAlgorithm;
sig.computeSignature(xml, opts, function (err) {
var signedResponse = sig.getSignedXml();
});
Expand Down Expand Up @@ -421,9 +433,8 @@ var SignedXml = require("xml-crypto").SignedXml,

var xml = "<library>" + "<book>" + "<name>Harry Potter</name>" + "</book>" + "</library>";

var sig = new SignedXml();
var sig = new SignedXml({ privateKey: fs.readFileSync("client.pem") });
sig.addReference("//*[local-name(.)='book']");
sig.privateKey = fs.readFileSync("client.pem");
sig.computeSignature(xml, {
prefix: "ds",
});
Expand All @@ -445,9 +456,8 @@ var SignedXml = require("xml-crypto").SignedXml,

var xml = "<library>" + "<book>" + "<name>Harry Potter</name>" + "</book>" + "</library>";

var sig = new SignedXml();
var sig = new SignedXml({ privateKey: fs.readFileSync("client.pem") });
sig.addReference("//*[local-name(.)='book']");
sig.privateKey = fs.readFileSync("client.pem");
sig.computeSignature(xml, {
location: { reference: "//*[local-name(.)='book']", action: "after" }, //This will place the signature after the book element
});
Expand Down
35 changes: 19 additions & 16 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,17 @@ type SignatureAlgorithmType =
* Options for the SignedXml constructor.
*/
type SignedXmlOptions = {
canonicalizationAlgorithm?: TransformAlgorithmType;
inclusiveNamespacesPrefixList?: string;
idMode?: "wssecurity";
idAttribute?: string;
implicitTransforms?: ReadonlyArray<TransformAlgorithmType>;
privateKey?: crypto.KeyLike;
publicCert?: crypto.KeyLike;
signatureAlgorithm?: SignatureAlgorithmType;
canonicalizationAlgorithm?: CanonicalizationAlgorithmType;
inclusiveNamespacesPrefixList?: string;
implicitTransforms?: ReadonlyArray<TransformAlgorithmType>;
keyInfoAttributes?: { [attrName: string]: string };
getKeyInfoContent?(args?: GetKeyInfoContentArgs): string | null;
getCertFromKeyInfo?(keyInfo: string): string | null;
};

type CanonicalizationOrTransformationAlgorithmProcessOptions = {
Expand Down Expand Up @@ -186,8 +192,6 @@ export class SignedXml {
canonicalizationAlgorithm: TransformAlgorithmType;
// It specifies a list of namespace prefixes that should be considered "inclusive" during the canonicalization process.
inclusiveNamespacesPrefixList: string;
// The structure for managing keys and KeyInfo section in XML data. See {@link KeyInfoProvider}
keyInfoProvider: KeyInfoProvider;
// Specifies the data to be signed within an XML document. See {@link Reference}
references: Reference[];
// One of the supported signature algorithms. See {@link SignatureAlgorithmType}
Expand All @@ -200,10 +204,9 @@ export class SignedXml {

/**
* The SignedXml constructor provides an abstraction for sign and verify xml documents. The object is constructed using
* @param idMode if the value of "wssecurity" is passed it will create/validate id's with the ws-security namespace.
* @param options {@link SignedXmlOptions
* @param options {@link SignedXmlOptions}
*/
constructor(idMode?: "wssecurity" | null, options?: SignedXmlOptions);
constructor(options?: SignedXmlOptions);

/**
* Due to key-confusion issues, it's risky to have both hmac
Expand Down Expand Up @@ -344,17 +347,17 @@ export class SignedXml {
getCertFromKeyInfo(keyInfo: string): string | null;
}

export class Utils {
export declare module utils {
/**
* @param pem The PEM-encoded base64 certificate to strip headers from
*/
static pemToDer(pem: string): string;
export function pemToDer(pem: string): string;

/**
* @param der The DER-encoded base64 certificate to add PEM headers too
* @param pemLabel The label of the header and footer to add
*/
static derToPem(
export function derToPem(
der: string,
pemLabel: ["CERTIFICATE" | "PRIVATE KEY" | "RSA PUBLIC KEY"]
): string;
Expand All @@ -373,12 +376,12 @@ export class Utils {
* - normalize line length to maximum of 64 characters
* - ensure that 'preeb' has line ending '\n'
*
* With couple of notes:
* With a couple of notes:
* - 'eol' is normalized to '\n'
*
* @param pem The PEM string to normalize to RFC7468 'stricttextualmsg' definition
*/
static normalizePem(pem: string): string;
export function normalizePem(pem: string): string;

/**
* PEM format has wide range of usages, but this library
Expand All @@ -393,9 +396,9 @@ export class Utils {
* - 'preeb' and 'posteb' lines are limited to 64 characters, but
* should not cause any issues in context of PKIX, PKCS and CMS.
*/
PEM_FORMAT_REGEX: RegExp;
EXTRACT_X509_CERTS: RegExp;
BASE64_REGEX: RegExp;
export const EXTRACT_X509_CERTS: RegExp;
export const PEM_FORMAT_REGEX: RegExp;
export const BASE64_REGEX: RegExp;
}

/**
Expand Down
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const select = require("xpath").select;
const utils = require("./lib/utils");

module.exports = require("./lib/signed-xml");
module.exports.xpath = function (node, xpath) {
return select(xpath, node);
};
module.exports.utils = utils;
54 changes: 35 additions & 19 deletions lib/signed-xml.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,49 @@ const signatureAlgorithms = require("./signature-algorithms");
* @type {import ("../index.d.ts").SignedXml}
*/
class SignedXml {
constructor(idMode, options = {}) {
this.options = options;
this.idMode = idMode;
/** @param {import("../index.d.ts").SignedXmlOptions} [options={}] */
constructor(options = {}) {
const {
idMode,
idAttribute,
privateKey,
publicCert,
signatureAlgorithm,
canonicalizationAlgorithm,
inclusiveNamespacesPrefixList,
implicitTransforms,
keyInfoAttributes,
getKeyInfoContent,
getCertFromKeyInfo,
} = options;

// Options
this.idMode = idMode || null;
this.idAttributes = ["Id", "ID", "id"];
if (idAttribute) {
this.idAttributes.unshift(idAttribute);
}
this.privateKey = privateKey || null;
this.publicCert = publicCert || null;
this.signatureAlgorithm = signatureAlgorithm || "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
this.canonicalizationAlgorithm =
canonicalizationAlgorithm || "http://www.w3.org/2001/10/xml-exc-c14n#";
this.inclusiveNamespacesPrefixList = inclusiveNamespacesPrefixList || "";
this.implicitTransforms = implicitTransforms || [];
this.keyInfoAttributes = keyInfoAttributes || {};
this.getKeyInfoContent = getKeyInfoContent || SignedXml.getKeyInfoContent;
this.getCertFromKeyInfo = getCertFromKeyInfo || SignedXml.getCertFromKeyInfo;

// Internal state
this.references = [];
this.id = 0;
this.privateKey = null;
this.publicCert = null;
this.signatureAlgorithm =
this.options.signatureAlgorithm || "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
this.canonicalizationAlgorithm =
this.options.canonicalizationAlgorithm || "http://www.w3.org/2001/10/xml-exc-c14n#";
this.inclusiveNamespacesPrefixList = this.options.inclusiveNamespacesPrefixList || "";
this.signedXml = "";
this.signatureXml = "";
this.signatureNode = null;
this.signatureValue = "";
this.originalXmlWithIds = "";
this.validationErrors = [];
this.keyInfo = null;
this.idAttributes = ["Id", "ID", "id"];
if (this.options.idAttribute) {
this.idAttributes.unshift(this.options.idAttribute);
}
this.implicitTransforms = this.options.implicitTransforms || [];
this.getKeyInfoContent = SignedXml.getKeyInfoContent;
this.getCertFromKeyInfo = SignedXml.getCertFromKeyInfo;
this.keyInfoAttributes = {};

this.CanonicalizationAlgorithms = {
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315": c14n.C14nCanonicalization,
Expand Down Expand Up @@ -65,7 +81,7 @@ class SignedXml {
}

/**
* Due to key-confusion issues, its risky to have both hmac
* Due to key-confusion issues, it's risky to have both hmac
* and digital signature algos enabled at the same time.
* This enables HMAC and disables other signing algos.
*/
Expand Down
12 changes: 6 additions & 6 deletions test/signature-unit-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe("Signature unit tests", function () {
doc
)[0];

const sig = new SignedXml(mode);
const sig = new SignedXml({ idMode: mode });
sig.publicCert = fs.readFileSync("./test/static/client_public.pem");
sig.loadSignature(node);
try {
Expand Down Expand Up @@ -92,7 +92,7 @@ describe("Signature unit tests", function () {
"<x xmlns:wsu='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd' " +
prefix +
"Id='_1'></x>";
const sig = new SignedXml(mode);
const sig = new SignedXml({ idMode: mode });
sig.privateKey = fs.readFileSync("./test/static/client.pem");
sig.addReference("//*[local-name(.)='x']");
sig.computeSignature(xml);
Expand All @@ -112,7 +112,7 @@ describe("Signature unit tests", function () {

function verifyAddsId(mode, nsMode) {
const xml = '<root><x xmlns="ns"></x><y attr="value"></y><z><w></w></z></root>';
const sig = new SignedXml(mode);
const sig = new SignedXml({ idMode: mode });
sig.privateKey = fs.readFileSync("./test/static/client.pem");

sig.addReference("//*[local-name(.)='x']");
Expand Down Expand Up @@ -177,7 +177,7 @@ describe("Signature unit tests", function () {
function verifyReferenceNS() {
const xml =
'<root xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"><name wsu:Id="_1">xml-crypto</name><repository wsu:Id="_2">github</repository></root>';
const sig = new SignedXml("wssecurity");
const sig = new SignedXml({ idMode: "wssecurity" });

sig.privateKey = fs.readFileSync("./test/static/client.pem");

Expand Down Expand Up @@ -922,7 +922,7 @@ describe("Signature unit tests", function () {

it("creates InclusiveNamespaces element inside CanonicalizationMethod when inclusiveNamespacesPrefixList is set on SignedXml options", function () {
const xml = "<root><x /></root>";
const sig = new SignedXml(null, { inclusiveNamespacesPrefixList: "prefix1 prefix2" });
const sig = new SignedXml({ inclusiveNamespacesPrefixList: "prefix1 prefix2" });
sig.privateKey = fs.readFileSync("./test/static/client.pem");
sig.publicCert = null;

Expand Down Expand Up @@ -955,7 +955,7 @@ describe("Signature unit tests", function () {

it("does not create InclusiveNamespaces element inside CanonicalizationMethod when inclusiveNamespacesPrefixList is not set on SignedXml options", function () {
const xml = "<root><x /></root>";
const sig = new SignedXml(null); // Omit inclusiveNamespacesPrefixList property
const sig = new SignedXml(); // Omit inclusiveNamespacesPrefixList property
sig.privateKey = fs.readFileSync("./test/static/client.pem");
sig.publicCert = null;

Expand Down