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'