tls: expose verified peer certificate digest for server-side replica pinning #73

Merged
ober merged 5 commits from feat/rustls-peer-cert-sha256 into master 2026-09-14 22:49:18 -04:00
Owner

Summary

Adds rustls-peer-certificate-sha256 to (std net tls-rustls) and native jerboa_tls_peer_cert_sha256: the SHA-256 digest of the leaf certificate rustls already verified on a completed connection, retrievable from both client and server (mTLS) handles.

Motivation

The existing pinning API (rustls-connect-pinned) only covers the client connect path. Servers that authorize callers by per-certificate pinning (e.g. botcommons-store replica authorization over mTLS) currently have no way to learn which verified client certificate arrived — the only alternative is a CA-based model, which is a different security posture. This has forced downstream consumers to carry a private FFI fork of exactly this function (builder-lineage jerboa 17c03773).

Design

  • Digest-only across the FFI: no parser-dependent certificate object is exposed; the native side hashes with ring::digest::SHA256 and writes exactly 32 bytes through the existing checked-output-buffer helper.
  • Failure semantics: invalid handle, incomplete handshake, unauthenticated peer, and undersized buffers return -1 with a set_last_error message; the Scheme wrapper raises.
  • No change to existing exports or native ABI (pure addition).

Testing

  • New tests/test-tls-peer-cert driver (auto-discovered by make test via the tests/test-* wildcard) + tests/fixtures/tls-peer-cert fixture: one self-signed cert serves as server cert, client cert, and CA; openssl independently computes the DER SHA-256; the fixture asserts both peers report exactly that digest after a completed mTLS roundtrip, and that an unknown handle raises.
  • cargo check clean on the native crate.

Verification evidence

  • Local cargo check: passes (pre-existing warnings only).
  • Live E2E of the same accessor ported into a production topology (botcommons-store mTLS replica authorization) verified on the botcommons loadtest VM during glm-53 GC-wedge verification.
## Summary Adds `rustls-peer-certificate-sha256` to `(std net tls-rustls)` and native `jerboa_tls_peer_cert_sha256`: the SHA-256 digest of the leaf certificate rustls already verified on a completed connection, retrievable from both client and **server (mTLS)** handles. ## Motivation The existing pinning API (`rustls-connect-pinned`) only covers the client connect path. Servers that authorize callers by per-certificate pinning (e.g. botcommons-store replica authorization over mTLS) currently have no way to learn which verified client certificate arrived — the only alternative is a CA-based model, which is a different security posture. This has forced downstream consumers to carry a private FFI fork of exactly this function (builder-lineage jerboa `17c03773`). ## Design - Digest-only across the FFI: no parser-dependent certificate object is exposed; the native side hashes with `ring::digest::SHA256` and writes exactly 32 bytes through the existing checked-output-buffer helper. - Failure semantics: invalid handle, incomplete handshake, unauthenticated peer, and undersized buffers return `-1` with a `set_last_error` message; the Scheme wrapper raises. - No change to existing exports or native ABI (pure addition). ## Testing - New `tests/test-tls-peer-cert` driver (auto-discovered by `make test` via the `tests/test-*` wildcard) + `tests/fixtures/tls-peer-cert` fixture: one self-signed cert serves as server cert, client cert, and CA; openssl independently computes the DER SHA-256; the fixture asserts both peers report exactly that digest after a completed mTLS roundtrip, and that an unknown handle raises. - `cargo check` clean on the native crate. ## Verification evidence - Local `cargo check`: passes (pre-existing warnings only). - Live E2E of the same accessor ported into a production topology (botcommons-store mTLS replica authorization) verified on the botcommons loadtest VM during glm-53 GC-wedge verification.
tls: expose verified peer certificate digest for server-side replica pinning
Some checks failed
required-ci / gerbil-compat (pull_request) Successful in 4m13s
version-policy / required (pull_request) Failing after 5m56s
required-ci / required (pull_request) Has been cancelled
dtrace / freebsd-usdt (pull_request) Has been cancelled
freebsd-required / required (pull_request) Has been cancelled
8c0536eaaf
Add rustls-peer-certificate-sha256 and native jerboa_tls_peer_cert_sha256:
the SHA-256 of the leaf certificate rustls already verified on a completed
connection, for both client and server (mTLS) handles. Consumers that pin
per-replica certificates (e.g. botcommons-store replica authorization)
need the server-side digest; the existing API only offers pinning at
client connect time.

The wrapper returns only the 32-byte digest so no parser-dependent
certificate object crosses the FFI boundary. Invalid handles, incomplete
handshakes, and unauthenticated peers raise instead of returning bytes.

E2E test under tests/ (test-tls-peer-cert driver + fixtures/tls-peer-cert):
an mTLS roundtrip where both peers must report the openssl-computed DER
SHA-256 of the shared self-signed certificate, plus the invalid-handle
rejection path.
chore: bump VERSION to 0.12.7 for version policy
Some checks failed
required-ci / gerbil-compat (pull_request) Successful in 4m3s
version-policy / required (pull_request) Successful in 5m51s
required-ci / required (pull_request) Failing after 10m19s
freebsd-required / required (pull_request) Successful in 13m7s
dtrace / freebsd-usdt (pull_request) Successful in 14m35s
ab2ed40596
Fix peer-cert test: proper CA+leaf PKI, DER digests, verify both peers
Some checks failed
required-ci / gerbil-compat (pull_request) Successful in 3m57s
version-policy / required (pull_request) Failing after 5m55s
freebsd-required / required (pull_request) Has been cancelled
dtrace / freebsd-usdt (pull_request) Has been cancelled
required-ci / required (pull_request) Has been cancelled
d6875bc146
- rustls rejects CA certs as end entities, so generate a test CA and
  separate server/client leaves (SAN IP:127.0.0.1, serverAuth/clientAuth)
- hash the certificate DER, not the PEM wrapper
- each peer reports the OTHER side's leaf digest, so compute and assert
  both expected values
- keep the client connection open until its digest is read
- fix the fixture argument gate (prog + 7 args)
test: raise peer-cert subprocess timeout to 90s for slow CI runners
Some checks failed
required-ci / gerbil-compat (pull_request) Successful in 4m14s
version-policy / required (pull_request) Failing after 6m19s
required-ci / required (pull_request) Failing after 11m22s
freebsd-required / required (pull_request) Successful in 15m25s
dtrace / freebsd-usdt (pull_request) Successful in 17m54s
fbd78f32bc
ober force-pushed feat/rustls-peer-cert-sha256 from fbd78f32bc
Some checks failed
required-ci / gerbil-compat (pull_request) Successful in 4m14s
version-policy / required (pull_request) Failing after 6m19s
required-ci / required (pull_request) Failing after 11m22s
freebsd-required / required (pull_request) Successful in 15m25s
dtrace / freebsd-usdt (pull_request) Successful in 17m54s
to ceb92f67c0
All checks were successful
required-ci / gerbil-compat (pull_request) Successful in 4m14s
version-policy / required (pull_request) Successful in 5m54s
freebsd-required / required (pull_request) Successful in 15m51s
dtrace / freebsd-usdt (pull_request) Successful in 17m36s
required-ci / required (pull_request) Successful in 18m36s
2026-09-14 22:32:33 -04:00
Compare
ober merged commit 57aa69e36b into master 2026-09-14 22:49:18 -04:00
ober referenced this pull request from a commit 2026-09-14 22:49:19 -04:00
Author
Owner

Post-merge note: the push-context ci.yaml run on 57aa69e3 (23:05) failed while the identical tree passed the pull_request-context run on ceb92f67 (22:47, all five checks green; freebsd/dtrace/gerbil-compat/version-policy also green on 57aa69e3 itself). Same content, ~20 min apart, single check diverging — this matches the timing-sensitive-test flake profile seen on master historically (e.g. e963f91f, 143ba6ef, 5905f56c). No content change is implicated; a re-run should settle it.

Separately filed while debugging in this area: #76 (jerboa-prog-* temp artifacts leak on kill/crash — found after it filled a validation jail's /tmp with 61 GB and ENOSPC-truncated an unrelated DB).

Post-merge note: the `push`-context ci.yaml run on 57aa69e3 (23:05) failed while the identical tree passed the `pull_request`-context run on ceb92f67 (22:47, all five checks green; freebsd/dtrace/gerbil-compat/version-policy also green on 57aa69e3 itself). Same content, ~20 min apart, single check diverging — this matches the timing-sensitive-test flake profile seen on master historically (e.g. e963f91f, 143ba6ef, 5905f56c). No content change is implicated; a re-run should settle it. Separately filed while debugging in this area: #76 (jerboa-prog-* temp artifacts leak on kill/crash — found after it filled a validation jail's /tmp with 61 GB and ENOSPC-truncated an unrelated DB).
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ober/jerboa!73
No description provided.