I want to use SSL Pinning in volley network library. Is there any way to implement SSL pinning with volley? Does volley provide this support for security improvements?
I just implemented it like described here: http://blog.ostorlab.co/2016/05/ssl-pinning-in-android-networking.html
Here is the needed code for a volley-implementation:
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// Generate the certificate using the certificate file under res/raw/cert.cer
InputStream caInput = new BufferedInputStream(getResources().openRawResource(R.raw.cert));
Certificate ca = cf.generateCertificate(caInput);
caInput.close();
// Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore trusted = KeyStore.getInstance(keyStoreType);
trusted.load(null, null);
trusted.setCertificateEntry("ca", ca);
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(trusted);
// Create an SSLContext that uses our TrustManager
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
SSLSocketFactory sf = context.getSocketFactory();
mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext(), new HurlStack(null, sf));
Seems to work!
I just looked into the same thing for a project I am working on. The position I am in may be different to you however.
I am using Volley with an OKHttp Network stack (https://gist.github.com/JakeWharton/5616899):
Add these to your Gradle Build:1
compile "com.squareup.okhttp:okhttp:2.7.5"
compile "com.squareup.okhttp:okhttp-urlconnection:2.7.5"
Add a OKHttpStack class;
public class OKHttpStack extends HurlStack {
private final OkUrlFactory okUrlFactory;
public OKHttpStack() {
this(new OkUrlFactory(
new OkHttpClient.Builder()
.certificatePinner(
new CertificatePinner.Builder()
.add("example.com", "sha256/afwiKY3RxoMmLkuRW1l7QsPZTJPwDS2pdDROQjXw8ig=") //This is the cert
.build())
.build();
));
}
public OKHttpStack(OkUrlFactory okUrlFactory) {
if (okUrlFactory == null) {
throw new NullPointerException("Client must not be null.");
}
this.okUrlFactory = okUrlFactory;
}
#Override
protected HttpURLConnection createConnection(URL url) throws IOException {
return okUrlFactory.open(url);
}
}
When you then create your RequestQueue do something like:
Network network = new BasicNetwork(new OKHttpStack());
File cacheDir = new File(context.getCacheDir(), "volley");
int threads = 4;
mRequestQueue = new RequestQueue(new DiskBasedCache(cacheDir), network, threads);
Please note I have yet to test this, we are thinking about pinning at the moment.
Good luck!
Gav
References:
https://gist.github.com/JakeWharton/5616899
https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/CertificatePinning.java
You can use public key pinning instead of certificate pinning:
Public Key Pinning with Volley Library
I am implementing the same exact thing. I found a blog post that will hopefully be of help to you
http://ogrelab.ikratko.com/using-android-volley-with-self-signed-certificate/
You can use network_security_config.xml, more info : https://developer.android.com/training/articles/security-config
Related
I am currently attempting to connect to a server which has no domain name and is only reachable by its ip address. I previously attempted to do it by using the library volley, but after spending a day of research I couldn't figure out why the ssl handshake wouldn't work. after switching to Okhttp I got the warning:
javax.net.ssl.SSLPeerUnverifiedException: Hostname 185.101.92.193 not verified:
certificate: sha256/QMgPlAslWrBi1dd/P17AKxJCniO2RfHQ5MufVO5Xji4=
DN: 1.2.840.113549.1.9.1=#1619626c61636b6a61636b34323636323440676d61696c2e636f6d,CN=185.101.92.193,O=Internet Widgits Pty Ltd,L=Berlin,ST=Berlin,C=DE
subjectAltNames: []
Now this problem has already been addressed on github: https://github.com/square/okhttp/issues/1467
I have "resolved" the problem with the following code (look at HostnameVerifier at the bottom):
// loading CAs from an InputStream
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream cert = context.getResources().openRawResource(R.raw.servercert);
Certificate ca;
try {
ca = cf.generateCertificate(cert);
} finally {
cert.close();
}
// creating a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
// creating a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
// creating an SSLSocketFactory that uses our TrustManager
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
client = new OkHttpClient.Builder().sslSocketFactory(sslContext.getSocketFactory())
.hostnameVerifier(new HostnameVerifier() {
#Override
public boolean verify(String s, SSLSession sslSession) {
if(s.equals(myIPAddress)){
return true;
}else{
return false;
}
}
})
.build();
}catch (Exception e){
e.printStackTrace();
}
Now this kinda looks like bad practice to me and my actual question is: what problems could arise from implementing the HostnameVerifier like this (security-wise) and how could I solve this matter in a more sophisticated way?
There is nothing to do with the domain name, the only issue is that your application (android) can't verify the certificate because it's private (self signed certificate). What you did in your code is just trying to bypass the validation process by overriding the SSLFactory and created a new one that uses your CAs.
Check android documentation below:
https://developer.android.com/training/articles/security-ssl.html#CommonProblems
You can continue of what you have. or buy a certificate and then there is no need for this code at all.
i have implemented the certificate and keystore to my app and succeeded in making request to my server , but now i want to accept another server's HTTPS (for online payment) to be able to integrate it in my app but volley says that it doesn't accept it , can i accept this domain without their keystore
and this is my code for accepting my server's certificate
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 = getApplicationContext().getResources().openRawResource(R.raw.keystore);
try {
// Initialize the keystore with the provided trusted certificates
// Provide the password of the keystore
trusted.load(in, KEYSTORE_PASSWORD);
} finally {
in.close();
}
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(trusted);
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
SSLSocketFactory sf = context.getSocketFactory();
return sf;
} catch (Exception e) {
throw new AssertionError(e);
}
}
My Android project (OkHttp 3.3.1) currently works with my HTTPS web service (my PC, IIS web server, Asp.Net Web API, self-signed certificate)
Helper methods:
private SSLSocketFactory getSSLSocketFactory()
throws CertificateException, KeyStoreException, IOException,
NoSuchAlgorithmException, KeyManagementException {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = getResources().openRawResource(R.raw.iis_cert);
Certificate ca = cf.generateCertificate(caInput);
caInput.close();
KeyStore keyStore = KeyStore.getInstance("BKS");
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
return sslContext.getSocketFactory();
}
private HostnameVerifier getHostnameVerifier() {
return new HostnameVerifier() {
#Override
public boolean verify(String hostname, SSLSession session) {
HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
return hv.verify("BNK-PC.LOCALHOST.COM", session);
}
};
}
Code A:
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(getSSLSocketFactory())
.hostnameVerifier(getHostnameVerifier())
.build();
After reading this CertificatePinner guide, my project also works well when adding .certificatePinner(certificatePinner) as below:
Code B:
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(getSSLSocketFactory())
.certificatePinner(certificatePinner)
.hostnameVerifier(getHostnameVerifier())
.build();
According to this Wiki, certificate pinning increases security.
However, actually I have not clearly understood this idea. So my question is that whether I need to or have to use certificatePinner or not when my app still works with the Code A. In other words, does Code B have better security than Code A?
Certificate Pinning should help with certain classes of attacks
Any trusted certificate authority gets hacked and generates valid certificates for your domain which a MITM attack e.g. by an invasive government.
Your app is running on a device with additional trusted certificates e.g. installed by the Corporation that supplied the phone.
I think generally if you issued your certificates from two major CA e.g. verisign, you would pin to their signing certificates rather than your own. This is because you are likely to generate new certificates for your server as a routine thing.
I'm a little confused on how to use the system trusted credentials with OkHttp. I'm using the following code:
SSLContext sslContext = SSLContext.getInstance("TLS");
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
String algo = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf =TrustManagerFactory.getInstance(algo);
tmf.init(ks);
TrustManager managers[]= tmf.getTrustManagers();
X509TrustManager defaultTrustMgr = null;
for (int i = 0; i < managers.length; i++) {
if (managers[i] instanceof X509TrustManager) {
defaultTrustMgr = (X509TrustManager) managers[i];
}
}
X509Certificate[] x509Certificates = defaultTrustMgr.getAcceptedIssuers();
sslContext.init(null, managers, null);
However when I'm using charles I'm still able to sniff the traffic thus allowing for a man in the middle attack. How do I configure OkHttp to use the default certs that come with Android?
Thanks, Graeme
You need to explicitly configure OkHttp's socket factory:
okHttpClient.setSslSocketFactory(sslContext.getSocketFactory());
Previously OkHttp used the system default socket factory, but this was problematic. In particular, configuring that socket factory to support NPN and ALPN for SPDY and HTTP/2 caused other code that shared that socket factory to crash.
The app I am working on uses Onionkit to do TSL/SSL certificate verification. I added Volley support in the app, but realized that Volley only supports the Apache HttpClient, while Onionkit uses the boye.androidlib HttpClient. So these 2 do not work together.
Right now I am using the fork of Volley at here https://github.com/kulik/volley.git, it works with the boye library so my app is working.
However I would rather just use Volley. Is there a way to make it work? I looked at HurlStack class, it has the SSLSocketFactory field. I suppose this one will work for the Cert verification. But I have not figured out a way to get it work. Sorry for the kind of vague question but I am not a security expert. When using the Onikit, I passed in the cert pins to the TrustManager. However I have not found a way to link the SSLFactory with the TrustManager. Any help will be appreciated.
Thanks
Ray
You can use HurlStack to use different HttpClient implementations. For example, see how to use OkHttp with Volley:
public class OkHttpStack extends HurlStack {
private final OkHttpClient client;
public OkHttpStack() {
this(new OkHttpClient());
}
public OkHttpStack(OkHttpClient client) {
if (client == null) {
throw new NullPointerException("Client must not be null.");
}
this.client = client;
}
#Override protected HttpURLConnection createConnection(URL url) throws IOException {
return client.open(url);
}
}
and use it with
Volley.newRequestQueue(context, new OkHttpStack())
It should be easy to derive how to use your http client from that. If you want to use your own SSL logic you can overwrite this method from HurlStack
private static SSLSocketFactory createPinnedSSLCertFactory(Context ctx) {
//create your implementation
}
I dont really know what exactly you want to do here but here is an example that loads an empty keystore and pins it to only 1 cert (any others would not be accepted -> ssl pinning)
private static SSLSocketFactory createPinnedSSLCertFactory(Context ctx) {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate myCert = //read certificate in;
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null); //inputstream null creates new keystore
keyStore.setCertificateEntry("mycert", myCert );
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
return context.getSocketFactory();
}
Note: I had problems with creating my own SSLFactory with Gingerbread (2.3) and lower meaning it didn't work.