Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Webview certificate pinning

I am implementing a WebView in Android which loads an https website. On this website I want to do certificate pinning which means I want to check certain aspects of the certificate the server serves. However I see there is no method in the WebViewClient that gives me the possibility to intercept the request and also retrieve the certificate.

On the Internet there are a lot of ppl that say that it just can't be done, certificate pinning on Androids WebView. So I hope anyone here knows some more.

like image 817
Sjaak Rusma Avatar asked Nov 26 '15 07:11

Sjaak Rusma


2 Answers

Certificate Pinning in Android

Certificate Pinning on Android is described well in this article.

The main milestone in Android for certificate pinning is Android 7.0 Nougat (SDK 24) because of Network Security Configuration allowing apps to define their own set of rules.

I suggest the use of TrustKit Android library to manage Certificate pinning on Android: it support most libraries (OkHttp etc.) and is compatible with the Android SDK 24 setup.

Webview Certificate Pinning

However keep in mind that before Android 7.0 you can't really manage pinning on a Webview, regardless of the library you use, anything that follow is a workaround.

If you have minSDK 7.0 just use the library above, no need to do anything else.

shouldInterceptRequest workaround

Pre-Nougat the best option you have for Webviews is to implement your own WebviewClient and perform the call manually, doing certificate pinning in the shouldInterceptRequest() method.

You can find an example of this in this project.

The shouldInterceptRequest() pre Lollipop (5.0 - SDK 21) do not give you anything more then the URL, no headers, no body, nothing else. This means you can't really do much more then just loading the URL and hope the webapp didn't need anything else (headers etc..).

Since Lollipop you have another method that gives you a WebResourceRequest object with more information and so you can reproduce a request properly.

Only GET Requests

Regardless of the Android version shouldInterceptRequest() will never receive anything but GET requests; POST/PUT/DELETE are not interceptable!!

Performance Issue pre-Lollipop

Furthermore if you use the method above with shouldInterceptRequest() you may introduce major performance issues: before October 2015 the Android Webview had a bug and used to execute the method always on the same Thread causing a blocking queue: the same thread was used for javascript, resulting in apparent "freeze" of the webview. The Webview is upgraded with the system since Lollipop but before that the issue is still present.

Workaround of the Workaround for intercepting PUT/POST/DELETE

You can inject a custom Javascript interface for intercepting Ajax calls and perform them manually with OkHttp or whatever library you use for HTTP request and certificate pinning.

If you really want to go this route have a look at this library, it doesn't support forms, only Ajax call but it can be done in a similar way, like shown in this other library.

BUT!!! I wouldn't recommend this AT ALL!!!! The author himself, and I quote, say:

[..]I wasn't happy with my adaption of this so far.[...]

Cause it's an UGLY HACK!

Workaround for the workaround for performance issue

You may consider pre-fetching resources so that you don't have to fetch them when they are requested in the shouldInterceptRequest() method. This requires you already know all the resources you'll need and I don't have to tell you that it may end up using a lot of bandwidth and disk space that might never be used by the user.

Alternatives to shouldInterceptRequest()

If you have control over the server providing the Webview data you may try to rely on HPKP, which basically means that your server is supposed to return with each request an header listing SHA256 digest of your certificates: the browser (in this case the webview) is then supposed to pick it up and make sure the next requests will only go through if you the certificate is the same.

Of course this means the first time you have no protection, so you may need a combination of shouldInterceptRequest() and HPKP.

It is also really easy to shut yourself in the foot if you do some mistake while setting HPKP up (like explained in the link I gave).

And, guess what, Google, who first introduced HPKP deprecated it in 2017 because of the setup difficulties and risk involved in mistakes. This also mean it may not be supported in recent Webviews or in the future.

Final Remarks

If you really want to securely pin certificate avoid webviews or target minSDK 24 (Android 7.0).

Also consider this...

Drawbacks

These Drawbacks are generic on the certificate pinning and have nothing to do with the Android version.

Proxies: forget your app working through those if you do certificate pinning. Usually, the only way to make an HTTPS connection work through a proxy is to install your proxy certificate on your device and make the proxy the "Man in the middle". If you pin certificate that won't work, no matter what.

Redirects: if you pin certificate and you have HTTP redirects (301 / 302) that change domain you also need to pin the other domain.

Dynamic content: if your app manage dynamic content (URLs input provided by the user or not under your control) and you don't allow anything outside your list of pinned domain/certificates those URLs won't be accepted and you won't be able to pin them anyway because you can't predict which domain / certificate they'll have (this is also true for redirects).

Do you really need certificate pinning?

There's no pinning for the web, computer browser around the world use no certificate pinning. HPKP (discussed above) has been deprecated by Google itself and no one complained about it so far. Every browser use a list of trusted certificate authorities.

The same things is done by a mobile Device.

The main difference between a browser and a mobile app is that the browser will clearly show certificate information and if the web page is secure or not; while an application can do whatever it wants without showing anything to the user. You could compare a PC application to an App instead to get a more fair comparison.

You need to understand what really pinning the certificate does for the security of your app: it render Man-In-The-Middle attacks impossible to pull off. Even compromised device (user/attacker installed malicious certificate authority) will not allow the communication to establish and thus someone to sniff what your app is sending and receiving.

In order for an attacker to successfully perform a man-in-the-middle attack on a pinned certificate domain he would have to obtain the actual certificate or being able to produce a valid one (from a trusted certificate authority).

The only time in history a trusted certificate authority has been compromised was DigiNotar in 2011. Aside from cases like this the only way to break that chain of trust is with a compromised device (be it a PC or a mobile phone).

Do you need to protect the user against himself? (aka = installing a fake / malicious certificate authority on his device) and then use your application and let his own data and credential be stolen by a MITM.

Do you need to protect yourself against an user stealing data you send through your secure connection? (api keys, tokens, etc...) -- keep in mind there are better tactics to handle this and usually api keys and tokens can be revoked.

Chances are you just need to secure a few API used by your app. OWASP suggest, of course to always pin, but I'm more of the idea that you should measure the protection over the threat that this gives you. This is entirely my opinion.

OWASP also say another important thing DO NOT DO IT MANUALLY, you may create greater security threats then the ones you are trying to solve! (which goes against all the workaround discussed above). And I 100% agree with this one. Use TrustKit.

like image 121
Daniele Segato Avatar answered Sep 25 '22 07:09

Daniele Segato


On API level 24 (Android 7) and above, you can use network security config to pin certificates on application process level, including also WebViews.

Example from the link above. Add to AndroidManifest.xml:

<manifest ... >
    <application android:networkSecurityConfig="@xml/network_security_config"
                    ... >
        ...
    </application>
</manifest>

Create a resource file xml/network_security_config.xml:

<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>
like image 44
laalto Avatar answered Sep 25 '22 07:09

laalto