java - 在 Android 7 org.apache.http (Apache HttpClient 4.0) 中启用已弃用的密码套件后主机名不匹配

标签 java android ssl apache-httpclient-4.x

我设法启用了正确的密码套件,但在此过程中我破坏了主机名比较。

这是我开始之前的代码:

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/

相关文章:

java - 为什么 NetworkInterface 的方法会抛出 SocketException

java - Sqlite 更新操作我可以添加值现有值吗?

java - 为什么在取消定时器任务之前让主线程进入休眠状态?

android - Notification Builder setSmallIcon "background"颜色

php - SNI 错误 Apache2 日志

Django:对从子域提供的静态内容使用 SSL (https) 方案

ssl - 为什么 grpc 服务器示例使用 net.Listen 而不是 tls.Listen

java - Java 字符串中的 Null\u0000?

android - RecyclerView 在到达数据末尾时崩溃

java - Android Studio 是否有一个包可以为我当前的类生成 getInstance() 方法?