From 60ed9f76affb335088adf03645e096ca42df6e20 Mon Sep 17 00:00:00 2001 From: Hashith Karunarathne Date: Tue, 21 Dec 2021 12:41:31 +0530 Subject: [PATCH] Android - Add hostname verifier to OkHttpClient --- README.md | 20 ++++++++----- .../com/toyberman/RNSslPinningModule.java | 30 ++++++++++++++++++- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 54c3d7d..aefa893 100644 --- a/README.md +++ b/README.md @@ -76,13 +76,15 @@ https://stackoverflow.com/questions/7885785/using-openssl-to-get-the-certificate - For public key pinning the public key should be extracted by the following options : (replace google with your domain) - ```openssl s_client -servername google.com -connect google.com:443 | openssl x509 -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64``` + - As per the android [SSLSocket](https://developer.android.com/reference/javax/net/ssl/SSLSocket) documentation by default it does not perform [hostname verification](https://developer.android.com/training/articles/security-ssl#WarningsSslSocket). So to perform the hostname verification it needed to pass `hostsList` (string array) as below and this is a mandatory param. - Turn on pinning with a broken configuration and read the expected configuration when the connection fails. ```javascript fetch("https://publicobject.com", { method: "GET" , pkPinning: true, sslPinning: { - certs: ["sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="] + certs: ["sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="], + hostsList: ['publicobject.com', '*.publicobject.com'], } }) ``` @@ -111,7 +113,8 @@ fetch(url, { body: body, // your certificates array (needed only in android) ios will pick it automatically sslPinning: { - certs: ["cert1","cert2"] // your certificates name (without extension), for example cert1.cer, cert2.cer + certs: ["cert1","cert2"], // your certificates name (without extension), for example cert1.cer, cert2.cer + hostsList: ['publicobject.com', '*.publicobject.com'] // host name list you are going to SSL pining verification }, headers: { Accept: "application/json; charset=utf-8", "Access-Control-Allow-Origin": "*", "e_platform": "mobile", @@ -134,10 +137,12 @@ fetch("https://publicobject.com", { // your certificates array (needed only in android) ios will pick it automatically pkPinning: true, sslPinning: { - certs: ["sha256//r8udi/Mxd6pLO7y7hZyUMWq8YnFnIWXCqeHsTDRqy8=", - "sha256/YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=", - "sha256/Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys=" - ] + certs: [ + "sha256//r8udi/Mxd6pLO7y7hZyUMWq8YnFnIWXCqeHsTDRqy8=", + "sha256/YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=", + "sha256/Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys=" + ], + hostsList: ['publicobject.com', '*.publicobject.com'] // host name list you are going to SSL pining verification }, headers: { Accept: "application/json; charset=utf-8", "Access-Control-Allow-Origin": "*", "e_platform": "mobile", @@ -214,7 +219,8 @@ fetch(url, { formData: request, }, sslPinning: { - certs: ["cert1","cert2"] + certs: ["cert1","cert2"], + hostsList: ['publicobject.com', '*.publicobject.com'] // host name list you are going to SSL pining verification }, headers: { accept: 'application/json, text/plain, /', diff --git a/android/src/main/java/com/toyberman/RNSslPinningModule.java b/android/src/main/java/com/toyberman/RNSslPinningModule.java index 6fb4c84..3b78518 100644 --- a/android/src/main/java/com/toyberman/RNSslPinningModule.java +++ b/android/src/main/java/com/toyberman/RNSslPinningModule.java @@ -30,6 +30,10 @@ import java.util.Map; import java.util.Set; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSession; + import okhttp3.Call; import okhttp3.Cookie; import okhttp3.CookieJar; @@ -167,7 +171,7 @@ public void removeCookieByName(String cookieName, final Promise promise) { @ReactMethod public void fetch(String hostname, final ReadableMap options, final Callback callback) { - + final WritableMap response = Arguments.createMap(); String domainName; try { @@ -185,7 +189,31 @@ else if (options.hasKey(OPT_SSL_PINNING_KEY)) { if (certs != null && certs.size() == 0) { throw new RuntimeException("certs array is empty"); } + client = OkHttpUtils.buildOkHttpClient(cookieJar, domainName, certs, options); + HostnameVerifier hostnameVerifier = (hostname1, session) -> { + HostnameVerifier hv = + HttpsURLConnection.getDefaultHostnameVerifier(); + // Hardwired intentionally (https://developer.android.com/training/articles/security-ssl) + if (options.getMap(OPT_SSL_PINNING_KEY).hasKey("hostsList")) { + ReadableArray hosts = options.getMap(OPT_SSL_PINNING_KEY).getArray("hostsList"); + if (hosts != null && hosts.size() == 0) { + throw new RuntimeException("hosts array is empty"); + } + boolean isVerifyAtLeastOne = false; + for (int i = 0; i < hosts.size(); i++) { + if(hv.verify(hosts.getString(i), session)) { + isVerifyAtLeastOne = true; + break; + } + } + return isVerifyAtLeastOne; + } + throw new RuntimeException("hosts list was not found"); + }; + OkHttpClient.Builder builder = client.newBuilder(); + builder.hostnameVerifier(hostnameVerifier); + client = builder.build(); } else { callback.invoke(new Throwable("key certs was not found"), null); }