Last active
October 10, 2018 23:15
-
-
Save johnwatsondev/b954597b631f5cb13b777c5e8caae806 to your computer and use it in GitHub Desktop.
OkHttpClient set custom trust.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.jwdev.data.api; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.security.GeneralSecurityException; | |
import java.security.KeyStore; | |
import java.security.cert.Certificate; | |
import java.security.cert.CertificateFactory; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.Collection; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.concurrent.TimeUnit; | |
import javax.net.ssl.HostnameVerifier; | |
import javax.net.ssl.KeyManagerFactory; | |
import javax.net.ssl.SSLContext; | |
import javax.net.ssl.SSLSession; | |
import javax.net.ssl.SSLSocketFactory; | |
import javax.net.ssl.TrustManager; | |
import javax.net.ssl.TrustManagerFactory; | |
import javax.net.ssl.X509TrustManager; | |
import okhttp3.ConnectionSpec; | |
import okhttp3.Interceptor; | |
import okhttp3.OkHttpClient; | |
import okhttp3.Request; | |
import okhttp3.Response; | |
import okio.Buffer; | |
import retrofit2.Retrofit; | |
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; | |
import retrofit2.converter.gson.GsonConverterFactory; | |
import retrofit2.converter.scalars.ScalarsConverterFactory; | |
public final class ApiRestClient { | |
public static <S> S createService(final Class<S> serviceClass, final String endPoint, | |
final List<ConnectionSpec> connectionSpecs, final long connectTimeout, final long readTimeout, | |
final long writeTimeout) { | |
return createServiceWithHeader(serviceClass, endPoint, connectionSpecs, null, connectTimeout, | |
readTimeout, writeTimeout); | |
} | |
public static <S> S createServiceWithHeader(final Class<S> serviceClass, final String endPoint, | |
final List<ConnectionSpec> connectionSpecs, final Map<String, String> headers, | |
final long connectTimeout, final long readTimeout, final long writeTimeout) { | |
List<Interceptor> interceptors = null; | |
if (headers != null) { | |
interceptors = new ArrayList<>(); | |
Interceptor interceptor = new Interceptor() { | |
@Override public Response intercept(Chain chain) throws IOException { | |
final Request.Builder builder = chain.request().newBuilder(); | |
for (Map.Entry<String, String> pair : headers.entrySet()) { | |
builder.addHeader(pair.getKey(), pair.getValue()); | |
} | |
final Request request = builder.build(); | |
return chain.proceed(request); | |
} | |
}; | |
interceptors.add(interceptor); | |
} | |
final OkHttpClient client = | |
createOkHttpClient(connectionSpecs, interceptors, connectTimeout, readTimeout, | |
writeTimeout); | |
final Retrofit retrofit = new Retrofit.Builder().baseUrl(endPoint) | |
.client(client) | |
//.addConverterFactory(LoganSquareConverterFactory.create()) | |
.addConverterFactory(ScalarsConverterFactory.create()) | |
.addConverterFactory(GsonConverterFactory.create()) | |
.addCallAdapterFactory(RxJavaCallAdapterFactory.create()) | |
.build(); | |
return retrofit.create(serviceClass); | |
} | |
public static OkHttpClient createOkHttpClient(final List<ConnectionSpec> connectionSpecs, | |
final List<Interceptor> interceptors, final long connectTimeout, final long readTimeout, | |
final long writeTimeout) { | |
//ConnectionSpec spec = | |
// new ConnectionSpec.Builder(ConnectionSpec.COMPATIBLE_TLS).tlsVersions(TlsVersion.TLS_1_0) | |
// .cipherSuites(CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, | |
// CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256) | |
// .build(); | |
final OkHttpClient.Builder builder = | |
new OkHttpClient.Builder().connectTimeout(connectTimeout, TimeUnit.SECONDS) | |
.readTimeout(readTimeout, TimeUnit.SECONDS) | |
.writeTimeout(writeTimeout, TimeUnit.SECONDS); | |
//if (connectionSpecs != null && !connectionSpecs.isEmpty()) { | |
// builder.connectionSpecs(connectionSpecs); | |
//} | |
X509TrustManager trustManager; | |
SSLSocketFactory sslSocketFactory; | |
try { | |
trustManager = trustManagerForCertificates(trustedCertificatesInputStream()); | |
SSLContext sslContext = SSLContext.getInstance("TLS"); | |
sslContext.init(null, new TrustManager[] { trustManager }, null); | |
sslSocketFactory = sslContext.getSocketFactory(); | |
builder.sslSocketFactory(sslSocketFactory, trustManager); | |
builder.hostnameVerifier(new HostnameVerifier() { | |
@Override public boolean verify(String hostname, SSLSession session) { | |
// HostnameVerifier hv = OkHostnameVerifier.INSTANCE; | |
// Log.e("HTTPS_OKHTTP", "" + hv.verify("sample.com", session)); | |
return true; | |
} | |
}); | |
} catch (GeneralSecurityException e) { | |
e.printStackTrace(); | |
} | |
if (interceptors != null && !interceptors.isEmpty()) { | |
for (Interceptor interceptor : interceptors) { | |
builder.addInterceptor(interceptor); | |
} | |
} | |
return builder.build(); | |
} | |
/** | |
* Returns an input stream containing one or more certificate PEM files. This implementation just | |
* embeds the PEM files in Java strings; most applications will instead read this from a resource | |
* file that gets bundled with the application. | |
*/ | |
private static InputStream trustedCertificatesInputStream() { | |
// PEM files for root certificates of Comodo and Entrust. These two CAs are sufficient to view | |
// https://publicobject.com (Comodo) and https://squareup.com (Entrust). But they aren't | |
// sufficient to connect to most HTTPS sites including https://godaddy.com and https://visa.com. | |
// Typically developers will need to get a PEM file from their organization's TLS administrator. | |
String entrustRootCertificateAuthority = "" | |
+ "-----BEGIN CERTIFICATE-----\n" | |
+ "MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC\n" | |
+ "VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0\n" | |
+ "Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW\n" | |
+ "KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl\n" | |
+ "cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw\n" | |
+ "NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw\n" | |
+ "NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy\n" | |
+ "ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV\n" | |
+ "BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ\n" | |
+ "KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo\n" | |
+ "Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4\n" | |
+ "4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9\n" | |
+ "KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI\n" | |
+ "rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi\n" | |
+ "94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB\n" | |
+ "sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi\n" | |
+ "gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo\n" | |
+ "kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE\n" | |
+ "vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA\n" | |
+ "A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t\n" | |
+ "O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua\n" | |
+ "AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP\n" | |
+ "9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/\n" | |
+ "eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m\n" | |
+ "0vdXcDazv/wor3ElhVsT/h5/WrQ8\n" | |
+ "-----END CERTIFICATE-----\n"; | |
return new Buffer().writeUtf8(entrustRootCertificateAuthority).inputStream(); | |
} | |
/** | |
* Returns a trust manager that trusts {@code certificates} and none other. HTTPS services whose | |
* certificates have not been signed by these certificates will fail with a {@code | |
* SSLHandshakeException}. | |
* | |
* <p>This can be used to replace the host platform's built-in trusted certificates with a custom | |
* set. This is useful in development where certificate authority-trusted certificates aren't | |
* available. Or in production, to avoid reliance on third-party certificate authorities. | |
* | |
* <p>See also {@link CertificatePinner}, which can limit trusted certificates while still using | |
* the host platform's built-in trust store. | |
* | |
* <h3>Warning: Customizing Trusted Certificates is Dangerous!</h3> | |
* | |
* <p>Relying on your own trusted certificates limits your server team's ability to update their | |
* TLS certificates. By installing a specific set of trusted certificates, you take on additional | |
* operational complexity and limit your ability to migrate between certificate authorities. Do | |
* not use custom trusted certificates in production without the blessing of your server's TLS | |
* administrator. | |
*/ | |
private static X509TrustManager trustManagerForCertificates(InputStream in) | |
throws GeneralSecurityException { | |
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); | |
Collection<? extends Certificate> certificates = certificateFactory.generateCertificates(in); | |
if (certificates.isEmpty()) { | |
throw new IllegalArgumentException("expected non-empty set of trusted certificates"); | |
} | |
// Put the certificates a key store. | |
char[] password = "password".toCharArray(); // Any password will work. | |
KeyStore keyStore = newEmptyKeyStore(password); | |
int index = 0; | |
for (Certificate certificate : certificates) { | |
String certificateAlias = Integer.toString(index++); | |
keyStore.setCertificateEntry(certificateAlias, certificate); | |
} | |
// Use it to build an X509 trust manager. | |
KeyManagerFactory keyManagerFactory = | |
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); | |
keyManagerFactory.init(keyStore, password); | |
TrustManagerFactory trustManagerFactory = | |
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); | |
trustManagerFactory.init(keyStore); | |
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); | |
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { | |
throw new IllegalStateException( | |
"Unexpected default trust managers:" + Arrays.toString(trustManagers)); | |
} | |
return (X509TrustManager) trustManagers[0]; | |
} | |
private static KeyStore newEmptyKeyStore(char[] password) throws GeneralSecurityException { | |
try { | |
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); | |
InputStream in = null; // By convention, 'null' creates an empty key store. | |
keyStore.load(in, password); | |
return keyStore; | |
} catch (IOException e) { | |
throw new AssertionError(e); | |
} | |
} | |
public static OkHttpClient createOkHttpClient(final List<Interceptor> interceptors, | |
final long connectTimeout, final long readTimeout, final long writeTimeout) { | |
return createOkHttpClient(null, interceptors, connectTimeout, readTimeout, writeTimeout); | |
} | |
public static OkHttpClient createOkHttpClient(final long connectTimeout, final long readTimeout, | |
final long writeTimeout) { | |
return createOkHttpClient(null, null, connectTimeout, readTimeout, writeTimeout); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment