I am trying to learn how to do certificate pinning in an Android application. I found the tutorial here. I wanted to clarify I doubt I have based on my testing this code.
I used the code as follows :
public class CertificatePinning {
static SSLSocketFactory constructSSLSocketFactory(Context context) {
SSLSocketFactory sslSocketFactory = null;
try {
AssetManager assetManager = context.getAssets();
InputStream keyStoreInputStream = assetManager.open("myapp.store");
KeyStore trustStore = KeyStore.getInstance("BKS");
trustStore.load(keyStoreInputStream, "somepass".toCharArray());
sslSocketFactory = new SSLSocketFactory(trustStore);
sslSocketFactory.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
}
catch(Exception e){
Log.d("Exception", e.getLocalizedMessage());
}
return sslSocketFactory;
}
public static HttpClient getNewHttpClient(Context context) {
DefaultHttpClient httpClient = null;
try {
SSLSocketFactory sslSocketFactory = constructSSLSocketFactory(context);
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", sslSocketFactory, 443));
ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);
httpClient = new DefaultHttpClient(ccm, params);
} catch (Exception e) {
Log.d("Exception", e.getLocalizedMessage() );
return null;
}
return httpClient;
}
}
Quoting a statement from that tutorial :
On the client side, you simply need to distribute the signing certificate
with your app and validate against it.
On my web server, I have my own CA, which I created using open SSL, and used to sign certificates for different domain names that are used with my app.
This statement indicates that this tutorial is meant for the CA certificate I have. I tested the code using ca.pem (from my CA's crt file) and it works fine.
But I also tested the same code with a certificate I signed with that CA, e.g. server.pem ( from the signed server.crt), and still it works.
Did I do something wrong, or is this code meant for pinning either :
1) a CA certificate (covering all certificates signed by that CA) or
2) a particular certificate (signed by some CA) ?
Related
I have a question about keystore.
Do android have a build in keystore?
If yes, where is the keystore?
Can I import my certificate into the keystore?
If yes, how can I do it manually and programmatically?
Please help, I have struggled with the SSLHandShakeException for a week.
I have used the keytool to generate a new keystore.bks and inserted the my certificate into it but I didn't know how can I use it for my android application.
I also tried the code that I have found online.
In MyHttpsClient.java
public class MyHttpsClient extends DefaultHttpClient{
final Context context;
public MyHttpsClient(Context context) {
System.out.println("context client http");
this.context = context;
}
#Override
protected ClientConnectionManager createClientConnectionManager(){
SchemeRegistry registry = new SchemeRegistry();
System.out.println("schema");
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
registry.register(new Scheme("https", newSslSocketFactory(), 8080));
return new SingleClientConnManager(getParams(), registry);
}
private SSLSocketFactory newSslSocketFactory(){
try{
KeyStore trusted = KeyStore.getInstance("BKS");
System.out.println("context client getInstance..");
InputStream in = context.getResources().openRawResource(R.raw.server);
System.out.println("context client input" + in);
try{
System.out.println("try");
trusted.load(in, "changeit".toCharArray());
}finally{
in.close();
}
System.out.println("finally");
SSLSocketFactory sf = new SSLSocketFactory(trusted);
sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
return sf;
}catch(Exception e){
throw new AssertionError(e);
}
}
}
In my Main.java
DefaultHttpClient client = new MyHttpsClient(getApplicationContext());
I have the code above in the onCreate() of my Main.java, but I have no idea why it doesn't go into my #Override function in MyHttpsClient.java
Can I read the original android emulator keystore and put the certificate into the keystore?
Is that possible to pinning webview Certificate inside onReceivedClientCertRequest method.
I want to implement something like :
#Override
protected ClientConnectionManager createClientConnectionManager() {
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
// Register for port 443 our SSLSocketFactory with our keystore
// to the ConnectionManager
registry.register(new Scheme("https", newSslSocketFactory(), 443));
return new SingleClientConnManager(getParams(), registry);
}
private SSLSocketFactory newSslSocketFactory() {
try {
// Get an instance of the Bouncy Castle KeyStore format
KeyStore trusted = KeyStore.getInstance("BKS");
// Get the raw resource, which contains the keystore with
// your trusted certificates (root and any intermediate certs)
InputStream in = MyHttpClient.context.getResources().openRawResource(R.raw.codeprojectssl); //name of your keystore file here
try {
// Initialize the keystore with the provided trusted certificates
// Provide the password of the keystore
trusted.load(in, "YourKeystorePassword".toCharArray());
} finally {
in.close();
}
// Pass the keystore to the SSLSocketFactory. The factory is responsible
// for the verification of the server certificate.
SSLSocketFactory sf = new SSLSocketFactory(trusted);
// Hostname verification from certificate
// http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506
sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER); // This can be changed to less stricter verifiers, according to need
return sf;
} catch (Exception e) {
throw new AssertionError(e);
}
}
inside this method that override from webview client :
#Override
#TargetApi(21)
public void onReceivedClientCertRequest (WebView view, ClientCertRequest request)
{
// By default pass to WebViewClient
super.onReceivedClientCertRequest(view, request);
}
is that possible, and is there a good example, because I can't find on google ?
I have created the bks file for my server's certificate. This was added in the project's sources in raw folder
I've created my https client as follows:
public class MyHttpsClient extends DefaultHttpClient {
final Context context;
public MyHttpsClient(Context context) {
this.context = context;
}
#Override
protected ClientConnectionManager createClientConnectionManager() {
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
// Register for port 443 our SSLSocketFactory with our keystore
// to the ConnectionManager
registry.register(new Scheme("https", newSslSocketFactory(), 443));
return new SingleClientConnManager(getParams(), registry);
}
private SSLSocketFactory newSslSocketFactory() {
try {
// Get an instance of the Bouncy Castle KeyStore format
KeyStore trusted = KeyStore.getInstance("BKS");
// Get the raw resource, which contains the keystore with
// your trusted certificates (root and any intermediate certs)
InputStream in = context.getResources().openRawResource(R.raw.mykeystore);
try {
// Initialize the keystore with the provided trusted certificates
// Also provide the password of the keystore
trusted.load(in, "testpassword".toCharArray());
} finally {
in.close();
}
// Pass the keystore to the SSLSocketFactory. The factory is responsible
// for the verification of the server certificate.
SSLSocketFactory sf = new SSLSocketFactory(trusted);
// Hostname verification from certificate
// http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506
sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
return sf;
} catch (Exception e) {
throw new AssertionError(e);
}
}
}
Then I use it like:
DefaultHttpClient httpClient = new MyHttpsClient(context);
HttpConnectionParams.setConnectionTimeout(httpClient.getParams(), 30000);
HttpContext localContext = new BasicHttpContext();
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("Accept", "application/json");
httpPost.setHeader("Content-type", "application/json");
httpPost.setEntity(new StringEntity(jsonString));
response = httpClient.execute(httpPost, localContext);
HttpEntity entity = response.getEntity();
httpresponse = getResponse(entity);
Now here comes the funny part. This works just fine on android 4+ both real devices and emulator. THIS fails on android 2.3 with
javax.net.ssl.SSLPeerUnverifiedException: No peer certificate
How can I make it work on android 2.3 without the known "trust all certificates" way ?
A few days age i solved same problem. The solution consisted in the following. I take all certificates installed on device(in my case it was samsung galaxy II) and go to the server side developer, who manage certificate chain installed on server. He analyzes ssl chain and detected that in chain there is one certificate (Thawte 2006) and other certificate (Thawte 2010). He removed oldest certificate, which issued in 2006, and ssl verification on android 2.x become working. I suggest you, before trying to get work local keystore, research your server side ssl chain and check that this chain doesn't have unnecessary certificates, because android 2.x devices cannot ignore not necessary certificates, but other platforms 3.x 4x and ios, windows phone can do it, i mean ignore "trash" in ssl certificate chain.
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!
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();
}
}