实际上,本周,我有与以下相同的问题
OkHttpClient and Certificate Authority Validation in Android
在我的手机(摩托罗拉,Android 4.1.2)中,我禁用了所有DigiCert CA(在Settings - Security - Trusted credentials - System
内)。
我的代码如下:
public class CertPinActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_cert_pin);
try {
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add("github.com", "sha256/WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18=")
.build();
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(getSSLSocketFactory())
.certificatePinner(certificatePinner)
.build();
Request request = new Request.Builder()
.url("https://github.com/square/okhttp/wiki/HTTPS")
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e("onFailure","-------------------------------------------------");
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d("onResponse", response.body().string());
}
});
} catch (Exception e){
e.printStackTrace();
}
}
private SSLSocketFactory getSSLSocketFactory()
throws CertificateException, KeyStoreException, IOException,
NoSuchAlgorithmException, KeyManagementException {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = getResources().openRawResource(R.raw.github); // this is exported from Chrome then stored inside \app\src\main\res\raw path
Certificate ca = cf.generateCertificate(caInput);
caInput.close();
KeyStore keyStore = KeyStore.getInstance("BKS");
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
return sslContext.getSocketFactory();
}
}
我的应用程序得到了以下logcat(抱歉我截断了因为它太长了)
06-14 09:10:10.065 30176-30211/com.example.okhttps3 E/onFailure: -------------------------------------------------
06-14 09:10:10.065 30176-30211/com.example.okhttps3 W/System.err: javax.net.ssl.SSLPeerUnverifiedException: Failed to find a trusted cert that signed X.509 Certificate:
06-14 09:10:10.065 30176-30211/com.example.okhttps3 W/System.err: [
06-14 09:10:10.065 30176-30211/com.example.okhttps3 W/System.err: [
06-14 09:10:10.065 30176-30211/com.example.okhttps3 W/System.err: Version: V3
06-14 09:10:10.065 30176-30211/com.example.okhttps3 W/System.err: Subject: CN=DigiCert SHA2 Extended Validation Server CA,OU=www.digicert.com,O=DigiCert Inc,C=US
06-14 09:10:10.065 30176-30211/com.example.okhttps3 W/System.err: Signature Algorithm: SHA256WithRSAEncryption, params unparsed, OID = 1.2.840.113549.1.1.11
06-14 09:10:10.065 30176-30211/com.example.okhttps3 W/System.err: Key:
...................................................................................
06-14 09:17:56.813 5934-5984/com.example.okhttps3 W/System.err: at okhttp3.internal.tls.CertificateChainCleaner$BasicCertificateChainCleaner.clean(CertificateChainCleaner.java:132)
06-14 09:17:56.813 5934-5984/com.example.okhttps3 W/System.err: at okhttp3.CertificatePinner.check(CertificatePinner.java:149)
06-14 09:17:56.813 5934-5984/com.example.okhttps3 W/System.err: at okhttp3.internal.io.RealConnection.connectTls(RealConnection.java:252)
06-14 09:17:56.823 5934-5984/com.example.okhttps3 W/System.err: at okhttp3.internal.io.RealConnection.establishProtocol(RealConnection.java:196)
06-14 09:17:56.823 5934-5984/com.example.okhttps3 W/System.err: at okhttp3.internal.io.RealConnection.buildConnection(RealConnection.java:171)
06-14 09:17:56.823 5934-5984/com.example.okhttps3 W/System.err: at okhttp3.internal.io.RealConnection.connect(RealConnection.java:111)
06-14 09:17:56.823 5934-5984/com.example.okhttps3 W/System.err: at okhttp3.internal.http.StreamAllocation.findConnection(StreamAllocation.java:187)
06-14 09:17:56.823 5934-5984/com.example.okhttps3 W/System.err: at okhttp3.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:123)
06-14 09:17:56.823 5934-5984/com.example.okhttps3 W/System.err: at okhttp3.internal.http.StreamAllocation.newStream(StreamAllocation.java:93)
06-14 09:17:56.823 5934-5984/com.example.okhttps3 W/System.err: at okhttp3.internal.http.HttpEngine.connect(HttpEngine.java:296)
06-14 09:17:56.823 5934-5984/com.example.okhttps3 W/System.err: at okhttp3.internal.http.HttpEngine.sendRequest(HttpEngine.java:248)
06-14 09:17:56.823 5934-5984/com.example.okhttps3 W/System.err: at okhttp3.RealCall.getResponse(RealCall.java:243)
06-14 09:17:56.823 5934-5984/com.example.okhttps3 W/System.err: at okhttp3.RealCall$ApplicationInterceptorChain.proceed(RealCall.java:201)
06-14 09:17:56.823 5934-5984/com.example.okhttps3 W/System.err: at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:163)
06-14 09:17:56.823 5934-5984/com.example.okhttps3 W/System.err: at okhttp3.RealCall.access$100(RealCall.java:30)
06-14 09:17:56.823 5934-5984/com.example.okhttps3 W/System.err: at okhttp3.RealCall$AsyncCall.execute(RealCall.java:127)
06-14 09:17:56.823 5934-5984/com.example.okhttps3 W/System.err: at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
06-14 09:17:56.823 5934-5984/com.example.okhttps3 W/System.err: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
06-14 09:17:56.823 5934-5984/com.example.okhttps3 W/System.err: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
06-14 09:17:56.823 5934-5984/com.example.okhttps3 W/System.err: at java.lang.Thread.run(Thread.java:856)
但是,onResponse
在以下情况下调用:
.sslSocketFactory(getSSLSocketFactory())
和.certificatePinner(certificatePinner)
已删除; .sslSocketFactory(getSSLSocketFactory())
; (实际上,使用模拟器时,系统CA禁用时,onFailure
调用并在其内部java.security.cert.CertPathValidatorException: Trust anchor for certification path not found
抛出).certificatePinner(certificatePinner)
。 Logcat与onResponse
如下(太长,所以我截断):
06-14 09:06:23.143 26571-26616/com.example.okhttps3 D/onResponse: <!DOCTYPE html>
<html lang="en" class="">
.....
所以,我的问题是:
javax.net.ssl.SSLPeerUnverifiedException: Failed to find a trusted cert that signed X.509 Certificate
与certificatePinner
一起使用时,为什么我的应用会获得getSSLSocketFactory
?请注意,此SSLPeerUnverifiedException
的内部消息与this JavaDoc中提及的Certificate pinning failure!
不同。更新
第一期:
看起来这个问题(系统/用户可信凭证无效)只发生在运行Android 4.1.2的手机中,我检查过02设备,所以我想我应该联系制造商。
第二期:
从"I assume you have to include the full certificate chain (or the root certificate if the server sends you the chain), not only the leave certificate"
下方@Robert的评论中,我已经导出了根CA,如下面的屏幕截图所示,虽然没有完成链,但在getSSLSocketFactory
内我改为getResources().openRawResource(R.raw.github_rootca);
现在,我的第二个问题已经解决了!
答案 0 :(得分:2)
如果禁用默认系统证书信任,(使用或不使用没有自定义信任的证书固定),您将始终收到异常,因为无法验证对等证书,所以
Certificate pinning
且default system certificate trustore
为working
(使用Digicert High Assurance EV根CA),您的连接将确定 Certificate pinning
且default system certificate trustore
为not working
,则您的连接将失败 Certificate pinning
且default system certificate trustore
为not working
,您的连接将失败 Certificate pinning
且default system certificate trustore
为not working
并且设置了custom trustore with the digicert CA
,那么您的连接将确定 Certificate pinning
和default system certificate trustore is not working
以及setup a custom trustore without the digicert CA
,您的连接将失败 使用证书锁定+ sslSocketFactory +自定义信任管理器(基于OkHttp3 CustomTrust example)的示例(Java)
(digicert.cer包含Digicert High Assurance EV Root CA)
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.util.Arrays;
import java.util.Collection;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import org.junit.Test;
import junit.framework.TestCase;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.CertificatePinner;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class OkHttpTest extends TestCase {
@Test
public void test() {
try {
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add("github.com", "sha256/WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18=").build();
X509TrustManager trustManager;
SSLSocketFactory sslSocketFactory;
try {
trustManager = trustManagerForCertificates(new FileInputStream(new File("/tmp/digicert.cer")));
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { trustManager }, null);
sslSocketFactory = sslContext.getSocketFactory();
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
OkHttpClient client = new OkHttpClient.Builder().sslSocketFactory(sslSocketFactory, trustManager)
.certificatePinner(certificatePinner).build();
Request request = new Request.Builder().url("https://github.com/square/okhttp/wiki/HTTPS").build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
System.out.println("onResponse: " + response.body().string());
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
private X509TrustManager trustManagerForCertificates(InputStream in) throws GeneralSecurityException {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
Collection<? extends Certificate> certificates = certificateFactory.generateCertificates(in);
if (certificates.isEmpty()) {
throw new IllegalArgumentException("expected non-empty set of trusted certificates");
}
// Put the certificates a key store.
char[] password = "password".toCharArray(); // Any password will work.
KeyStore keyStore = newEmptyKeyStore(password);
int index = 0;
for (Certificate certificate : certificates) {
String certificateAlias = Integer.toString(index++);
keyStore.setCertificateEntry(certificateAlias, certificate);
}
// Use it to build an X509 trust manager.
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, password);
TrustManagerFactory trustManagerFactory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
}
return (X509TrustManager) trustManagers[0];
}
private KeyStore newEmptyKeyStore(char[] password) throws GeneralSecurityException {
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream in = null;
keyStore.load(in, password);
return keyStore;
} catch (IOException e) {
throw new AssertionError(e);
}
}
}
答案 1 :(得分:1)
证书锁定是一项额外的安全措施,因此必须使用已使用的TrustManager 和来信任证书,它必须与固定证书相匹配。
由于您已禁用DigiCert CA证书,因此起诉TrustManager不会信任该证书,因此您将获得SSLPeerUnverifiedException。
我所描述的行为记录在CertificatePinner的JavaDoc中:
关于自签名证书的说明
如果TrustManager不接受此类证书,则不能使用CertificatePinner来固定自签名证书。