OpenVPN / PKI auditing

Auditing OpenVPN certificates with openvpn-cert-check

An OpenVPN PKI is just an OpenSSL CA, and its index.txt knows every certificate ever issued. This script compares the valid ones against a list you expect, flags unexpected certificates, and warns about upcoming expiries from CI.

An under-observed weakness in access audits

OpenVPN's PKI is an OpenSSL certificate authority. Every certificate ever issued - valid, revoked, or expired - is recorded in a tab-separated file called index.txt.

In my experience, two things are often forgotten when it comes to OpenVPN cert issuance.

  1. Are the certificates currently valid, exactly the ones I expect? Someone issues a cert. The employee leaves. The cert is still valid as revoking it was overlooked.
  2. Is anything about to expire? Certificates silently expire. OpenVPN clients stop connecting, with cryptic TLS errors. You, the sysadmin, are the last to know, instead of the first!

What this script does

openvpn-cert-check reads an OpenSSL index.txt and a file of expected valid certificate names (CNs), one per line. It reports:

  • Valid certificates that are not in the expected list (unexpected)
  • Expected certificates that are not currently valid (missing?)
  • Valid certificates whose CN cannot be parsed (unparseable)
  • Multiple valid certificates sharing the same CN (duplicates)
  • Certificates expiring within a warning window (--warn-days)
  • Revoked and expired certificates (informational)

A certificate whose status field still says V but whose expiry time has passed, is treated as expired.

The --warn-days N flag flags valid certificates expiring within N days. It adds an "Expiring soon" section to the human output and an expiring array to the JSON output, each entry with the certificate name, expiry timestamp, and days remaining.

Exit code 3 means "valid certificates found expiring within the warning window." It is distinct from exit code 1 (unexpected certificates), so CI can treat them differently if necessary. When both conditions apply, exit 1 takes precedence (an unexpected certificate is a failure, regardless of its expiry).

Example: normal run

Against a CA with four valid, three revoked, and one expired certificate:

OpenVPN certificate audit
=========================
CA database : index.txt
Checked     : 2026-09-26 00:21:41 UTC
Expected    : 4
Valid       : 4
Revoked     : 3
Expired     : 1

Valid certificates
------------------
  alice  OK            expires 2031-08-27 23:40:24 UTC
  bob    OK            expires 2033-02-25 08:52:27 UTC
  cora   OK            expires 2030-12-15 07:12:12 UTC
  dan    OK            expires 2029-12-14 00:58:19 UTC

Revoked certificates
--------------------
  edward    expires 2032-05-09 15:53:12 UTC  serial 20
  florence  expires 2032-02-15 12:19:26 UTC  serial 1C
  gordon    expires 2032-04-09 01:04:29 UTC  serial 1F

Expired certificates
--------------------
  harriet  expired 2026-05-07 23:19:23 UTC  serial 0F

OK: all valid certificates are expected.

Example: unexpected certificate

When a fifth valid certificate (eve) is present but not in the expected list:

Valid certificates
------------------
  alice  OK            expires 2031-08-27 23:40:24 UTC
  bob    OK            expires 2033-02-25 08:52:27 UTC
  cora   OK            expires 2030-12-15 07:12:12 UTC
  dan    OK            expires 2029-12-14 00:58:19 UTC
  eve    UNEXPECTED    expires 2033-01-01 00:00:00 UTC

FAILED: 1 unexpected valid certificate found.

Exit code 1.

Example: expiry warning in CI

With --warn-days 1200, dan (expiring in 1175 days) is flagged:

Expiring soon (within 1200 days)
--------------------------------
  dan   expires 2029-12-14 00:58:19 UTC  in 1175 days

WARNING: 1 valid certificate expiring within 1200 days.

Exit code 3.

The JSON output includes the same information with a days field for dashboard tooling:

{
  "expected_count": 4,
  "valid_count": 4,
  "unexpected": [],
  "expiring": [
    {
      "name": "dan",
      "expires": "2029-12-14T00:58:19+00:00",
      "days": 1175
    }
  ],
  "expiring_count": 1,
  "warn_days": 1200,
  "metric_value": 0
}

Exit codes

Code Meaning
0 No unexpected valid certificates, no expiring warnings
1 Unexpected or unparseable valid certificates found
2 Configuration or input error
3 Valid certificate(s) expiring within --warn-days (warning only)

Works with any OpenSSL CA

Despite the name, this tool reads a standard OpenSSL index.txt. It works with any CA that uses openssl ca, be it OpenVPN, a custom PKI, or anything that produces the six-column tab-separated format. The index.txt format is documented in OpenSSL's ca(1) manual page.

Running it

openvpn-cert-check -i index.txt -e expected.txt [--warn-days N] [--json]

The expected file is one certificate CN per line, with # comments allowed.

The source is on My Forgejo .

At a glance
  • Input: OpenSSL index.txt + expected names file
  • Output: human-readable or JSON (--json)
  • Exit codes: 0 (OK), 1 (unexpected), 2 (error), 3 (expiring soon)
  • CI feature: --warn-days N for expiry warnings
  • Works with: any OpenSSL CA, not just OpenVPN
  • Source: Forgejo
Need help with PKI or OpenVPN?
I help organisations make Linux and secure network infrastructure easier to operate.
Did you appreciate this article? Any support is appreciated!