Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save 4sskick/d1318379500747564d8f1c8d6af1f796 to your computer and use it in GitHub Desktop.
Save 4sskick/d1318379500747564d8f1c8d6af1f796 to your computer and use it in GitHub Desktop.
OkHttp 3 implementation of Volley's HttpStack
/*
* Copyright (C) 2016 Eric Cochran
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.boardroomlimited.ignite.requests;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.util.Log;
import android.widget.Toast;
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 com.boardroomlimited.ignite.activities.noconnection.NoConnectionActivity;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import okhttp3.Call;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
public class PinningOkClientBuilder extends BaseHttpStack {
private String[] mCertList = {"fake.com_public_certificate"};
private final List<Interceptor> mInterceptors;
private final Context mContext;
public PinningOkClientBuilder(Context mContext, List<Interceptor> interceptors) {
this.mContext = mContext;
this.mInterceptors = interceptors;
}
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);
//ssl init
try {
SSLContext sslContext = getSSLSocketFactory();
if (sslContext != null) {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null); // Use null for default trust store
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
clientBuilder.sslSocketFactory(sslContext.getSocketFactory()
, (X509TrustManager) getWrappedTrustManagers(trustManagers)[0]);
else clientBuilder.sslSocketFactory(sslContext.getSocketFactory());
}
} catch (KeyStoreException | NoSuchAlgorithmException e) {
e.printStackTrace();
Toast.makeText(mContext, "Failed to set SSL pinning", Toast.LENGTH_SHORT).show();
}
for (Interceptor interceptor : mInterceptors) {
clientBuilder.addNetworkInterceptor(interceptor);
}
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 SSLContext getSSLSocketFactory() {
try {
InputStream inputStream = mContext.getAssets().open(mCertList[0] + ".cer");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate ca = cf.generateCertificate(inputStream); //already pinned the certificate
inputStream.close();
//might no need using the keystore explicitly
//custom trust manager can directly reference the loaded certificate object from asset folder
KeyStore keyStore = KeyStore.getInstance("BKS");
keyStore.load(null, null);
keyStore.setCertificateEntry(mCertList[0], ca);
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
TrustManager[] wrappedTrustManager = getWrappedTrustManagers(tmf.getTrustManagers());
final SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, wrappedTrustManager, null);
return sslContext;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private TrustManager[] getWrappedTrustManagers(TrustManager[] trustManagers) {
final X509TrustManager originalTrustManager = (X509TrustManager) trustManagers[0];
return new TrustManager[]{new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
// Not used for server certificate validation
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null); // Use null for default trust store
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
X509TrustManager defaultTrust = (X509TrustManager) trustManagers[0];
defaultTrust.checkServerTrusted(chain, authType);
} catch (NoSuchAlgorithmException | KeyStoreException e) {
e.printStackTrace();
// when system validation fails, check against pinned certificate
try {
if (!verifyChain(chain)) {
goingToNoConnection();
Toast.makeText(mContext, "Invalid Certificate.", Toast.LENGTH_SHORT).show();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
private boolean verifyChain(X509Certificate[] chain) throws IOException {
// Check if the chain contains the pinned certificate
// validation relies on comparing the entire certificates themselves, not their hashes,
// no need the hash calculation or comparing
for (X509Certificate cert : chain) {
Log.e("ssl ok", "Certificate Subject: " + cert.getSubjectX500Principal());
X509Certificate pinnedCert = getPinnedCertificate();
if (pinnedCert != null && cert.equals(pinnedCert)) {
return true;
}
}
return false;
}
@Override
public X509Certificate[] getAcceptedIssuers() {
// return new X509Certificate[0];
return originalTrustManager.getAcceptedIssuers();
}
}};
}
private X509Certificate getPinnedCertificate() throws IOException {
try (InputStream in = mContext.getAssets().open(mCertList[0] + ".cer")) {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
return (X509Certificate) cf.generateCertificate(in);
} catch (CertificateException e) {
Log.e("ssl ok", "getPinnedCertificate: Error loading certificate", e);
return null;
}
}
private void goingToNoConnection() {
mContext.startActivity(new Intent(mContext, NoConnectionActivity.class));
}
}
import java.io.IOException;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public class PinningOkClientInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// Log Request Details
String requestUrl = request.url().toString();
String requestMethod = request.method();
Headers requestHeaders = request.headers();
StringBuilder requestBodyBuilder = new StringBuilder();
if (request.body() != null) {
requestBodyBuilder.append("Request Body: ");
requestBodyBuilder.append(request.body().toString());
}
StringBuilder logMessage = new StringBuilder();
logMessage.append("--> Sending request ")
.append(requestMethod)
.append(" ")
.append(requestUrl)
.append("\n");
for (int i = 0; i < requestHeaders.size(); i++) {
logMessage.append("--> Header: ")
.append(requestHeaders.name(i))
.append(": ")
.append(requestHeaders.value(i))
.append("\n");
}
logMessage.append(requestBodyBuilder.toString())
.append("\n");
// Send request and get response
Response response = chain.proceed(request);
// Log Response Details
int responseCode = response.code();
Headers responseHeaders = response.headers();
StringBuilder responseBodyBuilder = new StringBuilder();
try (ResponseBody responseBody = response.peekBody(4096)) {
if (responseBody != null) {
responseBodyBuilder.append("Response Body: ");
responseBodyBuilder.append(responseBody.string());
}
}
logMessage.append("<-- Received response: ")
.append(responseCode)
.append("\n");
for (int i = 0; i < responseHeaders.size(); i++) {
logMessage.append("<-- Header: ")
.append(responseHeaders.name(i))
.append(": ")
.append(responseHeaders.value(i))
.append("\n");
}
logMessage.append(responseBodyBuilder.toString())
.append("\n");
// Use your preferred logging library (e.g., Logcat)
System.out.println(logMessage.toString());
return response;
}
}
@4sskick
Copy link
Author

4sskick commented May 20, 2024

build.gradle (app under src)

implementation 'com.android.volley:volley:1.2.1'
implementation 'com.squareup.okhttp3:okhttp:3.9.0'
implementation 'com.squareup.okhttp:okhttp-urlconnection:1.6.0'

implement the request combine with Volley

RequestQueue requestQueue = Volley.newRequestQueue(mActivity, new PinningOkClientBuilder(mActivity, new ArrayList() {{
if (BuildConfig.DEBUG) add(new PinningOkClientInterceptor());
}}));

requestQueue.start();

and do build request as usual

Request apiRequest = new StringRequest(GET, requestURL, successListener, errorListener) { };

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment