androidhttpsssl-certificaterenewal

Can't access website from Android app after HTTPS certificate renewing


I have an Android app using web services from an external server (that I do not control). Recently that server failed to renew its HTTPS certificate, and was unavailable for a few hours. During this time interval, a few users of my app attempted to use the services, which naturally failed. The problem is that now that the problem is fixed on the server, these users are still unable to access the website from my app. One user can't even access the website from his mobile device's browser, another one is only blocked when trying from my app.

I have limited experience with HTTPS certificates renewing, so I'd like to know what could be wrong? It seems like these devices have kept in cache the expired certificate, and do not take the new one. Reinstalling my app doesn't fix the problem.

Thanks


Solution

  • I finally found a solution, thanks to Javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: Failure in SSL library, usually a protocol error .

    First download the HTTPS certificate of the problematic website (I used Firefox to do it) and put it in your assets folder. Then extend Application, and add the following:

    public class MyApplication extends Application {
    
        private static SSLSocketFactory _sslSocketFactory = null;
    
        @Override
        public void onCreate() {
    
            super.onCreate();
    
            installSslIfNeeded();
            loadSslSocketFactoryIfNeeded();
        }
    
        @Nullable
        public static SSLSocketFactory getSslSocketFactory() {
            return _sslSocketFactory;
        }
    
        private void installSslIfNeeded() {
    
            // Install SSL certificates if needed:
            // See: https://stackoverflow.com/questions/29916962/javax-net-ssl-sslhandshakeexception-javax-net-ssl-sslprotocolexception-ssl-han
            try {
                ProviderInstaller.installIfNeeded(this);
                SSLContext sslContext;
                sslContext = SSLContext.getInstance("TLSv1.2");
                sslContext.init(null, null, null);
                sslContext.createSSLEngine();
            }
            catch (GooglePlayServicesRepairableException | GooglePlayServicesNotAvailableException | NoSuchAlgorithmException | KeyManagementException e) { e.printStackTrace(); }
    
        }
    
        private void loadSslSocketFactoryIfNeeded() {
    
            // Create a static SSL factory trusting the server's HTTPS certificate whose authority
            // is unknown for Android < 5
            // https://developer.android.com/training/articles/security-ssl
    
            if (_sslSocketFactory == null) {
    
                try {
    
                    // Load certificate:
                    CertificateFactory cf = CertificateFactory.getInstance("X.509");
                    InputStream caInput = getAssets().open("theserver.crt");
                    Certificate ca;
                    //noinspection TryFinallyCanBeTryWithResources
                    try { ca = cf.generateCertificate(caInput); }
                    finally { caInput.close(); }
    
                    // Create a KeyStore containing our trusted CAs
                    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
                    keyStore.load(null, null);
                    keyStore.setCertificateEntry("ca", ca);
    
                    // Create a TrustManager that trusts the CAs in our KeyStore
                    String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
                    TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
                    tmf.init(keyStore);
    
                    // Create an SSLContext that uses our TrustManager
                    SSLContext sslContext = SSLContext.getInstance("TLS");
                    sslContext.init(null, tmf.getTrustManagers(), null);
    
                    _sslSocketFactory = sslContext.getSocketFactory();
                }
                catch (CertificateException e) { e.printStackTrace(); }
                catch (NoSuchAlgorithmException e) { e.printStackTrace(); }
                catch (KeyStoreException e) { e.printStackTrace(); }
                catch (IOException e) { e.printStackTrace(); }
                catch (KeyManagementException e) { e.printStackTrace(); }
            }
        }
    }
    

    Now, if you want for instance to download a JSON file from a REST API, you can do it this way:

    static JSONObject readJsonFromUrl(String urlString) throws IOException, JSONException {
    
        // Tell the URLConnection to use a SocketFactory from our SSLContext
        URL url = new URL(urlString);
        HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
        urlConnection.setSSLSocketFactory(MyApplication.getSslSocketFactory());
    
        return readJsonFromInputStream(urlConnection.getInputStream());
    }
    

    If you're using a webview, you need to do the following:

    _webview.setWebViewClient(new WebViewClient() {
    
        [...]
    
        @Override
        public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
    
            final AlertDialog.Builder builder = new AlertDialog.Builder(MyActivity.this);
            builder.setMessage(R.string.my_ssl_error_message);
    
            builder.setPositiveButton(R.string.common_continue, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    handler.proceed();
                }
            });
    
            builder.setNegativeButton(R.string.common_cancel, new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    handler.cancel();
                }
            });
    
            builder.show();
        }
    });