问题描述:

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:

  1. Both .sslSocketFactory(getSSLSocketFactory()) and .certificatePinner(certificatePinner) removed;
  2. Only .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)
  3. Only .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:

  1. The same as the link at the beginning in my question (actually his 1st issue);
  2. Why does my app get 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!

网友答案:

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 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

  • If you dont use Certificate pinning and your default system certificate trustore is working (with the Digicert High Assurance EV Root CA) your connection will be OK
  • If you dont use Certificate pinning and your default system certificate trustore is not working your connection will FAIL
  • If you use Certificate pinning and your default system certificate trustore is working (with the Digicert High Assurance EV Root CA) your connection will be OK
  • If you use Certificate pinning and your default system certificate trustore is not working your connection will FAIL
  • If you use 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
  • If you use 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);
        }
    }
}
相关阅读:
Top