Android: Trusting SSL certificates
These same instructions apply to trusting a custom CA, except you'd get the public certificate directly from the CA instead of from a server.
Required tools:
- OpenSSL's command line client
- Java SE 6 (for keytool)
- Bouncy Castle's provider jar
1. Grab the public certificate from the server you want to trust. Replace ${MY_SERVER} with your server's address.
echo | openssl s_client -connect ${MY_SERVER}:443 2>&1 | \ sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > mycert.pem
For example, here's the PEM-encoded public certificate from google.com:
-----BEGIN CERTIFICATE----- MIIDITCCAoqgAwIBAgIQL9+89q6RUm0PmqPfQDQ+mjANBgkqhkiG9w0BAQUFADBM MQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkg THRkLjEWMBQGA1UEAxMNVGhhd3RlIFNHQyBDQTAeFw0wOTEyMTgwMDAwMDBaFw0x MTEyMTgyMzU5NTlaMGgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlh MRYwFAYDVQQHFA1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKFApHb29nbGUgSW5jMRcw FQYDVQQDFA53d3cuZ29vZ2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC gYEA6PmGD5D6htffvXImttdEAoN4c9kCKO+IRTn7EOh8rqk41XXGOOsKFQebg+jN gtXj9xVoRaELGYW84u+E593y17iYwqG7tcFR39SDAqc9BkJb4SLD3muFXxzW2k6L 05vuuWciKh0R73mkszeK9P4Y/bz5RiNQl/Os/CRGK1w7t0UCAwEAAaOB5zCB5DAM BgNVHRMBAf8EAjAAMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwudGhhd3Rl LmNvbS9UaGF3dGVTR0NDQS5jcmwwKAYDVR0lBCEwHwYIKwYBBQUHAwEGCCsGAQUF BwMCBglghkgBhvhCBAEwcgYIKwYBBQUHAQEEZjBkMCIGCCsGAQUFBzABhhZodHRw Oi8vb2NzcC50aGF3dGUuY29tMD4GCCsGAQUFBzAChjJodHRwOi8vd3d3LnRoYXd0 ZS5jb20vcmVwb3NpdG9yeS9UaGF3dGVfU0dDX0NBLmNydDANBgkqhkiG9w0BAQUF AAOBgQCfQ89bxFApsb/isJr/aiEdLRLDLE5a+RLizrmCUi3nHX4adpaQedEkUjh5 u2ONgJd8IyAPkU0Wueru9G2Jysa9zCRo1kNbzipYvzwY4OA8Ys+WAi0oR1A04Se6 z5nRUP8pJcA2NhUzUnC+MY+f6H/nEQyNv4SgQhqAibAxWEEHXw== -----END CERTIFICATE-----
2. Android has built-in support for the Bouncy Castle keystore format (BKS). Put Bouncy Castle's jar in your classpath, and create a keystore containing only your trusted key.
export CLASSPATH=bcprov-jdk16-145.jar CERTSTORE=res/raw/mystore.bks if [ -a $CERTSTORE ]; then rm $CERTSTORE || exit 1 fi keytool \ -import \ -v \ -trustcacerts \ -alias 0 \ -file <(openssl x509 -in mycert.pem) \ -keystore $CERTSTORE \ -storetype BKS \ -provider org.bouncycastle.jce.provider.BouncyCastleProvider \ -providerpath /usr/share/java/bcprov.jar \ -storepass ez24get
3. Create a custom Apache HttpClient that uses your custom store for HTTPS connections.
import android.content.Context; import org.apache.http.conn.ClientConnectionManager; 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.impl.conn.SingleClientConnManager; import java.io.InputStream; import java.security.KeyStore; public class MyHttpClient extends DefaultHttpClient { final Context context; public MyHttpClient(Context context) { this.context = context; } @Override protected ClientConnectionManager createClientConnectionManager() { SchemeRegistry registry = new SchemeRegistry(); registry.register( new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); registry.register(new Scheme("https", newSslSocketFactory(), 443)); return new SingleClientConnManager(getParams(), registry); } private SSLSocketFactory newSslSocketFactory() { try { KeyStore trusted = KeyStore.getInstance("BKS"); InputStream in = context.getResources().openRawResource(R.raw.mystore); try { trusted.load(in, "ez24get".toCharArray()); } finally { in.close(); } return new SSLSocketFactory(trusted); } catch (Exception e) { throw new AssertionError(e); } } }
That's it! If you think this kind of stuff is fun, Square is hiring.
17 Comments:
interface ForTestCallback {
void Finished(String result, String error);
}
public class ForTest extends Thread {
private ForTestCallback callBack;
private Handler handler;
private String url;
public ForTest(String adress, ForTestCallback callBack) {
this.callBack = callBack;
this.url = adress;
handler = new Handler();
}
public void begin() {
start();
}
public void run() {
try {
final long t0 = System.currentTimeMillis();
final String result;
// fill empty keystore received a certificate
KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType());// BKS
store.load(null); // init empty keystore
URLConnection uc = new URL(url).openConnection();
try {
uc.connect();
Certificate certs[] = ((HttpsURLConnection)uc).getServerCertificates();
for (int i=0; i<certs.length; i++)
store.setCertificateEntry("cert_"+i, certs[i]);
}
catch(Exception e) {
Log.e("GET_CERT",e.getMessage());
}
finally {
if (uc!=null && uc instanceof HttpURLConnection)
((HttpsURLConnection)uc).disconnect();
}
// check the "trusted" connection
HttpEntity responseEntity;
HttpGet httpGet = new HttpGet(url);
HttpClient client = new MyHttpClient(store);
httpGet.addHeader("Content-Type", "text/xml");
HttpConnectionParams.setConnectionTimeout(client.getParams(), 15000);
HttpResponse getResponse = client.execute(httpGet);
responseEntity = getResponse.getEntity();
result = EntityUtils.toString(responseEntity);
final long t1 = System.currentTimeMillis();
handler.post(new Runnable() {
public void run() {
callBack.Finished(result+" for " + (t1-t0) + "ms", null);
}
});
}
catch (final Exception e) {
handler.post(new Runnable() {
public void run() {
Log.d("GET_ERROR", "error", e);
callBack.Finished(null, e.getMessage());
}
});
}
}
//Your code, slightly modified ...
public class MyHttpClient extends DefaultHttpClient {
final KeyStore store;
public MyHttpClient(KeyStore store) {
this.store = store;
}
@Override
protected ClientConnectionManager createClientConnectionManager() {
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
registry.register(new Scheme("https", newSslSocketFactory(), 443));
return new SingleClientConnManager(getParams(), registry);
}
private SSLSocketFactory newSslSocketFactory() {
try {
return new SSLSocketFactory(store);
} catch (Exception e) {
throw new AssertionError(e);
}
}
}
}
/*
example to call
public void callTest() {
final String url = "https://login.yahoo.com/"; // work fine
// final String url = "https://www.comodo.com/products/comodo-products.php"; //gets error
// final String url = "https://www.google.com/accounts/ManageAccount"; //gets error
ForTest test = new ForTest(url, new ForTestCallback() {
@Override
public void Finished(String result, String error) {
setProgressBarIndeterminateVisibility(false);
// show info...
startBtn.setText(bHttpsEnvSet?"get ready...":"replay...");
// next work...
}
});
setProgressBarIndeterminateVisibility(true);
startBtn.setText("testing...");
test.begin();
}
*/
Hi!
I tried your code, but it did not solve my problem: (
Here is my code and your little corrected.
As noted, the connection is fine, if the server has only one certificate in the chain, otherwise we get "Not trusted server certificate"
Maybe I am wrong somewhere. I would be very grateful if you help
I deal with problems.
Thank you.
Works great for me. Thx a lot, that saved my day !!!
Hi Bob,
I am facing the following error in my android application while connecting to https server
"Not a trusted certificate"
When i used your command "echo | openssl s_client -connect ${MY_SERVER_ADDRESS}:443 2>&1 | \ sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > mycert.pem" to get the certificate from server, no certificate was returned and i am unable to trace where i went wrong.Kindly assist me in getting certificate from server and creating R.raw.mystore file.
This post is incredibly helpful, thanks.
Thanks Bob. This works for me in my test environment where we have self signed certificates.
Cheers !!!
- Jay
I'm experiencing a "NOT TRUSTED CERTIFICATE" error even if the certificate has been signed by a public CA (Thawte) and all browser works properly with it.
Have you got any suggestion ?
Worked like a charm for trusting CACert CA, Thanks!
Thank you for this blog.
I have a similar situation where I need to generate the certificate on the device itself from my app. Is there a way to do that?
I tried using the Bouncycastle jar[bcprov-jdk16-145.jar] but the problem is that, since android already included those classes, it is not able to resolve these classes from the jar that I add. So I could not use the classes to generate certificates.
Do you know of a way to generate the certificates from my android app itself?
Thanks again.
Hi, nice listing. But I've got a weird one. Works flawlessly for all clients over WiFi. However, it fails on some devices over 3G/GPRS with CONNECTION_TIMED_OUT or CONNECTION_REFUSED exception. Any pieces of advice?
Thanks in advance
What version of the Bouncy castle Jar was used in this example? I used the 1.6 version from http://www.bouncycastle.org/latest_releases.html but get this exception:
java.io.IOException: Wrong version of key store.
Thanks Bob, very helpful
The bounycastle jar is ~2mb? Am I seeing this right?
http://www.bouncycastle.org/latest_releases.html
bcprov-jdk16-146.jar
In your code, what does "ez24get" means?
Is that a key that I need to use with the certificate?
I continue getting the "Not trusted server certificate" error.
Abe, that's the password.
I swear I am doing everything right, but am still getting: "Not trusted server certificate". Could there be something in the way I am executing my GET?
DefaultHttpClient httpclient = new MyHttpClient(getApplicationContext());
HttpGet get = new HttpGet("https://www.coffeecup.com/api/sdrive/user_accounts_list.json");
try {
HttpResponse response = httpclient.execute(get);
} catch (ClientProtocolException e) {
System.out.println(e.getMessage());
} catch (IOException e) {
System.out.println(e.getMessage());
}
I can't get the codes to work on my machine. Kobtyh, can I have the source code for this? I am really stuck and I got tons of errors, even importing the missing classes didnt work.
Post a Comment
<< Home