/** * Copyright (c) 2018 LEAP Encryption Access Project and contributers * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package se.leap.bitmaskclient; import android.content.SharedPreferences; import android.content.res.Resources; import android.os.Build; import android.support.annotation.NonNull; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; import java.net.UnknownHostException; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import okhttp3.CipherSuite; import okhttp3.ConnectionSpec; import okhttp3.Cookie; import okhttp3.CookieJar; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; import okhttp3.TlsVersion; import static android.text.TextUtils.isEmpty; import static se.leap.bitmaskclient.ProviderAPI.ERRORS; import static se.leap.bitmaskclient.R.string.certificate_error; import static se.leap.bitmaskclient.R.string.error_io_exception_user_message; import static se.leap.bitmaskclient.R.string.error_no_such_algorithm_exception_user_message; import static se.leap.bitmaskclient.R.string.keyChainAccessError; import static se.leap.bitmaskclient.R.string.server_unreachable_message; /** * Created by cyberta on 08.01.18. */ public class OkHttpClientGenerator { SharedPreferences preferences; Resources resources; public OkHttpClientGenerator(SharedPreferences preferences, Resources resources) { this.preferences = preferences; this.resources = resources; } public OkHttpClient initCommercialCAHttpClient(JSONObject initError) { return initHttpClient(initError, null); } public OkHttpClient initSelfSignedCAHttpClient(JSONObject initError) { String certificate = preferences.getString(Provider.CA_CERT, ""); return initHttpClient(initError, certificate); } public OkHttpClient initSelfSignedCAHttpClient(JSONObject initError, String certificate) { return initHttpClient(initError, certificate); } private OkHttpClient initHttpClient(JSONObject initError, String certificate) { try { TLSCompatSocketFactory sslCompatFactory; ConnectionSpec spec = getConnectionSpec(); OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); if (!isEmpty(certificate)) { sslCompatFactory = new TLSCompatSocketFactory(certificate); } else { sslCompatFactory = new TLSCompatSocketFactory(); } sslCompatFactory.initSSLSocketFactory(clientBuilder); clientBuilder.cookieJar(getCookieJar()) .connectionSpecs(Collections.singletonList(spec)); return clientBuilder.build(); } catch (IllegalArgumentException e) { e.printStackTrace(); addErrorMessageToJson(initError, resources.getString(R.string.certificate_error)); } catch (IllegalStateException | KeyManagementException | KeyStoreException e) { e.printStackTrace(); addErrorMessageToJson(initError, String.format(resources.getString(keyChainAccessError), e.getLocalizedMessage())); } catch (NoSuchAlgorithmException | NoSuchProviderException e) { e.printStackTrace(); addErrorMessageToJson(initError, resources.getString(error_no_such_algorithm_exception_user_message)); } catch (CertificateException e) { e.printStackTrace(); addErrorMessageToJson(initError, resources.getString(certificate_error)); } catch (UnknownHostException e) { e.printStackTrace(); addErrorMessageToJson(initError, resources.getString(server_unreachable_message)); } catch (IOException e) { e.printStackTrace(); addErrorMessageToJson(initError, resources.getString(error_io_exception_user_message)); } return null; } @NonNull private ConnectionSpec getConnectionSpec() { ConnectionSpec.Builder connectionSpecbuilder = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) .tlsVersions(TlsVersion.TLS_1_2, TlsVersion.TLS_1_3); //FIXME: restrict connection further to the following recommended cipher suites for ALL supported API levels //figure out how to use bcjsse for that purpose if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) connectionSpecbuilder.cipherSuites( CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 ); return connectionSpecbuilder.build(); } @NonNull private CookieJar getCookieJar() { return new CookieJar() { private final HashMap> cookieStore = new HashMap<>(); @Override public void saveFromResponse(HttpUrl url, List cookies) { cookieStore.put(url.host(), cookies); } @Override public List loadForRequest(HttpUrl url) { List cookies = cookieStore.get(url.host()); return cookies != null ? cookies : new ArrayList(); } }; } private void addErrorMessageToJson(JSONObject jsonObject, String errorMessage) { try { jsonObject.put(ERRORS, errorMessage); } catch (JSONException e) { e.printStackTrace(); } } }