npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@penneo/xadesjs

v2.0.17

Published

A pure Typescript/Javascript implementation of XAdES based on XMLDSIGjs.

Downloads

8

Readme

XAdESjs

license CircleCI Coverage Status npm version

NPM

XAdES is short for "XML Advanced Electronic Signatures", it is a superset of XMLDSIG. This library aims to provide an implementation of XAdES in Typescript/Javascript that is built on XMLDSIGjs.

Since it is based on XMLDSIGjs and that library uses Web Crypto for cryptographic operations it can be used both in browsers and in Node.js (when used with a polyfill like node-webcrypto-ossl or node-webcrypto-p11).

There are seven different profiles of XAdES, they are:

  • Basic Electronic Signature (XAdES-BES)
  • XAdES with Timestamp (XAdES-T)
  • XAdES with Complete Validation Data (XAdES-C)
  • XAdES with Extended Validation Data (XAdES-X)
  • XAdES with Extended Long Term Validation Data (XAdES-X-L)
  • XAdES with Archiving Validation Data (XAdES-A)
  • XAdES with Explicit policy electronic signatures (XAdES-EPES)

They differ slightly based on what is included in the signature:

| | Provides Digital Signature | Includes Cryptographic Timestamp | Includes Revocation References | Includes Revocation Data | Allows Secure Timestamp Countersignature | |------------|----------------------------|----------------------------------|--------------------------------|--------------------------|------------------------------------------| | XAdES-BES | Yes | No | No | No | No | | XAdES-EPES | Yes | No | No | No | No | | XAdES-T | Yes | Yes | No | No | No | | XAdES-C | Yes | Yes | Yes | No | No | | XAdES-X | Yes | Yes | Yes | No | No | | XAdES-X-L | Yes | Yes | Yes | Yes | No | | XAdES-A | Yes | Yes | Yes | Yes | Yes |

  • Only XAdES-BES (in BOLD) is fully supported by XAdESjs. For the other variants can be created, decoded and verified but the caller must do the construction and policy to ensure compliant messages on their own.

INSTALLING

npm install xadesjs

The npm module has a dist folder with the following files:

| Name | Size | Description | |-----------------|--------|------------------------------------------------| | index.js | 105 Kb | UMD module with external modules. Has comments | | xades.js | 803 Kb | UMD bundle module. Has comments | | xades.min.js | 296 Kb | minified UMD bundle module |

There is also a lib folder with an ES2015 JS file which you can use with rollup bundler.

COMPATABILITY

CRYPTOGRAPHIC ALGORITHM SUPPORT

| Name | SHA1 | SHA2-256 | SHA2-384 | SHA2-512 | |-------------------|------|----------|----------|----------| | RSASSA-PKCS1-v1_5 | X | X | X | X | | RSA-PSS | X | X | X | X | | ECDSA | X | X | X | X | | HMAC | X | X | X | X |

CANONICALIZATION ALGORITHM SUPPORT

  • XmlDsigC14NTransform
  • XmlDsigC14NWithCommentsTransform
  • XmlDsigExcC14NTransform
  • XmlDsigExcC14NWithCommentsTransform
  • XmlDsigEnvelopedSignatureTransform
  • XmlDsigBase64Transform

PLATFORM SUPPORT

XAdESjs works with any browser that suppports Web Crypto. Since node does not have Web Crypto you will need a polyfill on this platform, for this reason the npm package includes node-webcrypto-ossl; browsers do not need this dependency and in those cases though it will be installed it will be ignored.

If you need to use a Hardware Security Module we have also created a polyfill for Web Crypto that supports PKCS #11. Our polyfill for this is node-webcrypto-p11.

To use node-webcrypto-ossl you need to specify you want to use it, that looks like this:

var xadesjs = require("./built/xades.js");
var WebCrypto = require("node-webcrypto-ossl");

xadesjs.Application.setEngine("OpenSSL", new WebCrypto());

The node-webcrypto-p11 polyfill will work the same way. The only difference is that you have to specify the details about your PKCS #11 device when you instansiate it:

var xadesjs = require("./built/xades.js");
var WebCrypto = require("node-webcrypto-p11").WebCrypto;

xadesjs.Application.setEngine("PKCS11", new WebCrypto({
    library: "/path/to/pkcs11.so",
	name: "Name of PKCS11 lib",
	slot: 0,
    sessionFlags: 2 | 4, // RW_SESSION | SERIAL_SESSION
	pin: "token pin"
}));

WARNING

Using XMLDSIG is a bit like running with scissors, that said it is needed for interoperability with a number of systems, for this reason, we have done this implementation.

Usage

Sign

SignedXml.Sign(algorithm: Algorithm, key: CryptoKey, data: Document, options?: OptionsXAdES): PromiseLike<Signature>;

Parameters

| Name | Description | |:--------------|:------------------------------------------------------------------------| | algorithm | Signing Algorithm | | key | Signing Key | | data | XML document which must be signed | | options | Additional options |

Options

interface OptionsXAdES {
    /**
     * Public key for KeyInfo block
     */
    keyValue?: CryptoKey;
    /**
     * List of X509 Certificates
     */
    x509?: string[];
    /**
     * List of Reference
     * Default is Reference with hash alg SHA-256 and exc-c14n transform  
     */
    references?: OptionsSignReference[];

    // Signed signature properties

    signingCertificate?: string;
    policy?: OptionsPolicyId;
    productionPlace?: OptionsProductionPlace;
    signerRole?: OptionsSignerRole;
}

interface OptionsSignReference {
    /**
     * Id of Reference
     */
    id?: string;
    uri?: string;
    /**
     * Hash algorithm
     */
    hash: AlgorithmIdentifier;
    /**
     * List of transforms
     */
    transforms?: OptionsSignTransform[];
}

type OptionsSignTransform = "enveloped" | "c14n" | "exc-c14n" | "c14n-com" | "exc-c14n-com" | "base64";

interface OptionsSignerRole {
    claimed?: string[];
    certified?: string[];
}

interface OptionsProductionPlace {
    city?: string;
    state?: string;
    code?: string;
    country?: string;
}

interface OptionsPolicyId {
}

Verify

Verify(key?: CryptoKey): PromiseLike<boolean>;

Parameters

| Name | Description | |:--------------|:------------------------------------------------------------------------| | key | Verifying Key. Optional. If key not set it looks for keys in KeyInfo element of Signature. |

EXAMPLES

Create XAdES-BES Signature

In Node

var xadesjs = require("xadesjs");
var WebCrypto = require("node-webcrypto-ossl");

xadesjs.Application.setEngine("OpenSSL", new WebCrypto());

// Generate RSA key pair
var privateKey, publicKey;
xadesjs.Application.crypto.subtle.generateKey(
    {
        name: "RSASSA-PKCS1-v1_5",
        modulusLength: 1024, //can be 1024, 2048, or 4096,
        publicExponent: new Uint8Array([1, 0, 1]),
        hash: { name: "SHA-1" }, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
    },
    false, //whether the key is extractable (i.e. can be used in exportKey)
    ["sign", "verify"] //can be any combination of "sign" and "verify"
)
    .then(function (keyPair) {
        // Push ganerated keys to global variable
        privateKey = keyPair.privateKey;
        publicKey = keyPair.publicKey;

        // Call sign function
        var xmlString = '<player bats="left" id="10012" throws="right">\n\t<!-- Here\'s a comment -->\n\t<name>Alfonso Soriano</name>\n\t<position>2B</position>\n\t<team>New York Yankees</team>\n</player>';
        return SignXml(xmlString, keyPair, { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-1" } });
    })
    .then(function (signedDocument) {
        console.log("Signed document:\n\n", signedDocument);
    })
    .catch(function (e) {
        console.error(e);
    });


function SignXml(xmlString, keys, algorithm) {
    return Promise.resolve()
        .then(() => {
            var xmlDoc = xadesjs.Parse(xmlString);
            var signedXml = new xadesjs.SignedXml();

            return signedXml.Sign(               // Signing document
                algorithm,                              // algorithm
                keys.privateKey,                        // key
                xmlDoc,                                 // document
                {                                       // options
                    keyValue: keys.publicKey,
                    references: [
                        { hash: "SHA-256", transforms: ["enveloped"] }
                    ],
                    productionPlace: {
                        country: "Country",
                        state: "State",
                        city: "City",
                        code: "Code",
                    },
                    signingCertificate: "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"
                })
            })
            .then(signature => signature.toString());
}

In the browser

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title>XADESJS Signature Sample</title>
</head>

<body>
    <pre id="signature"><code></code></pre>
    <script src="https://peculiarventures.github.io/pv-webcrypto-tests/src/promise.js"></script>
    <script src="https://peculiarventures.github.io/pv-webcrypto-tests/src/webcrypto-liner.min.js"></script>
    <script src="https://peculiarventures.github.io/pv-webcrypto-tests/src/asmcrypto.js"></script>
    <script src="https://peculiarventures.github.io/pv-webcrypto-tests/src/elliptic.js"></script>
    <script type="text/javascript" src="dist/xades.js"></script>
    <script type="text/javascript">
        // Generate RSA key pair
        var privateKey, publicKey;
        window.crypto.subtle.generateKey(
            {
                name: "ECDSA",
                namedCurve: "P-256"
            },
            false, //whether the key is extractable (i.e. can be used in exportKey)
            ["sign", "verify"] //can be any combination of "sign" and "verify"
        )
            .then(function (keyPair) {
                // Push ganerated keys to global variable
                privateKey = keyPair.privateKey;
                publicKey = keyPair.publicKey;
                // Call sign function
                var xmlString = '<player bats="left" id="10012" throws="right">\n\t<!-- Here\'s a comment -->\n\t<name>Alfonso Soriano</name>\n\t<position>2B</position>\n\t<team>New York Yankees</team>\n</player>';
                return SignXml(xmlString, keyPair, { name: "ECDSA", hash: { name: "SHA-1" } });
            })
            .then(function (signedDocument) {
                document.getElementById("signature").textContent = signedDocument;
                console.log("Signed document:\n\n", signedDocument);
            })
            .catch(function (e) {
                console.error(e);
            });

        function SignXml(xmlString, keys, algorithm) {
            var signedXml;
            return Promise.resolve()
                .then(() => {
                    var xmlDoc = XAdES.Parse(xmlString);
                    signedXml = new XAdES.SignedXml();

                    return signedXml.Sign(               // Signing document
                        algorithm,                              // algorithm
                        keys.privateKey,                        // key
                        xmlDoc,                                 // document
                        {                                       // options
                            keyValue: keys.publicKey,
                            references: [
                                { hash: "SHA-256", transforms: ["enveloped"] }
                            ],
                            productionPlace: {
                                country: "Country",
                                state: "State",
                                city: "City",
                                code: "Code",
                            },
                            signerRoles: {
                                claimed: ["Some role"]
                            }
                        })
                })
                .then(() => signedXml.toString());
        }
    </script>
</body>

</html>

Check XAdES-BES Signature

In Node

var XAdES = require("xadesjs");
var WebCrypto = require("node-webcrypto-ossl").default;

XAdES.Application.setEngine("OpenSSL", new WebCrypto());

var fs = require("fs");
var xmlString = fs.readFileSync("some.xml","utf8");

var signedDocument = XAdES.Parse(xmlString, "application/xml");
var xmlSignature = signedDocument.getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "Signature");

var signedXml = new xadesjs.SignedXml(signedDocument);
signedXml.LoadXml(xmlSignature[0]);
signedXml.Verify()
    .then(res => {
        console.log((res ? "Valid" : "Invalid") + " signature");
    })
    .catch(function (e) {
        console.error(e);
    });

In the browser

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title>XADESJS Signature Sample</title>
</head>

<body>
    <pre id="signature"><code></code></pre>
    <script src="https://peculiarventures.github.io/pv-webcrypto-tests/src/promise.js"></script>
    <script src="https://peculiarventures.github.io/pv-webcrypto-tests/src/webcrypto-liner.min.js"></script>
    <script src="https://peculiarventures.github.io/pv-webcrypto-tests/src/asmcrypto.js"></script>
    <script src="https://peculiarventures.github.io/pv-webcrypto-tests/src/elliptic.js"></script>
    <script type="text/javascript" src="dist/xades.js"></script>
    <script type="text/javascript">
        "use strict";
        fetch("https://cdn.rawgit.com/PeculiarVentures/xadesjs/master/test/static/valid_signature.xml")
            .then(response => response.text())
            .then(body => {
                var xmlString = body;

                var signedDocument = XAdES.Parse(xmlString);
                var xmlSignature = signedDocument.getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "Signature");

                var signedXml = new xadesjs.SignedXml(signedDocument);
                signedXml.LoadXml(xmlSignature[0]);
                signedXml.Verify()
                    .then(function (signedDocument) {
                        alert((res ? "Valid" : "Invalid") + " signature");
                    })
                    .catch(function (e) {
                        alert(e.message);
                    });
            })
    </script>
</body>

</html>

XAdES-EPES signature

const fs = require("fs");
const WebCrypto = require("node-webcrypto-ossl");
const xadesjs = require("xadesjs");
const { XMLSerializer } = require("xmldom-alpha");

const crypto = new WebCrypto();
xadesjs.Application.setEngine("OpenSSL", crypto);

function preparePem(pem) {
    return pem
        // remove BEGIN/END
        .replace(/-----(BEGIN|END)[\w\d\s]+-----/g, "")
        // remove \r, \n
        .replace(/[\r\n]/g, "");
}

function pem2der(pem) {
    pem = preparePem(pem);
    // convert base64 to ArrayBuffer
    return new Uint8Array(Buffer.from(pem, "base64")).buffer;
}

async function main() {
    const hash = "SHA-256"
    const alg = {
        name: "RSASSA-PKCS1-v1_5",
        hash,
    }

    // Read cert
    const certPem = fs.readFileSync("cert.pem", { encoding: "utf8" });
    const certDer = pem2der(certPem);

    // Read key
    const keyPem = fs.readFileSync("key.pem", { encoding: "utf8" });
    const keyDer = pem2der(keyPem);
    const key = await crypto.subtle.importKey("pkcs8", keyDer, alg, false, ["sign"]);

    // XAdES-EPES
    var xmlString = `<Test><Document attr="Hello"/></Test>`;
    var xml = xadesjs.Parse(xmlString);

    var xadesXml = new xadesjs.SignedXml();
    const x509 = preparePem(certPem);
    const signature = await xadesXml.Sign(   // Signing document
        alg,                                    // algorithm
        key,                                    // key
        xml,                                    // document
        {                                       // options
            references: [
                { hash, transforms: ["c14n", "enveloped"] }
            ],
            policy: {
                hash,
                identifier: {
                    qualifier: "OIDAsURI",
                    value: "quilifier.uri",
                },
                qualifiers: [
                    {
                        noticeRef: {
                            organization: "PeculiarVentures",
                            noticeNumbers: [1, 2, 3, 4, 5]
                        }
                    }
                ]
            },
            productionPlace: {
                country: "Russia",
                state: "Marij El",
                city: "Yoshkar-Ola",
                code: "424000",
            },
            signingCertificate: x509
        });

    // append signature
    xml.documentElement.appendChild(signature.GetXml());

    // serialize XML
    const oSerializer = new XMLSerializer();
    const sXML = oSerializer.serializeToString(xml);
    console.log(sXML.toString())
}

main()
    .catch((err) => {
        console.error(err);
    });

TESTING

In NodeJS:

npm test

THANKS AND ACKNOWLEDGEMENT

This project takes inspiration (style, approach, design and code) from both the Mono System.Security.Cryptography.Xml implementation as well as xml-crypto.

RELATED