How to Handle SSL certification validation in proper way? - android

Hello my application is live and it is using "https" protocol. The Google Play Team throws warning as below.
"Your app(s) listed at the end of this email use an unsafe
implementation of the interface X509TrustManager. Specifically, the
implementation ignores all SSL certificate validation errors when
establishing an HTTPS connection to a remote host, thereby making your
app vulnerable to man-in-the-middle attacks. An attacker could read
transmitted data (such as login credentials) and even change the data
transmitted on the HTTPS connection. If you have more than 20 affected
apps in your account, please check the Developer Console for a full
list.
To properly handle SSL certificate validation, change your code in the
checkServerTrusted method of your custom X509TrustManager interface to
raise either CertificateException or IllegalArgumentException whenever
the certificate presented by the server does not meet your
expectations. Google Play will block publishing of any new apps or
updates containing the unsafe implementation of the interface
X509TrustManager."
In my project I am using custom http client to handle HTTPS instead default httpClient. My code is as below.
public static HttpClient getNewHttpClient() {
try
{
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
MySSLSocketFactory sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
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("http", PlainSocketFactory.getSocketFactory(), 80));
registry.register(new Scheme("https", sf, 443));
ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);
return new DefaultHttpClient(ccm, params);
}
catch (Exception e)
{
return new DefaultHttpClient();
}
}
public static class MySSLSocketFactory extends SSLSocketFactory {
SSLContext sslContext = SSLContext.getInstance("TLS");
public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(truststore);
TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
sslContext.init(null, new TrustManager[] { tm }, null);
}
#Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
}
#Override
public Socket createSocket() throws IOException {
return sslContext.getSocketFactory().createSocket();
}
}
How to overcome with this problem?
Hoping for favorable answers.

Now that you have posted the code concerned, it is difficult to see what part of the quoted message you don't understand.
The fix is simply to remove the TrustManager part of the code altogether, root and branch, and use the default one, and then deal with whatever problems may then arise in the proper way, by adjusting the contents of the truststore also as to trust all the certificates you need to trust that aren't already trusted by default. If any, which there shouldn't be.

Why do you need custom SSLSocketFactory. You can use DefaultHttpClient and that will handle all https by default. Just for your info, that HttpClient is deprecated and use HttpURLConnection.

Related

Getting a 400 bad request when using SSL on port other than 443?

I've got a server serving at port 9443. It's using an ssl cert signed by godaddy for "example.com". I try to connect to a url like:
https://example.com:9443/api/v1/foo
and that works fine from an ios app I've written, and desktop browsers. If I try connecting from an android phone, I get a 400 bad request response. I tried the same through chrome for android, it gives me:
ERR_TUNNEL_CONNECTION_FAILED
The server is also listening to port 443, using a self-signed cert, and the android client works ok there (I do need some additional code there to make my app trust the self-signed cert).
Is there some restriction in android where https must use port 443? I was thinking that since I'm using a legitimate ssl cert, I wouldn't need any custom code to force the app to trust the cert. My connection code:
HttpParams params = new BasicHttpParams();
DefaultHttpClient client = new DefaultHttpClient(params);
HttpResponse response = client.execute(request);
Result result = new Result(response.getStatusLine().getStatusCode());
response.getStatusLine().getStatusCode(); // 400
response.getStatusLine().toString(); // HTTP/1.1 400 Bad Request
Thanks
Two possibilities, I think.
Searching for ERR_TUNNEL_CONNECTION_FAILED android leads me to this page, where the author claims:
Android for some unknown reason will not allow you to use unstandard
ports for SSL (i.e. only port 443 works when using https)
I confirmed this by trying a SSL website on port 2000 using my wifes
iphone on AT&T. Sure enough it worked fine. So its not AT&T. Also
worth pointing out it works fine on wifi.
Hopefully this is just a bug and will be fixed in the future.
However, this site claims that error occurs when accessing a site through a proxy. It's possible you may have a proxy configured on your Android phone that it's trying to use somehow.
The easiest way to tell which world you're in would be to check from a different Android phone, if you have one.
It may help you
public HttpClient getNewHttpClient() {
try {
KeyStore trustStore = KeyStore.getInstance(KeyStore
.getDefaultType());
trustStore.load(null, null);
SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
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("http", PlainSocketFactory
.getSocketFactory(), 80));
registry.register(new Scheme("https", sf, 443));
ClientConnectionManager ccm = new ThreadSafeClientConnManager(
params, registry);
return new DefaultHttpClient(ccm, params);
} catch (Exception e) {
return new DefaultHttpClient();
}
}
public class MySSLSocketFactory extends SSLSocketFactory {
SSLContext sslContext = SSLContext.getInstance("TLS");
public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(truststore);
TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
sslContext.init(null, new TrustManager[] { tm }, null);
}
#Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
}
#Override
public Socket createSocket() throws IOException {
return sslContext.getSocketFactory().createSocket();
}
}

How to react on untrusted certificate exceptions while performing a HTTPS request?

I am doing a HTTPS request using a HttpsURLConnection.
The server I'm trying to contact has a self-signed certificate, so this logically causes the request to fail.
However, the failure isn't signaled to my program by an exception. It just fails and I can see why in the logcat.
03-22 17:54:35.203: W/System.err(21147): javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
03-22 17:54:35.203: W/System.err(21147): at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:381)
Here is the code I use for the request:
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setUseCaches(false);
connection.setAllowUserInteraction(false);
connection.connect();
Ideally, I'd like my program to react like browser do: popup a dialog indicating that the certificate is not trusted and possibly add it to some key-store and retry the request.
But since I can't figure out a way of getting the exception, I really don't know what to do here.
Any clue ?
While huge is correct that you need to implement a custom TrustManager, you should absolutely not just blindly accept all SSL certificates.
Nikolay Elenikov has an excellent blog post describes how to set up a custom TrustManager to validate such certificates
You can do it like this:
SSLContext sc = SSLContext.getInstance("TLS");
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
#Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return;
}
#Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return;
}
} };
sc.init(null, trustAllCerts, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
HttpURLConnection = connection = endPoint.openConnection();

accept self-signed SSL Certificates-> where to set default TrustManager

First I had to admit that I know that accepting all certs can be considered as having no security. We have "real" Certs but only on our live systems. The certs on our test systems are self-signed. So as long we're developing, we have to use test-servers, which forces me to kind of disable certificates.
I saw a lot of toppics here on Stackoverflow and all over the web which are all trying do do the same:
Accepting SSL Certificates. However None of these answers seem to apply to my Problem, as I'm not messing with HTTPSUrlConnections.
If I'm doing a request the code usually looks like this (commented for clearification):
//creates an HTTP-Post with an URL
HttpPost post = createBaseHttpPost();
//loads the request Data inside the httpPost
post.setEntity(getHttpPostEntity());
//appends some Headers like user-agend or Request UUIDs
appendHeaders(post);
HttpClient client = new DefaultHttpClient();
//mResponse is a custom Object which is returned
//from the custom ResponseHandler(mResponseHandler)
mResponse = client.execute(post, mResponseHandler);
return mResponse;
I read that I should inject my own TrustManager and X509HostnameVerivier. I created them like this:
private static final TrustManager[] TRUST_ALL_CERTS = new TrustManager[]{
new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{};
}
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
}
};
private static X509HostnameVerifier ACCEPT_ALL_HOSTNAMES =
new X509HostnameVerifier() {
public void verify(String host, String[] cns, String[] subjectAlts)
throws SSLException {
}
public void verify(String host, X509Certificate cert) throws SSLException {
}
public void verify(String host, SSLSocket ssl) throws IOException {
}
public boolean verify(String host, SSLSession session) {
return true;
}
};
If I inject the HostnameVerifier inside my request like this (client is DefaultHttpClient from above)
SSLSocketFactory ssl = (SSLSocketFactory)client.getConnectionManager().getSchemeRegistry().getScheme("https").getSocketFactory();
ssl.setHostnameVerifier(ACCEPT_ALL_HOSTNAMES);
the response turns from "hostname ** didn't match" to "Bad request". I guess I have to set the TrustManager, but I'm clueless where to set it inside my request, as I'm not using HttpsUrlConnections mentioned everywhere I looked it up.
No, it doesn't force you to disable validation, it forces you to implement validation properly. Do not blindly accept all certificates. And no, your case is not any different, you just need to trust a certificate that Android doesn't trust by default.
You are using HttpClient, so the APIs for setting the trust manager are somewhat different than HttpsURLConnection, but the procedure is the same:
Load a keystore file with trusted certificates (your server's self-signed certificates)
Initialize a KeyStore with it.
Create a SocketFactory using the KeyStore from 2.
Set your HTTP client library to use it when creating SSL sockets.
This is described in Android's documentation: http://developer.android.com/reference/org/apache/http/conn/ssl/SSLSocketFactory.html
A more detailed article on the subject, shows how to create the trust store file: http://blog.crazybob.org/2010/02/android-trusting-ssl-certificates.html
Some background information and example code: http://nelenkov.blogspot.com/2011/12/using-custom-certificate-trust-store-on.html
This is the code you need to initialize HttpClient:
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(localTrustStore);
schemeRegistry.register(new Scheme("https", sslSocketFactory, 443));
HttpParams params = new BasicHttpParams();
ClientConnectionManager cm =
new ThreadSafeClientConnManager(params, schemeRegistry);
HttpClient client = new DefaultHttpClient(cm, params);
At this point, you have no excuses for trusting all certificates. If you do, it all on you :)

SSL connection in android

I am in middle of developing an app in android, which requires me sslhandshake with server, using KSOAP2 libraries.
I am able to achieve the same on http sites, but fails on HTTPS sites,saying "could not validate certificate".
Can anybody help out
Please note that at least prior to 2.3 Android versions don't have the root CA for the RapidSSL CA among others.
You can check the issuer of a problematic certificate with sites such as http://www.digicert.com/help/
Another quick check is to try to load a HTTPs page in the stock browser and see if it complains about the certificate.
If this does not match your situation then ignore this answer.
If you have a certificate signed by this CA you must either
Handle it explicitly in your app by doing something like Danieles answer, but actually also comparing the certificate to a stored one for RapidSSL (or whichever you use).
Add an intermediate certificate to the chain at the web server in question to make the RapidSSL certificate certified by GeoTrust.
Check out
http://code.google.com/p/android/issues/detail?id=10807
https://knowledge.rapidssl.com/support/ssl-certificate-support/index?page=content&id=AR1549
It may be because the site you are trying to access may not have CA. It only may only have self-signed certificate. That is a issue you will get when you dealing with self-signed certificate.
Try these links and show us what you have implemented already
http://developer.android.com/reference/javax/net/ssl/HttpsURLConnection.html
http://developer.android.com/reference/org/apache/http/conn/ssl/SSLSocketFactory.html
Can this code be of help?
https://github.com/mixare/mixare/blob/master/src/org/mixare/MixContext.java
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());
This code is used in mixare.org to accept self-signed certificates.
Please be aware that you are not safe from MITM attacks when using this approach.
HTH,
Daniele
You can Use SelfSignedCertificate. Just use this method as your HTTPClient:
public static HttpClient getNewHttpClient() {
try {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
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("http", PlainSocketFactory.getSocketFactory(), 80));
registry.register(new Scheme("https", sf, 443));
ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);
return new DefaultHttpClient(ccm, params);
} catch (Exception e) {
return new DefaultHttpClient();
}
}

SSL Client on Android

I'm currently writing a client part for Android (2.2) and a server using SSL. I managed to exchange messages between the server and a normal client, but Android doesn't seem to be too happy about self signed certificates.
I've searched Stackoverflow and Googled A LOT and LOTS of people are having similar problems. All the answers I've found so far either wasn't working or didn't make any sense. Most of the code samples out there are for HTTPS, but this I cannot use, as I need to communicate through a socket (SSLSocket is my best guess). I've tried lots of different code, but right now I'm kinda back at zero again.
So far I've figured out that I have to create a certificate (think I got that right) and a custom TrustManager. Obviously I haven't been able to find any working code, which is why I ask here, as there usually are some really helpful people.
I'm looking for a detailed description of what is supposed to be done, and some code, which can be made into a working Android client code.
Thanks in advance
I have done it with porting native android browser.
I just changed how ssl context is created.
I promised that I will also put some working example on SandroB site but... :)
https://market.android.com/details?id=org.sandrob
http://code.google.com/p/sandrob/source/browse/misc/examples/HttpsConnection.java
CertificateChainValidator is part of android sources.
At least in version/tag 2.2.1_r1
To make it work you can build Browser from android sources and change just how
SSLContext is initialized in HttpsConnection.java file.
You need keyManagers(initialized with cerfile/password) and trustManagers (trust all).
sslContext.engineInit(keyManagers, trustManagers, null, cache, null);
public class MySSLSocketFactory extends SSLSocketFactory {
SSLContext sslContext = SSLContext.getInstance("TLS");
public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
super(truststore);
TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
sslContext.init(null, new TrustManager[] { tm }, null);
}
#Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
}
#Override
public Socket createSocket() throws IOException {
return sslContext.getSocketFactory().createSocket();
}
}
And you Httpclient is
public HttpClient getNewHttpClient() {
try {
KeyStore trustStore = KeyStore.getInstance(KeyStore
.getDefaultType());
trustStore.load(null, null);
SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
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("http", PlainSocketFactory
.getSocketFactory(), 80));
registry.register(new Scheme("https", sf, 443));
ClientConnectionManager ccm = new ThreadSafeClientConnManager(
params, registry);
return new DefaultHttpClient(ccm, params);
} catch (Exception e) {
return new DefaultHttpClient();
}
}
Hope it helps you.
https://market.android.com/details?id=org.sandrob.sslexample
Feel free to change sources as you needed.

Categories

Resources