SSL connection reuse with Android HttpClient - android

I'm developing a secure web service between my self-implemented java web proxy (that forwards requests to the actual web service) and an android application.
Doing so with standard (insecure) http connections works perfectly well. Now I want to use a secure (SSL) connection between the proxy and the android client.
This works as long as I instantiate a new HttpClient for each request, which is beside wasting resources extremely slow as I'm is doing a two way handshake for each request.
So I'm trying to reuse the HttpClient for each request which results for secure connections in the following exception
java.lang.IllegalStateException: Connection already open.
at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:150)
at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:119)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:360)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:555)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:487)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:465)
When I change my proxy and the client to no-ssl communication it works without any problems.
Does anybody know what I'm doing wrong? Thanks a lot for your help!
By the way the server code is totally similar except using SSLServerSocket with loaded certificates instead of using ServerSocket.
Client parameter setup
// Set basic data
HttpParams params = new BasicHttpParams();
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, "UTF-8");
HttpProtocolParams.setUseExpectContinue(params, true);
HttpProtocolParams.setUserAgent(params, "Android app/1.0.0");
// Make pool
ConnPerRoute connPerRoute = new ConnPerRouteBean(12);
ConnManagerParams.setMaxConnectionsPerRoute(params, connPerRoute);
ConnManagerParams.setMaxTotalConnections(params, 20);
// Set timeout
HttpConnectionParams.setStaleCheckingEnabled(params, false);
HttpConnectionParams.setConnectionTimeout(params, connectionTimeoutMillis);
HttpConnectionParams.setSoTimeout(params, socketTimeoutMillis);
HttpConnectionParams.setSocketBufferSize(params, 8192);
// Some client params
HttpClientParams.setRedirecting(params, false);
Http client creation
// load truststore certificate
InputStream clientTruststoreIs = context.getResources().openRawResource(R.raw.server);
KeyStore trustStore = KeyStore.getInstance("BKS");
trustStore.load(clientTruststoreIs, "server".toCharArray());
// initialize trust manager factory with the read truststore
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
// setup client certificate
// load client certificate
InputStream keyStoreStream = context.getResources().openRawResource(R.raw.client);
KeyStore keyStore = KeyStore.getInstance("BKS");
keyStore.load(keyStoreStream, "client".toCharArray());
// initialize key manager factory with the read client
// certificate
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, "client".toCharArray());
// initialize SSLSocketFactory to use the certificates
SSLSocketFactory socketFactory = new SSLSocketFactory(SSLSocketFactory.TLS, keyStore, "client", trustStore, null, null);
// Register http/s shemas!
SchemeRegistry schReg = new SchemeRegistry();
schReg.register(new Scheme("https", socketFactory, port));
ClientConnectionManager conMgr = new ThreadSafeClientConnManager(params, schReg);
httpClient = new DefaultHttpClient(conMgr, params);
Client request execution
HttpResponse response = httpClient.execute(request);
HttpEntity entity = response.getEntity();
in = entity.getContent();
String result = convertStreamToString(in);
in.close();

I have this exact same issue with Android and HttpClient - it only seems to manifest itself when using a client-side certificate, just like you have. Remove client auth and it works fine, haven't found a workaround as of yet.
Edit:
Enable stale connection checking.
HttpConnectionParams.setStaleCheckingEnabled(params, true);
Fixed it for me.

Are you using a thread-safe connection manager? If not, that may be the problem if you're using the same HttpClient in multiple threads.
This article has details: http://candrews.integralblue.com/2011/09/best-way-to-use-httpclient-in-android/

Related

Safely fixing: javax.net.ssl.SSLPeerUnverifiedException: No peer certificate

There are dozens of posts about this issue (javax.net.ssl.SSLPeerUnverifiedException: No peer certificate) but I haven't found anything that works for me.
Many posts (like this, and this) "solve" this by allowing all certificates to be accepted but, of course, this is not a good solution for anything other than testing.
Others seem quite localized and don't work for me. I really hope that someone has some insight that I lack.
So, my problem: I'm testing on a server accessible only through the local network, connecting via HTTPS. Making the call I need to through the browser works fine. No complaining about certificates and if you check the certificates, it all looks good.
When I try on my Android device, I get javax.net.ssl.SSLPeerUnverifiedException: No peer certificate
Here's the code that calls it:
StringBuilder builder = new StringBuilder();
builder.append( /* stuff goes here*/ );
httpGet.setEntity(new UrlEncodedFormEntity(nameValuePairs));
ResponseHandler<String> responseHandler = new BasicResponseHandler();
// Execute HTTP Post Request. Response body returned as a string
HttpClient httpClient = MyActivity.getHttpClient();
HttpGet httpGet = new HttpGet(builder.toString());
String jsonResponse = httpClient.execute(httpGet, responseHandler); //Line causing the Exception
My code for MyActivity.getHttpClient():
protected synchronized static HttpClient getHttpClient(){
if (httpClient != null)
return httpClient;
HttpParams httpParameters = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParameters, TIMEOUT_CONNECTION);
HttpConnectionParams.setSoTimeout(httpParameters, TIMEOUT_SOCKET);
HttpProtocolParams.setVersion(httpParameters, HttpVersion.HTTP_1_1);
//Thread safe in case various AsyncTasks try to access it concurrently
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
ClientConnectionManager cm = new ThreadSafeClientConnManager(httpParameters, schemeRegistry);
httpClient = new DefaultHttpClient(cm, httpParameters);
CookieStore cookieStore = httpClient.getCookieStore();
HttpContext localContext = new BasicHttpContext();
localContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
return httpClient;
}
Any help would be much appreciated.
Edit
Also just to mention I've had other SSL issues with another app but adding the SchemeRegistry portion fixed it for me before.
Edit 2
So far I've only tested on Android 3.1, but I need this to work on Android 2.2+ regardless. I just tested on the browser on my Android tab (Android 3.1) and it complains about the certificate. It's fine on my pc browser, but not on the Android browser or in my app.
Edit 3
Turns out the iOS browser also complains about it. I'm starting to think it's a certificate chain issue described here (SSL certificate is not trusted - on mobile only)
It turns out my code was fine and the problem was that the server was not returning the full certificate chain. For more information see this SO post and this superuser post:
SSL certificate is not trusted - on mobile only
https://superuser.com/questions/347588/how-do-ssl-chains-work
Try below code :-
BasicHttpResponse httpResponse = null;
HttpPost httppost = new HttpPost(URL);
HttpParams httpParameters = new BasicHttpParams();
// Set the timeout in milliseconds until a connection is
// established.
int timeoutConnection = ConstantLib.CONNECTION_TIMEOUT;
HttpConnectionParams.setConnectionTimeout(httpParameters,
timeoutConnection);
// Set the default socket timeout (SO_TIMEOUT)
// in milliseconds which is the timeout for waiting for data.
int timeoutSocket = ConstantLib.CONNECTION_TIMEOUT;
HttpConnectionParams
.setSoTimeout(httpParameters, timeoutSocket);
DefaultHttpClient httpClient = new DefaultHttpClient(
httpParameters);
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
nameValuePairs.add(new BasicNameValuePair(ConstantLib.locale,
locale));
In my case everything used to work fine. Suddenly after 2 days my device did not show any https site or image link.
After some investigation it turns out that My time settings was not up to date on device.
I changed my time settings properly and it worked.
I had this exception when I used self-signed certificate + ip address. Just add these lines
HostnameVerifier allHostsValid = new HostnameVerifier() {
#Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
and your HttpURLConnection will work.
That trick is not related to validation against CA! So if I specify wrong CA and use that trick, I will get another exception. So the host remains trusted
Just in case, I will leave code for specifying your own CA here:
String certStr = context.getString(R.string.caApi);
X509Certificate ca = SecurityHelper.readCert(certStr);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null);
ks.setCertificateEntry("caCert", ca);
tmf.init(ks);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
Do not forget to use cool feature buildTypes and place different CAs for debug/release in res folder.

Getting SSLPeerUnverifiedException in Android

i am getting SSL Peer Unverified Exception when i try to connect using HTTPs Connection.
I am new to HTTPs.
My code is :
HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;
DefaultHttpClient client = new DefaultHttpClient();
SchemeRegistry registry = new SchemeRegistry();
SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory();
socketFactory.setHostnameVerifier((X509HostnameVerifier) hostnameVerifier);
registry.register(new Scheme("https", socketFactory, 443));
SingleClientConnManager mgr = new SingleClientConnManager(client.getParams(), registry);
DefaultHttpClient httpClient = new DefaultHttpClient(mgr, client.getParams()); HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
HttpPost httppost = new HttpPost("https://server.example.com/Login");
List<BasicNameValuePair> nameValuePairs = new ArrayList<BasicNameValuePair>(
2);
nameValuePairs.add(new BasicNameValuePair("LoginId",uname));
nameValuePairs.add(new BasicNameValuePair("Password",pass));
try {
httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
HttpResponse response = httpClient.execute(httppost);
if (response.getStatusLine().getStatusCode() == 200) {
}
Log.i("zacharia", "Response :"+EntityUtils.toString(response.getEntity()));
} catch (Exception e) {
}
The SSL Peer Unverified Exception could be thrown for several reasons, the most common is when the certificate sent by the server is a self signed certificate and not a certificate signed by authorized CA, if that's the issue the common approach in android is adding the certificate to the Trusted Certificates chain and then making the request as follows:
KeyStore selfsignedKeys = KeyStore.getInstance("BKS");
selfsignedKeys.load(context.getResources().openRawResource(R.raw.selfsignedcertsbks),
"genericPassword".toCharArray());
TrustManagerFactory trustMgr = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustMgr.init(selfsignedKeys);
SSLContext selfsignedSSLcontext = SSLContext.getInstance("TLS");
selfsignedSSLcontext.init(null, trustMgr.getTrustManagers(), new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(selfsignedSSLcontext.getSocketFactory());
URL serverURL = new URL("https://server.example.com/endpointTest");
HttpsURLConnection serverConn = (HttpsURLConnection)serverURL.openConnection();
Take on count that this approach is only when you are sure the certificate not signed by a CA, and in order to make it work you need to have the certificate it self, put it in a BKS keystore (for android to read it) and then open an HttpURLConnection using the SSL context that "accepts" that self signed certificate, because the DefaultHttpClient will not handle those requests based on the Default SSLContext.
If you want to learn more about SSL i recommend you to read the book "Application Security for the Android Platform" by Jeff Six Editorial O'Reilly...
Regards!

Android SSL httpGet error 500 server denied the URL

I am trying to start a connection with a microsoft exchange server web access portal. The idea is my program initially opens a web view, and then continually refreshes the page using a BroadcastReceiver. I am aware that ordinarily, exchange web-based email access can be done through a protocol, but I'm trying this anyhow. The timing for the refresh works fine, but the problem is that although I open the connection with a WebView, I'm then doing the refresh with an HttpClient. I am passing the cookies after login from the WebView to the HttpClient successfully, and opening the connection for SSL, yet the web server is kicking back. This works fine in a browser, so I'm kinda lost.
EDIT: Ok, this is a more basic HTTP request question. All of the code that handles the httpGet is setup in the constructor. I don't even make a new HttpClient. Now, This is a BroadcastReceiver, and it responds to a timed message every 60 seconds or so to make the request, but every time the code finishes, the thread seems to die, and every time the timer goes off, the blank constructor is called. When coding an http GET request like this, do I need to be recreating the HttpClient every time? or is my using static variables that hold their value an ok way of doing it? The URL and cookies survive from one call to the next. What else am I missing? The very first call to this thing doesn't seem to work either, so I still think it's that I'm doing SSL wrong. Again, this site works just fine in a web browser, so I'm not emulating the request properly.
This is the constructor code:
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
// RESTORE THE COOKIES!!!
cookieJar = new BasicCookieStore();
localContext = new BasicHttpContext();
localContext.setAttribute(ClientContext.COOKIE_STORE, cookieJar);
cookie = extras.getString("cookies");
String[] cookieCutter = cookie.split(";");
for (int i=0; i < cookieCutter.length; i++)
{
String[] values = cookieCutter[i].split("=");
BasicClientCookie c = new BasicClientCookie(values[0], values[1]);
c.setDomain(MAIL_WEB_SERVER);
cookieJar.addCookie((Cookie)c);
}
HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;
// Create local HTTP context
SchemeRegistry registry = new SchemeRegistry();
SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory();
socketFactory.setHostnameVerifier((X509HostnameVerifier) hostnameVerifier);
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
registry.register(new Scheme("https", socketFactory, 443));
HttpParams params = new BasicHttpParams();
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
ThreadSafeClientConnManager mgr = new ThreadSafeClientConnManager(params, registry);
httpClient = new DefaultHttpClient(mgr, params);
httpClient.setCookieStore(cookieJar);
// Set verifier
HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);
And this is the refresh code:
BasicCookieStore cookies = (BasicCookieStore)localContext.getAttribute(ClientContext.COOKIE_STORE);
HttpGet httpGet = new HttpGet(url);
HttpResponse response = httpClient.execute(httpGet, localContext);
So where does this leave me? I get a Error Code: 500 Internal Server Error. The server denied the specified Uniform Resource Locator (URL). Contact the server administrator. (12202)
I am moving the cookies over, and accepting all certificates...is there anything else from a browser session with an SSL connection and a login that I need to be passing along for the httpGet request?

Android https server connection No peer

I'm developing android application, which connecting to server via https connection.
I've created my_store.bks file with trusted sertificate and using following code to connect to server :
DefaultHttpClient httpclient = new DefaultHttpClient();
KeyStore trustStore = KeyStore.getInstance("BKS");
InputStream in = context.getResources().openRawResource(R.raw.my_store);
try{
trustStore.load(in,pass.toCharArray());
}
catch(Exception e){
Log.v("MyLog", "errror reading sert: " + e.toString());
}
finally{
in.close();
}
SSLSocketFactory socketFactory = new SSLSocketFactory(trustStore);
Scheme sch = new Scheme("https", socketFactory, 443);
httpclient.getConnectionManager().getSchemeRegistry().register(sch);
HttpPost poster = new HttpPost(serverAdress);
UrlEncodedFormEntity ent = new UrlEncodedFormEntity(formparams,"UTF-8");
poster.setEntity(ent);
HttpResponse responsePost = httpclient.execute(poster);
but when I'm executing this code, I've got next error:
javax.net.ssl.SSLPeerUnverifiedException: No peer certificate No peer certificate
here is some similar problem
No peer cert. Not sure which route to take but i don't want ignore SSL certificates.
Does anyone know what it may be? May it be some server error? (server is developing, so may have errors, but in browser it works fine with sert).

How can I connect to an Http server with Basic Auth over SSL on Android?

I know this question has been asked before but none of the answers I have found are helping me. I am trying to connect to a simple login service over SSL with Basic Http Auth. The service is hosted at https://localhost:8443/login. When I hit the service from a browser (on windows, OSX, and Android) it works fine, I put in my username and password and I am authenticated properly. However, when I try to do this through code on my Xoom I get a ClientProtocolException that says "the server failed to respond with a valid HTTP response". Can anyone give me a push in the right direction? Here is my code
String result = null;
HttpParams params = new BasicHttpParams();
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, HTTP.DEFAULT_CONTENT_CHARSET);
HttpProtocolParams.setUseExpectContinue(params, true);
SchemeRegistry schReg = new SchemeRegistry();
schReg.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
schReg.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
ClientConnectionManager conMgr = new ThreadSafeClientConnManager(params, schReg);
DefaultHttpClient client = new DefaultHttpClient(conMgr, params);
HttpGet get = new HttpGet(preferences.getString(getString(R.string.Security_Login_URL),
"https://localhost:80/login"));
String credentials = Base64.encodeToString((username + ":" + password).getBytes(), Base64.URL_SAFE);
if(credentials!=null){
get.addHeader("Authorization","Basic "+credentials);
ResponseHandler responseHandler=new BasicResponseHandler();
try {
result = client.execute(get, responseHandler);
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
Two common problems could cause this:
You should not do the BASIC authentication yourself. The Apache HttpClient has all the functionalities already included to do the BASIC authentication. Look either Basic Authentication or Preemptive BASIC authentication
Does your android device trusts the certificate from the server? When your browser trusts the cert, it doesn't mean that your device is also trusting it. Android has a very limited list of trusted certification authorities. You could check this from your device, when you visit the site on your Android built-in browser. If no warning message appears, everything should be OK. For further information about trusting additional certificates from an android device, you can look at my blog article.
Enjoy

Categories

Resources