Post

Android Security Internals: Chapter 6 - Network Security and PKI

A deep technical exploration of Chapter 6 of Nikolay Elenkov's 'Android Security Internals', dissecting Android's TLS/SSL stack, TrustManager validation, system CA stores, certificate pinning, and MITM interception vectors with Frida bypass scripts.

Hello everyone,

Welcome back to our book summary series covering “Android Security Internals” by Nikolay Elenkov, expanded with network interception tradecraft and dynamic instrumentation methods from the OWASP Mobile Application Security Testing Guide (MASTG).

In Chapter 5, we examined cryptographic providers and PRNG entropy architecture. In this installment, we explore Chapter 6: Network Security and PKI, dissecting how Android protects data-in-transit across untrusted wireless networks, validates Public Key Infrastructure (PKI) certificate chains, manages root Certificate Authorities (CAs), and enforces transport layer security.

As mobile security engineers and penetration testers, intercepting network traffic via Man-In-The-Middle (MITM) proxies is one of our most fundamental workflows. In this chapter, we explore the deep internal mechanics of Android’s TLS stack, understand the difference between system and user CA stores, analyze flawed custom TrustManager implementations, deconstruct certificate pinning defenses, and deploy advanced Frida scripts to bypass pinning across Java, OkHttp, and Flutter native BoringSSL runtimes.

Android Security Internals Chapter 6


Series Navigation

Previous ChapterCurrent ChapterNext Chapter
Chapter 5: Cryptographic ProvidersChapter 6: Network Security and PKIChapter 7: Credential Storage

Key Takeaways

  • The Android TLS Stack: Android transitioned from the legacy Apache HttpClient to HttpURLConnection powered by Conscrypt/OpenSSL, providing modern TLS protocol support, SNI (Server Name Indication), and optimized session resumption.
  • The Certificate Validation Chain: TLS validation is executed by TrustManagerImpl. The engine validates the remote server certificate against local trusted root CAs, checks validity date windows, parses certificate revocation lists (CRLs/OCSP), and verifies hostname matching.
  • The System CA Store: System-trusted CAs reside in the read-only partition at /system/etc/security/cacerts/. Files are named according to a cryptographic hash of the Subject DN ([hash].0).
  • The Android 7.0+ Trust Boundary: Starting in Android 7.0 (Nougat, API 24), user-installed certificates (located in /data/misc/keychain/cacerts-added/) are ignored by applications by default, requiring custom network_security_config.xml configurations or root privileges to intercept.
  • Certificate Pinning & Runtime Hooking: Certificate and public key pinning prevent CA compromise from enabling MITM attacks. However, pinning can be bypassed dynamically in memory using Frida or Xposed by hooking validation methods in TrustManagerImpl or binary-patching native BoringSSL libraries in Flutter apps.

1. The Android TLS/SSL Stack

Historically, Android provided two HTTP client implementations:

  1. Apache HttpClient: Bundled in Android 1.0, but deprecated in Android 5.1 and completely removed from the SDK in Android 6.0 due to high technical debt and maintenance overhead.
  2. HttpURLConnection: The official Java networking standard, backed by Google’s Conscrypt (BoringSSL JNI wrapper).

Android Network PKI & Certificate Validation Hierarchy

The TLS Handshake Flow

When an application opens an HTTPS connection:

1
2
3
4
5
6
7
8
9
10
11
12
App -> HttpsURLConnection -> Conscrypt Engine -> Native libssl.so
                                                      |
                                    [TLS Client Hello + SNI]
                                                      |
                                    <-- [Server Hello + Cert Chain]
                                                      |
                             Conscrypt calls TrustManagerImpl.verifyChain()
                                                      |
                                    +---> Check System CA Store
                                    +---> Check Validity Dates
                                    +---> Verify Hostname (CN / SAN)
                                    +---> Check Pinning Rules

2. Certificate Validation: TrustManagerImpl

In Java and Android, certificate validation is delegated to implementations of the X509TrustManager interface. In Android, the core implementation is com.android.org.conscrypt.TrustManagerImpl.

The Validation Algorithm

When a remote TLS server presents its X.509 certificate chain:

  1. Chain Parsing: TrustManagerImpl parses the server certificate (leaf) and intermediate certificates into an ordered array.
  2. Anchor Resolution: The validator searches local root CA stores to find an authority certificate whose Subject DN and public key match the issuer of the top intermediate certificate.
  3. Cryptographic Signature Verification: It traverses the chain from root to leaf, cryptographically verifying each certificate’s digital signature using the public key of the issuing certificate above it.
  4. Temporal Validity Checks: Verifies that the current system time falls between Not Before and Not After timestamps for every certificate in the chain.
  5. Hostname Verification: Validates that the hostname in the requested URL matches either the Common Name (CN) or a Subject Alternative Name (SAN) extension inside the leaf certificate.

3. The Android CA Certificate Store Architecture

Android segregates trusted certificates into two physically and logically distinct filesystem repositories:

1. The System CA Store (/system/etc/security/cacerts/)

  • Location: Mounted on the read-only /system partition.
  • Permissions: Owned by root:root with permissions 0644.
  • Trust Scope: Trusted by the operating system, system daemons, Google Play services, and all third-party applications.
  • Contents: Over 130 globally recognized commercial Certificate Authorities (e.g., DigiCert, Let’s Encrypt, Sectigo).

Installing a Custom System CA via OpenSSL

For penetration testers on a rooted device or emulator:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. Export Burp Suite / mitmproxy certificate in DER or PEM format
openssl x509 -inform DER -in burp.der -out burp.pem

# 2. Compute legacy subject hash
HASH=$(openssl x509 -inform PEM -subject_hash_old -in burp.pem | head -n 1)

# 3. Rename certificate file
mv burp.pem ${HASH}.0

# 4. Push to device and remount /system as read-write
adb root
adb remount
adb push ${HASH}.0 /system/etc/security/cacerts/
adb shell chmod 644 /system/etc/security/cacerts/${HASH}.0

2. The User CA Store (/data/misc/keychain/cacerts-added/)

  • Location: Stored in the writable user data partition.
  • Creation: Created when a user installs a certificate via Settings -> Security -> Install from storage (requires setting a secure lockscreen PIN).
  • Isolation: In Android 7.0+ (API 24+), user-added certificates are strictly confined to user-facing applications that explicitly opt in.

4. The Android 7.0+ Trust Boundary & Network Security Config

Prior to Android 7.0, installing a proxy CA into the User CA store was sufficient to intercept HTTPS traffic across virtually all installed applications.

In Android 7.0, Google altered the default trust configuration:

1
2
3
4
5
6
7
8
9
<!-- Default Network Security Configuration in Android 7.0+ -->
<network-security-config>
    <base-config>
        <trust-anchors>
            <!-- Third-party apps ONLY trust the system store by default! -->
            <certificates src="system" />
        </trust-anchors>
    </base-config>
</network-security-config>

Opting In via network_security_config.xml

To permit debugging proxies during development, developers must explicitly add a Network Security Configuration file:

1
2
3
4
5
6
7
8
9
10
<!-- res/xml/network_security_config.xml -->
<network-security-config>
    <debug-overrides>
        <trust-anchors>
            <!-- Allow user-installed proxy CAs ONLY in debug builds -->
            <certificates src="user" />
            <certificates src="system" />
        </trust-anchors>
    </debug-overrides>
</network-security-config>

5. Insecure Custom TrustManager Vulnerabilities

Mobile developers frequently encounter TLS handshake errors during development when testing against internal staging servers with self-signed certificates. A tragically common anti-pattern is replacing Android’s default TrustManager with a “Trust-All” dummy implementation:

The “Trust-All” Anti-Pattern

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// DANGEROUS SECURITY FLAW: Bypasses all TLS validation!
TrustManager[] trustAllCerts = new TrustManager[] {
    new X509TrustManager() {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) {}

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) {
            // EMPTY BODY: Trusts ANY certificate, expired, forged, or self-signed!
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    }
};

SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());

6. Advanced Pinning Bypasses via Frida

High-security applications implement certificate pinning using OkHttp, TrustManagerImpl, or native C/C++ libraries.

Universal Frida Unpinning Script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Universal Android TLS Pinning Bypass (OkHttp3 + TrustManagerImpl)
Java.perform(function() {
    console.log('[*] Initializing Dynamic TLS Unpinning Engine...');

    // 1. Bypass TrustManagerImpl
    try {
        var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl');
        TrustManagerImpl.verifyChain.implementation = function(untrustedChain, trustAnchorChain, host, clientAuth, ocspData, tlsSctData) {
            console.log('[+] Bypassed TrustManagerImpl.verifyChain for: ' + host);
            return untrustedChain;
        };
    } catch (e) {
        console.log('[-] Conscrypt TrustManagerImpl not found');
    }

    // 2. Bypass OkHttp3 CertificatePinner
    try {
        var CertificatePinner = Java.use('okhttp3.CertificatePinner');
        CertificatePinner.check.overload('java.lang.String', 'java.util.List').implementation = function(hostname, peerCertificates) {
            console.log('[+] Bypassed OkHttp3 CertificatePinner for: ' + hostname);
            return;
        };
    } catch (e) {
        console.log('[-] OkHttp3 CertificatePinner not found');
    }
});

Next in the Series: Chapter 7 Preview

In Chapter 7: Credential Storage, we will examine:

  • The architecture of the /system/bin/keystore native daemon and modern Rust-based keystore2.
  • Master key generation, PBKDF2 derivation, and lockscreen integration.
  • Hardware-backed encryption via Keymaster HAL, StrongBox, and ARM TrustZone (TEE).
  • The difference between the system KeyChain API and the private AndroidKeyStore provider.

References & Authoritative Reading

  1. Elenkov, Nikolay. Android Security Internals. No Starch Press, 2015. Chapter 6: “Network Security and PKI,” pp. 145–170.
  2. Android Developers - Network Security Configuration
  3. Android Open Source Project - Conscrypt Java TLS Provider
  4. OWASP Mobile Security Testing Guide - Testing Network Communication (MASTG-NETW-1 to MASTG-NETW-4)
  5. PortSwigger Research - Bypassing Android 7.0+ Network Security Config

Let’s Connect

Have you analyzed mobile TLS implementations or bypassed certificate pinning in engagements?

Sadaqah Jariyah & Knowledge Sharing | صدقة جارية وعلم يُنتفع به

This technical content and everything shared across this blog is dedicated with sincere intention to serve as enduring, beneficial knowledge (علم يُنتفع به) and an ongoing charity (صدقة جارية). If you find value in this research, please remember me and my family in your sincere prayers.

This post is licensed under CC BY 4.0 by the author.