WebView: validate SSL_UNTRUSTED certs via OkHttp trust store#6838
WebView: validate SSL_UNTRUSTED certs via OkHttp trust store#6838cryptomilk wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR changes the app’s WebView SSL error handling so that when Chromium reports SSL_UNTRUSTED, the app can asynchronously “double-check” trust using the app’s OkHttp TLS configuration (which includes user-installed CAs via AndroidCAStore) before rejecting the load. This is intended to unblock connecting to Home Assistant instances using private CAs that OkHttp trusts but WebView/Chromium rejects.
Changes:
- Add async
SSL_UNTRUSTEDvalidation flow toTLSWebViewClientvia an overridableisTlsTrusted(url)hook and a newonSslErrorRejected(...)callback. - Implement OkHttp-based trust validation in
HAWebViewClientand thread lifecycle scopes throughHAWebViewClientFactory.create(validationScope=...). - Add/adjust unit tests for SSL error handling and factory signature changes.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| app/src/main/kotlin/io/homeassistant/companion/android/util/TLSWebViewClient.kt | Adds async handling for SSL_UNTRUSTED and introduces isTlsTrusted/onSslErrorRejected hooks |
| app/src/main/kotlin/io/homeassistant/companion/android/util/HAWebViewClient.kt | Implements OkHttp-based TLS trust validation and updates factory to inject OkHttpClient + validation scope |
| app/src/main/kotlin/io/homeassistant/companion/android/webview/WebViewActivity.kt | Switches legacy activity to use onSslErrorRejected hook and passes a coroutine scope into TLSWebViewClient |
| app/src/main/kotlin/io/homeassistant/companion/android/onboarding/connection/ConnectionViewModel.kt | Passes viewModelScope into factory for TLS validation lifecycle |
| app/src/main/kotlin/io/homeassistant/companion/android/frontend/FrontendViewModel.kt | Passes viewModelScope into factory for TLS validation lifecycle |
| app/src/test/kotlin/io/homeassistant/companion/android/util/TLSWebViewClientSslErrorTest.kt | New unit tests for async SSL_UNTRUSTED validation behavior in base client |
| app/src/test/kotlin/io/homeassistant/companion/android/util/HAWebViewClientTest.kt | Updates SSL tests for new behavior and adds coverage for async acceptance path |
| app/src/test/kotlin/io/homeassistant/companion/android/onboarding/connection/ConnectionViewModelTest.kt | Updates factory mocking for new validationScope parameter |
| app/src/test/kotlin/io/homeassistant/companion/android/frontend/FrontendViewModelTest.kt | Updates factory mocking and argument indexes for new validationScope parameter |
|
@TimoPtr thank you very much for the review! I will look into them. |
c34762a to
5763f9f
Compare
|
Tests passed locally. |
|
Actually wait, I also tested on my old Zenfone 10 and it fails there. Investigating ... |
|
The WebView auth page loads fine (you can see and approve the login), but the subsequent OkHttp POST to /auth/token in NameYourDeviceViewModel.onSaveClick() fails with "Trust anchor not found." I don't understand why. |
|
Ok, also works on the Zenfone 10. I guess it was some caching issue. I reinstalled and the setup worked just fine now. I think we are good to go. |
Which APIs did you test in the end? Could you give us reproduction steps for us to test too? |
Fairphone 6 with e/OS/: API level 35 |
|
I'm using https://smallstep.com/docs/step-ca/ for my CA Authority. I have a certificate for my home server which has Subject Alternative Names (SAN) for all the domain on it including home assistant. You can also user OpenSSL as an alternative https://openssl-ca.readthedocs.io/en/latest/introduction.html I have nginx as a proxy which does TLS and proxies to HA. It looks like this: I downloaded the root certificate and installed it in Android. Settings -> Security & Privacy -> Encryption & Credentials -> Install a certificate -> CA Certificate Once it is installed the best is to reboot the phone. Then you can open HA Companion and enter the address manually to point to https://homeassistant.example.local With the patch the certificate + intermediate chain will be checked and if valid the connection is accepted. |
When connecting to a Home Assistant instance using a private CA, the WebView's Chromium TLS stack reports SSL_UNTRUSTED (error code 3) even though network_security_config.xml includes user CAs. The app's OkHttpClient correctly trusts user-installed CAs via AndroidCAStore, so we use it as a second opinion before rejecting the connection. Observed log: onReceivedSslError: primary error: 3 certificate: Issued to: CN=antares.milli.ways; Issued by: CN=milly.ways CA Intermediate CA,O=milly.ways CA; on URL: https://homeassistant.milli.ways/auth/authorize?... TLSWebViewClient now overrides onReceivedSslError: for SSL_UNTRUSTED with a non-null URL it launches async validation via isTlsTrusted(); all other errors (IDMISMATCH, EXPIRED, etc.) are rejected immediately. Claude has been used to review the changes and it has written the tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
It's to setup mTLS no? I do use mTLS but I don't have the issue you are solving in this PR with my setup. Using Let's encrypt or Cloudflare. We need a way to replicate the issue you have to validate it. |
|
Your setup with Let's Encrypt or Cloudflare works out-of-the-box because those root CAs are already trusted by Android. This PR: The client (Android app) validates the server's certificate, which is signed by a private CA that is not in Android's public trust store. The fix uses OkHttp (which does honor user-installed CAs via AndroidCAStore) as a second validation path before rejecting the connection. |
Summary
When connecting to a Home Assistant instance using a private CA, the WebView's Chromium TLS stack reports SSL_UNTRUSTED (error code 3) even though network_security_config.xml includes user CAs. The app's OkHttpClient correctly trusts user-installed CAs via AndroidCAStore, so we use it as a second opinion before rejecting the connection.
Observed log:
onReceivedSslError: primary error: 3 certificate: Issued to: CN=antares.milli.ways;
Issued by: CN=milly.ways CA Intermediate CA,O=milly.ways CA;
on URL: https://homeassistant.milli.ways/auth/authorize?...
TLSWebViewClient now overrides onReceivedSslError: for SSL_UNTRUSTED with a non-null URL it launches async validation via isTlsTrusted(); all other errors (IDMISMATCH, EXPIRED, etc.) are rejected immediately.
Checklist
Any other notes
I don't have a build environment for this. I already looked a the code last month and there was a similar PR, but I already thought it isn't going to fix it. I tested 2026.5.3 today. I still can't login. So this should hopefully fix it.
As this is SSL cert validation code it needs probably a few more eyes to check if it is correct. I now certificate stuff but not Android.
Claude helped reviewing the code and wrote the tests.