Skip to content

refactor(tsa,oidc,tuf)!: accept caller-configured reqwest clients - #249

Merged
wolfv merged 1 commit into
mainfrom
refactor/inject-http-clients
Sep 25, 2026
Merged

wolfv merged 1 commit into
mainfrom
refactor/inject-http-clients

Conversation

@wolfv

@wolfv wolfv commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

Applies the decision from #248 to the HTTP clients that aren't blocked by #247: callers pass in their own reqwest::Client and configure timeouts, proxies, TLS roots, the user agent and the TLS provider (#220) on it. reqwest becomes a public dependency and is re-exported from each crate.

  • TSA: TimestampClientBuilder::with_http_client replaces its timeout / user_agent options
  • OIDC:
    • new OAuthClient::with_http_client(config, client)
    • OAuthClient::new now returns Result
    • the per-request 30 s token-exchange timeout moves into the default client, so it no longer overrides a caller's client
  • TUF: HttpRepository::with_http_client, documented to need timeouts against the slow-retrieval attack (TUF §1.5.10)

Without a supplied client, each crate keeps its current default timeouts. Rekor's clients get the same shape in the Rekor PR once #247 lands. Plumbing a client through TufConfig and SigningContext comes with the trust-root and sign PRs.

Validation

  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo test -p sigstore-tsa -p sigstore-oidc -p sigstore-tuf -p sigstore-trust-root -p sigstore-sign --all-features
  • cargo check --no-default-features for tsa, oidc and tuf

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

🤖 Generated with Claude Code

Let callers configure timeouts, proxies, TLS roots, the user agent and
the TLS provider on their own `reqwest::Client`, instead of each crate
growing its own knobs. `reqwest` is re-exported from each crate so a
matching client can be built.

- `TimestampClientBuilder::with_http_client` replaces its `timeout` and
  `user_agent` options.
- `OAuthClient::with_http_client(config, client)`; `OAuthClient::new`
  returns `Result`, and its default client (30-second timeout,
  sigstore-rust user agent) replaces the per-request token timeout, which
  would otherwise override a caller's client settings.
- `HttpRepository::with_http_client`, documented to need timeouts
  against the slow-retrieval attack.

Without a supplied client, each crate keeps its current default
timeouts.

BREAKING CHANGE: `TimestampClientBuilder::timeout` and `user_agent` are
replaced by `with_http_client`; `OAuthClient::new` returns `Result`;
reqwest is part of the public API of sigstore-tsa (with `client`),
sigstore-oidc and sigstore-tuf (with `fetch`).

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.

lgtm

// without them a malicious mirror can hang a refresh indefinitely. The
// read timeout is per read operation, so large-but-flowing target
// downloads are unaffected; only a stalled connection trips it.
let client = reqwest::Client::builder()

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 should have a default user-agent as well -- but we can leave that for #253

/// Configure timeouts on it: without them a malicious mirror can hang
/// a refresh indefinitely (the slow-retrieval attack, TUF spec
/// §1.5.10).
pub fn with_http_client(mut self, client: reqwest::Client) -> Self {

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.

new() has created a client already when this is called ... but maybe that's not too bad.

@wolfv
wolfv merged commit 6f89b08 into main Sep 25, 2026
19 checks passed
@wolfv
wolfv deleted the refactor/inject-http-clients branch September 25, 2026 13:34
@wolfv wolfv mentioned this pull request Sep 25, 2026
wolfv added a commit that referenced this pull request Sep 25, 2026
TufConfig::with_http_client passes a reqwest::Client through to the TUF
HTTP repository (#249), so trust material fetches use the caller's
timeouts, proxies, TLS roots and user agent. reqwest is re-exported with
the tuf feature.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
wolfv added a commit that referenced this pull request Sep 25, 2026
TufConfig::with_http_client passes a reqwest::Client through to the TUF
HTTP repository (#249), so trust material fetches use the caller's
timeouts, proxies, TLS roots and user agent. reqwest is re-exported with
the tuf feature.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
wolfv added a commit that referenced this pull request Sep 26, 2026
TufConfig::with_http_client passes a reqwest::Client through to the TUF
HTTP repository (#249), so trust material fetches use the caller's
timeouts, proxies, TLS roots and user agent. reqwest is re-exported with
the tuf feature.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
wolfv added a commit that referenced this pull request Sep 28, 2026
…material (#251)

* refactor(rekor)!: typed client API, status errors and injected HTTP clients

- Typed client methods: `get_entry_by_uuid(&EntryUuid)`,
  `get_entry_by_index(LogIndex)` (was `i64`), `search_index` and
  `search_by_hash` return `Vec<EntryUuid>`, `search_by_hash` takes a
  `&Sha256Hash`, and `get_public_key` returns a parsed `DerPublicKey`
  instead of PEM text.
- Non-success responses are `Error::Status { status, message }`, so a
  409 ("entry already exists") can be told apart from a 5xx; malformed
  bodies are `InvalidResponse` rather than `Http`. `Error` is
  `#[non_exhaustive]`, and the unused `Api` and `Base64` variants are
  removed.
- `RekorClientBuilder::with_http_client` and
  `RekorV2Client::with_http_client` accept a caller-configured
  `reqwest::Client` (re-exported), replacing `with_timeout` and
  `new_with_timeout`. `RekorClient::new`, `RekorV2Client::new` and
  `build` return `Result` instead of panicking.
- The duplicated request plumbing is folded into shared helpers.

BREAKING CHANGE: the Rekor client methods take and return typed values;
`RekorClient::new`, `RekorV2Client::new` and `RekorClientBuilder::build`
return `Result`; `with_timeout` and `new_with_timeout` are replaced by
`with_http_client`; `Error::Api` is replaced by `Error::Status`.

* refactor(rekor)!: typed entry bodies and extensible response types

- Replace `RekorEntryBody::from_base64_json(&str, &str, &str)` with
  `RekorEntryBody::parse(&CanonicalizedBody, KindVersion)` for bundle
  entries and `from_json(&[u8], kind, version)` for raw bodies. Callers
  no longer re-encode the canonicalized body to base64 only for it to be
  decoded again (#199 review). The untagged `Deserialize` derive, which
  guessed the body type from its shape, is removed.
- `LogEntry::log_index` and `RekorInclusionProof::log_index` are
  `LogIndex`, so out-of-range indices are rejected while parsing.
- `LogInfo::tree_i_d` and `InactiveShard::tree_i_d` are renamed `tree_id`
  (serialized as `treeID`).
- `RekorApiVersion`, `RekorV2KeyDetails`, `RekorEntryBody` and the
  response structs (`LogEntry`, `Verification`, `RekorInclusionProof`,
  `LogInfo`, `InactiveShard`) are `#[non_exhaustive]`.
  `RekorApiVersion::major()` and `from_major()` replace hand-written
  version mapping in sigstore-sign.
- `LogEntryResponse` is crate-private.

BREAKING CHANGE: `RekorEntryBody::from_base64_json` is replaced by
`parse` and `from_json`, and `RekorEntryBody` no longer implements
`Deserialize`; log indices are `LogIndex`; `tree_i_d` is `tree_id`; the
listed types are `#[non_exhaustive]`; `LogEntryResponse` is no longer
public.

* refactor(trust-root)!: extensible trust material types and typed errors

- Mark `TrustedRoot`, `TransparencyLog`, `CertificateAuthority`,
  `CertificateTransparencyLog`, `TimestampAuthority`, `PublicKey`,
  `CertificateSubject`, `CertChain`, `CertificateEntry`, `SigningConfig`,
  `ServiceEndpoint`, `ServiceConfiguration`, `SigstoreInstance` and
  `Error` `#[non_exhaustive]`, so new protobuf-specs fields and instances
  are not breaking changes.
- `PublicKey::key_details` is a `PublicKeyDetails` newtype instead of a
  bare `String`.
- `TrustedRoot::fulcio_certs` returns `Vec<DerCertificate>` instead of
  `rustls_pki_types::CertificateDer`, which is no longer a public
  dependency (nor, like `base64`, a dependency at all).
- Errors: `sigstore_types` errors are wrapped as `Error::Types` instead of
  being mapped by variant (unknown ones became `InvalidKey`); file reads
  report `Error::Io` instead of `MissingField` or a JSON error; the
  unused `Base64` and `MissingField` variants are removed.
- `SigningConfig::from_file` takes `impl AsRef<Path>`, like
  `TrustedRoot::from_file`.
- Remove the `ValidityPeriod` alias of `TimeRange`.

BREAKING CHANGE: the listed types are `#[non_exhaustive]`;
`PublicKey::key_details` is `PublicKeyDetails`; `fulcio_certs` returns
`DerCertificate`s; `ValidityPeriod` is removed (use `TimeRange`); `Error`
gains `Io` and `Types` and loses `Base64` and `MissingField`.

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

* refactor(trust-root)!: drop get_ prefixes from signing config lookups

Follow the Rust API guidelines. The filtered lists are named for what they
return, since plain names would collide with the public fields:
get_rekor_urls -> eligible_rekor_urls, get_rekor_url -> rekor_url, and
likewise for Fulcio, TSA and OIDC endpoints.

BREAKING CHANGE: SigningConfig::get_{rekor,fulcio,tsa,oidc}_url(s) are
renamed {rekor,fulcio,tsa,oidc}_url and eligible_{..}_urls.

* refactor(trust-root)!: SigstoreInstance as the source of well-known endpoints

Well-known TUF URLs and bootstrap roots were separate public constants
that every configuration constructor combined by hand. Make the instance
the one place that knows them, so a new instance is one enum variant
(the enum is `#[non_exhaustive]`).

- `SigstoreInstance::tuf_url()` and `tuf_root()` replace the public
  `DEFAULT_TUF_URL`, `STAGING_TUF_URL`, `GITHUB_TUF_URL` and
  `*_TUF_ROOT` constants, which are now private.
- `SigstoreInstance::embedded_signing_config()` /
  `embedded_signing_config_json()` sit next to the embedded trusted root
  (GitHub's instance publishes none).
- `TufConfig::for_instance(instance)` backs `production()`, `staging()`,
  `github()` and `SigstoreInstance::tuf_config()`.
- `TufBootstrap` is `#[non_exhaustive]`.

The TUF target names (`TRUSTED_ROOT_TARGET`, `SIGNING_CONFIG_TARGET`)
remain public.

BREAKING CHANGE: the TUF URL and root constants are no longer public;
use `SigstoreInstance::tuf_url()` / `tuf_root()`. `TufBootstrap` is
`#[non_exhaustive]`.

* feat(trust-root): accept a caller-configured reqwest client for TUF

TufConfig::with_http_client passes a reqwest::Client through to the TUF
HTTP repository (#249), so trust material fetches use the caller's
timeouts, proxies, TLS roots and user agent. reqwest is re-exported with
the tuf feature.

* refactor(rekor)!: remove log info and public key endpoints

`RekorClient::get_public_key` fetched the log's verification key from
the log itself. Verification must take Rekor keys from the trusted root,
so a server-supplied (and cached) key is a way to bypass the trust
model rather than a feature. `get_log_info` has no use in signing or
verification either; monitoring and diagnostics can query the endpoint
directly.

- Remove `get_public_key`, `get_log_info` and the `LogInfo` /
  `InactiveShard` types.
- These were the only users of response caching, so the `cache`
  feature and `RekorClientBuilder::with_cache` / `with_shared_cache`
  are removed as well.

Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
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