Self-signed SSL acceptance on Android

83,904

Solution 1

I have this functionality in exchangeIt, which connects to Microsoft exchange via WebDav. Here's some code to create an HttpClient which will connect to self signed cert's via SSL:

SchemeRegistry schemeRegistry = new SchemeRegistry();
// http scheme
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
// https scheme
schemeRegistry.register(new Scheme("https", new EasySSLSocketFactory(), 443));

HttpParams params = new BasicHttpParams();
params.setParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 30);
params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new ConnPerRouteBean(30));
params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false);
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);

ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);

The EasySSLSocketFactory is here, and the EasyX509TrustManager is here.

The code for exchangeIt is open source, and hosted on googlecode here, if you have any issues. I'm not actively working on it anymore, but the code should work.

Note that since Android 2.2 the process has changed a bit, so check this to make the code above work.

Solution 2

As EJP correctly commented, "Readers should note that this technique is radically insecure. SSL is not secure unless at least one peer is authenticated. See RFC 2246."

Having said that, here's another way, without any extra classes:

import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.X509TrustManager;

private void trustEveryone() {
    try {
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier(){
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }});
        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, new X509TrustManager[]{new X509TrustManager(){
            public void checkClientTrusted(X509Certificate[] chain,
                    String authType) throws CertificateException {}
            public void checkServerTrusted(X509Certificate[] chain,
                    String authType) throws CertificateException {}
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }}}, new SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(
                context.getSocketFactory());
    } catch (Exception e) { // should never happen
        e.printStackTrace();
    }
}

Solution 3

I faced this issue yesterday, while migrating our company's RESTful API to HTTPS, but using self-signed SSL certificates.

I've looking everywhere, but all the "correct" marked answers I've found consisted of disabling certificate validation, clearly overriding all the sense of SSL.

I finally came to a solution:

  1. Create Local KeyStore

    To enable your app to validate your self-signed certificates, you need to provide a custom keystore with the certificates in a manner that Android can trust your endpoint.

The format for such custom keystores is "BKS" from BouncyCastle, so you need the 1.46 version of BouncyCastleProvider that you can download here.

You also need your self-signed certificate, I will assume it's named self_cert.pem.

Now the command for creating your keystore is:

<!-- language: lang-sh -->

    $ keytool -import -v -trustcacerts -alias 0 \
    -file *PATH_TO_SELF_CERT.PEM* \
    -keystore *PATH_TO_KEYSTORE* \
    -storetype BKS \
    -provider org.bouncycastle.jce.provider.BouncyCastleProvider \
    -providerpath *PATH_TO_bcprov-jdk15on-146.jar* \
    -storepass *STOREPASS*

PATH_TO_KEYSTORE points to a file where your keystore will be created. It MUST NOT EXIST.

PATH_TO_bcprov-jdk15on-146.jar.JAR is the path to the downloaded .jar libary.

STOREPASS is your newly created keystore password.

  1. Include KeyStore in your Application

Copy your newly created keystore from PATH_TO_KEYSTORE to res/raw/certs.bks (certs.bks is just the file name; you can use whatever name you wish)

Create a key in res/values/strings.xml with

<!-- language: lang-xml -->

    <resources>
    ...
        <string name="store_pass">*STOREPASS*</string>
    ...
    </resources>
  1. Create a this class that inherits DefaultHttpClient

    import android.content.Context;
    import android.util.Log;
    import org.apache.http.conn.scheme.PlainSocketFactory;
    import org.apache.http.conn.scheme.Scheme;
    import org.apache.http.conn.scheme.SchemeRegistry;
    import org.apache.http.conn.ssl.SSLSocketFactory;
    import org.apache.http.impl.client.DefaultHttpClient;
    import org.apache.http.params.HttpParams;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.security.*;
    
    public class MyHttpClient extends DefaultHttpClient {
    
        private static Context appContext = null;
        private static HttpParams params = null;
        private static SchemeRegistry schmReg = null;
        private static Scheme httpsScheme = null;
        private static Scheme httpScheme = null;
        private static String TAG = "MyHttpClient";
    
        public MyHttpClient(Context myContext) {
    
            appContext = myContext;
    
            if (httpScheme == null || httpsScheme == null) {
                httpScheme = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80);
                httpsScheme = new Scheme("https", mySSLSocketFactory(), 443);
            }
    
            getConnectionManager().getSchemeRegistry().register(httpScheme);
            getConnectionManager().getSchemeRegistry().register(httpsScheme);
    
        }
    
        private SSLSocketFactory mySSLSocketFactory() {
            SSLSocketFactory ret = null;
            try {
                final KeyStore ks = KeyStore.getInstance("BKS");
    
                final InputStream inputStream = appContext.getResources().openRawResource(R.raw.certs);
    
                ks.load(inputStream, appContext.getString(R.string.store_pass).toCharArray());
                inputStream.close();
    
                ret = new SSLSocketFactory(ks);
            } catch (UnrecoverableKeyException ex) {
                Log.d(TAG, ex.getMessage());
            } catch (KeyStoreException ex) {
                Log.d(TAG, ex.getMessage());
            } catch (KeyManagementException ex) {
                Log.d(TAG, ex.getMessage());
            } catch (NoSuchAlgorithmException ex) {
                Log.d(TAG, ex.getMessage());
            } catch (IOException ex) {
                Log.d(TAG, ex.getMessage());
            } catch (Exception ex) {
                Log.d(TAG, ex.getMessage());
            } finally {
                return ret;
            }
        }
    }
    

Now simply use an instance of **MyHttpClient** as you would with **DefaultHttpClient** to make your HTTPS queries, and it will use and validate correctly your self-signed SSL certificates.

HttpResponse httpResponse;

HttpPost httpQuery = new HttpPost("https://yourserver.com");
... set up your query ...

MyHttpClient myClient = new MyHttpClient(myContext);

try{

    httpResponse = myClient.(peticionHttp);

    // Check for 200 OK code
    if (httpResponse.getStatusLine().getStatusCode() == HttpURLConnection.HTTP_OK) {
        ... do whatever you want with your response ...
    }

}catch (Exception ex){
    Log.d("httpError", ex.getMessage());
}

Solution 4

Unless I missed something, the other answers on this page are DANGEROUS, and are functionally equivalent to not using SSL at all. If you trust self-signed certificates without doing further checks to make sure the certificates are the ones that you are expecting, then anyone can create a self-signed certificate and can pretend to be your server. At that point, you have no real security.

The only legitimate way to do this (without writing a full SSL stack) is to add an additional trusted anchor to be trusted during the certificate verification process. Both involve hard-coding the trusted anchor certificate into your app and adding it to whatever trusted anchors that the OS provides (or else you won't be able to connect to your site if you get a real certificate).

I'm aware of two ways to do this:

  1. Create a custom trust store as described at http://www.ibm.com/developerworks/java/library/j-customssl/#8

  2. Create a custom instance of X509TrustManager and override the getAcceptedIssuers method to return an array that contains your certificate:

    public X509Certificate[] getAcceptedIssuers()
    {
        X509Certificate[] trustedAnchors =
            super.getAcceptedIssuers();
    
        /* Create a new array with room for an additional trusted certificate. */
        X509Certificate[] myTrustedAnchors = new X509Certificate[trustedAnchors.length + 1];
        System.arraycopy(trustedAnchors, 0, myTrustedAnchors, 0, trustedAnchors.length);  
    
        /* Load your certificate.
    
           Thanks to http://stackoverflow.com/questions/11857417/x509trustmanager-override-without-allowing-all-certs
           for this bit.
         */
        InputStream inStream = new FileInputStream("fileName-of-cert");
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        X509Certificate cert = (X509Certificate)cf.generateCertificate(inStream);
        inStream.close();
    
        /* Add your anchor cert as the last item in the array. */
        myTrustedAnchors[trustedAnchors.length] = cert;
    
        return myTrustedAnchors;
    }
    

Note that this code is completely untested and may not even compile, but should at least steer you in the right direction.

Solution 5

Brian Yarger's answer works in Android 2.2 as well if you modify the bigger createSocket method overload as follows. It took me a while to get self-signed SSLs working.

public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
    return getSSLContext().getSocketFactory().createSocket(socket, host, port, autoClose);
}
Share:
83,904
Faisal Abid
Author by

Faisal Abid

Faisal is a Google Developer Expert, Entrepreneur, and Engineer. He is a programming language enthusiast and loves solving software engineering challenges across the stack. Currently, Faisal is the Co-Founder @ dydx. At any point during the day, you can find Faisal writing mobile apps in Flutter, building backends in Dart, Node.js or Rust, creating models with Tensorflow or deploying services with Kubernetes.

Updated on July 09, 2022

Comments

  • Faisal Abid
    Faisal Abid almost 2 years

    How do I accept a self-signed certificate in Java on Android?

    A code sample would be perfect.

    I've looked everywhere on the Internet and while some people claim to have found the solution, it either does not work or there is no sample code to back it up.

  • Faisal Abid
    Faisal Abid over 14 years
    Where would you call this method?
  • Chris Boyle
    Chris Boyle over 14 years
    You would call it anywhere before opening an https connection. Any connection using URL.openConnection / HttpsURLConnection should be affected.
  • Felix
    Felix over 13 years
    I can't get this to work. I keep getting IOException: SSL handshake failure: I/O error during system call, Broken pipe on 2.2. Do you happen to know a way around this?
  • Nicolas
    Nicolas over 13 years
    I don't like trusting ANY self-signed certificate. Is there a way to add a Certificate Authority so as to prevent Man-in-the-middle attacks?
  • sgarman
    sgarman almost 13 years
    I could not get any solution to work, except this. It's a terrible hack but thanks.
  • Someone Somewhere
    Someone Somewhere about 12 years
    are the three params.setParameter() required ? The third one will require use of HTTP 1.1
  • Abdullah Adam
    Abdullah Adam about 12 years
    can we use this procedure for normal hosting and for facebook applications
  • user207421
    user207421 about 12 years
    Readers should note that this technique is radically insecure. SSL is not secure unless at least one peer is authenticated. See RFC 2246.
  • user567879
    user567879 over 11 years
    A hack without security is bad :)
  • Peterdk
    Peterdk over 11 years
    There was some stuff in the news lately about android developers not checking their certificates. So this looks good.
  • Peterdk
    Peterdk over 11 years
    Well, turns out X509TrustManager is a interface, so you can't subclass it.
  • withoutclass
    withoutclass over 11 years
    Like @NicolasMarchildon stated, this is dangerous since it exposes your application to man in the middle attacks. Here is a good explanation for you on Cert pinning.
  • dgatwood
    dgatwood about 11 years
    You can create your own trust manager. publib.boulder.ibm.com/infocenter/javasdk/v6r0/…
  • xarlymg89
    xarlymg89 about 11 years
    I cannot believe that somebody used the same (DefaultHttpClient). I really appreciate your effort, I'm gonna try to adapt it with my Android Project. Thanks!!!
  • Frederic Yesid Peña Sánchez
    Frederic Yesid Peña Sánchez about 11 years
    @CarlosAlbertoMartínezGadea proximamente actualizaré la clase completa, con algunas mejoras. || soon i will update my class with some improvements.
  • xarlymg89
    xarlymg89 about 11 years
    I used a class very similar to yours with some updates. It's the one from stackoverflow.com/questions/4065379/…
  • Rafe
    Rafe almost 11 years
    To add to @Peterdk 's first comment: see this paper called "Why Eve and Mallory Love Android: An Analysis of Android SSL (In)Security".
  • Admin
    Admin over 10 years
    Creating the keystore for secure connections using a program downloaded over an insecure connection misses the point.
  • Frederic Yesid Peña Sánchez
    Frederic Yesid Peña Sánchez over 10 years
    @FredrikPortström I don't get it, i've could downloaded BouncyCastle over an ssh and still being insecure, the point is to guarantee the security between the App<->Server channel...
  • Jake
    Jake almost 10 years
    @FredericYesidPeñaSánchez I am using your code in a project of my mine. I want to know whether the keystore step is to just trust the certificate without validating it ? Are your steps what certificate pinning does ? Also, if you could, please tell how certificate validation can be added to your code ?
  • Frederic Yesid Peña Sánchez
    Frederic Yesid Peña Sánchez almost 10 years
    Basically this allows to secure the connection between your server and your device, regardless of your cert being self-signed or issued by trusthworthy CA. I think (may be wrong) you don't neet validating your cert, as you are explicitly asking and expecting YOUR server certificate (the same included in your app)
  • Nativ
    Nativ almost 10 years
    @Brian Yarger can you please explain why you registered "http" to port 80? tnx
  • Octavian
    Octavian over 9 years
    It is a bad idea to disable SSL verification.
  • Prerak Sola
    Prerak Sola almost 9 years
    The file self_cert.pem, is it the self signed certificate used by the server ? Or I will have to generate a new one for my android app? And if it is generating the new one, how will my server trust it? Are there any parameters that should be same in both the self-signed certificates?
  • Frederic Yesid Peña Sánchez
    Frederic Yesid Peña Sánchez almost 9 years
    @PrerakSola yes, it is the server certificate that your app will trust.
  • Prerak Sola
    Prerak Sola almost 9 years
    Okay. Thank you very much for the answer.
  • portfoliobuilder
    portfoliobuilder almost 9 years
    How necessary is this, really? Seems like this is something that should be common practice.
  • dgatwood
    dgatwood almost 9 years
    Now necessary is what?
  • zys
    zys about 8 years
  • Rafael Ruiz Muñoz
    Rafael Ruiz Muñoz over 7 years
    What happens when I will need to renew my certificate? will this still work? Thanks
  • Frederic Yesid Peña Sánchez
    Frederic Yesid Peña Sánchez over 7 years
    Well, that implies you also need to update your app to use the new certificate.
  • John Calcote
    John Calcote almost 2 years
    pointless answer as link is broken