diff options
15 files changed, 235 insertions, 623 deletions
diff --git a/libs/MemorizingTrustManager/build.gradle b/libs/MemorizingTrustManager/build.gradle index 12072b81f..46d464a33 100644 --- a/libs/MemorizingTrustManager/build.gradle +++ b/libs/MemorizingTrustManager/build.gradle @@ -3,18 +3,18 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.2' + classpath 'com.android.tools.build:gradle:2.0.0' } } -apply plugin: 'android-library' +apply plugin: 'com.android.library' android { - compileSdkVersion 19 - buildToolsVersion "19.1" + compileSdkVersion 24 + buildToolsVersion "23.0.3" defaultConfig { - minSdkVersion 7 - targetSdkVersion 19 + minSdkVersion 14 + targetSdkVersion 24 } sourceSets { diff --git a/libs/MemorizingTrustManager/example/AndroidManifest.xml b/libs/MemorizingTrustManager/example/AndroidManifest.xml deleted file mode 100644 index ebc664d65..000000000 --- a/libs/MemorizingTrustManager/example/AndroidManifest.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="de.duenndns.mtmexample" - android:versionCode="1" - android:versionName="1.0"> - - <uses-sdk - android:minSdkVersion="3" - android:targetSdkVersion="19" /> - - <uses-permission android:name="android.permission.INTERNET" /> - - <application - android:label="@string/app_name" - android:icon="@android:drawable/ic_lock_lock"> - <activity - android:name=".MTMExample" - android:configChanges="keyboardHidden|orientation|screenSize|screenLayout" - android:label="@string/app_name"> - - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - </activity> - - <!-- ADD THE FOLLOWING TO YOUR MANIFEST: --> - <activity - android:name="de.duenndns.ssl.MemorizingActivity" - android:theme="@android:style/Theme.Translucent.NoTitleBar" /> - </application> -</manifest> diff --git a/libs/MemorizingTrustManager/example/ant.properties b/libs/MemorizingTrustManager/example/ant.properties deleted file mode 100644 index 2f48b3655..000000000 --- a/libs/MemorizingTrustManager/example/ant.properties +++ /dev/null @@ -1,14 +0,0 @@ -# This file is used to override default values used by the Ant build system. -# -# This file must be checked in Version Control Systems, as it is -# integral to the build system of your project. -# This file is only used by the Ant script. -# You can use this to override default values such as -# 'source.dir' for the location of your java source folder and -# 'out.dir' for the location of your output folder. -# You can also use it define how the release builds are signed by declaring -# the following properties: -# 'key.store' for the location of your keystore and -# 'key.alias' for the name of the key to use. -# The password will be asked during the build when you use the 'release' target. -application.package=de.duenndns.mtmexample diff --git a/libs/MemorizingTrustManager/example/build.gradle b/libs/MemorizingTrustManager/example/build.gradle deleted file mode 100644 index a07fbe87b..000000000 --- a/libs/MemorizingTrustManager/example/build.gradle +++ /dev/null @@ -1,23 +0,0 @@ -apply plugin: 'android' - -dependencies { - compile rootProject -} - -android { - compileSdkVersion 19 - buildToolsVersion "19.1" - defaultConfig { - minSdkVersion 7 - targetSdkVersion 19 - } - - sourceSets { - main { - manifest.srcFile 'AndroidManifest.xml' - java.srcDirs = ['src'] - res.srcDirs = ['res'] - } - } - -} diff --git a/libs/MemorizingTrustManager/example/build.xml b/libs/MemorizingTrustManager/example/build.xml deleted file mode 100644 index 0c61a2917..000000000 --- a/libs/MemorizingTrustManager/example/build.xml +++ /dev/null @@ -1,91 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project name="MTMExample" default="help"> - - <!-- The local.properties file is created and updated by the 'android' tool. - It contains the path to the SDK. It should *NOT* be checked into - Version Control Systems. --> - <property file="local.properties" /> - - <!-- The ant.properties file can be created by you. It is only edited by the - 'android' tool to add properties to it. - This is the place to change some Ant specific build properties. - Here are some properties you may want to change/update: - - source.dir - The name of the source directory. Default is 'src'. - out.dir - The name of the output directory. Default is 'bin'. - - For other overridable properties, look at the beginning of the rules - files in the SDK, at tools/ant/build.xml - - Properties related to the SDK location or the project target should - be updated using the 'android' tool with the 'update' action. - - This file is an integral part of the build system for your - application and should be checked into Version Control Systems. - - --> - <property file="ant.properties" /> - - <!-- if sdk.dir was not set from one of the property file, then - get it from the ANDROID_HOME env var. - This must be done before we load project.properties since - the proguard config can use sdk.dir --> - <property environment="env" /> - <condition property="sdk.dir" value="${env.ANDROID_HOME}"> - <isset property="env.ANDROID_HOME" /> - </condition> - - <!-- The project.properties file is created and updated by the 'android' - tool, as well as ADT. - - This contains project specific properties such as project target, and library - dependencies. Lower level build properties are stored in ant.properties - (or in .classpath for Eclipse projects). - - This file is an integral part of the build system for your - application and should be checked into Version Control Systems. --> - <loadproperties srcFile="project.properties" /> - - <!-- quick check on sdk.dir --> - <fail - message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable." - unless="sdk.dir" /> - - <!-- - Import per project custom build rules if present at the root of the project. - This is the place to put custom intermediary targets such as: - -pre-build - -pre-compile - -post-compile (This is typically used for code obfuscation. - Compiled code location: ${out.classes.absolute.dir} - If this is not done in place, override ${out.dex.input.absolute.dir}) - -post-package - -post-build - -pre-clean - --> - <import file="custom_rules.xml" optional="true" /> - - <!-- Import the actual build file. - - To customize existing targets, there are two options: - - Customize only one target: - - copy/paste the target into this file, *before* the - <import> task. - - customize it to your needs. - - Customize the whole content of build.xml - - copy/paste the content of the rules files (minus the top node) - into this file, replacing the <import> task. - - customize to your needs. - - *********************** - ****** IMPORTANT ****** - *********************** - In all cases you must update the value of version-tag below to read 'custom' instead of an integer, - in order to avoid having your file be overridden by tools such as "android update project" - --> - <!-- version-tag: 1 --> - <import file="${sdk.dir}/tools/ant/build.xml" /> - -</project> diff --git a/libs/MemorizingTrustManager/example/proguard-project.txt b/libs/MemorizingTrustManager/example/proguard-project.txt deleted file mode 100644 index f2fe1559a..000000000 --- a/libs/MemorizingTrustManager/example/proguard-project.txt +++ /dev/null @@ -1,20 +0,0 @@ -# To enable ProGuard in your project, edit project.properties -# to define the proguard.config property as described in that file. -# -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in ${sdk.dir}/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the ProGuard -# include property in project.properties. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/libs/MemorizingTrustManager/example/project.properties b/libs/MemorizingTrustManager/example/project.properties deleted file mode 100644 index be830d977..000000000 --- a/libs/MemorizingTrustManager/example/project.properties +++ /dev/null @@ -1,11 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system use, -# "ant.properties", and override values to adapt the script to your -# project structure. -android.library.reference.1=../ -# Project target. -target=android-19 diff --git a/libs/MemorizingTrustManager/example/res/layout/mtmexample.xml b/libs/MemorizingTrustManager/example/res/layout/mtmexample.xml deleted file mode 100644 index 4a08b6899..000000000 --- a/libs/MemorizingTrustManager/example/res/layout/mtmexample.xml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:orientation="vertical" - android:layout_width="fill_parent" - android:layout_height="fill_parent"> - - <EditText - android:id="@+id/url" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:hint="HTTPS address" - android:text="https://op-co.de/mtm/" - android:singleLine="true" /> - - <Button - android:id="@+id/connect" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:text="Connect" /> - - <TextView - android:id="@+id/content" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_weight="1" - android:text="Please enter a HTTPS URL and press 'Connect'!" - android:textSize="11pt" /> - - <Button - android:id="@+id/manage" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:text="Clean up Certificates" - android:onClick="onManage" /> -</LinearLayout> - diff --git a/libs/MemorizingTrustManager/example/res/values/strings.xml b/libs/MemorizingTrustManager/example/res/values/strings.xml deleted file mode 100644 index e4f505bc0..000000000 --- a/libs/MemorizingTrustManager/example/res/values/strings.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <string name="app_name">MemorizingTrustManager Example</string> -</resources> diff --git a/libs/MemorizingTrustManager/example/src/de/duenndns/mtmexample/JULHandler.java b/libs/MemorizingTrustManager/example/src/de/duenndns/mtmexample/JULHandler.java deleted file mode 100644 index 2cd70d0c5..000000000 --- a/libs/MemorizingTrustManager/example/src/de/duenndns/mtmexample/JULHandler.java +++ /dev/null @@ -1,179 +0,0 @@ -package de.duenndns.mtmexample; - -import android.util.Log; - -import java.io.IOException; -import java.io.InputStream; -import java.io.PrintWriter; -import java.io.StringBufferInputStream; -import java.io.StringWriter; -import java.util.logging.Formatter; -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.LogManager; -import java.util.logging.LogRecord; -import java.util.logging.Logger; - -/** - * A <code>java.util.logging</code> (JUL) Handler for Android. - * <p> - * If you want fine-grained control over MTM's logging, you can copy this - * class to your code base and call the static {@link #initialize()} method. - * </p> - * <p> - * This JUL Handler passes log messages sent to JUL to the Android log, while - * keeping the format and stack traces of optionally supplied Exceptions. It - * further allows to install a {@link DebugLogSettings} class via - * {@link #setDebugLogSettings(DebugLogSettings)} that determines whether JUL log messages of - * level {@link java.util.logging.Level#FINE} or lower are logged. This gives - * the application developer more control over the logged messages, while - * allowing a library developer to place debug log messages without risking to - * spam the Android log. - * </p> - * <p> - * If there are no {@code DebugLogSettings} configured, then all messages sent - * to JUL will be logged. - * </p> - * - * @author Florian Schmaus - */ -@SuppressWarnings("deprecation") -public class JULHandler extends Handler { - - /** - * Implement this interface to toggle debug logging. - */ - public interface DebugLogSettings { - public boolean isDebugLogEnabled(); - } - - private static final String CLASS_NAME = JULHandler.class.getName(); - - /** - * The global LogManager configuration. - * <p> - * This configures: - * <ul> - * <li> JULHandler as the default handler for all log messages - * <li> A default log level FINEST (300). Meaning that log messages of a level 300 or higher a - * logged - * </ul> - * </p> - */ - private static final InputStream LOG_MANAGER_CONFIG = new StringBufferInputStream( -// @formatter:off - "handlers = " + CLASS_NAME + '\n' + - ".level = FINEST" - ); -// @formatter:on - - // Constants for Android vs. JUL debug level comparisons - private static final int FINE_INT = Level.FINE.intValue(); - private static final int INFO_INT = Level.INFO.intValue(); - private static final int WARN_INT = Level.WARNING.intValue(); - private static final int SEVE_INT = Level.SEVERE.intValue(); - - private static final Logger LOGGER = Logger.getLogger(CLASS_NAME); - - /** - * A formatter that creates output similar to Android's Log.x. - */ - private static final Formatter FORMATTER = new Formatter() { - @Override - public String format(LogRecord logRecord) { - Throwable thrown = logRecord.getThrown(); - if (thrown != null) { - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw, false); - pw.write(logRecord.getMessage() + ' '); - thrown.printStackTrace(pw); - pw.flush(); - return sw.toString(); - } else { - return logRecord.getMessage(); - } - } - }; - - private static DebugLogSettings sDebugLogSettings; - private static boolean initialized = false; - - public static void initialize() { - try { - LogManager.getLogManager().readConfiguration(LOG_MANAGER_CONFIG); - initialized = true; - } catch (IOException e) { - Log.e("JULHandler", "Can not initialize configuration", e); - } - if (initialized) LOGGER.info("Initialzied java.util.logging logger"); - } - - public static void setDebugLogSettings(DebugLogSettings debugLogSettings) { - if (!isInitialized()) initialize(); - sDebugLogSettings = debugLogSettings; - } - - public static boolean isInitialized() { - return initialized; - } - - public JULHandler() { - setFormatter(FORMATTER); - } - - @Override - public void close() { - } - - @Override - public void flush() { - } - - @Override - public boolean isLoggable(LogRecord record) { - final boolean debugLog = sDebugLogSettings == null ? true : sDebugLogSettings - .isDebugLogEnabled(); - - if (record.getLevel().intValue() <= FINE_INT) { - return debugLog; - } - return true; - } - - /** - * JUL method that forwards log records to Android's LogCat. - */ - @Override - public void publish(LogRecord record) { - if (!isLoggable(record)) return; - - final int priority = getAndroidPriority(record.getLevel()); - final String tag = substringAfterLastDot(record.getSourceClassName()); - final String msg = getFormatter().format(record); - - Log.println(priority, tag, msg); - } - - /** - * Helper to convert JUL verbosity levels to Android's Log. - */ - private static int getAndroidPriority(Level level) { - int value = level.intValue(); - if (value >= SEVE_INT) { - return Log.ERROR; - } else if (value >= WARN_INT) { - return Log.WARN; - } else if (value >= INFO_INT) { - return Log.INFO; - } else { - return Log.DEBUG; - } - } - - /** - * Helper to extract short class names. - */ - private static String substringAfterLastDot(String s) { - return s.substring(s.lastIndexOf('.') + 1).trim(); - } -} diff --git a/libs/MemorizingTrustManager/example/src/de/duenndns/mtmexample/MTMExample.java b/libs/MemorizingTrustManager/example/src/de/duenndns/mtmexample/MTMExample.java deleted file mode 100644 index 31e37bd00..000000000 --- a/libs/MemorizingTrustManager/example/src/de/duenndns/mtmexample/MTMExample.java +++ /dev/null @@ -1,151 +0,0 @@ -package de.duenndns.mtmexample; - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.DialogInterface; -import android.os.Bundle; -import android.os.Handler; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.Window; -import android.widget.ArrayAdapter; -import android.widget.EditText; -import android.widget.TextView; - -import java.net.URL; -import java.security.KeyStoreException; -import java.util.ArrayList; -import java.util.Collections; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.X509TrustManager; - -import de.duenndns.ssl.MemorizingTrustManager; - -/** - * Example to demonstrate the use of MemorizingTrustManager on HTTPS - * sockets. - */ -public class MTMExample extends Activity implements OnClickListener { - MemorizingTrustManager mtm; - - TextView content; - HostnameVerifier defaultverifier; - EditText urlinput; - String text; - Handler hdlr; - - /** - * Creates the Activity and registers a MemorizingTrustManager. - */ - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - JULHandler.initialize(); - requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); - setContentView(R.layout.mtmexample); - - - // set up gui elements - findViewById(R.id.connect).setOnClickListener(this); - content = (TextView) findViewById(R.id.content); - urlinput = (EditText) findViewById(R.id.url); - - // register handler for background thread - hdlr = new Handler(); - - // Here, the MemorizingTrustManager is activated for HTTPS - try { - // set location of the keystore - MemorizingTrustManager.setKeyStoreFile("private", "sslkeys.bks"); - - // register MemorizingTrustManager for HTTPS - SSLContext sc = SSLContext.getInstance("TLS"); - mtm = new MemorizingTrustManager(this); - sc.init(null, new X509TrustManager[]{mtm}, - new java.security.SecureRandom()); - HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); - HttpsURLConnection.setDefaultHostnameVerifier( - mtm.wrapHostnameVerifier(HttpsURLConnection.getDefaultHostnameVerifier())); - - // disable redirects to reduce possible confusion - HttpsURLConnection.setFollowRedirects(false); - } catch (Exception e) { - e.printStackTrace(); - } - } - - /** - * Updates the screen content from a background thread. - */ - void setText(final String s, final boolean progress) { - text = s; - hdlr.post(new Runnable() { - public void run() { - content.setText(s); - setProgressBarIndeterminateVisibility(progress); - } - }); - } - - /** - * Spawns a new thread connecting to the specified URL. - * The result of the request is displayed on the screen. - * - * @param urlString a HTTPS URL to connect to. - */ - void connect(final String urlString) { - new Thread() { - public void run() { - try { - URL u = new URL(urlString); - HttpsURLConnection c = (HttpsURLConnection) u.openConnection(); - c.connect(); - setText("" + c.getResponseCode() + " " - + c.getResponseMessage(), false); - c.disconnect(); - } catch (Exception e) { - setText(e.toString(), false); - e.printStackTrace(); - } - } - }.start(); - } - - /** - * Reacts on the connect Button press. - */ - @Override - public void onClick(View view) { - String url = urlinput.getText().toString(); - setText("Loading " + url, true); - setProgressBarIndeterminateVisibility(true); - connect(url); - } - - /** - * React on the "Manage Certificates" button press. - */ - public void onManage(View view) { - final ArrayList<String> aliases = Collections.list(mtm.getCertificates()); - ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.select_dialog_item, aliases); - new AlertDialog.Builder(this).setTitle("Tap Certificate to Delete") - .setNegativeButton(android.R.string.cancel, null) - .setAdapter(adapter, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - try { - String alias = aliases.get(which); - mtm.deleteCertificate(alias); - setText("Deleted " + alias, false); - } catch (KeyStoreException e) { - e.printStackTrace(); - setText("Error: " + e.getLocalizedMessage(), false); - } - } - }) - .create().show(); - } -} diff --git a/libs/MemorizingTrustManager/src/de/duenndns/ssl/MemorizingTrustManager.java b/libs/MemorizingTrustManager/src/de/duenndns/ssl/MemorizingTrustManager.java index d2fc75db8..e46b6d5fb 100644 --- a/libs/MemorizingTrustManager/src/de/duenndns/ssl/MemorizingTrustManager.java +++ b/libs/MemorizingTrustManager/src/de/duenndns/ssl/MemorizingTrustManager.java @@ -28,35 +28,52 @@ package de.duenndns.ssl; import android.app.Activity; import android.app.Application; -import android.app.Notification; import android.app.NotificationManager; -import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Handler; +import android.preference.PreferenceManager; +import android.util.Base64; +import android.util.Log; import android.util.SparseArray; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.List; import java.util.Locale; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.regex.Pattern; import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; @@ -72,7 +89,15 @@ import javax.net.ssl.X509TrustManager; * <b>WARNING:</b> This only works if a dedicated thread is used for * opening sockets! */ -public class MemorizingTrustManager implements X509TrustManager { +public class MemorizingTrustManager { + + + 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"); + final static String DECISION_INTENT = "de.duenndns.ssl.DECISION"; final static String DECISION_INTENT_ID = DECISION_INTENT + ".decisionId"; final static String DECISION_INTENT_CERT = DECISION_INTENT + ".cert"; @@ -98,6 +123,7 @@ public class MemorizingTrustManager implements X509TrustManager { private KeyStore appKeyStore; private X509TrustManager defaultTrustManager; private X509TrustManager appTrustManager; + private String poshCacheDir; /** * Creates an instance of the MemorizingTrustManager class that falls back to a custom TrustManager. @@ -156,31 +182,13 @@ public class MemorizingTrustManager implements X509TrustManager { File dir = app.getDir(KEYSTORE_DIR, Context.MODE_PRIVATE); keyStoreFile = new File(dir + File.separator + KEYSTORE_FILE); + poshCacheDir = app.getFilesDir().getAbsolutePath() + "/posh_cache/"; + appKeyStore = loadAppKeyStore(); } /** - * Returns a X509TrustManager list containing a new instance of - * TrustManagerFactory. - * <p> - * This function is meant for convenience only. You can use it - * as follows to integrate TrustManagerFactory for HTTPS sockets: - * <p> - * <pre> - * SSLContext sc = SSLContext.getInstance("TLS"); - * sc.init(null, MemorizingTrustManager.getInstanceList(this), - * new java.security.SecureRandom()); - * HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); - * </pre> - * - * @param c Activity or Service to show the Dialog / Notification - */ - public static X509TrustManager[] getInstanceList(Context c) { - return new X509TrustManager[]{new MemorizingTrustManager(c)}; - } - - /** * Binds an Activity to the MTM for displaying the query dialog. * <p> * This is useful if your connection is run from a service that is @@ -395,7 +403,7 @@ public class MemorizingTrustManager implements X509TrustManager { return false; } - public void checkCertTrusted(X509Certificate[] chain, String authType, boolean isServer, boolean interactive) + public void checkCertTrusted(X509Certificate[] chain, String authType, String domain, boolean isServer, boolean interactive) throws CertificateException { LOGGER.log(Level.FINE, "checkCertTrusted(" + chain + ", " + authType + ", " + isServer + ")"); try { @@ -424,6 +432,15 @@ public class MemorizingTrustManager implements X509TrustManager { else defaultTrustManager.checkClientTrusted(chain, authType); } catch (CertificateException e) { + boolean trustSystemCAs = !PreferenceManager.getDefaultSharedPreferences(master).getBoolean("dont_trust_system_cas", false); + if (domain != null && isServer && trustSystemCAs && !isIp(domain)) { + String hash = getBase64Hash(chain[0], "SHA-256"); + List<String> fingerprints = getPoshFingerprints(domain); + if (hash != null && fingerprints.contains(hash)) { + Log.d("mtm", "trusted cert fingerprint of " + domain + " via posh"); + return; + } + } e.printStackTrace(); if (interactive) { interactCert(chain, authType, e); @@ -434,17 +451,147 @@ public class MemorizingTrustManager implements X509TrustManager { } } - public void checkClientTrusted(X509Certificate[] chain, String authType) - throws CertificateException { - checkCertTrusted(chain, authType, false, true); + private List<String> getPoshFingerprints(String domain) { + List<String> cached = getPoshFingerprintsFromCache(domain); + if (cached == null) { + return getPoshFingerprintsFromServer(domain); + } else { + return cached; + } } - public void checkServerTrusted(X509Certificate[] chain, String authType) - throws CertificateException { - checkCertTrusted(chain, authType, true, true); + private List<String> getPoshFingerprintsFromServer(String domain) { + return getPoshFingerprintsFromServer(domain, "https://" + domain + "/.well-known/posh/xmpp-client.json", -1, true); + } + + private List<String> getPoshFingerprintsFromServer(String domain, String url, int maxTtl, boolean followUrl) { + Log.d("mtm", "downloading json for " + domain + " from " + url); + try { + List<String> results = new ArrayList<>(); + HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); + connection.setConnectTimeout(5000); + connection.setReadTimeout(5000); + BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream())); + String inputLine; + StringBuilder builder = new StringBuilder(); + while ((inputLine = in.readLine()) != null) { + builder.append(inputLine); + } + JSONObject jsonObject = new JSONObject(builder.toString()); + in.close(); + int expires = jsonObject.getInt("expires"); + if (expires <= 0) { + return new ArrayList<>(); + } + if (maxTtl >= 0) { + expires = Math.min(maxTtl, expires); + } + String redirect; + try { + redirect = jsonObject.getString("url"); + } catch (JSONException e) { + redirect = null; + } + if (followUrl && redirect != null && redirect.toLowerCase().startsWith("https")) { + return getPoshFingerprintsFromServer(domain, redirect, expires, false); + } + JSONArray fingerprints = jsonObject.getJSONArray("fingerprints"); + for (int i = 0; i < fingerprints.length(); i++) { + JSONObject fingerprint = fingerprints.getJSONObject(i); + String sha256 = fingerprint.getString("sha-256"); + if (sha256 != null) { + results.add(sha256); + } + } + writeFingerprintsToCache(domain, results, 1000L * expires + System.currentTimeMillis()); + return results; + } catch (Exception e) { + Log.d("mtm", "error fetching posh " + e.getMessage()); + return new ArrayList<>(); + } + } + + private File getPoshCacheFile(String domain) { + return new File(poshCacheDir + domain + ".json"); + } + + private void writeFingerprintsToCache(String domain, List<String> results, long expires) { + File file = getPoshCacheFile(domain); + file.getParentFile().mkdirs(); + try { + file.createNewFile(); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("expires", expires); + jsonObject.put("fingerprints", new JSONArray(results)); + FileOutputStream outputStream = new FileOutputStream(file); + outputStream.write(jsonObject.toString().getBytes()); + outputStream.flush(); + outputStream.close(); + } catch (Exception e) { + e.printStackTrace(); + } } - public X509Certificate[] getAcceptedIssuers() { + private List<String> getPoshFingerprintsFromCache(String domain) { + File file = getPoshCacheFile(domain); + try { + InputStream is = new FileInputStream(file); + BufferedReader buf = new BufferedReader(new InputStreamReader(is)); + + String line = buf.readLine(); + StringBuilder sb = new StringBuilder(); + + while (line != null) { + sb.append(line).append("\n"); + line = buf.readLine(); + } + JSONObject jsonObject = new JSONObject(sb.toString()); + is.close(); + long expires = jsonObject.getLong("expires"); + long expiresIn = expires - System.currentTimeMillis(); + if (expiresIn < 0) { + file.delete(); + return null; + } else { + Log.d("mtm", "posh fingerprints expire in " + (expiresIn / 1000) + "s"); + } + List<String> result = new ArrayList<>(); + JSONArray jsonArray = jsonObject.getJSONArray("fingerprints"); + for (int i = 0; i < jsonArray.length(); ++i) { + result.add(jsonArray.getString(i)); + } + return result; + } catch (FileNotFoundException e) { + return null; + } catch (IOException e) { + return null; + } catch (JSONException e) { + file.delete(); + return null; + } + } + + 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()); + } + + private static String getBase64Hash(X509Certificate certificate, String digest) throws CertificateEncodingException { + MessageDigest md; + try { + md = MessageDigest.getInstance(digest); + } catch (NoSuchAlgorithmException e) { + return null; + } + md.update(certificate.getEncoded()); + return Base64.encodeToString(md.digest(), Base64.NO_WRAP); + } + + private X509Certificate[] getAcceptedIssuers() { LOGGER.log(Level.FINE, "getAcceptedIssuers()"); return defaultTrustManager.getAcceptedIssuers(); } @@ -556,21 +703,6 @@ public class MemorizingTrustManager implements X509TrustManager { return si.toString(); } - // We can use Notification.Builder once MTM's minSDK is >= 11 - @SuppressWarnings("deprecation") - void startActivityNotification(Intent intent, int decisionId, String certName) { - Notification n = new Notification(android.R.drawable.ic_lock_lock, - master.getString(R.string.mtm_notification), - System.currentTimeMillis()); - PendingIntent call = PendingIntent.getActivity(master, 0, intent, 0); - n.setLatestEventInfo(master.getApplicationContext(), - master.getString(R.string.mtm_notification), - certName, call); - n.flags |= Notification.FLAG_AUTO_CANCEL; - - notificationManager.notify(NOTIFICATION_ID + decisionId, n); - } - /** * Returns the top-most entry of the activity stack. * @@ -600,7 +732,6 @@ public class MemorizingTrustManager implements X509TrustManager { getUI().startActivity(ni); } catch (Exception e) { LOGGER.log(Level.FINE, "startActivity(MemorizingActivity)", e); - startActivityNotification(ni, myId, message); } } }); @@ -711,22 +842,39 @@ public class MemorizingTrustManager implements X509TrustManager { } + public X509TrustManager getNonInteractive(String domain) { + return new NonInteractiveMemorizingTrustManager(domain); + } + + public X509TrustManager getInteractive(String domain) { + return new InteractiveMemorizingTrustManager(domain); + } + public X509TrustManager getNonInteractive() { - return new NonInteractiveMemorizingTrustManager(); + return new NonInteractiveMemorizingTrustManager(null); + } + + public X509TrustManager getInteractive() { + return new InteractiveMemorizingTrustManager(null); } private class NonInteractiveMemorizingTrustManager implements X509TrustManager { + private final String domain; + + public NonInteractiveMemorizingTrustManager(String domain) { + this.domain = domain; + } + @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) - throws CertificateException { - MemorizingTrustManager.this.checkCertTrusted(chain, authType, false, false); + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + MemorizingTrustManager.this.checkCertTrusted(chain, authType, domain, false, false); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { - MemorizingTrustManager.this.checkCertTrusted(chain, authType, true, false); + MemorizingTrustManager.this.checkCertTrusted(chain, authType, domain, true, false); } @Override @@ -735,4 +883,28 @@ public class MemorizingTrustManager implements X509TrustManager { } } + + private class InteractiveMemorizingTrustManager implements X509TrustManager { + private final String domain; + + public InteractiveMemorizingTrustManager(String domain) { + this.domain = domain; + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + MemorizingTrustManager.this.checkCertTrusted(chain, authType, domain, false, true); + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + MemorizingTrustManager.this.checkCertTrusted(chain, authType, domain, true, true); + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return MemorizingTrustManager.this.getAcceptedIssuers(); + } + } } diff --git a/src/main/java/de/pixart/messenger/http/HttpConnectionManager.java b/src/main/java/de/pixart/messenger/http/HttpConnectionManager.java index c6f1d02ec..20213521f 100644 --- a/src/main/java/de/pixart/messenger/http/HttpConnectionManager.java +++ b/src/main/java/de/pixart/messenger/http/HttpConnectionManager.java @@ -60,7 +60,7 @@ public class HttpConnectionManager extends AbstractConnectionManager { final X509TrustManager trustManager; final HostnameVerifier hostnameVerifier; if (interactive) { - trustManager = mXmppConnectionService.getMemorizingTrustManager(); + trustManager = mXmppConnectionService.getMemorizingTrustManager().getInteractive(); hostnameVerifier = mXmppConnectionService .getMemorizingTrustManager().wrapHostnameVerifier( new StrictHostnameVerifier()); diff --git a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java index 8f83851bd..99167fa94 100644 --- a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java +++ b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java @@ -1816,7 +1816,7 @@ public class XmppConnectionService extends Service { callback.onAccountCreated(account); if (Config.X509_VERIFICATION) { try { - getMemorizingTrustManager().getNonInteractive().checkClientTrusted(chain, "RSA"); + getMemorizingTrustManager().getNonInteractive(account.getJid().getDomainpart()).checkClientTrusted(chain, "RSA"); } catch (CertificateException e) { callback.informUser(R.string.certificate_chain_is_not_trusted); } @@ -1844,7 +1844,7 @@ public class XmppConnectionService extends Service { databaseBackend.updateAccount(account); if (Config.X509_VERIFICATION) { try { - getMemorizingTrustManager().getNonInteractive().checkClientTrusted(chain, "RSA"); + getMemorizingTrustManager().getNonInteractive(account.getJid().getDomainpart()).checkClientTrusted(chain, "RSA"); } catch (CertificateException e) { showErrorToastInUi(R.string.certificate_chain_is_not_trusted); } diff --git a/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java b/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java index dd78a3eab..676a498ac 100644 --- a/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java +++ b/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java @@ -524,7 +524,8 @@ public class XmppConnection implements Runnable { } else { keyManager = null; } - sc.init(keyManager, new X509TrustManager[]{mInteractive ? trustManager : trustManager.getNonInteractive()}, mXmppConnectionService.getRNG()); + String domain = account.getJid().getDomainpart(); + sc.init(keyManager, new X509TrustManager[]{mInteractive ? trustManager.getInteractive(domain) : trustManager.getNonInteractive(domain)}, mXmppConnectionService.getRNG()); final SSLSocketFactory factory = sc.getSocketFactory(); final HostnameVerifier verifier; if (mInteractive) { |