出现情况
当我们将之前封装的一套OKhttp的网络请求换成HTTPS的时候会发现,日志中会报出:
java.security.cert.CertPathValidatorException 和
Caused by: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
如下所示:
2021-11-19 09:16:12.140 7773-7773 W/System.err: Caused by: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
2021-11-19 09:16:12.140 7773-7773 W/System.err: at com.android.org.conscrypt.TrustManagerImpl.verifyChain(TrustManagerImpl.java:674)
2021-11-19 09:16:12.140 7773-7773 W/System.err: at com.android.org.conscrypt.TrustManagerImpl.checkTrustedRecursive(TrustManagerImpl.java:551)
2021-11-19 09:16:12.140 7773-7773 W/System.err: at com.android.org.conscrypt.TrustManagerImpl.checkTrustedRecursive(TrustManagerImpl.java:617)
2021-11-19 09:16:12.140 7773-7773 W/System.err: at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:507)
2021-11-19 09:16:12.140 7773-7773 W/System.err: at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:426)
2021-11-19 09:16:12.140 7773-7773 W/System.err: at com.android.org.conscrypt.TrustManagerImpl.getTrustedChainForServer(TrustManagerImpl.java:354)
2021-11-19 09:16:12.140 7773-7773 W/System.err: at android.security.net.config.NetworkSecurityTrustManager.checkServerTrusted(NetworkSecurityTrustManager.java:94)
2021-11-19 09:16:12.140 7773-7773 W/System.err: at android.security.net.config.RootTrustManager.checkServerTrusted(RootTrustManager.java:89)
2021-11-19 09:16:12.140 7773-7773 W/System.err: at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:224)
2021-11-19 09:16:12.140 7773-7773 W/System.err: at com.android.org.conscrypt.ConscryptFileDescriptorSocket.verifyCertificateChain(ConscryptFileDescriptorSocket.java:407)
2021-11-19 09:16:12.140 7773-7773 W/System.err: at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
2021-11-19 09:16:12.140 7773-7773 W/System.err: at com.android.org.conscrypt.NativeSsl.doHandshake(NativeSsl.java:387)
2021-11-19 09:16:12.140 7773-7773 W/System.err: at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:226)
2021-11-19 09:16:12.140 7773-7773 W/System.err: ... 34 more
2021-11-19 09:16:12.141 7773-7773 W/System.err: Caused by: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
2021-11-19 09:16:12.141 7773-7773 W/System.err: ... 47 more
一番摸索之后,网上给出常规解决方案是在 OkHttpClient.Builder 创建者中添加 sslSocketFactory() 和 hostnameVerifier() 来忽略对SSL证书的验证,如下所示:
okHttpClient = new OkHttpClient.Builder()
.connectTimeout(DEFAULT_CONNECT_TIME, TimeUnit.SECONDS)
.writeTimeout(DEFAULT_WRITE_TIME, TimeUnit.SECONDS)
.readTimeout(DEFAULT_READ_TIME, TimeUnit.SECONDS)
//注意此处
.sslSocketFactory(MySSLSocketClient.getSSLSocketFactory(), MySSLSocketClient.X509)
.hostnameVerifier(MySSLSocketClient.getHostnameVerifier())
//
.addInterceptor(chain -> {
Request original = chain.request();
Request request = original.newBuilder()
.headers(headersBuilder.build())
.method(original.method(), original.body())
.build();
return chain.proceed(request);
})
.build();
MySSLSocketClient
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class MySSLSocketClient {
//获取这个SSLSocketFactory
public static SSLSocketFactory getSSLSocketFactory() {
try {
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, getTrustManager(), new SecureRandom());
return sslContext.getSocketFactory();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//获取TrustManager
public static TrustManager[] getTrustManager() {
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
//8.0之前返回null
return new X509Certificate[]{};
}
}
};
return trustAllCerts;
}
//获取HostnameVerifier
public static HostnameVerifier getHostnameVerifier() {
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
};
return hostnameVerifier;
}
}
至此,常规操作已经可以解决问题了。
但是,对于Android开发者来说总所周知的一件事就是:版本适配!(手动心酸泪目…)
对于在Android 10及以上的设备上,会出现设备信任问题(个人理解命名),报错如:
Required method checkServerTrusted、
Unable to extract the trust manager on Android10Platform 之类
解决方式
在 OkHttp 的GitHub 中的 issues 已经给出了具体的解决方案
就是把上面 MySSLSocketClient类中的 X509TrustManager 的实现类换成 X509ExtendedTrustManager 实现类即可。
最终 MySSLSocketClient 类如下:
import java.net.Socket;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509ExtendedTrustManager;
public class MySSLSocketClient {
//获取TrustManager
public static X509ExtendedTrustManager X509 = new X509ExtendedTrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException {
}
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) throws CertificateException {
}
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
//获取这个SSLSocketFactory
public static SSLSocketFactory getSSLSocketFactory() {
try {
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, new TrustManager[]{X509}, new SecureRandom());
return sslContext.getSocketFactory();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//获取HostnameVerifier
public static HostnameVerifier getHostnameVerifier() {
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
};
return hostnameVerifier;
}
}