Skip to content
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

Arc-wrap ClientConfig RootCertStore and remove expensiveness warnings #1413

Merged
merged 2 commits into from
Aug 24, 2023

Conversation

djc
Copy link
Member

@djc djc commented Aug 23, 2023

From #1403:

We should add some nuance to this, explaining for instance that loading a bundle of roots from disk may be slow, so ClientConfigs/ServerConfigs that use that method should be reused to the extent possible. And perhaps going into even more detail, that the slowness specifically lives in ConfigBuilder::with_root_certificates (and the steps that come before that, to load the RootCertStore).

I ended up just removing the remarks for now, after changing with_root_certificates() for the ClientConfig builder to take an Arc<RootCertStore> (the ServerConfig builder started taking an Arc-wrapped RootCertStore in #1368). IMO it would be best to document the performance implications where they occur, which in this case means rustls_native_certs::load_native_certs(). Its documentation currently reads:

This function can be expensive: on some platforms it involves loading and parsing a ~300KB disk file. It’s therefore prudent to call this sparingly.

I also believe the use of Arc wrappers itself will help signal that (re)creating something of that type is performance-sensitive, so I feel there's not much value in further discussion directly on the config types (which, at worst, cost a few allocations for clones, many of which are actually refcount bumps from other Arcs).

Fixes #1403. Motivated by discussion in hickory-dns/hickory-dns#1990 (trust-dns-resolver currently stores a Lazy<Arc<ClientConfig>> wrapping webpki-roots -- which doesn't seem great).

@djc djc requested review from jsha, cpu and ctz August 23, 2023 11:31
@@ -16,7 +16,7 @@ impl<C: CryptoProvider> ConfigBuilder<ClientConfig<C>, WantsVerifier<C>> {
/// Choose how to verify server certificates.
pub fn with_root_certificates(
self,
root_store: webpki::RootCertStore,
root_store: Arc<webpki::RootCertStore>,
Copy link
Member Author

Choose a reason for hiding this comment

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

Alternatively, we could take impl Into<Arc<webpki::RootCertStore>> to make this a little more ergonomic at the cost of a downstream monomorphization (which, in this case, is probably not a large cost?).

Copy link
Member

Choose a reason for hiding this comment

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

AIUI because there is a blanket implementation for that on all types this would make this much less invasive to users?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, exactly.

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm, I guess that would also make it semver-compatible?

@codecov
Copy link

codecov bot commented Aug 23, 2023

Codecov Report

Merging #1413 (afecb0a) into main (6e9a61f) will increase coverage by 0.00%.
The diff coverage is 100.00%.

❗ Current head afecb0a differs from pull request most recent head 82d77b9. Consider uploading reports for the commit 82d77b9 to get more accurate results

@@           Coverage Diff           @@
##             main    #1413   +/-   ##
=======================================
  Coverage   96.33%   96.33%           
=======================================
  Files          66       66           
  Lines       14860    14862    +2     
=======================================
+ Hits        14316    14318    +2     
  Misses        544      544           
Files Changed Coverage Δ
rustls/src/client/client_conn.rs 88.32% <ø> (ø)
rustls/src/server/server_conn.rs 87.41% <ø> (ø)
rustls/src/client/builder.rs 88.23% <100.00%> (ø)
rustls/src/webpki/verify.rs 98.70% <100.00%> (+<0.01%) ⬆️

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

@daxpedda
Copy link
Contributor

On that note, I'm aware that this is future talk here, but what is the plan for rustls-platform-verifier?
Will there be a way to use it without dangerous_configuration? Because if we decide to move ServerCertVerifier out of dangerous_configuration we might want to change that API a bit.

@djc
Copy link
Member Author

djc commented Aug 23, 2023

On that note, I'm aware that this is future talk here, but what is the plan for rustls-platform-verifier? Will there be a way to use it without dangerous_configuration?

Conceptually I think there should be but it's not entirely obvious to me how that should work.

Because if we decide to move ServerCertVerifier out of dangerous_configuration we might want to change that API a bit.

Like how?

@daxpedda
Copy link
Contributor

Because if we decide to move ServerCertVerifier out of dangerous_configuration we might want to change that API a bit.

Like how?

Probably expose WebPkiVerifier as well and change with_root_certificates() to take a Arc<impl ServerCertVerifier>.

@djc
Copy link
Member Author

djc commented Aug 23, 2023

Probably expose WebPkiVerifier as well and change with_root_certificates() to take a Arc<impl ServerCertVerifier>.

It could make sense to drop with_root_certificates() in favor of with_verifier(Arc<dyn ServerCertVerifier>) (we already have the more elaborately named with_custom_certificate_verifier() today), and move the RootCertStore integration point to a ServerCertVerifier builder or constructor.

The open question is in that today, any foreign verifier wouldn't be able to impl ServerCertVerifier without also enabling dangerous_configuration. It might be nice if we found a way to do that. I suppose we could have a PlatformVerifier newtype wrapped around a rustls-platform-verifier type (to avoid leaking the rustls-platform-verifier version into the rustls public API) which could be enabled based on a Cargo feature. That might actually be an interesting pattern for webpki-roots and rustls-native-certs, too -- should make things easier for users.

@jsha
Copy link
Contributor

jsha commented Aug 23, 2023

Like how?

The nice thing about the builder API is that we can have multiple setters that affect the same underlying setting but use different types. For instance we could have:

fn with_platform_verifier(v: rustls_platform_verifier::ClientConfig)

This could be exposed in non-dangerous_configuration builds. Or, as you point out, make a newtype for it.

Similarly, for this change we could expose with_arc_root_certificates, deprecating with_root_certificates. I know we have a breaking change coming up anyway but this is a sort of low-stakes papercut that makes upgrades frustrating (and affects almost all downstream users). Providing both versions for a release cycle would make people's lives a bit easier.

For the expensiveness warning: Rather than delete it we should replace it with text that says the opposite ("making one of these is cheap"). People have read the existing warning and if it goes away they are likely to assume they simply can't find it or misremembered where it is, rather than that they no longer need to consider these objects expensive.

@daxpedda
Copy link
Contributor

But currently rustls-platform-verifier depends on rustls, so if we want to expose something like with_platform_verifier() or a newtype we would need to remove that first.

@djc
Copy link
Member Author

djc commented Aug 23, 2023

Like how?

The nice thing about the builder API is that we can have multiple setters that affect the same underlying setting but use different types. For instance we could have:

fn with_platform_verifier(v: rustls_platform_verifier::ClientConfig)

This could be exposed in non-dangerous_configuration builds. Or, as you point out, make a newtype for it.

Sure -- so far, though, we haven't "blessed" any verifier other than the webpki one by directly exposing it as part of the rustls API. This would change that, which feels like a larger conceptual change?

Edit:

But currently rustls-platform-verifier depends on rustls, so if we want to expose something like with_platform_verifier() or a newtype we would need to remove that first.

Ah yes, that's one reason this isn't quite trivial. I think the fix here would be that rustls-platform-verifier just offers an API (like rustls-webpki currently does) that mimics but doesn't implement the ServerCertVerifier API, and then the rustls crate would wrap the trait impl around it.

Similarly, for this change we could expose with_arc_root_certificates, deprecating with_root_certificates. I know we have a breaking change coming up anyway but this is a sort of low-stakes papercut that makes upgrades frustrating (and affects almost all downstream users). Providing both versions for a release cycle would make people's lives a bit easier.

I do like the idea of providing with_verifier() and a public WebPkiVerifier with an Arc<RootCertStore> constructor on 0.21 so people can adapt.

@ctz thoughts?

For the expensiveness warning: Rather than delete it we should replace it with text that says the opposite ("making one of these is cheap"). People have read the existing warning and if it goes away they are likely to assume they simply can't find it or misremembered where it is, rather than that they no longer need to consider these objects expensive.

Should we explicitly acknowledge that we used to say it was expensive?

@jsha
Copy link
Contributor

jsha commented Aug 23, 2023

Sure -- so far, though, we haven't "blessed" any verifier other than the webpki one by directly exposing it as part of the rustls API. This would change that, which feels like a larger conceptual change?

Yes, part of a larger conceptual change, but I think it's the right direction. Maybe it should wait until rustls-platform-verifier has gotten more public usage, but I think long-term it should be blessed like webpki. Actually, long-term I would really love to be able to provide:

ClientConfig::default()

Which would choose default cipher suites and so on, plus rustls-platform-verifier as the verifier. I think its properties make it the right choice as a default verifier. It does what people expect, and is a good verifier on all platforms. And because it doesn't require loading all roots (on certain platforms), it's faster to start up.

Still, that's somewhat aside from this specific PR, just something that's been on my mind to bring up.

Should we explicitly acknowledge that we used to say it was expensive?

Yes, I think that would be good.

@ctz
Copy link
Member

ctz commented Aug 23, 2023

Ah yes, that's one reason this isn't quite trivial. I think the fix here would be that rustls-platform-verifier just offers an API (like rustls-webpki currently does) that mimics but doesn't implement the ServerCertVerifier API, and then the rustls crate would wrap the trait impl around it.

I'd agree with that approach (and seems like it would be marginally easier with the base types crate?). I've also found platform-verifier difficult to reuse/compose because of its dependency on rustls.

Similarly, for this change we could expose with_arc_root_certificates, deprecating with_root_certificates. I know we have a breaking change coming up anyway but this is a sort of low-stakes papercut that makes upgrades frustrating (and affects almost all downstream users). Providing both versions for a release cycle would make people's lives a bit easier.

I do like the idea of providing with_verifier() and a public WebPkiVerifier with an Arc<RootCertStore> constructor on 0.21 so people can adapt.

@ctz thoughts?

I don't think we're achieving anything by gating WebPkiServerVerifier behind dangerous_configuration.

@djc
Copy link
Member Author

djc commented Aug 23, 2023

I don't think we're achieving anything by gating WebPkiServerVerifier behind dangerous_configuration.

But how do you feel about a change from with_root_certificates(roots) to with_verifier(WebPkiServerVerifier::new(roots))?

@ctz
Copy link
Member

ctz commented Aug 24, 2023

I don't think we're achieving anything by gating WebPkiServerVerifier behind dangerous_configuration.

But how do you feel about a change from with_root_certificates(roots) to with_verifier(WebPkiServerVerifier::new(roots))?

I think that would be OK, it's certainly not breaking the bank on top of the quite significant amount of ceremony needed to get to that point (making a RootCertStore, getting the certs in there, etc.)

Also, I have added a second constructor to WebPkiServerVerifier to allow reuse of this without ring, so this change would make use of that easier.

@djc
Copy link
Member Author

djc commented Aug 24, 2023

I've changed the ServerCertVerifier setup to take impl Into<Arc<RootCertStore>> so that the change is backwards compatible and replaced the "expensive" warning with a more precise explanation:

/// While creation of a `ServerConfig` was previously documented to be expensive, this has
/// recently been imprecise. The largest potential cost in building a `ServerConfig` is in
/// gathering trust roots from the operating system to add to the [`RootCertStore`] passed
/// to to a [`ClientCertVerifier`] builder (the rustls-native-certs is often used for this).
/// Other than that, the cost of instantiating is relatively cheap, involving a few allocations.

I've left out further changes to start taking an explicit verifier for the ClientConfig, since I think these will be more involve. If this is merged I'd like to backport it to the 0.21.x branch.

jsha
jsha previously requested changes Aug 24, 2023
Copy link
Contributor

@jsha jsha left a comment

Choose a reason for hiding this comment

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

Other than comment tweaks, this implementation plan and the backport look excellent to me.

rustls/src/server/server_conn.rs Outdated Show resolved Hide resolved
rustls/src/client/client_conn.rs Outdated Show resolved Hide resolved
rustls/src/client/client_conn.rs Outdated Show resolved Hide resolved
rustls/src/client/client_conn.rs Outdated Show resolved Hide resolved
/// While creation of a `ServerConfig` was previously documented to be expensive, this has
/// recently been imprecise. The largest potential cost in building a [`ServerConfig`] is in
/// gathering trust roots from the operating system to add to the [`RootCertStore`] passed
/// to to a `ClientCertVerifier` builder (the rustls-native-certs is often used for this).
Copy link
Member

Choose a reason for hiding this comment

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

/// to to a `ClientCertVerifier` builder (the rustls-native-certs crate is often used for this).

not sure that is true, because rustls-native-certs filters root certs (on windows and macos at least) only for trusting servers. of course, most of those are also trusted for publicly-issued client certs...

Copy link
Member Author

Choose a reason for hiding this comment

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

That's actually not so clear from the rustls-native-certs documentation. Maybe we should clear that up?

The actually expensive part is mostly the gathering of certificates
from the platform trust root store, and it would be better to document
that in the relevant API (that is, in rustls-native-certs). Apart
from that, I believe that the use of `Arc`-wrapped types is also an
effective signal that the wrapped types should be reused where possible.
Copy link
Member

@cpu cpu left a comment

Choose a reason for hiding this comment

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

This looks great. Thanks for picking this up @djc.

@djc djc requested a review from ctz August 24, 2023 15:31
@djc djc enabled auto-merge August 24, 2023 15:38
@djc djc added this pull request to the merge queue Aug 24, 2023
Merged via the queue into main with commit 09903a5 Aug 24, 2023
37 checks passed
@djc djc deleted the arc-server-roots branch August 24, 2023 15:48
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.

doc: update "can be expensive" on configs
5 participants