I have already gone through similar posts on SO which points to similar issues. In my case I'm trying to run on Nexus 6p Emulator with API Level 28.
This implementation works with Volley HurlStack but not with OkHttp3 HttpStack. So I'm sure there is no issue on the server side but on OkHttp side. Not sure what am I missing here. Any leads will be appreciated. Thanks in advance !
PS : I'm in process to get away from Volley. This is just a to test with okHttp so that further migrations is easier.
Volley Working Implementation as follows :
import android.annotation.SuppressLint;
import android.util.Base64;
import com.abc.test.core.network.Tls12SocketFactory;
import com.android.volley.toolbox.HurlStack;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import timber.log.Timber;
public class VolleyHurlStack extends HurlStack {
private final Boolean enablePinning;
VolleyHurlStack(Boolean enablePinning) {
this.enablePinning = enablePinning;
}
@Override
protected HttpURLConnection createConnection(URL url) throws IOException {
HttpsURLConnection httpsURLConnection = (HttpsURLConnection) super.createConnection(url);
try {
httpsURLConnection.setSSLSocketFactory(getSSLSocketFactory());
httpsURLConnection.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
try {
return validatePinning(session.getPeerCertificates());
} catch (SSLPeerUnverifiedException e) {
Timber.e(e);
}
return false;
}
});
} catch (Exception e) {
Timber.e(e);
}
return httpsURLConnection;
}
private SSLSocketFactory getSSLSocketFactory() {
SSLContext sc;
SSLSocketFactory sslSocketFactory = null;
try {
sc = SSLContext.getInstance("TLSv1.1");
if (sc != null) {
sc.init(null, getAllTrustManagers(), new SecureRandom());
sslSocketFactory = new Tls12SocketFactory(sc.getSocketFactory());
} else {
Timber.e("SSLContext is null");
}
} catch (NoSuchAlgorithmException | KeyManagementException e) {
Timber.e(e);
}
return sslSocketFactory;
}
private TrustManager[] getAllTrustManagers() {
return new TrustManager[]{getX509TrustManager()};
}
private X509TrustManager getX509TrustManager() {
return new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
@SuppressLint("TrustAllX509TrustManager")
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
@SuppressLint("TrustAllX509TrustManager")
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
};
}
public boolean validatePinning(Certificate[] certificate) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
for (Certificate cert : certificate) {
byte[] publicKey = cert.getPublicKey().getEncoded();
md.update(publicKey, 0, publicKey.length);
String pin = Base64.encodeToString(md.digest(), Base64.NO_WRAP);
for (String validPin : validPins) {
if (validPin.contains(pin)) {
Timber.d("validatePinning successful");
return true;
}
}
}
} catch (NoSuchAlgorithmException e) {
Timber.e(e);
}
return false;
}
}
Non- Working OkHttp Implementation as follows :
import android.annotation.SuppressLint;
import android.util.Base64;
import com.abc.test.core.network.Tls12SocketFactory;
import com.android.volley.AuthFailureError;
import com.android.volley.Header;
import com.android.volley.Request;
import com.android.volley.toolbox.BaseHttpStack;
import com.android.volley.toolbox.HttpResponse;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import okhttp3.Call;
import okhttp3.ConnectionSpec;
import okhttp3.Headers;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import timber.log.Timber;
public class OkHttp3Stack extends BaseHttpStack {
public OkHttp3Stack() {
}
private static void setConnectionParametersForRequest(okhttp3.Request.Builder builder, Request<?> request)
throws AuthFailureError {
switch (request.getMethod()) {
case Request.Method.DEPRECATED_GET_OR_POST:
// Ensure backwards compatibility. Volley assumes a request with a null body is a GET.
byte[] postBody = request.getBody();
if (postBody != null) {
builder.post(RequestBody.create(MediaType.parse(request.getBodyContentType()), postBody));
}
break;
case Request.Method.GET:
builder.get();
break;
case Request.Method.DELETE:
builder.delete(createRequestBody(request));
break;
case Request.Method.POST:
builder.post(createRequestBody(request));
break;
case Request.Method.PUT:
builder.put(createRequestBody(request));
break;
case Request.Method.HEAD:
builder.head();
break;
case Request.Method.OPTIONS:
builder.method("OPTIONS", null);
break;
case Request.Method.TRACE:
builder.method("TRACE", null);
break;
case Request.Method.PATCH:
builder.patch(createRequestBody(request));
break;
default:
throw new IllegalStateException("Unknown method type.");
}
}
private static RequestBody createRequestBody(Request r) throws AuthFailureError {
final byte[] body = r.getBody();
if (body == null) {
return null;
}
return RequestBody.create(MediaType.parse(r.getBodyContentType()), body);
}
@Override
public HttpResponse executeRequest(Request<?> request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError {
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
int timeoutMs = request.getTimeoutMs();
clientBuilder.connectTimeout(timeoutMs, TimeUnit.MILLISECONDS);
clientBuilder.readTimeout(timeoutMs, TimeUnit.MILLISECONDS);
clientBuilder.writeTimeout(timeoutMs, TimeUnit.MILLISECONDS);
okhttp3.Request.Builder okHttpRequestBuilder = new okhttp3.Request.Builder();
okHttpRequestBuilder.url(request.getUrl());
Map<String, String> headers = request.getHeaders();
for (final String name : headers.keySet()) {
okHttpRequestBuilder.addHeader(name, headers.get(name));
}
for (final String name : additionalHeaders.keySet()) {
okHttpRequestBuilder.addHeader(name, additionalHeaders.get(name));
}
setConnectionParametersForRequest(okHttpRequestBuilder, request);
clientBuilder.sslSocketFactory(getSSLSocketFactory(), getX509TrustManager());
clientBuilder.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
try {
return validatePinning(session.getPeerCertificates());
} catch (SSLPeerUnverifiedException e) {
Timber.e(e);
}
return false;
}
});
ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.allEnabledTlsVersions()
.allEnabledCipherSuites()
.build();
clientBuilder.connectionSpecs(Collections.singletonList(spec));
clientBuilder.retryOnConnectionFailure(true);
OkHttpClient client = clientBuilder.build();
okhttp3.Request okHttpRequest = okHttpRequestBuilder.build();
Call okHttpCall = client.newCall(okHttpRequest);
Response okHttpResponse = okHttpCall.execute();
int code = okHttpResponse.code();
ResponseBody body = okHttpResponse.body();
InputStream content = body == null ? null : body.byteStream();
int contentLength = body == null ? 0 : (int) body.contentLength();
List<Header> responseHeaders = mapHeaders(okHttpResponse.headers());
return new HttpResponse(code, responseHeaders, contentLength, content);
}
private List<Header> mapHeaders(Headers responseHeaders) {
List<Header> headers = new ArrayList<>();
for (int i = 0, len = responseHeaders.size(); i < len; i++) {
final String name = responseHeaders.name(i), value = responseHeaders.value(i);
if (name != null) {
headers.add(new Header(name, value));
}
}
return headers;
}
private SSLSocketFactory getSSLSocketFactory() {
SSLContext sc;
SSLSocketFactory sslSocketFactory = null;
try {
sc = SSLContext.getInstance("TLSv1.1");
if (sc != null) {
sc.init(null, getAllTrustManagers(), new SecureRandom());
sslSocketFactory = new Tls12SocketFactory(sc.getSocketFactory());
} else {
Timber.e("SSLContext is null");
}
} catch (NoSuchAlgorithmException | KeyManagementException e) {
Timber.e(e);
}
return sslSocketFactory;
}
private TrustManager[] getAllTrustManagers() {
return new TrustManager[]{getX509TrustManager()};
}
private X509TrustManager getX509TrustManager() {
return new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
@SuppressLint("TrustAllX509TrustManager")
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
@SuppressLint("TrustAllX509TrustManager")
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
};
}
private boolean validatePinning(Certificate[] certificate) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
for (Certificate cert : certificate) {
byte[] publicKey = cert.getPublicKey().getEncoded();
md.update(publicKey, 0, publicKey.length);
String pin = Base64.encodeToString(md.digest(), Base64.NO_WRAP);
for (String validPin : validPins) {
if (validPin.contains(pin)) {
Timber.d("validatePinning successful");
return true;
}
}
}
} catch (NoSuchAlgorithmException e) {
Timber.e(e);
}
return false;
}
}