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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@f-o-t/digital-certificate

v2.4.4

Published

Brazilian A1 digital certificate handling with XML/PDF signing and mTLS support.

Readme

@f-o-t/digital-certificate

Brazilian A1 digital certificate handling with XML/PDF signing and mTLS support.

npm version License: MIT

Features

  • Certificate Management: Parse and validate Brazilian A1 digital certificates (.pfx/.p12)
  • Brazilian Standards: Extract CNPJ/CPF from certificate fields
  • Type Safety: Full TypeScript support with Zod schema validation
  • XML Digital Signatures: Sign XML documents with XML-DSig (via plugin)
  • Mutual TLS: Create mTLS contexts for secure HTTPS connections (via plugin)
  • Pure JavaScript: No system dependencies — PKCS#12 parsing via @f-o-t/crypto
  • Browser Compatible: All APIs use Uint8Array; works in browsers, Edge Runtime, and Cloudflare Workers
  • Validity Checking: Built-in certificate expiry validation
  • Framework Agnostic: Works with any JavaScript/TypeScript project

Installation

# npm
npm install @f-o-t/digital-certificate

# bun
bun add @f-o-t/digital-certificate

# yarn
yarn add @f-o-t/digital-certificate

# pnpm
pnpm add @f-o-t/digital-certificate

Requirements:

  • For XML signing: @f-o-t/xml (automatically included)

Quick Start

import { parseCertificate, isCertificateValid, daysUntilExpiry } from "@f-o-t/digital-certificate";
import { readFileSync } from "fs";

// Load certificate file (Node/Bun)
const pfxBuffer = new Uint8Array(readFileSync("certificate.pfx"));
const password = "your-certificate-password";

// In a browser, read from a File input:
// const pfxBuffer = new Uint8Array(await file.arrayBuffer());

// Parse certificate
const cert = await parseCertificate(pfxBuffer, password);

// Check validity
console.log("Valid:", isCertificateValid(cert));
console.log("Days until expiry:", daysUntilExpiry(cert));

// Access certificate information
console.log("Common Name:", cert.subject.commonName);
console.log("Organization:", cert.subject.organization);
console.log("CNPJ:", cert.brazilian.cnpj);
console.log("CPF:", cert.brazilian.cpf);
console.log("Valid from:", cert.validity.notBefore);
console.log("Valid until:", cert.validity.notAfter);
console.log("Fingerprint:", cert.fingerprint);

Core API

Certificate Parsing

Parse .pfx/.p12 certificate files:

import { parseCertificate } from "@f-o-t/digital-certificate";

const cert = await parseCertificate(pfxBuffer, password);

// Certificate structure
console.log(cert.serialNumber);        // Certificate serial number
console.log(cert.subject);             // Subject information
console.log(cert.issuer);              // Issuer information
console.log(cert.validity);            // Validity period
console.log(cert.fingerprint);         // SHA-256 fingerprint
console.log(cert.isValid);             // Current validity status
console.log(cert.brazilian);           // Brazilian-specific fields
console.log(cert.certPem);             // Certificate in PEM format
console.log(cert.keyPem);              // Private key in PEM format
console.log(cert.pfxBuffer);           // Original PFX buffer
console.log(cert.pfxPassword);         // PFX password

Subject Information

Access subject (certificate holder) details:

const { subject } = cert;

console.log(subject.commonName);           // CN - Common Name
console.log(subject.organization);         // O - Organization
console.log(subject.organizationalUnit);   // OU - Organizational Unit
console.log(subject.country);              // C - Country
console.log(subject.state);                // ST - State
console.log(subject.locality);             // L - Locality
console.log(subject.raw);                  // Raw DN string

Issuer Information

Access issuer (CA) details:

const { issuer } = cert;

console.log(issuer.commonName);      // CN - CA name
console.log(issuer.organization);    // O - CA organization
console.log(issuer.country);         // C - CA country
console.log(issuer.raw);             // Raw DN string

Brazilian-Specific Fields

Extract CNPJ/CPF from certificate:

const { brazilian } = cert;

// Company certificate
if (brazilian.cnpj) {
  console.log("CNPJ:", brazilian.cnpj);  // e.g., "12345678000190"
}

// Individual certificate
if (brazilian.cpf) {
  console.log("CPF:", brazilian.cpf);  // e.g., "12345678900"
}

Validity Checking

Check certificate validity:

import { isCertificateValid, daysUntilExpiry } from "@f-o-t/digital-certificate";

// Check if currently valid
const isValid = isCertificateValid(cert);

// Get days until expiry (negative if expired)
const days = daysUntilExpiry(cert);

if (days < 0) {
  console.log(`Certificate expired ${Math.abs(days)} days ago`);
} else if (days < 30) {
  console.log(`Certificate expires in ${days} days - renewal recommended`);
} else {
  console.log(`Certificate valid for ${days} more days`);
}

// Access validity dates directly
console.log("Valid from:", cert.validity.notBefore);
console.log("Valid until:", cert.validity.notAfter);

PEM Extraction

Get PEM-formatted certificate and key:

import { getPemPair } from "@f-o-t/digital-certificate";

// Extract PEM pair
const { cert: certPem, key: keyPem } = getPemPair(cert);

// Use with custom HTTP clients
import https from "https";

const agent = new https.Agent({
  cert: certPem,
  key: keyPem
});

// Make authenticated request
https.get("https://api.example.com", { agent }, (res) => {
  // Handle response
});

Type System

Full TypeScript support:

import type {
  CertificateInfo,
  CertificateSubject,
  CertificateIssuer,
  CertificateValidity,
  BrazilianFields,
  PemPair,
  SignatureAlgorithm
} from "@f-o-t/digital-certificate";

// Certificate information
const certInfo: CertificateInfo = {
  serialNumber: "1234567890",
  subject: {
    commonName: "Company Name",
    organization: "Company Inc",
    organizationalUnit: "IT Department",
    country: "BR",
    state: "SP",
    locality: "São Paulo",
    raw: "CN=Company Name,O=Company Inc,..."
  },
  issuer: {
    commonName: "CA Name",
    organization: "Certificate Authority",
    country: "BR",
    raw: "CN=CA Name,O=Certificate Authority,..."
  },
  validity: {
    notBefore: new Date("2024-01-01"),
    notAfter: new Date("2025-01-01")
  },
  fingerprint: "abcdef...",
  isValid: true,
  brazilian: {
    cnpj: "12345678000190",
    cpf: null
  },
   certPem: "-----BEGIN CERTIFICATE-----...",
   keyPem: "-----BEGIN PRIVATE KEY-----...",
   pfxBuffer: new Uint8Array([]),
   pfxPassword: "password"
};

Zod Schemas

Validate certificate-related data:

import {
  signatureAlgorithmSchema,
  signOptionsSchema,
  mtlsOptionsSchema
} from "@f-o-t/digital-certificate";

// Validate signature algorithm
const algorithm = signatureAlgorithmSchema.parse("RSA-SHA256");

// Available algorithms: "RSA-SHA1", "RSA-SHA256"

Utilities

Low-level utilities for working with certificates:

import {
  extractCnpj,
  extractCpf,
  parseDistinguishedName,
  pemToBase64,
  base64ToBytes,
  bytesToBase64,
  BRAZILIAN_OIDS,
  DIGEST_ALGORITHMS,
  SIGNATURE_ALGORITHMS,
  TRANSFORM_ALGORITHMS,
  XMLDSIG_NS,
  EXC_C14N_NS
} from "@f-o-t/digital-certificate";

// Extract CNPJ from text
const cnpj = extractCnpj("serialNumber=12345678000190");
// "12345678000190"

// Extract CPF from text
const cpf = extractCpf("CN=John Doe:12345678900");
// "12345678900"

// Parse distinguished name
const dn = parseDistinguishedName("CN=Company,O=Inc,C=BR");
// { CN: "Company", O: "Inc", C: "BR" }

// Convert PEM to base64
const base64 = pemToBase64(certPem);

// Cross-platform base64 helpers (work in Node, Bun, browsers, Edge Runtime)
const bytes = base64ToBytes("SGVsbG8gV29ybGQ=");    // Uint8Array
const b64   = bytesToBase64(new Uint8Array([1, 2])); // "AQI="

// Brazilian OIDs
console.log(BRAZILIAN_OIDS.CNPJ);  // "2.16.76.1.3.3"
console.log(BRAZILIAN_OIDS.CPF);   // "2.16.76.1.3.1"

// Algorithm constants
console.log(DIGEST_ALGORITHMS["RSA-SHA256"]);     // "http://www.w3.org/2001/04/xmlenc#sha256"
console.log(SIGNATURE_ALGORITHMS["RSA-SHA256"]); // "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"

Plugins

XML Digital Signatures

Sign XML documents with XML-DSig:

import { parseCertificate } from "@f-o-t/digital-certificate";
import { signXml } from "@f-o-t/digital-certificate/plugins/xml-signer";
import { parseXml } from "@f-o-t/xml";

// Parse certificate
const cert = await parseCertificate(pfxBuffer, password);

// Parse XML document
const doc = parseXml(`
  <NFe xmlns="http://www.portalfiscal.inf.br/nfe">
    <infNFe Id="NFe12345678901234567890123456789012345678901234">
      <ide>
        <cUF>35</cUF>
        <nNF>123</nNF>
      </ide>
    </infNFe>
  </NFe>
`);

// Sign XML
const signedXml = signXml(doc, cert, {
  algorithm: "RSA-SHA256",
  referenceUri: "#NFe12345678901234567890123456789012345678901234",
  transforms: ["enveloped-signature", "c14n"]
});

console.log(signedXml);  // XML with <Signature> element

Features:

  • Enveloped signatures (inside the signed document)
  • RSA-SHA1 and RSA-SHA256 algorithms
  • Exclusive C14N canonicalization
  • Reference URI support for partial document signing
  • Compatible with Brazilian fiscal XML standards (NF-e, NFS-e, etc.)

Sign options:

import type { SignOptions } from "@f-o-t/digital-certificate";

const options: SignOptions = {
  algorithm: "RSA-SHA256",           // or "RSA-SHA1"
  referenceUri: "#elementId",        // URI to signed element
  transforms: [                      // Optional transforms
    "enveloped-signature",
    "c14n"
  ]
};

Mutual TLS (mTLS)

Create mTLS contexts for HTTPS connections:

import { parseCertificate } from "@f-o-t/digital-certificate";
import { createMtlsContext, createMtlsAgent } from "@f-o-t/digital-certificate/plugins/mtls";
import https from "https";

// Parse certificate
const cert = await parseCertificate(pfxBuffer, password);

// Create mTLS context
const context = createMtlsContext(cert);
// Returns: { cert: string, key: string, passphrase?: string, pfx?: Buffer }

// Create HTTPS agent
const agent = createMtlsAgent(cert, {
  rejectUnauthorized: true,  // Verify server certificate
  ca: [caCertPem]           // Optional CA certificates
});

// Use with https module
https.get("https://api.sefaz.sp.gov.br/ws/nfe", { agent }, (res) => {
  let data = "";
  res.on("data", (chunk) => data += chunk);
  res.on("end", () => console.log(data));
});

// Use with fetch (Node.js 18+)
const response = await fetch("https://api.example.com", {
  // @ts-ignore - dispatcher not yet typed
  dispatcher: agent
});

Create custom agent:

import https from "https";

const agent = new https.Agent({
  cert: cert.certPem,
  key: cert.keyPem,
  rejectUnauthorized: true,
  // Additional options
  keepAlive: true,
  maxSockets: 10
});

PDF Signing

Removed in v2.0.0. PDF signing has moved to @f-o-t/e-signature.

Advanced Usage

Certificate Chain Validation

import { parseCertificate, isCertificateValid } from "@f-o-t/digital-certificate";

// Parse certificate
const cert = await parseCertificate(pfxBuffer, password);

// Basic validation
if (!isCertificateValid(cert)) {
  throw new Error("Certificate is expired or not yet valid");
}

// Check expiry threshold
const days = daysUntilExpiry(cert);
if (days < 30) {
  console.warn(`Certificate expires soon: ${days} days remaining`);
}

// Verify certificate fields
if (!cert.brazilian.cnpj && !cert.brazilian.cpf) {
  throw new Error("Not a Brazilian certificate");
}

Working with Multiple Certificates

import { parseCertificate, isCertificateValid } from "@f-o-t/digital-certificate";

// Load and validate multiple certificates
const certificates = [
  { file: "cert1.pfx", password: "pass1" },
  { file: "cert2.pfx", password: "pass2" }
].map(({ file, password }) => {
  const buffer = readFileSync(file);
  return await parseCertificate(buffer, password);
});

// Find valid certificates
const validCerts = certificates.filter(isCertificateValid);

// Find certificate by CNPJ
function findByCnpj(cnpj: string) {
  return validCerts.find(cert => cert.brazilian.cnpj === cnpj);
}

// Find certificate expiring soonest
const expiringFirst = [...validCerts].sort((a, b) =>
  daysUntilExpiry(a) - daysUntilExpiry(b)
)[0];

Batch XML Signing

import { signXml } from "@f-o-t/digital-certificate/plugins/xml-signer";

// Sign multiple XML documents
const xmlDocuments = [doc1, doc2, doc3];

const signedDocuments = xmlDocuments.map(doc =>
  signXml(doc, cert, {
    algorithm: "RSA-SHA256",
    referenceUri: `#${doc.root?.attributes.find(a => a.name === "Id")?.value}`
  })
);

Custom HTTPS Client with mTLS

import { createMtlsAgent } from "@f-o-t/digital-certificate/plugins/mtls";
import axios from "axios";

// Create axios instance with mTLS
const client = axios.create({
  httpsAgent: createMtlsAgent(cert, {
    rejectUnauthorized: true
  }),
  timeout: 30000
});

// Make authenticated requests
const response = await client.post("https://api.sefaz.sp.gov.br/ws/nfe", xmlData, {
  headers: { "Content-Type": "application/xml" }
});

Best Practices

1. Secure Certificate Storage

// Don't commit certificates or passwords to version control
// Use environment variables or secure vaults

const pfxPath = process.env.CERTIFICATE_PATH;
const password = process.env.CERTIFICATE_PASSWORD;

if (!pfxPath || !password) {
  throw new Error("Certificate configuration missing");
}

const cert = await parseCertificate(
  readFileSync(pfxPath),
  password
);

2. Cache Parsed Certificates

// Parse once, reuse many times
let cachedCert: CertificateInfo | null = null;

async function getCertificate(): Promise<CertificateInfo> {
  if (!cachedCert) {
    cachedCert = await parseCertificate(pfxBuffer, password);
  }

  // Verify still valid
  if (!isCertificateValid(cachedCert)) {
    throw new Error("Certificate expired");
  }

  return cachedCert;
}

3. Monitor Certificate Expiry

import { daysUntilExpiry } from "@f-o-t/digital-certificate";

// Check expiry regularly
function checkCertificateExpiry(cert: CertificateInfo) {
  const days = daysUntilExpiry(cert);

  if (days < 0) {
    throw new Error("Certificate expired");
  }

  if (days < 30) {
    console.warn(`Certificate expires in ${days} days - renewal required`);
    // Send alert, email, etc.
  }
}

// Run daily
setInterval(() => checkCertificateExpiry(cert), 24 * 60 * 60 * 1000);

4. Handle Parsing Errors Gracefully

try {
  const cert = await parseCertificate(pfxBuffer, password);
} catch (error) {
  if (error instanceof Error) {
    if (error.message.includes("password")) {
      console.error("Invalid certificate password");
    } else if (error.message.includes("PFX")) {
      console.error("Invalid PFX file");
    } else {
      console.error("Certificate parsing failed:", error.message);
    }
  }
  throw error;
}

5. Validate Before Signing

import { signXml } from "@f-o-t/digital-certificate/plugins/xml-signer";
import { isCertificateValid } from "@f-o-t/digital-certificate";

function safeSignXml(doc: XmlDocument, cert: CertificateInfo) {
  // Validate certificate first
  if (!isCertificateValid(cert)) {
    throw new Error("Cannot sign with expired certificate");
  }

  // Check expiry threshold
  if (daysUntilExpiry(cert) < 7) {
    console.warn("Certificate expires soon - consider renewing");
  }

  // Proceed with signing
  return signXml(doc, cert, {
    algorithm: "RSA-SHA256"
  });
}

Error Handling

try {
  const cert = await parseCertificate(pfxBuffer, password);

  if (!isCertificateValid(cert)) {
    throw new Error(`Certificate expired on ${cert.validity.notAfter}`);
  }

  // Use certificate...
} catch (error) {
  if (error instanceof Error) {
    console.error("Certificate error:", error.message);

    // Handle specific error cases
    if (error.message.includes("wrong password")) {
      // Handle authentication error
    } else if (error.message.includes("OpenSSL")) {
      // Handle OpenSSL errors
    }
  }
}

Performance

Measured on modern hardware with Bun runtime (pure-TS implementation, no OpenSSL):

| Operation | Typical time | |---|---| | Parse certificate (parseCertificate) | ~30–40ms | | Get PEM pair (getPemPair) | < 0.1ms | | Check validity (isCertificateValid) | < 0.1ms | | Days until expiry (daysUntilExpiry) | < 0.1ms |

Note: parseCertificate includes PKCS#12 decryption (PBES2/PBKDF2-SHA256), which runs in pure TypeScript. With @f-o-t/crypto ≥ 1.2.0 this is ~3× faster than earlier versions (PBKDF2-SHA256 improved from ~100ms to ~13ms). A WebCrypto/native fallback for PBKDF2 is planned to improve this further.

Best practice: Parse once and cache the result; subsequent operations are sub-millisecond.

let cachedCert: CertificateInfo | null = null;

async function getCertificate(): Promise<CertificateInfo> {
  if (!cachedCert) {
    cachedCert = await parseCertificate(pfxBuffer, password); // ~30–40ms, once
  }
  if (!isCertificateValid(cachedCert)) throw new Error("Certificate expired");
  return cachedCert;
}

Contributing

Contributions are welcome! Please check the repository for guidelines.

License

MIT License - see LICENSE file for details.

Links