Skip to content

fix(tls): verify rollback restoration - #42

Draft
bymoye wants to merge 2 commits into
agent/tls-receipt-commit-recoveryfrom
agent/tls-rollback-revalidation
Draft

fix(tls): verify rollback restoration#42
bymoye wants to merge 2 commits into
agent/tls-receipt-commit-recoveryfrom
agent/tls-rollback-revalidation

Conversation

@bymoye

@bymoye bymoye commented Aug 14, 2026

Copy link
Copy Markdown
Member

Scope

Stacked on #41 and limited to the rollback proof boundary for issue #31. It does not add a NazoAuth API, generate NazoAuth protocol keys, or modify the NazoAuth repository.

Related: #31
Cross-repository dependency: nazozero/NazoAuth#130
Stack: #41 -> #40 -> #39 -> #37 -> #34

Root cause

Rollback trusted the recorded previous leaf digest and the provider reload process exit code. It did not securely reopen the previous generation before activation, so damaged or replaced certificate/private-key files could be switched back without proving chain, SAN, serverAuth usage, expiry, key match, permissions or receipt digests.

For a failed first installation, rollback removed the activation link and accepted a successful reload without public evidence that the candidate was no longer served. It could therefore finalize with no receipt while a consumer retained the candidate in memory.

The pending transaction also carried an embedded provider configuration without a separate stable digest binding that snapshot to its commands and authority fields.

Changes

  • require an existing receipt to match the current provider configuration, trust anchors, activation link and public URL during plan/apply/check;
  • add a versioned canonical provider-snapshot digest to transaction schema 4 and reject command/path/URL/policy drift during recovery;
  • encode provider paths as typed components so the digest is stable across Windows and Unix without conflating a Unix backslash-containing component with a path separator;
  • before changing the rollback pointer, securely reopen the previous fullchain and private key and repeat complete offline validation plus receipt/source/material/provider binding;
  • after reload, require the activation pointer to equal the recorded previous state and publicly verify the exact previous leaf and accepted health response;
  • for a first-install rollback, require every bounded public address to serve a trust-valid accepted endpoint whose leaf is not the candidate;
  • retain the pending journal and inactive candidate whenever absence or restoration cannot be proved;
  • keep network observation in the public-endpoint verifier, PKI validation in the material module, and transaction ownership in the TLS module.

The first-install behavior is intentionally fail-closed: an unavailable endpoint is not treated as proof that the candidate disappeared.

Validation

Local Windows, exact head f2d500ac9eb6888a3f1a46abae8c26c3b5a15d3d:

  • cargo fmt --all -- --check
  • provider snapshot stability/drift test: 1 passed
  • TLS unit filter: 14 passed
  • cargo check --locked --workspace --all-targets
  • cargo clippy --locked --workspace --all-targets -- -D warnings
  • cargo test --locked --workspace: 369 passed
  • cargo build --locked --release --workspace: 0 errors; one aggregated informational MSVC import-library linker message

The first remote run exposed that serializing PathBuf directly produced different provider snapshot digests on Windows and Unix. Commit f2d500a replaced it with typed path-component encoding and retained a fixed digest vector plus a Unix non-collision test.

Remote GitHub checks for the exact head: 13/13 passed:

  • controller CI on Ubuntu 24.04, macOS 15 and Windows 2025;
  • current-controller build;
  • current and previous ctl against signed server releases v0.1.19, v0.1.20, v0.1.24 and v0.1.34;
  • real Docker/Podman/systemd discovery with signed artifacts.

Verification boundary

No real certificate rollback, provider reload, public hostname, remote deployment, power-loss injection, stopped-ctl runtime, or NazoAuth Direct TLS integration was exercised locally. The Unix symlink rollback path is covered by Linux CI, but not by a real service switch. ACME authority restrictions remain a separate follow-up 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