diff --git a/app/src/main/java/eu/faircode/email/EmailService.java b/app/src/main/java/eu/faircode/email/EmailService.java index df5949281a..80ccca054d 100644 --- a/app/src/main/java/eu/faircode/email/EmailService.java +++ b/app/src/main/java/eu/faircode/email/EmailService.java @@ -63,6 +63,7 @@ import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.Principal; import java.security.PrivateKey; +import java.security.cert.CertPathValidatorException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; @@ -104,6 +105,7 @@ public class EmailService implements AutoCloseable { private Context context; private String protocol; private boolean insecure; + private boolean anchor; private int purpose; private boolean harden; private boolean useip; @@ -183,6 +185,7 @@ public class EmailService implements AutoCloseable { prefs.edit().putBoolean("protocol", false).apply(); this.log = prefs.getBoolean("protocol", false); this.level = prefs.getInt("log_level", Log.getDefaultLogLevel()); + this.anchor = prefs.getBoolean("ssl_anchor", !BuildConfig.PLAY_STORE_RELEASE); this.harden = prefs.getBoolean("ssl_harden", false); boolean auth_plain = prefs.getBoolean("auth_plain", true); @@ -404,7 +407,7 @@ public class EmailService implements AutoCloseable { } } - factory = new SSLSocketFactoryService(host, insecure, harden, key, chain, fingerprint); + factory = new SSLSocketFactoryService(host, insecure, anchor, harden, key, chain, fingerprint); properties.put("mail." + protocol + ".ssl.socketFactory", factory); properties.put("mail." + protocol + ".socketFactory.fallback", "false"); properties.put("mail." + protocol + ".ssl.checkserveridentity", "false"); @@ -942,14 +945,16 @@ public class EmailService implements AutoCloseable { // openssl s_client -connect host:port < /dev/null 2>/dev/null | openssl x509 -fingerprint -noout -in /dev/stdin private String server; private boolean secure; + private boolean anchor; private boolean harden; private String trustedFingerprint; private SSLSocketFactory factory; private X509Certificate certificate; - SSLSocketFactoryService(String host, boolean insecure, boolean harden, PrivateKey key, X509Certificate[] chain, String fingerprint) throws GeneralSecurityException { + SSLSocketFactoryService(String host, boolean insecure, boolean anchor, boolean harden, PrivateKey key, X509Certificate[] chain, String fingerprint) throws GeneralSecurityException { this.server = host; this.secure = !insecure; + this.anchor = anchor; this.harden = harden; this.trustedFingerprint = fingerprint; @@ -987,13 +992,23 @@ public class EmailService implements AutoCloseable { // Check certificates try { + Log.i("Auth type=" + authType); rtm.checkServerTrusted(chain, authType); } catch (CertificateException ex) { Principal principal = certificate.getSubjectDN(); if (principal == null) throw ex; - else - throw new CertificateException(principal.getName(), ex); + else { + if (ex.getCause() instanceof CertPathValidatorException && + "Trust anchor for certification path not found." + .equals(ex.getCause().getMessage())) { + if (anchor) + throw new CertificateException(principal.getName(), ex); + else + Log.e(ex); + } else + throw new CertificateException(principal.getName(), ex); + } } // Check host name diff --git a/app/src/main/java/eu/faircode/email/FragmentOptionsConnection.java b/app/src/main/java/eu/faircode/email/FragmentOptionsConnection.java index fba3922bcf..dcb109b7ae 100644 --- a/app/src/main/java/eu/faircode/email/FragmentOptionsConnection.java +++ b/app/src/main/java/eu/faircode/email/FragmentOptionsConnection.java @@ -70,6 +70,7 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre private SwitchCompat swStandaloneVpn; private SwitchCompat swTcpKeepAlive; private TextView tvTcpKeepAliveHint; + private SwitchCompat swSslAnchor; private SwitchCompat swSslHarden; private Button btnManage; private TextView tvNetworkMetered; @@ -83,7 +84,8 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre "metered", "download", "roaming", "rlah", "download_headers", "download_eml", "require_validated", "vpn_only", - "timeout", "prefer_ip4", "bind_socket", "standalone_vpn", "tcp_keep_alive", "ssl_harden" + "timeout", "prefer_ip4", "bind_socket", "standalone_vpn", "tcp_keep_alive", + "ssl_anchor", "ssl_harden" }; @Override @@ -110,6 +112,7 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre swStandaloneVpn = view.findViewById(R.id.swStandaloneVpn); swTcpKeepAlive = view.findViewById(R.id.swTcpKeepAlive); tvTcpKeepAliveHint = view.findViewById(R.id.tvTcpKeepAliveHint); + swSslAnchor = view.findViewById(R.id.swSslAnchor); swSslHarden = view.findViewById(R.id.swSslHarden); btnManage = view.findViewById(R.id.btnManage); @@ -256,6 +259,13 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre } }); + swSslAnchor.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { + prefs.edit().putBoolean("ssl_anchor", checked).apply(); + } + }); + swSslHarden.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { @@ -373,6 +383,7 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre swBindSocket.setChecked(prefs.getBoolean("bind_socket", false)); swStandaloneVpn.setChecked(prefs.getBoolean("standalone_vpn", false)); swTcpKeepAlive.setChecked(prefs.getBoolean("tcp_keep_alive", false)); + swSslAnchor.setChecked(prefs.getBoolean("ssl_anchor", !BuildConfig.PLAY_STORE_RELEASE)); swSslHarden.setChecked(prefs.getBoolean("ssl_harden", false)); } diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java index 4cadb151d3..391b85b266 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java +++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java @@ -151,7 +151,7 @@ public class ServiceSynchronize extends ServiceBase implements SharedPreferences "sync_folders", "sync_shared_folders", "download_headers", "download_eml", - "prefer_ip4", "bind_socket", "standalone_vpn", "tcp_keep_alive", "ssl_harden", // force reconnect + "prefer_ip4", "bind_socket", "standalone_vpn", "tcp_keep_alive", "ssl_anchor", "ssl_harden", // force reconnect "experiments", "debug", "protocol", // force reconnect "auth_plain", "auth_login", "auth_ntlm", "auth_sasl", // force reconnect "keep_alive_poll", "empty_pool", "idle_done", // force reconnect diff --git a/app/src/main/res/layout/fragment_options_connection.xml b/app/src/main/res/layout/fragment_options_connection.xml index 862f10be21..ba61a2347d 100644 --- a/app/src/main/res/layout/fragment_options_connection.xml +++ b/app/src/main/res/layout/fragment_options_connection.xml @@ -379,6 +379,29 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/swTcpKeepAlive" /> + + + + Bind sockets to the active network Standalone VPN TCP keep alive + Anchor SSL connections Harden SSL connections Manage connectivity @@ -755,6 +756,7 @@ Enabling this can cause connection problems on some devices This can result in not synchronizing messages, for example when using a VPN, but also in other situations The read/write timeout will be set to the double of the connection timeout. Higher values will result in more battery use. + Enabling this will check the root certificate of server certificate chains Enabling this will disable weak SSL protocols and ciphers, which can lead to connection problems Messages headers will always be fetched when roaming. You can use the device\'s roaming setting to disable internet while roaming.