diff --git a/build.gradle b/build.gradle index 2268cd4a7..97e5bc524 100644 --- a/build.gradle +++ b/build.gradle @@ -70,7 +70,7 @@ dependencies { implementation 'org.bouncycastle:bcmail-jdk15on:1.64' implementation 'org.gnu.inet:libidn:1.15' implementation 'com.google.zxing:core:3.5.0' - implementation 'org.minidns:minidns-hla:1.0.4' + implementation 'org.minidns:minidns-hla:1.0.5' implementation 'me.leolin:ShortcutBadger:1.1.22@aar' implementation 'org.whispersystems:signal-protocol-android:2.6.2' implementation 'jetty:javax.servlet:5.1.12' diff --git a/src/main/java/eu/siacs/conversations/services/MemorizingTrustManager.java b/src/main/java/eu/siacs/conversations/services/MemorizingTrustManager.java index b58f02cdf..da02cebd8 100644 --- a/src/main/java/eu/siacs/conversations/services/MemorizingTrustManager.java +++ b/src/main/java/eu/siacs/conversations/services/MemorizingTrustManager.java @@ -33,6 +33,7 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; +import android.os.Build; import android.os.Handler; import android.preference.PreferenceManager; import android.util.Base64; @@ -44,9 +45,21 @@ import androidx.core.util.Consumer; import com.google.common.base.Charsets; import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; import com.google.common.io.ByteStreams; import com.google.common.io.CharStreams; +import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; +import eu.siacs.conversations.crypto.BundledTrustManager; +import eu.siacs.conversations.crypto.CombiningTrustManager; +import eu.siacs.conversations.crypto.TrustManagers; +import eu.siacs.conversations.crypto.XmppDomainVerifier; +import eu.siacs.conversations.entities.MTMDecision; +import eu.siacs.conversations.http.HttpConnectionManager; +import eu.siacs.conversations.persistance.FileBackend; +import eu.siacs.conversations.ui.MemorizingActivity; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -81,39 +94,40 @@ import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; -import eu.siacs.conversations.Config; -import eu.siacs.conversations.R; -import eu.siacs.conversations.crypto.XmppDomainVerifier; -import eu.siacs.conversations.entities.MTMDecision; -import eu.siacs.conversations.http.HttpConnectionManager; -import eu.siacs.conversations.persistance.FileBackend; -import eu.siacs.conversations.ui.MemorizingActivity; - /** - * A X509 trust manager implementation which asks the user about invalid - * certificates and memorizes their decision. - *
- * The certificate validity is checked using the system default X509 - * TrustManager, creating a query Dialog if the check fails. - *
- * WARNING: This only works if a dedicated thread is used for - * opening sockets! + * A X509 trust manager implementation which asks the user about invalid certificates and memorizes + * their decision. + * + *
The certificate validity is checked using the system default X509 TrustManager, creating a + * query Dialog if the check fails. + * + *
WARNING: This only works if a dedicated thread is used for opening sockets! */ public class MemorizingTrustManager { - private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.US); + private static final SimpleDateFormat DATE_FORMAT = + new SimpleDateFormat("yyyy-MM-dd", Locale.US); - final static String DECISION_INTENT = "de.duenndns.ssl.DECISION"; - public final static String DECISION_INTENT_ID = DECISION_INTENT + ".decisionId"; - public final static String DECISION_INTENT_CERT = DECISION_INTENT + ".cert"; - public final static String DECISION_TITLE_ID = DECISION_INTENT + ".titleId"; - final static String NO_TRUST_ANCHOR = "Trust anchor for certification path not found."; - private static final Pattern PATTERN_IPV4 = Pattern.compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); - private static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); - private static final Pattern PATTERN_IPV6_6HEX4DEC = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); - private static final Pattern PATTERN_IPV6_HEXCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z"); - private static final Pattern PATTERN_IPV6 = Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z"); - private final static Logger LOGGER = Logger.getLogger(MemorizingTrustManager.class.getName()); + static final String DECISION_INTENT = "de.duenndns.ssl.DECISION"; + public static final String DECISION_INTENT_ID = DECISION_INTENT + ".decisionId"; + public static final String DECISION_INTENT_CERT = DECISION_INTENT + ".cert"; + public static final String DECISION_TITLE_ID = DECISION_INTENT + ".titleId"; + static final String NO_TRUST_ANCHOR = "Trust anchor for certification path not found."; + private static final Pattern PATTERN_IPV4 = + Pattern.compile( + "\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); + private static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED = + Pattern.compile( + "\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); + private static final Pattern PATTERN_IPV6_6HEX4DEC = + Pattern.compile( + "\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); + private static final Pattern PATTERN_IPV6_HEXCOMPRESSED = + Pattern.compile( + "\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z"); + private static final Pattern PATTERN_IPV6 = + Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z"); + private static final Logger LOGGER = Logger.getLogger(MemorizingTrustManager.class.getName()); static String KEYSTORE_DIR = "KeyStore"; static String KEYSTORE_FILE = "KeyStore.bks"; private static int decisionId = 0; @@ -129,21 +143,22 @@ public class MemorizingTrustManager { private final DaneVerifier daneVerifier; /** - * Creates an instance of the MemorizingTrustManager class that falls back to a custom TrustManager. - *
- * You need to supply the application context. This has to be one of: - * - Application - * - Activity - * - Service - *
- * The context is used for file management, to display the dialog / - * notification and for obtaining translated strings. + * Creates an instance of the MemorizingTrustManager class that falls back to a custom + * TrustManager. * - * @param m Context for the application. - * @param defaultTrustManager Delegate trust management to this TM. If null, the user must accept every certificate. + *
You need to supply the application context. This has to be one of: - Application - + * Activity - Service + * + *
The context is used for file management, to display the dialog / notification and for + * obtaining translated strings. + * + * @param context Context for the application. + * @param defaultTrustManager Delegate trust management to this TM. If null, the user must + * accept every certificate. */ - public MemorizingTrustManager(Context m, X509TrustManager defaultTrustManager) { - init(m); + public MemorizingTrustManager( + final Context context, final X509TrustManager defaultTrustManager) { + init(context); this.appTrustManager = getTrustManager(appKeyStore); this.defaultTrustManager = defaultTrustManager; this.daneVerifier = new DaneVerifier(); @@ -151,34 +166,55 @@ public class MemorizingTrustManager { /** * Creates an instance of the MemorizingTrustManager class using the system X509TrustManager. - *
- * You need to supply the application context. This has to be one of: - * - Application - * - Activity - * - Service - *
- * The context is used for file management, to display the dialog / - * notification and for obtaining translated strings. * - * @param m Context for the application. + *
You need to supply the application context. This has to be one of: - Application - + * Activity - Service + * + *
The context is used for file management, to display the dialog / notification and for + * obtaining translated strings. + * + * @param context Context for the application. */ - public MemorizingTrustManager(Context m) { - init(m); + public MemorizingTrustManager(final Context context) { + init(context); this.appTrustManager = getTrustManager(appKeyStore); - this.defaultTrustManager = getTrustManager(null); this.daneVerifier = new DaneVerifier(); + try { + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) { + this.defaultTrustManager = defaultWithBundledLetsEncrypt(context); + } else { + this.defaultTrustManager = TrustManagers.createDefaultTrustManager(); + } + } catch (final NoSuchAlgorithmException + | KeyStoreException + | CertificateException + | IOException e) { + throw new RuntimeException(e); + } + } + + private static X509TrustManager defaultWithBundledLetsEncrypt(final Context context) + throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException { + final BundledTrustManager bundleTrustManager = + BundledTrustManager.builder() + .loadKeyStore( + context.getResources().openRawResource(R.raw.letsencrypt), + "letsencrypt") + .build(); + return CombiningTrustManager.combineWithDefault(bundleTrustManager); } private static boolean isIp(final String server) { - return server != null && ( - PATTERN_IPV4.matcher(server).matches() - || PATTERN_IPV6.matcher(server).matches() - || PATTERN_IPV6_6HEX4DEC.matcher(server).matches() - || PATTERN_IPV6_HEX4DECCOMPRESSED.matcher(server).matches() - || PATTERN_IPV6_HEXCOMPRESSED.matcher(server).matches()); + return server != null + && (PATTERN_IPV4.matcher(server).matches() + || PATTERN_IPV6.matcher(server).matches() + || PATTERN_IPV6_6HEX4DEC.matcher(server).matches() + || PATTERN_IPV6_HEX4DECCOMPRESSED.matcher(server).matches() + || PATTERN_IPV6_HEXCOMPRESSED.matcher(server).matches()); } - private static String getBase64Hash(X509Certificate certificate, String digest) throws CertificateEncodingException { + private static String getBase64Hash(X509Certificate certificate, String digest) + throws CertificateEncodingException { MessageDigest md; try { md = MessageDigest.getInstance(digest); @@ -193,8 +229,7 @@ public class MemorizingTrustManager { StringBuffer si = new StringBuffer(); for (int i = 0; i < data.length; i++) { si.append(String.format("%02x", data[i])); - if (i < data.length - 1) - si.append(":"); + if (i < data.length - 1) si.append(":"); } return si.toString(); } @@ -225,20 +260,22 @@ public class MemorizingTrustManager { } } - void init(final Context m) { - master = m; - masterHandler = new Handler(m.getMainLooper()); - notificationManager = (NotificationManager) master.getSystemService(Context.NOTIFICATION_SERVICE); + void init(final Context context) { + master = context; + masterHandler = new Handler(context.getMainLooper()); + notificationManager = + (NotificationManager) master.getSystemService(Context.NOTIFICATION_SERVICE); Application app; - if (m instanceof Application) { - app = (Application) m; - } else if (m instanceof Service) { - app = ((Service) m).getApplication(); - } else if (m instanceof AppCompatActivity) { - app = ((AppCompatActivity) m).getApplication(); + if (context instanceof Application) { + app = (Application) context; + } else if (context instanceof Service) { + app = ((Service) context).getApplication(); + } else if (context instanceof AppCompatActivity) { + app = ((AppCompatActivity) context).getApplication(); } else - throw new ClassCastException("MemorizingTrustManager context must be either Activity or Service!"); + throw new ClassCastException( + "MemorizingTrustManager context must be either Activity or Service!"); File dir = app.getDir(KEYSTORE_DIR, Context.MODE_PRIVATE); keyStoreFile = new File(dir + File.separator + KEYSTORE_FILE); @@ -263,12 +300,9 @@ public class MemorizingTrustManager { /** * Removes the given certificate from MTMs key store. * - *
- * WARNING: this does not immediately invalidate the certificate. It is - * well possible that (a) data is transmitted over still existing connections or - * (b) new connections are created using TLS renegotiation, without a new cert - * check. - *
+ *WARNING: this does not immediately invalidate the certificate. It is well possible
+ * that (a) data is transmitted over still existing connections or (b) new connections are
+ * created using TLS renegotiation, without a new cert check.
*
* @param alias the certificate's alias as returned by {@link #getCertificates()}.
* @throws KeyStoreException if the certificate could not be deleted.
@@ -278,20 +312,21 @@ public class MemorizingTrustManager {
keyStoreUpdated();
}
- X509TrustManager getTrustManager(KeyStore ks) {
+ private X509TrustManager getTrustManager(final KeyStore keyStore) {
+ Preconditions.checkNotNull(keyStore);
try {
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
- tmf.init(ks);
+ tmf.init(keyStore);
for (TrustManager t : tmf.getTrustManagers()) {
if (t instanceof X509TrustManager) {
return (X509TrustManager) t;
}
}
- } catch (Exception e) {
+ } catch (final Exception e) {
// Here, we are covering up errors. It might be more useful
// however to throw them out of the constructor so the
// embedding app knows something went wrong.
- LOGGER.log(Level.SEVERE, "getTrustManager(" + ks + ")", e);
+ LOGGER.log(Level.SEVERE, "getTrustManager(" + keyStore + ")", e);
}
return null;
}
@@ -364,10 +399,18 @@ public class MemorizingTrustManager {
}
}
-
- private void checkCertTrusted(X509Certificate[] chain, String authType, String domain, boolean isServer, boolean interactive, String verifiedHostname, int port, Consumer