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.
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.
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 am working on android app that is p2p, and I started using SSLSockets to secure the connection, everything works, and by that I mean I get a connection (not handshake error) established, I receive data, and I am able to send data, but I feel like its only working due to a fluke in the code. I am only using one keystore, and its the same one on my client, and server application.
Server:
String[] enabledCipherSuites = { "SSL_DH_anon_WITH_RC4_128_MD5" };
//serverSkt = new ServerSocket(PORT);
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(getAssets().open("serverkeystore"), "mypassword".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore,"mypassword".toCharArray());
SSLContext sslcontext = SSLContext.getInstance("TLS");
sslcontext.init(kmf.getKeyManagers(), null, null);
SSLServerSocketFactory sslServerSocketF = sslcontext.getServerSocketFactory();
sslServerSkt = (SSLServerSocket) sslServerSocketF.createServerSocket(PORT);
sslServerSkt.setEnabledCipherSuites(enabledCipherSuites);
Client:
String[] enabledCipherSuites = { "SSL_DH_anon_WITH_RC4_128_MD5" };
AssetManager aM = getAssets();
InputStream kSIS = aM.open("serverkeystore");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(kSIS, "mypassword".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
SSLContext sslC = SSLContext.getInstance("TLS");
sslC.init(null,tmf.getTrustManagers(),null);
SSLSocketFactory sslSF = sslC.getSocketFactory();
s1 = (SSLSocket) sslSF.createSocket(IP,PORT);
s1.setEnabledCipherSuites(enabledCipherSuites);
s1.startHandshake();
I read about having two keystores (or certificates?) on the server side (no mentioned of the client side) one would be called truststore (what do they mean by that?) and one called client, like I said earlier, I only made one, and put the same one on each app, is that wrong to do? I used portecle to make the bks keystore file.
I am also conerned about this line here
kmf.init(keyStore,"mypassword".toCharArray());
it seems bad practice to hard code a password. Is there a way around this? Maybe encrypting the string, and have the app decrypt it when it needs the keystore?
I went and googled ssl sockets with android, but most of the results returned for HTTPS, which is not what I need.
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
I 'm trying to Secure my TCP/IP based Socket connection using SSL for Android platform.
here is some snippet from my code:
Client Side:
SSLSocketFactory sslFact = null;
SSLContext ctx;
KeyStore ks;
char[] passphrase = "hosttest".toCharArray();
ks = KeyStore.getInstance("BKS");
ks.load(SSLActivity.mContext.getResources().openRawResource(R.raw.hosttestcert), passphrase);
TrustManagerFactory tmf =TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
tmf.init(ks);
ctx = SSLContext.getInstance("TLS");
ctx.init(null,tmf.getTrustManagers(), null);
sslFact = ctx.getSocketFactory();
mySocket = (SSLSocket)sslFact.createSocket();
mySocket.connect(new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 7891),1000);
Server Side:
SSLServerSocketFactory sslSrvFact = null;
SSLContext ctx;
KeyManagerFactory kmf;
KeyStore ks;
// Load the self-signed server certificate
char[] passphrase = "hosttest".toCharArray();
ks = KeyStore.getInstance("BKS");
ks.load(SSLActivity.mContext.getResources().openRawResource(R.raw.hosttestcert), passphrase);
kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, passphrase);
// Create a SSLContext with the certificate
ctx = SSLContext.getInstance("TLS");
ctx.init(kmf.getKeyManagers(),null, null);
sslSrvFact = ctx.getServerSocketFactory();
myServerSocket =(SSLServerSocket)sslSrvFact.createServerSocket(port);
When I call the accept() socket call from server, I'm getting the following exception:
javax.net.ssl.SSLException: Could not find any key store entries to support the enabled cipher suites.
org.apache.harmony.xnet.provider.jsse.OpenSSLServerSocketImpl.checkEnabledCipherSuites(OpenSSLServerSocketImpl.java:241)
org.apache.harmony.xnet.provider.jsse.OpenSSLServerSocketImpl.accept(OpenSSLServerSocketImpl.java:186)
at com.aricent.ssltesst.ApplinkTCPServer$1.run(ApplinkTCPServer.java:189)
at java.lang.Thread.run(Thread.java:856)
I'm following this post for generating my certs and keystore:
http://randomizedsort.blogspot.in/2010/09/step-to-step-guide-to-programming.html
please suggest if I'm missing something or something else needs to be done for the keystore on Android platform.
Not sure if you have the same logic with what I did, in my application I have 1 JKS Keystore generated, which then I used a converter to duplicate a copy of BKS keystore for Android. If this is what you're doing, here's my Server Side codes:
String keyStorePath = "absolute path to your JKS keystore file";
String keyStorePass = "keystore password";
System.setProperty("javax.net.ssl.keyStore", keyStorePath);
System.setProperty("javax.net.ssl.keyStorePassword", keyStorePass);
SSLServerSocketFactory sslserversocketfactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
SSLServerSocket serverSocket = (SSLServerSocket) sslserversocketfactory.createServerSocket(port_number);
while (true) {
new ClientThread((SSLSocket) serverSocket.accept()).start();
}