Using a Custom Certificate Trust Store on Android

As mentioned in a previous post, Android 4.0 (ICS) adds both a system UI and SDK API's that let you add certificates to the system trust store. On all previous version though, the system trust store is read-only and there is no way to add certificates on non-rooted devices. Therefore, if you want to connect to a server that is using a certificate not signed by one of the CA's included in the system trust store (including a self-signed one), you need to create and use a private trust store for the application. That is not particularly hard to do, but 'how to connect to a server with a self-signed certificate' is one of the most asked Android questions on StackOverflow, and the usual answer goes along the lines of 'simply trust all certificates and you are done'. While this will indeed let you connect, and might be OK for testing, it defeats the whole purpose of using HTTPS: your connection might be encrypted but you have no way of knowing who you are talking to. This opens the door to man-in-the-middle (MITM) attacks, and, needless to say, is bad practice. In this post we will explore how Android's HTTPS system works pre-ICS and show how to create and use a custom certificate trust store and a dynamically configurable TrustManager.

Some background: JSSE

Java, and by extension Android, implement SSL using a framework called Java Secure Socket Extension (JSSE). A discussion of how SSL and JSSE work is beyond the scope of this post, but you can find a shot introduction to SSL in the context of JSSE here. In brief, SSL provides both privacy and data integrity (i.e., an encrypted communications channel) and authentication of the parties involved. Authentication is implemented using public key cryptography and certificates. Each party presents their certificate, and if the other party trusts it, they negotiate a shared key to encrypt communications using the associated key pairs (public and private). JSSE delegates trust decisions to a TrustManager class, and authentication key selection to a KeyManager class. Each SSLSocket instance created via JSSE has access to those classes via the associated SSLContext (you can find a pretty picture here). Each TrustManager has a set of trusted CA certificates (trust anchors) and makes trust decisions based on those: if the target party's certificate is issued by one of the trusted CA's, it is considered trusted itself.

One way to specify the trust anchors is to add the CA certificates to a Java key store file, referred to as a 'trust store'. The default JSSE TrustManager is initialized using the system trust store which is generally a single key store file, saved to a system location and pre-populated with a set of major commercial and government CA certificates. If you want to change this, you need to create an appropriately configured TrustManager instance, either via a TrustManagerFactory, or by directly implementing the X509TrustManager interface. To make the general case where one just wants to use their own key store file to initialize the default TrustManager and/or KeyManager, JSSE provides a set of system properties to specify the files to use.

Android and javax.net.ssl.trustStore

If you want to specify your own system trust store file in desktop Java, it is just a matter of setting a value to the javax.net.ssl.trustStore system property when starting the program (usually using the -D JVM command line parameter). This property is also supported on Android, but things work a little differently. If you print the value of the property it will most likely be /system/etc/security/cacerts.bks, the system trust store file (pre-ICS; the property is not set on ICS). This value is used to intialize the default TrustManagerFactory, which in turn creates an X.509 certificate-based TrustManager. You can print the current trust anchors like this:

TrustManagerFactory tmf = TrustManagerFactory
                    .getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init((KeyStore) null);
X509TrustManager xtm = (X509TrustManager) tmf.getTrustManagers()[0];
for (X509Certificate cert : xtm.getAcceptedIssuers()) {
    String certStr = "S:" + cert.getSubjectDN().getName() + "\nI:"
                        + cert.getIssuerDN().getName();
    Log.d(TAG, certStr);
}

If you now use System.setProperty() to point the property to your own trust store file, and run the above code again, you will see that it outputs the certificates in the specified file. Check the 'Set javax.net.ssl.trustStore' checkbox and use the 'Dump trusted certs' button of the sample app to try it.


If we can change the set of trusted certificates using this property, connecting to a server using a custom certificate should be easy, right? It turns out this is not the case. You can try it yourself using the sample app: pressing 'Default Connect' will result in a 'Trust anchor for certificate path not found' error regardless of the state of the 'Set javax.net.ssl.trustStore' checkbox. A little further investigation reveals that the default SSLContext is already initialized with the system trust anchors and setting the javax.net.ssl.trustStore property does not change this. Why? Because Android pre-loads system classes, and by the time your application starts, the default SSLContext is already initialized. Of course, any TrustManager's you create after setting the property will pick it up (see above).

Using your own trust store: HttpClient

Since we can't use the 'easy way' on Android, we need to specify the trust store to use programmatically. This is not hard either, but first we need to create a key store file with the certificates we need. The sample project contains a shell script that does this automatically. All you need is a recent Bouncy Castle jar file and the openssl command (usually available on Linux systems).  Drop the jar and a certificate (in PEM format) in the script's directory and run it like this:

$ ./importcert.sh cacert.pem

This will calculate the certificate subject's hash and use it as the alias in a Bouncy Castle key store file (BKS format) created in the application's raw/ resource directory. The script deletes the key store file if it already exists, but you can easily modify it to append certificates instead. If you are not the command-line type, you can use the Portecle GUI utility to create the key store file.

Apache's HttpClient provides a convenient SSLSocketFactory class that can be directly initialized with a trust store file (and a key store file if client authentication is needed). All you need to do is to register it in the scheme registry to handle the https scheme:

KeyStore localTrustStore = KeyStore.getInstance("BKS");
InputStream in = getResources().openRawResource(R.raw.mytruststore);
localTrustStore.load(in, TRUSTSTORE_PASSWORD.toCharArray());

SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", PlainSocketFactory
                .getSocketFactory(), 80));
SSLSocketFactory sslSocketFactory = new SSLSocketFactory(trustStore);
schemeRegistry.register(new Scheme("https", sslSocketFactory, 443));
HttpParams params = new BasicHttpParams();
ClientConnectionManager cm = 
    new ThreadSafeClientConnManager(params, schemeRegistry);

HttpClient client = new DefaultHttpClient(cm, params);

Once initialized like this, the HttpClient instance will use our local trust store when verifying server certificates. If you need to use client authentication as well, just load and pass the key store containing the client's private key and certificate to the appropriate SSLSocketFactory constructor. See the sample project for details and use the 'HttpClient SSLSocketFactory Connect' button to test. Note that, when initialized like this, our HttpClient will use only the certificates in the specified file, completely ignoring the system trust store. Thus connections to say, https://google.com will fail. We will address this later.

Using your own trust store: HttpsURLConnection

Another popular HTTPS API on Android is HttpsURLConnection. Despite the not particularly flexible or expressive interface, apparently this is the preferred API from Android 2.3 (Gingerbread) and on. Whether to actually use is it is, of course, entirely up to you :) It uses JSSE to connect via HTTPS, so initializing it with our own trust and/or key store involves creating and initializing an SSLContext (HttpClient's SSLSocketFactory does this behind the scenes):

KeyStore trustStore = loadTrustStore();
KeyStore keyStore = loadKeyStore();

TrustManagerFactory tmf = TrustManagerFactory
                    .getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);

KeyManagerFactory kmf = KeyManagerFactory
                    .getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, KEYSTORE_PASSWORD.toCharArray());

SSLContext sslCtx = SSLContext.getInstance("TLS");
sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

URL url = new URL("https://myserver.com");
HttpsURLConnection urlConnection = (HttpsURLConnection) url
urlConnection.setSSLSocketFactory(sslCtx.getSocketFactory());

In this example we are using both a trust store and a key store, but if you don't need client authentication, you can just pass null as the first parameter of SSLContext.init().

Creating a dynamic TrustManager

As mentioned above, a TrustManager initialized with a custom trust store will only use the certificates in that store as trust anchors: the system defaults will be completely ignored. Sometimes this is all that is needed, but if you need to connect to both your own server and other public servers that use HTTPS (such as Twitter, for example), you will need to create two separate instances of HttpClient or HttpsURLConnection and switch between the two. Additionally, since the trust store is stored as an application resource, there is no way to add trusted certificates dynamically, you need to repackage the application to update the trust anchors. Certainly we can do better than that. The first problem is easily addressed by creating a custom TrustManager that delegates certificate checks to the system default one and uses the local trust store if verification fails. Here's how this looks like:

public class MyTrustManager implements X509TrustManager {

    private X509TrustManager defaultTrustManager;
    private X509TrustManager localTrustManager;

    private X509Certificate[] acceptedIssuers;

    public MyTrustManager(KeyStore localKeyStore) { 
      // init defaultTrustManager using the system defaults
      // init localTrustManager using localKeyStore
    }

    public void checkServerTrusted(X509Certificate[] chain, String authType)
            throws CertificateException {
        try {
            defaultTrustManager.checkServerTrusted(chain, authType);
        } catch (CertificateException ce) {
            localTrustManager.checkServerTrusted(chain, authType);
        }
    }
    
    //...
}

To address the second problem, we simply copy the trust store to internal storage when we first start the application and use that file to initialize our TrustManager's. Since the file is owned by the application, you can easily add and remove trusted certificates. To test modifying the trust store works, copy a certificate file(s) in DER format to the SD card (external storage) root and use the sample application's 'Add certs' and 'Remove certs' menus to add or remove it to/from the local trust store file. You can then verify the contents of the file by using the 'Dump trusted certs' button (don't forget to check 'Set javax.net.ssl.trustStore'). To implement this the app simply uses the JCE KeyStore API to add or remove certificates and save the trust store file:

CertificateFactory cf = CertificateFactory.getInstance("X509");
InputStream is = new BufferedInputStream(new FileInputStream(certFile));
X509Certificate cert = (X509Certificate) cf.generateCertificate(is);
String alias = hashName(cert.getSubjectX500Principal());
localTrustStore.setCertificateEntry(alias, cert);

FileOutputStream out = new FileOutputStream(localTrustStoreFile);
localTrustStore.store(out, TRUSTSTORE_PASSWORD.toCharArray());

Using our MyTrustManager with HttpsURLConnection is not much different than using the default one:

MyTrustManager myTrustManager = new MyTrustManager(localTrustStore);
TrustManager[] tms = new TrustManager[] { myTrustManager };
SSLContext sslCtx = SSLContext.getInstance("TLS");
context.init(null, tms, null);

HttpsURLConnection urlConnection = (HttpsURLConnection) url
                            .openConnection();
urlConnection.setSSLSocketFactory(sslCtx.getSocketFactory());

HttpClient's SSLSocketFactory doesn't let us specify a custom TrustManager, so we need to create our own SocketFactory. To make initialization consistent with that of HttpsURLConnection, we have it take an already initialized SSLContext as a parameter and use it to get a factory that lets us create SSL sockets as needed:

public class MySSLSocketFactory implements LayeredSocketFactory {

    private SSLSocketFactory socketFactory;
    private X509HostnameVerifier hostnameVerifier;

    public MySSLSocketFactory(SSLContext sslCtx,
            X509HostnameVerifier hostnameVerifier) {
        this.socketFactory = sslCtx.getSocketFactory();
        this.hostnameVerifier = hostnameVerifier;
    }

    //..

    @Override
    public Socket createSocket() throws IOException {
        return socketFactory.createSocket();
    }
}

Initializing an HttpClient instance is now simply a matter of registering our socket factory for the https scheme:

SSLContext sslContext = createSslContext();
MySSLSocketFactory socketFactory = new MySSLSocketFactory(
                       sslContext, new BrowserCompatHostnameVerifier());
schemeRegistry.register(new Scheme("https", sslSocketFactory, 443));

You can check that this actually works with the 'HttpClient Connect' and 'HttpsURLConnection Connect' buttons of the sample application. Both clients are using our custom TrustManager outlined above and trust anchors are loaded dynamically: adding and removing certificates via the menu will directly influence whether you can connect to the target server.

Summary

We've shown how the default TrustManager on pre-ICS Android devices works and how to set up both HttpClient and HttpsURLConnection to use a local (application-scoped) trust and/or key store. In addition, the sample app provides a custom TrustManager implementation that both extends the system one, and supports dynamically adding and removing application-specified trust anchors. While this is not as flexible as the system-wide trust store introduced in ICS, it should be sufficient for most applications that need to manage their own SSL trust store. Do use those examples as a starting point and please do not use any of the trust-all 'solutions' that pop up on StackOverflow every other day.

Comments

Sebastien said…
Hello, Thank you for all explication. I have one question : i need to develop an application wich connect to many server using https. So I can't put the cert manually on the SD card. Is it possible to propose to the user to accept the certificate and then add it into the trust store like the firefox application I think .. ? Thank you
Nikolay Elenkov said…
Sure, you just need to catch the CertificateException and save the certificate somewhere. Then parse it, show a dialog displaying certificate info and ask the user asking if they want to accept it permanently. If they do, add it to the trust store and retry the connection.
Sebastien said…
Thank you for you response I will try to implement it this week end. You help me to understand how Android works with SSL and it is very helpful, Thank you !
MCruiseOn said…
Amazing blog. Bang on.

I am trying to build a authentication system that will run with my android phones.
I wrote the following in a extends AndroidUnitTestCase class

X509Certificate[] certList = xtm.getAcceptedIssuers() ;
for (X509Certificate cert : certList) {
String certStr = "S:" + cert.getSubjectDN().getName() + "\nI:"
+ cert.getIssuerDN().getName();
System.out.println(certStr+"<<"+cert.getPublicKey()+">>") ;

}

This prints out

S:C=US,O=VeriSign\, Inc.,OU=VeriSign Trust Network,OU=(c) 1999 VeriSign\, Inc. - For authorized use only,CN=VeriSign Class 4 Public << AND THE PUBLIC KEY >>

Does the above certificate always ship with Android phones and emulators ? I am hoping to always use Verisign's cert.getPublicKey to encrypt some of my data to achieve client authentication. But if Verisign does not ship with all phones and emulators, I hope to identify a minimal set of certificates that do ship (for sure). So that I can play with that.

Anmol
Nikolay Elenkov said…
Thanks. But I am afraid you might be misunderstanding how this works. If you encrypt anything with (one of) VeriSign's public key(s), you won't be able to decrypt it. The only one that will be able to decrypt it is the entity holding the corresponding private key, in this case VeriSign.

As for what certificates ship by default, there are no guarantees since I believe carries can customize this and add or remove certificates. That said, you can usually rely on VeriSign being there, but they have multiple CA's and multiple certificates, so newer ones might be missing from some versions of Android.

Generally, you should only care that the CA that issued your server, etc. certificate is trusted by default by Android, so that your certificate validates out of the box. If it is not, you have to use the techniques outlined in this post on pre-ICS devices. On ICS, you can simply use the system settings or the provided APIs to add trusted certificates.
MCruiseOn said…
Thanks for your prompt response. I got that. More clear now. So in my case I need to get a certificate for my server, and then package my client's with client certificates (with my servers public key).

My scenario is, that my server can only be contacted by my clients, which I package and release. So, can I use some basic certificate or do I need to spend that 100-200$ to get a certificate from symantec ?
Nikolay Elenkov said…
You might want to go a step back and explain what you are trying to do, although this might not be the best place for a discussion. How best to handle this depends on a lot of things: are you distributing via Android Market/Play (public markets) or privately? do you need to identify each client? do you need client authentication for everything? etc.
MCruiseOn said…
Oh yeah, sorry for that. So I have a java server running on a EC2. I have a client library, which can be integrated into a android app. With this library, I am thinking of shipping a client certificate. This client certificate will be loaded as the library is initialized.
Nikolay Elenkov said…
If you have to use client certificate, you might as well go with self-signed for the server as well.
Erik said…
First of all, many thanks for your splendid article! Finally somebody who tries to explain the concept and solve the problem rather than simply neglecting it.

Unfortunately, I have not been able to run your sample program with success.
I have an own web server with a signed certificate from COMODO CA Limited.
Using ISP Manager I downloaded a .crt (=.pem) and a .p12 (=.pfx) using a random password. Creating the mytruststore.bks using importcert.sh seems successful.
However, using any of the options triggers the error 'Not trusted server certificate'. When I dump the trusted certs I see the correct domain being found.

Any thoughts on what I could be doing wrong?
I am on Android 2.2 (API 8). I am using bcprov-jdk15on-146.jar.
Many thanks in advance!
Nikolay Elenkov said…
Hi,

Glad you find this post useful.

As for your problem, there might be many possible causes. For starters, how does your server certificate chain look like? With a commercial CA, it is most probably issued by an intermediate CA (which may in turn be issued by another intermediate CA). If so, does your webserver send the intermediate CA certificates? Additionally, there is a bug in how Android handles out-of-order certificates in 2.1 and 2.2, so you might be hitting this. The easiest way to check what exactly the server is sending is to use the 'openssl s_client -connect myserver.com' command.

You mention a .p12 file, are you trying to do client authentication? Finally, what is in the PEM file? If the server is sending the intermediate certificates properly (see above), all you need is the Comodo root CA certificate.
Erik said…
This comment has been removed by the author.
Erik said…
Hi Nikolay,

Thank you for your quick reply!
I am quite new to SSL, so I am not entirely sure I know what I am doing ;)
Using the command you suggested and adding -showcerts leaves me with three certificates (the output is a bit large to put over here). Adding all three in the truststore did not make a difference.
However, I have tried two identical phones running the same system. One connects! The other does not. And you're right, the order of the certificates is not the same. So apparently the bug caused the trouble the whole time. Is there any workaround you happen to know of?
Concerning the client authentication; this is no high priority, but it would be nice if I would get it to work.

Many thanks,
Erik
Erik said…
It is working! The date and time in my (development) phone were incorrect, so stupid. Now somehow it is already working without even loading a custom build truststore, does this mean my server delivers its certificate the way it should? Even the order of the certificates is simply correct.
By the way, I used the second comment on http://stackoverflow.com/questions/4115101/apache-httpclient-on-android-producing-certpathvalidatorexception-issuername to solve the 'out-of-order' bug.
Nikolay Elenkov said…
Good to hear. So the Comodo CA is already trusted by Android, and your server is most probably sending the certs in the right order. As for the workaround for out-of-order, yes that is the one.
Erik said…
It looks like that. I did some log checking in my custom TrustManager and his work seems quite useless now ;)
As I am still a bit unsatisfied with my solution, I am trying to get the client authentication working as well. Again without luck.
I tried to build my own KeyManager to check the order of the certificates and it happend to be only one. Could this be correct? I again tried using your sample app, but both methods time out after a long wait. On my ICS device a get a 'Failure in SSL library, usually a protocol error' error. Any thoughts?
On my PHP server I adjusted the .htaccess to force client authentication:
| SSLVerifyClient require
| SSLVerifyDepth 3
| SSLCACertificateFile /ca.crt
Chrome gives me the following (quite promising) error: 'Unable to make a secure connection to the server. This may be a problem with the server, or it may be requiring a client authentication certificate that you don't have.'
Thanks for your help again!
Erik said…
Edit: Chrome is not accepting the certificate either. (If I set it via 'Manage certificates' in the settings) I think I have misconfigured my PHP server. Just consulting my webhost now :)
Erik said…
Well my webhost is not familiar with client authorization, so I am not using this for now. Any thoughts are welcome of course.
Hans-Erik said…
Wonderful post thanks.
Everything works grate except for one thing
When i load up the app into my device and try it
always the 'keyStore.load(..' return exception
"stream does not represent a PKCS12 key store"
When i press the button again immediately the exception is not thrown again
and everything works.
It always happens one time when loading the app into device

Nikolay Elenkov said…
Thanks! I haven't seen this error. Can you try to debug this? May be the key store you are using is not read correctly for some reason? Have you modified the code somehow?
Thank you for this very useful article.
I've a little question, my website is on mutualized hosting. The SSL certificate is the same for every body, that's why the domain name inside the certificate doesn't correspond to my domain name.
I want to use HttpsURLConnection, but I've an error Domain XXX was not verified.

I've tried to realize my own DomainVerifier, but after some experiences, I understood that this way was disabling the SSL check.

So, I'm wondering how I can do to keep the SSL check and avoid this error, have you an idea?

Thank you
Nikolay Elenkov said…
This is somewhat unrelated to the topic of this post, but I'll try to give you some hints.

I guess you are referring to the HostnameVerifier? On shared hosting you should have either a wildcard certificate or one with multiple SubjectAlternativeName (SAN) extensions, one for each supported host. The DefaultHostnameVerifier should be able to handle both cases correctly, so this is either a bug (in some older Android version) or your host is somehow misconfigured. How does the certificate look like? The HosnameVerifier is independent from (PKI) certificate validation, so setting should not affect it. Why do you thing it is being disabled?

Additionally, you might want to post on StackOverflow with related code and more details. This gives more people a chance to see it and comment.
Jens Hoffmann said…
Hi Nikolay,

I'am writing an Android Client which needs to access a WCF-Service.
My first thinking was, that I have to write a Custom HTTP Client class, which can handle the certificates.

I have already read your other article with the KeyChain-API.
So would like to use the existing KeyStore instead of creating a custom one.
How could I accomplish this? The KeyChain-API doesn't seem to have a methode for that.

Code:
SSLSocketFactory sf = new SSLSocketFactory(trusted); // needs a KeyStore object

Thanks in advance.

Best regards,

jens
Nikolay Elenkov said…
Not sure what exactly you are trying to do. KeyChain is no directly related to SSL sockets. If you need add a trust anchor, you load it in a KeyStore and initialize the SSLContext with it (see this article for details). If you already have a KeyStore instance, you can use it in more than one place.
mark1759 said…
Hi Nikolay,

Nice post Please where can i get the sample project for HttpClient unsing the SSLSocket you mentioned above. Am stock with "No certificate peering" in my application for more than 8weeks now. I will really appreciate your help on this thanks
Nikolay Elenkov said…
Sample code is available on Gihub, links are in the article. Here it is again: https://github.com/nelenkov/custom-cert-https/
mark1759 said…
Thanks Nikolay my code is working fine now, I can send and receive data using https through the SSLSocket.
Adam Mansour said…
This comment has been removed by the author.
Adam Mansour said…
Hey Nikolay, great example. I have a bit of confusion on the trust store and the keystore, there needs to be two, but with portecle I only generate one bks file called mystrustore.bks, right? What is the mykestore for, what is the extension?

Also when I try to add a cert it just crashes the app, is this only for pre 4.0 phones?

ReplyDelete
Nikolay Elenkov said…
You only need a keystore if the server requires client authentication. Extension and filename don't really matter, as long as it is in the right format. Not sure why it crashes, check the logcat. It should work on pre-4.0 versions too.
Phil Bellalouna said…
Nikolay,

Is it possible to get WebView/WebViewClient to use a custom trust store in ICS and up?

Here's a more detailed version of my question...

I've learned quite a bit from your posts re: custom certs both pre- and post- ICS and am curious if it's possible to obtain hybrid behavior on ICS: I'd like to use a custom trust store on ICS and greater, but not have it be device-wide (i.e. it's a self-signed cert that is for use within the app, not when using another browser) So I'm trying to use information from your pre-ICS post on ICS and greater... as you might expect, I'm having some difficulty.

I've confirmed that things are working as expected when manually generating requests via HttpsURLConnection but can't see a way to tell my WebView/WebViewClient to use the custom trust store. Handling in onReceivedSslError is about the best option I've found so far but that seems to result in some page rendering issues even when I just call handler.proceed() without doing anything else. Should I get past the rendering issue, it's not clear how I can obtain the actual cert from SslError to validate it manually. So that takes us back to the question at the top of this post...

Thanks,
Phil
Nikolay Elenkov said…
I don't really use WebView, but I think it doesn't expose such an interface. You can get an SslCertificate from SslError, but that doesn't expose the actual underlying X509Certificate. You could probably get it using reflection or something and try to validate (X509Certificate mX509Certificate). Not sure why the crippled it in this way.
Phil Bellalouna said…
Nikolay,

I appreciate your response and you've saved me some time and headache continuing to look for a solution that doesn't appear to exist publicly in the API. After seeing that SslCertificate was just an opaque wrapper, I've gone the reflection route as you suggested.

Thanks again for your help,
Phil
Sven Kapudija said…
This comment has been removed by the author.
Pankaj Gaykar said…
Hi Thanks for Good Post,

I am following same process at my end, All works fine for till android 4.1 & Below.
But It wont work on and 4.1 & Above.

Can you please help me this.
Al_ said…
Hi
Many thanks for the explanations, and for the fully working sample application "custom-cert-https". With that application, I get a strange result; I may misunderstand its function. For testing, I use an unrelated certificate as only certificate in the trust store created by importcert.sh, e.g., from GoDaddy; verified using the 'Dump trusted certs' function (with javax.net.ssl.trustStore set). My server does not use GoDaddy as certificate provider. Nevertheless 'HttpsURLConnection connect' succeeds. I would have expected to fail as *only* the custom made certificate store (with a single certificate) should be used. Can you explain, please?
Best
Al_
Nikolay Elenkov said…
MyTrustManager delegates to the default system TrustManager. So all system trust anchors+your own CA certificates are used when verifying server certificates. This is explained in 'Creating a dynamic TrustManager'. If you only you want to turst your own certificates, use the example code in the first part of the article to build your app.
Irmi said…
Hi Nikolay,

thank you for the only post I found in the internet that seems to solve the problem to accept and manage self-signed certificates in Android!!

Unfortunately it doesn't work for me. I used Portecle to generate the file mytruststore.bks in res\raw, which contains one self-signed certificate.

First problem: As long as the truststore does not yet exist it is copied allright to /data/data/org.../files/mytruststore.bks. But if I want to show its content with dumpTrustedCerts(), the line "tmf.init((Keystore) null);" leads to GeneralSecurityException! Moreover: If I look in my file system, I cannot find any data/data subdirectory!

Second problem: If I want to run HttpsURLConnectionConnect(), the line localTrustStore.load(in, TRUSTSTORE_PASSWORD.toCharArray()) within the function loadTrustStore() leads to the toast "Wrong version of key store" (by the way I didn't use a password for the certificate and therefore set it to "").

Do you have any idea??
Nikolay Elenkov said…
What version of Android are you using? You need to use matching (or lower) Bouncy Castle version to create the keystore file. As for /data/data, unless you are running on the emulator or a rooted device, you don't have access to this directory through the shell. Check the exception stack trace to find out what the cause is.
Irmi said…
Thank you so much. You led me on the right track. After fighting a lot the last days I finally found a confusion in my eclipse/adt installation, made a new fresh installation and now all works well!
Kiruwka Moklyuk said…
Hello, Phil. Could you please share some code on how you setup custom trusted certs for the WebView ?
Irmi said…
Do you have another advice for me?

Using the mechanisms of your example I could now enable my own app to accept self-signed certificates that are already saved in the local trust store.

Now I want to do exactly what you suggested in your comment from 23 February, 2012, for not yet saved certificates.

I catch the SSLHandshakeException that occurs during httpsUrlConnection.getOutputStream(). But how can I now get the server certificate? Calling httpsUrlConnection.getServerCertificates() within the catch block would make the app stopp unexpectedly :-(

Do you have any idea how to get the certificate from server?
Nikolay Elenkov said…
It might be a bit more work than simply catching the exception. You might need to retry to with an allow -all TrustManager in order to get the certificate and display it to the user. Then break that temporary connection and retry with the 'real' TrustManager.
Irmi said…
Thank you for answering my question! I just found out what I can do (I explain it in detail for other users because it took me some time to figure it out ...):

During httpsUrlConnection.getOutputStream() there is a call to MyTrustManager.checkServerTrusted(). This function throws a CertificateException if the certificate is not found in the default trust store. But it is still available as long as it has not yet looked up in the local trust store. This is the perfect place to save the certificate in a temporary file. If the certificate is not found in the local trust store too, then httpsUrlConnection.getOutputStream() throws an SSLHandshakeException and here I can show the user the content of the temporary saved certificate to let him decide if he wants to trust it!

Thank you again for your excellent article, it helped me a lot!

Johann H said…
Hi Nikolay. Thank you for this great Article, also for your android application on github, which helped me a lot, since i struggled for weeks on a similar problem here: stackoverflow.com/questions/21624663/ssl-client-authentication-with-certificate-in-android
With your application I realized that my connection, just like yours works with client authentication, but only if I first call that connect without client authentication. If I directly use client authentication, I get the connection reset by peer error, even with your app. Am I still missing something? Because if I try a connection with the Chrome Browser, everything works. :-/
Dorian Cussen said…
I think its worth commenting `tmf.init((KeyStore) null);` as only by passing null in here you will get the system TrustManager - as opposed to passing `KeyStore.getDefault...` (which would make more sense to me!)
This line:
InputStream in = getResources().openRawResource(R.raw.mytruststore);

Gets me the error:
"Cannot make a static reference to the non-static method getResources() from the type ContextWrapper"
Mayur Parekh said…
Hello Nikolay Elenkov,

I would like to thanks your for this nice post and github example. That's really very much helpful.

I do have one question regarding SSL HTTPS and BKS certificates usage. Suppose I create one BKS for my Host and use it in My App.

Now as you know it's easy to decompile APK and get raw folder values so anyone can obtain my BKS file.

If my web service URL is known to someone else, then is it possible for him to call the same service using this BKS file in his app or in browser ?

Please reply on this as soon as possible.
Nikolay Elenkov said…
You do release that this is not a paid support service, so 'reply as soon as possible' doesn't really work, do you?

As for your question, certificates are public by definition, so extracting one from your app is not a problem. Using SSL server authentication is not a replacement for *user* authentication. If by 'BKS file' you meant 'BKS file that contains a private key', as with other credentials, you shouldn't bundle private keys with your app. Find another way to distribute them, and let uses import them into the app's store.
ajulka said…
This comment has been removed by the author.
Harinadh said…
Hi,

Thanks for detail explanation and is there anyway to convert .crt file to .bks at run time instead of reading it from res/raw folder.

Thanks
Harinadh.
Nikolay Elenkov said…
Sure, convert to X509Certificate using CertificateFactory, create new KeyStore and add it using setEntry(). Then save the KeyStore using the store() method.
Ravi Tejasvi said…
Hey,
Following is my code :

SSLContext sslContext = SSLContext.getInstance("TLS");
KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
kmf.init(ks, KEYSTORE_PASSWORD.toCharArray());

KeyManager [] keyManagers = kmf.getKeyManagers();

sslContext.init(keyManagers, null, null);
SSLSocketFactory socketFactory = sslContext.getSocketFactory();

My ks has the required cert, even kmf has the required cert. But keyManager instance size is coming to be 0. It has nothing in it. Therefore the socketFactory sslParams also has a x509 keyManager which doesn't have any cert. Therefore my URLConnection doesn't present any cert and my connection fails.
Is there something wrong I'm doing?
Sanoop Thomas said…
Hi Nikolay,

Is it possible to restrict the application to use only specific TLS or SSL version ? Consider the backend web server supports all TLS or SSL version, let's say, if the OS is old and doesn't support newer versions, will the application fall back into the older one ?
Nikolay Elenkov said…
This comment has been removed by the author.
Nikolay Elenkov said…
You can use the SSLContext and SSLParameters classes to configure the version and/or supported cipher suites. Depends on server configuration, but If you say you only supports e.g. TLS/1.2 you should connect with that version.
Unknown said…
This comment has been removed by the author.
Hi Nikolay,

Thanks for such a detailed explanation. It really helped !!

We are running into below issue :

Caused by: java.io.IOException: Wrong version of key store.

We are currently using 4.1.2 android version. Can you please guide us with this error.

Thanks,
Darshan
SatyaSeshu said…
Thanks for the post Nikolay. I have one doubt. I have .p12 file extension certificate. Now i need to send the file along with web service. could you please any sample code or links.
Thanks in advance.

Popular posts from this blog

Decrypting Android M adopted storage

Password storage in Android M

Unpacking Android backups