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