diff --git a/app/src/main/java/eu/faircode/email/FragmentOptionsConnection.java b/app/src/main/java/eu/faircode/email/FragmentOptionsConnection.java index f42025ee3f..19e566b0d0 100644 --- a/app/src/main/java/eu/faircode/email/FragmentOptionsConnection.java +++ b/app/src/main/java/eu/faircode/email/FragmentOptionsConnection.java @@ -56,6 +56,7 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre private Spinner spDownload; private SwitchCompat swRoaming; private SwitchCompat swRlah; + private SwitchCompat swSslHarden; private SwitchCompat swSocks; private EditText etSocks; private Button btnSocks; @@ -64,7 +65,7 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre private TextView tvConnectionRoaming; private final static String[] RESET_OPTIONS = new String[]{ - "metered", "download", "roaming", "rlah", "socks_enabled", "socks_proxy" + "metered", "download", "roaming", "rlah", "ssl_harden", "socks_enabled", "socks_proxy" }; @Override @@ -81,6 +82,7 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre spDownload = view.findViewById(R.id.spDownload); swRoaming = view.findViewById(R.id.swRoaming); swRlah = view.findViewById(R.id.swRlah); + swSslHarden = view.findViewById(R.id.swSslHarden); swSocks = view.findViewById(R.id.swSocks); etSocks = view.findViewById(R.id.etSocks); btnSocks = view.findViewById(R.id.btnSocks); @@ -129,6 +131,13 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre } }); + swSslHarden.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { + prefs.edit().putBoolean("ssl_harden", checked).apply(); + } + }); + swSocks.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { @@ -246,6 +255,7 @@ public class FragmentOptionsConnection extends FragmentBase implements SharedPre swRoaming.setChecked(prefs.getBoolean("roaming", true)); swRlah.setChecked(prefs.getBoolean("rlah", true)); + swSslHarden.setChecked(prefs.getBoolean("ssl_harden", false)); swSocks.setChecked(prefs.getBoolean("socks_enabled", false)); etSocks.setText(prefs.getString("socks_proxy", null)); etSocks.setEnabled(swSocks.isChecked()); diff --git a/app/src/main/java/eu/faircode/email/MailService.java b/app/src/main/java/eu/faircode/email/MailService.java index a066008dd9..2379226e48 100644 --- a/app/src/main/java/eu/faircode/email/MailService.java +++ b/app/src/main/java/eu/faircode/email/MailService.java @@ -4,7 +4,6 @@ import android.accounts.Account; import android.accounts.AccountManager; import android.content.Context; import android.content.SharedPreferences; -import android.os.Build; import android.text.TextUtils; import androidx.annotation.NonNull; @@ -49,6 +48,7 @@ import java.util.Map; import java.util.Properties; import java.util.concurrent.ExecutorService; import java.util.concurrent.Semaphore; +import java.util.regex.Pattern; import javax.mail.AuthenticationFailedException; import javax.mail.Folder; @@ -58,9 +58,7 @@ import javax.mail.Service; import javax.mail.Session; import javax.mail.Store; import javax.mail.event.StoreListener; -import javax.net.ssl.SNIHostName; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; @@ -71,6 +69,7 @@ public class MailService implements AutoCloseable { private Context context; private String protocol; private boolean insecure; + private boolean harden; private boolean useip; private boolean debug; private Properties properties; @@ -93,7 +92,12 @@ public class MailService implements AutoCloseable { private static final int APPEND_BUFFER_SIZE = 4 * 1024 * 1024; // bytes + private static final List SSL_PROTOCOL_BLACKLIST = Collections.unmodifiableList(Arrays.asList( + "SSLv2", "SSLv3", "TLSv1", "TLSv1.1" + )); + private MailService() { + // Prevent instantiation } MailService(Context context, String protocol, String realm, boolean insecure, boolean check, boolean debug) throws NoSuchProviderException { @@ -105,6 +109,8 @@ public class MailService implements AutoCloseable { properties = MessageHelper.getSessionProperties(); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + this.harden = prefs.getBoolean("ssl_harden", false); + boolean socks_enabled = prefs.getBoolean("socks_enabled", false); String socks_proxy = prefs.getString("socks_proxy", "localhost:9050"); @@ -230,7 +236,7 @@ public class MailService implements AutoCloseable { String fingerprint) throws MessagingException { SSLSocketFactoryService factory = null; try { - factory = new SSLSocketFactoryService(host, insecure, fingerprint); + factory = new SSLSocketFactoryService(host, insecure, harden, fingerprint); properties.put("mail." + protocol + ".ssl.socketFactory", factory); properties.put("mail." + protocol + ".socketFactory.fallback", "false"); properties.put("mail." + protocol + ".ssl.checkserveridentity", "false"); @@ -533,13 +539,15 @@ public class MailService 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 harden; private String trustedFingerprint; private SSLSocketFactory factory; private X509Certificate certificate; - SSLSocketFactoryService(String host, boolean insecure, String fingerprint) throws GeneralSecurityException { + SSLSocketFactoryService(String host, boolean insecure, boolean harden, String fingerprint) throws GeneralSecurityException { this.server = host; this.secure = !insecure; + this.harden = harden; this.trustedFingerprint = fingerprint; SSLContext sslContext = SSLContext.getInstance("TLS"); @@ -651,19 +659,31 @@ public class MailService implements AutoCloseable { } private Socket configure(Socket socket) { -/* - if (socket instanceof SSLSocket - && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - SSLParameters params = ((SSLSocket) socket).getSSLParameters(); - if (params == null) - params = new SSLParameters(); - Log.i("SNI server names=" + params.getServerNames()); - params.setEndpointIdentificationAlgorithm("HTTPS"); - params.setServerNames(Arrays.asList(new SNIHostName(server))); - Log.i("SNI server name=" + params.getServerNames().get(0)); - ((SSLSocket) socket).setSSLParameters(params); + if (harden && socket instanceof SSLSocket) { + // https://developer.android.com/reference/javax/net/ssl/SSLSocket.html + SSLSocket sslSocket = (SSLSocket) socket; + + List protocols = new ArrayList<>(); + for (String protocol : sslSocket.getEnabledProtocols()) + if (SSL_PROTOCOL_BLACKLIST.contains(protocol)) + Log.i("SSL disabling protocol=" + protocol); + else + protocols.add(protocol); + Log.i("SSL protocols=" + TextUtils.join(",", protocols)); + sslSocket.setEnabledProtocols(protocols.toArray(new String[0])); + + ArrayList ciphers = new ArrayList<>(); + Pattern pattern = Pattern.compile(".*(_DES|DH_|DSS|EXPORT|MD5|NULL|RC4|TLS_FALLBACK_SCSV).*"); + for (String cipher : sslSocket.getEnabledCipherSuites()) { + if (pattern.matcher(cipher).matches()) + Log.i("SSL disabling cipher=" + cipher); + else + ciphers.add(cipher); + } + Log.i("SSL ciphers=" + TextUtils.join(",", ciphers)); + sslSocket.setEnabledCipherSuites(ciphers.toArray(new String[0])); } -*/ + return socket; } diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java index 562f311da6..b2cfdff515 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java +++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java @@ -116,6 +116,7 @@ public class ServiceSynchronize extends ServiceBase implements SharedPreferences private static final List PREF_RELOAD = Collections.unmodifiableList(Arrays.asList( "metered", "roaming", "rlah", // force reconnect + "ssl_harden", // force reconnect "socks_enabled", "socks_proxy", // force reconnect "subscribed_only", // force folder sync "badge", "unseen_ignored", // force update badge/widget diff --git a/app/src/main/res/layout/fragment_options_connection.xml b/app/src/main/res/layout/fragment_options_connection.xml index 1ed5b5b0ed..4e64e5cef4 100644 --- a/app/src/main/res/layout/fragment_options_connection.xml +++ b/app/src/main/res/layout/fragment_options_connection.xml @@ -119,6 +119,30 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/swRlah" /> + + + + Automatically download messages and attachments on a metered connection up to Download messages and attachments while roaming Roam like at home + Harden SSL connections Use SOCKS proxy Manage connectivity @@ -423,6 +424,7 @@ Metered connections are generally mobile connections or paid Wi-Fi hotspots Disabling this option will disable receiving and sending messages on mobile internet connections Assuming no roaming within the EU + Enabling this will disable weak SSL protocols and ciphers Using a remote proxy server is insecure because proxy connections are not encrypted Messages headers will always be fetched when roaming. You can use the device\'s roaming setting to disable internet while roaming.