Brazilian electronic invoices require digital signing with an A1 e-CNPJ certificate (PFX/PKCS#12 format). The module handles PFX extraction, certificate info display, and XML digital signature following the NF-e MOC 4.00 specification.
Files: certificate.ts, sefaz-transport.ts (reuses certificate extraction)
Bun's native crypto cannot parse PKCS#12 (PFX) files — the pkcs12 module is not implemented. The workaround uses openssl CLI via child_process:
# Extract certificate (PEM)
openssl pkcs12 -in cert.pfx -clcerts -nokeys -passin pass:xxx -legacy
# Extract private key (PEM)
openssl pkcs12 -in cert.pfx -nocerts -nodes -passin pass:xxx -legacyThe -legacy flag is required for older PFX files that use legacy encryption (RC2-40).
export function extractCertFromPfx(pfxBuffer: Buffer, passphrase: string): string
export function extractKeyFromPfx(pfxBuffer: Buffer, passphrase: string): stringBoth:
- Write PFX buffer to a temp file
- Run
openssl pkcs12to extract PEM - Parse output with regex to isolate the PEM block (
-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----) - Clean up temp file in
finally - Throw if PEM block not found
These functions are exported and shared with sefaz-transport.ts (which needs PEM files for curl mTLS).
export function loadCertificate(pfx: Buffer, passphrase: string): CertificateDataReturns { privateKey, certificate, pfxBuffer, passphrase } — the PEM strings needed for signing and the raw PFX for storage.
export function getCertificateInfo(pfx: Buffer, passphrase: string): CertificateInfoExtracts human-readable certificate details for the settings UI:
commonName— company name from the certificatevalidFrom,validUntil— validity datesserialNumber— certificate serialissuer— CA (Certificate Authority) name
Uses node:crypto.X509Certificate (available in Bun).
export function signXml(xml: string, privateKeyPem: string, certificatePem: string): stringSigns an NF-e XML document per the MOC 4.00 signature specification:
- Reference: The
<infNFe Id="NFe...">element is the signed reference - Canonicalization: Exclusive C14N (
http://www.w3.org/2001/10/xml-exc-c14n#) - Transforms:
- Enveloped signature (removes
<Signature>before hashing) - Exclusive C14N
- Enveloped signature (removes
- Digest: SHA-1 (
http://www.w3.org/2000/09/xmldsig#sha1) - Signature algorithm: RSA-SHA1 (
http://www.w3.org/2000/09/xmldsig#rsa-sha1) - KeyInfo: Contains
<X509Data><X509Certificate>with Base64 cert (no headers)
The <Signature> element is inserted inside <NFe>, after <infNFe>:
<NFe xmlns="http://www.portalfiscal.inf.br/nfe">
<infNFe Id="NFe35..." versao="4.00">
...
</infNFe>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="..." />
<SignatureMethod Algorithm="..." />
<Reference URI="#NFe35...">
<Transforms>...</Transforms>
<DigestMethod Algorithm="..." />
<DigestValue>...</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>...</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>MIIGxTCCBa...</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>
</NFe>Uses xml-crypto for the actual signing operation. The library handles canonicalization, hashing, and signature computation.
- PFX passwords are stored encrypted in the database (
certificate_passwordfield) - Temp files are always cleaned up in
finallyblocks - PEM private keys are never persisted to disk beyond the signing operation
- Certificate expiration is tracked in
certificate_valid_untilfor UI warnings