Trust Certificate Authority in WebView loadUrl() - android

From a result of security scan program, I have a need to restrict the Certificate Authorities the app trusts.
The scan result points out the line at webView.loadUrl("https://example.com/page");. I see how I can create a SslSocketFactory that uses my TrustManager, but I don't see an API in WebView that allows me to set that.
https://developer.android.com/training/articles/security-ssl.html#UnknownCa
What are some possible ways to achieve this?

I think WebViewClient 's onReceivedSslError method will be a good entry point.
First of all, follow the exact same snippet from https://developer.android.com/training/articles/security-ssl.html#UnknownCa to prepare TrustManager.
TrustManagerFactory tmf = null;
private void initTrustStore() throws
java.security.cert.CertificateException, FileNotFoundException,
IOException, KeyStoreException, NoSuchAlgorithmException {
// Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore trustedKeyStore = KeyStore.getInstance(keyStoreType);
trustedKeyStore.load(null, null);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = new BufferedInputStream(
getResources().getAssets().open("ca.crt"));
Certificate ca;
try {
ca = cf.generateCertificate(caInput);
Log.d(TAG, "ca-root DN=" + ((X509Certificate) ca).getSubjectDN());
}
finally {
caInput.close();
}
trustedKeyStore.setCertificateEntry("ca", ca);
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(trustedKeyStore);
}
Then, extends custom WebViewClient class, checking snippet from https://stackoverflow.com/a/6379434/1099884
private class CheckServerTrustedWebViewClient extends WebViewClient{
public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
Log.d(TAG, "onReceivedSslError");
boolean passVerify = false;
if(error.getPrimaryError() == SslError.SSL_UNTRUSTED){
SslCertificate cert = error.getCertificate();
String subjectDN = cert.getIssuedTo().getDName();
Log.d(TAG, "subjectDN: "+subjectDN);
try{
Field f = cert.getClass().getDeclaredField("mX509Certificate");
f.setAccessible(true);
X509Certificate x509 = (X509Certificate)f.get(cert);
X509Certificate[] chain = {x509};
for (TrustManager trustManager: tmf.getTrustManagers()) {
if (trustManager instanceof X509TrustManager) {
X509TrustManager x509TrustManager = (X509TrustManager)trustManager;
try{
x509TrustManager.checkServerTrusted(chain, "generic");
passVerify = true;break;
}catch(Exception e){
Log.e(TAG, "verify trustManager failed", e);
passVerify = false;
}
}
}
Log.d(TAG, "passVerify: "+passVerify);
}catch(Exception e){
Log.e(TAG, "verify cert fail", e);
}
}
if(passVerify == true)handler.proceed();
else handler.cancel();
}
}
Finally, set the CheckServerTrustedWebViewClient to WebView
webView.setWebViewClient(new CheckServerTrustedWebViewClient());
However, there is one problem. The prepared CA certificate is the exact one sign the server one (intermediate-CA NOT root CA). Only provide root CA certificate will not work. Isn't TrustManager can download server certificate chain on runtime? Any suggestion?

The doc seems to be updated:
Fortunately, you can teach your application to trust custom CAs by configuring your application's Network Security Config, without needing to modify the code inside your application.
blog post I think to be related

Related

Okhttp 3 (android) : connecting to ip address with self signed ssl

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.

ssl android certificate with volley

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);
}
}

Install a certificate for opening site inside a Webview?

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.

SSL handshake exception while connecting over https using self signed certificate in android Nougat

In my android application i connect over https. I am using a self signed certificate to connect.
It is working on devices below api level 24 (before android nougat).But on android Nougat it throws the SSL Handshake exception :
javax.net.ssl.SSLHandshakeException:
java.security.cert.CertPathValidatorException: Trust anchor for
certification path not found.
This is how i connect over https:-
SSLContext context = null;
try
{
KeyStore keyStore = KeyStore.getInstance("PKCS12");
// Get the raw resource, which contains the keystore with
// your trusted certificates (root and any intermediate certs)
InputStream input = new BufferedInputStream(context.getAssets().open(pkcsFilename));
try {
// Initialize the keystore with the provided trusted certificates
// Also provide the password of the keystore
keyStore.load(input, password.toCharArray());
} finally {
input.close();
}
KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyFactory.init(keyStore, "".toCharArray());
// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate ca = null;
input = new BufferedInputStream(context.getAssets().open(certificateFilename));
try
{
ca = cf.generateCertificate(input);
}
finally
{
input.close();
}
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustStore.load(null, null);
trustStore.setCertificateEntry("server", ca);
// Create a TrustManager that trusts the CAs in our KeyStore
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
// Create an SSLContext that uses our TrustManager
context = SSLContext.getInstance("TLS");
context.init(keyFactory.getKeyManagers(), tmf.getTrustManagers(), null);
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
I tried the following link, But it does not help.
This is my network config file. I have added it in my AndroidManifest.xml file.
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="true">xyz.com</domain>
<trust-anchors>
<certificates src="#raw/root_ca" />
</trust-anchors>
</domain-config>
</network-security-config>
Please help me how to solve this .
System.setProperty("javax.net.ssl.trustStore",newKeystoreFile.getAbsolutePath());
simliarly, try to set keystorepassword and keypassword property
restart the server each time whenever you are trying to use above property
https://docs.oracle.com/cd/E17802_01/webservices/webservices/reference/tutorials/wsit/doc/WSIT_Security6.html
i got it working by adding a custom trust managers. While initializing the SSL Context context.init(keyFactory.getKeyManagers(), tmf.getTrustManagers(), null);
I modified it as :
context.init(keyFactory.getKeyManagers(), new TrustManager[] { tm }, null);
TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
for (int j=0; j<chain.length; j++)
{
chain[j].checkValidity();
try {
chain[j].verify(ca.getPublicKey());
} catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchProviderException |
SignatureException e) {
e.printStackTrace();
throw new CertificateException(e.getMessage());
}
}
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
i verify the certificate with the server certificate . Now its working .

HttpsURLConnection - Trusting server in Android application

I'm currently developing an Android app (Android Studio 2.0), that will connect to my server (Glassfish 4.1, Netbeans 8.1) through HTTPS (using javax.net.ssl.HttpsURLConnection). This is currently all being run on my local network (smartphone + laptop). The problem I'm getting now is:
javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
I think because my Android application doesn't trust the server yet. How do I go about so it does indeed trust the server?
In some official documentation (http://developer.android.com/training/articles/security-ssl.html) and other guides, I've seen getting Keystores mentioned, or using .crt files. But where do I get either of those and how do I get them on my mobile device?
Currently my code looks like:
public static HttpsURLConnection setupHttpsConnection(URL url, Context context) {
try {
// Load CAs from an InputStream
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream caInput = new BufferedInputStream(context.getAssets().open("localhost.crt"));
Certificate ca;
try {
ca = cf.generateCertificate(caInput);
Log.d(LOG_TAG, "> setupHttpsConnection > ca.getSubjectDN = " + ((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 sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
/*
HostnameVerifier hostnameVerifier = new HostnameVerifier() {
#Override
public boolean verify(String hostname, SSLSession session) {
HostnameVerifier hv =
HttpsURLConnection.getDefaultHostnameVerifier();
return hv.verify("192.168.0.121", session);
}
};
*/
// Tell the URLConnection to use a SocketFactory from our SSLContext
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
//urlConnection.setHostnameVerifier(hostnameVerifier);
urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
return urlConnection;
} catch (Exception e) {
e.printStackTrace();
Log.d(LOG_TAG, e.toString());
return null;
}
}
Then in another place I call:
urlConnection = Utility.setupHttpsConnection(url, context);
urlConnection.connect(); // <-- Exception thrown here!

Categories

Resources