Skip to content

fix(ocsp): answer OCSP for old-key leaves with the previous responder after a re-key - #393

Merged
Bugs5382 merged 2 commits into
mainfrom
fix/387-previous-ocsp-responder
Nov 2, 2026
Merged

Bugs5382 merged 2 commits into
mainfrom
fix/387-previous-ocsp-responder

Conversation

@Bugs5382

@Bugs5382 Bugs5382 commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

What and why

After an Intermediate re-key the node throws away the old CA key, and since #386 it re-mints the responder under the new key. A client checking a leaf issued before the re-key validates against the old Intermediate, the one that leaf chains to, so it rejected the response on the signature. This keeps those leaves working until the old responder expires.

  • CommitRotation moves the stored responder, its key and the old CA certificate to a previous-responder slot in the same transaction as the swap.
  • /ocsp checks the slot first. A request whose CertID names the old CA (name hash and key hash, RFC 6960 section 4.1.1) is answered with the kept responder. This path never loads the CA key. Any other request takes the current path unchanged.
  • The slot lasts until the responder expires, at most 7 days. An expired responder never answers, and the 30-second ticker drops the slot.
  • A second re-key inside the window replaces the slot with the responder it retires.

Two calls I made beyond the issue text:

  • Before keeping a responder, the commit checks it's signed by the CA being retired. Without that, two re-keys with no re-mint in between (the ticker runs every 30 seconds, so this window is small) would pair the first key's responder with the second CA certificate. In that case the slot keeps the pair it has, which still matches, and leaves of the middle key get no working OCSP until they're re-issued. The log says so.
  • Responses now use the request's hash algorithm. They always used SHA-1, so a SHA-256 request got a CertID it couldn't match, on the current key too. This fixes a real RFC 6960 section 4.2.1 miss and the old-key path needs it. It does change responses for SHA-256 clients on every node.

Not in this change: the CRL for old leaves is signed by the new key too, as the issue says.

Closes #387.

No docs change: no docs page describes OCSP behaviour after a re-key.

Merge order: I moved this to main so it ships in v0.1.0-rc.2 and gets full CI now. It doesn't depend on any other open PR.

Verification

  • Lint clean
  • Tests pass
  • Build succeeds

How this was verified

GitHub Actions doesn't run on this PR: every workflow is scoped to branches: [main]. All of this ran locally, under go1.26.8, and nothing has run in CI.

  • internal/init/ocsp_previous_test.go drives the real rekeyer and enroller. ocsp.ParseResponseForCert against the old Intermediate failed before the fix with bad OCSP signature and passes now.
  • Also covered: a revoked old leaf, current-key requests, the SHA-256 echo on both paths, a second re-key, the stale-responder case, and expiry. TestRequestNamesIssuer covers SHA-1, 256, 384 and 512 with same name other key, and other name same key.
  • Checks that CI would have run: gofmt, buf lint, golangci-lint v2.12.2 (0 issues), go vet, CGO_ENABLED=0 go build ./..., go test -count=1 ./... (all packages), go mod tidy clean, golic, gitleaks, and DCO sign-off.
  • Not run: ci-kind-acme, the appliance image suite, and any TPM-backed or lab re-key. The responder key is a software key in both the slot and the current path, but this hasn't been through a real re-key on an image.

Before merging: add a closing comment summarizing what was actually done in this PR
(not just the checked boxes).

… after a re-key

A re-key discards the old CA key, so leaves it issued could no longer get an
OCSP response that verifies against the certificate they chain to. The commit
now moves the responder, its key and the old CA certificate to a separate slot
in the same transaction, requests naming the old key are answered from it, and
the slot is dropped once the responder expires.

Responses also echo the request's CertID hash algorithm (RFC 6960 section
4.2.1); they always used SHA-1 before, which a SHA-256 request can't match.

Closes #387

Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
@Bugs5382 Bugs5382 added this to the v0.1.1 milestone Nov 2, 2026
@Bugs5382 Bugs5382 added the bug Something isn't working. label Nov 2, 2026
@Bugs5382 Bugs5382 self-assigned this Nov 2, 2026
@Bugs5382
Bugs5382 marked this pull request as ready for review November 2, 2026 04:56
@Bugs5382
Bugs5382 changed the base branch from release/v0.1.1 to main November 2, 2026 11:44
@github-actions github-actions Bot added the fix Bug fix (fix). Patch. label Nov 2, 2026
@Bugs5382
Bugs5382 marked this pull request as draft November 2, 2026 12:15
@Bugs5382
Bugs5382 marked this pull request as ready for review November 2, 2026 12:15
@Bugs5382

Bugs5382 commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

Closing summary: after an Intermediate re-key, the node keeps the previous OCSP responder until it expires, at most 7 days, and answers requests for leaves of the old key with it, so those leaves keep validating. Responses now use the request's hash algorithm instead of always SHA-1, which fixes SHA-256 clients on every node. It ships in v0.1.0-rc.2 instead of waiting for v0.1.1.

@Bugs5382
Bugs5382 merged commit 4d8c69b into main Nov 2, 2026
16 checks passed
@Bugs5382
Bugs5382 deleted the fix/387-previous-ocsp-responder branch November 2, 2026 13:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working. fix Bug fix (fix). Patch.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(ocsp): answer OCSP for old-key leaves with the previous responder after a re-key

1 participant