From d78ad87c5f29736e39529e05f483263f4a6240aa Mon Sep 17 00:00:00 2001 From: M66B Date: Wed, 20 Dec 2023 10:24:53 +0100 Subject: [PATCH] Added option to disable host name checks --- .../java/eu/faircode/email/EmailService.java | 9 ++- .../email/FragmentOptionsConnection.java | 13 +++- .../java/eu/faircode/email/SSLHelper.java | 67 ++++++++++--------- .../eu/faircode/email/ServiceSynchronize.java | 2 +- .../layout/fragment_options_connection.xml | 14 +++- app/src/main/res/values/strings.xml | 1 + 6 files changed, 68 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/eu/faircode/email/EmailService.java b/app/src/main/java/eu/faircode/email/EmailService.java index 4a6b52bfb0..c01ff78e39 100644 --- a/app/src/main/java/eu/faircode/email/EmailService.java +++ b/app/src/main/java/eu/faircode/email/EmailService.java @@ -103,6 +103,7 @@ public class EmailService implements AutoCloseable { private boolean ssl_harden; private boolean ssl_harden_strict; private boolean cert_strict; + private boolean check_names; private boolean useip; private String ehlo; private boolean log; @@ -190,6 +191,7 @@ public class EmailService implements AutoCloseable { this.ssl_harden = prefs.getBoolean("ssl_harden", false); this.ssl_harden_strict = prefs.getBoolean("ssl_harden_strict", false); this.cert_strict = prefs.getBoolean("cert_strict", true); + this.check_names = prefs.getBoolean("check_names", !BuildConfig.PLAY_STORE_RELEASE); boolean auth_plain = prefs.getBoolean("auth_plain", true); boolean auth_login = prefs.getBoolean("auth_login", true); @@ -449,7 +451,8 @@ public class EmailService implements AutoCloseable { boolean bc = prefs.getBoolean("bouncy_castle", false); boolean fips = prefs.getBoolean("bc_fips", false); - factory = new SSLSocketFactoryService(host, insecure, ssl_harden, strict, cert_strict, bc, fips, key, chain, fingerprint); + factory = new SSLSocketFactoryService( + host, insecure, ssl_harden, strict, cert_strict, check_names, bc, fips, key, chain, fingerprint); properties.put("mail." + protocol + ".ssl.socketFactory", factory); properties.put("mail." + protocol + ".socketFactory.fallback", "false"); properties.put("mail." + protocol + ".ssl.checkserveridentity", "false"); @@ -1037,7 +1040,7 @@ public class EmailService implements AutoCloseable { private X509Certificate certificate; SSLSocketFactoryService(String host, boolean insecure, - boolean ssl_harden, boolean ssl_harden_strict, boolean cert_strict, + boolean ssl_harden, boolean ssl_harden_strict, boolean cert_strict, boolean check_names, boolean bc, boolean fips, PrivateKey key, X509Certificate[] chain, String fingerprint) throws GeneralSecurityException { this.server = host; @@ -1047,7 +1050,7 @@ public class EmailService implements AutoCloseable { this.cert_strict = cert_strict; this.trustedFingerprint = fingerprint; - TrustManager[] tms = SSLHelper.getTrustManagers(server, secure, cert_strict, trustedFingerprint, + TrustManager[] tms = SSLHelper.getTrustManagers(server, secure, cert_strict, check_names, trustedFingerprint, new SSLHelper.ITrust() { @Override public void checkServerTrusted(X509Certificate[] chain) { diff --git a/app/src/main/java/eu/faircode/email/FragmentOptionsConnection.java b/app/src/main/java/eu/faircode/email/FragmentOptionsConnection.java index b147b9a851..aeefca1842 100644 --- a/app/src/main/java/eu/faircode/email/FragmentOptionsConnection.java +++ b/app/src/main/java/eu/faircode/email/FragmentOptionsConnection.java @@ -100,6 +100,7 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre private SwitchCompat swSslHarden; private SwitchCompat swSslHardenStrict; private SwitchCompat swCertStrict; + private SwitchCompat swCheckNames; private SwitchCompat swOpenSafe; private SwitchCompat swHttpRedirect; private SwitchCompat swBouncyCastle; @@ -123,7 +124,8 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre "download_headers", "download_eml", "download_plain", "require_validated", "require_validated_captive", "vpn_only", "timeout", "prefer_ip4", "bind_socket", "standalone_vpn", "tcp_keep_alive", - "ssl_update", "ssl_harden", "ssl_harden_strict", "cert_strict", "open_safe", "http_redirect", + "ssl_update", "ssl_harden", "ssl_harden_strict", "cert_strict", "check_names", + "open_safe", "http_redirect", "bouncy_castle", "bc_fips" }; @@ -159,6 +161,7 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre swSslHarden = view.findViewById(R.id.swSslHarden); swSslHardenStrict = view.findViewById(R.id.swSslHardenStrict); swCertStrict = view.findViewById(R.id.swCertStrict); + swCheckNames = view.findViewById(R.id.swCheckNames); swOpenSafe = view.findViewById(R.id.swOpenSafe); swHttpRedirect = view.findViewById(R.id.swHttpRedirect); swBouncyCastle = view.findViewById(R.id.swBouncyCastle); @@ -376,6 +379,13 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre } }); + swCheckNames.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { + prefs.edit().putBoolean("check_names", checked).apply(); + } + }); + swOpenSafe.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { @@ -691,6 +701,7 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre swSslHardenStrict.setChecked(prefs.getBoolean("ssl_harden_strict", false)); swSslHardenStrict.setEnabled(swSslHarden.isChecked()); swCertStrict.setChecked(prefs.getBoolean("cert_strict", true)); + swCheckNames.setChecked(prefs.getBoolean("check_names", !BuildConfig.PLAY_STORE_RELEASE)); swOpenSafe.setChecked(prefs.getBoolean("open_safe", false)); swHttpRedirect.setChecked(prefs.getBoolean("http_redirect", true)); swBouncyCastle.setChecked(prefs.getBoolean("bouncy_castle", false)); diff --git a/app/src/main/java/eu/faircode/email/SSLHelper.java b/app/src/main/java/eu/faircode/email/SSLHelper.java index ee9c1b1992..d4c0b27d1e 100644 --- a/app/src/main/java/eu/faircode/email/SSLHelper.java +++ b/app/src/main/java/eu/faircode/email/SSLHelper.java @@ -20,7 +20,8 @@ import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; public class SSLHelper { - static TrustManager[] getTrustManagers(String server, boolean secure, boolean cert_strict, String trustedFingerprint, ITrust intf) { + static TrustManager[] getTrustManagers( + String server, boolean secure, boolean cert_strict, boolean check_names, String trustedFingerprint, ITrust intf) { TrustManagerFactory tmf; try { tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); @@ -85,39 +86,41 @@ public class SSLHelper { } // Check host name - List names = EntityCertificate.getDnsNames(chain[0]); - if (EntityCertificate.matches(server, names)) - return; - - // Fallback: check server/certificate IP address - if (!cert_strict) - try { - InetAddress ip = InetAddress.getByName(server); - Log.i("Checking server ip=" + ip); - for (String name : names) { - if (name.startsWith("*.")) - name = name.substring(2); - Log.i("Checking cert name=" + name); - - try { - for (InetAddress addr : InetAddress.getAllByName(name)) - if (Arrays.equals(ip.getAddress(), addr.getAddress())) { - Log.i("Accepted " + name + " for " + server); - return; - } - } catch (UnknownHostException ex) { - Log.w(ex); + if (check_names) { + List names = EntityCertificate.getDnsNames(chain[0]); + if (EntityCertificate.matches(server, names)) + return; + + // Fallback: check server/certificate IP address + if (!cert_strict) + try { + InetAddress ip = InetAddress.getByName(server); + Log.i("Checking server ip=" + ip); + for (String name : names) { + if (name.startsWith("*.")) + name = name.substring(2); + Log.i("Checking cert name=" + name); + + try { + for (InetAddress addr : InetAddress.getAllByName(name)) + if (Arrays.equals(ip.getAddress(), addr.getAddress())) { + Log.i("Accepted " + name + " for " + server); + return; + } + } catch (UnknownHostException ex) { + Log.w(ex); + } } + } catch (UnknownHostException ex) { + Log.w(ex); + } catch (Throwable ex) { + Log.e(ex); } - } catch (UnknownHostException ex) { - Log.w(ex); - } catch (Throwable ex) { - Log.e(ex); - } - - String error = server + " not in certificate: " + TextUtils.join(",", names); - Log.i(error); - throw new CertificateException(error); + + String error = server + " not in certificate: " + TextUtils.join(",", names); + Log.i(error); + throw new CertificateException(error); + } } } diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java index 4522c590ee..c281a53032 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java +++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java @@ -172,7 +172,7 @@ public class ServiceSynchronize extends ServiceBase implements SharedPreferences "sync_shared_folders", "download_headers", "download_eml", "prefer_ip4", "bind_socket", "standalone_vpn", "tcp_keep_alive", // force reconnect - "ssl_harden", "ssl_harden_strict", "cert_strict", "bouncy_castle", "bc_fips", // force reconnect + "ssl_harden", "ssl_harden_strict", "cert_strict", "check_names", "bouncy_castle", "bc_fips", // force reconnect "experiments", "debug", "protocol", // force reconnect "auth_plain", "auth_login", "auth_ntlm", "auth_sasl", "auth_apop", // 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 a3f5d39ff9..fa29200b20 100644 --- a/app/src/main/res/layout/fragment_options_connection.xml +++ b/app/src/main/res/layout/fragment_options_connection.xml @@ -533,6 +533,18 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/swCertStrict" /> + + Harden SSL connections Require TLS 1.3 Strict certificate checking + Check host names against server certificates Open secure connections only Allow connection redirection Use Bouncy Castle\'s secure socket provider (JSSE)