Skip to content

Always send the configured client certificate to HTTP stores - #444

Merged
folbricht merged 2 commits into
folbricht:masterfrom
shibuya-r:client-cert-always-send
Oct 5, 2026
Merged

folbricht merged 2 commits into
folbricht:masterfrom
shibuya-r:client-cert-always-send

Conversation

@shibuya-r

Copy link
Copy Markdown
Contributor

Fixes #443

When a client certificate is configured for a network store, tlsClientConfig() sets only tls.Config.Certificates. Go then sends the certificate only if its issuer appears in the server's CertificateRequest CA list. Otherwise it sends no certificate, and the handshake fails with tls: certificate required.

That breaks TLS terminators that advertise a fixed set of public CAs and forward the client certificate to the backend for verification. Azure Container Apps' ingress mTLS (clientCertificateMode: require) is one of them.

This PR sets GetClientCertificate to always return the configured certificate, the same way curl --cert and OpenSSL-based clients behave. The server still decides whether to accept it, so verification is not weakened. Setups where the server advertises the issuing CA, such as desync's own chunk-server --mutual-tls --client-ca, behave the same as before.

Changes

  • store.go: set tls.Config.GetClientCertificate alongside Certificates.
  • store_tls_test.go (new): starts a TLS server that requires a client certificate and advertises an unrelated CA. Checks that the certificate issued by another CA still reaches the server.

Testing

  • go test -run TestTLSClientCertSentRegardlessOfAcceptableCAs .: fails without the store.go change (remote error: tls: certificate required) and passes with it.
  • Checked against Azure Container Apps with a private-CA client certificate. Before the change the handshake failed. After it, the request reaches the backend and follows the redirect to the chunk.
  • go vet and gofmt are clean. On macOS, TestExtractWithNonStaticSeeds and TestTar fail on unmodified v1.1.4 too, so they are not related to this change.

🤖 Generated with Claude Code

@folbricht

Copy link
Copy Markdown
Owner

Thanks for the PR, the change itself looks good. The Lint job is failing in CI, though. Could you run golangci-lint locally (CI pins v2.12) and push a fix?

go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12
golangci-lint run

My guess is errcheck flagging the unchecked resp.Body.Close() in store_tls_test.go. The config only exempts deferred Close calls, so defer resp.Body.Close() or require.NoError(t, resp.Body.Close()) should clear it.

shibuya-r and others added 2 commits October 5, 2026 21:13
Go's default client certificate selection sends no certificate when the
server's CertificateRequest doesn't list the issuing CA. Set
GetClientCertificate so the configured certificate is always presented,
as curl and OpenSSL-based clients do.

Fixes folbricht#443

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
golangci-lint's errcheck only exempts deferred Close calls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shibuya-r
shibuya-r force-pushed the client-cert-always-send branch from 4dadb00 to 8c7f8e0 Compare October 5, 2026 12:13
@shibuya-r

Copy link
Copy Markdown
Contributor Author

Thanks for the review! You were right, it was errcheck on the unchecked
resp.Body.Close() in store_tls_test.go. I changed it to
require.NoError(t, resp.Body.Close()) and pushed the fix (8c7f8e0).

golangci-lint run with v2.12 now reports 0 issues locally (run as linux,
with the Go version from go.mod).

@folbricht
folbricht merged commit 56238d8 into folbricht:master Oct 5, 2026
17 of 18 checks passed
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.

HTTP store: client certificate is not sent when the server's CertificateRequest lists a different CA

2 participants