android: Wear OS: TLS Client Authentication Support does not work

Home Assistant Android version: beta-2534-90a9a27e

Android version: Wear OS 2

Phone model: Fossil Gen 6

Home Assistant version: 2022.8.1

Last working Home Assistant release (if known):

Description of problem: Wear OS home assistant app does not ask which certificate to use like the regular android version does.

Traceback (if applicable, to get the logs you may refer to: https://companion.home-assistant.io/docs/troubleshooting/faqs/#android-crash-logs):

08-07 10:29:39.850  2250  4302 E WebSocketRepository: Websocket: onFailure
08-07 10:29:39.850  2250  4302 E WebSocketRepository: java.net.ProtocolException: Expected HTTP 101 response but was '400 Bad Request'
08-07 10:29:39.850  2250  4302 E WebSocketRepository: 	at okhttp3.internal.ws.RealWebSocket.checkUpgradeSuccess$okhttp(RealWebSocket.kt:224)
08-07 10:29:39.850  2250  4302 E WebSocketRepository: 	at okhttp3.internal.ws.RealWebSocket$connect$1.onResponse(RealWebSocket.kt:170)
08-07 10:29:39.850  2250  4302 E WebSocketRepository: 	at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
08-07 10:29:39.850  2250  4302 E WebSocketRepository: 	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
08-07 10:29:39.850  2250  4302 E WebSocketRepository: 	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
08-07 10:29:39.850  2250  4302 E WebSocketRepository: 	at java.lang.Thread.run(Thread.java:764)
08-07 10:29:39.852  2250  2250 E WebSocketRepository: Unable to authenticate

Screenshot of problem:

Additional information:

I am unsure if this is really a bug report or a feature request as I don’t know if the feature has been implemented in the Wear OS app yet. But unlike the regular android app, the Wear OS app does not seem to care about the client certificate I installed.

The certificate was installed on the watch using adb as follows:

$ adb push watchcert.p12 /data/local/tmp/
$ adb shell am start -n com.android.certinstaller/.CertInstallerMain -a android.intent.action.VIEW -t application/x-pkcs12 -d 'file:///data/local/tmp/watchcert.p12'

CertInstaller reports that installation was successful.

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 2
  • Comments: 15 (6 by maintainers)

Most upvoted comments

I can think of two possible directions:

1.) Use the phone to pick the client certificate file(s) and transfer them to the HA app’s private storage folder on the watch. This can raise some security concerns, as the user can forget about the secret files and leave them in a public folder on the phone in plaintext. Also, I don’t know how secure and trustworthy the connection between the phone and the watch is, but it should be secure, right? This isn’t a far-fetched idea and has the clear advantage of being pretty straightforward for the user.

2.) There’s another potential option that nobody really raised so far, and it’s a pretty secure option. However, I didn’t yet have the time to validate it, but let me throw it in… We could take advantage of the AndroidKeyStore’s hardware-backed crypto capabilities and generate an ECC or RSA keypair on the watch itself, and export a Certificate Signing Request (which doesn’t contain secret data), piped through the phone app. This CSR then should be signed with the proxy’s CA, and AFAIK then the signed result should be returned to the watch (through the phone app). Then the watch should use the key residing in the AndroidKeyStore to perform mTLS authentication. It’s definitely more complicated, but it’s the most secure flow I could think of, that’s an advantage. However, there’s also a disadvantage besides being complicated: when the user uninstalls the app, the mTLS key will be lost. Please note that it’s more like a theory, and I haven’t done this before, but I think it should work.

@m4k2k This issue is about TLS Client Certificate Authentication (CCA), not trusting additional CA certificates.

I am unsure if this is really a bug report or a feature request as I don’t know if the feature has been implemented in the Wear OS app yet.

No, this is currently not supported in the Wear app so it’s a feature request 🙂

If you have to use ADB commands it is already a workaround and not something supposed to be supported, and when doing this on an emulator I get a message ‘The certificate is not installed’ with an error in the logs:

2022-08-20 15:32:03.461 15281-15281/com.android.certinstaller W/CertInstaller: installCertificateToKeystore(): 
    android.content.ActivityNotFoundException: Unable to find explicit activity class {com.android.settings/com.android.settings.security.CredentialStorage}; have you declared this activity in your AndroidManifest.xml?
        at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:2065)
        at android.app.Instrumentation.execStartActivity(Instrumentation.java:1727)
        at android.app.Activity.startActivityForResult(Activity.java:5314)
        at android.app.Activity.startActivityForResult(Activity.java:5272)
        at com.android.certinstaller.CertInstaller.installCertificateToKeystore(CertInstaller.java:502)
        at com.android.certinstaller.CertInstaller.lambda$createNameCertificateDialog$11(CertInstaller.java:490)
        at com.android.certinstaller.CertInstaller.lambda$createNameCertificateDialog$11$CertInstaller(Unknown Source:0)
        at com.android.certinstaller.-$$Lambda$CertInstaller$TnrDgl-IUM_880fMZaJaG1VNFTQ.onClick(Unknown Source:4)
        at com.android.internal.app.AlertController$ButtonHandler.handleMessage(AlertController.java:174)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7651)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

and com.android.certinstaller D/CertInstaller: credential not saved, err: 0.

1.) Use the phone to pick the client certificate file(s) and transfer them to the HA app’s private storage folder on the watch. This can raise some security concerns, as the user can forget about the secret files and leave them in a public folder on the phone in plaintext.

As soon as you transfer the files to the HA app private storage, they could be loaded to the AndroidKeyStore immediately (as the code already does) and then they could be deleted. Then, as in my test number 6 above, no files are necessary for the watch app to work.

This doesn’t leave traces the user could forget.

Wow, that is A LOT of hoops to jump through and hardcoded stuff.

I just wanted to share the complete process, instead of just posting the last results. It could be interesting to others, or at least I would rate as interesting (as I wasn’t able to find this info easily on the web).

as watches don’t appear to have a user certificate store or UI for this

OK but this is a lack of Wear OS and we don’t have any control on that. Yeah we could open ticket with the Google support, but either they won’t read or do anything for that. They simply don’t care at all!

It should be relatively easy and secure, and preferably similar to the ‘main’ app

The main app relies on the OS UI, so we don’t have that possibility until Wear OS ‘evolves’ (basically never). That’s why I asked if it was possible for the Wear OS APK to rely on the phone connection in case of errors, but you pointed out that this requires a complete rewrite and hard work so neither this option is available.

[…] and not this much effort to use. I don’t see how this would work in production.

Maybe it’s possible to use the phone APK to just transfer the private keys to the watch APK, without relying on files… Similarly to setting up the template watchface. I don’t know what limitations are there, but the key data shouldn’t be so big.

Anyway, as I’ve written on paragraph 6, this “hard effort” is required only for the first installation. Then even deleting the keys or updating the app doesn’t touch the certificates.

Personally I use TLS Client Authentication to not expose the HA instance directly and think is a useful and safe feature… and losing the ability to update the data from the watch (when out of Wi-fi) is a big disadvantage for me. Maybe I’m one of the few who are interested… 😢

Wow, that is A LOT of hoops to jump through and hardcoded stuff. It was never a question of whether certificates would work, but more how you’d use them as watches don’t appear to have a user certificate store or UI for this. It should be relatively easy and secure, and preferably similar to the ‘main’ app. Integrating something into the app requires maintainable code and not this much effort to use. While this is great, I don’t see how this would work in production.

I think I have good news, as I think I’ve been able to use the TLS Client Certificate to establish a connection with my own server from Wear OS 3 (using a Galaxy Watch 4).

Unfortunately I’m just a hobbyist that doesn’t know Kotlin or Java: most of the tests I’ve done below have been made by trial & error and Googling. So I apologize in advance!

The PR you linked doesn’t include anything special for Wear OS.

Actually it seems that the code by @mircoboschi works, at least to load the certificate from a file. Let me explain below.

Preparation and patching

I downloaded the ZIP code of commit 52bb4c7 from his repository. I added some logging to understand if the code that loads the certificate from a file works.

MTLSHelper.kt

Added the imports:

import android.os.Environment
import android.util.Log

Changed the tryImportKeys function:

private fun tryImportKeys(context: Context) {
   Log.d("HAKeyTest", "tryImportKeys")
   //val baseDir = context.getExternalFilesDir(null)!! // I'm unable to write here with ADB, commented out
   // So I'm trying to use the Download public folder with the following:
   val baseDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
   Log.d("HAKeyTest", "basedir = " + baseDir.absolutePath) // Added this -> prints: /storage/emulated/0/Download
   val keyFile = File(baseDir, "$filename.key")
   val certFile = File(baseDir, "$filename.crt")
   Log.d("HAKeyTest", "Files handlers created") // Added this
 
   if (!keyFile.exists())return
   if (!certFile.exists())return
   Log.d("HAKeyTest", "Files exist") // Added this -> if printed, the files exist

and near the end:

      certFile.delete()
   }
   importMsg = context.getString(R.string.mtls_cert_importmsg_message_ok)
   Log.d("HAKeyTest", "Client key imported successfully") // Added this
} catch (ex: Exception) {
   Log.e("HAKeyTest", "Exception when loading keys", ex) // Added this
   importErrorMsg = ex.localizedMessage
} finally {
   certificateInputStream?.close()
}

HomeAssistantApis.kt

Added the imports:

import android.util.Log
import okhttp3.Request
import okhttp3.Response

Added some code to the configureOkHttpClient function to test if okHttp is really reading something useful from the server. I know, this is bad… but I’m not skilled enough edit the original code 😭

builder.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
 
MTLSHelper().setupOkHttpClientSSLSocketFactory(builder)

// Added the following block before 'return'
Thread(Runnable {
   Log.d("HAKeyTest", "Trying to build")
   var cli = builder.build()
   var rq = Request.Builder().url("https://homeassistant.mydomain.duckdns.org").build()
   var re: Response = cli.newCall(rq).execute()
   re.code?.let { Log.d("HAKeyTest", "Return code: $it") }
   re.body?.string()?.let { Log.d("HAKeyTest", it) }
   Log.d("HAKeyTest", "Build done")
}).start()
// End of edit

return builder

strings.xml

Changed the input_url_hint to avoid typing in the watch:

<string name="input_url_hint">https://homeassistant.mydomain.duckdns.org</string>

AndroidManifest.xml

Added the permission to read files in the Public folders (this has to be changed due to Android 11):

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

wear/build.gradle.kts

Lowered the API to 28 to allow using the File reading APIs (I don’t know how to convert with Scoped Storage and Android 11):

defaultConfig {
         applicationId = "io.homeassistant.companion.android"
         minSdk = 25
         targetSdk = 28 //30

Patch file for easier apply

Logging.patch

Testing

After doing the patching and compiling the APK with Android Studio (in debug mode) I connected to the watch using ADB and installed the app.

[PC] # adb connect 192.168.0.100:5555
[PC] # adb install -t wear-debug.apk

Then, before launching the app on the watch, I keep listening with adb logcat with this command:

[PC] # adb logcat AndroidRuntime:E io.homeassistant.companion.android.debug:E HAKeyTest:D *:S

1) Run the APK without any certificate

When running the APK without any certificate file in the public Download folder of the watch, I get an exception.

07-24 20:55:32.943  6986  6986 D HAKeyTest: tryImportKeys
07-24 20:55:32.947  6986  6986 D HAKeyTest: basedir = /storage/emulated/0/Download
07-24 20:55:32.948  6986  6986 D HAKeyTest: Files handlers created
07-24 20:55:35.628  6986  7947 D HAKeyTest: Trying to build
07-24 20:55:35.653  6986  7948 D HAKeyTest: Trying to build
07-24 20:55:35.681  6986  7950 D HAKeyTest: Trying to build
07-24 20:55:35.713  6986  7954 D HAKeyTest: Trying to build
07-24 20:55:35.716  6986  7953 D HAKeyTest: Trying to build
07-24 20:55:35.769  6986  7951 D HAKeyTest: Trying to build
--------- beginning of crash
07-24 20:55:38.507  6986  7954 E AndroidRuntime: FATAL EXCEPTION: Thread-7
07-24 20:55:38.507  6986  7954 E AndroidRuntime: Process: io.homeassistant.companion.android.debug, PID: 6986
07-24 20:55:38.507  6986  7954 E AndroidRuntime: javax.net.ssl.SSLHandshakeException

2) Extract the certificate files from PKCS12

I use a PFX certificate file (mycert.pfx) to connect to my nginx instance that exposes Home Assistant. So I first had to export the .KEY and .CER files from the .PFX file with:

[PC] # openssl pkcs12 -in mycert.pfx -nocerts -nodes -out tls_client.key
Enter Import Password:
MAC verified OK

[PC] # openssl pkcs12 -in mycert.pfx -clcerts -nokeys -out tls_client.crt
Enter Import Password:
MAC verified OK

As I didn’t have a password, I just pressed the <kbd>Enter</kbd> key. Then I copied the files to the watch using ADB:

[PC] # adb push tls_client.key /storage/emulated/0/Download/
[PC] # adb push tls_client.crt /storage/emulated/0/Download/
[PC] # adb shell ls /storage/emulated/0/Download/
tls_client.crt  tls_client.key

Then I run the app on the watch again, always by listening with adb logcat and this time:

07-24 20:58:30.383  6987  6987 D HAKeyTest: tryImportKeys
07-24 20:58:30.386  6987  6987 D HAKeyTest: basedir = /storage/emulated/0/Download
07-24 20:58:30.387  6987  6987 D HAKeyTest: Files handlers created
07-24 20:58:30.389  6987  6987 D HAKeyTest: Files exist
07-24 20:58:30.418  6987  6987 E HAKeyTest: Exception when loading keys
07-24 20:58:30.418  6987  6987 E HAKeyTest: java.io.FileNotFoundException: /storage/emulated/0/Download/tls_client.key: open failed: EACCES (Permission denied)

3) Enabling permissions

The previous try failed due to permissions, so I went into the watch Settings > Applications > Permissions > Home Assistant > File and multimedia content and chose Allow.

App permissions

After doing that, a new try:

07-24 21:13:17.059  9938  9938 D HAKeyTest: tryImportKeys
07-24 21:13:17.063  9938  9938 D HAKeyTest: basedir = /storage/emulated/0/Download
07-24 21:13:17.063  9938  9938 D HAKeyTest: Files handlers created
07-24 21:13:17.065  9938  9938 D HAKeyTest: Files exist
07-24 21:13:17.144  9938  9938 E HAKeyTest: Exception when loading keys
07-24 21:13:17.144  9938  9938 E HAKeyTest: java.security.spec.InvalidKeySpecException: com.android.org.conscrypt.OpenSSLX509CertificateFactory$ParsingException: Error parsing private key

New error, seems it doesn’t like the format of the .KEY file.

4) Fixing the KEY file

If I understand the code well, here it expects the Base64 only data. But my .KEY file was like so:

Bag Attributes
    localKeyID: B3 2C 79 AE C2 CB AC EE B8 FB 27 57 88 D6 5D 13 35 67 CB 06 
Key Attributes: <No Attributes>
-----BEGIN PRIVATE KEY-----
NQIJrwIDADANBgkqhkiG9w0BAQECAASCCS0wggkpAgEAAoICAQDTintudlsJHmnI
... cut ...

So I removed the first 3 lines from the .KEY file having it start with the -----BEGIN PRIVATE KEY----- text, like so:

-----BEGIN PRIVATE KEY-----
NQIJrwIDADANBgkqhkiG9w0BAQECAASCCS0wggkpAgEAAoICAQDTintudlsJHmnI
... cut ...

Copied the file again:

[PC] # adb push tls_client.key /storage/emulated/0/Download/

and new try:

07-24 21:16:52.709 10830 10830 D HAKeyTest: tryImportKeys
07-24 21:16:52.712 10830 10830 D HAKeyTest: basedir = /storage/emulated/0/Download
07-24 21:16:52.712 10830 10830 D HAKeyTest: Files handlers created
07-24 21:16:52.715 10830 10830 D HAKeyTest: Files exist
07-24 21:16:52.785 10830 10830 E HAKeyTest: Exception when loading keys
07-24 21:16:52.785 10830 10830 E HAKeyTest: java.security.cert.CertificateException: com.android.org.conscrypt.OpenSSLX509CertificateFactory$ParsingException: com.android.org.conscrypt.OpenSSLX509CertificateFactory$ParsingException: java.lang.RuntimeException: error:0c0000be:ASN.1 encoding routines:OPENSSL_internal:WRONG_TAG

5) Fixing the CER file

According to this comment, that exception is raised when the format of the private key is not DER. So I exported the key again, using the correct format and pushed it again to the watch:

[PC] # openssl pkcs12 -in mycert.pfx -clcerts -nokeys -out tls_client.pem
Enter Import Password:
MAC verified OK

[PC] # openssl x509 -outform der -in tls_client.pem -out tls_client.crt

[PC] # adb push tls_client.crt /storage/emulated/0/Download/

New try and…

07-24 21:22:02.915 11408 11408 D HAKeyTest: tryImportKeys
07-24 21:22:02.919 11408 11408 D HAKeyTest: basedir = /storage/emulated/0/Download
07-24 21:22:02.920 11408 11408 D HAKeyTest: Files handlers created
07-24 21:22:02.922 11408 11408 D HAKeyTest: Files exist
07-24 21:22:03.430 11408 11408 D HAKeyTest: Client key imported successfully
07-24 21:22:07.068 11408 11543 D HAKeyTest: Trying to build
07-24 21:22:07.073 11408 11542 D HAKeyTest: Trying to build
07-24 21:22:07.107 11408 11544 D HAKeyTest: Trying to build
07-24 21:22:07.126 11408 11547 D HAKeyTest: Trying to build
07-24 21:22:07.169 11408 11549 D HAKeyTest: Trying to build
07-24 21:22:07.188 11408 11548 D HAKeyTest: Trying to build
07-24 21:22:07.192 11408 11544 D HAKeyTest: Return code: 200
07-24 21:22:07.198 11408 11547 D HAKeyTest: Return code: 200
07-24 21:22:07.204 11408 11543 D HAKeyTest: Return code: 200
07-24 21:22:07.208 11408 11549 D HAKeyTest: Return code: 200
07-24 21:22:07.215 11408 11548 D HAKeyTest: Return code: 200
07-24 21:22:12.230 11408 11549 D HAKeyTest: <!DOCTYPE html><html><head><link rel="modulepreload" href="/frontend_latest/core.f2e094cd.js" crossorigin="use-credentials"><link rel="modulepreload" href="/frontend_latest/app.933bd729.js" crossorigin="use-credentials"><meta charset="utf-8"><link rel="manifest" href="/manifest.json" crossorigin="use-credentials"><link rel="icon" href="/static/icons/favicon.ico"><meta name="viewport" content="width=device-width,user-scalable=no,viewport-fit=cover,initial-scale=1"><style>body{font-family:Roboto,sans-serif;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-weight:400;margin:0;padding:0;height:100%}</style><title>Home Assistant</title><link rel="mask-icon" href="/static/icons/mask-icon.svg" color="#03a9f4"><link rel="apple-touch-icon" sizes="180x180" href="/static/icons/favicon-apple-180x180.png"><meta name="apple-itunes-app" content="app-id=1099568401"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="Home Assistant"><meta name="msapplication-square70x70logo" content="/static/icons/tile-win-70x70.png"><meta name="msapplication-square150x150logo" content="/static/icons/tile-win-150x150.png"><meta name="msapplication-wide310x150logo" content="/static/icons/tile-win-310x150.png"><meta name="msapplication-square310x310logo" content="/static/icons/tile-win-310x310.png"><meta name="msapplication-TileColor" content="#03a9f4ff"><meta name="mobile-web-app-capable" content="yes"><meta name="referrer" content="same-origin"><meta name="theme-color" content="#03A9F4"><meta name="color-scheme" content="dark light"><style>html{background-color:var(--primary-background-color,#fafafa);color:var(--primary-text-color,#212121)}@media (prefers-color-scheme:dark){html{background-color:var(--primary-background-color,#111);color:var(--primary-text-color,#e1e1e1)}}body{font-family:Roboto,Noto,sans-serif;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-weight:400;height:100vh;margin:0;padding:0}#ha-launch-screen{height:100%;display:flex;flex-direction:column;justify-content:center;align-items:center}#ha-launch-screen svg{width:170px;flex-shrink:0}#ha-launch-screen .ha-launch-screen-spacer{flex:1}</style></head><body><div id="ha-launch-screen"><div class="ha-launch-screen-spacer"></div><svg version="1.1" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"><rect fill="#41bdf5" width="200" height="200" ry="16.4"/><path fill="#fff" d="m38.416 165.29v-53.456h-13.901a3.7332 3.7332 0 0 1-2.662-6.3504l74.804-76.084c1.8068-1.8376 4.7612-1.8628 6.5992-0.056l0.048 0.048 39.04 39.518v-7.3188a3.1112 3.1112 0 0 1 3.1112-3.1112h12.964a3.1112 3.1112 0 0 1 3.1116 3.1112v26.855l16.627 17.047a3.7332 3.7332 0 0 1-2.6728 6.34h-13.954v53.456a3.1112 3.1112 0 0 1-3.1112 3.1112h-116.89a3.1112 3.1112 0 0 1-3.1112-3.1112zm82.556-65.304a6.0116 6.0116 0 0 0 0.584-2.5944c0-3.3232-2.684-6.0172-5.9956-6.0172-3.3112 0-5.9956 2.694-5.9956 6.0172s2.6844 6.0176 5.996 6.0176c0.9256 0 1.802-0.2108 2.5848-0.5868l8.6072 8.6384v8.3672l-10.792 10.831v-7.936a6.0184 6.0184 0 0 0 3.9972-5.6748c0-3.3232-2.6844-6.0176-5.996-6.0176-3.3112 0-5.996 2.6944-5.996 6.0176 0 2.62 1.6688 4.8488 3.9976 5.6748v11.947l-9.9932 10.029v-58.912l8.2076-8.2368a5.9544 5.9544 0 0 0 2.5848 0.5864c3.3116 0 5.996-2.694 5.996-6.0176 0-3.3232-2.6844-6.0172-5.996-6.0172-3.3112 0-5.9956 2.694-5.9956 6.0172 0 0.9292 0.2096 1.8088 0.584 2.5944l-7.3792 7.406-7.3796-7.406a6.0116 6.0116 0 0 0 0.584-2.5944c0-3.3232-2.684-6.0172-5.9956-6.0172-3.3112 0-5.9956 2.694-5.9956 6.0172 0 3.3236 2.6844 6.0176 5.996 6.0176 0.9256 0 1.802-0.2108 2.5848-0.5864l8.2072 8.2368v42.064l-14.39-14.442v-11.546a6.0184 6.0184 0 0 0 3.9972-5.6748c0-3.3236-2.6844-6.0176-5.996-6.0176-3.3112 0-5.996 2.694-5.996 6.0176 0 2.62 1.6688 4.8488 3.9976 5.6748v7.5348l-11.192-11.232v-11.145a6.0184 6.0184 0 0 0 3.9972-5.6748c0-3.3232-2.6844-6.0176-5.996-6.0176-3.3112 0-5.996 2.6944-5.996 6.0176 0 2.62 1.6688 4.8488 3.9

Whoo hoo! Isn’t that the HTML code of the onboarding page (with login) of Home Assistant? I’m pretty sure it is! So it loaded the TLS Client certificate!

6) Trying to delete the key files

Then I terminated the app with the Galaxy Watch task switcher app and removed the certificate files from the Download folder (actually I moved them in the old subfolder):

[PC] # adb shell ls /storage/emulated/0/Download/
old

And I tried relaunching the app to see if it really stores the keys into the AndroidKeyStore (like the code seems to do).

07-24 21:25:21.110 11247 11247 D HAKeyTest: tryImportKeys
07-24 21:25:21.112 11247 11247 D HAKeyTest: basedir = /storage/emulated/0/Download
07-24 21:25:21.113 11247 11247 D HAKeyTest: Files handlers created
07-24 21:25:23.668 11247 12324 D HAKeyTest: Trying to build
07-24 21:25:23.724 11247 12328 D HAKeyTest: Trying to build
07-24 21:25:23.729 11247 12329 D HAKeyTest: Trying to build
07-24 21:25:23.759 11247 12330 D HAKeyTest: Trying to build
07-24 21:25:23.766 11247 12331 D HAKeyTest: Trying to build
07-24 21:25:23.790 11247 12325 D HAKeyTest: Trying to build
07-24 21:25:23.810 11247 12328 D HAKeyTest: Return code: 200
07-24 21:25:23.816 11247 12330 D HAKeyTest: Return code: 200
07-24 21:25:23.822 11247 12329 D HAKeyTest: Return code: 200
07-24 21:25:23.826 11247 12331 D HAKeyTest: Return code: 200
07-24 21:25:23.829 11247 12325 D HAKeyTest: Return code: 200
07-24 21:25:28.835 11247 12329 D HAKeyTest: <!DOCTYPE html><html><head><link rel="modulepreload" href="/frontend_latest/core.f2e094cd.js" crossorigin="use-credentials"><link rel="modulepreload" href="/frontend_latest/app.933bd729.js" crossorigin="use-credentials"><meta charset="utf-8"><link rel="manifest" href="/manifest.json" crossorigin="use-credentials"><link rel="icon" href="/static/icons/favicon.ico"><meta name="viewport" content="width=device-width,user-scalable=no,viewport-fit=cover,initial-scale=1"><style>body{font-family:Roboto,sans-serif;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-weight:400;margin:0;padding:0;height:100%}</style><title>Home Assistant</title><link rel="mask-icon" href="/static/icons/mask-icon.svg" color="#03a9f4"><link rel="apple-touch-icon" sizes="180x180" href="/static/icons/favicon-apple-180x180.png"><meta name="apple-itunes-app" content="app-id=1099568401"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="default">

As you can see, it seems it does. The log in fact is missing the:

HAKeyTest: Files exist
HAKeyTest: Client key imported successfully

lines but it still loads the page content (and returns the status code 200).

7) Trying to load a wrong certificate

As a last test, I wanted to try a wrong certificate (actually an expired one). So I uninstalled the app (to clear the keystore), then I reinstalled the APK again, applied the permissions to read files and pushed a formally valid but expired tls_client.key and tls_client.crt files. Here the results:

07-24 21:47:00.994 13901 13901 D HAKeyTest: tryImportKeys
07-24 21:47:00.997 13901 13901 D HAKeyTest: basedir = /storage/emulated/0/Download
07-24 21:47:00.997 13901 13901 D HAKeyTest: Files handlers created
07-24 21:47:01.000 13901 13901 D HAKeyTest: Files exist
07-24 21:47:01.220 13901 13901 D HAKeyTest: Client key imported successfully
07-24 21:47:03.777 13901 15216 D HAKeyTest: Trying to build
07-24 21:47:03.806 13901 15217 D HAKeyTest: Trying to build
07-24 21:47:03.851 13901 15221 D HAKeyTest: Trying to build
07-24 21:47:03.859 13901 15220 D HAKeyTest: Trying to build
07-24 21:47:03.871 13901 15222 D HAKeyTest: Trying to build
07-24 21:47:03.900 13901 15223 D HAKeyTest: Trying to build
07-24 21:47:08.872 13901 15217 D HAKeyTest: Return code: 400
07-24 21:47:08.880 13901 15216 D HAKeyTest: Return code: 400
07-24 21:47:08.911 13901 15216 D HAKeyTest: <html>
07-24 21:47:08.911 13901 15216 D HAKeyTest: <head><title>400 The SSL certificate error</title></head>
07-24 21:47:08.911 13901 15216 D HAKeyTest: <body>
07-24 21:47:08.911 13901 15216 D HAKeyTest: <center><h1>400 Bad Request</h1></center>
07-24 21:47:08.911 13901 15216 D HAKeyTest: <center>The SSL certificate error</center>
07-24 21:47:08.911 13901 15216 D HAKeyTest: <hr><center>openresty</center>
07-24 21:47:08.911 13901 15216 D HAKeyTest: </body>

The certificate is loaded from file but I get a 400 status code, i.e. a “correct” Bad Request (the same that the browser on my PC shows if I use that certificate)!

Final thoughts

So, to me, it seems that the code works. It just have to be updated to have a way to load the certificate from the 2 files (using a file picker or something like that supported on Wear OS) due to the Android API 30 restrictions.

@jpelgrom what do you think about that? Could it be feasible? Hope this helps! 🤞

Hello @virtualdj,

I think for WEAR OS 2 it should be possible to sideload CA files using adb (source). I’m afraid in WEAR OS 3 it is currently not possible (at least for unrooted watches) to sideload CA files (source).

best regards Markus