diff --git a/.gitmodules b/.gitmodules index 81a8df8b..938ab729 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,7 +7,6 @@ [submodule "vcard4android"] path = vcard4android url = ../vcard4android.git -[submodule "MemorizingTrustManager"] - path = MemorizingTrustManager - url = https://github.com/ge0rg/MemorizingTrustManager - ignore = dirty +[submodule "cert4android"] + path = cert4android + url = ../cert4android.git diff --git a/MemorizingTrustManager b/MemorizingTrustManager deleted file mode 160000 index b6a3d558..00000000 --- a/MemorizingTrustManager +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b6a3d558e4b78cd9ad5e8ad5246e44f04c854137 diff --git a/app/build.gradle b/app/build.gradle index 3de7e74d..82ed8339 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,15 +17,15 @@ android { minSdkVersion 14 targetSdkVersion 24 - versionCode 112 + versionCode 113 buildConfigField "long", "buildTime", System.currentTimeMillis() + "L" - buildConfigField "boolean", "useMTM", "true" + buildConfigField "boolean", "customCerts", "true" } productFlavors { standard { - versionName "1.2.3-ose" + versionName "1.3-ose" } } @@ -72,7 +72,7 @@ dependencies { compile 'com.android.support:preference-v14:24.+' compile 'com.github.yukuku:ambilwarna:2.0.1' - compile project(':MemorizingTrustManager') + compile project(':cert4android') compile 'dnsjava:dnsjava:2.1.7' compile 'org.apache.commons:commons-lang3:3.4' diff --git a/app/src/androidTest/java/at/bitfire/davdroid/SSLSocketFactoryCompatTest.java b/app/src/androidTest/java/at/bitfire/davdroid/SSLSocketFactoryCompatTest.java index feb5e768..ed49a5aa 100644 --- a/app/src/androidTest/java/at/bitfire/davdroid/SSLSocketFactoryCompatTest.java +++ b/app/src/androidTest/java/at/bitfire/davdroid/SSLSocketFactoryCompatTest.java @@ -16,7 +16,7 @@ import java.net.Socket; import javax.net.ssl.SSLSocket; -import de.duenndns.ssl.MemorizingTrustManager; +import at.bitfire.cert4android.CustomCertManager; import okhttp3.mockwebserver.MockWebServer; public class SSLSocketFactoryCompatTest extends InstrumentationTestCase { @@ -26,7 +26,7 @@ public class SSLSocketFactoryCompatTest extends InstrumentationTestCase { @Override protected void setUp() throws Exception { - factory = new SSLSocketFactoryCompat(new MemorizingTrustManager(getInstrumentation().getTargetContext().getApplicationContext())); + factory = new SSLSocketFactoryCompat(new CustomCertManager(getInstrumentation().getTargetContext().getApplicationContext(), true)); server.start(); } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 215c06ed..40d9413e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -53,6 +53,7 @@ android:name=".App" android:allowBackup="true" android:fullBackupContent="false" + android:networkSecurityConfig="@xml/network_security_config" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" @@ -192,11 +193,6 @@ android:label="@string/debug_info_title"> - - - diff --git a/app/src/main/java/at/bitfire/davdroid/App.java b/app/src/main/java/at/bitfire/davdroid/App.java index 0b6b8155..5ae12bf8 100644 --- a/app/src/main/java/at/bitfire/davdroid/App.java +++ b/app/src/main/java/at/bitfire/davdroid/App.java @@ -34,11 +34,11 @@ import java.util.logging.Logger; import javax.net.ssl.HostnameVerifier; +import at.bitfire.cert4android.CustomCertManager; import at.bitfire.davdroid.log.LogcatHandler; import at.bitfire.davdroid.log.PlainTextFormatter; import at.bitfire.davdroid.model.ServiceDB; import at.bitfire.davdroid.model.Settings; -import de.duenndns.ssl.MemorizingTrustManager; import lombok.Cleanup; import lombok.Getter; import okhttp3.internal.tls.OkHostnameVerifier; @@ -46,10 +46,12 @@ import okhttp3.internal.tls.OkHostnameVerifier; public class App extends Application { public static final String FLAVOR_GOOGLE_PLAY = "gplay"; - public static final String LOG_TO_EXTERNAL_STORAGE = "logToExternalStorage"; + public static final String + DISTRUST_SYSTEM_CERTIFICATES = "distrustSystemCerts", + LOG_TO_EXTERNAL_STORAGE = "logToExternalStorage"; @Getter - private static MemorizingTrustManager memorizingTrustManager; + private static CustomCertManager certManager; @Getter private static SSLSocketFactoryCompat sslSocketFactoryCompat; @@ -60,19 +62,28 @@ public class App extends Application { public final static Logger log = Logger.getLogger("davdroid"); static { at.bitfire.dav4android.Constants.log = Logger.getLogger("davdroid.dav4android"); + at.bitfire.cert4android.Constants.log = Logger.getLogger("davdroid.cert4android"); } @Override public void onCreate() { super.onCreate(); + reinitCertManager(); + reinitLogger(); + } - // initialize MemorizingTrustManager - memorizingTrustManager = new MemorizingTrustManager(this); - sslSocketFactoryCompat = new SSLSocketFactoryCompat(memorizingTrustManager); - hostnameVerifier = memorizingTrustManager.wrapHostnameVerifier(OkHostnameVerifier.INSTANCE); + public void reinitCertManager() { + if (BuildConfig.customCerts) { + if (certManager != null) + certManager.close(); - // initializer logger - reinitLogger(); + @Cleanup ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(this); + Settings settings = new Settings(dbHelper.getReadableDatabase()); + + certManager = new CustomCertManager(this, !settings.getBoolean(DISTRUST_SYSTEM_CERTIFICATES, false)); + sslSocketFactoryCompat = new SSLSocketFactoryCompat(certManager); + hostnameVerifier = certManager.hostnameVerifier(OkHostnameVerifier.INSTANCE); + } } public void reinitLogger() { diff --git a/app/src/main/java/at/bitfire/davdroid/HttpClient.java b/app/src/main/java/at/bitfire/davdroid/HttpClient.java index 3758bc46..556f4d90 100644 --- a/app/src/main/java/at/bitfire/davdroid/HttpClient.java +++ b/app/src/main/java/at/bitfire/davdroid/HttpClient.java @@ -69,8 +69,8 @@ public class HttpClient { OkHttpClient.Builder builder = client.newBuilder(); // use MemorizingTrustManager to manage self-signed certificates - if (App.getSslSocketFactoryCompat() != null) - builder.sslSocketFactory(App.getSslSocketFactoryCompat(), App.getMemorizingTrustManager()); + if (App.getSslSocketFactoryCompat() != null && App.getCertManager() != null) + builder.sslSocketFactory(App.getSslSocketFactoryCompat(), App.getCertManager()); if (App.getHostnameVerifier() != null) builder.hostnameVerifier(App.getHostnameVerifier()); diff --git a/app/src/main/java/at/bitfire/davdroid/SSLSocketFactoryCompat.java b/app/src/main/java/at/bitfire/davdroid/SSLSocketFactoryCompat.java index 4274b110..4dbd224f 100644 --- a/app/src/main/java/at/bitfire/davdroid/SSLSocketFactoryCompat.java +++ b/app/src/main/java/at/bitfire/davdroid/SSLSocketFactoryCompat.java @@ -27,7 +27,6 @@ import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509TrustManager; -import de.duenndns.ssl.MemorizingTrustManager; import lombok.Cleanup; public class SSLSocketFactoryCompat extends SSLSocketFactory { @@ -99,10 +98,10 @@ public class SSLSocketFactoryCompat extends SSLSocketFactory { } } - public SSLSocketFactoryCompat(@NonNull MemorizingTrustManager mtm) { + public SSLSocketFactoryCompat(@NonNull X509TrustManager trustManager) { try { SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, new X509TrustManager[] { mtm }, null); + sslContext.init(null, new X509TrustManager[] { trustManager }, null); delegate = sslContext.getSocketFactory(); } catch (GeneralSecurityException e) { throw new AssertionError(); // The system has no TLS. Just give up. diff --git a/app/src/main/java/at/bitfire/davdroid/ui/AccountActivity.java b/app/src/main/java/at/bitfire/davdroid/ui/AccountActivity.java index f2d12832..2fcd2130 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/AccountActivity.java +++ b/app/src/main/java/at/bitfire/davdroid/ui/AccountActivity.java @@ -62,6 +62,7 @@ import java.util.LinkedList; import java.util.List; import java.util.logging.Level; +import at.bitfire.cert4android.CustomCertManager; import at.bitfire.davdroid.App; import at.bitfire.davdroid.DavService; import at.bitfire.davdroid.R; @@ -110,6 +111,22 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu getLoaderManager().initLoader(0, getIntent().getExtras(), this); } + @Override + protected void onPause() { + super.onPause(); + CustomCertManager certManager = App.getCertManager(); + if (certManager != null) + certManager.appInForeground = false; + } + + @Override + protected void onResume() { + super.onResume(); + CustomCertManager certManager = App.getCertManager(); + if (certManager != null) + certManager.appInForeground = true; + } + @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_account, menu); diff --git a/app/src/main/java/at/bitfire/davdroid/ui/AppSettingsActivity.java b/app/src/main/java/at/bitfire/davdroid/ui/AppSettingsActivity.java index 3dad59ea..f67986f3 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/AppSettingsActivity.java +++ b/app/src/main/java/at/bitfire/davdroid/ui/AppSettingsActivity.java @@ -16,15 +16,10 @@ import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceFragmentCompat; import android.support.v7.preference.SwitchPreferenceCompat; -import java.security.KeyStoreException; -import java.util.Enumeration; -import java.util.logging.Level; - import at.bitfire.davdroid.App; import at.bitfire.davdroid.R; import at.bitfire.davdroid.model.ServiceDB; import at.bitfire.davdroid.model.Settings; -import de.duenndns.ssl.MemorizingTrustManager; import lombok.Cleanup; public class AppSettingsActivity extends AppCompatActivity { @@ -42,19 +37,43 @@ public class AppSettingsActivity extends AppCompatActivity { public static class SettingsFragment extends PreferenceFragmentCompat { - Preference prefResetHints, - prefResetCertificates; - SwitchPreferenceCompat prefLogToExternalStorage; + ServiceDB.OpenHelper dbHelper; + Settings settings; + + Preference + prefResetHints, + prefResetCertificates; + SwitchPreferenceCompat + prefDistrustSystemCerts, + prefLogToExternalStorage; + + @Override + public void onCreate(Bundle savedInstanceState) { + dbHelper = new ServiceDB.OpenHelper(getContext()); + settings = new Settings(dbHelper.getReadableDatabase()); + + super.onCreate(savedInstanceState); + } + + @Override + public void onDestroy() { + super.onDestroy(); + dbHelper.close(); + } @Override public void onCreatePreferences(Bundle bundle, String s) { addPreferencesFromResource(R.xml.settings_app); prefResetHints = findPreference("reset_hints"); + + prefDistrustSystemCerts = (SwitchPreferenceCompat)findPreference("distrust_system_certs"); + prefDistrustSystemCerts.setChecked(settings.getBoolean(App.DISTRUST_SYSTEM_CERTIFICATES, false)); + prefResetCertificates = findPreference("reset_certificates"); + if (App.getCertManager() == null) + prefResetCertificates.setVisible(false); - @Cleanup ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(getContext()); - Settings settings = new Settings(dbHelper.getReadableDatabase()); prefLogToExternalStorage = (SwitchPreferenceCompat)findPreference("log_to_external_storage"); prefLogToExternalStorage.setChecked(settings.getBoolean(App.LOG_TO_EXTERNAL_STORAGE, false)); } @@ -63,6 +82,8 @@ public class AppSettingsActivity extends AppCompatActivity { public boolean onPreferenceTreeClick(Preference preference) { if (preference == prefResetHints) resetHints(); + else if (preference == prefDistrustSystemCerts) + setDistrustSystemCerts(((SwitchPreferenceCompat)preference).isChecked()); else if (preference == prefResetCertificates) resetCertificates(); else if (preference == prefLogToExternalStorage) @@ -73,31 +94,25 @@ public class AppSettingsActivity extends AppCompatActivity { } private void resetHints() { - @Cleanup ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(getContext()); - Settings settings = new Settings(dbHelper.getWritableDatabase()); settings.remove(StartupDialogFragment.HINT_BATTERY_OPTIMIZATIONS); settings.remove(StartupDialogFragment.HINT_OPENTASKS_NOT_INSTALLED); Snackbar.make(getView(), R.string.app_settings_reset_hints_success, Snackbar.LENGTH_LONG).show(); } + private void setDistrustSystemCerts(boolean distrust) { + settings.putBoolean(App.DISTRUST_SYSTEM_CERTIFICATES, distrust); + + // re-initialize certificate manager + App app = (App)getContext().getApplicationContext(); + app.reinitCertManager(); + } + private void resetCertificates() { - MemorizingTrustManager mtm = App.getMemorizingTrustManager(); - - int deleted = 0; - Enumeration iterator = mtm.getCertificates(); - while (iterator.hasMoreElements()) - try { - mtm.deleteCertificate(iterator.nextElement()); - deleted++; - } catch (KeyStoreException e) { - App.log.log(Level.SEVERE, "Couldn't distrust certificate", e); - } - Snackbar.make(getView(), getResources().getQuantityString(R.plurals.app_settings_reset_trusted_certificates_success, deleted, deleted), Snackbar.LENGTH_LONG).show(); + App.getCertManager().resetCertificates(); + Snackbar.make(getView(), getString(R.string.app_settings_reset_certificates_success), Snackbar.LENGTH_LONG).show(); } private void setExternalLogging(boolean externalLogging) { - @Cleanup ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(getContext()); - Settings settings = new Settings(dbHelper.getWritableDatabase()); settings.putBoolean(App.LOG_TO_EXTERNAL_STORAGE, externalLogging); // reinitialize logger of default process diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginActivity.java b/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginActivity.java index 1e56a444..5f9d027d 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginActivity.java +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginActivity.java @@ -14,6 +14,7 @@ import android.support.v7.app.AppCompatActivity; import android.view.Menu; import android.view.MenuItem; +import at.bitfire.davdroid.App; import at.bitfire.davdroid.Constants; import at.bitfire.davdroid.R; @@ -53,6 +54,20 @@ public class LoginActivity extends AppCompatActivity { } + @Override + protected void onResume() { + super.onResume(); + if (App.getCertManager() != null) + App.getCertManager().appInForeground = true; + } + + @Override + protected void onPause() { + super.onPause(); + if (App.getCertManager() != null) + App.getCertManager().appInForeground = false; + } + @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_login, menu); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cb47a0f9..8357d384 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -73,12 +73,12 @@ Re-enables hints which have been dismissed previously All hints will be shown again Security - Reset trusted certificates - Forgets all certificates which have been accepted previously - - Distrusted one certificate - Distrusted %d certificates - + Distrust system certificates + System and user-added CAs won\'t be trusted + System and user-added CAs will be trusted + Reset (un)trusted certificates + Resets trust of all custom certificates + All custom certificates have been cleared Debugging Log to external file Logging to external storage (if available) @@ -261,4 +261,8 @@ User name/password wrong + + DAVdroid: Connection security + DAVdroid has encountered an unknown certificate. Do you want to trust it? + diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 00000000..284be8c6 --- /dev/null +++ b/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/src/main/res/xml/settings_app.xml b/app/src/main/res/xml/settings_app.xml index 50a81acb..4aa244a5 100644 --- a/app/src/main/res/xml/settings_app.xml +++ b/app/src/main/res/xml/settings_app.xml @@ -17,10 +17,15 @@ + + android:title="@string/app_settings_reset_certificates" + android:summary="@string/app_settings_reset_certificates_summary"/> diff --git a/build.gradle b/build.gradle index 8d038061..44fd48ff 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.2' + classpath 'com.android.tools.build:gradle:2.+' } } diff --git a/cert4android b/cert4android new file mode 160000 index 00000000..c342cbd8 --- /dev/null +++ b/cert4android @@ -0,0 +1 @@ +Subproject commit c342cbd81185d7f6bd2cd25eea55bd0ecf94c1cc diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6b064657..3bdc1f0f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Apr 09 22:18:11 CEST 2016 +#Tue Aug 23 16:42:17 CEST 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/settings.gradle b/settings.gradle index 617c30b4..6100cf62 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,5 +10,4 @@ include ':app' include ':dav4android' include ':ical4android' include ':vcard4android' - -include ':MemorizingTrustManager' +include ':cert4android'