windows: add Verifier::offline() constructor#238
Conversation
|
Thanks for the quick response on this, @djc! The For comparison, Go's
Would you consider a mode that simply omits As it stands, |
|
Do you want to send a PR against my branch that better fits your requirements? We should potentially rethink the |
|
I am fine with this PR. We can revisit with more granular control in the future if needed. Appreciate the quick turnaround. Do you have a sense of when this might be merged and released? |
Reviews in this repo have been a little slow recently. @complexspaces will you have time to review this in the next, say, two weeks or so? |
|
Yeah I will make time this week 👍. Thanks for putting some refactoring work into this as well. I wanted to take a second look at the flags being used here since I was confused by this comment in the linked issue:
|
|
Hi @complexspaces, just a friendly nudge - since today is the last working day of the week, would you have a chance to take a look at this? A release with this fix would unblock some downstream work. No pressure of course, and thanks again for taking the time to review! |
| let mut new: Self = unsafe { mem::zeroed() }; | ||
| new.cbSize = Self::SIZE; | ||
| new | ||
| /// Creates a Windows TLS certificate verifier that avoids using online revocation checking. |
There was a problem hiding this comment.
I would expect an offline constructor to avoid any/all network fetches, not just revocation checks. If the goal is just to avoid OCSP/CRL fetching, then a different name should be used. Regardless, I think it is also useful to state more explicitly what this does for AIA fetching.
However, IDK if the Windows certificate verification API is practically usable in a purely offline mode, in general, since Windows usually fetches the trust anchors themselves dynamically, IIRC.
There was a problem hiding this comment.
However, IDK if the Windows certificate verification API is practically usable in a purely offline mode, in general, since Windows usually fetches the trust anchors themselves dynamically, IIRC.
For environments where trust anchors are provisioned into the local cert store, offline mode works fine.
There was a problem hiding this comment.
For environments where trust anchors are provisioned into the local cert store, offline mode works fine.
That's a minority of systems, though, isn't it? My understanding is that on most systems, the root store starts almost empty OOB.
There was a problem hiding this comment.
I do not have data on what percentage of Windows systems rely on dynamic root fetching vs. pre-provisioned stores. This is an argument for making the behavior configurable rather than a single offline() toggle. If no revocation (omit CERT_CHAIN_REVOCATION_CHECK_END_CERT, leave everything else alone) - eliminates revocation network calls while preserving AIA and dynamic root fetching. This matches Go's crypto/x509 behavior on Windows.
That said, if splitting these into separate modes is a significant effort from the author, I think the current PR is still a clear improvement over the status quo - it enables flows where both revocation and AIA are unnecessary, which is a real use case today.
There was a problem hiding this comment.
@briansmith: However, IDK if the Windows certificate verification API is practically usable in a purely offline mode, in general, since Windows usually fetches the trust anchors themselves dynamically, IIRC.
It is hard for me to tell exactly how much AIA is needed (especially with MS's own certificate fetching logic in Windows Update) but from what I can see on my own Windows systems I would lean towards scoping this change to just disallow networking for revocation specifically.
To provide an example: On my Windows 10 system, I only have LetsEncrypt's X1 root by default. X2 is not in any machine/user stores. In that state, I tested a few different flag configurations of CertGetCertificateChain (which is the function that actually fetches certificates that aren't present) when no intermediates from rustls get passed to Windows.
I used reqwest to fetch https://letsencrypt.org which uses a certificate from their X2 root. I also added CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL to every call of CertGetCertificateChain as a baseline then changed the below to see the different behaviors:
| CERT_CHAIN_DISABLE_AIA | CERT_CHAIN_DISABLE_AUTH_ROOT_AUTO_UPDATE | Result |
|---|---|---|
| No | No | Root is added to Intermediate Certification Authorities store |
| No | Yes | Root is added to Intermediate Certification Authorities store by AIA |
| Yes | No | Root is added to Third-Party Root Certification Authorities store by Windows Update(?) |
| Yes | Yes | Fails to verify certificate due to unknown issuer |
Similar applies to the YE intermediate root but I didn't spend the time cleaning that one up too between runs, just X2.
Given that there is a complete failure in a very common case, I think that its safe to say that an entirely offline Windows verifier is not a useful tool to expose from this crate. It won't be able to load a vast majority of sites on the internet.
Maybe it would be best to rename this to cached_revocation or offline_revocation?
@lpapp: For environments where trust anchors are provisioned into the local cert store, offline mode works fine.
I would be surprised if your provisioning process is set up in such a way that machines are always given a fresh copy of the Windows CAB containing all known trusted roots on a regular basis.
I've also ran into a case at work where someone wasn't properly configuring their VDI infrastructure with root certificates so everything 1Password tried to do online failed. I air on the side of assuming the machine won't arrive in an offline-usable state for third-party desktop apps.
There was a problem hiding this comment.
I would be surprised if your provisioning process is set up in such a way that machines are always given a fresh copy of the Windows CAB containing all known trusted roots on a regular basis.
This setup does happen.
There was a problem hiding this comment.
This setup does happen.
I think it is useful to have a pure no-networking configuration. But, I think that the goal is to fix #237, which requires a different solution and different naming.
There was a problem hiding this comment.
To clarify my use case from #237 - I need to disable revocation checks specifically. Whether AIA remains enabled or disabled is not a concern for my scenario, so either the current offline() approach or a revocation-only toggle would resolve the issue for me. The current one week delay is more concerning to me because this is a high-priority blocker for my project, so I am now also evaluating rustls alternatives.
There was a problem hiding this comment.
@lpapp Being annoying is unlikely to speed up the process.
|
@lpapp Yup, I am reviewing this today. I'm currently doing some local testing as well on Windows. |
| @@ -466,7 +466,7 @@ impl CertificateStore { | |||
| | CERT_CHAIN_CACHE_END_CERT; | |||
|
|
|||
| // Lowering URL retrieval timeout from default 15s to 10s to account for higher internet speeds | |||
There was a problem hiding this comment.
Nit: We can either update this comment or just delete it outright now.
|
|
||
| #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] | ||
| fn new_with_fake_root(root: &[u8]) -> Result<Self, TlsError> { | ||
| fn new_with_fake_root(root: pki_types::CertificateDer<'static>) -> Result<Self, TlsError> { |
There was a problem hiding this comment.
Is there a reason this doesn't take a reference to CertificateDer if it only needs that? It would avoid the .clone() noise.
| impl CertificateStore { | ||
| #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] | ||
| fn new_with_fake_root(root: &[u8]) -> Result<Self, TlsError> { | ||
| fn new_with_fake_root(root: pki_types::CertificateDer<'static>) -> Result<Self, TlsError> { |
|
|
||
| /// Creates a Windows TLS certificate verifier that avoids using online revocation checking. | ||
| #[cfg_attr(docsrs, doc(cfg(all())))] | ||
| pub fn offline( |
There was a problem hiding this comment.
What do you think about making this switchable with a flag setter instead of a whole new constructor? I think its makes the API more confusing exposing the roots parameter outside of the new_with_extra_roots constructor, in addition to a whole new constructor option.
The downside of doing it that way would be that the set_offline_mode(&mut self, offline: bool) function would need to call CertCreateCertificateChainEngine again and replace the existing one. But everywhere else we could more lazily obtain the flag value and not need any more Win32 calls to accommodate.
| #[cfg(any(test, feature = "ffi-testing", feature = "dbg"))] | ||
| test_only_root_ca_override: None, | ||
| crypto_provider, | ||
| extra_roots: Some(CertEngine::new(roots, CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL)?), |
There was a problem hiding this comment.
Its probably best to keep the discussion in #238 (comment), but per my findings there setting this isn't actually enough to stop Windows from doing all networking during the verification calls FYI.
Does a bunch of cleanup/refactoring first, and tightens the timeout from 10 to 3s.
Fixes #237.