Skip to content

feat: report remote certificate validation - #531

Open
robjtede wants to merge 1 commit into
validate-local-chainsfrom
t3code/validate-certificate-chains
Open

robjtede wants to merge 1 commit into
validate-local-chainsfrom
t3code/validate-certificate-chains

Conversation

@robjtede

@robjtede robjtede commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

Remote inspection previously bypassed certificate validation. Apply the local-chain validator during the TLS handshake and show trust, hostname, date, and handshake-signature results in text, JSON, and interactive output.

Use the selected --server-name and allow custom CA files. Keep invalid certificates available for inspection and --dump. Preserve the fetch deadline and date-based --check exit codes. Revocation remains not checked in this layer.

This is layer 2 of 4, based on local validation. Includes TLS 1.2/1.3 coverage, invalid handshake signatures, JSON and timeout compatibility, and six live BadSSL checks.

Validation on macOS: just test (77 passed), nix develop -c just check (formatting and Clippy passed), and just test-badssl (6 passed).

@coderabbitai

coderabbitai Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 58 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: d4ad5631-6837-4061-9623-c6aad18790cf
📥 Commits

Reviewing files that changed from the base of the PR and between c8b4045 and 64597d9.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • CHANGELOG.md
  • Cargo.toml
  • README.md
  • justfile
  • src/fetch.rs
  • src/main.rs
  • src/tui.rs
  • src/validation.rs
  • tests/badssl.rs
  • tests/remote_timeout.rs
  • tests/validation.rs

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e5a2a928-c42f-4272-84e6-734506a6ea8f
📥 Commits

Reviewing files that changed from the base of the PR and between 784a9a4 and c8b4045.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (1)
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • CHANGELOG.md

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The CLI now validates remote and local certificate chains for trust, hostname, dates, handshake signatures, and leaf revocation. It reports validation results in text, JSON, and the TUI while keeping invalid remote chains available for inspection.

Changes

Certificate Chain Validation

Layer / File(s) Summary
Trust and validation reports
Cargo.toml, src/validation.rs, tests/validation.rs
Native and custom CA roots support path and hostname checks. Reports include per-certificate dates, validation status, and leaf-revocation results. Tests cover trust, hostname, dates, CA constraints, and report output.
Remote inspection and CRL retrieval
src/revocation.rs, src/fetch.rs, tests/validation.rs
Remote TLS inspection records path and handshake-signature results. It checks supplied CRLs or downloads CRLs within configured time and response limits. Tests cover CRL decoding, retrieval, and remote TLS results.
Local validation and report output
src/main.rs, src/report.rs, src/tui.rs, tests/remote_timeout.rs, tests/badssl.rs, CHANGELOG.md, README.md, justfile
CLI options enable local validation with a hostname and optional CA or CRL files. Validation reports flow to text, JSON, and TUI output. Tests and documentation cover local and remote results, including BadSSL cases.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~60 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant cert_chain
  participant TLSServer
  participant ReportingServerCertVerifier
  participant Verifier
  participant revocationDownload
  CLI->>cert_chain: Pass host, CA files, and CRL files
  cert_chain->>TLSServer: Establish TLS connection
  TLSServer->>ReportingServerCertVerifier: Provide certificate chain and handshake signature
  ReportingServerCertVerifier->>Verifier: Check certificate path and handshake signature
  cert_chain->>revocationDownload: Request CRLs when no CRL files were supplied
  revocationDownload-->>cert_chain: Return downloaded CRLs or an error
  cert_chain-->>CLI: Return certificates and validation report
Loading

Merge Risk: ⚪ Minimal · up to c8b40

This increment only adds changelog entries describing the validation feature, so it has no runtime impact and is ready to merge.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to 784a9

Remote inspection now automatically retrieves revocation data from URLs in certificates whose chains are trusted. Those requests can reach services accessible from the inspecting machine without a destination restriction. Trust checks, request limits and offline local inspection narrow the exposure. Validation results remain advisory rather than enforcing certificate acceptance.

Retained concerns

  • Medium · security · observed: Default remote inspection converts certificate-provided CRL URLs into network requests without restricting destination hosts or addresses. The path-validity gate excludes untrusted chains but does not require a valid hostname or handshake signature. A trusted-path certificate with a suitable distribution point can therefore direct requests to loopback, private or other privileged network services reachable from the inspecting process. Subsequent CRL verification cannot prevent those requests.
Security review details

Security Blast Radius

  • inferred — The affected scope is the inspecting process’s reachable network, including destinations different from the operator-selected TLS host. Effective exposure depends on the machine’s network controls and configured trust roots; privileged deployment or tenant-wide exposure has not been established.

Security Findings and Attack Paths

  • inferred — An attacker able to present a trusted-path certificate with a suitable HTTP(S) distribution point can cause the inspector to issue GET requests to that destination. Hostname mismatch and failed handshake signatures do not disable retrieval. This supports a certificate-directed network-access concern, not a verified credential theft or data-exfiltration finding; redirect and implicit credential behavior remain unresolved.

Trust Boundaries and Controls

  • observed — The certificate-path gate prevents arbitrary untrusted chains from initiating automatic CRL retrieval. Explicit CRL files bypass remote downloading, and local checking remains network-free. Signed CRL verification controls the resulting revocation status, but does not authorize or constrain the preceding network request.

Resilience and Maintainability Implications

  • observed — Inspection continuation is separated from validation success: permissive TLS assertions are paired with recorded verification outcomes, and downstream status evaluation consumes those failures. Fresh per-fetch state prevents validation reports from being shared across independent inspections.

Hardening Proposals

  • proposed — Provide an explicit no-network revocation mode and an operator-controlled destination policy. Apply that policy to resolved addresses and every redirect, with deliberate opt-in for private-PKI endpoints. Make proxy and credential behavior explicit rather than relying on HTTP-client defaults.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 49.49% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 99 functions across 10 files. (1 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: reporting validation results for remotely fetched certificate chains.
Full details: Docstring Coverage

Explanation

Docstring coverage is 49.49% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 99 functions across 10 files. (1 skipped: 1 unsupported.)

  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@robjtede
robjtede force-pushed the t3code/validate-certificate-chains branch 2 times, most recently from 96832da to 784a9a4 Compare October 2, 2026 05:41
@robjtede

robjtede commented Oct 2, 2026

Copy link
Copy Markdown
Member Author

Rebased onto 179147d (latest main, v0.0.42). The feature now works with field selection and JSON output. --check keeps its date-based exit codes and expiry thresholds; add --hostname for local trust validation.

These are actual CLI outputs from the rebased build on macOS with system trust roots. The remote examples all exited with 0 and wrote their PEM dumps, including the invalid chains. Certificate field rows are omitted from the text excerpts below. Dump paths are shown relative to the output directory.

$ inspect-cert-chain --host expired.badssl.com --timeout 30s --fields subject --dump expired.badssl.com.pem
Certificate chain: INVALID
Path validation: INVALID (certificate expired)
Hostname (expired.badssl.com): VALID
Certificate 1 dates: INVALID (expired)
Certificate 2 dates: VALID
Certificate 3 dates: INVALID (expired)
TLS handshake signature: VALID
Revocation (leaf): UNKNOWN (certificate path is invalid)
$ inspect-cert-chain --host wrong.host.badssl.com --timeout 30s --fields subject --dump wrong.host.badssl.com.pem
Certificate chain: INVALID
Path validation: VALID
Hostname (wrong.host.badssl.com): INVALID (hostname does not match certificate names)
Certificate 1 dates: VALID
Certificate 2 dates: VALID
Certificate 3 dates: VALID
TLS handshake signature: VALID
Revocation (leaf): VALID
$ inspect-cert-chain --host revoked.badssl.com --timeout 30s --fields subject --dump revoked.badssl.com.pem
Certificate chain: INVALID
Path validation: VALID
Hostname (revoked.badssl.com): VALID
Certificate 1 dates: VALID
Certificate 2 dates: VALID
Certificate 3 dates: VALID
Certificate 4 dates: VALID
TLS handshake signature: VALID
Revocation (leaf): INVALID (certificate revoked)
$ inspect-cert-chain --host incomplete-chain.badssl.com --timeout 30s --fields subject --dump incomplete-chain.badssl.com.pem
Certificate chain: INVALID
Path validation: INVALID (missing issuer certificate or untrusted issuer)
Hostname (incomplete-chain.badssl.com): VALID
Certificate 1 dates: VALID
TLS handshake signature: VALID
Revocation (leaf): UNKNOWN (certificate path is invalid)
$ inspect-cert-chain --host self-signed.badssl.com --timeout 30s --fields subject --dump self-signed.badssl.com.pem
Certificate chain: INVALID
Path validation: INVALID (self-issued certificate is not trusted)
Hostname (self-signed.badssl.com): VALID
Certificate 1 dates: VALID
TLS handshake signature: VALID
Revocation (leaf): UNKNOWN (certificate path is invalid)
$ inspect-cert-chain --host untrusted-root.badssl.com --timeout 30s --fields subject --dump untrusted-root.badssl.com.pem
Certificate chain: INVALID
Path validation: INVALID (root CA is not trusted)
Hostname (untrusted-root.badssl.com): VALID
Certificate 1 dates: VALID
Certificate 2 dates: VALID
TLS handshake signature: VALID
Revocation (leaf): UNKNOWN (certificate path is invalid)
$ inspect-cert-chain --host sha256.badssl.com --timeout 30s --fields subject --dump sha256.badssl.com.pem
Certificate chain: VALID
Path validation: VALID
Hostname (sha256.badssl.com): VALID
Certificate 1 dates: VALID
Certificate 2 dates: VALID
Certificate 3 dates: VALID
TLS handshake signature: VALID
Revocation (leaf): VALID

JSON output keeps field selection and adds a separate validation object. This hostname mismatch also exits with 0:

$ inspect-cert-chain --host wrong.host.badssl.com --timeout 30s --json --fields subject
{
  "certificates": [
    {
      "subject": "CN=*.badssl.com"
    },
    {
      "subject": "CN=YR1,O=Let's Encrypt,C=US"
    },
    {
      "subject": "CN=Root YR,O=ISRG,C=US"
    }
  ],
  "validation": {
    "dates": [
      {
        "status": "valid"
      },
      {
        "status": "valid"
      },
      {
        "status": "valid"
      }
    ],
    "handshake_signature": {
      "status": "valid"
    },
    "hostname": {
      "name": "wrong.host.badssl.com",
      "reason": "hostname does not match certificate names",
      "status": "invalid"
    },
    "path": {
      "status": "valid"
    },
    "revocation": {
      "status": "valid"
    },
    "status": "invalid"
  }
}

Local validation of the saved valid chain exits with 0. Local revocation is NOT CHECKED without a supplied --crl-file:

$ inspect-cert-chain --file sha256.badssl.com.pem --check --hostname sha256.badssl.com --fields subject
OK: certificate 1: within validity period
OK: certificate 2: within validity period
OK: certificate 3: within validity period
OK: 3 certificates
Certificate chain: VALID
Path validation: VALID
Hostname (sha256.badssl.com): VALID
Certificate 1 dates: VALID
Certificate 2 dates: VALID
Certificate 3 dates: VALID
Revocation (leaf): NOT CHECKED

Validation: all 73 tests passed on macOS and Rust 1.93 Linux; all seven live BadSSL checks passed; the repository formatting and Clippy checks passed.

@robjtede
robjtede force-pushed the t3code/validate-certificate-chains branch from 784a9a4 to c8b4045 Compare October 7, 2026 16:38
@robjtede
robjtede force-pushed the t3code/validate-certificate-chains branch from c8b4045 to 64597d9 Compare October 8, 2026 04:54
@robjtede
robjtede changed the base branch from main to validate-local-chains October 8, 2026 04:54
@robjtede
robjtede added this pull request to stack #547 October 8, 2026 04:55
@robjtede robjtede changed the title feat: validate certificate chains and leaf revocation feat: report remote certificate validation Oct 8, 2026
@robjtede

robjtede commented Oct 8, 2026

Copy link
Copy Markdown
Member Author

Reproducible real-site examples for the remote-validation layer at 64597d9.

The key output lines below were verified by this commit's six passing live BadSSL checks when the stack was split. Fresh manual captures on 2026-10-08 encountered TLS connection resets/timeouts from this environment, so these are tested expected excerpts, not a fresh stdout capture.

Build this PR:

gh pr checkout 531
cargo build --locked
ICC="$PWD/target/debug/inspect-cert-chain"

A valid control:

"$ICC" --host sha256.badssl.com --timeout 30s --fields subject
Certificate chain: VALID
Path validation: VALID
Revocation (leaf): NOT CHECKED

A certificate for the wrong hostname:

"$ICC" --host wrong.host.badssl.com --timeout 30s --fields subject
Certificate chain: INVALID
Path validation: VALID
Hostname (wrong.host.badssl.com): INVALID (hostname does not match certificate names)

An incomplete server chain:

"$ICC" --host incomplete-chain.badssl.com --timeout 30s --fields subject
Certificate chain: INVALID
Path validation: INVALID (missing issuer certificate or untrusted issuer)
Hostname (incomplete-chain.badssl.com): VALID

The live tests verified exit code 0 for these inspections. Invalid certificates remain inspectable. A connection reset or timeout is a fetch error and returns a nonzero exit code. Revocation is not checked in this layer; CRL support follows in #545 and #546.

To repeat all six live assertions:

just test-badssl

The excerpts omit other certificate and validation lines. Public endpoints and certificates can change.

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