Add support for self-signed SSL certificates

gtask_related_email
Alex Baker 4 years ago
parent eb8963d44d
commit f6fd7105e6

@ -112,6 +112,14 @@ android {
setDimension("store")
}
}
dataBinding {
isEnabled = true
}
packagingOptions {
exclude("META-INF/*.kotlin_module")
}
}
configure<CheckstyleExtension> {
@ -136,6 +144,7 @@ dependencies {
implementation("com.gitlab.bitfireAT:ical4android:be6d515db8") {
exclude(group = "org.threeten", module = "threetenbp")
}
implementation("com.gitlab.bitfireAT:cert4android:1488e39a66")
annotationProcessor("com.google.dagger:dagger-compiler:${Versions.dagger}")
implementation("com.google.dagger:dagger:${Versions.dagger}")

@ -8,6 +8,11 @@
copyrightHolder: bitfire web engineering (Ricki Hirner, Bernhard Stockmann)
license: GNU General Public License, Version 3.0
licenseUrl: https://www.gnu.org/licenses/gpl.txt
- artifact: com.gitlab.bitfireAT:cert4android:+
name: cert4android
copyrightHolder: bitfire web engineering (Ricki Hirner, Bernhard Stockmann)
licenseUrl: https://www.gnu.org/licenses/gpl.txt
license: GNU General Public License, Version 3.0
- artifact: androidx.coordinatorlayout:coordinatorlayout:+
name: Android Support Library Coordinator Layout
copyrightHolder: Android Open Source Project
@ -738,3 +743,61 @@
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/topic/libraries/architecture/index.html
- artifact: org.jetbrains.kotlinx:kotlinx-coroutines-core-common:+
name: kotlinx-coroutines-core-common
copyrightHolder: JetBrains s.r.o.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/Kotlin/kotlinx.coroutines
- artifact: org.conscrypt:conscrypt-android:+
name: org.conscrypt:conscrypt-android
copyrightHolder: Android Open Source Project
license: Apache 2
licenseUrl: https://www.apache.org/licenses/LICENSE-2.0
url: https://conscrypt.org/
- artifact: org.jetbrains.kotlin:kotlin-stdlib-jdk8:+
name: org.jetbrains.kotlin:kotlin-stdlib-jdk8
copyrightHolder: JetBrains s.r.o.
license: The Apache License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://kotlinlang.org/
- artifact: org.jetbrains.kotlinx:kotlinx-coroutines-android:+
name: kotlinx-coroutines-android
copyrightHolder: JetBrains s.r.o.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/Kotlin/kotlinx.coroutines
- artifact: androidx.databinding:databinding-adapters:+
name: databinding-adapters
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
- artifact: androidx.lifecycle:lifecycle-viewmodel-ktx:+
name: Android Lifecycle ViewModel Kotlin Extensions
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: http://developer.android.com/tools/extras/support-library.html
- artifact: androidx.annotation:annotation-experimental:+
name: Experimental annotation
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/jetpack/androidx
- artifact: org.jetbrains.kotlinx:kotlinx-coroutines-core:+
name: kotlinx-coroutines-core
copyrightHolder: JetBrains s.r.o.
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://github.com/Kotlin/kotlinx.coroutines
- artifact: androidx.databinding:databinding-common:+
name: Data Binding Base Library
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt
url: https://developer.android.com/studio
- artifact: androidx.databinding:databinding-runtime:+
name: databinding-runtime
copyrightHolder: Android Open Source Project
license: The Apache Software License, Version 2.0
licenseUrl: http://www.apache.org/licenses/LICENSE-2.0.txt

@ -1,8 +1,9 @@
package org.tasks.caldav;
import static androidx.test.InstrumentationRegistry.getTargetContext;
import static org.tasks.injection.TestModule.newPreferences;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -12,7 +13,8 @@ public class CaldavClientTest {
@Test
public void dontCrashOnSpaceInUrl() {
new CaldavClient(null, newPreferences(getTargetContext()), null)
Context context = ApplicationProvider.getApplicationContext();
new CaldavClient(context, null, newPreferences(context), null)
.forUrl("https://example.com/remote.php/a space/", "username", "password");
}
}

@ -5,6 +5,7 @@ import static com.google.common.primitives.Ints.asList;
import android.os.Bundle;
import android.preference.Preference;
import androidx.annotation.StringRes;
import at.bitfire.cert4android.CustomCertManager;
import com.android.billingclient.api.BillingClient.SkuType;
import javax.inject.Inject;
import org.tasks.R;
@ -12,11 +13,13 @@ import org.tasks.billing.BillingClient;
import org.tasks.billing.Inventory;
import org.tasks.injection.ActivityComponent;
import org.tasks.injection.InjectingPreferenceActivity;
import org.tasks.ui.Toaster;
public class DebugPreferences extends InjectingPreferenceActivity {
@Inject Inventory inventory;
@Inject BillingClient billingClient;
@Inject Toaster toaster;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -37,6 +40,14 @@ public class DebugPreferences extends InjectingPreferenceActivity {
});
}
findPreference(R.string.debug_reset_ssl)
.setOnPreferenceClickListener(
preference -> {
CustomCertManager.Companion.resetCertificates(this);
toaster.longToast("SSL certificates reset");
return false;
});
setupIap(R.string.debug_themes, Inventory.SKU_THEMES);
setupIap(R.string.debug_tasker, Inventory.SKU_TASKER);
setupIap(R.string.debug_dashclock, Inventory.SKU_DASHCLOCK);

@ -10,4 +10,5 @@
<string name="debug_themes">debug_themes</string>
<string name="debug_tasker">debug_tasker</string>
<string name="debug_dashclock">debug_dashclock</string>
<string name="debug_reset_ssl">Reset SSL certificates</string>
</resources>

@ -17,6 +17,10 @@
android:key="@string/p_debug_pro"
android:title="@string/debug_pro"/>
<Preference
android:key="@string/debug_reset_ssl"
android:title="@string/debug_reset_ssl" />
<Preference
android:key="@string/debug_themes"/>

@ -32,6 +32,22 @@
"version": "be6d515db8"
}
},
{
"notice": null,
"copyrightHolder": "bitfire web engineering (Ricki Hirner, Bernhard Stockmann)",
"copyrightStatement": "Copyright &copy; bitfire web engineering (Ricki Hirner, Bernhard Stockmann). All rights reserved.",
"license": "GNU General Public License, Version 3.0",
"licenseUrl": "https://www.gnu.org/licenses/gpl.txt",
"normalizedLicense": "gpl3",
"year": null,
"url": "",
"libraryName": "cert4android",
"artifactId": {
"name": "cert4android",
"group": "com.gitlab.bitfireAT",
"version": "1488e39a66"
}
},
{
"notice": null,
"copyrightHolder": "Android Open Source Project",
@ -45,7 +61,7 @@
"artifactId": {
"name": "coordinatorlayout",
"group": "androidx.coordinatorlayout",
"version": "1.1.0-rc01"
"version": "1.1.0"
}
},
{
@ -445,7 +461,7 @@
"artifactId": {
"name": "annotation",
"group": "androidx.annotation",
"version": "1.1.0"
"version": "1.0.0"
}
},
{
@ -925,7 +941,7 @@
"artifactId": {
"name": "recyclerview",
"group": "androidx.recyclerview",
"version": "1.1.0-rc01"
"version": "1.1.0"
}
},
{
@ -957,7 +973,7 @@
"artifactId": {
"name": "collection",
"group": "androidx.collection",
"version": "1.1.0"
"version": "1.0.0"
}
},
{
@ -1053,7 +1069,7 @@
"artifactId": {
"name": "material",
"group": "com.google.android.material",
"version": "1.1.0-beta02"
"version": "1.2.0-alpha03"
}
},
{
@ -1917,7 +1933,7 @@
"artifactId": {
"name": "viewpager2",
"group": "androidx.viewpager2",
"version": "1.0.0-rc01"
"version": "1.0.0"
}
},
{
@ -1983,6 +1999,166 @@
"group": "androidx.paging",
"version": "2.1.1"
}
},
{
"notice": null,
"copyrightHolder": "JetBrains s.r.o.",
"copyrightStatement": "Copyright &copy; JetBrains s.r.o. All rights reserved.",
"license": "The Apache Software License, Version 2.0",
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt",
"normalizedLicense": "apache2",
"year": null,
"url": "https://github.com/Kotlin/kotlinx.coroutines",
"libraryName": "kotlinx-coroutines-core-common",
"artifactId": {
"name": "kotlinx-coroutines-core-common",
"group": "org.jetbrains.kotlinx",
"version": "1.1.1"
}
},
{
"notice": null,
"copyrightHolder": "Android Open Source Project",
"copyrightStatement": "Copyright &copy; Android Open Source Project. All rights reserved.",
"license": "Apache 2",
"licenseUrl": "https://www.apache.org/licenses/LICENSE-2.0",
"normalizedLicense": "apache2",
"year": null,
"url": "https://conscrypt.org/",
"libraryName": "org.conscrypt:conscrypt-android",
"artifactId": {
"name": "conscrypt-android",
"group": "org.conscrypt",
"version": "2.2.1"
}
},
{
"notice": null,
"copyrightHolder": "JetBrains s.r.o.",
"copyrightStatement": "Copyright &copy; JetBrains s.r.o. All rights reserved.",
"license": "The Apache License, Version 2.0",
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt",
"normalizedLicense": "apache2",
"year": null,
"url": "https://kotlinlang.org/",
"libraryName": "org.jetbrains.kotlin:kotlin-stdlib-jdk8",
"artifactId": {
"name": "kotlin-stdlib-jdk8",
"group": "org.jetbrains.kotlin",
"version": "1.3.61"
}
},
{
"notice": null,
"copyrightHolder": "JetBrains s.r.o.",
"copyrightStatement": "Copyright &copy; JetBrains s.r.o. All rights reserved.",
"license": "The Apache Software License, Version 2.0",
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt",
"normalizedLicense": "apache2",
"year": null,
"url": "https://github.com/Kotlin/kotlinx.coroutines",
"libraryName": "kotlinx-coroutines-android",
"artifactId": {
"name": "kotlinx-coroutines-android",
"group": "org.jetbrains.kotlinx",
"version": "1.1.1"
}
},
{
"notice": null,
"copyrightHolder": "Android Open Source Project",
"copyrightStatement": "Copyright &copy; Android Open Source Project. All rights reserved.",
"license": "The Apache Software License, Version 2.0",
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt",
"normalizedLicense": "apache2",
"year": null,
"url": "",
"libraryName": "databinding-adapters",
"artifactId": {
"name": "databinding-adapters",
"group": "androidx.databinding",
"version": "3.5.3"
}
},
{
"notice": null,
"copyrightHolder": "Android Open Source Project",
"copyrightStatement": "Copyright &copy; Android Open Source Project. All rights reserved.",
"license": "The Apache Software License, Version 2.0",
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt",
"normalizedLicense": "apache2",
"year": null,
"url": "http://developer.android.com/tools/extras/support-library.html",
"libraryName": "Android Lifecycle ViewModel Kotlin Extensions",
"artifactId": {
"name": "lifecycle-viewmodel-ktx",
"group": "androidx.lifecycle",
"version": "2.1.0"
}
},
{
"notice": null,
"copyrightHolder": "Android Open Source Project",
"copyrightStatement": "Copyright &copy; Android Open Source Project. All rights reserved.",
"license": "The Apache Software License, Version 2.0",
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt",
"normalizedLicense": "apache2",
"year": null,
"url": "https://developer.android.com/jetpack/androidx",
"libraryName": "Experimental annotation",
"artifactId": {
"name": "annotation-experimental",
"group": "androidx.annotation",
"version": "1.0.0"
}
},
{
"notice": null,
"copyrightHolder": "JetBrains s.r.o.",
"copyrightStatement": "Copyright &copy; JetBrains s.r.o. All rights reserved.",
"license": "The Apache Software License, Version 2.0",
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt",
"normalizedLicense": "apache2",
"year": null,
"url": "https://github.com/Kotlin/kotlinx.coroutines",
"libraryName": "kotlinx-coroutines-core",
"artifactId": {
"name": "kotlinx-coroutines-core",
"group": "org.jetbrains.kotlinx",
"version": "1.1.1"
}
},
{
"notice": null,
"copyrightHolder": "Android Open Source Project",
"copyrightStatement": "Copyright &copy; Android Open Source Project. All rights reserved.",
"license": "The Apache Software License, Version 2.0",
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt",
"normalizedLicense": "apache2",
"year": null,
"url": "https://developer.android.com/studio",
"libraryName": "Data Binding Base Library",
"artifactId": {
"name": "databinding-common",
"group": "androidx.databinding",
"version": "3.5.3"
}
},
{
"notice": null,
"copyrightHolder": "Android Open Source Project",
"copyrightStatement": "Copyright &copy; Android Open Source Project. All rights reserved.",
"license": "The Apache Software License, Version 2.0",
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt",
"normalizedLicense": "apache2",
"year": null,
"url": "",
"libraryName": "databinding-runtime",
"artifactId": {
"name": "databinding-runtime",
"group": "androidx.databinding",
"version": "3.5.3"
}
}
]
}

@ -16,7 +16,7 @@ public class AddCaldavAccountViewModel extends CompletableViewModel<String> {
run(
() -> {
playServices.updateSecurityProvider(context);
return client.forUrl(url, username, password).getHomeSet();
return client.setForeground().forUrl(url, username, password).getHomeSet();
});
}
}

@ -6,6 +6,9 @@ import static at.bitfire.dav4jvm.XmlUtils.NS_CARDDAV;
import static at.bitfire.dav4jvm.XmlUtils.NS_WEBDAV;
import static java.util.Arrays.asList;
import android.content.Context;
import at.bitfire.cert4android.CustomCertManager;
import at.bitfire.cert4android.CustomCertManager.CustomHostnameVerifier;
import at.bitfire.dav4jvm.BasicDigestAuthHandler;
import at.bitfire.dav4jvm.DavResource;
import at.bitfire.dav4jvm.Property.Name;
@ -23,17 +26,23 @@ import at.bitfire.dav4jvm.property.SupportedCalendarComponentSet;
import com.todoroo.astrid.helper.UUIDHelper;
import java.io.IOException;
import java.io.StringWriter;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.OkHttpClient.Builder;
import okhttp3.internal.tls.OkHostnameVerifier;
import org.tasks.DebugNetworkInterceptor;
import org.tasks.R;
import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavCalendar;
import org.tasks.injection.ForApplication;
import org.tasks.preferences.Preferences;
import org.tasks.security.Encryption;
import org.tasks.ui.DisplayableException;
@ -49,10 +58,16 @@ public class CaldavClient {
private final DebugNetworkInterceptor interceptor;
private final OkHttpClient httpClient;
private final HttpUrl httpUrl;
private final Context context;
private boolean foreground;
@Inject
public CaldavClient(
Encryption encryption, Preferences preferences, DebugNetworkInterceptor interceptor) {
@ForApplication Context context,
Encryption encryption,
Preferences preferences,
DebugNetworkInterceptor interceptor) {
this.context = context;
this.encryption = encryption;
this.preferences = preferences;
this.interceptor = interceptor;
@ -61,16 +76,26 @@ public class CaldavClient {
}
private CaldavClient(
Context context,
Encryption encryption,
Preferences preferences,
DebugNetworkInterceptor interceptor,
String url,
String username,
String password) {
String password,
boolean foreground) throws NoSuchAlgorithmException, KeyManagementException {
this.context = context;
this.encryption = encryption;
this.preferences = preferences;
this.interceptor = interceptor;
CustomCertManager customCertManager = new CustomCertManager(context);
customCertManager.setAppInForeground(foreground);
CustomHostnameVerifier hostnameVerifier =
customCertManager.hostnameVerifier(OkHostnameVerifier.INSTANCE);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { customCertManager }, null);
BasicDigestAuthHandler basicDigestAuthHandler =
new BasicDigestAuthHandler(null, username, password);
Builder builder =
@ -81,6 +106,8 @@ public class CaldavClient {
.cookieJar(new MemoryCookieStore())
.followRedirects(false)
.followSslRedirects(true)
.sslSocketFactory(sslContext.getSocketFactory(), customCertManager)
.hostnameVerifier(hostnameVerifier)
.readTimeout(30, TimeUnit.SECONDS);
if (preferences.isFlipperEnabled()) {
interceptor.add(builder);
@ -89,16 +116,20 @@ public class CaldavClient {
httpUrl = HttpUrl.parse(url);
}
public CaldavClient forAccount(CaldavAccount account) {
public CaldavClient forAccount(CaldavAccount account)
throws NoSuchAlgorithmException, KeyManagementException {
return forUrl(account.getUrl(), account.getUsername(), account.getPassword(encryption));
}
public CaldavClient forCalendar(CaldavAccount account, CaldavCalendar calendar) {
public CaldavClient forCalendar(CaldavAccount account, CaldavCalendar calendar)
throws NoSuchAlgorithmException, KeyManagementException {
return forUrl(calendar.getUrl(), account.getUsername(), account.getPassword(encryption));
}
public CaldavClient forUrl(String url, String username, String password) {
return new CaldavClient(encryption, preferences, interceptor, url, username, password);
public CaldavClient forUrl(String url, String username, String password)
throws KeyManagementException, NoSuchAlgorithmException {
return new CaldavClient(
context, encryption, preferences, interceptor, url, username, password, foreground);
}
private String tryFindPrincipal() throws DavException, IOException {
@ -234,4 +265,9 @@ public class CaldavClient {
OkHttpClient getHttpClient() {
return httpClient;
}
public CaldavClient setForeground() {
foreground = true;
return this;
}
}

@ -39,6 +39,8 @@ import java.io.IOException;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
@ -125,7 +127,9 @@ public class CaldavSynchronizer {
| ConnectException
| UnknownHostException
| UnauthorizedException
| ServiceUnavailableException e) {
| ServiceUnavailableException
| KeyManagementException
| NoSuchAlgorithmException e) {
setError(account, e.getMessage());
} catch (IOException | DavException e) {
setError(account, e.getMessage());
@ -135,7 +139,8 @@ public class CaldavSynchronizer {
}
}
private void synchronize(CaldavAccount account) throws IOException, DavException {
private void synchronize(CaldavAccount account)
throws IOException, DavException, KeyManagementException, NoSuchAlgorithmException {
CaldavClient caldavClient = client.forAccount(account);
List<Response> resources = caldavClient.getCalendars();
Set<String> urls = newHashSet(transform(resources, c -> c.getHref().toString()));

Loading…
Cancel
Save