rustls / rustls-platform-verifier

A certificate verification library for rustls that uses the operating system's verifier
Apache License 2.0
57 stars 18 forks source link

Self signed certificates with user-installed CA show as revoked on Android #69

Closed dani-garcia closed 1 week ago

dani-garcia commented 4 months ago

I’ve been testing the use of this library and while it’s been working great so far on Mac, iOS and Android for certificates generated from system-trusted CAs, I’ve found that Android can’t seem to deal with certificates signed by user-installed CAs, and always reports them as Revoked.

My tests have gone like this:

I've also tested other certificates signed by a local CA in a dev server, like the ones generated by mkcert, and I can reproduce the same problem there too.

I've also checked that the requests succeed if you make them with an Android native HTTP client (like Kotlins Ktor HTTP client), which suggests to me that the certificate is installed correctly at least.

Any ideas what could be causing this issue?

PS: I have made a small app to reproduce the issue if that helps, it's just using the ureq library with rustls-platform-verifier, and can be used from the included Android app and the CLI: https://github.com/dani-garcia/rpv-selfsigned

cpu commented 4 months ago

Hi @dani-garcia,

Thanks for opening an issue and linking to your reproducer. This is a curious situation and I don't have any immediate ideas.

Based on what you describe it sounds like X509TrustManager.checkServerTrusted is passing for the presented chain using the Android trust store, but CertPathValidator.validate is throwing an exception we map to the revoked state (possibly masking an underlying error of a different sort?).

I think reproducing this with a test run in the emulator would be a good next step. I think it's likely that either the revocation checking is expecting certain metadata that isn't present in the added trust anchor and is failing closed, or there is a separate validation error being presented incorrectly as a revocation error.

The main open question in my mind is the best way to programmatically inject a new trust anchor into the emulator's system trust bundle to make writing a test possible. How did you handle that in your rpv-selfsigned reproducer?

cpu commented 4 months ago

The main open question in my mind is the best way to programmatically inject a new trust anchor into the emulator's system trust bundle

Perhaps like this: https://docs.mitmproxy.org/stable/howto-install-system-trusted-ca-android/

cpu commented 4 months ago

Perhaps like this: https://docs.mitmproxy.org/stable/howto-install-system-trusted-ca-android/

Ah, I misread. These instructions inject a trust anchor as if it were system provided to explicitly side-step the usual opt-in that's required for user-installed trust anchors. I think to repro this we want to programmatically mount a user-installed trust anchor and then make sure the test application is opted in to the mechanism that allows its use, like you appear to have done in the repro app.

dani-garcia commented 4 months ago

I've done my testing on an Android 14 emulator, and haven't found a way to automate the user provided certificate installation, so I just copied the certificate over with ADB and then installed it myself and ran the repro manually:

This is how I copied and installed the cert

In that file there are also some of the attempts I tried to install the certificate in the user trust store, but I couldn't get it to work. I'll check if I can find another way to automate the process.

I've also updated the repro to remove the HTTP client and instead use rustls_platform_verifier::Verifier::new().verify_server_cert(...) directly in case it was causing problems or hiding the error but I still get the same result:

flisky commented 1 month ago

https://github.com/rustls/rustls-platform-verifier/blob/46b194b95ef761f6eafded1ff723e327aeb107a5/android/rustls-platform-verifier/src/main/java/org/rustls/platformverifier/CertificateVerifier.kt#L342-L346

In my case, e is java.security.cert.CertPathValidatorException: Certificate does not specify OCSP responder, so I vendor the CertificateVerifier.kt and disable OCSP validation when building debug app.

complexspaces commented 1 month ago

@flisky Thank you for that extra information!

@cpu I think that bringing back the isKnownRoot check that existed prior to https://github.com/rustls/rustls-platform-verifier/pull/40 might be the solution here. IMO, we shouldn't be attempting the revocation codepath for non-public certificate chains here since enterprises and other self-signed issuance cases aren't required to meet the same bar.

I suspect (but don't have the time to verify) that the reason it works fine on macOS is because it doesn't have the same very-strict revocation certificate extension data requirements that Android's implementation does.

cpu commented 1 month ago

@cpu I think that bringing back the isKnownRoot check that existed prior to https://github.com/rustls/rustls-platform-verifier/pull/40 might be the solution here.

Sounds reasonable to me. Do you think you'll have time to open a PR or should I look at doing that? I could probably find time closer to the end of the week.

complexspaces commented 1 month ago

I think that I can get a PR open for this 👍

complexspaces commented 3 weeks ago

If anyone has the time, I would appreciate a look and attempt to try running #108. It should resolve this issue from my testing.

dani-garcia commented 4 days ago

Sorry for the delay, I tested the changes in the PR and it also solves my issues, thank you!