Skip to content

Commit

Permalink
Added proxy functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
martenrebane committed Oct 9, 2023
1 parent 15249a7 commit 91ab175
Show file tree
Hide file tree
Showing 33 changed files with 1,187 additions and 65 deletions.
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ dependencies {
implementation "androidx.cardview:cardview:${androidxCardviewVersion}"
implementation "androidx.lifecycle:lifecycle-viewmodel:${androidxLifecycleVersion}"
implementation "androidx.work:work-runtime:${androidxWorkRuntime}"
implementation "androidx.security:security-crypto:${androidxSecurityCrypto}"

implementation "com.takisoft.preferencex:preferencex:${preferencexVersion}"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.Proxy;
import java.nio.charset.StandardCharsets;
import java.nio.file.NoSuchFileException;
import java.text.Normalizer;
Expand All @@ -43,12 +44,14 @@
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

import ee.ria.DigiDoc.BuildConfig;
import ee.ria.DigiDoc.R;
import ee.ria.DigiDoc.android.Activity;
import ee.ria.DigiDoc.android.ApplicationApp;
import ee.ria.DigiDoc.android.accessibility.AccessibilityUtils;
import ee.ria.DigiDoc.android.main.settings.SettingsDataStore;
import ee.ria.DigiDoc.android.utils.ClickableDialogUtil;
import ee.ria.DigiDoc.android.utils.TSLException;
import ee.ria.DigiDoc.android.utils.TSLUtil;
Expand All @@ -59,6 +62,10 @@
import ee.ria.DigiDoc.android.utils.navigator.Transaction;
import ee.ria.DigiDoc.android.utils.widget.ConfirmationDialog;
import ee.ria.DigiDoc.common.FileUtil;
import ee.ria.DigiDoc.common.ManualProxy;
import ee.ria.DigiDoc.common.ProxyConfig;
import ee.ria.DigiDoc.common.ProxySetting;
import ee.ria.DigiDoc.common.ProxyUtil;
import ee.ria.DigiDoc.configuration.ConfigurationDateUtil;
import ee.ria.DigiDoc.configuration.ConfigurationManagerService;
import ee.ria.DigiDoc.configuration.ConfigurationProvider;
Expand All @@ -67,17 +74,22 @@
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import okhttp3.Authenticator;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.internal.tls.OkHostnameVerifier;
import timber.log.Timber;

public final class DiagnosticsView extends CoordinatorLayout implements ContentView {

private static final int DEFAULT_TIMEOUT = 5;

private final Navigator navigator;
private final SimpleDateFormat dateFormat;
private final Toolbar toolbarView;
private final ConfirmationDialog diagnosticsRestartConfirmationDialog;
private final SettingsDataStore settingsDataStore;

private final ViewDisposables disposables;

Expand All @@ -96,14 +108,15 @@ public DiagnosticsView(Context context) {
toolbarView = findViewById(R.id.toolbar);
View saveDiagnosticsButton = findViewById(R.id.configurationSaveButton);
navigator = ApplicationApp.component(context).navigator();
settingsDataStore = ApplicationApp.component(context).settingsDataStore();

ContentView.addInvisibleElement(getContext(), this);

diagnosticsRestartConfirmationDialog = new ConfirmationDialog(navigator.activity(),
R.string.main_diagnostics_restart_message, R.id.mainDiagnosticsRestartConfirmationDialog);

SwitchCompat activateLogFileGenerating = findViewById(R.id.mainDiagnosticsLogging);
activateLogFileGenerating.setChecked(((Activity) this.getContext()).getSettingsDataStore().getIsLogFileGenerationEnabled());
activateLogFileGenerating.setChecked(settingsDataStore.getIsLogFileGenerationEnabled());
Button saveLogFileButton = findViewById(R.id.mainDiagnosticsSaveLoggingButton);
saveLogFileButton.setVisibility(
(activateLogFileGenerating.isChecked() &&
Expand Down Expand Up @@ -134,27 +147,27 @@ public DiagnosticsView(Context context) {
private void fileLogToggleListener(SwitchCompat activateLogFileGenerating) {
activateLogFileGenerating.setOnCheckedChangeListener((buttonView, isChecked) -> {
Activity activityContext = ((Activity) this.getContext());
boolean isLogFileGenerationEnabled = activityContext.getSettingsDataStore().getIsLogFileGenerationEnabled();
boolean isLogFileGenerationEnabled = settingsDataStore.getIsLogFileGenerationEnabled();
if (isChecked) {
diagnosticsRestartConfirmationDialog.show();
ClickableDialogUtil.makeLinksInDialogClickable(diagnosticsRestartConfirmationDialog);
diagnosticsRestartConfirmationDialog.positiveButtonClicks()
.doOnNext(next -> {
diagnosticsRestartConfirmationDialog.dismiss();
activityContext.getSettingsDataStore().setIsLogFileGenerationEnabled(true);
settingsDataStore.setIsLogFileGenerationEnabled(true);
activityContext.restartAppWithIntent(activityContext.getIntent(), true);
})
.subscribe();
diagnosticsRestartConfirmationDialog.cancels()
.doOnNext(next -> {
diagnosticsRestartConfirmationDialog.dismiss();
activateLogFileGenerating.setChecked(false);
activityContext.getSettingsDataStore().setIsLogFileGenerationEnabled(false);
settingsDataStore.setIsLogFileGenerationEnabled(false);
})
.subscribe();
} else {
activityContext.getSettingsDataStore().setIsLogFileGenerationEnabled(false);
activityContext.getSettingsDataStore().setIsLogFileGenerationRunning(false);
settingsDataStore.setIsLogFileGenerationEnabled(false);
settingsDataStore.setIsLogFileGenerationRunning(false);
if (isLogFileGenerationEnabled) {
activityContext.restartAppWithIntent(activityContext.getIntent(), true);
}
Expand Down Expand Up @@ -354,7 +367,7 @@ private void setData(ConfigurationProvider configurationProvider) {
}

private void appendTslVersion(TextView tslUrlTextView, String tslUrl) {
tslVersionDisposable = getObservableTslVersion(tslUrl )
tslVersionDisposable = getObservableTslVersion(tslUrl)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
Expand All @@ -363,9 +376,35 @@ private void appendTslVersion(TextView tslUrlTextView, String tslUrl) {
);
}

private ProxySetting getProxySetting() {
return settingsDataStore.getProxySetting();
}

private ManualProxy getManualProxySettings() {
return new ManualProxy(
settingsDataStore.getProxyHost(),
settingsDataStore.getProxyPort(),
settingsDataStore.getProxyUsername(),
settingsDataStore.getProxyPassword(navigator.activity())
);
}

private Observable<Integer> getObservableTslVersion(String tslUrl) {
ProxySetting proxySetting = getProxySetting();
boolean isProxySSLEnabled = settingsDataStore.getIsProxyForSSLEnabled();
boolean useHTTPSProxy = ProxyUtil.useHTTPSProxy(isProxySSLEnabled, getManualProxySettings());
ProxyConfig proxyConfig = ProxyUtil.getProxy(proxySetting, getManualProxySettings());

return Observable.fromCallable(() -> {
OkHttpClient client = new OkHttpClient();
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.proxy(proxySetting == ProxySetting.NO_PROXY || !useHTTPSProxy ? Proxy.NO_PROXY : proxyConfig.proxy())
.proxyAuthenticator(proxySetting == ProxySetting.NO_PROXY || !useHTTPSProxy ? Authenticator.NONE : proxyConfig.authenticator())
.hostnameVerifier(OkHostnameVerifier.INSTANCE)
.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.callTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS);
OkHttpClient client = builder.build();
Request request = new Request.Builder().url(tslUrl).build();
Response response = client.newCall(request).execute();
if (response.isSuccessful() && response.body() != null) {
Expand All @@ -381,15 +420,15 @@ private Observable<Integer> getObservableTslVersion(String tslUrl) {
}

private String getRpUuidText() {
String rpUuid = ((Activity) this.getContext()).getSettingsDataStore().getUuid();
String rpUuid = settingsDataStore.getUuid();
int uuid = rpUuid == null || rpUuid.isEmpty()
? R.string.main_diagnostics_rpuuid_default
: R.string.main_diagnostics_rpuuid_custom;
return getResources().getString(uuid);
}

private String getTsaUrlText() {
return ((Activity) this.getContext()).getSettingsDataStore().getTsaUrl();
return settingsDataStore.getTsaUrl();
}

private void setTslCacheData() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,44 @@
package ee.ria.DigiDoc.android.main.settings;

import static ee.ria.DigiDoc.common.ProxySetting.NO_PROXY;

import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.util.Log;

import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
import androidx.security.crypto.EncryptedSharedPreferences;
import androidx.security.crypto.MasterKeys;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

import javax.inject.Inject;

import ee.ria.DigiDoc.R;
import ee.ria.DigiDoc.android.utils.ToastUtil;
import ee.ria.DigiDoc.common.ManualProxy;
import ee.ria.DigiDoc.common.ProxySetting;
import timber.log.Timber;

public final class SettingsDataStore {

private static final String KEY_LOCALE = "locale";
private static final String ENCRYPTED_PREFERENCES_KEY = "encryptedPreferencesStorage";

private final SharedPreferences preferences;
private final SharedPreferences encryptedPreferences;
private final Resources resources;

@Inject SettingsDataStore(Application application) {
preferences = PreferenceManager.getDefaultSharedPreferences(application);
encryptedPreferences = getEncryptedPreferences(application.getApplicationContext());
this.resources = application.getResources();
}

Expand Down Expand Up @@ -277,4 +292,104 @@ public void setIsTsaCertificateViewVisible(boolean isVisible) {
public boolean getIsTsaCertificateViewVisible() {
return preferences.getBoolean(resources.getString(R.string.main_settings_tsa_cert_view), false);
}

public void setProxySetting(ProxySetting proxySetting) {
SharedPreferences.Editor editor = preferences.edit();
editor.putString(resources.getString(R.string.main_settings_proxy_setting_key), proxySetting.name());
editor.commit();
}

public ProxySetting getProxySetting() {
String settingKey = preferences.getString(resources.getString(R.string.main_settings_proxy_setting_key), NO_PROXY.name());
try {
return ProxySetting.valueOf(settingKey);
} catch (IllegalArgumentException iae) {
Timber.log(Log.ERROR, iae, "Unable to get proxy setting");
return NO_PROXY;
}
}

public void setIsProxyForSSLEnabled(boolean isEnabled) {
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean(resources.getString(R.string.main_settings_proxy_ssl_enabled_key), isEnabled);
editor.commit();
}

public boolean getIsProxyForSSLEnabled() {
return preferences.getBoolean(resources.getString(R.string.main_settings_proxy_ssl_enabled_key), true);
}

public void setProxyHost(String host) {
SharedPreferences.Editor editor = preferences.edit();
editor.putString(resources.getString(R.string.main_settings_proxy_host_key), host);
editor.commit();
}

public String getProxyHost() {
return preferences.getString(resources.getString(R.string.main_settings_proxy_host_key), "");
}

public void setProxyPort(int port) {
SharedPreferences.Editor editor = preferences.edit();
editor.putInt(resources.getString(R.string.main_settings_proxy_port_key), port);
editor.commit();
}

public int getProxyPort() {
return preferences.getInt(resources.getString(R.string.main_settings_proxy_port_key), 80);
}

public void setProxyUsername(String username) {
SharedPreferences.Editor editor = preferences.edit();
editor.putString(resources.getString(R.string.main_settings_proxy_username_key), username);
editor.commit();
}

public String getProxyUsername() {
return preferences.getString(resources.getString(R.string.main_settings_proxy_username_key), "");
}

public void setProxyPassword(Context context, String password) {
SharedPreferences encryptedPreferences = getEncryptedPreferences(context);
if (encryptedPreferences != null) {
SharedPreferences.Editor editor = encryptedPreferences.edit();
editor.putString(resources.getString(R.string.main_settings_proxy_password_key), password);
editor.commit();

Check failure

Code scanning / CodeQL

Cleartext storage of sensitive information using `SharedPreferences` on Android High

This stores the 'SharedPreferences' class
edit(...)
containing
sensitive data
which
was set as a shared preference
.
This stores the 'SharedPreferences' class
edit(...)
containing
sensitive data
which
was set as a shared preference
.
This stores the 'SharedPreferences' class
edit(...)
containing
sensitive data
which
was set as a shared preference
.
This stores the 'SharedPreferences' class
edit(...)
containing
sensitive data
which
was set as a shared preference
.
}
Timber.log(Log.ERROR, "Unable to set proxy password");
}

public String getProxyPassword(Context context) {
SharedPreferences encryptedPreferences = getEncryptedPreferences(context);
if (encryptedPreferences != null) {
return encryptedPreferences.getString(resources.getString(R.string.main_settings_proxy_password_key), "");
}
Timber.log(Log.ERROR, "Unable to get proxy password");
return "";
}

public ManualProxy getManualProxySettings(Context context) {
return new ManualProxy(getProxyHost(), getProxyPort(),
getProxyUsername(), getProxyPassword(context));
}

@Nullable
private static SharedPreferences getEncryptedPreferences(Context context) {
String masterKey;
try {
masterKey = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC);
return EncryptedSharedPreferences.create(
ENCRYPTED_PREFERENCES_KEY,
masterKey,
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);

} catch (GeneralSecurityException | IOException e) {
Timber.log(Log.ERROR, e, "Unable to get encrypted preferences");
ToastUtil.showError(context, R.string.signature_update_mobile_id_error_general_client);
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,24 @@
import ee.ria.DigiDoc.R;
import ee.ria.DigiDoc.android.ApplicationApp;
import ee.ria.DigiDoc.android.main.settings.access.SettingsAccessScreen;
import ee.ria.DigiDoc.android.main.settings.proxy.SettingsProxyScreen;
import ee.ria.DigiDoc.android.main.settings.role.SettingsRoleAndAddressScreen;
import ee.ria.DigiDoc.android.utils.ViewDisposables;
import ee.ria.DigiDoc.android.utils.navigator.ContentView;
import ee.ria.DigiDoc.android.utils.navigator.Navigator;
import ee.ria.DigiDoc.android.utils.navigator.Transaction;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.disposables.Disposable;
import kotlin.Unit;

public final class SettingsView extends CoordinatorLayout implements ContentView {

private final Toolbar toolbarView;
private final Button accessCategory;
private final Button proxyCategory;
private final Button roleAndAddressCategory;

private final Navigator navigator;

private final ViewDisposables disposables;

private final Button accessCategory;
private final Button roleAndAddressCategory;

public SettingsView(Context context) {
this(context, null);
}
Expand All @@ -53,6 +50,7 @@ public SettingsView(Context context, AttributeSet attrs, int defStyleAttr) {
disposables = new ViewDisposables();

accessCategory = findViewById(R.id.mainSettingsAccessCategory);
proxyCategory = findViewById(R.id.mainSettingsProxyCategory);
roleAndAddressCategory = findViewById(R.id.mainSettingsRoleAndAddressCategory);

toolbarView.setTitle(R.string.main_settings_title);
Expand All @@ -73,6 +71,9 @@ public void onAttachedToWindow() {
disposables.add(clicks(accessCategory).subscribe(o ->
navigator.execute(
Transaction.push(SettingsAccessScreen.create()))));
disposables.add(clicks(proxyCategory).subscribe(o ->
navigator.execute(
Transaction.push(SettingsProxyScreen.create()))));
disposables.add(clicks(roleAndAddressCategory).subscribe(o ->
navigator.execute(
Transaction.push(SettingsRoleAndAddressScreen.create()))));
Expand Down
Loading

0 comments on commit 91ab175

Please sign in to comment.