okhttp doesn't validate pins correctly - android

Using com.squareup.retrofit2:retrofit:2.0.1 with com.squareup.okhttp3:okhttp:3.2.0 on an AVD with Android 6.0.
I'm trying to implement public key pinning using a self signed certificate that is signed by a Root CA. That Root CA is in the system CA trust store.
Using the example provided by okhttp wiki with some small changes:
OkHttpClient client = new OkHttpClient.Builder().certificatePinner(
new CertificatePinner.Builder()
.add(pinningUrl, "sha256/invalidPIN")
.build()).build();
Request request = new Request.Builder()
.url(pinningUrl)
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
for (Certificate certificate : response.handshake().peerCertificates()) {
System.out.println(CertificatePinner.pin(certificate));
}
What happens is that response.isSuccessful returns true, no exception is thrown, although the pin isn't correct. The only thing that is done correctly is the validation of the certificate with the Root CAs in systems CA trust store.
What I've found to be working, is adding this line before the for loop. But that isn't the right approach because the request is already sent, the pinning should work before TLS negotiation is finished. Also this line isn't mentioned in any sample code I've found.
client.certificatePinner().check(pinningUrl, response.handshake().peerCertificates());
throws
javax.net.ssl.SSLPeerUnverifiedException: Certificate pinning failure!
Is there a bug in the sample code provided by okhttp or am I doing something wrong?

You’re configuring it incorrectly. Replace pinningUrl with the hostname of the pinning URL. For example, you want example.com instead of http://example.com/. If you’d like to send a PR to make hostname validation more strict, it would be quite welcome.

Related

how to force OkHttp on Android 11 to send ssl client cert auth with TLS 1.3

We need to perform certificate based client authentication to a server accepting only TLS 1.3 connections.
The server is using Apache 2 and HTTP 1.1 and is configured to allow client cert auth but not to enforce it because some resources require client auth while others don't.
We're using OkHttp 4.9.1 on Android 11 to perform the call and we're following the standard docs on how to perform client auth: https://github.com/square/okhttp/tree/master/okhttp-tls
The server however replies with: 403 (auth renegotiation not allowed)
This is in line with TLS 1.3 specification which does not allow auth renegotation unless agreed upon during the initial handshake.
So far we've debugged the connection and inside OkHttp the class RealConnection and it actually performs the Handshake without negotiating a client cert.
Our research so far indicates that this might be due to the server using OPTIONAL ssl client auth but this is not somethign we can change so....
Is there any other option we can pass during OkHttp initialization to force it to perform the initial handshake using the client cert?
in case OkHttp is not a viable option is there any other HTTP client implementation which would allow us to force such authetnication during the initial handshake?
Create a HandshakeCertificates object using its builder. You'll need your private key, client cert and any intermediates on the client side.
The client will also need a root certificate for your server to trust it. The builder has a function to use the built in certificate authorities for that if you like.
private OkHttpClient buildClient(
HeldCertificate heldCertificate, X509Certificate... intermediates) {
HandshakeCertificates.Builder builder = new HandshakeCertificates.Builder()
.addTrustedCertificate(serverRootCa.certificate());
if (heldCertificate != null) {
builder.heldCertificate(heldCertificate, intermediates);
}
HandshakeCertificates handshakeCertificates = builder.build();
return clientTestRule.newClientBuilder()
.sslSocketFactory(
handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager())
.build();
}
https://github.com/square/okhttp/blob/1ce86f35a9d957bae711fb81cec60abe9f43dda0/okhttp/src/test/java/okhttp3/internal/tls/ClientAuthTest.java#L126
Optional client certificate authentication is no different from required client certificate authentication from the perspective of the client. All what the client sees is a CertificateRequest and as a response it will send some certificates (leaf and chain), which might also be an empty list of certificates. It is up to the server to decide if this is acceptable, i.e. optional simply means that the server accepts that the client sends an empty list of certificates.
The problem thus is not optional or required client certificates, but that the server has not requested a certificate at all in the initial TLS handshake and likely only requests a certificate when accessing a specific path (the requested path can only be seen after the initial handshake). With Apache this is typically the case if client certificate is not requested in the server global configuration but instead in a path specific .htaccess file. Fix would be to move the requirement at the level of the domain and not the path only.

Log the SSL Certificate programmatically

I have 2 devices in the wild that are not able to connect to my TLS v1.2 endpoint. All others seem able to, including browsers, PostMan and iOS devices.
The devices are running Android 5 & 7 (so there should not be a problem with the TLS v1.2 support).
Note: This is not a self-signed certificate. It is signed by Amazon.
Immediate thoughts were:
Android fragmentation - perhaps the devices (one is a Kindle Fire 7)
are not including the correct certificates into the OS. It wouldn't
be the first time that a device manufacturer made an odd decision
that breaks functionality.
API is being accessed via a proxy, and there actually is a Man-In-The-Middle, correctly being detected.
Fixing (1) means bundling our certificate, and leads to the usual problems when our cert expires.
I would prefer to get the user to install a debug build that confirms whether (1) or (2) is the problem. Such build would inspect the SSL Certificate provided by the server/proxy, and log that back to me.
Web Frameworks:
Retrofit v2.3.0
OkHttp v3.9.1
Question:
How do I inspect the information of the SSL Certificate that the device is seeing when hitting my endpoint?
Update per comment from #SangeetSuresh:
It turns out there are 2 different exceptions being thrown.
The Kindle Fire 7" Tablet (KFAUWI, OS 5.1.1) is throwing the one I have started to investigate, which this question is meant to have focused on. i.e. basic SSL failure.
java.security.cert.CertPathValidatorException:
Trust anchor for certification path not found.
at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:331)
at com.android.org.conscrypt.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:232)
at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:114)
The LG device (LG-SP200, OS 7.1.2) is having the connection closed by the peer, which should be addressed under a new question if not solved here:
javax.net.ssl.SSLHandshakeException:
Connection closed by peer
at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(NativeCrypto.java)
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:360)
at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.java:299)
Robby Cornelissen provided the basic answer in a comment referencing the OkHttp Response:
the information should be available from
response.handshake().peerCertificates().
A simple Interceptor was implemented to inspect the certificates, given a valid handshake:
private static class SslCertificateLogger implements Interceptor {
public static final String TAG = "SSL";
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response;
try {
response = chain.proceed(request);
} catch (Exception e) {
Log.d(TAG, "<-- HTTP FAILED: " + e);
throw e;
}
Handshake handshake = response.handshake();
if (handshake == null) {
Log.d(TAG, "no handshake");
return response;
}
Log.d(TAG, "handshake success");
List<Certificate> certificates = handshake.peerCertificates();
if (certificates == null) {
Log.d(TAG, "no peer certificates");
return response;
}
String s;
for (Certificate certificate : certificates) {
s = certificate.toString();
Log.d(TAG, s);
}
return response;
}
}
This gets added to the OkHttpClient as per normal:
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.addInterceptor(new SslCertificateLogger())
.build();
A similar solution was proposed by Sangeet Suresh that references the Retrofit Response object:
response?.raw()?.handshake() I think this will help you
Here the important information being the fact that Retrofit gives access to the raw OkHttp response in this manner.
This would not be used in an Interceptor but rather at a higher level, in the actual Retrofit handling code, after getting a Retrofit Response<> from the API.
Converting his Kotlin solution back to Java could yield something like this:
okhttp3.Response raw = httpResponse.raw();
if (raw != null) {
Handshake handshake = raw.handshake();
if (handshake != null) {
List<Certificate> certificates = handshake.peerCertificates();
if (certificates != null) {
for (Certificate certificate : certificates) {
Log.d(TAG, certificate.toString());
}
}
}
}
Both solutions work fine, provided the handshake() is not null i.e. when the handshake succeeds.
Given that this is an investigation into failed handshakes, a further step was required to "trust all certificates" (NB debug builds only!).
This has been documented many times - here is one such version:
Unsafe SSL Client (do not do this in production)

com.android.volley.NoConnectionError: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException

i need to get echo value i.e (yes or no) from php file to myresulttextview but unable to retrieve it says error :"com.android.volley.NoConnectionError: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found. "
val barcode = data.getParcelableExtra<Barcode>(BarcodeCaptureActivity.BarcodeObject)
val p = barcode.cornerPoints
var m = barcode.displayValue.toString().trim()
val jsonobj=JSONObject()
jsonobj.put("email",m)
val url="https://192.168.2.11/verf1.php"
val que=Volley.newRequestQueue(this#MainActivity)
val req=JsonObjectRequest(Request.Method.POST,url,jsonobj,Response.Listener {
response ->mResultTextView.setText(response.toString())
},Response.ErrorListener {
response ->mResultTextView.setText(response.toString())
})
que.add(req)
my php file
<?php
require "conn.php";
$user_name=false;
if(isset($_POST["email"])){
$user_name = $_POST["email"];
}
$mysql_qry="select * from exitpass where email like '%".$user_name."%'";
$result=mysqli_query($conn,$mysql_qry);
if(mysqli_num_rows($result)>0)
{
echo"yes";
}
else{
echo"no";}
?>
The error is related to the TLS/SSL certificate associated with your 192.168.2.11 web server. There are a few different ways to solve this problem depending on what your setup is like.
If you have no cert installed on your web server then replace https with http to send over an unencrypted channel.
val url="http://192.168.2.11/verf1.php"
If the web server does have a cert installed then you could have a few issues. Common issues related to the stack trace are invalid certs, using the wrong (or no) domain name, wrong Java code for the encryption type and Java version, issue somewhere in the trust chain, expired cert, etc.
In summary, I believe you simply need to send over http, but if that doesn't work, you should troubleshoot it in this order.
Change the IP to the domain name associated with your cert
Verify you can access your resource in the browser and that the certificate is not expired
Figure out what type of encryption you are using (TLS 1.1, TLS 1.2, etc) and what Java you are using (1.6 or 1.7). There is a key difference between Java 6 and Java 7 as it relates to accessing TLS encrypted resources and I explain it in my highest rated post (as a reference).
If all else fails you will likely need to manually install the certificate in Androids JVM trust store. I believe you can do this from most GUIs and definitely from the terminal if you have access. You shouldn't have to do this, but sometimes you are forced to if the certificate chain breaks. It might not even be your cert causing the issue, it can be any cert in your trust chain.
Please let me know if the initial suggestion works. If it doesn't work please provide the public cert here ( or just the domain name and I will pull it down) and your Java version and I can better assist.

Android SSL Certificate pinning

I know there are many questions regarding pinning certificates in Android but I can't find what I am looking for...
I subclass SSLSocketFactory and override the checkServerTrusted() method. In this method, I do the following:
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate ca = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(PUB_KEY.getBytes("UTF-8")));
for (X509Certificate cert : chain) {
// Verifing by public key
cert.verify(ca.getPublicKey());
}
One of the items in the chain verifies and the other doesn't (which throws an Exception). I guess I can't get a grasp of how certificate chains work.
Should the same public certificate verify with all certificates in the chain?
The easiest way I found to implement certificate pinning on Android is to use the OkHttp library.
Here is an excerpt from the documentation:
By default, OkHttp trusts the certificate authorities of the host platform. This strategy maximizes connectivity, but it is subject to certificate authority attacks such as the 2011 DigiNotar attack. It also assumes your HTTPS servers’ certificates are signed by a certificate authority.
Use CertificatePinner to constrain which certificate authorities are trusted. Certificate pinning increases security, but limits your server team’s abilities to update their TLS certificates. Do not use certificate pinning without the blessing of your server’s TLS administrator!
public CertificatePinning() {
client = new OkHttpClient();
client.setCertificatePinner(
new CertificatePinner.Builder()
.add("publicobject.com", "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=")
.add("publicobject.com", "sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=")
.add("publicobject.com", "sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=")
.add("publicobject.com", "sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=")
.build());
}
public void run() throws Exception {
Request request = new Request.Builder()
.url("https://publicobject.com/robots.txt")
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
for (Certificate certificate : response.handshake().peerCertificates()) {
System.out.println(CertificatePinner.pin(certificate));
}
}
And if you need to support a self-signed certificate, the answer to Does OkHttp support accepting self-signed SSL certs? will guide you.
Should the same public certificate verify with all certificates in the
chain?
Answer:- No.
Most public CAs don't sign server certificates directly. Instead, they use their main CA certificate, referred to as the root CA, to sign intermediate CAs. They do this so the root CA can be stored offline to reduce risk of compromise. However, operating systems like Android typically trust only root CAs directly, which leaves a short gap of trust between the server certificate—signed by the intermediate CA—and the certificate verifier, which knows the root CA.
To solve this, the server doesn't send the client only it's
certificate during the SSL handshake, but a chain of certificates from
the server CA through any intermediates necessary to reach a trusted
root CA.
Check this link for more information. Hope this will help users.
Certificate and Public Key Pinning (a.k.a Certificate Pinning) in a
nutshell -
Normally, an app trusts all pre-installed CAs. If any of these CAs were to issue a fraudulent certificate, the app would be at risk from a man-in-the-middle attack( a.k.a eavesdropping ). Some apps choose to limit the set of certificates they accept by either limiting the set of CAs they trust or by certificate pinning. Certificate pinning is done by providing a set of certificates by hash of the public key. Certificate Pinning is a method that depends on server certificate verification on the client-side.
Below are the 3 ways to implement Certificate Pinning on Android -
The old-school way - TrustManager - https://medium.com/#ric…/ssl-pinning-on-android-8baa822e3bd5
OkHttp CertificatePinner - https://square.github.io/…/okh…/okhttp3/-certificate-pinner/
Something fresh - Network Security Configuration - https://developer.android.com/trai…/articles/security-config
In particular to your question, you could configure the certificates by hash of the public key in NSC using <pin-set> tag.
Note that, when using certificate pinning, you should always include a backup key so that if you are forced to switch to new keys or change CAs (when pinning to a CA certificate or an intermediate of that CA), your app's connectivity is unaffected. Otherwise, you must push out an update to the app to restore connectivity.
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="true">example.com</domain>
<pin-set expiration="2018-01-01">
<pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
<!-- backup pin -->
<pin digest="SHA-256">fwza0LRMXouZHRC8Ei+4PyuldPDcf3UKgO/04cDM1oE=</pin>
</pin-set>
</domain-config>
</network-security-config>
Your Questions and Doubts
Should the same public certificate verify with all certificates in the chain?
No, because each certificate in the chain (root, intermediate and leaf certificate)) was signed with a different private/public key pair.
One of the items in the chain verifies and the other doesn't (which throws an Exception). I guess I can't get a grasp of how certificate chains work.
That's because your certificate is the leaf one, thus you can only verify your public key against it, not against the root and intermediate certificate(s).
A Code Approach
I subclass SSLSocketFactory and override the checkServerTrusted() method.
If you really want to code it yourself I would suggest you to use instead the built-in OkHttp Ceritficate Pinner, that you can build like this:
import okhttp3.CertificatePinner;
public class OkHttpPinnerService {
// true if the Approov SDK initialized okay
private boolean initialized;
// cached OkHttpClient to use or null if not set
private OkHttpClient okHttpClient;
public synchronized OkHttpClient getOkHttpClient() {
if (okHttpClient == null) {
// build a new OkHttpClient on demand
if (initialized) {
// build the pinning configuration
CertificatePinner.Builder pinBuilder = new CertificatePinner.Builder();
Map<String, List<String>> pins = YourConfig.getPins("public-key-sha256");
for (Map.Entry<String, List<String>> entry : pins.entrySet()) {
for (String pin : entry.getValue())
pinBuilder = pinBuilder.add(entry.getKey(), "sha256/" + pin);
}
// build the OkHttpClient with the correct pins preset and ApproovTokenInterceptor
Log.i(TAG, "Building new Approov OkHttpClient");
okHttpClient = okHttpBuilder.certificatePinner(pinBuilder.build()).build();
} else {
// if the Approov SDK could not be initialized then we can't pin or add Approov tokens
Log.e(TAG, "Cannot build Approov OkHttpClient due to initialization failure");
okHttpClient = okHttpBuilder.build();
}
}
return okHttpClient;
}
}
The code was not tested for syntax errors or logical correctness. I just copied it from this repo and slightly adapted it.
A Codeless Approach
Since Android API 24 it is possible to implement certificate pinning to the public key hash via the built-in security config file, that doesn't require any code to be written, just a properly configured network_security_config.xml file added to your project.
To avoid mistakes while building the network_security_config.xml file I recommend you to use the Mobile Certificate Pinning Generator to extract the live pin being used by the domain you want to pin against and to build for you the correct configuration. For example:
Now just copy paste the generated configuration the network_security_config.xml file in your project and add this same file to the AndroifManifest.xml. Just follow the instructions on the page.

Untrusted certificate error and httpclient on android

I'm using the Apache HttpClient library to setup a https connection. Unfortunately Android gives me a "Not trusted server certificate" error. If I browse to the site with the phone's browser it validates the certificate correctly, which leads me to believe that I need to make the HttpClient 'aware' of the root certificates on the phone. This is my HttpClient setup code:
HttpParams params = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout( params, 20000 );
HttpConnectionParams.setSoTimeout( params, 20000 );
HttpProtocolParams.setVersion( params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset( params, HTTP.DEFAULT_CONTENT_CHARSET);
HttpProtocolParams.setUseExpectContinue( params, false);
SchemeRegistry schReg = new SchemeRegistry();
schReg.register( new Scheme( "http", PlainSocketFactory.getSocketFactory(), 80 ) );
schReg.register( new Scheme( "https", SSLSocketFactory.getSocketFactory(), 443 ) );
ClientConnectionManager conMgr = new ThreadSafeClientConnManager( params, schReg );
DefaultHttpClient defaultHttpClient = new DefaultHttpClient( conMgr, params );
return ( defaultHttpClient );
As you can see I do nothing special with the SSLSocketFactory. How can I make the HttpClient library validate my site without adding a custom certificate in the keystore?. Should I create a custom SSLSocketFactory and load the cacerts.bks from the Android phone? In that case I might get problems with different passwords for the keystore on different phones?
Please let me know if you need further information. This SSL stuff is pretty difficult for me.
I believe your certificate doesn't contain all intermediate certificates necessary to validate path to a system trusted root certificate. It can be reported as incomplete certificate chain by some SSL config validation tools.
A certificate can contain a special Authority Information Access extension (RFC-3280) with URL to issuer's certificate. Most browsers can use the AIA extension to download missing intermediate certificate to complete the certificate chain. But some clients (mobile browsers, OpenSSL) don't support this extension, so they report such certificate as untrusted.
You can solve the incomplete certificate chain issue manually by concatenating all certificates from the certificate to the trusted root certificate (exclusive, in this order), to prevent such issues. Note, the trusted root certificate should not be there, as it is already included in the system’s root certificate store.
You should be able to fetch intermediate certificates from the issuer and concat them together by yourself. I have written a script to automate the procedure, it loops over the AIA extension to produce output of correctly chained certificates. https://github.com/zakjan/cert-chain-resolver
Following up on zakjan's answer, I had a problem when I tried to use jquery to do an AJAX request on my newly secure server, in an android webview. It worked in the browser, but not in my app.
I used this site: https://certificatechain.io/
I pasted in the text of my signed .crt file I got back from Comodo (positiveSSL), and it gave me back a concatination of everything I needed. I saved it as my domain + "chain.crt" (see below)
Then, in my apache configs, I entered something like this for that particular virtual host:
SSLEngine On
SSLCertificateFile /etc/ssl/localcerts/example_com.crt
SSLCertificateKeyFile /etc/ssl/localcerts/example.com.key
SSLCACertificateFile /etc/ssl/localcerts/example.com.chain.crt
After that, my Android app's webview did not have a problem using ajax to POST to my server. I tried it on 2 real-world devices, one running 2.3.4, one running 4.something. And on the emulator running 2.3. All worked.
I hope this helps.
i have same problem before..
you can use this answer, it works for me nicely..
Trusting all certificates using HttpClient over HTTPS

Categories

Resources