Skip to content
ecdsa.com

Certificate expiry · WebCrypto

How to check SSL certificate expiration date in WebCrypto

To check SSL certificate expiration date in the browser, parse the certificate with @peculiar/x509 — a parser built on WebCrypto — and read notAfter. Browsers give page scripts no access to the certificates of TLS connections, so this works on certificates the user picks or pastes, entirely client-side; the recipe checks a valid and an expired one, as PEM text and as DER bytes.

Tested against the WebCrypto API in @peculiar/x509 2.2.0 (WebCrypto in Node.js 24.22.0 (globalThis.crypto), reflect-metadata 0.2.2) — the same crypto.subtle interface browsers implement.

expiry.mjs — notAfter and days left for a pasted or picked certificate
import "reflect-metadata"; // @peculiar/x509 2.x needs a Reflect polyfill, imported first
import * as x509 from "@peculiar/x509"; // built on WebCrypto, runs in the browser unchanged
// Expiry of a certificate the user picked or pasted (PEM text, or DER bytes)
export function expiry(input) {
const cert = new x509.X509Certificate(input);
const daysLeft = Math.floor((cert.notAfter.getTime() - Date.now()) / 86_400_000);
return { subject: cert.subject, notAfter: cert.notAfter.toISOString(), daysLeft, expired: daysLeft < 0 };
}
// In a page: <input type="file" accept=".pem,.crt,.cer"> and
// input.onchange = async () => console.log(expiry(await input.files[0].text()));

How it works

  1. new x509.X509Certificate(input) accepts PEM text, base64 or DER bytes (ArrayBuffer, Uint8Array); notAfter is a Date.
  2. reflect-metadata must be imported before @peculiar/x509 2.x, which relies on the Reflect metadata API.
  3. The certificate never leaves the page: reading a file with file.text() and parsing it are both local operations.
  4. The run prints 2035-12-31T23:59:59.000Z for the test leaf and expired: true for the certificate that ended on 31 March 2025.

Gotchas

  • Math.floor makes the day count negative after expiry (-568 in the run), so daysLeft < 30 also flags certificates that have already expired.
  • subject is a string in RFC 4514 style with the attributes in reverse order (C=US, O=Example Corp, CN=api.example.com); use cert.subjectName for structured access rather than parsing the string.
  • The same function works on DER bytes: the run passes the decoded base64 of the PEM and gets the same notAfter.

Related recipes