From a7986e3bb8e0db0148f43f0c0effdb079e473174 Mon Sep 17 00:00:00 2001 From: Steve Shenouda Date: Thu, 31 Oct 2019 18:54:55 -0700 Subject: [PATCH 1/6] Add multiple domain pinning support for OkHttp3 Add support for SSL pinning validation on multiple different domains with a single OkHttp3 client --- build.gradle | 3 ++ trustkit/build.gradle | 1 + .../android/trustkit/TrustKit.java | 23 +++++++++ .../trustkit/pinning/PinningInterceptor.java | 29 +++++++++++ .../trustkit/pinning/RootTrustManager.java | 51 +++++++++++++++++++ 5 files changed, 107 insertions(+) create mode 100644 trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/PinningInterceptor.java create mode 100644 trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/RootTrustManager.java diff --git a/build.gradle b/build.gradle index 02b2fe3..02f8905 100644 --- a/build.gradle +++ b/build.gradle @@ -64,6 +64,9 @@ ext{ ], google: [ material: '1.0.0' + ], + squareup: [ + okhttp3: '3.11.0' ] ] diff --git a/trustkit/build.gradle b/trustkit/build.gradle index f69b473..5a01a44 100644 --- a/trustkit/build.gradle +++ b/trustkit/build.gradle @@ -29,6 +29,7 @@ dependencies { implementation "androidx.annotation:annotation:$rootProject.libVersions.androidx.annotation" implementation "androidx.legacy:legacy-support-v4:$rootProject.libVersions.androidx.legacySupport" implementation "androidx.preference:preference:$rootProject.libVersions.androidx.preference" + compileOnly "com.squareup.okhttp3:okhttp:$rootProject.libVersions.squareup.okhttp3" androidTestImplementation "junit:junit:$rootProject.libVersions.junit" androidTestImplementation "androidx.test:runner:$rootProject.libVersions.androidx.test" diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/TrustKit.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/TrustKit.java index ccc5084..d0a79ae 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/TrustKit.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/TrustKit.java @@ -371,6 +371,29 @@ public SSLSocketFactory getSSLSocketFactory(@NonNull String serverHostname) { } } + /** + * Retrieve an {@code SSLSSocketFactory} that implements SSL pinning validation based on the + * current TrustKit configuration. It can be used with an OkHttpClient to add SSL + * pinning validation to the connections. + * + *

+ * The {@code SSLSocketFactory} is configured for the current TrustKit configuration and + * will enforce the configuration's pinning policy. + *

+ */ + @NonNull + public SSLSocketFactory getSSLSocketFactory(@NonNull RootTrustManager trustManager) { + try { + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new X509TrustManager[]{trustManager}, null); + + return sslContext.getSocketFactory(); + } catch (NoSuchAlgorithmException | KeyManagementException e) { + e.printStackTrace(); + throw new IllegalStateException("SSLSocketFactory creation failed"); + } + } + /** Retrieve an {@code X509TrustManager} that implements SSL pinning validation based on the * current TrustKit configuration for the supplied hostname. It can be used with some network diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/PinningInterceptor.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/PinningInterceptor.java new file mode 100644 index 0000000..9634be3 --- /dev/null +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/PinningInterceptor.java @@ -0,0 +1,29 @@ +package com.datatheorem.android.trustkit.pinning; + +import androidx.annotation.NonNull; + +import java.io.IOException; + +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +/** + * {@link Interceptor} used to parse the hostname of the {@link Request} URL and then save the + * hostname in the {@link RootTrustManager} which will later be used for Certificate Pinning. + */ +public class PinningInterceptor implements Interceptor { + private final RootTrustManager mTrustManager; + + public PinningInterceptor(@NonNull RootTrustManager trustManager) { + mTrustManager = trustManager; + } + + @Override public Response intercept(Interceptor.Chain chain) throws IOException { + Request request = chain.request(); + String serverHostname = request.url().host(); + + mTrustManager.setServerHostname(serverHostname); + return chain.proceed(request); + } +} diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/RootTrustManager.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/RootTrustManager.java new file mode 100644 index 0000000..0d2e85c --- /dev/null +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/RootTrustManager.java @@ -0,0 +1,51 @@ +package com.datatheorem.android.trustkit.pinning; + +import android.net.http.X509TrustManagerExtensions; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +import com.datatheorem.android.trustkit.TrustKit; +import com.datatheorem.android.trustkit.config.DomainPinningPolicy; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.X509TrustManager; + +/** + * {@link X509TrustManager} used for Certificate Pinning. + * + *

This trust manager delegates to the appropriate {@link PinningTrustManager} decided by the + * hostname set by the {@link PinningInterceptor}.

+ */ +@RequiresApi(api = 17) +public class RootTrustManager implements X509TrustManager { + private final ThreadLocal mServerHostname = new ThreadLocal<>(); + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + TrustKit.getInstance().getTrustManager(mServerHostname.get()).checkClientTrusted(chain, authType); + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + String host = mServerHostname.get(); + DomainPinningPolicy serverConfig = + TrustKit.getInstance().getConfiguration().getPolicyForHostname(host); + if (serverConfig == null) { + new X509TrustManagerExtensions(TrustKit.getInstance().getTrustManager(host)).checkServerTrusted(chain, authType, host); + } else { + TrustKit.getInstance().getTrustManager(host).checkServerTrusted(chain, authType); + } + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + void setServerHostname(@NonNull String serverHostname) { + mServerHostname.set(serverHostname); + } +} From 26c5d324753c5609f6313765e087e44a21f0cd58 Mon Sep 17 00:00:00 2001 From: Steve Shenouda Date: Tue, 5 Nov 2019 19:32:50 -0800 Subject: [PATCH 2/6] Add multiple domain pinning support for OkHttp2 Add support for SSL pinning validation on multiple different domains with a single OkHttp2 client --- build.gradle | 3 +- trustkit/build.gradle | 1 + .../trustkit/pinning/PinningInterceptor2.java | 29 +++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/PinningInterceptor2.java diff --git a/build.gradle b/build.gradle index 02f8905..0a10d3a 100644 --- a/build.gradle +++ b/build.gradle @@ -66,7 +66,8 @@ ext{ material: '1.0.0' ], squareup: [ - okhttp3: '3.11.0' + okhttp3: '3.11.0', + okhttp2: '2.4.0' ] ] diff --git a/trustkit/build.gradle b/trustkit/build.gradle index 5a01a44..cdef99a 100644 --- a/trustkit/build.gradle +++ b/trustkit/build.gradle @@ -30,6 +30,7 @@ dependencies { implementation "androidx.legacy:legacy-support-v4:$rootProject.libVersions.androidx.legacySupport" implementation "androidx.preference:preference:$rootProject.libVersions.androidx.preference" compileOnly "com.squareup.okhttp3:okhttp:$rootProject.libVersions.squareup.okhttp3" + compileOnly "com.squareup.okhttp:okhttp:$rootProject.libVersions.squareup.okhttp2" androidTestImplementation "junit:junit:$rootProject.libVersions.junit" androidTestImplementation "androidx.test:runner:$rootProject.libVersions.androidx.test" diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/PinningInterceptor2.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/PinningInterceptor2.java new file mode 100644 index 0000000..93b817a --- /dev/null +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/PinningInterceptor2.java @@ -0,0 +1,29 @@ +package com.datatheorem.android.trustkit.pinning; + +import androidx.annotation.NonNull; + +import com.squareup.okhttp.Interceptor; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.Response; + +import java.io.IOException; + +/** + * {@link Interceptor} used to parse the hostname of the {@link Request} URL and then save the + * hostname in the {@link RootTrustManager} which will later be used for Certificate Pinning. + */ +public class PinningInterceptor2 implements Interceptor { + private final RootTrustManager mTrustManager; + + public PinningInterceptor2(@NonNull RootTrustManager trustManager) { + mTrustManager = trustManager; + } + + @Override public Response intercept(Interceptor.Chain chain) throws IOException { + Request request = chain.request(); + String serverHostname = request.url().getHost(); + + mTrustManager.setServerHostname(serverHostname); + return chain.proceed(request); + } +} From febbdb84ab727a6331dd09adfc3adb82fd03b702 Mon Sep 17 00:00:00 2001 From: Steve Shenouda Date: Tue, 5 Nov 2019 19:39:39 -0800 Subject: [PATCH 3/6] Add documentation for multiple domain pinning support --- README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index de09154..94c5e30 100644 --- a/README.md +++ b/README.md @@ -116,20 +116,25 @@ protected void onCreate(Bundle savedInstanceState) { connection.setSSLSocketFactory(TrustKit.getInstance().getSSLSocketFactory(serverHostname)); // OkHttp 2.x + RootTrustManager trustManager = new RootTrustManager(); OkHttpClient client = new OkHttpClient() - .setSSLSocketFactory(TrustKit.getInstance().getSSLSocketFactory(serverHostname)); + .setSslSocketFactory(TrustKit.getInstance().getSSLSocketFactory(trustManager)); + client.interceptors().add(new PinningInterceptor2(trustManager)); // OkHttp 3.0.x, 3.1.x and 3.2.x + RootTrustManager trustManager = new RootTrustManager(); OkHttpClient client = new OkHttpClient.Builder() - .sslSocketFactory(TrustKit.getInstance().getSSLSocketFactory(serverHostname)) + .sslSocketFactory(TrustKit.getInstance().getSSLSocketFactory(trustManager)) + .addInterceptor(new PinningInterceptor(trustManager)) // OkHttp 3.3.x and higher + RootTrustManager trustManager = new RootTrustManager(); OkHttpClient client = - new OkHttpClient().newBuilder() - .sslSocketFactory(TrustKit.getInstance().getSSLSocketFactory(serverHostname), - TrustKit.getInstance().getTrustManager(serverHostname)) + new OkHttpClient.Builder() + .sslSocketFactory(TrustKit.getInstance().getSSLSocketFactory(trustManager), trustManager) + .addInterceptor(new PinningInterceptor(trustManager)) .build(); } From a105150a6c570e2004049b3d759cb52af3c28bf4 Mon Sep 17 00:00:00 2001 From: Steve Shenouda Date: Thu, 27 Feb 2020 19:19:14 -0800 Subject: [PATCH 4/6] Add OkHttp Helper classes for easier dev implementation --- README.md | 15 ++--- .../android/trustkit/TrustKit.java | 23 ------- .../trustkit/pinning/OkHttp2Helper.java | 52 ++++++++++++++++ .../trustkit/pinning/OkHttp3Helper.java | 60 +++++++++++++++++++ .../trustkit/pinning/RootTrustManager.java | 2 +- 5 files changed, 119 insertions(+), 33 deletions(-) create mode 100644 trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp2Helper.java create mode 100644 trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp3Helper.java diff --git a/README.md b/README.md index 94c5e30..1e17625 100644 --- a/README.md +++ b/README.md @@ -116,25 +116,22 @@ protected void onCreate(Bundle savedInstanceState) { connection.setSSLSocketFactory(TrustKit.getInstance().getSSLSocketFactory(serverHostname)); // OkHttp 2.x - RootTrustManager trustManager = new RootTrustManager(); OkHttpClient client = new OkHttpClient() - .setSslSocketFactory(TrustKit.getInstance().getSSLSocketFactory(trustManager)); - client.interceptors().add(new PinningInterceptor2(trustManager)); + .setSslSocketFactory(OkHttp2Helper.getSSLSocketFactory()); + client.interceptors().add(OkHttp2Helper.getPinningInterceptor()); // OkHttp 3.0.x, 3.1.x and 3.2.x - RootTrustManager trustManager = new RootTrustManager(); OkHttpClient client = new OkHttpClient.Builder() - .sslSocketFactory(TrustKit.getInstance().getSSLSocketFactory(trustManager)) - .addInterceptor(new PinningInterceptor(trustManager)) + .sslSocketFactory(OkHttp3Helper.getSSLSocketFactory()) + .addInterceptor(OkHttp3Helper.getPinningInterceptor()) // OkHttp 3.3.x and higher - RootTrustManager trustManager = new RootTrustManager(); OkHttpClient client = new OkHttpClient.Builder() - .sslSocketFactory(TrustKit.getInstance().getSSLSocketFactory(trustManager), trustManager) - .addInterceptor(new PinningInterceptor(trustManager)) + .sslSocketFactory(OkHttp3Helper.getSSLSocketFactory(), OkHttp3Helper.getTrustManager()) + .addInterceptor(OkHttp3Helper.getPinningInterceptor()) .build(); } diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/TrustKit.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/TrustKit.java index d0a79ae..ccc5084 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/TrustKit.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/TrustKit.java @@ -371,29 +371,6 @@ public SSLSocketFactory getSSLSocketFactory(@NonNull String serverHostname) { } } - /** - * Retrieve an {@code SSLSSocketFactory} that implements SSL pinning validation based on the - * current TrustKit configuration. It can be used with an OkHttpClient to add SSL - * pinning validation to the connections. - * - *

- * The {@code SSLSocketFactory} is configured for the current TrustKit configuration and - * will enforce the configuration's pinning policy. - *

- */ - @NonNull - public SSLSocketFactory getSSLSocketFactory(@NonNull RootTrustManager trustManager) { - try { - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, new X509TrustManager[]{trustManager}, null); - - return sslContext.getSocketFactory(); - } catch (NoSuchAlgorithmException | KeyManagementException e) { - e.printStackTrace(); - throw new IllegalStateException("SSLSocketFactory creation failed"); - } - } - /** Retrieve an {@code X509TrustManager} that implements SSL pinning validation based on the * current TrustKit configuration for the supplied hostname. It can be used with some network diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp2Helper.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp2Helper.java new file mode 100644 index 0000000..ba3fe9f --- /dev/null +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp2Helper.java @@ -0,0 +1,52 @@ +package com.datatheorem.android.trustkit.pinning; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +import com.squareup.okhttp.Interceptor; +import com.squareup.okhttp.Request; + +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.X509TrustManager; + +@RequiresApi(api = 17) +public class OkHttp2Helper { + private static RootTrustManager trustManager = new RootTrustManager(); + + /** + * Retrieve an {@code SSLSSocketFactory} that implements SSL pinning validation based on the + * current TrustKit configuration. It can be used with an OkHttpClient to add SSL + * pinning validation to the connections. + * + *

+ * The {@code SSLSocketFactory} is configured for the current TrustKit configuration and + * will enforce the configuration's pinning policy. + *

+ */ + @NonNull + public static SSLSocketFactory getSSLSocketFactory() { + try { + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new X509TrustManager[]{trustManager}, null); + + return sslContext.getSocketFactory(); + } catch (NoSuchAlgorithmException | KeyManagementException e) { + e.printStackTrace(); + throw new IllegalStateException("SSLSocketFactory creation failed"); + } + } + + /** + * Returns an {@link com.squareup.okhttp.Interceptor} used to parse the hostname of the + * {@link Request} URL and then save the hostname in the {@link RootTrustManager} which will + * later be used for Certificate Pinning. + */ + @NonNull + public static Interceptor getPinningInterceptor() { + return new PinningInterceptor2(trustManager); + } +} diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp3Helper.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp3Helper.java new file mode 100644 index 0000000..3c968ee --- /dev/null +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/OkHttp3Helper.java @@ -0,0 +1,60 @@ +package com.datatheorem.android.trustkit.pinning; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.X509TrustManager; + +import okhttp3.Interceptor; +import okhttp3.Request; + +@RequiresApi(api = 17) +public class OkHttp3Helper { + private static RootTrustManager trustManager = new RootTrustManager(); + + /** + * Retrieve an {@code SSLSSocketFactory} that implements SSL pinning validation based on the + * current TrustKit configuration. It can be used with an OkHttpClient to add SSL + * pinning validation to the connections. + * + *

+ * The {@code SSLSocketFactory} is configured for the current TrustKit configuration and + * will enforce the configuration's pinning policy. + *

+ */ + @NonNull + public static SSLSocketFactory getSSLSocketFactory() { + try { + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new X509TrustManager[]{trustManager}, null); + + return sslContext.getSocketFactory(); + } catch (NoSuchAlgorithmException | KeyManagementException e) { + e.printStackTrace(); + throw new IllegalStateException("SSLSocketFactory creation failed"); + } + } + + /** + * Returns an {@link okhttp3.Interceptor} used to parse the hostname of the {@link Request} URL + * and then save the hostname in the {@link RootTrustManager} which will later be used for + * Certificate Pinning. + */ + @NonNull + public static Interceptor getPinningInterceptor() { + return new PinningInterceptor(trustManager); + } + + /** + * Returns an instance of the {@link RootTrustManager} used for Certificate Pinning. + */ + @NonNull + public static RootTrustManager getTrustManager() { + return trustManager; + } +} diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/RootTrustManager.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/RootTrustManager.java index 0d2e85c..c66cb01 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/RootTrustManager.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/RootTrustManager.java @@ -20,7 +20,7 @@ * hostname set by the {@link PinningInterceptor}.

*/ @RequiresApi(api = 17) -public class RootTrustManager implements X509TrustManager { +class RootTrustManager implements X509TrustManager { private final ThreadLocal mServerHostname = new ThreadLocal<>(); @Override From cb6723f20673223e1bb15ffb0ecf6da63f7242c6 Mon Sep 17 00:00:00 2001 From: Steve Shenouda Date: Thu, 27 Feb 2020 19:20:28 -0800 Subject: [PATCH 5/6] Add documentation for hostname-aware invocation of SSL --- .../android/trustkit/pinning/RootTrustManager.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/RootTrustManager.java b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/RootTrustManager.java index c66cb01..428d6b4 100644 --- a/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/RootTrustManager.java +++ b/trustkit/src/main/java/com/datatheorem/android/trustkit/pinning/RootTrustManager.java @@ -33,6 +33,10 @@ public void checkServerTrusted(X509Certificate[] chain, String authType) throws String host = mServerHostname.get(); DomainPinningPolicy serverConfig = TrustKit.getInstance().getConfiguration().getPolicyForHostname(host); + //This check is needed for compatibility with the Platform default's implementation of + //the Trust Manager. For APIs 24 and greater, the Platform's default TrustManager states + //that it requires usage of the hostname-aware version of checkServerTrusted for app's that + //implement Android's network_security_config file. if (serverConfig == null) { new X509TrustManagerExtensions(TrustKit.getInstance().getTrustManager(host)).checkServerTrusted(chain, authType, host); } else { From 7273b2d0c7a8c6586c72b861f42410a214421311 Mon Sep 17 00:00:00 2001 From: Steve Shenouda Date: Thu, 27 Feb 2020 19:30:38 -0800 Subject: [PATCH 6/6] Add documentation stating no support of url redirects --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 1e17625..7f25429 100644 --- a/README.md +++ b/README.md @@ -120,18 +120,23 @@ protected void onCreate(Bundle savedInstanceState) { new OkHttpClient() .setSslSocketFactory(OkHttp2Helper.getSSLSocketFactory()); client.interceptors().add(OkHttp2Helper.getPinningInterceptor()); + client.setFollowRedirects(false); // OkHttp 3.0.x, 3.1.x and 3.2.x OkHttpClient client = new OkHttpClient.Builder() .sslSocketFactory(OkHttp3Helper.getSSLSocketFactory()) .addInterceptor(OkHttp3Helper.getPinningInterceptor()) + .followRedirects(false) + .followSslRedirects(false) // OkHttp 3.3.x and higher OkHttpClient client = new OkHttpClient.Builder() .sslSocketFactory(OkHttp3Helper.getSSLSocketFactory(), OkHttp3Helper.getTrustManager()) .addInterceptor(OkHttp3Helper.getPinningInterceptor()) + .followRedirects(false) + .followSslRedirects(false) .build(); } @@ -162,6 +167,7 @@ On Android M and earlier devices, TrustKit provides uses its own implementation * The `` setting is only applied when used within the global `` tag. Hence, custom trust anchors for specific domains cannot be set. * Within the `` tag, only `` tags pointing to a raw certificate file are supported (the `user` or `system` values for the `src` attribute will be ignored). +For consumers of TrustKit's OkHttpHelper solutions, redirects must to be disabled as Pinning will currently only work properly on the initial request and not any redirects License -------