Skip to content

refactor(fulcio)!: typed responses, private requests and a client builder - #248

Merged
wolfv merged 3 commits into
mainfrom
refactor/fulcio-typed-client
Sep 25, 2026
Merged

wolfv merged 3 commits into
mainfrom
refactor/fulcio-typed-client

Conversation

@wolfv

@wolfv wolfv commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

1.0 API work, PR F in the plan, as two commits.

refactor(fulcio)!: typed responses, private requests and a client builder

  • SigningCertificate is a #[non_exhaustive] enum over the response's oneof: EmbeddedSct { chain } / DetachedSct { chain, signed_certificate_timestamp }
    • PEM certificates are parsed into DerCertificates up front
    • leaf_certificate() and certificate_chain() are infallible, since empty chains, a missing certificate, and both SCT kinds at once are rejected while parsing
  • TrustBundle holds parsed Vec<Vec<DerCertificate>> chains. The cache now stores the raw response and parses it like a fresh one
  • the request types are private. Credentials no longer derives Debug, which used to print the raw OIDC token
  • FulcioClient::new / build return Result instead of panicking, and the builder trims trailing slashes
  • the built-in public() / staging() URLs are removed, the same as for Rekor (refactor(rekor)!: remove built-in log URLs and fix stale docs #240), the TSA (refactor(tsa)!: keep the RFC 3161 ASN.1 model private #245) and OIDC (refactor(oidc)!: explicit identity provider configuration #243)
  • Error is #[non_exhaustive]:
    • Status { status, message } for non-success responses, so callers can tell a 401 from a 5xx
    • InvalidResponse for malformed bodies
    • Signing for key-pair failures

refactor(fulcio)!: rename OIDCIssuer to OidcIssuer: acronym casing per the API guidelines.

refactor(fulcio)!: accept a caller-configured reqwest client: FulcioClientBuilder::with_http_client(reqwest::Client) replaces the builder's own timeout / with_timeout. Callers configure timeouts, proxies, TLS roots, the user agent and the TLS provider (#220) on their client. Without one, a client with a 30 s timeout and a sigstore-rust/<version> UA is used. reqwest is re-exported and is now a public dependency, which was a deliberate decision. The other HTTP clients (Rekor, TSA, OIDC, TUF) will get the same shape.

The only file this shares with #247 is sigstore-sign/src/sign.rs, in a different hunk.

Validation

  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo test -p sigstore-fulcio -p sigstore-sign -p sigstore-conformance --all-features (new tests: SCT oneof parsing, trust-bundle parsing)
  • cargo check -p sigstore-fulcio --no-default-features

Signed-off-by: Wolf Vollprecht w.vollprecht@gmail.com

🤖 Generated with Claude Code

wolfv and others added 3 commits September 24, 2026 18:14
…lder

- `SigningCertificate` is a `#[non_exhaustive]` enum over the response's
  oneof: `EmbeddedSct { chain }` or `DetachedSct { chain,
  signed_certificate_timestamp }`, with PEM certificates parsed into
  `DerCertificate`s up front. `leaf_certificate()` and
  `certificate_chain()` are infallible, because an empty chain is rejected
  while parsing.
- `TrustBundle` holds parsed `Vec<Vec<DerCertificate>>` chains; the cache
  now stores the raw response and parses it like a fresh one.
- Request types (`CreateSigningCertificateRequest`, `Credentials`,
  `PublicKeyRequest`, `PublicKeyData`) are private. `Credentials` no
  longer derives `Debug`, which printed the raw OIDC token.
- `FulcioClient::new` and `FulcioClientBuilder::build` return `Result`
  instead of panicking; the builder gains `timeout` (was `with_timeout`)
  and `user_agent` (default `sigstore-rust/<version>`) and trims trailing
  slashes. The built-in `public()` / `staging()` URLs are removed.
- `Error` is `#[non_exhaustive]`. Non-success responses are
  `Status { status, message }`, malformed ones `InvalidResponse`, and key
  pair failures `Signing`; `Api` and `Certificate` are removed.

BREAKING CHANGE: `SigningCertificate` and `TrustBundle` changed shape;
the request types and `CertificateChain`, `ChainContent` and
`CertificateWithSCT` are no longer public; `FulcioClient::new` and
`build` return `Result`; `with_timeout` is `timeout`;
`FulcioClient::public` and `staging` are removed; `Error` variants
changed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
Follow the Rust API guidelines' casing for acronyms.

BREAKING CHANGE: `OIDCIssuer` is renamed `OidcIssuer`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
Callers configure timeouts, proxies, TLS roots and the user agent on
their own reqwest::Client and pass it with
FulcioClientBuilder::with_http_client. This replaces the builder's own
timeout and user_agent options. Without one, a client with a 30-second
timeout and a sigstore-rust user agent is used. reqwest is re-exported
so callers can build a matching client.

BREAKING CHANGE: FulcioClientBuilder::timeout and user_agent are replaced
by with_http_client(reqwest::Client); reqwest is part of the public API.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>

@jku jku left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm approving, this looks good, but have a look at the comment on get_trust_bundle -- should we just get rid of it?

Some(client) => client,
None => reqwest::Client::builder()
.timeout(DEFAULT_TIMEOUT)
.user_agent(DEFAULT_USER_AGENT)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a good call -- we should do this in other clients too. This can be very useful on the service side.

For future work, maybe should allow users of the library to override the UA

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

///
/// With the `cache` feature enabled and a cache configured, this will
/// cache the trust bundle with the default TTL (24 hours).
pub async fn get_trust_bundle(&self) -> Result<TrustBundle> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this feels like API we could alternatively just get rid of?

It feels like a debugging tool included in the API -- TrustBundle is not even used by any other part of the API.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the comment, will chekc it out!

@jku jku mentioned this pull request Sep 25, 2026
@wolfv
wolfv merged commit 879b18e into main Sep 25, 2026
19 checks passed
@wolfv
wolfv deleted the refactor/fulcio-typed-client branch September 25, 2026 13:33
@wolfv wolfv mentioned this pull request Sep 25, 2026
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.

2 participants