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:
Start emulator with -writable-system
flag: emulator -avd emulator_name -writable-system
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
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
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.
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?
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