改造:Android 应用在 API21+ 设备上启动时 SSL 握手失败
Retrofit: Android app fails SSL handshakes when launched on API21+ devices
我正在构建一个通过 HTTPS
获取一些 JSON
数据的应用程序,将 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);
}
}
我知道这段代码在安全性方面有多么糟糕。我的主管要求那样,我告诉他这有多糟糕。
我已经尝试了 2 种方法来解决我的问题,它们在那段代码中被注释掉了 - 证书固定和请求 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)
.
响应
非常感谢所有帮助,因为我完全没有想法。如果需要,请要求澄清。
谢谢。
告诉你的主管你的 HTTP 客户端无法连接到极不安全的 HTTPS 服务器。你可以说这是电脑的限制,你唯一的选择就是更新服务器。
完成后,您可以添加 development-only 调试模式。为此,请启用您的服务器支持的密码套件。您可以从 Qualys 工具中获取列表。
我的解决方案是添加更多 OkHttpClient 可接受的密码。自 API 21 起,Android 已弃用某些 TLS 证书。这可能有帮助:
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
获取一些 JSON
数据的应用程序,将 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);
}
}
我知道这段代码在安全性方面有多么糟糕。我的主管要求那样,我告诉他这有多糟糕。
我已经尝试了 2 种方法来解决我的问题,它们在那段代码中被注释掉了 - 证书固定和请求 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)
.
非常感谢所有帮助,因为我完全没有想法。如果需要,请要求澄清。
谢谢。
告诉你的主管你的 HTTP 客户端无法连接到极不安全的 HTTPS 服务器。你可以说这是电脑的限制,你唯一的选择就是更新服务器。
完成后,您可以添加 development-only 调试模式。为此,请启用您的服务器支持的密码套件。您可以从 Qualys 工具中获取列表。
我的解决方案是添加更多 OkHttpClient 可接受的密码。自 API 21 起,Android 已弃用某些 TLS 证书。这可能有帮助:
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();