SSL Pining

SSL Pinning in Android

> Correct technical name: Certificate Pinning. It is commonly called SSL pinning, although modern Android connections use TLS.

1. What is SSL Pinning?

Normally, Android trusts a server certificate when it is signed by any trusted Certificate Authority (CA).

Certificate pinning adds one more check:

> The server certificate or public key must match a value already stored inside the application.

flowchart TD
    A["Android App"] --> S["Server"]
    S --> C["Server sends certificate"]
    C --> V{"Normal TLS validation valid?"}
    V -->|No| R["Reject connection"]
    V -->|Yes| P{"Pin matches?"}
    P -->|Yes| E["Create secure connection"]
    P -->|No| R

Pinning protects highly sensitive connections against some compromised or incorrectly trusted CAs.

---

2. Normal HTTPS vs Certificate Pinning

Normal HTTPS

flowchart LR
    A["Application"] --> C["Certificate"]
    C --> CA["Trusted CA"]
    CA --> OK["Connection allowed"]

The application trusts a certificate signed by any CA trusted by the device.

HTTPS with pinning

flowchart LR
    A["Application"] --> C["Certificate"]
    C --> CA["Normal CA validation"]
    CA --> P["Public-key pin validation"]
    P --> OK["Connection allowed"]

Both validations must succeed:

1. Normal TLS certificate validation

2. Application pin validation

---

3. Why Do We Need Pinning?

Suppose an attacker tries a man-in-the-middle attack:

flowchart LR
    A["Application"] --> M["Attacker"]
    M --> S["Real server"]

If the attacker presents another certificate:

* Normal HTTPS checks may reject it.

* If a compromised or user-installed CA makes it otherwise trusted, a correctly configured pin can still reject it.

* The public key does not match the application’s stored pins.

Common use cases include:

* Banking applications

* Payment applications

* Healthcare applications

* Applications handling sensitive enterprise data

Pinning should be a risk-based decision because incorrect rotation can block every pinned API request.

---

4. What Can Be Pinned?

Pin typeMeaningMain problem
Whole certificatePin exact certificateBreaks when certificate is renewed
Public keyPin certificate’s public key hashSurvives renewal if the same key remains
Intermediate CATrust a specific intermediate authorityTrusts other certificates from that authority
Root CATrust a root authorityVery broad trust; usually not preferred

A common Android approach is to pin the SHA-256 hash of the certificate’s Subject Public Key Info (SPKI).

sha256/Base64EncodedPublicKeyHash

OkHttp’s CertificatePinner uses SPKI public-key hashes. OkHttp CertificatePinner

---

5. Pinning with OkHttp

val certificatePinner = CertificatePinner.Builder()
    .add(
        "api.example.com",
        "sha256/PRIMARY_PUBLIC_KEY_HASH="
    )
    .add(
        "api.example.com",
        "sha256/BACKUP_PUBLIC_KEY_HASH="
    )
    .build()

val okHttpClient = OkHttpClient.Builder()
    .certificatePinner(certificatePinner)
    .build()

Retrofit:

val retrofit = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .client(okHttpClient)
    .addConverterFactory(
        GsonConverterFactory.create()
    )
    .build()

Now every HTTPS request to api.example.com must pass both normal TLS verification and pin verification.

---

6. Multiple Domains

Add pins separately for each protected hostname:

val certificatePinner = CertificatePinner.Builder()
    .add(
        "api.example.com",
        "sha256/API_PRIMARY_PIN="
    )
    .add(
        "api.example.com",
        "sha256/API_BACKUP_PIN="
    )
    .add(
        "payment.example.com",
        "sha256/PAYMENT_PRIMARY_PIN="
    )
    .add(
        "payment.example.com",
        "sha256/PAYMENT_BACKUP_PIN="
    )
    .build()

A pin for one hostname does not automatically protect every other domain.

Wildcard hostname

.add(
    "*.example.com",
    "sha256/PUBLIC_KEY_HASH="
)

Use wildcards carefully. Confirm exactly which hosts and certificate chains are expected.

---

7. Primary and Backup Pins

Never depend on only one pin.

flowchart TD
    C["Server certificate chain"] --> P{"Matches any valid pin?"}
    P -->|Primary pin| A["Allow"]
    P -->|Backup pin| A
    P -->|No match| R["Reject"]

Example:

.add(
    "api.example.com",
    "sha256/CURRENT_KEY_HASH="
)
.add(
    "api.example.com",
    "sha256/NEXT_KEY_HASH="
)

The backup pin should belong to a separately controlled key that can be activated during certificate rotation or emergency recovery.

OWASP recommends using a pin set and planning backup/rotation carefully because a broken pin can create a complete service outage. OWASP Pinning Guidance

---

8. Certificate Rotation

Certificates expire and must be renewed. A safe rotation plan is essential.

Safe rotation process

flowchart TD
    A["App contains current and future pins"] --> B["Release app update"]
    B --> C["Wait for adoption"]
    C --> D["Server switches to new key"]
    D --> E["Both old and new apps continue working"]
    E --> F["Remove expired pin in a later release"]

Recommended process

1. Generate the future server key.

2. Add its public-key hash as a backup pin.

3. Release the application update.

4. Allow users time to update.

5. Rotate the server certificate/key.

6. Keep old and new pins during the transition.

7. Remove the old pin in a later application version.

Dangerous process

Server changes key first
        ↓
Installed apps contain only old pin
        ↓
All API calls fail
        ↓
Application cannot recover remotely

This is called bricking the network connection.

---

9. Finding the Public-Key Pin

OpenSSL can calculate an SPKI SHA-256 hash:

openssl s_client -connect api.example.com:443 \
  -servername api.example.com </dev/null 2>/dev/null \
  | openssl x509 -pubkey -noout \
  | openssl pkey -pubin -outform der \
  | openssl dgst -sha256 -binary \
  | openssl base64

Result format:

AbCdEf123...=

Use it as:

sha256/AbCdEf123...=

Important precautions:

* Run the command on a trusted network.

* Confirm the value through an approved independent source.

* Check the complete production certificate chain.

* Coordinate with the backend/security team.

* Never copy an unverified hash from a random failure message.

---

10. Network Security Configuration

Android also supports pinning through XML Network Security Configuration.

res/xml/network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>

    <domain-config cleartextTrafficPermitted="false">

        <domain includeSubdomains="true">
            api.example.com
        </domain>

        <pin-set expiration="2027-12-31">

            <pin digest="SHA-256">
                PRIMARY_PUBLIC_KEY_HASH
            </pin>

            <pin digest="SHA-256">
                BACKUP_PUBLIC_KEY_HASH
            </pin>

        </pin-set>

    </domain-config>

</network-security-config>

Register it in the manifest:

<application
    android:networkSecurityConfig=
        "@xml/network_security_config"
    android:usesCleartextTraffic="false">
</application>

OkHttp vs Network Security Config

OkHttp CertificatePinnerNetwork Security Config
Implemented in KotlinImplemented in XML
Applies to configured OkHttp clientApplies through Android network-security policy
Easy for Retrofit/OkHttpCentral Android configuration
Pin rotation requires app updatePin rotation also normally requires app update
Works at HTTP client levelWorks at platform trust configuration level

Choose one controlled approach based on project architecture and security policy. Avoid inconsistent duplicate configurations.

---

11. Internal Working

When the application sends an API request:

sequenceDiagram
    participant A as Android App
    participant S as Server
    participant TLS as TLS Validator
    participant P as Pin Validator

    A->>S: HTTPS connection request
    S-->>A: Certificate chain
    A->>TLS: Validate dates, hostname and CA chain
    TLS-->>A: Standard validation passed
    A->>P: Hash public keys in certificate chain
    P->>P: Compare with configured pin set
    P-->>A: Pin matched
    A->>S: Continue encrypted connection
If no configured pin matches, OkHttp throws an `SSLPeerUnverifiedException`.
try {
    api.getProducts()
} catch (exception: SSLPeerUnverifiedException) {
    // Report a secure connection failure
}

Do not silently bypass pinning after a failure.

---

12. Error Handling

A pinning failure should normally be treated as a secure-connection failure.

User-friendly message:

A secure connection could not be established.
Please update the application or try again later.

Internally record safe diagnostic information such as:

* App version

* Hostname

* Failure type

* Time

* Network category

* Certificate-chain metadata allowed by policy

Do not log:

* Access tokens

* Authorization headers

* Passwords

* OTPs

* Sensitive request/response bodies

---

13. Debug and Security Testing

Charles Proxy, Fiddler or corporate TLS inspection may fail against a pinned production configuration because they present a different certificate.

A controlled debug build can use different security behaviour when explicitly approved.

val clientBuilder = OkHttpClient.Builder()

if (!BuildConfig.DEBUG) {
    clientBuilder.certificatePinner(
        productionCertificatePinner
    )
}

Important:

* Do not accidentally disable pinning in release builds.

* Verify the release artifact.

* Keep debug trust configuration separate.

* Do not ship debugging proxy certificates.

* Test production pinning on a controlled device and network.

OWASP notes that separate debug and release variants can support security testing, provided the production build retains its protections. OWASP Mobile Security Testing

---

14. What Pinning Protects Against

Pinning can reduce risk from:

* Compromised CA

* Incorrectly issued certificate

* Malicious user-installed CA

* Some TLS interception proxies

* Some man-in-the-middle scenarios

---

15. What Pinning Does Not Protect Against

Pinning does not protect against:

* Compromised backend server

* Stolen server private key matching the pin

* Vulnerabilities in application logic

* Malware controlling the device

* Runtime hooking on a compromised/rooted device

* An attacker modifying the application and disabling the check

* Insecure token storage

* Sensitive-data logging

OWASP explicitly warns that pinning can be bypassed when the client device or application process is compromised. OWASP Certificate and Public-Key Pinning

Pinning is defence in depth, not complete security.

---

16. Common Mistakes

Mistake 1: Using only one pin

If that key changes, every network call fails.

Use a primary and backup pin.

Mistake 2: Pinning an exact certificate without planning renewal

A renewed certificate may have a different certificate hash.

Public-key pinning offers more flexibility when the same key is retained, but key rotation must still be planned.

Mistake 3: Trusting all certificates

Never use a TrustManager that accepts everything:

override fun checkServerTrusted(
    chain: Array<X509Certificate>,
    authType: String
) {
    // Accept everything — insecure
}

This disables certificate validation.

Mistake 4: Disabling hostname verification

Never do this:

hostnameVerifier { _, _ -> true }

A valid certificate for another hostname must not be accepted.

Mistake 5: Bypassing pinning after failure

Do not retry the request using an unpinned client.

That removes the security protection exactly when a suspicious connection occurs.

Mistake 6: No rotation plan

Backend, mobile and security teams must coordinate:

* Current pin

* Backup pin

* Rotation date

* App adoption period

* Emergency recovery procedure

Mistake 7: Pinning third-party services without control

A third party may change certificates or keys without coordinating with your mobile release schedule.

Pin only where your organization can operate the certificate lifecycle reliably.

Mistake 8: Assuming pinning is always recommended

Pinning adds security only when its operational risks are controlled. For many applications, correct HTTPS validation and secure network configuration may be safer than poorly managed pinning.

---

17. Testing Strategy

Test at least these cases:

ScenarioExpected result
Correct primary pinConnection succeeds
Correct backup pinConnection succeeds
Incorrect pinConnection fails
Expired certificateConnection fails
Wrong hostnameConnection fails
Untrusted CAConnection fails
Certificate/key rotationSupported app versions continue working
TLS interception proxyProduction connection fails
Debug security setupWorks only in approved debug build

Testing failure is as important as testing success.

---

18. Interview Questions and Answers

Q1. What is SSL pinning?

SSL pinning, more accurately certificate pinning, restricts an application to predefined server certificate or public-key identities in addition to normal TLS validation.

Q2. How does it prevent MITM attacks?

Even if an attacker presents a certificate that the device otherwise trusts, the connection is rejected when its public-key hash does not match the application’s pins.

Q3. Certificate pinning vs public-key pinning?

Certificate pinning pins the entire certificate and usually breaks when the certificate changes. Public-key pinning pins the public-key hash and can survive certificate renewal when the same key is reused.

Q4. Why do we need a backup pin?

A backup pin allows the backend to rotate to a pre-approved key without breaking installed application versions.

Q5. Where are pins stored?

Pins are packaged inside application code or Network Security Configuration. Changing them normally requires an application update.

Q6. What happens when pin validation fails in OkHttp?

OkHttp rejects the connection and throws SSLPeerUnverifiedException.

Q7. Should the application fall back to normal HTTPS after pin failure?

No. Falling back would bypass the protection and could permit the suspected connection.

Q8. How do you rotate certificates safely?

Ship the future public-key pin as a backup, wait for sufficient app adoption, rotate the server key and later remove the old pin.

Q9. Can pinning be bypassed?

Yes, on a compromised device an attacker may hook or modify application code. Pinning is defence in depth, not an absolute guarantee.

Q10. What are the risks of pinning?

Incorrect pins, certificate changes or poor rotation planning can block all API communication for installed application versions.

Q11. Should we pin third-party APIs?

Only with caution. If the third party changes its certificate or public key without coordination, the application can stop working.

Q12. How would you implement it with Retrofit?

Configure an OkHttp CertificatePinner with primary and backup SHA-256 SPKI hashes, attach the OkHttp client to Retrofit and never use an insecure fallback client.

---

Final Interview Answer

> Certificate pinning adds an extra identity check on top of normal HTTPS. During the TLS handshake, Android first validates the certificate chain, expiry and hostname. OkHttp then compares the SHA-256 public-key hash from the server’s certificate chain with the pins packaged in the application. If any approved pin matches, the connection continues; otherwise, it fails with `SSLPeerUnverifiedException`. I always configure primary and backup pins, coordinate certificate rotation with the backend team, test failure scenarios and never fall back to an unpinned connection. Pinning is defence in depth and must be used only when the organization can manage its operational risks.