[ACCEPTED]-Self-signed SSL acceptance on Android-self-signed

Accepted answer
Score: 37

I have this functionality in exchangeIt, which 10 connects to Microsoft exchange via WebDav. Here's 9 some code to create an HttpClient which 8 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 7 EasySSLSocketFactory is here, and the EasyX509TrustManager 6 is here.

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

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

Score: 37

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 2 that, here's another way, without any extra 1 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();
    }
}
Score: 36

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

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

I finally came to a 20 solution:

  1. Create Local KeyStore

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

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

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

Now 11 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 10 to a file where your keystore will be created. It 9 MUST NOT EXIST.

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

STOREPASS is 8 your newly created keystore password.

  1. Include KeyStore in your Application

Copy 7 your newly created keystore from PATH_TO_KEYSTORE to res/raw/certs.bks (certs.bks is 6 just the file name; you can use whatever 5 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 4 use an instance of **MyHttpClient** as you would with **DefaultHttpClient** to 3 make your HTTPS queries, and it will use 2 and validate correctly your self-signed 1 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());
}
Score: 15

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

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

I'm 8 aware of two ways to do this:

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

  2. Create a custom 6 instance of X509TrustManager and override 5 the getAcceptedIssuers method to return 4 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 3 that this code is completely untested and 2 may not even compile, but should at least 1 steer you in the right direction.

Score: 4

Brian Yarger's answer works in Android 2.2 3 as well if you modify the bigger createSocket 2 method overload as follows. It took me a 1 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);
}
Score: 0

On Android, HttpProtocolParams accepts ProtocolVersion rather than HttpVersion.

ProtocolVersion pv = new ProtocolVersion("HTTP", 1, 1);
HttpProtocolParams.setVersion(params, pv);

0

Score: 0

@Chris - Posting this as an answer since 11 I can't add comments (yet). I'm wondering 10 if your approach is supposed to work when 9 using a webView. I can't get it do so on 8 Android 2.3 - instead I just get a white 7 screen.

After some more searching, I came 6 across this simple fix for handling SSL errors in a webView which worked like a charm for 5 me.

In the handler I check to see if I'm 4 in a special dev mode and call handler.proceed(), otherwise 3 I call handler.cancel(). This allows 2 me to do development against a self-signed 1 cert on a local website.

Score: 0

There are a-lot alternatives for this use 4 case. If you don't want to have any custom 3 code in your code base such as custom TrustManager I 2 would suggest to try GitHub - SSLContext Kickstart and the following 1 code snippet:

<dependency>
    <groupId>io.github.hakky54</groupId>
    <artifactId>sslcontext-kickstart</artifactId>
    <version>6.7.0</version>
</dependency>

SSL configuration

SSLFactory sslFactory = SSLFactory.builder()
    .withUnsafeTrustMaterial()
    .build();

SSLContext sslContext = sslFactory.getSslContext();
SSLSocketFactory sslSocketFactory = sslFactory.getSslSocketFactory();

HttpClient configuration

HttpParams params = new BasicHttpParams();
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);

SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("https", sslSocketFactory, 443));

ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);

HttpClient httpClient = new DefaultHttpClient(ccm, params);

HttpsUrlConnection

HttpsURLConnection.setDefaultSSLSocketFactory(sslSocketFactory); 

More Related questions