改造:在API21 +设备上启动时,Android应用程序无法通过SSL握手

时间:2017-02-03 08:35:12

标签: android ssl retrofit okhttp handshake

我正在构建一个应用程序,通过JSON使用自定义HTTPS客户端的Retrofit获取一些OkHttp数据。它在KitKat上工作正常。一旦我转移到Android 5,6或7,SSL握手失败。

服务器支持TLSv1,而不支持任何其他内容。它还使用了一个古老的,过期的,自签名的证书。使用Qualys的SSL工具进行测试,该工具告诉我所有版本的Android都应该能够连接。这就是我所拥有的:

OkHttp客户端:

public class HTTPClient {

public static OkHttpClient getUnsafeOkHttpClient() {
    try {
        // Create a trust manager that does not validate certificate chains

        final TrustManager[] trustAllCerts = new TrustManager[] {
                new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {
                    }

                    @Override
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                        return new java.security.cert.X509Certificate[]{};
                    }
                }
        };

        // Install the all-trusting trust manager
        final SSLContext sslContext = SSLContext.getInstance("TLSv1");
        sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
        // Create a ssl socket factory with our all-trusting manager
        final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
        //URL url = new URL(ApiIntentService.getHostAddress());
        //final SSLSocketFactory sslSocketFactory = new NoSSLv3SocketFactory(url);

//            ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.COMPATIBLE_TLS)
//                    .tlsVersions(TlsVersion.TLS_1_0)
//                    .cipherSuites(
//                            CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
//                            CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
//                            CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
//                            CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
//                            CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
//                            CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
//                            CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
//                            CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
//                            CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
//                            CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA)
//                    .build();

//            String hostname = "api.server.domain";
//            CertificatePinner certificatePinner = new CertificatePinner.Builder()
//                    .add(hostname, "sha256/3Iiwgs3a0qjPCnBQzW/GeHhPbZvhaJtxKvMJJVO5KdU=")
//                    .build();

        final OkHttpClient.Builder builder = new OkHttpClient.Builder();
//            builder.connectionSpecs(Collections.singletonList(spec));
//            builder.certificatePinner(certificatePinner);
            builder.sslSocketFactory(sslSocketFactory);
            builder.hostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            });
            builder.authenticator(new Authenticator() {
                @Override
                public Request authenticate(Route route, Response response) throws IOException {
                    String credential = Credentials.basic("user", "pass");
                    return response.request().newBuilder()
                            .header("Authorization", credential)
                            .build();
                }
            });
        OkHttpClient okHttpClient = builder.build();
        return okHttpClient;
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

我知道这段代码在安全性方面是多么严重的。我的主管要求它是这样的,我告诉他这有多糟糕。

我已经尝试了两件事来解决我的问题,他们在那段代码中被注释掉了 - 特别是证书固定和请求TLSv1,以及密码列表。在其他问题中找到了这两个,但他们没有改变任何东西(堆栈跟踪完全相同)。

堆栈跟踪

以下是堆栈跟踪的有趣位:

I/RETROFIT: Data retrieval failed! javax.net.ssl.SSLHandshakeException: Handshake failed
*snip*
Caused by: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0xb43eb200: Failure in SSL library, usually a protocol error
error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure (external/openssl/ssl/s23_clnt.c:770 0xac6fedd4:0x00000000)
                at       com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)

看起来Android似乎突然尝试使用SSLv3,但Wireshark通过TLSv1显示通信。它始终以Client Hello开头,但服务器会立即以Handshake Failure (40)回答。

所有的帮助都非常有用,因为我完全没有想法。如果需要,请询问澄清。

谢谢。

2 个答案:

答案 0 :(得分:2)

告诉您的主管您的HTTP客户端无法连接到致命不安全的HTTPS服务器。您可以说这是计算机的限制,您唯一的选择是更新服务器。

完成后,您可以添加仅开发调试模式。为此,请启用服务器支持的密码套件。您可以从Qualys工具中获取列表。

答案 1 :(得分:2)

我的解决方案是为OkHttpClient添加更多可接受的密码。自API 21起,某些TLS证书已弃用于Android。这可能会有所帮助:

ConnectionSpec spec = new 
ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
            .tlsVersions(TlsVersion.TLS_1_2)
            .cipherSuites(      
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,                    
CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256)
            .build();

OkHttpClient client = new OkHttpClient.Builder()
        .connectionSpecs(Collections.singletonList(spec))
        .build();

有关详细信息,请访问:https://github.com/square/okhttp/wiki/HTTPS