just_user just_user - 2 months ago 61
Android Question

OkHttp javax.net.ssl.SSLPeerUnverifiedException: Hostname domain.com not verified

I've been trying for days to get this working. I'm trying to connect to my server over https with a self signed certificate. I don't think there is any pages or examples that I haven't read by now.

What I have done:


  1. Created bks keystore by following this tutorial: http://blog.crazybob.org/2010/02/android-trusting-ssl-certificates.html



It uses
openssl s_client -connect domain.com:443
to get the certificate from the server. Then creates a bks keystore using bouncy castle.


  1. Reading created keystore from raw folder adding it to sslfactory and and then to OkHttpClient. Like this:

    public ApiService() {
    mClient = new OkHttpClient();
    mClient.setConnectTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
    mClient.setReadTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
    mClient.setCache(getCache());
    mClient.setCertificatePinner(getPinnedCerts());
    mClient.setSslSocketFactory(getSSL());
    }

    protected SSLSocketFactory getSSL() {
    try {
    KeyStore trusted = KeyStore.getInstance("BKS");
    InputStream in = Beadict.getAppContext().getResources().openRawResource(R.raw.mytruststore);
    trusted.load(in, "pwd".toCharArray());
    SSLContext sslContext = SSLContext.getInstance("TLS");
    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    trustManagerFactory.init(trusted);
    sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
    return sslContext.getSocketFactory();
    } catch(Exception e) {
    e.printStackTrace();
    }
    return null;
    }

    public CertificatePinner getPinnedCerts() {
    return new CertificatePinner.Builder()
    .add("domain.com", "sha1/theSha=")
    .build();
    }

  2. This for some reason this always generates a
    SSLPeerUnverifiedException
    with or without the keystore. And with or without the
    CertificatePinner
    .

    javax.net.ssl.SSLPeerUnverifiedException: Hostname domain.com not verified: 0
    W/System.err﹕ certificate: sha1/theSha=
    W/System.err﹕ DN: 1.2.840.113549.1.9.1=#1610696e666f40626561646963742e636f6d,CN=http://domain.com,OU=development,O=domain,L=Valencia,ST=Valencia,C=ES
    W/System.err﹕ subjectAltNames: []
    W/System.err﹕ at com.squareup.okhttp.internal.http.SocketConnector.connectTls(SocketConnector.java:124)
    W/System.err﹕ at com.squareup.okhttp.Connection.connect(Connection.java:143)
    W/System.err﹕ at com.squareup.okhttp.Connection.connectAndSetOwner(Connection.java:185)
    W/System.err﹕ at com.squareup.okhttp.OkHttpClient$1.connectAndSetOwner(OkHttpClient.java:128)
    W/System.err﹕ at com.squareup.okhttp.internal.http.HttpEngine.nextConnection(HttpEngine.java:341)
    W/System.err﹕ at com.squareup.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:330)
    W/System.err﹕ at com.squareup.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:248)
    W/System.err﹕ at com.squareup.okhttp.Call.getResponse(Call.java:273)
    W/System.err﹕ at com.squareup.okhttp.Call$ApplicationInterceptorChain.proceed(Call.java:230)
    W/System.err﹕ at com.squareup.okhttp.Call.getResponseWithInterceptorChain(Call.java:201)
    W/System.err﹕ at com.squareup.okhttp.Call.execute(Call.java:81)
    ...



What am I doing wrong?

Answer

I finally got this working with a mix of multiple answers.

First, the certificates was made wrongly, not sure how. But by creating them using the script in this answer made them work. What was needed was a server certificate and a key. Then the client needed another certificate.

To use the certificate in android I converted the .pem file to a .crt file like this:

openssl x509 -outform der -in client.pem  -out client.crt

In android I added the certificate to my OkHttp client like the following:

public ApiService() {
    mClient = new OkHttpClient();
    mClient.setConnectTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
    mClient.setReadTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS);
    mClient.setCache(getCache());
    mClient.setSslSocketFactory(getSSL());
}

protected SSLSocketFactory getSSL() {
    try {
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        InputStream cert = getAppContext().getResources().openRawResource(R.raw.client);
        Certificate ca = cf.generateCertificate(cert);
        cert.close();

        // creating a KeyStore containing our trusted CAs
        String keyStoreType = KeyStore.getDefaultType();
        KeyStore keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);
        keyStore.setCertificateEntry("ca", ca);

        return new AdditionalKeyStore(keyStore);
    } catch(Exception e) {
        e.printStackTrace();
    }
    return null;
}

The last part with new AdditionalKeyStore() is taken from this very well written answer. Which adds a fallback keystore.

I hope this might help anyone else! This is the simplest way to get HTTPS working with a self-signed certificate that I have found. Other ways include having a BouncyCastle keystore which seems excessive to me.

Comments