xsign
v1.0.0
Published
Sign and validate XML documents against the SERPRO Integra Contador XMLDSig profile, extensible to other documents via typed templates.
Maintainers
Readme
xsign
Sign and validate XML documents against the SERPRO Integra Contador XMLDSig profile — with typed, opt-in templates for known document shapes, starting with the AUTENTICAPROCURADOR authorization term.
Install
npm install xsignNode.js 20 or later.
Quick start
import { readFile, writeFile } from "node:fs/promises";
import { Credential, XmlSigner, serproXmldsig } from "xsign";
const xml = await readFile("term.xml", "utf8");
const pfx = await readFile("certificate.pfx");
const credential = Credential.fromPkcs12(pfx, process.env["PFX_PASSWORD"] ?? "");
const signed = new XmlSigner(serproXmldsig).sign(xml, credential);
await writeFile("term-signed.xml", signed.xml);sign throws CertificateExpiredError for a certificate outside its validity window,
InvalidDocumentError if xml already contains a <Signature>, or a parse error for malformed
XML. Every error this library throws extends XsignError and carries a stable code:
import { Credential, XsignError } from "xsign";
declare const pfx: Uint8Array;
declare const password: string;
try {
Credential.fromPkcs12(pfx, password);
} catch (error) {
if (error instanceof XsignError) {
console.error(error.code, error.message);
}
}Verify a signature
import { SignatureValidator, serproXmldsig } from "xsign";
declare const signedXml: string;
new SignatureValidator(serproXmldsig).validate(signedXml);
// Throws ProfileViolationError or SignatureVerificationError; returns nothing on success.validate checks both that the XMLDSig structure matches the profile (canonicalization,
signature and digest algorithms, transforms, reference URI, certificate count) and that the
signature is cryptographically valid against the document.
Templates
A template is a typed, reusable description of one known document: its root element, the
signature profile it must be signed with, and a render function that builds well-formed,
escaped XML from typed input. xsign ships one, for SERPRO's Integra Contador
AUTENTICAPROCURADOR authorization term, importable from the xsign/templates subpath so
importing the core xsign package never pulls in template code.
import { Credential, XmlSigner } from "xsign";
import { templates, type TemplateInput } from "xsign/templates";
const input: TemplateInput<"serpro/autentica-procurador"> = {
signedOn: "20260101",
validUntil: "20270101",
recipient: { document: "00000000000191", name: "ACME CONTABILIDADE LTDA", kind: "PJ" },
author: { document: "11111111000191", name: "JOHN DOE", kind: "PF" },
};
const template = templates["serpro/autentica-procurador"];
const xml = template.render(input);
declare const credential: Credential;
const signed = new XmlSigner(template.profile).sign(xml, credential);
void signed;template.assertMatches(xml) throws InvalidDocumentError when xml's root element doesn't
match the template — useful to validate an externally-produced document before signing it.
TemplateId is derived from the registry's own keys, so an unknown id is a compile error, and
TemplateInput<Id> is derived from that template's own render signature — neither is
hand-maintained. Adding a template is a new file plus one line in the registry; see
docs/templates.md and CONTRIBUTING.md.
API reference
xsign
| Export | Kind | What it is |
|---|---|---|
| Credential | class | An RSA private key paired with its X.509 certificate. Credential.fromPkcs12(pkcs12, password) and Credential.fromPem({ privateKey, certificate }). |
| PemCredential | type | Input shape for Credential.fromPem. |
| CertificateInfo | class | Parsed certificate metadata: subject, issuer, fingerprint, validity window, key type. CertificateInfo.parse(pem). |
| XmlSigner | class | new XmlSigner(profile).sign(xml, credential) → SignedDocument. |
| SignatureValidator | class | new SignatureValidator(profile).validate(xml). |
| SignedDocument | class | The signed XML plus .toBase64(). |
| SignatureProfile | type | The XMLDSig shape a document must be signed with: canonicalization, signature and digest algorithms, transforms, reference URI, certificate count. |
| serproXmldsig | const | The SignatureProfile SERPRO's Integra Contador requires — see docs/serpro-profile.md for what each field enforces and why. |
| XsignError | class | Abstract base of every error this library throws. Never thrown directly; always carries a stable code. |
| InvalidCredentialError | class | A PKCS#12 bundle couldn't be opened, or its key and certificate don't pair. |
| CertificateExpiredError | class | The certificate is outside its validity window at signing time. |
| UnsupportedKeyError | class | The certificate's key algorithm isn't RSA. |
| InvalidDocumentError | class | The XML is malformed, has an unexpected shape, or is already signed. |
| ProfileViolationError | class | A signed document's XMLDSig structure doesn't match the required profile. |
| SignatureVerificationError | class | The signature failed cryptographic verification. |
xsign/templates
| Export | Kind | What it is |
|---|---|---|
| templates | const | Every registered template, keyed by stable id. Frozen. |
| TemplateId | type | keyof typeof templates — derived, so an unknown id doesn't compile. |
| TemplateInput<Id> | type | The typed input a given template's render expects — derived from that template's own signature. |
CLI
A thin shell over the same public API — everything it does, the library also does.
xsign --xml term.xml --pfx certificate.pfx
xsign --xml term.xml --pfx certificate.pfx --out signed.xml --template serpro/autentica-procurador
xsign --verify signed.xml| Flag | Effect |
|---|---|
| --xml <path> | Document to sign. |
| --pfx <path> | A1 certificate, .pfx / .p12. |
| --out <path> | Output path. Default: <xml path>-signed.xml. |
| --base64 / --no-base64 | Also write <out>.base64.txt. On by default. |
| --password-stdin | Read the PFX password from stdin instead of prompting. |
| --template <id> | Validate the XML against a known template before signing. |
| --verify <path> | Verify an already-signed XML. Signs nothing. |
| --help | Show usage. |
The XML (and, when --template is given, its root element) is always read and validated
before the password is requested — a typo'd path shouldn't cost you a password entry.
The PFX password
Three ways to supply it, in this order of precedence:
| # | How | For |
|---|---|---|
| 1 | --password-stdin | Automation. cat secret \| xsign --password-stdin ... |
| 2 | PFX_PASSWORD environment variable | CI/CD and containers, where the secret already arrives via env. |
| 3 | Interactive, no-echo prompt | Default when neither of the above is set — fails immediately, instead of hanging, when there's no TTY to prompt on either. |
There is no --password flag, on purpose: a password in a command-line argument leaks
through both the shell history and the process list (any user on the machine can read it with
ps aux while the command runs). It isn't missing by oversight; it will not be added.
Security notes on the PFX
- A
.pfx/.p12file contains a private key. Treat it like any other secret: never commit it. This repo's own.gitignorealready excludes*.pfxand*.p12. - The library API never reads an environment variable, opens a prompt, or touches a file for
the password — it takes a
stringand nothing else. Where that string comes from (a secrets manager, a prompt, an env var) is entirely the caller's decision. stringin JavaScript is immutable, so a password cannot be scrubbed from memory after use — it lives until the garbage collector gets to it. That's true of any JS signing library, not something specific toxsign. If your threat model includes a process heap dump, the key belongs in an HSM, not in this package.- A successful signature says less than it looks like. See
docs/what-signing-proves.mdfor exactly whatxsignproves (integrity, key possession, profile conformance) and what it deliberately leaves out (chain trust, revocation, timestamping).
Contributing
Issues and PRs are welcome. See CONTRIBUTING.md for the dev setup, the
test-driven workflow this project is written under, and how to add a new template.
Changelog
See CHANGELOG.md.
