35

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?

just_user
  • 11,769
  • 19
  • 90
  • 135
  • 3
    Please check that your certificate contains actual host name and not IP address(IP addresses are supposed to be in `Subject Alternative Name` field of certificate). As for `HostnameVerifier` which returns true - it will make SSL useless and insecure(most upvoted answer). On contemporary Androids you can install your self signed certificate without problems via Security settings. – Boris Treukhov Jul 06 '16 at 09:59

7 Answers7

32

I had the same problem, however I needed my application to work on several staging environments, all of which had self signed certs. To make matters worse, they could change those certs on the fly.

To fix this, when connecting to staging only, I added a SSLSocketFactory which trusted all certs. This fixed the java error, however it left me with the okhttp exception noted in this ticket.

To avoid this error, I needed to add one more customization to my okHttpClient. This fixed the error for me.

okHttpClient.setHostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        });
Jake Hall
  • 1,963
  • 22
  • 24
  • 26
    This is not safe! I know I didn't specify that in my question. I know it works, I have used it earlier. But I'm all grown up now and want the full safety of https. :) – just_user Sep 12 '16 at 10:21
  • 5
    of course this isn't safe -- this explicitly bypasses https certificate verification. This should only be used on pre-prod environments where you want to avoid ssl verification, never on a production build. – Jake Hall Sep 13 '16 at 21:28
  • 2
    `setHostnameVerifier` not compiling – Lavekush Agrawal Jul 06 '17 at 10:00
  • 1
    `setHostnameVerifier()` is not a method of [OkHttpClient](https://square.github.io/okhttp/3.x/okhttp/okhttp3/OkHttpClient.html). I think you're confusing it with `javax.net.ssl.HttpsURLConnection` – Mike Harris Mar 06 '19 at 16:52
  • 1
    thanks. helped me to get rid of error and do my tests on debug – Reza Aug 19 '20 at 17:48
  • This is never the way to go, don't pretend a secure connection when it is not, add a big warning! – JanPl Dec 12 '22 at 15:11
18

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.

Community
  • 1
  • 1
just_user
  • 11,769
  • 19
  • 90
  • 135
  • what is the file(client) in raw? – iSrinivasan27 Sep 12 '16 at 07:33
  • that is the `client.crt`file generated on the openssl line. – just_user Sep 12 '16 at 10:19
  • What is the client.pem file you have used here? That script generate few .pem file. Is it chain.pem file in client directory? – Chrishan Mar 22 '17 at 01:55
  • You have to create it as part of the ssl certificate you want to use for your connection: https://security.stackexchange.com/questions/32768/converting-keys-between-openssl-and-openssh – just_user Mar 22 '17 at 07:36
  • Is is safe to have the .crt file as a raw resource in your apk? – PrashanD Dec 16 '19 at 04:45
  • I have a spring-boot app (version 2.3.7) that uses okhtt3 and I'm trying to call an api in https://test.server.com:8888/api . The certificate on the server is self-signed so I have updated my carets to trust this certificate. When I m running the SSLPoke.class like this `java SSLPoke test.server.com 8888` I m getting Successfully Connected. But when the request is made from my app I m getting the `javax.net.ssl.SSLPeerUnverifiedException: Hostname test.server.com not verified:` error. Since the cacerts is updated do I need to write extra code to verify the hostname ? – Alexandrakis alexandros Feb 11 '21 at 09:36
9

This issue is solved by setting setHostNameVerifier to okHttpBuilder. Make sure verify method should return true.

Sample:

okHttpClient.setHostnameVerifier(new HostnameVerifier() {
    @Override
    public boolean verify(String hostname, SSLSession session) {
        return true;
    }
});

OkHttpClient.Builder builder = new OkHttpClient.Builder();
    builder.hostnameVerifier(new HostnameVerifier() {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    });
OkHttpClient client = builder.build();
dckuehn
  • 2,427
  • 3
  • 27
  • 37
KayKay
  • 139
  • 1
  • 2
6

Please check if CN name on the client cert is added to the Subject alternative name. I had the same issue

Rahul Goel
  • 61
  • 1
  • 1
  • 1
    Thank you so much, in particular the Subject alternative name _must_ be set if it is an IP address instead of a hostname: "In some cases, the URI is specified as an IP address rather than a hostname. In this case, the iPAddress subjectAltName must be present in the certificate and must exactly match the IP in the URI." https://tools.ietf.org/html/rfc2818#section-3.1 – Bas Jan 19 '19 at 13:33
5

During cert generation the subjectAltName must be set if the uri is an ip to not fall through validation.

"In some cases, the URI is specified as an IP address rather than a hostname. In this case, the iPAddress subjectAltName must be present in the certificate and must exactly match the IP in the URI." RFC (mentioned by Bas in comment)

Instead of fiddeling client side with HostnameVerifier or else, reiusse the self-signed cert (which we have control over) via:

openssl req \
-newkey rsa:2048 \
-nodes \
-x509 \
-days 36500 -nodes \
-addext "subjectAltName = IP.1:1.2.3.4" \
-keyout /etc/ssl/private/nginx-selfsigned2.key \
-out /etc/ssl/certs/nginx-selfsigned2.crt

Addon, if on android one also needs to trust the cert:

the crt is pem format and can be imported into android via
<?xml version="1.0" encoding="utf-8"?>
    <base-config cleartextTrafficPermitted="false">
        <trust-anchors>
            <certificates src="@raw/nginx_selfsigned2" />
            <certificates src="system" />
        </trust-anchors>
    </base-config>
</network-security-config>

Thus we verify the cert is from a trusted source And previously by hostname verification (via SAN) ensured the server we talk to presents the right cert for his ip.

more here: https://developer.android.com/training/articles/security-config https://developer.android.com/training/articles/security-ssl.html#SelfSigned

Community
  • 1
  • 1
droid192
  • 2,011
  • 26
  • 43
0

If you are used network_security_config in res/xml for solve Cleartext HTTP traffic not permitted and you changed domain/IP address remember that you should change includeSubdomains value to the new address. This worked for me.

  <network-security-config>
    <domain-config cleartextTrafficPermitted="true">
      <domain includeSubdomains="true">yourDomain.com</domain>
    </domain-config>
  </network-security-config>
Pooya Chavoshi
  • 346
  • 4
  • 12
0

Using HTTPS instead of HTTP also causes this error.

When changing your URL to HTTP don't forget to add android:usesCleartextTraffic="true" on AndroidManifest file inside application tag.

adwardwo1f
  • 817
  • 6
  • 18