From 426cc45d3bf7cb23f97b5f603c8c8fa915f16d3a Mon Sep 17 00:00:00 2001 From: M66B Date: Thu, 9 Nov 2023 21:17:52 +0100 Subject: [PATCH] Added optional BC JSSE provider --- app/build.gradle | 1 + .../java/eu/faircode/email/EmailService.java | 15 ++- .../email/FragmentOptionsConnection.java | 12 ++- app/src/main/java/eu/faircode/email/Log.java | 99 ++++++++++--------- .../eu/faircode/email/ServiceSynchronize.java | 2 +- .../layout/fragment_options_connection.xml | 13 ++- app/src/main/res/values/strings.xml | 1 + 7 files changed, 91 insertions(+), 52 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 2b05c981bb..45f2b6c2e3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -716,6 +716,7 @@ dependencies { // https://www.bouncycastle.org/latest_releases.html // https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk15on implementation "org.bouncycastle:bcpkix-jdk15to18:$bouncycastle_version" + implementation "org.bouncycastle:bctls-jdk15to18:$bouncycastle_version" // https://github.com/openid/AppAuth-Android // https://mvnrepository.com/artifact/net.openid/appauth diff --git a/app/src/main/java/eu/faircode/email/EmailService.java b/app/src/main/java/eu/faircode/email/EmailService.java index 3dd953ad4e..cb5119e438 100644 --- a/app/src/main/java/eu/faircode/email/EmailService.java +++ b/app/src/main/java/eu/faircode/email/EmailService.java @@ -46,6 +46,8 @@ import com.sun.mail.util.MailConnectException; import com.sun.mail.util.SocketConnectException; import com.sun.mail.util.TraceOutputStream; +import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -445,7 +447,8 @@ public class EmailService implements AutoCloseable { } } - factory = new SSLSocketFactoryService(host, insecure, ssl_harden, strict, cert_strict, key, chain, fingerprint); + boolean bc = prefs.getBoolean("bouncy_castle", false); + factory = new SSLSocketFactoryService(host, insecure, ssl_harden, strict, cert_strict, bc, key, chain, fingerprint); properties.put("mail." + protocol + ".ssl.socketFactory", factory); properties.put("mail." + protocol + ".socketFactory.fallback", "false"); properties.put("mail." + protocol + ".ssl.checkserveridentity", "false"); @@ -1035,7 +1038,7 @@ public class EmailService implements AutoCloseable { private SSLSocketFactory factory; private X509Certificate certificate; - SSLSocketFactoryService(String host, boolean insecure, boolean ssl_harden, boolean ssl_harden_strict, boolean cert_strict, PrivateKey key, X509Certificate[] chain, String fingerprint) throws GeneralSecurityException { + SSLSocketFactoryService(String host, boolean insecure, boolean ssl_harden, boolean ssl_harden_strict, boolean cert_strict, boolean bc, PrivateKey key, X509Certificate[] chain, String fingerprint) throws GeneralSecurityException { this.server = host; this.secure = !insecure; this.ssl_harden = ssl_harden; @@ -1044,7 +1047,13 @@ public class EmailService implements AutoCloseable { this.trustedFingerprint = fingerprint; // https://developer.android.com/about/versions/oreo/android-8.0-changes.html#security-all - SSLContext sslContext = SSLContext.getInstance(insecure ? "SSL" : "TLS"); + SSLContext sslContext; + String protocol = (insecure ? "SSL" : "TLS"); + if (bc) + sslContext = SSLContext.getInstance(protocol, new BouncyCastleJsseProvider()); + else + sslContext = SSLContext.getInstance(protocol); + Log.i("Using protocol=" + protocol + " bc=" + bc); TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init((KeyStore) null); diff --git a/app/src/main/java/eu/faircode/email/FragmentOptionsConnection.java b/app/src/main/java/eu/faircode/email/FragmentOptionsConnection.java index db38896cb9..71d884a667 100644 --- a/app/src/main/java/eu/faircode/email/FragmentOptionsConnection.java +++ b/app/src/main/java/eu/faircode/email/FragmentOptionsConnection.java @@ -92,6 +92,7 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre private SwitchCompat swSslHardenStrict; private SwitchCompat swCertStrict; private SwitchCompat swOpenSafe; + private SwitchCompat swBouncyCastle; private Button btnManage; private TextView tvNetworkMetered; private TextView tvNetworkRoaming; @@ -110,7 +111,7 @@ 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_harden", "ssl_harden_strict", "cert_strict", "open_safe" + "ssl_harden", "ssl_harden_strict", "cert_strict", "open_safe", "bouncy_castle" }; @Override @@ -144,6 +145,7 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre swSslHardenStrict = view.findViewById(R.id.swSslHardenStrict); swCertStrict = view.findViewById(R.id.swCertStrict); swOpenSafe = view.findViewById(R.id.swOpenSafe); + swBouncyCastle = view.findViewById(R.id.swBouncyCastle); btnManage = view.findViewById(R.id.btnManage); tvNetworkMetered = view.findViewById(R.id.tvNetworkMetered); @@ -348,6 +350,13 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre } }); + swBouncyCastle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { + prefs.edit().putBoolean("bouncy_castle", checked).apply(); + } + }); + final Intent manage = getIntentConnectivity(); PackageManager pm = getContext().getPackageManager(); btnManage.setVisibility( @@ -609,6 +618,7 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre swSslHardenStrict.setEnabled(swSslHarden.isChecked()); swCertStrict.setChecked(prefs.getBoolean("cert_strict", !BuildConfig.PLAY_STORE_RELEASE)); swOpenSafe.setChecked(prefs.getBoolean("open_safe", false)); + swBouncyCastle.setChecked(prefs.getBoolean("bouncy_castle", false)); } catch (Throwable ex) { Log.e(ex); } diff --git a/app/src/main/java/eu/faircode/email/Log.java b/app/src/main/java/eu/faircode/email/Log.java index 37292cb94c..6aefae58da 100644 --- a/app/src/main/java/eu/faircode/email/Log.java +++ b/app/src/main/java/eu/faircode/email/Log.java @@ -124,6 +124,7 @@ import com.sun.mail.util.MailConnectException; import net.openid.appauth.AuthState; import net.openid.appauth.TokenResponse; +import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; import org.json.JSONException; import org.json.JSONObject; @@ -3613,67 +3614,73 @@ public class Log { static SpannableStringBuilder getCiphers() { SpannableStringBuilder ssb = new SpannableStringBuilderEx(); - for (String protocol : new String[]{"SSL", "TLS"}) - try { - int begin = ssb.length(); - ssb.append("Protocol: ").append(protocol); - ssb.setSpan(new StyleSpan(Typeface.BOLD), begin, ssb.length(), 0); - ssb.append("\r\n\r\n"); + for (Provider provider : new Provider[]{null, new BouncyCastleJsseProvider()}) + for (String protocol : new String[]{"SSL", "TLS"}) + try { + int begin = ssb.length(); + ssb.append("Protocol: ").append(protocol) + .append(" ") + .append(provider == null ? "Android" : provider.getClass().getSimpleName()); + ssb.setSpan(new StyleSpan(Typeface.BOLD), begin, ssb.length(), 0); + ssb.append("\r\n\r\n"); - TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - tmf.init((KeyStore) null); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init((KeyStore) null); - ssb.append("Provider: ").append(tmf.getProvider().getName()).append("\r\n"); - ssb.append("Algorithm: ").append(tmf.getAlgorithm()).append("\r\n"); + ssb.append("Provider: ").append(tmf.getProvider().getName()).append("\r\n"); + ssb.append("Algorithm: ").append(tmf.getAlgorithm()).append("\r\n"); - TrustManager[] tms = tmf.getTrustManagers(); - if (tms != null) - for (TrustManager tm : tms) - ssb.append("Manager: ").append(tm.getClass().getName()).append("\r\n"); + TrustManager[] tms = tmf.getTrustManagers(); + if (tms != null) + for (TrustManager tm : tms) + ssb.append("Manager: ").append(tm.getClass().getName()).append("\r\n"); - SSLContext sslContext = SSLContext.getInstance(protocol); + SSLContext sslContext = (provider == null + ? SSLContext.getInstance(protocol) + : SSLContext.getInstance(protocol, provider)); - ssb.append("Context: ").append(sslContext.getProtocol()).append("\r\n\r\n"); - sslContext.init(null, tmf.getTrustManagers(), null); - SSLSocket socket = (SSLSocket) sslContext.getSocketFactory().createSocket(); + ssb.append("Context: ").append(sslContext.getProtocol()).append("\r\n\r\n"); - List protocols = new ArrayList<>(); - protocols.addAll(Arrays.asList(socket.getEnabledProtocols())); + sslContext.init(null, tmf.getTrustManagers(), null); + SSLSocket socket = (SSLSocket) sslContext.getSocketFactory().createSocket(); - for (String p : socket.getSupportedProtocols()) { - boolean enabled = protocols.contains(p); - if (!enabled) - ssb.append('('); - int start = ssb.length(); - ssb.append(p); - if (!enabled) { - ssb.setSpan(new StrikethroughSpan(), start, ssb.length(), 0); - ssb.append(')'); + List protocols = new ArrayList<>(); + protocols.addAll(Arrays.asList(socket.getEnabledProtocols())); + + for (String p : socket.getSupportedProtocols()) { + boolean enabled = protocols.contains(p); + if (!enabled) + ssb.append('('); + int start = ssb.length(); + ssb.append(p); + if (!enabled) { + ssb.setSpan(new StrikethroughSpan(), start, ssb.length(), 0); + ssb.append(')'); + } + ssb.append("\r\n"); } ssb.append("\r\n"); - } - ssb.append("\r\n"); - List ciphers = new ArrayList<>(); - ciphers.addAll(Arrays.asList(socket.getEnabledCipherSuites())); + List ciphers = new ArrayList<>(); + ciphers.addAll(Arrays.asList(socket.getEnabledCipherSuites())); - for (String c : socket.getSupportedCipherSuites()) { - boolean enabled = ciphers.contains(c); - if (!enabled) - ssb.append('('); - int start = ssb.length(); - ssb.append(c); - if (!enabled) { - ssb.setSpan(new StrikethroughSpan(), start, ssb.length(), 0); - ssb.append(')'); + for (String c : socket.getSupportedCipherSuites()) { + boolean enabled = ciphers.contains(c); + if (!enabled) + ssb.append('('); + int start = ssb.length(); + ssb.append(c); + if (!enabled) { + ssb.setSpan(new StrikethroughSpan(), start, ssb.length(), 0); + ssb.append(')'); + } + ssb.append("\r\n"); } ssb.append("\r\n"); + } catch (Throwable ex) { + ssb.append(ex.toString()); } - ssb.append("\r\n"); - } catch (Throwable ex) { - ssb.append(ex.toString()); - } ssb.setSpan(new RelativeSizeSpan(HtmlHelper.FONT_SMALL), 0, ssb.length(), 0); diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java index fa063a3dcc..5b5f47ac70 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java +++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java @@ -170,7 +170,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", "ssl_harden_strict", "cert_strict", // force reconnect + "prefer_ip4", "bind_socket", "standalone_vpn", "tcp_keep_alive", "ssl_harden", "ssl_harden_strict", "cert_strict", "bouncy_castle", // 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 20057cef22..984725af44 100644 --- a/app/src/main/res/layout/fragment_options_connection.xml +++ b/app/src/main/res/layout/fragment_options_connection.xml @@ -519,6 +519,17 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/swOpenSafe" /> + +