OkHttp3 hostnameVerifier 原因:javax.net.ssl.SSLException:读取错误:ssl=0xc8cf1fc8:系统调用期间的 I/O 错误,对等方重置连接

Posted

技术标签:

【中文标题】OkHttp3 hostnameVerifier 原因:javax.net.ssl.SSLException:读取错误:ssl=0xc8cf1fc8:系统调用期间的 I/O 错误,对等方重置连接【英文标题】:OkHttp3 hostnameVerifier Caused by: javax.net.ssl.SSLException: Read error: ssl=0xc8cf1fc8: I/O error during system call, Connection reset by peer 【发布时间】:2020-03-07 06:36:22 【问题描述】:

我已经在 SO 上浏览过类似的帖子,这些帖子指出了类似的问题。 就我而言,我正在尝试在 API 级别 28 的 Nexus 6p 模拟器上运行。

此实现适用于 Volley HurlStack,但不适用于 OkHttp3 HttpStack。所以我确定在服务器端没有问题,但在 OkHttp 端。不知道我在这里错过了什么。任何线索将不胜感激。提前致谢!

PS我正在远离 Volley。这只是使用 okHttp 进行测试,以便进一步迁移。

排球工作实施如下:

 import android.annotation.SuppressLint;
 import android.util.Base64;

 import com.abc.test.core.network.Tls12SocketFactory;
 import com.android.volley.toolbox.HurlStack;

 import java.io.IOException;
 import java.net.HttpURLConnection;
 import java.net.URL;
 import java.security.KeyManagementException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
 import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;

 import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.HttpsURLConnection;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLPeerUnverifiedException;
 import javax.net.ssl.SSLSession;
 import javax.net.ssl.SSLSocketFactory;
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.X509TrustManager;

 import timber.log.Timber;

public class VolleyHurlStack extends HurlStack 

private final Boolean enablePinning;

VolleyHurlStack(Boolean enablePinning) 
    this.enablePinning = enablePinning;


@Override
protected HttpURLConnection createConnection(URL url) throws IOException 
    HttpsURLConnection httpsURLConnection = (HttpsURLConnection) super.createConnection(url);

    try 
        httpsURLConnection.setSSLSocketFactory(getSSLSocketFactory());
        httpsURLConnection.setHostnameVerifier(new HostnameVerifier() 
            @Override
            public boolean verify(String hostname, SSLSession session) 

                try 
                    return validatePinning(session.getPeerCertificates());
                 catch (SSLPeerUnverifiedException e) 
                    Timber.e(e);
                

                return false;
            
        );

     catch (Exception e) 
        Timber.e(e);
    
    return httpsURLConnection;


private SSLSocketFactory getSSLSocketFactory() 

    SSLContext sc;
    SSLSocketFactory sslSocketFactory = null;
    try 
        sc = SSLContext.getInstance("TLSv1.1");
        if (sc != null) 
            sc.init(null, getAllTrustManagers(), new SecureRandom());
            sslSocketFactory = new Tls12SocketFactory(sc.getSocketFactory());
         else 
            Timber.e("SSLContext is null");
        
     catch (NoSuchAlgorithmException | KeyManagementException e) 
        Timber.e(e);
    

    return sslSocketFactory;



private TrustManager[] getAllTrustManagers() 
    return new TrustManager[]getX509TrustManager();



private X509TrustManager getX509TrustManager() 
    return new X509TrustManager() 
        public X509Certificate[] getAcceptedIssuers() 
            return new X509Certificate[0];
        

        @SuppressLint("TrustAllX509TrustManager")
        @Override
        public void checkClientTrusted(X509Certificate[] certs, String authType) 
        

        @SuppressLint("TrustAllX509TrustManager")
        @Override
        public void checkServerTrusted(X509Certificate[] certs, String authType) 
        
    ;


public boolean validatePinning(Certificate[] certificate) 

    try 
        MessageDigest md = MessageDigest.getInstance("SHA-256");

        for (Certificate cert : certificate) 
            byte[] publicKey = cert.getPublicKey().getEncoded();
            md.update(publicKey, 0, publicKey.length);
            String pin = Base64.encodeToString(md.digest(), Base64.NO_WRAP);

            for (String validPin : validPins) 
                if (validPin.contains(pin)) 
                    Timber.d("validatePinning successful");
                    return true;
                
            
        
     catch (NoSuchAlgorithmException e) 
        Timber.e(e);
    

    return false;
 


不工作的 OkHttp 实现如下:

 import android.annotation.SuppressLint;
 import android.util.Base64;

 import com.abc.test.core.network.Tls12SocketFactory;
 import com.android.volley.AuthFailureError;
 import com.android.volley.Header;
 import com.android.volley.Request;
 import com.android.volley.toolbox.BaseHttpStack;
 import com.android.volley.toolbox.HttpResponse;

 import java.io.IOException;
 import java.io.InputStream;
 import java.security.KeyManagementException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
 import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;

 import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLPeerUnverifiedException;
 import javax.net.ssl.SSLSession;
 import javax.net.ssl.SSLSocketFactory;
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.X509TrustManager;

 import okhttp3.Call;
 import okhttp3.ConnectionSpec;
 import okhttp3.Headers;
 import okhttp3.MediaType;
 import okhttp3.OkHttpClient;
 import okhttp3.RequestBody;
 import okhttp3.Response;
 import okhttp3.ResponseBody;
 import timber.log.Timber;

 public class OkHttp3Stack extends BaseHttpStack 


public OkHttp3Stack() 



private static void setConnectionParametersForRequest(okhttp3.Request.Builder builder, Request<?> request)
        throws AuthFailureError 
    switch (request.getMethod()) 
        case Request.Method.DEPRECATED_GET_OR_POST:
            // Ensure backwards compatibility.  Volley assumes a request with a null body is a GET.
            byte[] postBody = request.getBody();
            if (postBody != null) 
                builder.post(RequestBody.create(MediaType.parse(request.getBodyContentType()), postBody));
            
            break;
        case Request.Method.GET:
            builder.get();
            break;
        case Request.Method.DELETE:
            builder.delete(createRequestBody(request));
            break;
        case Request.Method.POST:
            builder.post(createRequestBody(request));
            break;
        case Request.Method.PUT:
            builder.put(createRequestBody(request));
            break;
        case Request.Method.HEAD:
            builder.head();
            break;
        case Request.Method.OPTIONS:
            builder.method("OPTIONS", null);
            break;
        case Request.Method.TRACE:
            builder.method("TRACE", null);
            break;
        case Request.Method.PATCH:
            builder.patch(createRequestBody(request));
            break;
        default:
            throw new IllegalStateException("Unknown method type.");
    


private static RequestBody createRequestBody(Request r) throws AuthFailureError 
    final byte[] body = r.getBody();
    if (body == null) 
        return null;
    
    return RequestBody.create(MediaType.parse(r.getBodyContentType()), body);


@Override
public HttpResponse executeRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError 
    OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
    int timeoutMs = request.getTimeoutMs();

    clientBuilder.connectTimeout(timeoutMs, TimeUnit.MILLISECONDS);
    clientBuilder.readTimeout(timeoutMs, TimeUnit.MILLISECONDS);
    clientBuilder.writeTimeout(timeoutMs, TimeUnit.MILLISECONDS);

    okhttp3.Request.Builder okHttpRequestBuilder = new okhttp3.Request.Builder();
    okHttpRequestBuilder.url(request.getUrl());

    Map<String, String> headers = request.getHeaders();
    for (final String name : headers.keySet()) 
        okHttpRequestBuilder.addHeader(name, headers.get(name));
    
    for (final String name : additionalHeaders.keySet()) 
        okHttpRequestBuilder.addHeader(name, additionalHeaders.get(name));
    

    setConnectionParametersForRequest(okHttpRequestBuilder, request);


    clientBuilder.sslSocketFactory(getSSLSocketFactory(), getX509TrustManager());
    clientBuilder.hostnameVerifier(new HostnameVerifier() 
        @Override
        public boolean verify(String hostname, SSLSession session) 

            try 
                return validatePinning(session.getPeerCertificates());
             catch (SSLPeerUnverifiedException e) 
                Timber.e(e);
            

            return false;
        
    );

    ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
            .allEnabledTlsVersions()
            .allEnabledCipherSuites()
            .build();

    clientBuilder.connectionSpecs(Collections.singletonList(spec));

    clientBuilder.retryOnConnectionFailure(true);


    OkHttpClient client = clientBuilder.build();
    okhttp3.Request okHttpRequest = okHttpRequestBuilder.build();
    Call okHttpCall = client.newCall(okHttpRequest);
    Response okHttpResponse = okHttpCall.execute();


    int code = okHttpResponse.code();
    ResponseBody body = okHttpResponse.body();
    InputStream content = body == null ? null : body.byteStream();
    int contentLength = body == null ? 0 : (int) body.contentLength();
    List<Header> responseHeaders = mapHeaders(okHttpResponse.headers());
    return new HttpResponse(code, responseHeaders, contentLength, content);


private List<Header> mapHeaders(Headers responseHeaders) 
    List<Header> headers = new ArrayList<>();
    for (int i = 0, len = responseHeaders.size(); i < len; i++) 
        final String name = responseHeaders.name(i), value = responseHeaders.value(i);
        if (name != null) 
            headers.add(new Header(name, value));
        
    
    return headers;



private SSLSocketFactory getSSLSocketFactory() 

    SSLContext sc;
    SSLSocketFactory sslSocketFactory = null;
    try 
        sc = SSLContext.getInstance("TLSv1.1");
        if (sc != null) 
            sc.init(null, getAllTrustManagers(), new SecureRandom());
            sslSocketFactory = new Tls12SocketFactory(sc.getSocketFactory());
         else 
            Timber.e("SSLContext is null");
        
     catch (NoSuchAlgorithmException | KeyManagementException e) 
        Timber.e(e);
    

    return sslSocketFactory;



private TrustManager[] getAllTrustManagers() 
    return new TrustManager[]getX509TrustManager();



private X509TrustManager getX509TrustManager() 
    return new X509TrustManager() 
        public X509Certificate[] getAcceptedIssuers() 
            return new X509Certificate[0];
        

        @SuppressLint("TrustAllX509TrustManager")
        @Override
        public void checkClientTrusted(X509Certificate[] certs, String authType) 
        

        @SuppressLint("TrustAllX509TrustManager")
        @Override
        public void checkServerTrusted(X509Certificate[] certs, String authType) 
        
    ;


private boolean validatePinning(Certificate[] certificate) 

    try 
        MessageDigest md = MessageDigest.getInstance("SHA-256");

        for (Certificate cert : certificate) 
            byte[] publicKey = cert.getPublicKey().getEncoded();
            md.update(publicKey, 0, publicKey.length);
            String pin = Base64.encodeToString(md.digest(), Base64.NO_WRAP);

            for (String validPin : validPins) 
                if (validPin.contains(pin)) 
                    Timber.d("validatePinning successful");
                    return true;
                
            
        
     catch (NoSuchAlgorithmException e) 
        Timber.e(e);
    

    return false;
  
 

【问题讨论】:

@JesseWilson 你能建议我应该研究哪一部分代码来进行可能的修复吗? 您不信任任何东西的信任经理有问题。信任管理器不应返回空数组。 @JesseWilson 但它不是空的,它是return new X509Certificate[0]; 具有零元素的数组传统上称为空数组。 这个问题和这个有关吗? ***.com/questions/29916962/… 【参考方案1】:

显然,传递的默认用户代理值存在问题,该值在后端被阻止

通过 okhttp 传递的用户代理值是:okhttp 开始传递自定义用户代理值并解决了问题。

它没有被记录为 okhttpInterceptor 的一部分,因此跟踪两个请求中的差异需要很多时间。不要指望用户代理是罪魁祸首。

【讨论】:

以上是关于OkHttp3 hostnameVerifier 原因:javax.net.ssl.SSLException:读取错误:ssl=0xc8cf1fc8:系统调用期间的 I/O 错误,对等方重置连接的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 okhttp 引擎将 HostnameVerifier 添加到 ktor 客户端

Android 中不安全的 HostnameVerifier - React Native

配置Jetty HTTP / 2客户端以使用我的HostnameVerifier实现

httpsUrlConnection 如何设置的默认sslcontext和 hostnameverifier?

httpsUrlConnection 如何设置的默认sslcontext和 hostnameverifier?

使用 reactor netty 为 spring-webflux WebClient 配置 HostnameVerifier