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.
Related
Android self-signed client side CA certificate authentication fails, Can anyone share their experience dealing with similar issues? With the same credentials we are able to get it to work using CURL client.
We followed this GIST however we could not get through it.
Following it just led us to have this exception gets thrown:
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:374)
Try the following code
// Input stream for self signed CA certificate
InputStream caIs = getInputStream(caCert);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate certificate = (X509Certificate) certificateFactory.generateCertificate(caIs);
String alias = certificate.getSubjectX500Principal().getName();
Log.d(TAG, String.format("Alias: %s", alias));
// KeyStore for trusted CA certificate(s)
KeyStore trustedStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustedStore.load(null);
trustedStore.setCertificateEntry(alias, certificate);
// Create trust managers to be used for connecting to servics(s)
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(trustedStore);
TrustManager[] trustManagers = tmf.getTrustManagers();
// KeyStore for X.509 certificate/key
KeyStore keyStore = KeyStore.getInstance("PKCS12");
InputStream clientIs = getInputStream(clientKeyCert);
keyStore.load(clientIs, "password".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
kmf.init(keyStore, "password".toCharArray());
// Create key managers to be uses for connecting to service(s)
KeyManager[] keyManagers = kmf.getKeyManagers();
// Create the SSL context
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagers, trustManagers, null);
// Test by connecting ta server proteced by self signed certificate
OkHttpClient client = new OkHttpClient.Builder().sslSocketFactory(sslContext.getSocketFactory()).build();
Call call = client.newCall(new Request.Builder().url("https://testurl.com").build());
Response response = call.execute();
Log.d(TAG, response.message());
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 a website which I want to open in an Android Webview. The website uses a certificate emitted by COMODO RSA Domain Validation Secure Server CA. The problem is that I get an Unkown Certificate Error for devices all running a version less than (including) Android 5.
I serched in the documentation and as far as I understood the problem is that the CA was created before the release of Android 5.
I could do a handler.proceed(); on onReceivedSslError but I want to keep the app secure and I think Google can reject the app on play store anyway.
I found that I cound do something like this
// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// From https://www.washington.edu/itconnect/security/ca/load-der.crt
InputStream caInput = new BufferedInputStream(new FileInputStream("load-der.crt"));
Certificate ca;
try {
ca = cf.generateCertificate(caInput);
System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());
} finally {
caInput.close();
}
// Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
// Create an SSLContext that uses our TrustManager
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
// Tell the URLConnection to use a SocketFactory from our SSLContext
URL url = new URL("https://certs.cac.washington.edu/CAtest/");
HttpsURLConnection urlConnection =
(HttpsURLConnection)url.openConnection();
urlConnection.setSSLSocketFactory(context.getSocketFactory());
InputStream in = urlConnection.getInputStream();
copyInputStreamToOutputStream(in, System.out);
but the problem is that my code is like so
myWebView.loadUrl("https://site.domain.com")
How can I install the certificate of the CA within my Webview?
Thanks
You need to override method to your WebViewClient implementation. Try this:
public void onReceivedSslError(WebView view, SslErrorHandler handler,
SslError error) { handler.proceed(); }
Also, it could be with the SSL certificate installed on the website that you wish to run on webview. The SSL certificate provided by the authority is not supported on mobile, assuming if there's no error viewing the website on a desktop.
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.
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