How to prevent root device to bypass certificate pinning in Android? - java

I am developing a project that require the Android app can prevent bypassing certificate pinning/trust a fake cert when doing network calling even in a rooted devices.
So far I can make it when the device is not rooted. I just need to prevent some bypassing method like using JustTrustMe in Xposed framework.
I am using retrofit and okHttp during network calling.
I have tried the using CertPinner in okHttp and its version is 3.10.0
and also tried to follow the code in android developer https://developer.android.com/training/articles/security-ssl#java
here is the sample code i have tried and copied from google
// 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);
And the cert pinning sample code
String hostname = "publicobject.com";
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add(hostname, "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build();
OkHttpClient client = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build();
Request request = new Request.Builder()
.url("https://" + hostname)
.build();
client.newCall(request).execute();
Both are the simplest code but none of it working
I would like to make it at least prevent some bypassing method like using JustTrustMe in Xposed framework/some easy automated bypassing method.
May i know if it is possible to do it or not, I have also tried some libs like
https://github.com/moxie0/AndroidPinning
suggested by JustTrustMe
https://github.com/Fuzion24/JustTrustMe

After some testing, load CAs from an InputStream would not work for all rooted devices with bypassing module enabled. It still works for normal device
The only way I could prevent it is to use public key cert pinning with proguard at the same time, hope this only help some ppl encounter the same problems.

Related

Having trouble getting Oracle SSLEngineSimpleDemo.java working

I took the following example and moved code around so it is better simulating an actual client and actual a server where
the server only has access to the trust store file
the client only has access to the client keystore file
At least in TLS1v2, that is how it worked. After I rework the code so there are two SSL contexts(one server side and one client side), it blows up and does not work
javax.net.ssl.SSLHandshakeException: No available authentication scheme
The code I reworked now reads like this
public SSLEngineSimpleDemo() throws Exception {
File baseWorkingDir = FileFactory.getBaseWorkingDir();
File keyStoreFile = FileFactory.newFile(baseWorkingDir, "src/test/resources/client2.keystore");
char[] passphrase = "123456".toCharArray();
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(keyStoreFile), passphrase);
clientCtx = SSLContext.getInstance("TLS");
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, passphrase);
clientCtx.init(kmf.getKeyManagers(), null, null);
File trustStoreFile = FileFactory.newFile(baseWorkingDir, "src/test/resources/server2.keystore");
KeyStore ts = KeyStore.getInstance("JKS");
ts.load(new FileInputStream(trustStoreFile), passphrase);
serverCtx = SSLContext.getInstance("TLS");
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ts);
serverCtx.init(null, tmf.getTrustManagers(), null);
}
I have code like this that works on TLS1v2 so I am not sure why in TLS1v3, this is not working anymore.
What do I have wrong here?
Is my assumption correct in that the trustStoreFile is my private server key?
Is my assumption correct in that the clientStoreFile is my public key?
Is my assumption correct in that the server only needs the private key?
Is my assumption correct in that the client only needs the public key?
Java version: /Library/Java/JavaVirtualMachines/jdk-11.0.5.jdk
OMG, I am an idiot. The issue was my key generation script naming the first thing client2.keystore(Which was the server2.keystore).
once I fix script to generate the private key/public key par into server2.keystore(instead of the mistake of client2.keystore), export, import public key into client2.keystore, it all works.
I should have provided that script :(.
The exception javax.net.ssl.SSLHandshakeException: No available authentication scheme happens when the operating system running your server doesn't support the authentication method the JVM is looking for.
Additionally, TLSv1.3 can be explicitly specified using when instantiating an SSL context.
Change your clientCtx = SSLContext.getInstance("TLS"); to clientCtx = SSLContext.getInstance("TLSv1.3");
and
serverCtx = SSLContext.getInstance("TLS"); to serverCtx = SSLContext.getInstance("TLSv1.3");
Note: SSLContext supports more options such as
SSLv3,TLSv1,TLSv1.1,TLSv1.2

How to use SSL Client certificate with Apache commons http client

My application is using Apache Commons HTTP Client to consume HTTP service URL. Now we have to move over HTTPS endpoint URL. To consume the same, we received SSL Client Certificate. How we can use .JKS with password while consuming HTTPS URL ? (Due to application limitations cant use other APIs)
KeyStore identityKeyStore = KeyStore.getInstance("JKS");
FileInputStream identityKeyStoreFile = new FileInputStream(new File(certificatePath));
identityKeyStore.load(identityKeyStoreFile, password.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(identityKeyStore);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(identityKeyStore, password.toCharArray());
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(keyManagerFactory.getKeyManagers(), tmf.getTrustManagers(), null);
SSLContext.setDefault(sslContext);
PostMethod post = new PostMethod("https://url");
HttpClient httpClient = new HttpClient();
String reqMessage = getSolaceRequestMessage(message,hostName,port,authentication);
Part[] parts = {
new StringPart("reqMessage", message),
};
post.setRequestEntity(
new MultipartRequestEntity(parts, post.getParams())
);
httpClient.executeMethod(post);
The *.jks we use in the back service part.
I can give you a example of my project Java Spring boot, I change http --> https in my back service and I added my certificate in Nginx.
Example of https simple services
When you changed back service you can call https directly in your front application(ex.web angular).
I used below implementation which worked for me as had limitation not to upgrade the http client libraries.
System.setProperty(JAVAX_NET_SSL_TRUSTSTORE, "H://certificateFile.jks");
System.setProperty(JAVAX_NET_SSL_TRUSTSTORE_KEY, "abcd");

X509 Certificate Custom validation (for specific paths)

Im trying to validate self-signed certificate against self-signed root CA (given by service provider who will make calls to my service). Now, if i enable two way ssl all works fine but SSL requirement is globally enforced. I need to use it just for one request, and for one user. Other services should not be affected and since there is no way (unless im mistaken) of enabling it for specific paths, i deactivated two ssl and instead, in my controller im doing this:
String auth = request.getHeader("Authorization");
X509Certificate[] cert = (X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate");
ResourceLoader resourceLoader = new DefaultResourceLoader();
try {
KeyStore trustStore = KeyStore.getInstance("JKS");
InputStream certInStream = resourceLoader.getClassLoader().getResourceAsStream("keystore/CaCertificate.jks");
trustStore.load(certInStream, "changeit".toCharArray());
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");
trustManagerFactory.init(trustStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
for (TrustManager tm: trustManagerFactory.getTrustManagers())
((X509TrustManager)tm).checkClientTrusted(cert, "RSA");
}
Thus my question is, is this method of validating certificate same as default?
Another thing is that unless i configure the following correctly:
server.ssl.trust-store=keystore/truststore.jks
server.ssl.trust-store-password=pass
server.ssl.trust-store-type= JKS
server.ssl.client-auth=want
then getAttribute method returns null. And i very much want to know why it happens. So the certificate still goes validation but request doesnt fail and its attrbute(SSL one) is not set if client certificate is not trusted? So essentially the code i wrote is useless and i can just check if following is null?
X509Certificate[] cert = (X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate");
please help im thoroughly lost.
EDIT:
For testing purposes im using .net 4.5 HttpWebRequest. With this:
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) =>
{
return true;
}

CRL check during SSH connection

I have an Android application where I use HttpURLConnection for SSL connection to my server. The server certificate contains CRL Distribution Points with valid URI. This certificate was revoked and CRL by URI contains this information. But I don’t receive any exception during the handshake and I can receive any information from my server. I use Android 6 and 7.
I found some posts where developers write that Android disables revocation checks by default. Also, I saw some examples with setting PREFER_CRLS option to PKIXRevocationChecker and setting it to TrustManagerFactory but seems that it applied only for Java SE, when I try this code in my app I receive exception initializing TrustManagerFactory:
java.security.InvalidAlgorithmParameterException: Unsupported spec: javax.net.ssl.CertPathTrustManagerParameters#dccac9. Only android.security.net.config.RootTrustManagerFactorySpi$ApplicationConfigParameters supported
at android.security.net.config.RootTrustManagerFactorySpi.engineInit(RootTrustManagerFactorySpi.java:44)
network_security_config.xml file is correct:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config>
<trust-anchors>
<certificates src="#raw/ca_test"/>
</trust-anchors>
</base-config>
</network-security-config>
here is my code:
CertificateFactory cf = CertificateFactory.getInstance("X.509");
AssetManager am = getResources().getAssets();
Certificate ca;
try (InputStream caInput = am.open("ca_test.pem")) {
ca = cf.generateCertificate(caInput);
Log.d(LOG_TAG, "ca = " + ((X509Certificate) ca).getSubjectDN());
}
// Create a KeyStore containing our trusted CAs
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
keyStore.setCertificateEntry("ca", ca);
KeyManagerFactory kmf =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX");
PKIXRevocationChecker rc =(PKIXRevocationChecker)cpb.getRevocationChecker();
rc.setOptions(EnumSet.of(
PKIXRevocationChecker.Option.PREFER_CRLS, // prefer CLR over OCSP
PKIXRevocationChecker.Option.SOFT_FAIL)); // handshake should not fail when CRL is not available
PKIXBuilderParameters pkixParams = new PKIXBuilderParameters(keyStore, new X509CertSelector());
pkixParams.addCertPathChecker(rc);
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(new CertPathTrustManagerParameters(pkixParams));
kmf.init(keyStore, null);
So I try to understand how can I enable CRL check for my app.
Can it be done via shell for root device?
Is there any way to override parameters for your own key store? Or there is any way to enable it for system android key store?
Also, I found the bug on this here: https://issuetracker.google.com/issues/36993981
but I don't see any updates for this issue.
Someone knows any solution for Android app developers?

Setting a client certificate as a request property in a Java HTTP connection?

I have a Java application that connects to another Java app through a socket with SSL, so my client JVM already has the -Djavax.net.ssl.keyStore and -Djavax.net.ssl.trustStore properties set.
This application needs to make some HTTP requests to a web server that requires client authentication. I can open the connection by using a URLConnection in Java which returns an HTTPSURLConnectionImpl.
The client certificate I want to present to the web server in the request is different than the one set as my JVM system property. Is there a way I can set a client cert. as a request property in the HTTPSURLConnectionImpl ?
Setting a SSL "client certificate" is not adequate directly through HTTPSURLConnectionImpl's request properties, because a digital signature is also required to prove you own the certificate. SSL already does all that automatically, so to makes sense to use that layer.
You have two ways to solve your issue going forward.
Through configuration
You can add you client key and certificate to your JVM KeyStore, it should be picked up at Runtime when the server asks for your client-side SSL authentication. (SSL/TLS is designed for that : the server will ask for a client certificate that is signed by it's trusted authority, which allows the SSL Engine to choose the right certificate, even when your KeyStore holds many).
Through Code
You can roll you own SSLContext using custom made KeyStore/TrustStores.
This is a bit complex (I won't elaborate on how to build Keystore instances in Java), but the gist of it is here :
public static void main(String[] args) throws Exception {
KeyStore clientKeyStore = ... // Whatever
KeyStore clientTrustStore = ... // Whatever you need to load
// We build the KeyManager (SSL client credentials we can send)
KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyFactory.init(clientKeyStore, "password".toCharArray());
KeyManager[] km = keyFactory.getKeyManagers();
// We build the TrustManager (Server certificates we trust)
TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustFactory.init(clientTrustStore);
TrustManager[] tm = trustFactory.getTrustManagers();
// We build a SSLContext with both our trust/key managers
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(km, tm, null);
SSLSocketFactory sslSf = sslContext.getSocketFactory();
// We prepare a URLConnection
URL url = new URL("https://www.google.com");
URLConnection urlConnection = url.openConnection();
// Before actually opening the sockets, we affect the SSLSocketFactory
HttpsURLConnection httpsUrlConnection = (HttpsURLConnection) urlConnection;
httpsUrlConnection.setSSLSocketFactory(sslSf);
// Ready to go !
}

Categories