Android Self-signed certificate cannot be verified without a previous internet connection

lukaskrieger picture lukaskrieger · Aug 12, 2014 · Viewed 9k times · Source

Working SSL infrastructure:

We have a working client/server setup where phones with the Android versions 4.2 and 4.4 act as clients that have to verify a server by its self-signed SSL certificate.

The problem:

The server certificate verification works as long as the devices have had internet access at least once before attempting to connect. However, if a factory reset is performed and the devices directly connect to a private network without internet connection the certificate verification fails.

To reproduce the behavior:

  1. Factory reset the phone
  2. Restart without choosing to connect to a WiFi with internet access
  3. Try to verify a self-signed SSL certificate -> FAILS
  4. Connect to a WiFi with internet access
  5. Reconnect to the original private network
  6. Try to verify a self-signed SSL certificate -> WORKS

Technically, the devices should not need internet access to verify the self signed certificate. Could there be some kind of a blacklist that has to be loaded once before any SSL server verification can take place? And can I prevent this behavior?

Creating the SSL context:

    //Using a client certificate
    String password = "clientpass";
    KeyStore keyStore = KeyStore.getInstance("PKCS12");
    InputStream is = context.getResources().openRawResource(R.raw.client);
    keyStore.load(is, password.toCharArray());
    is.close();
    KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
    kmf.init(keyStore, password.toCharArray());
    KeyManager[] keyManagers = kmf.getKeyManagers();


    // Using self signed certificate
    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    is = context.getResources().openRawResource(R.raw.cacert);
    InputStream caInput = new BufferedInputStream(is);
    Certificate ca;
    try {
        ca = cf.generateCertificate(caInput);
        Log.i("CA","ca=" + ((X509Certificate) ca).getSubjectDN());
    } finally {
        caInput.close();
    }

    // Create a KeyStore containing our trusted CAs
    KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
    trustStore.load(null);
    trustStore.setCertificateEntry("ca", ca);

    // Create a TrustManager that trusts the CAs in our KeyStore
    TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
    tmf.init(trustStore);
    TrustManager[] trustManagers = tmf.getTrustManagers();

    // Create an SSLContext that uses our Trustmanager and Keymanager
    SSLContext sslcontext = SSLContext.getInstance("TLS");

    sslcontext.init(keyManagers, trustManagers, null);

    //create a socket to connect with the server
    SSLSocketFactory socketFactory = sslContext.getSocketFactory();
    SSLSocket socket = (SSLSocket) socketFactory.createSocket(serverAddr, port);
    socket.setUseClientMode(true);
    socket.addHandshakeCompletedListener(this);
    socket.startHandshake();

Fails with the exception in startHandshake:

javax.net.ssl.SSLHandshakeException: com.android.org.bouncycastle.jce.exception.ExtCertPathValidatorException: Could not     validate certificate: null

Answer

Nikolay Elenkov picture Nikolay Elenkov · Aug 14, 2014

Make sure the time on the device is correct, certificates have a validity period and won't validate if the date is set to the past (usually Jan 1st, 2000 after a factory reset) or future. The device will automatically sync via NTP, but that obviously doesn't work when there is no usable Internet connection.