-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Cargo mTLS registry authentication #3907
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 5 commits
73a123b
905eb14
704d24f
e461aed
92fdb37
3a16df3
24a2881
ae27ce7
d1806de
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| - Feature Name: `mtls-registry-authentication` | ||
| - Start Date: 2026-01-16 | ||
| - RFC PR: [rust-lang/rfcs#3907](https://github.com/rust-lang/rfcs/pull/3907) | ||
| - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) | ||
|
|
||
| # Summary | ||
| [summary]: #summary | ||
|
|
||
| This is an RFC aimed at allowing Cargo to present client certificates when forming HTTP connections and support mutual TLS authentication with registries. | ||
|
|
||
| # Motivation | ||
| [motivation]: #motivation | ||
|
|
||
| Some organizations require client identity verification when interacting with privately hosted services. This can be achieved a number of ways, but is commonly done with certificates in a process called "mutual TLS" (mTLS). | ||
|
|
||
| Cargo does not currently support forwarding client certificate information when it configures its `libcurl` HTTP handle. This poses an issue for organizations that host private crate registries and perform client authentication via certificates, since there is no alternative way to forward these client provided certificates. | ||
|
|
||
| Authentication at the TLS level is different from the token-based methods for [Registry Authentication](https://doc.rust-lang.org/cargo/reference/registry-authentication.html) exposed by the [Credential Provider Protocol](https://doc.rust-lang.org/cargo/reference/credential-provider-protocol.html) since it takes place before the connection to the registry is established. It is not currently possible to write a [credential plugin](https://doc.rust-lang.org/cargo/reference/registry-authentication.html#credential-plugins) that enables this type of authentication with a registry, but an extension to that protocol would make this possible. | ||
|
|
||
| # Guide-level explanation | ||
| [guide-level-explanation]: #guide-level-explanation | ||
|
|
||
| Credential providers will be able to provide client certificates and private keys to Cargo via new request and response messages. | ||
|
|
||
| Cargo will issue a tls-identity request when configuring an HTTP client for a registry, and this identity will be used for all subsequent connections to the same registry (within the current Cargo session). | ||
|
|
||
| ## TLS client identity request | ||
|
|
||
| * Sent by: Cargo | ||
| * Purpose: Get client certificates and private keys for HTTP communication | ||
|
|
||
| ```json | ||
| { | ||
| // Protocol version | ||
| "v":2, | ||
| // Request kind: set TLS client identity | ||
| "kind":"tls-identity", | ||
| // Registry information (see https://doc.rust-lang.org/cargo/reference/credential-provider-protocol.html#registry-information) | ||
| "registry":{"index-url":"sparse+https://registry-url/index/"}, | ||
| // Additional command-line args (optional) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do these |
||
| "args":[] | ||
| } | ||
| ``` | ||
|
|
||
| ## TLS client identity response | ||
|
|
||
| * Sent by: credential provider | ||
| * Purpose: Set client certificates and private keys for HTTP communication | ||
|
|
||
| ```json | ||
| {"Ok":{ | ||
| // Response kind: this was a TLS client identity request | ||
| "kind":"tls-identity", | ||
| // Base64 byte buffer containing the binary content of your client certificate (empty if unset) | ||
| "cert_blob":"aGVsbG8...gd29ybGQ=", | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would supporting raw public keys be an option? That way a registry could just store the public key as a token in the database and doesn't need to parse a client certificate and do a whole bunch of checks to see if the certificate is valid, making it much more robust.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This RFC is intended to allow Cargo to present client certificates, not change how the certificates are processed or validated by the server. In my use case, a registry is being reverse-proxied by a web server like Nginx, Apache, HAproxy, etc... to provide additional features like SSO, SCIM and LDAP in addition to client identity verification. Using raw public keys wouldn’t support this reverse-proxy use case, since it would require changes to how the proxy validates the client certificates.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean having the option for the credential provider to provide raw public keys in case a registry wants to support this for robustness or any other reason. If you use client certificates to authenticate specific clients rather than just any client who gets a certificate from the CA, then whichever side channel is used to communicate the identity of the client from the reverse-proxy to the registry can pass a the raw public key too, right?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh I was misunderstanding your previous comment. I think we could add an additional field that allows that, what did you have in mind? |
||
| // The format of your client certificate. With the current curl-based backend, supported formats are “PEM”, “DER”, and "P12" (empty for backend default) | ||
| "cert_type":"PEM", | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we actually need to support PKCS#12 format certificates? The credential provider could just convert it to a PEM certificate + separate private key, right? If you are using PKCS#12 to support private key encryption, the credential provider did have to parse the PKCS#12 file anyway to decrypt the private key as cargo doesn't know the password. Also I don't think there should be a backend dependent default for the type. Either it should be automatically detected from the file header or it should be always explicitly specified.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don’t think we need to support PKCS#12, it was listed as a format that libcurl already supports so I listed it too. If we’re fine explicitly specifying the format a PEM certificate + separate signing key would make sense. It can be on the credential provider to convert objects into that format.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for libcurl to load a password-protected P12 file you'll have to supply
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 3a16df3 now specifies that the certificates and keys are in PEM format
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A PKCS#8 key can also be password-protected. The need of password is not really a question of PEM vs DER vs P12. (We could just say we don't support encrypted private keys)
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I’ll add a section saying that this feature won’t support encrypted private keys, and that it’s the credential provider’s responsibility to take care of decryption if necessary.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. d1806de adds a section on |
||
| // Base64 byte buffer containing the binary content of your private key (empty if unset) | ||
| "key_blob":"aGVsbG8...gd29ybGQ=", | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Already mentioned this on Zulip and probably should be omitted in the first version, but it might be nice to support credential providers that can't export the private key but rather act as a signing oracle to support for example TPM or smartcard backed certificates. Rustls should support that through the
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would definitely be a nice use case to support, and as you point out, it looks like rustls has support for this, and I also believe libcurl can be persuaded into doing this. I don’t know enough about how these signing request flows work to propose a sensible protocol, so that’s mainly why I’ve omitted it here. I’d be happy to consider adding it to this RFC, but also agree that it’s a somewhat independent feature that could be added in a later version. |
||
| // The format of your private key. With the current curl-based backend, supported formats are “PEM” and “DER” (empty for backend default) | ||
| "key_type":"PEM", | ||
| }} | ||
| ``` | ||
|
|
||
| # Reference-level explanation | ||
| [reference-level-explanation]: #reference-level-explanation | ||
|
|
||
| The currently used crate for `libcurl` exposes methods for setting these certificates and keys, and can be used to set these configuration options when HTTP handles are being configured. These methods are: | ||
| * `curl::easy::Easy::ssl_cert_blob` | ||
| * `curl::easy::Easy::ssl_key_blob` | ||
| * `curl::easy::Easy::ssl_cert_type` | ||
| * `curl::easy::Easy::ssl_key_type` | ||
|
|
||
| These "easy" methods wrap well tested code in the curl source: | ||
| * https://github.com/curl/curl/blob/master/docs/libcurl/opts/CURLOPT_SSLKEY.md | ||
| * https://github.com/curl/curl/blob/master/docs/libcurl/opts/CURLOPT_SSLKEYTYPE.md | ||
| * https://github.com/curl/curl/blob/master/docs/libcurl/opts/CURLOPT_SSLCERT.md | ||
|
|
||
| # Security Considerations | ||
|
|
||
| Cargo MUST treat all certificate and private key data returned by a credential provider as sensitive material. | ||
|
|
||
| Cargo MUST NOT persist tls-identity response data to disk. | ||
|
|
||
| All certificate and key material should be held in memory only for the lifetime required to configure the HTTP client. | ||
|
|
||
| Cargo MUST NOT log, print, or otherwise expose the contents of these blobs, including in debug or trace output. | ||
|
|
||
| Credential providers are responsible for securely sourcing and protecting private key material. | ||
|
|
||
| # Drawbacks | ||
| [drawbacks]: #drawbacks | ||
|
|
||
| This adds additional complexity to Cargo's HTTP configuration and could have impacts on where in the code HTTP handles are configured, and which handles are used for communication with different registries. | ||
|
|
||
| # Rationale and alternatives | ||
| [rationale-and-alternatives]: #rationale-and-alternatives | ||
|
|
||
| ## Allow custom credential plugins to handle certificates | ||
|
|
||
| - Credential management is hard, and Cargo does not need to be directly involved in managing these certificates. | ||
|
|
||
| - There are near endlessly niche ways that a user may want to provide their client certificates, and this protocol extension will allow users to write custom plugins to handle their situation. | ||
|
|
||
| - This RFC does not introduce new trust boundaries beyond those already present for credential providers, which are treated as fully trusted by the user. | ||
|
|
||
| ## Avoid backend lock-in | ||
|
|
||
| - Today Cargo is using `libcurl` for its backend HTTP client. There might be a future where a `rustls` based backend would be preferred. Nearly all TLS libraries support client certificates in some form, and this protocol extension gives Cargo ability to convert from a binary blob to whatever format may be needed in the future. | ||
|
|
||
| # Prior art | ||
| [prior-art]: #prior-art | ||
|
|
||
| Mutual TLS authentication is widely supported across TLS libraries, developer tools, and artifact distribution systems. | ||
|
|
||
| libcurl has supported client certificates via CURLOPT_SSLCERT and CURLOPT_SSLKEY since version 7.1 (released August 2000), and these options are commonly used by applications that require authenticated HTTPS connections. Other widely used TLS implementations, including OpenSSL, BoringSSL, NSS, and rustls, also provide first-class support for configuring client certificates and private keys for TLS connections. | ||
|
|
||
| Many developer tools and package managers support mutual TLS when interacting with private registries or artifact repositories. For example, Python package management tools such as Poetry and uv allow users to configure client certificates for authenticated registry access. Other ecosystems similarly support client certificate authentication, including pip, npm, Maven, and Gradle, where mutual TLS is commonly used in enterprise environments. | ||
|
|
||
| Private artifact repository systems and registry infrastructure, such as JFrog Artifactory, Sonatype Nexus, GitHub Enterprise, and GitLab, frequently support or encourage mutual TLS as an authentication mechanism for internal services. These systems are often deployed in environments with existing public key infrastructure, where TLS-level client authentication integrates naturally with organizational security policies. | ||
|
|
||
| Within Cargo itself, this RFC builds on existing design patterns established by the credential provider protocol. Cargo already delegates authentication concerns to external credential providers and avoids managing long-lived secrets directly. Extending this protocol to allow credential providers to supply TLS client identity material follows the same approach and enables mutual TLS support without introducing new secret management responsibilities into Cargo. | ||
|
|
||
| # Unresolved questions | ||
| [unresolved-questions]: #unresolved-questions | ||
|
|
||
| This RFC is intentionally limited to providing client certificate and private key material to Cargo. It does not address configuring additional certificate authority (CA) roots, interacting with platform trust stores, or performing certificate signing request (CSR)–based authentication flows (which would be needed to support hardware security modules). Future extensions to the credential provider protocol might allow credential providers to supply additional trust anchors or to participate in dynamic certificate issuance mechanisms, but these design decisions would likely be influenced by particular TLS backend choices. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is our guidance on how registries should implement authentication? Should registries use MTLS? Should registries use tokens? Should registries use both? If the answer is, as it probably is, "It's complicated" then what are the factors that would drive that decision?