httptoolkit / httptoolkit-server

The backend of HTTP Toolkit
https://httptoolkit.com
GNU Affero General Public License v3.0
460 stars 101 forks source link

Certificate injection failure in Android 14 #114

Open MHBdev2 opened 8 months ago

MHBdev2 commented 8 months ago

I'm using Android 14 and want to inject a custom CA certificate The device is rooted using magisk, so for a smooth operation that will be performed every restart, I put the following code in service.sh

API=`getprop ro.build.version.sdk`
if [ "$API" -ge 33 ]; then
while [ "$(getprop sys.boot_completed)" != 1 ];
do
   sleep 1
done
if [ -d "/apex/com.android.conscrypt/cacerts" ]; then
     cp /apex/com.android.conscrypt/cacerts/* /data/local/tmp/htk-ca-copy/
else
     cp /system/etc/security/cacerts/* /data/local/tmp/htk-ca-copy/
fi

# Create the in-memory mount on top of the system certs folder
su -mm -c mount -t tmpfs tmpfs /system/etc/security/cacerts

# Copy the existing certs back into the tmpfs mount, so we keep trusting them
mv /data/local/tmp/htk-ca-copy/* /system/etc/security/cacerts/

# Copy our new cert in, so we trust that too
cp /system/etc/cacustom/* /system/etc/security/cacerts/

# Update the perms & selinux context labels, so everything is as readable as before
chown root:root /system/etc/security/cacerts/*
chmod 644 /system/etc/security/cacerts/*
chcon u:object_r:system_file:s0 /system/etc/security/cacerts/*

echo 'System cacerts setup completed'

           # Create a separate temp directory, to hold the current certificates
             # Without this, when we add the mount we can't read the current certs anymore.

             # Deal with the APEX overrides in Android 14+, which need injecting into each namespace:
             if [ -d "/apex/com.android.conscrypt/cacerts" ]; then

                 # When the APEX manages cacerts, we need to mount them at that path too. We can't do
                 # this globally as APEX mounts are name-spaced per process, so we need to inject a
                 # bind mount for this directory into every mount namespace.

                 # First we get the Zygote process(es), which launch each app
                 ZYGOTE_PID=$(pidof zygote || true)
                 ZYGOTE64_PID=$(pidof zygote64 || true)
                 Z_PIDS="$ZYGOTE_PID $ZYGOTE64_PID"
                 # N.b. some devices appear to have both, some have >1 of each (!)

                 # Apps inherit the Zygote's mounts at startup, so we inject here to ensure everything is new
                 # started apps will see these certs straight away:
                 for Z_PID in $Z_PIDS; do
                     if [ -n "$Z_PID" ]; then
                         nsenter --mount=/proc/$Z_PID/ns/mnt -- \
                             /bin/mount --bind /system/etc/security/cacerts /apex/com.android.conscrypt/cacerts
                     fi
                 done

                 # Then we inject the mount into all already running apps, so they see these certs immediately.

                 # Get the PID of every process whose parent is one of the Zygotes:
                 APP_PIDS=$(
                     echo $Z_PIDS | \
                     xargs -n1 ps -o 'PID' -P | \
                     grep -v PID
                 )

                 # Inject into the mount namespace of each of those apps:
                 for PID in $APP_PIDS; do
                     nsenter --mount=/proc/$PID/ns/mnt -- \
                         /bin/mount --bind /system/etc/security/cacerts /apex/com.android.conscrypt/cacerts &
                 done
                 wait # Launched in parallel - wait for completion here

             fi

It really seems that the personalized certificates enter the system but after a few seconds the device crashes (restarts) This is the log at the moment of the crash

 !@*** FATAL EXCEPTION IN SYSTEM PROCESS: pool-246-thread-1
                                                                                                    java.lang.AssertionError
                                                                                                        at com.android.okhttp.OkHttpClient.getDefaultSSLSocketFactory(OkHttpClient.java:649)
                                                                                                        at com.android.okhttp.OkHttpClient.copyWithDefaults(OkHttpClient.java:605)
                                                                                                        at com.android.okhttp.OkUrlFactory.open(OkUrlFactory.java:63)
                                                                                                        at com.android.okhttp.OkUrlFactory.open(OkUrlFactory.java:58)
                                                                                                        at com.android.okhttp.HttpHandler.openConnection(HttpHandler.java:56)
                                                                                                        at java.net.URL.openConnection(URL.java:1006)
                                                                                                        at com.android.server.location.gnss.GnssPsdsDownloader.doDownload(GnssPsdsDownloader.java:165)
                                                                                                        at com.android.server.location.gnss.GnssPsdsDownloader.doDownloadWithTrafficAccounted(GnssPsdsDownloader.java:152)
                                                                                                        at com.android.server.location.gnss.GnssPsdsDownloader.downloadPsdsData(GnssPsdsDownloader.java:129)
                                                                                                        at com.android.server.location.gnss.GnssLocationProvider.lambda$handleDownloadPsdsData$6(GnssLocationProvider.java:909)
                                                                                                        at com.android.server.location.gnss.GnssLocationProvider.$r8$lambda$JL2e1UXaMzOyFO9jVFmVGlpGR4o(GnssLocationProvider.java:0)
                                                                                                        at com.android.server.location.gnss.GnssLocationProvider$$ExternalSyntheticLambda17.run(R8$$SyntheticClass:0)
MHBdev2 commented 8 months ago

More precisely Even if you use your application (http toolkit), and use full monitoring mode by injecting certificates into the system, When trying to connect to the network via wifi, a crash similar to the above occurs

 !@*** FATAL EXCEPTION IN SYSTEM PROCESS: Thread-48
                                                                                                    java.lang.AssertionError
                                                                                                        at com.android.okhttp.OkHttpClient.getDefaultSSLSocketFactory(OkHttpClient.java:649)
                                                                                                        at com.android.okhttp.OkHttpClient.copyWithDefaults(OkHttpClient.java:605)
                                                                                                        at com.android.okhttp.OkUrlFactory.open(OkUrlFactory.java:63)
                                                                                                        at com.android.okhttp.OkUrlFactories.open(OkUrlFactories.java:39)
                                                                                                        at com.android.okhttp.internalandroidapi.HttpURLConnectionFactory.internalOpenConnection(HttpURLConnectionFactory.java:154)
                                                                                                        at com.android.okhttp.internalandroidapi.HttpURLConnectionFactory.openConnection(HttpURLConnectionFactory.java:123)
                                                                                                        at libcore.net.http.HttpURLConnectionFactory.openConnection(HttpURLConnectionFactory.java:101)
                                                                                                        at android.net.Network.openConnection(Network.java:372)
                                                                                                        at android.net.Network.openConnection(Network.java:332)
                                                                                                        at com.samsung.android.server.wifi.routerinfo.WifiRouterInfoCollector.runHttpGet(WifiRouterInfoCollector.java:143)
                                                                                                        at com.samsung.android.server.wifi.routerinfo.WifiRouterInfoCollector.checkRouterInfoFromHttp(WifiRouterInfoCollector.java:50)
                                                                                                        at com.samsung.android.server.wifi.routerinfo.WifiRouterInfoCollector.-$$Nest$mcheckRouterInfoFromHttp(WifiRouterInfoCollector.java:1)
                                                                                                        at com.samsung.android.server.wifi.routerinfo.WifiRouterInfoCollector$ConnectedState.lambda$processMessage$0(WifiRouterInfoCollector.java:13)
                                                                                                        at com.samsung.android.server.wifi.routerinfo.WifiRouterInfoCollector$ConnectedState.$r8$lambda$MVjnii19NWrcUKHWZqtUZ7NOc2o(WifiRouterInfoCollector.java:1)
pimterry commented 8 months ago

The failing code is here: https://android.googlesource.com/platform/external/okhttp/+/refs/heads/main/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java#635

It's not clear why, but it seems like on your device this results in a setup where TLS becomes completely unavailable - and so any attempt to use it fails.

That error isn't really the problem. The issue here is why TLS has become unavailable. I suspect for some reason, the changes here are effectively hiding all certificates (or the directory entirely) from the TLS loading on your device, so TLS initialization fails completely.

This definitely isn't the normal behaviour of those hooks on an Android device, so there must be something unique to your setup.

Can you reboot the device, and then run (via ADB, as root):

then run HTTP Toolkit's ADB setup, and then run the same commands again?

You should see one new certificate appear (in both locations) but otherwise no changes - everything else should look exactly the same before & after, and the new certificate should exactly match all the other certs here.

If that doesn't come up with any clues, you're going to need to do some in-depth debugging. It would be useful to know the full details of the GeneralSecurityException that must be being thrown in the code linked above, and which that code just ignores (rethrowing an AssertionError with no more details instead). To find that, you might be able to attach a debugger to something somehow (not sure) or build a demo app that calls the same code but exposes the actual exception directly.

It would also be useful to have a really detailed understanding of your device setup, to be able to reproduce this or match it to other issues/solutions. Can you please share: