SSL 证书固定不再适用于 Android 9

SSL Certificate Pinning not working anymore on Android 9

我正在使用以下已经工作了一段时间的证书固定代码(为简洁起见,错误处理已被删除):

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) {
        // ...
    }
}

这或多或少是由 official documentation 提供的代码。然后使用 SocketFactory 如下:

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

当我在 Android 8 设备上 运行 这段代码时,一切正常。然而,在我的 Android 9 模拟器上,抛出了一个异常:

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 ...

似乎在 Android 9 中发生了一些变化,但到目前为止我还没有找到有关此行为的任何信息。我的想法如下:

还有其他想法吗?

我刚遇到同样的问题。根据 Android 9 Change-Log,对于没有 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.

来源: Hostname verification using a certificate

要验证主机名,服务器必须出示具有匹配 SAN 的证书。不包含与主机名匹配的 SAN 的证书不再受信任。

我的查询是证书是否是像 *.mydomain.com 这样的域的通配符,例如 online.mydomain.com 。 SAN *.mydomain.com 会工作还是不再支持通配符?