3

I have a simple web api running on a raspberry pi that sits behind an nginx server on the same pi. I'm using self-signed client certificates to authenticate calls from an android app. This worked completely fine in the past, but I recently came back to this project after rebuilding some hardware, and when I try and use it on my Pixel 2 running Android 8.1, it gives the following exception:

javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
    at com.android.org.conscrypt.ConscryptFileDescriptorSocket.startHandshake(ConscryptFileDescriptorSocket.java:219)
    at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.java:268)
    ...

I generated certs and keys according to: http://nategood.com/client-side-certificate-authentication-in-ngi

Testing with curl works fine.

I created keystore for the app to use with:

openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12

I followed the following article to setup the client certs in Android and connect to the server: http://chariotsolutions.com/blog/post/https-with-client-certificates-on/

but I rewrote it in Kotlin, using OkHttp:

private const val SERVER = "https://my.server"

/**
* trustManagers is used to authorize the server's self-signed cert
*/
private val trustManagers by lazy {
    val cert = CertificateFactory.getInstance("X.509")
            .generateCertificate(appCtx.assets.open("ca.crt")) as X509Certificate

    val trustStore = KeyStore.getInstance(KeyStore.getDefaultType()).apply {
        load(null, null)
        setCertificateEntry(cert.subjectX500Principal.name, cert)
    }

    TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply {
        init(trustStore)
    }.trustManagers
}

/**
* keyManagers is used to load the client-authentication cert
*/
private val keyManagers by lazy {
    // assuming this can only be called after Application is created

    val keyStore = KeyStore.getInstance("PKCS12").apply {
        load(appCtx.assets.open("client.p12"), "".toCharArray())
    }

    KeyManagerFactory.getInstance("X509").apply {
        init(keyStore, "".toCharArray())
    }.keyManagers
}

/**
* sslContext for opening TLS connection to server
*/
private val sslContext by lazy {
    SSLContext.getInstance("TLS").apply {
        init(keyManagers, trustManagers, null)
    }
}

/**
* pass an HTTPS request to server
*/
suspend fun request(url: String): ByteArray? {
    return try {
        val request = Request.Builder()
                .url(url)
                .build()
        val client = OkHttpClient.Builder()
                .sslSocketFactory(sslContext.socketFactory, trustManagers[0] as X509TrustManager)
                .build()
        client.newCall(request).await().body().byteStream().readBytes()
    } catch (e: Exception) {
        err(e) { "failed to send request" }
        null
    }
}

This use to work, but now it does not. I spent a day and a half searching for an answer and I've tried the following:

  • I've tried using HttpURLConnection instead of OkHttp
  • I've tried re-creating all the certs/keys from scratch.
  • I've tried using the new "Network Security Configuration":

<?xml version="1.0" encoding="utf-8"?> <network-security-config> <base-config> <trust-anchors> <!-- Additionally trust user added CAs --> <certificates src="user" /> <certificates src="@raw/ca"/> </trust-anchors> </base-config> </network-security-config>

I've read all the examples I can find on creating custom trust managers, and they are all pretty much the same, even https://developer.android.com/training/articles/security-ssl.html#UnknownCa

Everythign I've tried produces the same exception, am I missing something?

bj0
  • 7,893
  • 5
  • 38
  • 49
  • Note that you need to register that network security configuration XML in the manifest. FWIW, [this is what I used for testing self-signed certs with network security configuration](https://stackoverflow.com/q/36552316/115145). – CommonsWare Feb 25 '18 at 23:50
  • I did include in the manifest, I guess I left it out of my question though. – bj0 Feb 25 '18 at 23:57
  • Hi bj0 i am also facing same issue. Any solution ? – Firnaz May 15 '18 at 12:46
  • I haven't been able to get it to work with a CA, but as a workaround you can just use the server cert directly and it works. – bj0 May 18 '18 at 08:24

1 Answers1

0

This is how I was able to get it to work in my project, using a self-signed cert on GoDaddy:

  1. In your project, in Android Studio, create a new directory named 'raw' (no single quotes)
  2. Create a new text file in the raw folder with any name and the .crt extension (IE gdCert.crt)
  3. On the GoDaddy cPanel, under Security, click 'SSL/TLS'. Click the link under 'Certificates' and then it should show you the certificate you have already created. Click the 'Edit' link. Select and copy the contents of the certificate and paste that into the text file you created in the Android Studio 'raw' folder and save it.
  4. In Android Studio, right click the 'res' folder, New > XML > App Actions XML File. Name this file 'network_security_config' (no single quotes)
  5. Add the following to the 'network_security_config.xml' file:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <debug-overrides>
        <trust-anchors>
            <certificates src="user"/>
            <certificates src="@raw/gdcert"/>
        </trust-anchors>
    </debug-overrides>
</network-security-config>
  1. In your AndroidManifest.xml file, add the following under the <application node:

android:networkSecurityConfig="@xml/network_security_config"
  1. Use the following java code in your solution:

SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
SSLSocketFactory ssf = sc.getSocketFactory();
SSLSocket sslsocket = (SSLSocket) ssf.createSocket("yourwebsitename.com", 443);

Followed by whatever code you are trying to run to connect to the server.

  1. Note that the second line calls a new method which should be added to your class:

TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager() {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {

        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }
    }};

Save your project and run it.

Will Buffington
  • 1,048
  • 11
  • 13