Skip to content

Commit cb18874

Browse files
authored
Merge pull request #63 from SteveShenouda/support-multiple-domains
Add support for pinning multiple domains with a single OkHttpClient
2 parents 5e761b6 + 7273b2d commit cb18874

8 files changed

Lines changed: 244 additions & 5 deletions

File tree

README.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,18 +118,25 @@ protected void onCreate(Bundle savedInstanceState) {
118118
// OkHttp 2.x
119119
OkHttpClient client =
120120
new OkHttpClient()
121-
.setSSLSocketFactory(TrustKit.getInstance().getSSLSocketFactory(serverHostname));
121+
.setSslSocketFactory(OkHttp2Helper.getSSLSocketFactory());
122+
client.interceptors().add(OkHttp2Helper.getPinningInterceptor());
123+
client.setFollowRedirects(false);
122124

123125
// OkHttp 3.0.x, 3.1.x and 3.2.x
124126
OkHttpClient client =
125127
new OkHttpClient.Builder()
126-
.sslSocketFactory(TrustKit.getInstance().getSSLSocketFactory(serverHostname))
128+
.sslSocketFactory(OkHttp3Helper.getSSLSocketFactory())
129+
.addInterceptor(OkHttp3Helper.getPinningInterceptor())
130+
.followRedirects(false)
131+
.followSslRedirects(false)
127132

128133
// OkHttp 3.3.x and higher
129134
OkHttpClient client =
130-
new OkHttpClient().newBuilder()
131-
.sslSocketFactory(TrustKit.getInstance().getSSLSocketFactory(serverHostname),
132-
TrustKit.getInstance().getTrustManager(serverHostname))
135+
new OkHttpClient.Builder()
136+
.sslSocketFactory(OkHttp3Helper.getSSLSocketFactory(), OkHttp3Helper.getTrustManager())
137+
.addInterceptor(OkHttp3Helper.getPinningInterceptor())
138+
.followRedirects(false)
139+
.followSslRedirects(false)
133140
.build();
134141
}
135142

@@ -160,6 +167,7 @@ On Android M and earlier devices, TrustKit provides uses its own implementation
160167
* The `<trust-anchors>` setting is only applied when used within the global `<debug-overrides>` tag. Hence, custom trust anchors for specific domains cannot be set.
161168
* Within the `<trust-anchors>` tag, only `<certificate>` tags pointing to a raw certificate file are supported (the `user` or `system` values for the `src` attribute will be ignored).
162169

170+
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
163171

164172
License
165173
-------

build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ ext{
6464
],
6565
google: [
6666
material: '1.0.0'
67+
],
68+
squareup: [
69+
okhttp3: '3.11.0',
70+
okhttp2: '2.4.0'
6771
]
6872
]
6973

trustkit/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ dependencies {
2929
implementation "androidx.annotation:annotation:$rootProject.libVersions.androidx.annotation"
3030
implementation "androidx.legacy:legacy-support-v4:$rootProject.libVersions.androidx.legacySupport"
3131
implementation "androidx.preference:preference:$rootProject.libVersions.androidx.preference"
32+
compileOnly "com.squareup.okhttp3:okhttp:$rootProject.libVersions.squareup.okhttp3"
33+
compileOnly "com.squareup.okhttp:okhttp:$rootProject.libVersions.squareup.okhttp2"
3234

3335
androidTestImplementation "junit:junit:$rootProject.libVersions.junit"
3436
androidTestImplementation "androidx.test:runner:$rootProject.libVersions.androidx.test"
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.datatheorem.android.trustkit.pinning;
2+
3+
import androidx.annotation.NonNull;
4+
import androidx.annotation.RequiresApi;
5+
6+
import com.squareup.okhttp.Interceptor;
7+
import com.squareup.okhttp.Request;
8+
9+
import java.security.KeyManagementException;
10+
import java.security.NoSuchAlgorithmException;
11+
12+
import javax.net.ssl.SSLContext;
13+
import javax.net.ssl.SSLSocketFactory;
14+
import javax.net.ssl.X509TrustManager;
15+
16+
@RequiresApi(api = 17)
17+
public class OkHttp2Helper {
18+
private static RootTrustManager trustManager = new RootTrustManager();
19+
20+
/**
21+
* Retrieve an {@code SSLSSocketFactory} that implements SSL pinning validation based on the
22+
* current TrustKit configuration. It can be used with an OkHttpClient to add SSL
23+
* pinning validation to the connections.
24+
*
25+
* <p>
26+
* The {@code SSLSocketFactory} is configured for the current TrustKit configuration and
27+
* will enforce the configuration's pinning policy.
28+
* </p>
29+
*/
30+
@NonNull
31+
public static SSLSocketFactory getSSLSocketFactory() {
32+
try {
33+
SSLContext sslContext = SSLContext.getInstance("TLS");
34+
sslContext.init(null, new X509TrustManager[]{trustManager}, null);
35+
36+
return sslContext.getSocketFactory();
37+
} catch (NoSuchAlgorithmException | KeyManagementException e) {
38+
e.printStackTrace();
39+
throw new IllegalStateException("SSLSocketFactory creation failed");
40+
}
41+
}
42+
43+
/**
44+
* Returns an {@link com.squareup.okhttp.Interceptor} used to parse the hostname of the
45+
* {@link Request} URL and then save the hostname in the {@link RootTrustManager} which will
46+
* later be used for Certificate Pinning.
47+
*/
48+
@NonNull
49+
public static Interceptor getPinningInterceptor() {
50+
return new PinningInterceptor2(trustManager);
51+
}
52+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.datatheorem.android.trustkit.pinning;
2+
3+
import androidx.annotation.NonNull;
4+
import androidx.annotation.RequiresApi;
5+
6+
import java.security.KeyManagementException;
7+
import java.security.NoSuchAlgorithmException;
8+
9+
import javax.net.ssl.SSLContext;
10+
import javax.net.ssl.SSLSocketFactory;
11+
import javax.net.ssl.X509TrustManager;
12+
13+
import okhttp3.Interceptor;
14+
import okhttp3.Request;
15+
16+
@RequiresApi(api = 17)
17+
public class OkHttp3Helper {
18+
private static RootTrustManager trustManager = new RootTrustManager();
19+
20+
/**
21+
* Retrieve an {@code SSLSSocketFactory} that implements SSL pinning validation based on the
22+
* current TrustKit configuration. It can be used with an OkHttpClient to add SSL
23+
* pinning validation to the connections.
24+
*
25+
* <p>
26+
* The {@code SSLSocketFactory} is configured for the current TrustKit configuration and
27+
* will enforce the configuration's pinning policy.
28+
* </p>
29+
*/
30+
@NonNull
31+
public static SSLSocketFactory getSSLSocketFactory() {
32+
try {
33+
SSLContext sslContext = SSLContext.getInstance("TLS");
34+
sslContext.init(null, new X509TrustManager[]{trustManager}, null);
35+
36+
return sslContext.getSocketFactory();
37+
} catch (NoSuchAlgorithmException | KeyManagementException e) {
38+
e.printStackTrace();
39+
throw new IllegalStateException("SSLSocketFactory creation failed");
40+
}
41+
}
42+
43+
/**
44+
* Returns an {@link okhttp3.Interceptor} used to parse the hostname of the {@link Request} URL
45+
* and then save the hostname in the {@link RootTrustManager} which will later be used for
46+
* Certificate Pinning.
47+
*/
48+
@NonNull
49+
public static Interceptor getPinningInterceptor() {
50+
return new PinningInterceptor(trustManager);
51+
}
52+
53+
/**
54+
* Returns an instance of the {@link RootTrustManager} used for Certificate Pinning.
55+
*/
56+
@NonNull
57+
public static RootTrustManager getTrustManager() {
58+
return trustManager;
59+
}
60+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.datatheorem.android.trustkit.pinning;
2+
3+
import androidx.annotation.NonNull;
4+
5+
import java.io.IOException;
6+
7+
import okhttp3.Interceptor;
8+
import okhttp3.Request;
9+
import okhttp3.Response;
10+
11+
/**
12+
* {@link Interceptor} used to parse the hostname of the {@link Request} URL and then save the
13+
* hostname in the {@link RootTrustManager} which will later be used for Certificate Pinning.
14+
*/
15+
public class PinningInterceptor implements Interceptor {
16+
private final RootTrustManager mTrustManager;
17+
18+
public PinningInterceptor(@NonNull RootTrustManager trustManager) {
19+
mTrustManager = trustManager;
20+
}
21+
22+
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
23+
Request request = chain.request();
24+
String serverHostname = request.url().host();
25+
26+
mTrustManager.setServerHostname(serverHostname);
27+
return chain.proceed(request);
28+
}
29+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.datatheorem.android.trustkit.pinning;
2+
3+
import androidx.annotation.NonNull;
4+
5+
import com.squareup.okhttp.Interceptor;
6+
import com.squareup.okhttp.Request;
7+
import com.squareup.okhttp.Response;
8+
9+
import java.io.IOException;
10+
11+
/**
12+
* {@link Interceptor} used to parse the hostname of the {@link Request} URL and then save the
13+
* hostname in the {@link RootTrustManager} which will later be used for Certificate Pinning.
14+
*/
15+
public class PinningInterceptor2 implements Interceptor {
16+
private final RootTrustManager mTrustManager;
17+
18+
public PinningInterceptor2(@NonNull RootTrustManager trustManager) {
19+
mTrustManager = trustManager;
20+
}
21+
22+
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
23+
Request request = chain.request();
24+
String serverHostname = request.url().getHost();
25+
26+
mTrustManager.setServerHostname(serverHostname);
27+
return chain.proceed(request);
28+
}
29+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.datatheorem.android.trustkit.pinning;
2+
3+
import android.net.http.X509TrustManagerExtensions;
4+
5+
import androidx.annotation.NonNull;
6+
import androidx.annotation.RequiresApi;
7+
8+
import com.datatheorem.android.trustkit.TrustKit;
9+
import com.datatheorem.android.trustkit.config.DomainPinningPolicy;
10+
11+
import java.security.cert.CertificateException;
12+
import java.security.cert.X509Certificate;
13+
14+
import javax.net.ssl.X509TrustManager;
15+
16+
/**
17+
* {@link X509TrustManager} used for Certificate Pinning.
18+
*
19+
* <p>This trust manager delegates to the appropriate {@link PinningTrustManager} decided by the
20+
* hostname set by the {@link PinningInterceptor}.</p>
21+
*/
22+
@RequiresApi(api = 17)
23+
class RootTrustManager implements X509TrustManager {
24+
private final ThreadLocal<String> mServerHostname = new ThreadLocal<>();
25+
26+
@Override
27+
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
28+
TrustKit.getInstance().getTrustManager(mServerHostname.get()).checkClientTrusted(chain, authType);
29+
}
30+
31+
@Override
32+
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
33+
String host = mServerHostname.get();
34+
DomainPinningPolicy serverConfig =
35+
TrustKit.getInstance().getConfiguration().getPolicyForHostname(host);
36+
//This check is needed for compatibility with the Platform default's implementation of
37+
//the Trust Manager. For APIs 24 and greater, the Platform's default TrustManager states
38+
//that it requires usage of the hostname-aware version of checkServerTrusted for app's that
39+
//implement Android's network_security_config file.
40+
if (serverConfig == null) {
41+
new X509TrustManagerExtensions(TrustKit.getInstance().getTrustManager(host)).checkServerTrusted(chain, authType, host);
42+
} else {
43+
TrustKit.getInstance().getTrustManager(host).checkServerTrusted(chain, authType);
44+
}
45+
}
46+
47+
@Override
48+
public X509Certificate[] getAcceptedIssuers() {
49+
return new X509Certificate[0];
50+
}
51+
52+
void setServerHostname(@NonNull String serverHostname) {
53+
mServerHostname.set(serverHostname);
54+
}
55+
}

0 commit comments

Comments
 (0)