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.
- 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.
- 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 .
- 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 Nfor expiry warnings - Works with: any OpenSSL CA, not just OpenVPN
- Source: Forgejo