diff options
Diffstat (limited to 'app/src')
8 files changed, 159 insertions, 71 deletions
diff --git a/app/src/debug/java/se/leap/bitmaskclient/ConfigurationWizard.java b/app/src/debug/java/se/leap/bitmaskclient/ConfigurationWizard.java index 59d77d83..aac53a07 100644 --- a/app/src/debug/java/se/leap/bitmaskclient/ConfigurationWizard.java +++ b/app/src/debug/java/se/leap/bitmaskclient/ConfigurationWizard.java @@ -126,7 +126,7 @@ public class ConfigurationWizard extends Activity if (fragment_manager.findFragmentByTag(ProviderDetailFragment.TAG) == null && setting_up_provider) { if (selected_provider != null) - onItemSelectedUi(selected_provider); + onItemSelectedUi(); if (progress > 0) mProgressBar.setProgress(progress); } @@ -231,20 +231,18 @@ public class ConfigurationWizard extends Activity void onItemSelected(int position) { //TODO Code 2 pane view selected_provider = adapter.getItem(position); - onItemSelectedLogic(selected_provider); - onItemSelectedUi(selected_provider); + onItemSelectedLogic(); + onItemSelectedUi(); } - private void onItemSelectedLogic(Provider selected_provider) { - boolean danger_on = true; - if (preferences.contains(ProviderItem.DANGER_ON)) - danger_on = preferences.getBoolean(ProviderItem.DANGER_ON, false); - setUpProvider(selected_provider.mainUrl(), danger_on); + private void onItemSelectedLogic() { + boolean danger_on = preferences.getBoolean(ProviderItem.DANGER_ON, true); + setUpProvider(danger_on); } - private void onItemSelectedUi(Provider provider) { + private void onItemSelectedUi() { startProgressBar(); - adapter.hideAllBut(adapter.indexOf(provider)); + adapter.hideAllBut(adapter.indexOf(selected_provider)); } @Override @@ -383,21 +381,21 @@ public class ConfigurationWizard extends Activity private void autoSelectProvider(Provider provider, boolean danger_on) { preferences.edit().putBoolean(ProviderItem.DANGER_ON, danger_on).apply(); selected_provider = provider; - onItemSelectedLogic(selected_provider); - onItemSelectedUi(selected_provider); + onItemSelectedLogic(); + onItemSelectedUi(); } /** * Asks ProviderAPI to download a new provider.json file * - * @param provider_main_url * @param danger_on tells if HTTPS client should bypass certificate errors */ - public void setUpProvider(URL provider_main_url, boolean danger_on) { + public void setUpProvider(boolean danger_on) { Intent provider_API_command = new Intent(this, ProviderAPI.class); Bundle parameters = new Bundle(); - parameters.putString(Provider.MAIN_URL, provider_main_url.toString()); + parameters.putString(Provider.MAIN_URL, selected_provider.mainUrl().toString()); parameters.putBoolean(ProviderItem.DANGER_ON, danger_on); + parameters.putString(Provider.CA_CERT_FINGERPRINT, selected_provider.certificatePin()); provider_API_command.setAction(ProviderAPI.SET_UP_PROVIDER); provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); diff --git a/app/src/debug/java/se/leap/bitmaskclient/ProviderAPI.java b/app/src/debug/java/se/leap/bitmaskclient/ProviderAPI.java index 9b5601a9..6c57fca2 100644 --- a/app/src/debug/java/se/leap/bitmaskclient/ProviderAPI.java +++ b/app/src/debug/java/se/leap/bitmaskclient/ProviderAPI.java @@ -22,6 +22,10 @@ import android.content.res.*; import android.os.*; import android.util.*; +import org.apache.http.client.*; +import org.json.*; +import org.thoughtcrime.ssl.pinning.util.*; + import java.io.*; import java.math.*; import java.net.*; @@ -32,10 +36,7 @@ import java.util.*; import javax.net.ssl.*; -import org.apache.http.client.*; -import org.json.*; - -import se.leap.bitmaskclient.ProviderListContent.ProviderItem; +import se.leap.bitmaskclient.ProviderListContent.*; import se.leap.bitmaskclient.eip.*; /** @@ -88,6 +89,7 @@ public class ProviderAPI extends IntentService { private static boolean go_ahead = true; private static SharedPreferences preferences; private static String provider_api_url; + private static String provider_ca_cert_fingerprint; private Resources resources; public static void stop() { @@ -102,6 +104,7 @@ 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)); @@ -124,7 +127,7 @@ public class ProviderAPI extends IntentService { final ResultReceiver receiver = command.getParcelableExtra(RECEIVER_KEY); String action = command.getAction(); Bundle parameters = command.getBundleExtra(PARAMETERS); - if (provider_api_url == null) { + if (provider_api_url == null && preferences.contains(Provider.KEY)) { try { JSONObject provider_json = new JSONObject(preferences.getString(Provider.KEY, "no provider")); provider_api_url = provider_json.getString(Provider.API_URL) + "/" + provider_json.getString(Provider.API_VERSION); @@ -136,12 +139,10 @@ public class ProviderAPI extends IntentService { if (action.equalsIgnoreCase(SET_UP_PROVIDER)) { Bundle result = setUpProvider(parameters); - if (go_ahead) { - if (result.getBoolean(RESULT_KEY)) { - receiver.send(PROVIDER_OK, result); - } else { - receiver.send(PROVIDER_NOK, result); - } + if (result.getBoolean(RESULT_KEY)) { + receiver.send(PROVIDER_OK, result); + } else { + receiver.send(PROVIDER_NOK, result); } } else if (action.equalsIgnoreCase(SIGN_UP)) { UserSessionStatus.updateStatus(UserSessionStatus.SessionStatus.SIGNING_UP, resources); @@ -511,15 +512,20 @@ public class ProviderAPI extends IntentService { int progress = 0; Bundle current_download = new Bundle(); - if (task != null && task.containsKey(ProviderItem.DANGER_ON) && task.containsKey(Provider.MAIN_URL)) { - last_danger_on = task.getBoolean(ProviderItem.DANGER_ON); - last_provider_main_url = task.getString(Provider.MAIN_URL); + if (task != null) { + last_danger_on = task.containsKey(ProviderItem.DANGER_ON) && task.getBoolean(ProviderItem.DANGER_ON); + last_provider_main_url = task.containsKey(Provider.MAIN_URL) ? + task.getString(Provider.MAIN_URL) : + ""; + provider_ca_cert_fingerprint = task.containsKey(Provider.CA_CERT_FINGERPRINT) ? + task.getString(Provider.CA_CERT_FINGERPRINT) : + ""; CA_CERT_DOWNLOADED = PROVIDER_JSON_DOWNLOADED = EIP_SERVICE_JSON_DOWNLOADED = false; go_ahead = true; } if (!PROVIDER_JSON_DOWNLOADED) - current_download = getAndSetProviderJson(last_provider_main_url, last_danger_on); + current_download = getAndSetProviderJson(last_provider_main_url, last_danger_on, provider_ca_cert_fingerprint); if (PROVIDER_JSON_DOWNLOADED || (current_download.containsKey(RESULT_KEY) && current_download.getBoolean(RESULT_KEY))) { broadcastProgress(progress++); PROVIDER_JSON_DOWNLOADED = true; @@ -608,11 +614,15 @@ public class ProviderAPI extends IntentService { return hexData.toString(); } - private Bundle getAndSetProviderJson(String provider_main_url, boolean danger_on) { + private Bundle getAndSetProviderJson(String provider_main_url, boolean danger_on, String provider_ca_cert_fingerprint) { Bundle result = new Bundle(); if (go_ahead) { - String provider_dot_json_string = downloadWithCommercialCA(provider_main_url + "/provider.json", danger_on); + String provider_dot_json_string; + if(provider_ca_cert_fingerprint.isEmpty()) + provider_dot_json_string = downloadWithCommercialCA(provider_main_url + "/provider.json", danger_on); + else + provider_dot_json_string = downloadWithCommercialCA(provider_main_url + "/provider.json", danger_on, provider_ca_cert_fingerprint); try { JSONObject provider_json = new JSONObject(provider_dot_json_string); @@ -678,6 +688,29 @@ public class ProviderAPI extends IntentService { return error_message; } + private String downloadWithCommercialCA(String url_string, boolean danger_on, String ca_cert_fingerprint) { + String result = ""; + + int seconds_of_timeout = 2; + String[] pins = new String[] {ca_cert_fingerprint}; + try { + URL url = new URL(url_string); + HttpsURLConnection connection = PinningHelper.getPinnedHttpsURLConnection(Dashboard.getContext(), pins, url); + connection.setConnectTimeout(seconds_of_timeout * 1000); + if (!LeapSRPSession.getToken().isEmpty()) + connection.addRequestProperty(LeapSRPSession.AUTHORIZATION_HEADER, "Token token = " + LeapSRPSession.getToken()); + result = new Scanner(connection.getInputStream()).useDelimiter("\\A").next(); + } catch (IOException e) { + if(e instanceof SSLHandshakeException) { + result = danger_on ? downloadWithoutCA(url_string) : + formatErrorMessage(R.string.error_security_pinnedcertificate); + } else + result = formatErrorMessage(R.string.error_io_exception_user_message); + } + + return result; + } + /** * Tries to download the contents of the provided url using commercially validated CA certificate from chosen provider. * <p/> @@ -696,6 +729,7 @@ public class ProviderAPI extends IntentService { try { provider_url = new URL(string_url); URLConnection url_connection = provider_url.openConnection(); + url_connection.setConnectTimeout(seconds_of_timeout * 1000); if (!LeapSRPSession.getToken().isEmpty()) url_connection.addRequestProperty(LeapSRPSession.AUTHORIZATION_HEADER, "Token token = " + LeapSRPSession.getToken()); diff --git a/app/src/main/java/se/leap/bitmaskclient/Provider.java b/app/src/main/java/se/leap/bitmaskclient/Provider.java index ee06a586..a030927d 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Provider.java +++ b/app/src/main/java/se/leap/bitmaskclient/Provider.java @@ -32,6 +32,7 @@ public final class Provider implements Parcelable { private JSONObject definition; // Represents our Provider's provider.json private URL main_url; + private String certificate_pin = ""; final public static String API_URL = "api_uri", @@ -62,8 +63,9 @@ public final class Provider implements Parcelable { this.main_url = main_url; } - public Provider(File provider_file) { - + public Provider(URL main_url, String certificate_pin) { + this.main_url = main_url; + this.certificate_pin = certificate_pin; } public static final Parcelable.Creator<Provider> CREATOR @@ -81,11 +83,9 @@ public final class Provider implements Parcelable { try { main_url = new URL(in.readString()); String definition_string = in.readString(); - if (definition_string != null) + if (!definition_string.isEmpty()) definition = new JSONObject((definition_string)); - } catch (MalformedURLException e) { - e.printStackTrace(); - } catch (JSONException e) { + } catch (MalformedURLException | JSONException e) { e.printStackTrace(); } } @@ -106,6 +106,8 @@ public final class Provider implements Parcelable { return main_url; } + protected String certificatePin() { return certificate_pin; } + protected String getName() { // Should we pass the locale in, or query the system here? String lang = Locale.getDefault().getLanguage(); diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java b/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java index 40fe8b5a..220a71c8 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java @@ -49,11 +49,14 @@ public class ProviderManager implements AdapteeCollection<Provider> { Set<Provider> providers = new HashSet<Provider>(); try { for (String file : relative_file_paths) { - String main_url = extractMainUrlFromInputStream(assets_manager.open(directory + "/" + file)); - providers.add(new Provider(new URL(main_url))); + InputStream provider_file = assets_manager.open(directory + "/" + file); + String main_url = extractMainUrlFromInputStream(provider_file); + String certificate_pin = extractCertificatePinFromInputStream(provider_file); + if(certificate_pin.isEmpty()) + providers.add(new Provider(new URL(main_url))); + else + providers.add(new Provider(new URL(main_url), certificate_pin)); } - } catch (MalformedURLException e) { - e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } @@ -75,30 +78,43 @@ public class ProviderManager implements AdapteeCollection<Provider> { String main_url = extractMainUrlFromInputStream(new FileInputStream(external_files_dir.getAbsolutePath() + "/" + file)); providers.add(new Provider(new URL(main_url))); } - } catch (MalformedURLException e) { - e.printStackTrace(); - } catch (FileNotFoundException e) { + } catch (MalformedURLException | FileNotFoundException e) { e.printStackTrace(); } return providers; } - private String extractMainUrlFromInputStream(InputStream input_stream_file_contents) { + private String extractMainUrlFromInputStream(InputStream input_stream) { String main_url = ""; - byte[] bytes = new byte[0]; + + JSONObject file_contents = inputStreamToJson(input_stream); + if(file_contents != null) + main_url = file_contents.optString(Provider.MAIN_URL); + return main_url; + } + + private String extractCertificatePinFromInputStream(InputStream input_stream) { + String certificate_pin = ""; + + JSONObject file_contents = inputStreamToJson(input_stream); + if(file_contents != null) + certificate_pin = file_contents.optString(Provider.CA_CERT_FINGERPRINT); + + return certificate_pin; + } + + private JSONObject inputStreamToJson(InputStream input_stream) { + JSONObject json = null; try { - bytes = new byte[input_stream_file_contents.available()]; - if (input_stream_file_contents.read(bytes) > 0) { - JSONObject file_contents = new JSONObject(new String(bytes)); - main_url = file_contents.getString(Provider.MAIN_URL); - } - } catch (IOException e) { - e.printStackTrace(); - } catch (JSONException e) { + byte[] bytes = new byte[input_stream.available()]; + if (input_stream.read(bytes) > 0) + json = new JSONObject(new String(bytes)); + input_stream.reset(); + } catch (IOException | JSONException e) { e.printStackTrace(); } - return main_url; + return json; } public Set<Provider> providers() { diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 7ab5150e..82ca44e9 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -48,6 +48,7 @@ <string name="setup_error_close_button">Salir</string> <string name="setup_error_text">Sucedió un error configurando Bitmask con tu proveedor elegido.\n\nPuedes volver a intentarlo, o elegir otro proveedor.</string> <string name="server_unreachable_message">No se ha detectado red para hablar con el servidor, inténtalo de nuevo.</string> + <string name="error.security.pinnedcertificate">Error de seguridad, actualiza la aplicación o elige otro proveedor.</string> <string name="malformed_url">No parece que sea un proveedor de Bitmask.</string> <string name="certificate_error">No es un proveedor de Bitmak de confianza.</string> <string name="service_is_down_error">El servicio está caído.</string> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ac6191a9..bcfd3a2c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -47,6 +47,7 @@ <string name="setup_error_close_button">Exit</string> <string name="setup_error_text">There was an error configuring Bitmask with your chosen provider.\n\nYou may choose to reconfigure, or exit and configure a provider upon next launch.</string> <string name="server_unreachable_message">Server is unreachable, please try again.</string> + <string name="error.security.pinnedcertificate">Security error, update the app or choose another provider.</string> <string name="malformed_url">It doesn\'t seem to be a Bitmask provider.</string> <string name="certificate_error">This is not a trusted Bitmask provider.</string> <string name="service_is_down_error">Service is down.</string> diff --git a/app/src/release/java/se/leap/bitmaskclient/ConfigurationWizard.java b/app/src/release/java/se/leap/bitmaskclient/ConfigurationWizard.java index 19ba1ba8..68ff9e47 100644 --- a/app/src/release/java/se/leap/bitmaskclient/ConfigurationWizard.java +++ b/app/src/release/java/se/leap/bitmaskclient/ConfigurationWizard.java @@ -126,7 +126,7 @@ public class ConfigurationWizard extends Activity if (fragment_manager.findFragmentByTag(ProviderDetailFragment.TAG) == null && setting_up_provider) { if (selected_provider != null) - onItemSelectedUi(selected_provider); + onItemSelectedUi(); if (progress > 0) mProgressBar.setProgress(progress); } @@ -229,17 +229,17 @@ public class ConfigurationWizard extends Activity void onItemSelected(int position) { //TODO Code 2 pane view selected_provider = adapter.getItem(position); - onItemSelectedUi(selected_provider); - onItemSelectedLogic(selected_provider); + onItemSelectedUi(); + onItemSelectedLogic(); } - private void onItemSelectedLogic(Provider selected_provider) { - setUpProvider(selected_provider.mainUrl()); + private void onItemSelectedLogic() { + setUpProvider(); } - private void onItemSelectedUi(Provider provider) { + private void onItemSelectedUi() { startProgressBar(); - adapter.hideAllBut(adapter.indexOf(provider)); + adapter.hideAllBut(adapter.indexOf(selected_provider)); } @Override @@ -379,8 +379,8 @@ public class ConfigurationWizard extends Activity private void autoSelectProvider(Provider provider) { selected_provider = provider; - onItemSelectedUi(selected_provider); - onItemSelectedLogic(selected_provider); + onItemSelectedUi(); + onItemSelectedLogic(); } /** @@ -389,10 +389,11 @@ public class ConfigurationWizard extends Activity * @param provider_name * @param provider_main_url */ - public void setUpProvider(URL provider_main_url) { + public void setUpProvider() { Intent provider_API_command = new Intent(this, ProviderAPI.class); Bundle parameters = new Bundle(); - parameters.putString(Provider.MAIN_URL, provider_main_url.toString()); + parameters.putString(Provider.MAIN_URL, selected_provider.mainUrl().toString()); + parameters.putString(Provider.CA_CERT_FINGERPRINT, selected_provider.certificatePin()); provider_API_command.setAction(ProviderAPI.SET_UP_PROVIDER); provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); diff --git a/app/src/release/java/se/leap/bitmaskclient/ProviderAPI.java b/app/src/release/java/se/leap/bitmaskclient/ProviderAPI.java index 890262ce..a96556bc 100644 --- a/app/src/release/java/se/leap/bitmaskclient/ProviderAPI.java +++ b/app/src/release/java/se/leap/bitmaskclient/ProviderAPI.java @@ -34,6 +34,7 @@ import javax.net.ssl.*; import org.apache.http.client.*; import org.json.*; +import org.thoughtcrime.ssl.pinning.util.*; import se.leap.bitmaskclient.ProviderListContent.ProviderItem; import se.leap.bitmaskclient.eip.*; @@ -87,6 +88,7 @@ public class ProviderAPI extends IntentService { private static boolean go_ahead = true; private static SharedPreferences preferences; private static String provider_api_url; + private static String provider_ca_cert_fingerprint; private Resources resources; public static void stop() { @@ -504,13 +506,19 @@ public class ProviderAPI extends IntentService { Bundle current_download = new Bundle(); if (task != null && task.containsKey(Provider.MAIN_URL)) { - last_provider_main_url = task.getString(Provider.MAIN_URL); + last_provider_main_url = task.containsKey(Provider.MAIN_URL) ? + task.getString(Provider.MAIN_URL) : + ""; + provider_ca_cert_fingerprint = task.containsKey(Provider.CA_CERT_FINGERPRINT) ? + task.getString(Provider.CA_CERT_FINGERPRINT) : + ""; + CA_CERT_DOWNLOADED = PROVIDER_JSON_DOWNLOADED = EIP_SERVICE_JSON_DOWNLOADED = false; go_ahead = true; } if (!PROVIDER_JSON_DOWNLOADED) - current_download = getAndSetProviderJson(last_provider_main_url); + current_download = getAndSetProviderJson(last_provider_main_url, provider_ca_cert_fingerprint); if (PROVIDER_JSON_DOWNLOADED || (current_download.containsKey(RESULT_KEY) && current_download.getBoolean(RESULT_KEY))) { broadcastProgress(progress++); PROVIDER_JSON_DOWNLOADED = true; @@ -602,11 +610,16 @@ public class ProviderAPI extends IntentService { return hexData.toString(); } - private Bundle getAndSetProviderJson(String provider_main_url) { + private Bundle getAndSetProviderJson(String provider_main_url, String provider_ca_cert_fingerprint) { Bundle result = new Bundle(); if (go_ahead) { - String provider_dot_json_string = downloadWithCommercialCA(provider_main_url + "/provider.json"); + String provider_dot_json_string; + + if(provider_ca_cert_fingerprint.isEmpty()) + provider_dot_json_string = downloadWithCommercialCA(provider_main_url + "/provider.json"); + else + provider_dot_json_string = downloadWithCommercialCA(provider_main_url + "/provider.json", provider_ca_cert_fingerprint); try { JSONObject provider_json = new JSONObject(provider_dot_json_string); @@ -672,6 +685,28 @@ public class ProviderAPI extends IntentService { return error_message; } + private String downloadWithCommercialCA(String url_string, String ca_cert_fingerprint) { + String result = ""; + + int seconds_of_timeout = 2; + String[] pins = new String[] {ca_cert_fingerprint}; + try { + URL url = new URL(url_string); + HttpsURLConnection connection = PinningHelper.getPinnedHttpsURLConnection(Dashboard.getContext(), pins, url); + connection.setConnectTimeout(seconds_of_timeout * 1000); + if (!LeapSRPSession.getToken().isEmpty()) + connection.addRequestProperty(LeapSRPSession.AUTHORIZATION_HEADER, "Token token = " + LeapSRPSession.getToken()); + result = new Scanner(connection.getInputStream()).useDelimiter("\\A").next(); + } catch (IOException e) { + if(e instanceof SSLHandshakeException) + result = formatErrorMessage(R.string.error_security_pinnedcertificate); + else + result = formatErrorMessage(R.string.error_io_exception_user_message); + } + + return result; + } + /** * Tries to download the contents of the provided url using commercially validated CA certificate from chosen provider. * |