Actually, this week, I have the same question as the following
OkHttpClient and Certificate Authority Validation in Android
In my phone (Motorola, Android 4.1.2), I disable all DigiCert CAs (inside Settings - Security - Trusted credentials - System
).
My code below:
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();
}
}
My app got the following logcat (sorry I truncate because it's too long)
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)
However, onResponse
called in the following cases:
.sslSocketFactory(getSSLSocketFactory())
and .certificatePinner(certificatePinner)
removed;.sslSocketFactory(getSSLSocketFactory())
removed; (actually, with emulator, when system CA disabled, onFailure
called and inside it java.security.cert.CertPathValidatorException: Trust anchor for certification path not found
thrown).certificatePinner(certificatePinner)
removed.Logcat with onResponse
as below (too long so I truncate):
06-14 09:06:23.143 26571-26616/com.example.okhttps3 D/onResponse: <!DOCTYPE html>
<html lang="en" class="">
.....
So, my questions are:
javax.net.ssl.SSLPeerUnverifiedException: Failed to find a trusted cert that signed X.509 Certificate
when certificatePinner
used with getSSLSocketFactory
? Please note that the inner message of this SSLPeerUnverifiedException
is different from Certificate pinning failure!
as mentioned in this JavaDoc.UPDATE:
For 1st issue:
Looks like this issue (System/User Trusted credentials not effective) only happened in my phones which run Android 4.1.2, I have checked 02 devices, so I think I should contact the manufacturer.
For 2nd issue:
From the comment of @Robert below "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"
, I have exported Root CA instead, as the following screenshot, although not completed chain, and inside getSSLSocketFactory
I changed to getResources().openRawResource(R.raw.github_rootca);
Now, my 2nd issue is solved!
If you disable your default system certificate trustore, (using or not the Certificate pinning without a custom trustore) you will always get an exception because will not be possible to verify the peer certificate, so
Certificate pinning
and your default system certificate trustore
is working
(with the Digicert High Assurance EV Root CA) your connection will be OK
Certificate pinning
and your default system certificate trustore
is not working
your connection will FAIL
Certificate pinning
and your default system certificate trustore
is not working
your connection will FAIL
Certificate pinning
and your default system certificate trustore
is not working
and you setup a custom trustore with the digicert CA
your connection will be OK
Certificate pinning
and your default system certificate trustore is not working
and you setup a custom trustore without the digicert CA
your connection will FAIL
An example (Java) using Certificate pinning + sslSocketFactory + custom trustmanager (based on OkHttp3 CustomTrust example)
(digicert.cer contains the the 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);
}
}
}
Certificate pinning is an additional security, therefore the certificate must be trusted by the used TrustManager and it have to match the pinned certificate.
As you have disable the DigiCert CA certificates the certificate is not trusted by the sued TrustManager and therefore you are getting the SSLPeerUnverifiedException.
The behavior as I described is documented in the JavaDoc of CertificatePinner:
Note about self-signed certificates
CertificatePinner can not be used to pin self-signed certificate if such certificate is not accepted by TrustManager.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With