Search code examples
javaandroidkotlinandroid-emulator

Android 14 (UpsideDownCake) doesn't see certificate installed in /system/etc/security/cacert


For our automated testing platform for Android emulator, we are able to push a Proxyman certificate to a /system/etc/security/cacerts with the tried and tested commands:

  1. Start emulator with -writable-system flag: emulator -avd emulator_name -writable-system

  2. Prepare Proxyman certificate:

    • openssl x509 -inform PEM -subject_hash_old -in proxyman-ssl-proxying-certificate.pem | head -1 - it returns a hash for the cert, eg. 30eb732c

    • save that to a file: cat proxyman-ssl-proxying-certificate.pem > 30eb732c.0

    • openssl x509 -inform PEM -text -in proxyman-ssl-proxying-certificate.pem -out /dev/null >> 30eb732c.0

  3. Push the created 30eb732c.0 to emulator:

    • adb root
    • adb remount
    • adb root
    • adb shell avbctl disable-verification
    • adb reboot
    • adb root
    • adb remount
    • adb push 30eb732c.0 /system/etc/security/cacerts
  4. I can verify that the file is there by listing all certs with adb shell ls /system/etc/security/cacert and seeing mine on the list.

With these, we are able to see the traffic in Proxyman on release builds of our apps, up to API 33, and I can see the certificate installed on system partition in the Emulator Settings/Security/Encryption & credentials/Trusted credentials.

enter image description here

With the same steps for Android Emulator UpsideDownCake, I can see that the cert file is indeed in /system/etc/security/cacert, but the UI doesn't show it, and the traffic also fails to be captured by Proxyman (getting SSL Handshake Failed).

The method for installing cert has been informed by many of these:

I feel like I might be missing something here, but also wondering what's the difference between API 33 and UpsideDownCake emulators. Has anyone been successful in installing a certificate in /system/etc/security/cacert on Android 14 (UpsideDownCake) emulator?


Solution

  • Android 14 now reads CA certs from within the Conscrypt library's APEX filesystem, at /apex/com.android.conscrypt/cacerts.

    That's an awkward problem for use cases like this, because that path is impossible to directly modify or remount. You can try all you like, but APEX modules are loaded using different mechanisms to the rest of the filesystem, and in general if you make simple changes via an ADB shell, apps on the device won't see them - they use isolated mount namespaces, so they'll continue using their original set of mounts independently.

    I've done some digging here though, and found some working solutions: you can bind mount an alternative directory into this location, and then use nsenter to duplicate that mount individually into each app's mount namespace, and into the Zygote process (which starts future apps, so that newly launched apps copy this by default).

    That's the essence of it. The full steps & background context are quite involved, but I've documented it and built a full script to automate this here: https://httptoolkit.com/blog/android-14-install-system-ca-certificate/

    You can see the resulting changes to automate that process in HTTP Toolkit here: https://github.com/httptoolkit/httptoolkit-server/commit/965fd8d9b287af0e4b305d828d5e8e1aa52dce36