diff options
author | cyBerta <cyberta@riseup.net> | 2017-10-25 15:55:49 +0200 |
---|---|---|
committer | cyBerta <cyberta@riseup.net> | 2017-10-25 15:55:49 +0200 |
commit | c37149dec7dbc2ff2bccfa643792080c3c86ce18 (patch) | |
tree | f9e9c46c20448668639239e43bdbb9c88f198663 /app/src | |
parent | 0b146ea7ce6b628c7ff56a20318e7495960dfb7c (diff) |
8757 fixes session cookie handling by implementing okHttpClient and custom cookiejar, enables TLS 1.2 on old devices, restricts allowed cipher suites on new devices in order to harden tls based communication
Diffstat (limited to 'app/src')
10 files changed, 533 insertions, 165 deletions
diff --git a/app/src/insecure/java/se/leap/bitmaskclient/ProviderAPI.java b/app/src/insecure/java/se/leap/bitmaskclient/ProviderAPI.java index a1b1b383..4805456c 100644 --- a/app/src/insecure/java/se/leap/bitmaskclient/ProviderAPI.java +++ b/app/src/insecure/java/se/leap/bitmaskclient/ProviderAPI.java @@ -16,31 +16,89 @@ */ package se.leap.bitmaskclient; -import android.app.*; -import android.content.*; -import android.content.res.*; -import android.os.*; +import android.app.IntentService; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.os.Build; +import android.os.Bundle; +import android.os.ResultReceiver; +import android.support.annotation.NonNull; import android.util.Base64; -import org.json.*; -import org.thoughtcrime.ssl.pinning.util.*; - -import java.io.*; -import java.math.*; -import java.net.*; -import java.security.*; -import java.security.cert.*; -import java.security.interfaces.*; -import java.util.*; - -import javax.net.ssl.*; - -import se.leap.bitmaskclient.ProviderListContent.*; -import se.leap.bitmaskclient.eip.*; +import org.json.JSONException; +import org.json.JSONObject; +import org.thoughtcrime.ssl.pinning.util.PinningHelper; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.net.ConnectException; +import java.net.CookieHandler; +import java.net.MalformedURLException; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.net.URLConnection; +import java.net.UnknownHostException; +import java.net.UnknownServiceException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Scanner; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLHandshakeException; +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.CipherSuite; +import okhttp3.ConnectionSpec; +import okhttp3.Cookie; +import okhttp3.CookieJar; +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.TlsVersion; +import se.leap.bitmaskclient.ProviderListContent.ProviderItem; +import se.leap.bitmaskclient.eip.Constants; +import se.leap.bitmaskclient.eip.EIP; import se.leap.bitmaskclient.userstatus.SessionDialog; import se.leap.bitmaskclient.userstatus.User; import se.leap.bitmaskclient.userstatus.UserStatus; +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_json_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.malformed_url; +import static se.leap.bitmaskclient.R.string.server_unreachable_message; +import static se.leap.bitmaskclient.R.string.service_is_down_error; + /** * Implements HTTP api methods used to manage communications with the provider server. * <p/> @@ -79,7 +137,8 @@ public class ProviderAPI extends IntentService { PROVIDER_OK = 11, PROVIDER_NOK = 12, CORRECTLY_DOWNLOADED_EIP_SERVICE = 13, - INCORRECTLY_DOWNLOADED_EIP_SERVICE = 14; + INCORRECTLY_DOWNLOADED_EIP_SERVICE = 14, + INITIALIZATION_ERROR = 15; private static boolean CA_CERT_DOWNLOADED = false, @@ -93,11 +152,15 @@ public class ProviderAPI extends IntentService { private static String provider_api_url; private static String provider_ca_cert_fingerprint; private Resources resources; - public static void stop() { go_ahead = false; } + private final MediaType JSON + = MediaType.parse("application/json; charset=utf-8"); + private OkHttpClient okHttpClient; + private String initializationError = null; + public ProviderAPI() { super(TAG); } @@ -106,10 +169,9 @@ public class ProviderAPI extends IntentService { public void onCreate() { super.onCreate(); - preferences = getSharedPreferences(Dashboard.SHARED_PREFERENCES, MODE_PRIVATE); resources = getResources(); - CookieHandler.setDefault(new CookieManager(null, CookiePolicy.ACCEPT_ORIGINAL_SERVER)); + initOkHttpClient(); } public static String lastProviderMainUrl() { @@ -120,13 +182,19 @@ public class ProviderAPI extends IntentService { return last_danger_on; } - private String formatErrorMessage(final int toast_string_id) { - return "{ \"" + ERRORS + "\" : \"" + getResources().getString(toast_string_id) + "\" }"; - } @Override protected void onHandleIntent(Intent command) { final ResultReceiver receiver = command.getParcelableExtra(RECEIVER_KEY); + + if (initializationError != null) { + Bundle result = new Bundle(); + result.putString(SessionDialog.ERRORS.INITIALIZATION_ERROR.toString(), initializationError); + result.putBoolean(RESULT_KEY, false); + receiver.send(INITIALIZATION_ERROR, result); + return; + } + String action = command.getAction(); Bundle parameters = command.getBundleExtra(PARAMETERS); if (provider_api_url == null && preferences.contains(Provider.KEY)) { @@ -190,6 +258,96 @@ public class ProviderAPI extends IntentService { } } + private String formatErrorMessage(final int toastStringId) { + return "{ \"" + ERRORS + "\" : \"" + getResources().getString(toastStringId) + "\" }"; + } + + private JSONObject getErrorMessageAsJson(final int toastStringId) { + try { + return new JSONObject(formatErrorMessage(toastStringId)); + } catch (JSONException e) { + e.printStackTrace(); + return new JSONObject(); + } + } + + private void initOkHttpClient() { + try { + + ConnectionSpec spec = getConnectionSpec(); + OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); + initSSLSocketFactory(clientBuilder); + clientBuilder.cookieJar(getCookieJar()) + .connectionSpecs(Collections.singletonList(spec)); + okHttpClient = clientBuilder.build(); + + } catch (IllegalStateException e) { + e.printStackTrace(); + //initializationError = String.format(formatErrorMessage(keyChainAccessError), e.getLocalizedMessage()); + initializationError = String.format(getResources().getString(keyChainAccessError), e.getLocalizedMessage()); + } catch (KeyStoreException e) { + e.printStackTrace(); + //initializationError = String.format(formatErrorMessage(keyChainAccessError), e.getLocalizedMessage()); + initializationError = String.format(getResources().getString(keyChainAccessError), e.getLocalizedMessage()); + } catch (KeyManagementException e) { + e.printStackTrace(); + //initializationError = String.format(formatErrorMessage(keyChainAccessError), e.getLocalizedMessage()); + initializationError = String.format(getResources().getString(keyChainAccessError), e.getLocalizedMessage()); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + //initializationError = formatErrorMessage(error_no_such_algorithm_exception_user_message); + initializationError = getResources().getString(error_no_such_algorithm_exception_user_message); + } catch (CertificateException e) { + e.printStackTrace(); + initializationError = getResources().getString(certificate_error); + } catch (UnknownHostException e) { + e.printStackTrace(); + initializationError = getResources().getString(server_unreachable_message); + } catch (IOException e) { + e.printStackTrace(); + initializationError = getResources().getString(error_io_exception_user_message); + } catch (NoSuchProviderException e) { + e.printStackTrace(); + } + } + + @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<String, List<Cookie>> cookieStore = new HashMap<>(); + + @Override + public void saveFromResponse(HttpUrl url, List<Cookie> cookies) { + cookieStore.put(url.host(), cookies); + } + + @Override + public List<Cookie> loadForRequest(HttpUrl url) { + List<Cookie> cookies = cookieStore.get(url.host()); + return cookies != null ? cookies : new ArrayList<Cookie>(); + } + }; + } + + private Bundle tryToRegister(Bundle task) { Bundle result = new Bundle(); int progress = 0; @@ -310,20 +468,33 @@ public class ProviderAPI extends IntentService { } private Bundle authFailedNotification(JSONObject result, String username) { - Bundle user_notification_bundle = new Bundle(); - try { - JSONObject error_message = result.getJSONObject(ERRORS); - String error_type = error_message.keys().next().toString(); - String message = error_message.get(error_type).toString(); - user_notification_bundle.putString(getResources().getString(R.string.user_message), message); - } catch (JSONException e) { + Bundle userNotificationBundle = new Bundle(); + Object baseErrorMessage = result.opt(ERRORS); + if (baseErrorMessage != null) { + if (baseErrorMessage instanceof JSONObject) { + try { + JSONObject errorMessage = result.getJSONObject(ERRORS); + String errorType = errorMessage.keys().next().toString(); + String message = errorMessage.get(errorType).toString(); + userNotificationBundle.putString(getResources().getString(R.string.user_message), message); + } catch (JSONException e) { + e.printStackTrace(); + } + } else if (baseErrorMessage instanceof String) { + try { + String errorMessage = result.getString(ERRORS); + userNotificationBundle.putString(getResources().getString(R.string.user_message), errorMessage); + } catch (JSONException e) { + e.printStackTrace(); + } + } } if (!username.isEmpty()) - user_notification_bundle.putString(SessionDialog.USERNAME, username); - user_notification_bundle.putBoolean(RESULT_KEY, false); + userNotificationBundle.putString(SessionDialog.USERNAME, username); + userNotificationBundle.putBoolean(RESULT_KEY, false); - return user_notification_bundle; + return userNotificationBundle; } /** @@ -374,10 +545,8 @@ public class ProviderAPI extends IntentService { * @return response from authentication server */ private JSONObject sendAToSRPServer(String server_url, String username, String clientA) { - Map<String, String> parameters = new HashMap<String, String>(); - parameters.put("login", username); - parameters.put("A", clientA); - return sendToServer(server_url + "/sessions.json", "POST", parameters); + SrpCredentials srpCredentials = new SrpCredentials(username, clientA); + return sendToServer(server_url + "/sessions.json", "POST", srpCredentials.toString()); } /** @@ -389,10 +558,9 @@ public class ProviderAPI extends IntentService { * @return response from authentication server */ private JSONObject sendM1ToSRPServer(String server_url, String username, byte[] m1) { - Map<String, String> parameters = new HashMap<String, String>(); - parameters.put("client_auth", new BigInteger(1, ConfigHelper.trim(m1)).toString(16)); - return sendToServer(server_url + "/sessions/" + username + ".json", "PUT", parameters); + String m1json = "{\"client_auth\":\"" + new BigInteger(1, ConfigHelper.trim(m1)).toString(16)+ "\"}"; + return sendToServer(server_url + "/sessions/" + username + ".json", "PUT", m1json); } /** @@ -405,100 +573,52 @@ public class ProviderAPI extends IntentService { * @return response from authentication server */ private JSONObject sendNewUserDataToSRPServer(String server_url, String username, String salt, String password_verifier) { - Map<String, String> parameters = new HashMap<String, String>(); - parameters.put("user[login]", username); - parameters.put("user[password_salt]", salt); - parameters.put("user[password_verifier]", password_verifier); - return sendToServer(server_url + "/users.json", "POST", parameters); + return sendToServer(server_url + "/users.json", "POST", new SrpRegistrationData(username, salt, password_verifier).toString()); } - /** - * Executes an HTTP request expecting a JSON response. - * - * @param url - * @param request_method - * @param parameters - * @return response from authentication server - */ - private JSONObject sendToServer(String url, String request_method, Map<String, String> parameters) { - JSONObject json_response; - HttpsURLConnection urlConnection = null; - try { - InputStream is = null; - urlConnection = (HttpsURLConnection) new URL(url).openConnection(); - urlConnection.setRequestMethod(request_method); - String locale = Locale.getDefault().getLanguage() + Locale.getDefault().getCountry(); - urlConnection.setRequestProperty("Accept-Language", locale); - urlConnection.setChunkedStreamingMode(0); - urlConnection.setSSLSocketFactory(getProviderSSLSocketFactory()); + private JSONObject sendToServer(String url, String request_method, String jsonString) { + Response response; + JSONObject responseJson = new JSONObject(); - DataOutputStream writer = new DataOutputStream(urlConnection.getOutputStream()); - writer.writeBytes(formatHttpParameters(parameters)); - writer.close(); + RequestBody jsonBody = RequestBody.create(JSON, jsonString); - is = urlConnection.getInputStream(); - String plain_response = new Scanner(is).useDelimiter("\\A").next(); - json_response = new JSONObject(plain_response); - } catch (IOException e) { - json_response = getErrorMessage(urlConnection); - e.printStackTrace(); - } catch (JSONException e) { - json_response = getErrorMessage(urlConnection); - e.printStackTrace(); - } catch (NoSuchAlgorithmException e) { - json_response = getErrorMessage(urlConnection); - e.printStackTrace(); - } catch (KeyManagementException e) { - json_response = getErrorMessage(urlConnection); - e.printStackTrace(); - } catch (KeyStoreException e) { - json_response = getErrorMessage(urlConnection); - e.printStackTrace(); - } catch (CertificateException e) { - json_response = getErrorMessage(urlConnection); - e.printStackTrace(); - } + Request request = new Request.Builder() + .url(url) + .method(request_method, jsonBody) + .build(); - return json_response; - } + try { + response = okHttpClient.newCall(request).execute(); - private JSONObject getErrorMessage(HttpsURLConnection urlConnection) { - JSONObject error_message = new JSONObject(); - if (urlConnection != null) { - InputStream error_stream = urlConnection.getErrorStream(); - if (error_stream != null) { - String error_response = new Scanner(error_stream).useDelimiter("\\A").next(); - try { - error_message = new JSONObject(error_response); - } catch (JSONException e) { - e.printStackTrace(); - } - urlConnection.disconnect(); + InputStream inputStream = response.body().byteStream(); + Scanner scanner = new Scanner(inputStream).useDelimiter("\\A"); + if (scanner.hasNext()) { + String plain_response = scanner.next(); + responseJson = new JSONObject(plain_response); } - } - return error_message; - } - - private String formatHttpParameters(Map<String, String> parameters) throws UnsupportedEncodingException { - StringBuilder result = new StringBuilder(); - boolean first = true; - Iterator<String> parameter_iterator = parameters.keySet().iterator(); - while (parameter_iterator.hasNext()) { - if (first) - first = false; - else - result.append("&&"); - - String key = parameter_iterator.next(); - String value = parameters.get(key); - - result.append(URLEncoder.encode(key, "UTF-8")); - result.append("="); - result.append(URLEncoder.encode(value, "UTF-8")); + } catch (JSONException e) { + responseJson = getErrorMessageAsJson(error_json_exception_user_message); + } catch (NullPointerException npe) { + responseJson = getErrorMessageAsJson(error_json_exception_user_message); + } catch (UnknownHostException e) { + responseJson = getErrorMessageAsJson(server_unreachable_message); + } catch (MalformedURLException e) { + responseJson = getErrorMessageAsJson(malformed_url); + } catch (SocketTimeoutException e) { + responseJson = getErrorMessageAsJson(server_unreachable_message); + } catch (SSLHandshakeException e) { + responseJson = getErrorMessageAsJson(certificate_error); + } catch (ConnectException e) { + responseJson = getErrorMessageAsJson(service_is_down_error); + } catch (UnknownServiceException e) { + //unable to find acceptable protocols - tlsv1.2 not enabled? + responseJson = getErrorMessageAsJson(error_no_such_algorithm_exception_user_message); + } catch (IOException e) { + responseJson = getErrorMessageAsJson(error_io_exception_user_message); } - return result.toString(); + return responseJson; } @@ -561,7 +681,7 @@ public class ProviderAPI extends IntentService { result.putBoolean(RESULT_KEY, false); } } catch (JSONException e) { - String reason_to_fail = formatErrorMessage(R.string.malformed_url); + String reason_to_fail = formatErrorMessage(malformed_url); result.putString(ERRORS, reason_to_fail); result.putBoolean(RESULT_KEY, false); } @@ -705,7 +825,7 @@ public class ProviderAPI extends IntentService { result = danger_on ? downloadWithoutCA(url_string) : formatErrorMessage(R.string.error_security_pinnedcertificate); } else - result = formatErrorMessage(R.string.error_io_exception_user_message); + result = formatErrorMessage(error_io_exception_user_message); } return result; @@ -735,19 +855,19 @@ public class ProviderAPI extends IntentService { url_connection.addRequestProperty(LeapSRPSession.AUTHORIZATION_HEADER, "Token token = " + LeapSRPSession.getToken()); json_file_content = new Scanner(url_connection.getInputStream()).useDelimiter("\\A").next(); } catch (MalformedURLException e) { - json_file_content = formatErrorMessage(R.string.malformed_url); + json_file_content = formatErrorMessage(malformed_url); } catch (SocketTimeoutException e) { - json_file_content = formatErrorMessage(R.string.server_unreachable_message); + json_file_content = formatErrorMessage(server_unreachable_message); } catch (SSLHandshakeException e) { if (provider_url != null) { json_file_content = downloadWithProviderCA(string_url, danger_on); } else { - json_file_content = formatErrorMessage(R.string.certificate_error); + json_file_content = formatErrorMessage(certificate_error); } } catch (ConnectException e) { - json_file_content = formatErrorMessage(R.string.service_is_down_error); + json_file_content = formatErrorMessage(service_is_down_error); } catch (FileNotFoundException e) { - json_file_content = formatErrorMessage(R.string.malformed_url); + json_file_content = formatErrorMessage(malformed_url); } catch (Exception e) { if (provider_url != null && danger_on) { json_file_content = downloadWithProviderCA(string_url, danger_on); @@ -764,6 +884,7 @@ public class ProviderAPI extends IntentService { * @param danger_on true to download CA certificate in case it has not been downloaded. * @return an empty string if it fails, the url content if not. */ + //FIXME: refactor and use okHttpClient instead! private String downloadWithProviderCA(String url_string, boolean danger_on) { String json_file_content = ""; @@ -781,13 +902,13 @@ public class ProviderAPI extends IntentService { e.printStackTrace(); } catch (UnknownHostException e) { e.printStackTrace(); - json_file_content = formatErrorMessage(R.string.server_unreachable_message); + json_file_content = formatErrorMessage(server_unreachable_message); } catch (IOException e) { // The downloaded certificate doesn't validate our https connection. if (danger_on) { json_file_content = downloadWithoutCA(url_string); } else { - json_file_content = formatErrorMessage(R.string.certificate_error); + json_file_content = formatErrorMessage(certificate_error); } } catch (KeyStoreException e) { // TODO Auto-generated catch block @@ -800,11 +921,12 @@ public class ProviderAPI extends IntentService { e.printStackTrace(); } catch (NoSuchElementException e) { e.printStackTrace(); - json_file_content = formatErrorMessage(R.string.server_unreachable_message); + json_file_content = formatErrorMessage(server_unreachable_message); } return json_file_content; } + @Deprecated private javax.net.ssl.SSLSocketFactory getProviderSSLSocketFactory() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, KeyManagementException { String provider_cert_string = preferences.getString(Provider.CA_CERT, ""); @@ -828,9 +950,15 @@ public class ProviderAPI extends IntentService { return context.getSocketFactory(); } + private void initSSLSocketFactory(OkHttpClient.Builder builder) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, KeyManagementException, IllegalStateException, NoSuchProviderException { + TLSCompatSocketFactory sslCompatFactory = new TLSCompatSocketFactory(preferences.getString(Provider.CA_CERT, "")); + sslCompatFactory.initSSLSocketFactory(builder); + } + /** * Downloads the string that's in the url with any certificate. */ + // FIXME: refactor and use okHttpClient instead! private String downloadWithoutCA(String url_string) { String string = ""; try { @@ -869,11 +997,11 @@ public class ProviderAPI extends IntentService { System.out.println("String ignoring certificate = " + string); } catch (FileNotFoundException e) { e.printStackTrace(); - string = formatErrorMessage(R.string.malformed_url); + string = formatErrorMessage(malformed_url); } catch (IOException e) { // The downloaded certificate doesn't validate our https connection. e.printStackTrace(); - string = formatErrorMessage(R.string.certificate_error); + string = formatErrorMessage(certificate_error); } catch (NoSuchAlgorithmException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -889,6 +1017,7 @@ public class ProviderAPI extends IntentService { * * @return true if there were no exceptions */ + //FIXME: refactor and use okHttpClient instead! private boolean logOut() { String delete_url = provider_api_url + "/logout"; diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 67fd0a1a..d751d9f0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -27,10 +27,11 @@ android:maxSdkVersion="18"/> <uses-sdk - android:minSdkVersion="14" + android:minSdkVersion="16" android:targetSdkVersion="26"/> <application + android:name=".BitmaskApp" android:allowBackup="true" android:icon="@drawable/icon" android:logo="@drawable/icon" diff --git a/app/src/main/java/se/leap/bitmaskclient/BitmaskApp.java b/app/src/main/java/se/leap/bitmaskclient/BitmaskApp.java new file mode 100644 index 00000000..953a559d --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/BitmaskApp.java @@ -0,0 +1,17 @@ +package se.leap.bitmaskclient; + +import android.app.Application; + +/** + * Created by cyberta on 24.10.17. + */ + +public class BitmaskApp extends Application { + + @Override + public void onCreate() { + super.onCreate(); + PRNGFixes.apply(); + //TODO: add LeakCanary! + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java index a6a3717b..9fc7d593 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java +++ b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java @@ -32,24 +32,36 @@ */ package se.leap.bitmaskclient; -import android.annotation.*; -import android.app.*; -import android.content.*; -import android.content.pm.PackageManager.*; -import android.os.*; -import android.util.*; -import android.view.*; -import android.widget.*; - -import org.jetbrains.annotations.*; -import org.json.*; - -import java.net.*; - -import butterknife.*; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.FragmentTransaction; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Bundle; +import android.os.Handler; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.widget.TextView; + +import org.jetbrains.annotations.NotNull; +import org.json.JSONException; +import org.json.JSONObject; + +import java.net.MalformedURLException; +import java.net.URL; + +import butterknife.ButterKnife; +import butterknife.InjectView; import de.blinkt.openvpn.core.VpnStatus; -import se.leap.bitmaskclient.eip.*; -import se.leap.bitmaskclient.userstatus.*; +import se.leap.bitmaskclient.eip.Constants; +import se.leap.bitmaskclient.userstatus.SessionDialog; +import se.leap.bitmaskclient.userstatus.User; +import se.leap.bitmaskclient.userstatus.UserStatusFragment; /** * The main user facing Activity of Bitmask Android, consisting of status, controls, @@ -99,7 +111,6 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec if (app == null) { app = this; - PRNGFixes.apply(); VpnStatus.initLogCache(getApplicationContext().getCacheDir()); handleVersion(); User.init(getString(R.string.default_username)); @@ -352,7 +363,9 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec @Override public void onReceiveResult(int resultCode, Bundle resultData) { - if (resultCode == ProviderAPI.SUCCESSFUL_SIGNUP) { + if (resultCode == ProviderAPI.INITIALIZATION_ERROR) { + sessionDialog(resultData); + } else if (resultCode == ProviderAPI.SUCCESSFUL_SIGNUP) { String username = resultData.getString(SessionDialog.USERNAME); String password = resultData.getString(SessionDialog.PASSWORD); user_status_fragment.logIn(username, password); diff --git a/app/src/main/java/se/leap/bitmaskclient/SrpCredentials.java b/app/src/main/java/se/leap/bitmaskclient/SrpCredentials.java new file mode 100644 index 00000000..c1815ac5 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/SrpCredentials.java @@ -0,0 +1,26 @@ +package se.leap.bitmaskclient; + +import com.google.gson.Gson; + +/** + * Created by cyberta on 23.10.17. + */ + +public class SrpCredentials { + + /** + * Parameter A of SRP authentication + */ + private String A; + private String login; + + public SrpCredentials(String username, String A) { + this.login = username; + this.A = A; + } + + @Override + public String toString() { + return new Gson().toJson(this); + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/SrpRegistrationData.java b/app/src/main/java/se/leap/bitmaskclient/SrpRegistrationData.java new file mode 100644 index 00000000..d4e00308 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/SrpRegistrationData.java @@ -0,0 +1,42 @@ +package se.leap.bitmaskclient; + +import com.google.gson.Gson; + +/** + * Created by cyberta on 23.10.17. + */ + +public class SrpRegistrationData { + + + private User user; + + public SrpRegistrationData(String username, String passwordSalt, String passwordVerifier) { + user = new User(username, passwordSalt, passwordVerifier); + } + + + @Override + public String toString() { + return new Gson().toJson(this); + } + + + public class User { + + String login; + String password_salt; + String password_verifier; + + public User(String login, String password_salt, String password_verifier) { + this.login = login; + this.password_salt = password_salt; + this.password_verifier = password_verifier; + } + + @Override + public String toString() { + return new Gson().toJson(this); + } + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/TLSCompatSocketFactory.java b/app/src/main/java/se/leap/bitmaskclient/TLSCompatSocketFactory.java new file mode 100644 index 00000000..fdad6ba9 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/TLSCompatSocketFactory.java @@ -0,0 +1,133 @@ +package se.leap.bitmaskclient; + +import android.util.Log; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.cert.CertificateException; +import java.util.Arrays; + +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.OkHttpClient; + +/** + * Created by cyberta on 24.10.17. + * This class ensures that modern TLS algorithms will also be used on old devices (Android 4.1 - Android 4.4.4) in order to avoid + * attacks like POODLE. + */ + +public class TLSCompatSocketFactory extends SSLSocketFactory { + + private static final String TAG = TLSCompatSocketFactory.class.getName(); + private SSLSocketFactory internalSSLSocketFactory; + private SSLContext sslContext; + private TrustManager trustManager; + + public TLSCompatSocketFactory(String trustedCaCert) throws KeyManagementException, NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException, NoSuchProviderException { + + initTrustManager(trustedCaCert); + internalSSLSocketFactory = sslContext.getSocketFactory(); + + } + + public void initSSLSocketFactory(OkHttpClient.Builder builder) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, KeyManagementException, IllegalStateException { + builder.sslSocketFactory(this, (X509TrustManager) trustManager); + } + + + private void initTrustManager(String trustedCaCert) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, IllegalStateException, KeyManagementException, NoSuchProviderException { + java.security.cert.Certificate provider_certificate = ConfigHelper.parseX509CertificateFromString(trustedCaCert); + + // Create a KeyStore containing our trusted CAs + String defaultType = KeyStore.getDefaultType(); + KeyStore keyStore = KeyStore.getInstance(defaultType); + keyStore.load(null, null); + keyStore.setCertificateEntry("provider_ca_certificate", provider_certificate); + + // Create a TrustManager that trusts the CAs in our KeyStore + String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm); + tmf.init(keyStore); + + // Check if there's only 1 X509Trustmanager -> from okttp3 source code example + TrustManager[] trustManagers = tmf.getTrustManagers(); + if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { + throw new IllegalStateException("Unexpected default trust managers:" + + Arrays.toString(trustManagers)); + } + + trustManager = trustManagers[0]; + + // Create an SSLContext that uses our TrustManager + sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, tmf.getTrustManagers(), null); + + } + + + @Override + public String[] getDefaultCipherSuites() { + return internalSSLSocketFactory.getDefaultCipherSuites(); + } + + @Override + public String[] getSupportedCipherSuites() { + return internalSSLSocketFactory.getSupportedCipherSuites(); + } + + @Override + public Socket createSocket() throws IOException { + return enableTLSOnSocket(internalSSLSocketFactory.createSocket()); + } + + @Override + public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { + return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose)); + } + + @Override + public Socket createSocket(String host, int port) throws IOException, UnknownHostException { + return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port)); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { + return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort)); + } + + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port)); + } + + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { + return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort)); + } + + private Socket enableTLSOnSocket(Socket socket) { + if(socket != null && (socket instanceof SSLSocket)) { + ((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.2"}); + ((SSLSocket)socket).setEnabledCipherSuites(getSupportedCipherSuites()); + } + return socket; + + + } + + + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/userstatus/SessionDialog.java b/app/src/main/java/se/leap/bitmaskclient/userstatus/SessionDialog.java index 7dbbe059..88dec39b 100644 --- a/app/src/main/java/se/leap/bitmaskclient/userstatus/SessionDialog.java +++ b/app/src/main/java/se/leap/bitmaskclient/userstatus/SessionDialog.java @@ -23,10 +23,13 @@ import android.view.*; import android.widget.*; import butterknife.*; +import se.leap.bitmaskclient.ProviderAPI; import se.leap.bitmaskclient.VpnFragment; import se.leap.bitmaskclient.Provider; import se.leap.bitmaskclient.R; +import static android.view.View.VISIBLE; + /** * Implements the log in dialog, currently without progress dialog. * <p/> @@ -47,7 +50,8 @@ public class SessionDialog extends DialogFragment { public static enum ERRORS { USERNAME_MISSING, PASSWORD_INVALID_LENGTH, - RISEUP_WARNING + RISEUP_WARNING, + INITIALIZATION_ERROR } @InjectView(R.id.user_message) @@ -117,8 +121,11 @@ public class SessionDialog extends DialogFragment { if (arguments.containsKey(ERRORS.PASSWORD_INVALID_LENGTH.toString())) password_field.setError(getString(R.string.error_not_valid_password_user_message)); else if (arguments.containsKey(ERRORS.RISEUP_WARNING.toString())) { - user_message.setVisibility(TextView.VISIBLE); + user_message.setVisibility(VISIBLE); user_message.setText(R.string.login_riseup_warning); + } else if (arguments.containsKey(ERRORS.INITIALIZATION_ERROR.toString())) { + user_message.setVisibility(VISIBLE); + user_message.setText(String.valueOf(arguments.get(ERRORS.INITIALIZATION_ERROR.toString()))); } if (arguments.containsKey(USERNAME)) { String username = arguments.getString(USERNAME); @@ -129,8 +136,8 @@ public class SessionDialog extends DialogFragment { } if (arguments.containsKey(getString(R.string.user_message))) { user_message.setText(arguments.getString(getString(R.string.user_message))); - user_message.setVisibility(View.VISIBLE); - } else if (user_message.getVisibility() != TextView.VISIBLE) + user_message.setVisibility(VISIBLE); + } else if (user_message.getVisibility() != VISIBLE) user_message.setVisibility(View.GONE); if (!username_field.getText().toString().isEmpty() && password_field.isFocusable()) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 7f0670b8..09bac1ef 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -38,7 +38,7 @@ <string name="error_client_http_user_message">Inténtalo de nuevo: error en el cliente HTTP</string> <string name="error_io_exception_user_message">Inténtalo de nuevo: error de E/S</string> <string name="error_json_exception_user_message">Inténtalo de nuevo: respuesta mal formada del servidor</string> - <string name="error_no_such_algorithm_exception_user_message">Actualiza Bitmask</string> + <string name="error_no_such_algorithm_exception_user_message">Algoritmo de cifrado no encontrado. Por favor actualice su sistema operativo!</string> <string name="signup_or_login_button">Registrarse/Iniciar sesión</string> <string name="login_button">Iniciar sesión</string> <string name="logout_button">Cerrar sesión</string> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 06c80f12..b1fce0ad 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -37,7 +37,7 @@ <string name="error_client_http_user_message">Try again: Client HTTP error</string> <string name="error_io_exception_user_message">Try again: I/O error</string> <string name="error_json_exception_user_message">Try again: Bad response from the server</string> - <string name="error_no_such_algorithm_exception_user_message">Update the app</string> + <string name="error_no_such_algorithm_exception_user_message">Encryption algorithm not found. Please update your OS!</string> <string name="signup_or_login_button">Sign Up/Log In</string> <string name="login_button">Log In</string> <string name="logout_button">Log Out</string> |