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

Fix key import #80

Merged
merged 8 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
32 changes: 0 additions & 32 deletions .circleci/config.yml

This file was deleted.

79 changes: 79 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: CI

on:
push:
branches:
- "**"
tags:
- "v*.*.*"

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20.x"

- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT

- uses: actions/cache@v4
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-

- name: Install dependencies
run: yarn

- name: Run tests with coverage
run: npm run coverage

- name: Upload coverage
uses: coverallsapp/github-action@v2

publish:
needs: test
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/')

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20.x"

- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT

- uses: actions/cache@v4
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-

- name: Install dependencies
run: yarn

- name: Build the project
run: npm run build

- name: Publish to npm
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
],
"reporter": [
"text-summary",
"html"
"lcov"
]
},
"mocha": {
Expand All @@ -104,4 +104,4 @@
"test/**/*.ts"
]
}
}
}
36 changes: 35 additions & 1 deletion src/signed_xml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ export class SignedXml implements XmlCore.IXmlSerializable {

protected signature = new Signature();
protected document?: Document;
/**
* If set to true, transformations with comments will be replaced with transformations without comments.
* This is a non-standard implementation to ensure compatibility with systems that do not support
* canonicalization with comments.
*/
public replaceCanonicalization = false;

/**
* Creates an instance of SignedXml.
Expand Down Expand Up @@ -169,20 +175,28 @@ export class SignedXml implements XmlCore.IXmlSerializable {
const signature = await alg.Sign(si, key, signingAlg);

this.Key = key;
this.Algorithm = algorithm;
this.XmlSignature.SignatureValue = new Uint8Array(signature);
if (XmlCore.isElement(data)) {
this.document = data.ownerDocument;
}
return this.XmlSignature;
}

private async reimportKey(key: CryptoKey, alg: Algorithm) {
if (key.algorithm.name === alg.name) {
return key;
}
const spki = await Application.crypto.subtle.exportKey("spki", key);
return Application.crypto.subtle.importKey("spki", spki, alg, true, ["verify"]);
}

public Verify(params: OptionsVerify): Promise<boolean>;
public Verify(key: CryptoKey): Promise<boolean>;
public Verify(): Promise<boolean>;
public async Verify(params?: CryptoKey | OptionsVerify) {
let content: DigestReferenceSource | undefined;
let key: CryptoKey | undefined;

if (params) {
if ("algorithm" in params && "usages" in params && "type" in params) {
key = params;
Expand All @@ -192,6 +206,10 @@ export class SignedXml implements XmlCore.IXmlSerializable {
}
}

if (key && this.Algorithm) {
key = await this.reimportKey(key, this.Algorithm);
}

if (!content) {
const xml = this.document;
if (!(xml && xml.documentElement)) {
Expand Down Expand Up @@ -227,6 +245,11 @@ export class SignedXml implements XmlCore.IXmlSerializable {
*/
public LoadXml(value: Element | string) {
this.signature = Signature.LoadXml(value);

// Load signature algorithm
this.Algorithm = CryptoConfig
.CreateSignatureAlgorithm(this.XmlSignature.SignedInfo.SignatureMethod)
.algorithm;
}

public toString() {
Expand Down Expand Up @@ -553,6 +576,17 @@ export class SignedXml implements XmlCore.IXmlSerializable {
}
return 0;
}).ForEach((transform) => {
// Non-standard implementation: If enforceCanonicalization is set to true,
// we replace transformations with comments with transformations without comments.
// This is done to ensure compatibility with systems that do not support
// canonicalization with comments.
if (this.replaceCanonicalization) {
if (transform instanceof Transforms.XmlDsigExcC14NWithCommentsTransform) {
transform = new Transforms.XmlDsigExcC14NTransform();
} else if (transform instanceof Transforms.XmlDsigC14NWithCommentsTransform) {
transform = new Transforms.XmlDsigC14NTransform();
}
}
transform.LoadInnerXml(input);
if (transform instanceof Transforms.XmlDsigXPathTransform) {
transform.GetOutput();
Expand Down
7 changes: 2 additions & 5 deletions test/config.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import { Crypto } from "@peculiar/webcrypto";
import * as fs from "fs";
import { Crypto } from "@peculiar/webcrypto";
import { DOMParser, XMLSerializer } from "@xmldom/xmldom";
import * as xmldsig from "../src";
// tslint:disable-next-line: no-var-requires

const crypto = new Crypto();
global["crypto"] = crypto;
global["DOMParser"] = DOMParser;
global["XMLSerializer"] = XMLSerializer;

// Set crypto to XML application
xmldsig.Application.setEngine("NodeJS", crypto);
xmldsig.Application.setEngine("NodeJS", new Crypto());

export function readXml(path: string) {
const data = fs.readFileSync(path, { encoding: "utf8" });
Expand Down
86 changes: 80 additions & 6 deletions test/sign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import * as assert from "assert";
import * as child_process from "child_process";
import * as fs from "fs";
import * as xmldsig from "../src";
import { Crypto } from "@peculiar/webcrypto";
import { Convert } from "pvtsutils";

const SIGN_XML_FILE = "sign.xml";
const { crypto } = xmldsig.Application;

context("XML Signing + XMLSEC verification", () => {

Expand Down Expand Up @@ -110,8 +110,6 @@ context("XML Signing + XMLSEC verification", () => {
});

it("Sign multiple contents", async () => {
const crypto = new Crypto();
// tslint:disable-next-line: no-shadowed-variable
const alg: RsaHashedKeyGenParams = {
name: "RSASSA-PKCS1-v1_5",
hash: "SHA-256",
Expand Down Expand Up @@ -157,8 +155,6 @@ context("XML Signing + XMLSEC verification", () => {
});

it("xhtml with xpath and multiple signatures", async () => {
const crypto = new Crypto();

async function sign(doc: Document) {
const keys = await crypto.subtle.generateKey(alg, false, ["sign", "verify"]);

Expand Down Expand Up @@ -208,7 +204,7 @@ context("XML Signing + XMLSEC verification", () => {

const signatures = xmlDoc.getElementsByTagNameNS(xmldsig.XmlSignature.NamespaceURI, "Signature");
const signedData = new xmldsig.SignedXml(xmlDoc);
for (let i=0; i<signatures.length; i++) {
for (let i = 0; i < signatures.length; i++) {
const signature = signatures[i];
signedData.LoadXml(signature);

Expand All @@ -219,4 +215,82 @@ context("XML Signing + XMLSEC verification", () => {

});

context("Vector Tests for XML Signing and Verification with Different Algorithms", () => {
let keysRSASSA: CryptoKeyPair;
let keysRSAPSS: CryptoKeyPair;
const algRSASSA = {
name: "RSASSA-PKCS1-v1_5",
hash: "SHA-1",
publicExponent: new Uint8Array([1, 0, 1]),
modulusLength: 2048,
};
const algRSAPSS = {
name: "RSA-PSS",
hash: "SHA-256",
publicExponent: new Uint8Array([1, 0, 1]),
modulusLength: 2048,
saltLength: 32,
};

before(async () => {
// Generate keys for different algorithms
keysRSASSA = await crypto.subtle.generateKey(algRSASSA, false, ["sign", "verify"]);
keysRSAPSS = await crypto.subtle.generateKey(algRSAPSS, false, ["sign", "verify"]);
});

async function signXML(xml: string, alg: any, keys: CryptoKeyPair) {
const signedXml = new xmldsig.SignedXml();
const xmlDocument = xmldsig.Parse(xml);

const signature = await signedXml.Sign(
alg,
keys.privateKey,
xmlDocument,
{
keyValue: keys.publicKey,
references: [
{
hash: alg.hash,
transforms: ["enveloped"],
},
],
});

xmlDocument.documentElement.appendChild(signature.GetXml()!);

// serialize XML
const oSerializer = new XMLSerializer();
return oSerializer.serializeToString(xmlDocument);
}

async function verifyXML(xml: string, alg: any, keys: CryptoKeyPair) {
const vXml = xmldsig.Parse(xml);
const vSignature = vXml.getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "Signature")[0];
const verifyXml = new xmldsig.SignedXml(vXml);
verifyXml.LoadXml(vSignature);
return await verifyXml.Verify();
}

it("RSASSA-PKCS1-v1_5 with SHA-1 signing and RSASSA-PKCS1-v1_5 with SHA-256 verification", async () => {
const xml = `<root><first/><second/></root>`;
const signedXML = await signXML(xml, algRSASSA, keysRSASSA);
const ok = await verifyXML(signedXML, { ...algRSASSA, hash: "SHA-256" }, keysRSASSA);
assert.strictEqual(ok, true);
});

it("RSA-PSS signing and RSASSA-PKCS1-v1_5 verification", async () => {
const xml = `<root><first/><second/></root>`;
const signedXML = await signXML(xml, algRSAPSS, keysRSAPSS);
const ok = await verifyXML(signedXML, algRSASSA, keysRSAPSS);
assert.strictEqual(ok, true);
});

it("RSASSA-PKCS1-v1_5 with SHA-256 signing and RSA-PSS verification", async () => {
const xml = `<root><first/><second/></root>`;
const signedXML = await signXML(xml, { ...algRSASSA, hash: "SHA-256" }, keysRSASSA);
const ok = await verifyXML(signedXML, algRSAPSS, keysRSASSA);
assert.strictEqual(ok, true);
});
});

});
2 changes: 1 addition & 1 deletion test/sign_options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ context("Signing options", () => {
let keys;

before(async () => {
keys = await crypto.subtle.generateKey(algorithm, false, ["sign", "verify"]);
keys = await xmldsig.Application.crypto.subtle.generateKey(algorithm, false, ["sign", "verify"]);
});

async function Sign(options) {
Expand Down
Loading