Skip to content

Commit

Permalink
Merge pull request #80 from PeculiarVentures/fix-verification
Browse files Browse the repository at this point in the history
Fix key import
  • Loading branch information
microshine authored Aug 13, 2024
2 parents 6138a3e + 388cffb commit 7f7f1dc
Show file tree
Hide file tree
Showing 8 changed files with 1,284 additions and 1,042 deletions.
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

0 comments on commit 7f7f1dc

Please sign in to comment.