Skip to content

pki: refuse a dead CA, and stop certificates outliving it - #38

Draft
allandegnan wants to merge 1 commit into
mainfrom
review/pki
Draft

pki: refuse a dead CA, and stop certificates outliving it#38
allandegnan wants to merge 1 commit into
mainfrom
review/pki

Conversation

@allandegnan

Copy link
Copy Markdown
Collaborator

Draft — not ready to merge. The fixes are in and internal/pki passes, but the tests are still scratch. See Remaining at the bottom.

Two findings in internal/pki, both confirmed before changing anything.

A certificate can outlive the CA that signed it

Issue computes notAfter as now + d with no reference to the issuer. A CA with an hour left, asked for the shipped livedata profile's two hours:

CA expires   2026-09-08T20:58:04Z
cert expires 2026-09-08T21:58:04Z
the certificate outlives its issuer by 1h0m0s

and at a moment inside that gap, openssl verify — which is what OpenVPN does on every connection:

CN=Expiring CA
error 10 at 1 depth lookup: certificate has expired
error cert.pem: verification failed

The holder is handed a credential claiming an hour it does not have. Nothing warns them, and the portal displays the longer figure.

An expired CA loads silently and keeps issuing

Load of a CA that expired a minute ago: err=<nil>
and it issued a certificate: err=<nil> serial=63971162674344133002732525538392551921

The portal starts, the page renders, the download succeeds — and every connection fails on a chain that cannot be verified. There is nothing in the logs pointing at the CA.

Change

Load refuses a CA that has expired or is not yet valid, and one without crlSign — Gruff signs the revocation list with the same key, and a CRL from a CA lacking that usage is rejected by the servers that read it.

Issue and IssueServer bound validity to the issuer's own. Shortening is the honest outcome: Credentials.NotAfter carries the real figure, so the portal shows what the holder actually has, and it is what a renewal would produce anyway. Once the CA is genuinely gone, issuance errors rather than minting something dead — a process outlives its startup checks, so this cannot rely on Load alone.

Remaining

  • lifetime_probe_test.go is scratch: TestProbeCertOutlivingItsCA and TestProbeLoadingAnExpiredCA log rather than assert. They need rewriting as real tests.
  • Confirm those fail against main.
  • make lint and the full -race suite — only internal/pki has been run so far.

Noted, not changed

CRL sets Number to a random 63-bit integer. RFC 5280 wants it monotonically increasing. OpenVPN re-reads the file per connection and compares nothing, so it works today, and a refresh test depends on the randomness — flagging it rather than folding an unrelated change into this.

Unfinished - pushed to save the work. The fixes are in and internal/pki
passes; what is left is turning the two probe tests into real ones and
running the full suite.

Two findings, both confirmed before changing anything:

A certificate can outlive the CA that signed it. A CA with an hour left
issuing the shipped livedata profile's two hours produces a credential whose
chain stops verifying an hour before its stated expiry:

  error 10 at 1 depth lookup: certificate has expired

The holder is handed hours they do not have, and nothing warns anyone.

An expired CA loads silently and keeps issuing. Load of a CA that lapsed a
minute earlier returned nil, and it minted a certificate happily. The portal
starts, the page renders, the download works, and every connection fails on a
chain that cannot be verified.

Load now refuses a CA that has expired or is not yet valid, and one without
crlSign, which Gruff needs because it signs the revocation list with the same
key. Issue and IssueServer bound validity to the issuer's own, which is
honest - the portal shows the real expiry - and errors outright once the CA
is gone, since a process outlives its startup checks.

TODO: lifetime_probe_test.go is scratch. Replace TestProbeCertOutlivingItsCA
and TestProbeLoadingAnExpiredCA with assertions, confirm they fail on main,
then run make lint and the full race suite before opening a PR.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant