SSL Certificate Pinning not working anymore on Android 9
Asked Answered
E

2

7

I'm using the following certificate pinning code which has worked for a while (error handling edited out for brevity's sake):

private static SSLContext _ssl_context = null;

public static SSLSocketFactory get_ssl_socket_factory(Context context)
{
    if (_ssl_context != null) {
        return _ssl_context.getSocketFactory();
    }

    KeyStore keystore = get_keystore(context);
    try
    {
        TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
        tmf.init(keystore);
        _ssl_context = SSLContext.getInstance("TLS");
        _ssl_context.init(null, tmf.getTrustManagers(), null);
        return _ssl_context.getSocketFactory();
    }
    catch (GeneralSecurityException e) {
        // ...
    }
}

This is more or less code provided by the official documentation. The SocketFactory is then used as follows:

if ("https".equals(target.getProtocol()) &&
    "example.com".equals(target.getHost()) &&
    huc instanceof HttpsURLConnection)
{
    ((HttpsURLConnection) huc).setSSLSocketFactory(
            SSLHelper.get_ssl_socket_factory(this));
}

When I run this code on an Android 8 device, things work as expected. On my Android 9 emulator however, an exception is thrown:

E/App: https://example.com/page.html could not be retrieved! (Hostname example.com not verified:
            certificate: sha1/VYMjxowFaRuZpycEoz+srAuXzlU=
            subjectAltNames: [])
        javax.net.ssl.SSLPeerUnverifiedException: Hostname example.com not verified:
            certificate: sha1/VYMjxowFaRuZpycEoz+srAuXzlU=
            subjectAltNames: []
            at com.android.okhttp.internal.io.RealConnection.connectTls(RealConnection.java:201)
            at com.android.okhttp.internal.io.RealConnection.connectSocket(RealConnection.java:149)
            at com.android.okhttp.internal.io.RealConnection.connect(RealConnection.java:112)
            at com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:184)
            at com.android.okhttp.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:126)
            at com.android.okhttp.internal.http.StreamAllocation.newStream(StreamAllocation.java:95)
            at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:281)
            at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:224)
            at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:461)
            at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:127)
            at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.connect(DelegatingHttpsURLConnection.java:89)
            at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:26)
            at ...

It seems that something has changed in Android 9, but so far I haven't been able to find any information regarding this behavior. My ideas are the following:

  • Maybe this way of doing certificate pinning has been deprecated
  • Maybe Android 9 will no longer verify domains with SHA1 certificates

Anything other ideas?

Emphysema answered 20/9, 2018 at 13:51 Comment(4)
In you case, according to the stacktrace, it's hostname which is not verified. example.com vs domain.com. I have similar problem. Pinning is not working when certificate is issued with a wildcard. Like cn=*.domain.com. So my.domain.com isn't accepted.Role
Sorry about the "domain.com" mixup, it was edited according to SO guidelines and one had been left out. The actual domain in the app matches exactly in both cases and is not a wildcard.Emphysema
I had to create new certificate with subject alternative names like SAN DNS:*.domain.com and it works. Looks like android 9 put some restrictions on CN verificationRole
Could you clarify what you needed to add as a SAN?Emphysema
N
5

I just had the same issue. According to the Android 9 Change-Log this is expected for certificates without SAN:

RFC 2818 describes two methods to match a domain name against a certificate—using the available names within the subjectAltName (SAN) extension, or in the absence of a SAN extension, falling back to the commonName (CN).

However, the fallback to the CN was deprecated in RFC 2818. For this reason, Android no longer falls back to using the CN. To verify a hostname, the server must present a certificate with a matching SAN. Certificates that don't contain a SAN matching the hostname are no longer trusted.

Source: Hostname verification using a certificate

Noodle answered 27/9, 2018 at 8:24 Comment(0)
J
0

To verify a hostname, the server must present a certificate with a matching SAN. Certificates that don't contain a SAN matching the hostname are no longer trusted.

My Query is if the Certificate is a wildcard like *.mydomain.com for a domain like online.mydomain.com . Will the SAN *.mydomain.com will work or the wildcard is no longer supported?

Jobina answered 17/10, 2019 at 14:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.