We try to send several requests to target server in one HttpClient (one session). The target server will first authenticate all requests with digest authentication (based on MD5-sess). The result shows that only first access is successful. The following accesses are rejected by server because server treats later accesses as replay attack as the "nc" value is always "00000001".
It seems Android HttpClient hard-coded digest authorization header attirbute "nc" to "00000001"?
Any way for client to increase this value when new request is sent? Thanks.
public class HttpService {
private static final HttpService instance = new HttpService();
private HttpService() {
client = getHttpClient();
}
public static HttpService getInstance() {
return instance;
}
private DefaultHttpClient getHttpClient() {
HttpParams params = new BasicHttpParams();
HttpConnectionParams.setStaleCheckingEnabled(params, false);
HttpConnectionParams.setConnectionTimeout(params, 15 * 1000);
HttpConnectionParams.setSoTimeout(params, 15 * 1000);
HttpConnectionParams.setSocketBufferSize(params, 8192);
HttpProtocolParams.setUserAgent(params, USER_AGENT);
SchemeRegistry schemeRegistry = new SchemeRegistry();
Scheme httpScheme = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80);
Scheme httpsScheme = new Scheme("https", SSLCertificateSocketFactory.getHttpSocketFactory(30 * 1000, null), 443);
schemeRegistry.register(httpScheme);
schemeRegistry.register(httpsScheme);
ClientConnectionManager manager = new ThreadSafeClientConnManager(params, schemeRegistry);
//create client
DefaultHttpClient httpClient = new DefaultHttpClient(manager, params);
httpClient.getCredentialsProvider().setCredentials(new AuthScope(address, port),
new UsernamePasswordCredentials(username, password));
}
}
Android ships with an extremely outdated (pre-BETA) fork of Apache HttpClient. There has been a countless number of changes in the stock version of HttpClient since 4.0 ALPHA, also in the Digest auth area.
The best thing you can do is to copy the DigestScheme from the stock version of Apache HttpClient and configure your application to use the copy instead of the default implementation.
https://github.com/apache/httpcomponents-client/blob/master/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/DigestScheme.java
For that end you will have to register a custom DigestSchemeFactory instance with the auth scheme registry.
https://github.com/apache/httpcomponents-client/blob/master/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/DigestSchemeFactory.java
It seems you are right. This is from Digest.java:
//TODO: supply a real nonce-count, currently a server will interprete a repeated request as a replay
private static final String NC = "00000001"; //nonce-count is always 1
You should file a bug at http://b.android.com. You have a few options:
calculate the digest and create the header yourself, then set it on every request. You could add a HttpRequestInterceptor to make this authomatic (don't set the credentials to the credential provider)
AFAIK authentication schemes should be pluggable, so implement digest auth properly and configure your client to use it.
use another HTTP client library.
EDIT: Since it's fixed in the stock version, there is another alternative:
Another alternative:
change the Apache HttpClient package name using jarjar, package it with the app, and don't use the Android system one at all.
Related
I have developed an Android app and it is running smooth. Not yet into production. This app communicates with the server using REST services. Now
arrived an issue. As of now I am using HTTP to communicate with the server and get the info back from the server.
I wish to have the data transmission more secured. Can anyone please shed some light on how to accomplish this? I am clueless about HTTPS and implementing it in Android.
Any pointers and links will be helpful like how to start. Do I need to purchase certificates and get it signed.
Thanks
I fairly certain you don't need to do anything special for HTTPS in Android, just change your url to start with "https". new URL(url).openConnection() should work fine with that. If you're using Apache's HttpClient stuff, you might need to define your own client. Below is code I have from a project I'm currently working on.
public static DefaultHttpClient makeHTTPClient() {
BasicHttpParams mHttpParams = new BasicHttpParams();
// Set the timeout in milliseconds until a connection is established.
// The default value is zero, that means the timeout is not used.
int timeoutConnection = 15000;
HttpConnectionParams.setConnectionTimeout(mHttpParams, timeoutConnection);
// Set the default socket timeout (SO_TIMEOUT)
// in milliseconds which is the timeout for waiting for data.
int timeoutSocket = 20000;
HttpConnectionParams.setSoTimeout(mHttpParams, timeoutSocket);
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
final SSLSocketFactory sslSocketFactory = SSLSocketFactory.getSocketFactory();
sslSocketFactory.setHostnameVerifier(SSLSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
registry.register(new Scheme("https", sslSocketFactory, 443));
ClientConnectionManager cm = new ThreadSafeClientConnManager(mHttpParams, registry);
DefaultHttpClient defaultHttpClient = new DefaultHttpClient(cm, mHttpParams);
return defaultHttpClient;
}
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.
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?
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
I am trying to make a secure connection to a OCS server through https in android.
I found the EasySSLFactory and EasyX509TrustManager classes to make android trust the certificate but I don't know how to initialize only one time the EasySSLFactory and EasyX509TrustManager objects.
I have the following code to accept a certificate and make a single connection:
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("https", new EasySSLSocketFactory(),
443));
HttpParams params = new BasicHttpParams();
params.setParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 3);
params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE,
new ConnPerRouteBean(1));
params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false);
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, "utf8");
int timeoutConnection = 1000;
HttpConnectionParams.setConnectionTimeout(params, timeoutConnection);
int timeoutSocket = 1000;
HttpConnectionParams.setSoTimeout(params, timeoutSocket);
clientConnectionManager = new ThreadSafeClientConnManager(params,
schemeRegistry);
HttpClient client = new DefaultHttpClient(clientConnectionManager,
params);
In order to make a new connection in an new method, I have to do write those lines too...
Is there a way that I can put them in the class constructor and then do connections in that class without writing that before the connection..
Thank you
Look at my blog article. I've posted a detailed description how you can add your desired certificate to a custom keystore and initialize the HttpClient with it.
Hope this helps
EDIT: I havent tried it, but maybe the TrustStrategy interface may help.
You could implement your own TrustStrategy interface and initialize the SSLSocketFactory with the appropriate constructor. Your strategy can just return true (in the isTrusted method), but you should do for security reasons a bit of checking to be sure if the certificate can be considered as trusted (it depends on your needs)
Look at line 35 on my blog article of the SecureHttpClient class. Replace the line with something like this:
SSLSocketFactory sf = new SSLSocketFactory(myTrustStrategy);
Please let me know if this works for you.
Regards