我设法启用了正确的密码套件,但在此过程中我破坏了主机名比较。
这是我开始之前的代码:
HttpParams params = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(params, 10000);
HttpConnectionParams.setSoTimeout(params, 20000);
DefaultHttpClient client = new DefaultHttpClient(params);
HttpPost request = new HttpPost(url);
...
HttpResponse response = client.execute(request);
...
从 Android 7 开始,我收到以下错误:
javax.net.ssl.SSLHandshakeException: Handshake failed
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:429)
at org.apache.http.conn.ssl.SSLSocketFactory.createSocket(SSLSocketFactory.java:406)
at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:170)
at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:169)
at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:124)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:366)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:560)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:492)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:470)
...
Caused by: javax.net.ssl.SSLProtocolException: SSL handshake terminated: ssl=0x9f7a03c0: Failure in SSL library, usually a protocol error
error:10000410:SSL routines:OPENSSL_internal:SSLV3_ALERT_HANDSHAKE_FAILURE (external/boringssl/src/ssl/s3_pkt.c:610 0x9c571b00:0x00000001)
error:1000009a:SSL routines:OPENSSL_internal:HANDSHAKE_FAILURE_ON_CLIENT_HELLO (external/boringssl/src/ssl/s3_clnt.c:764 0x9c614196:0x00000000)
at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:357)
... 19 more
这显然是由于网络服务器要求客户端支持 RC4, 似乎支持 through Android 6 (api 23)只要。不幸的是,目前无法升级网络服务器,所以我必须在应用程序中启用所需的密码套件。
这是我尝试过的:
// HttpParams params is same as before
DefaultHttpClient client = new DefaultHttpClient(params);
ClientConnectionManager connManager = client.getConnectionManager();
SchemeRegistry schemeRegistry = connManager.getSchemeRegistry();
Scheme scheme = schemeRegistry.getScheme("https");
final SSLSocketFactory impl = (SSLSocketFactory) scheme.getSocketFactory();
SocketFactory sf = new SocketFactory() {
@Override
public Socket createSocket() throws IOException {
SSLSocket socket = (SSLSocket) impl.createSocket();
ArrayList<String> list = new ArrayList<>(Arrays.asList(socket.getEnabledCipherSuites()));
list.add("SSL_RSA_WITH_RC4_128_SHA");
socket.setEnabledCipherSuites(list.toArray(new String[list.size()])); // this is where I add the required cipher suites after the SSLSocket is created
return socket;
}
// unchanged wrappers
@Override
public Socket connectSocket(Socket socket, String s, int i, InetAddress inetAddress, int i1, HttpParams httpParams) throws IOException { return impl.connectSocket(socket, s, i, inetAddress, i1, httpParams); }
@Override
public boolean isSecure(Socket socket) throws IllegalArgumentException { return impl.isSecure(socket); }
};
schemeRegistry.register(new Scheme("https", sf, 443));
// request & execute same as before
这成功添加了所需的密码套件,但似乎破坏了主机名比较,因为我得到以下信息:
javax.net.ssl.SSLException: hostname in certificate didn't match: <10.10.10.10> != <sub.example.com> OR <sub.example.com>
此主机的 IP 地址是正确的。据推测,我所有的覆盖/包装器都导致了使用 IP 地址而不是在 url
中传递的原始 https://sub.example.com/
主机名的情况>.
你能帮帮我吗?我不知道如何克服主机名不匹配的问题。
最佳答案
明白了!似乎 Apache HTTP 客户端库正在检查我的 Socket Factory sf
是否是 instanceof LayeredSocketFactory
,在这种情况下,它使用了另一个 createSocket
方法库传递所需的主机名(sub.example.com
而不是 10.10.10.10
)以进行正确的 SSL/TLS 比较。
所以,sf
需要这样写。注意额外的 createSocket
覆盖:
LayeredSocketFactory sf = new LayeredSocketFactory() {
javax.net.ssl.SSLSocketFactory defaultFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
@Override
public Socket createSocket(Socket tcpSocket, String host, int port, boolean autoClose) throws IOException {
// I'm not actually using impl.createSocket, because then the hostname verifier would have kicked in prior to addMyCipherSuite
SSLSocket sslSocket = (SSLSocket) defaultFactory.createSocket(tcpSocket, host, port, autoClose);
impl.getHostnameVerifier().verify(host, addMyCipherSuite(sslSocket));
return sslSocket;
}
@Override
public Socket createSocket() throws IOException {
return addMyCipherSuite((SSLSocket) impl.createSocket());
}
private Socket addMyCipherSuite(SSLSocket socket) {
ArrayList<String> list = new ArrayList<>(Arrays.asList(socket.getEnabledCipherSuites()));
list.add("SSL_RSA_WITH_RC4_128_SHA");
socket.setEnabledCipherSuites(list.toArray(new String[list.size()])); // this is where I add the required cipher suites after the SSLSocket is created
return socket;
}
// unchanged wrappers
@Override
public Socket connectSocket(Socket socket, String s, int i, InetAddress inetAddress, int i1, HttpParams httpParams) throws IOException { return impl.connectSocket(socket, s, i, inetAddress, i1, httpParams); }
@Override
public boolean isSecure(Socket socket) throws IllegalArgumentException { return impl.isSecure(socket); }
};
关于java - 在 Android 7 org.apache.http (Apache HttpClient 4.0) 中启用已弃用的密码套件后主机名不匹配,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40644378/