From 1d3d72a46bd1ad52f0ea8c60c3547651b44fefad Mon Sep 17 00:00:00 2001
From: Karol Szklarski <karol.szklarski@lppsa.com>
Date: Fri, 3 Mar 2017 13:27:21 +0100
Subject: [PATCH] Adding DefaultSSLSetupFactory and DefaultSSLSetup to allow
 configuring SSL connection

---
 .../org/acra/annotation/ReportsCrashes.java   |  7 +++
 .../org/acra/config/ACRAConfiguration.java    | 12 +++-
 .../org/acra/config/ConfigurationBuilder.java | 49 ++++++++++++++-
 .../org/acra/security/DefaultSSLSetup.java    | 60 +++++++++++++++++++
 .../acra/security/DefaultSSLSetupFactory.java | 35 +++++++++++
 src/main/java/org/acra/security/SSLSetup.java | 28 +++++++++
 .../org/acra/security/SSLSetupFactory.java    | 33 ++++++++++
 src/main/java/org/acra/util/HttpRequest.java  | 20 ++-----
 8 files changed, 225 insertions(+), 19 deletions(-)
 create mode 100644 src/main/java/org/acra/security/DefaultSSLSetup.java
 create mode 100644 src/main/java/org/acra/security/DefaultSSLSetupFactory.java
 create mode 100644 src/main/java/org/acra/security/SSLSetup.java
 create mode 100644 src/main/java/org/acra/security/SSLSetupFactory.java

diff --git a/src/main/java/org/acra/annotation/ReportsCrashes.java b/src/main/java/org/acra/annotation/ReportsCrashes.java
index fada412363..5d3bba3f5d 100644
--- a/src/main/java/org/acra/annotation/ReportsCrashes.java
+++ b/src/main/java/org/acra/annotation/ReportsCrashes.java
@@ -35,8 +35,10 @@
 import org.acra.dialog.BaseCrashReportDialog;
 import org.acra.dialog.CrashReportDialog;
 import org.acra.file.Directory;
+import org.acra.security.DefaultSSLSetupFactory;
 import org.acra.security.KeyStoreFactory;
 import org.acra.security.NoKeyStoreFactory;
+import org.acra.security.SSLSetupFactory;
 import org.acra.sender.DefaultReportSenderFactory;
 import org.acra.sender.HttpSender.Method;
 import org.acra.sender.HttpSender.Type;
@@ -597,6 +599,11 @@
      */
     @NonNull Class<? extends KeyStoreFactory> keyStoreFactoryClass() default NoKeyStoreFactory.class;
 
+    /**
+     * @return Class which creates a SSLSetup for HttpsURLConnection
+     */
+    @NonNull Class<? extends SSLSetupFactory> sslSetupFactoryClass() default DefaultSSLSetupFactory.class;
+
     /**
      * @return path to a custom trusted certificate. Must start with "asset://" if the file is in the assets folder
      */
diff --git a/src/main/java/org/acra/config/ACRAConfiguration.java b/src/main/java/org/acra/config/ACRAConfiguration.java
index bffcdd3ef5..46e45bbff4 100644
--- a/src/main/java/org/acra/config/ACRAConfiguration.java
+++ b/src/main/java/org/acra/config/ACRAConfiguration.java
@@ -26,14 +26,15 @@
 import org.acra.ReportingInteractionMode;
 import org.acra.builder.ReportPrimer;
 import org.acra.collections.ImmutableList;
+import org.acra.collections.ImmutableMap;
+import org.acra.collections.ImmutableSet;
 import org.acra.dialog.BaseCrashReportDialog;
 import org.acra.file.Directory;
 import org.acra.security.KeyStoreFactory;
+import org.acra.security.SSLSetupFactory;
 import org.acra.sender.HttpSender.Method;
 import org.acra.sender.HttpSender.Type;
 import org.acra.sender.ReportSenderFactory;
-import org.acra.collections.ImmutableMap;
-import org.acra.collections.ImmutableSet;
 
 import java.io.Serializable;
 
@@ -109,6 +110,7 @@ public final class ACRAConfiguration implements Serializable {
     private final Type reportType;
     private final ImmutableMap<String, String> httpHeaders;
     private final Class<? extends KeyStoreFactory> keyStoreFactoryClass;
+    private final Class<? extends SSLSetupFactory> sslSetupFactoryClass;
     private final ImmutableSet<Class<? extends ReportSenderFactory>> reportSenderFactoryClasses;
     @RawRes
     private final int resCertificate;
@@ -169,6 +171,7 @@ public final class ACRAConfiguration implements Serializable {
         reportType = builder.reportType();
         reportSenderFactoryClasses = new ImmutableSet<Class<? extends ReportSenderFactory>>(builder.reportSenderFactoryClasses());
         keyStoreFactoryClass = builder.keyStoreFactoryClass();
+        sslSetupFactoryClass = builder.sslSetupFactoryClass();
         resCertificate = builder.resCertificate();
         certificatePath = builder.certificatePath();
         certificateType = builder.certificateType();
@@ -420,6 +423,11 @@ public Class<? extends KeyStoreFactory> keyStoreFactoryClass() {
         return keyStoreFactoryClass;
     }
 
+    @NonNull
+    public Class<? extends SSLSetupFactory> sslSetupFactoryClass() {
+        return sslSetupFactoryClass;
+    }
+
     @RawRes
     public int resCertificate() {
         return resCertificate;
diff --git a/src/main/java/org/acra/config/ConfigurationBuilder.java b/src/main/java/org/acra/config/ConfigurationBuilder.java
index c56f4ae7ef..f0568c6ed5 100644
--- a/src/main/java/org/acra/config/ConfigurationBuilder.java
+++ b/src/main/java/org/acra/config/ConfigurationBuilder.java
@@ -32,8 +32,10 @@
 import org.acra.dialog.BaseCrashReportDialog;
 import org.acra.dialog.CrashReportDialog;
 import org.acra.file.Directory;
+import org.acra.security.DefaultSSLSetupFactory;
 import org.acra.security.KeyStoreFactory;
 import org.acra.security.NoKeyStoreFactory;
+import org.acra.security.SSLSetupFactory;
 import org.acra.sender.DefaultReportSenderFactory;
 import org.acra.sender.HttpSender;
 import org.acra.sender.HttpSender.Method;
@@ -50,7 +52,30 @@
 import java.util.Set;
 
 import static org.acra.ACRA.LOG_TAG;
-import static org.acra.ACRAConstants.*;
+import static org.acra.ACRAConstants.DEFAULT_APPLICATION_LOGFILE;
+import static org.acra.ACRAConstants.DEFAULT_APPLICATION_LOGFILE_LINES;
+import static org.acra.ACRAConstants.DEFAULT_CERTIFICATE_TYPE;
+import static org.acra.ACRAConstants.DEFAULT_CONNECTION_TIMEOUT;
+import static org.acra.ACRAConstants.DEFAULT_DELETE_OLD_UNSENT_REPORTS_ON_APPLICATION_START;
+import static org.acra.ACRAConstants.DEFAULT_DELETE_UNAPPROVED_REPORTS_ON_APPLICATION_START;
+import static org.acra.ACRAConstants.DEFAULT_DIALOG_ICON;
+import static org.acra.ACRAConstants.DEFAULT_DIALOG_NEGATIVE_BUTTON_TEXT;
+import static org.acra.ACRAConstants.DEFAULT_DIALOG_POSITIVE_BUTTON_TEXT;
+import static org.acra.ACRAConstants.DEFAULT_DROPBOX_COLLECTION_MINUTES;
+import static org.acra.ACRAConstants.DEFAULT_INCLUDE_DROPBOX_SYSTEM_TAGS;
+import static org.acra.ACRAConstants.DEFAULT_LOGCAT_FILTER_BY_PID;
+import static org.acra.ACRAConstants.DEFAULT_LOGCAT_LINES;
+import static org.acra.ACRAConstants.DEFAULT_MAIL_REPORT_FIELDS;
+import static org.acra.ACRAConstants.DEFAULT_NON_BLOCKING_READ_FOR_LOGCAT;
+import static org.acra.ACRAConstants.DEFAULT_NOTIFICATION_ICON;
+import static org.acra.ACRAConstants.DEFAULT_REPORT_FIELDS;
+import static org.acra.ACRAConstants.DEFAULT_REPORT_TO_ANDROID_FRAMEWORK;
+import static org.acra.ACRAConstants.DEFAULT_RES_VALUE;
+import static org.acra.ACRAConstants.DEFAULT_SEND_REPORTS_IN_DEV_MODE;
+import static org.acra.ACRAConstants.DEFAULT_SHARED_PREFERENCES_MODE;
+import static org.acra.ACRAConstants.DEFAULT_SOCKET_TIMEOUT;
+import static org.acra.ACRAConstants.DEFAULT_STRING_VALUE;
+import static org.acra.ACRAConstants.NULL_VALUE;
 
 /**
  * Builder responsible for programmatic construction of an {@link ACRAConfiguration}.
@@ -117,6 +142,7 @@ public final class ConfigurationBuilder {
     private Type reportType;
     private final Map<String, String> httpHeaders = new HashMap<String, String>();
     private Class<? extends KeyStoreFactory> keyStoreFactoryClass;
+    private Class<? extends SSLSetupFactory> sslSetupFactoryClass;
     private Class<? extends ReportSenderFactory>[] reportSenderFactoryClasses;
     @RawRes private Integer resCertificate;
     private String certificatePath;
@@ -186,6 +212,7 @@ public ConfigurationBuilder(@NonNull Application app) {
             reportType = annotationConfig.reportType();
             reportSenderFactoryClasses = annotationConfig.reportSenderFactoryClasses();
             keyStoreFactoryClass = annotationConfig.keyStoreFactoryClass();
+            sslSetupFactoryClass = annotationConfig.sslSetupFactoryClass();
             resCertificate = annotationConfig.resCertificate();
             certificatePath = annotationConfig.certificatePath();
             certificateType = annotationConfig.certificateType();
@@ -236,7 +263,7 @@ public ACRAConfiguration build() throws ACRAConfigurationException {
             throw new ACRAConfigurationException("Report sender factories: using no report senders will make ACRA useless. Configure at least one ReportSenderFactory.");
         }
         checkValidity((Class[]) reportSenderFactoryClasses());
-        checkValidity(reportDialogClass(), reportPrimerClass(), retryPolicyClass(), keyStoreFactoryClass());
+        checkValidity(reportDialogClass(), reportPrimerClass(), retryPolicyClass(), keyStoreFactoryClass(), sslSetupFactoryClass());
 
         return new ACRAConfiguration(this);
     }
@@ -807,6 +834,16 @@ public ConfigurationBuilder setKeyStoreFactoryClass(Class<? extends KeyStoreFact
         return this;
     }
 
+    /**
+     * @param sslSetupFactoryClass Set this to a factory class which creates SSLSetup for HttpsURLConnection
+     * @return this instance
+     */
+    @NonNull
+    public ConfigurationBuilder setSSLSetupFactoryClass(Class<? extends SSLSetupFactory> sslSetupFactoryClass) {
+        this.sslSetupFactoryClass = sslSetupFactoryClass;
+        return this;
+    }
+
     /**
      * @param resCertificate a raw resource of a custom certificate file
      * @return this instance
@@ -1260,6 +1297,14 @@ Class<? extends KeyStoreFactory> keyStoreFactoryClass() {
         return NoKeyStoreFactory.class;
     }
 
+    @NonNull
+    Class<? extends SSLSetupFactory> sslSetupFactoryClass() {
+        if (sslSetupFactoryClass != null) {
+            return sslSetupFactoryClass;
+        }
+        return DefaultSSLSetupFactory.class;
+    }
+
     @RawRes
     int resCertificate() {
         if (resCertificate != null) {
diff --git a/src/main/java/org/acra/security/DefaultSSLSetup.java b/src/main/java/org/acra/security/DefaultSSLSetup.java
new file mode 100644
index 0000000000..97a4df216d
--- /dev/null
+++ b/src/main/java/org/acra/security/DefaultSSLSetup.java
@@ -0,0 +1,60 @@
+/*
+ *  Copyright 2016
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.acra.security;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+
+import org.acra.config.ACRAConfiguration;
+
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManagerFactory;
+
+/**
+ * Default SSLSetup.
+ *
+ * @author Karol Szklarski
+ */
+public class DefaultSSLSetup implements SSLSetup {
+
+    private final HttpsURLConnection httpsUrlConnection;
+    private final Context context;
+    private final ACRAConfiguration config;
+
+    public DefaultSSLSetup(@NonNull HttpsURLConnection httpsUrlConnection, @NonNull Context context, @NonNull ACRAConfiguration config) {
+        this.httpsUrlConnection = httpsUrlConnection;
+        this.context = context;
+        this.config = config;
+    }
+
+    @Override
+    public void setup() throws GeneralSecurityException {
+        final String algorithm = TrustManagerFactory.getDefaultAlgorithm();
+        final TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
+        final KeyStore keyStore = KeyStoreHelper.getKeyStore(context, config);
+
+        tmf.init(keyStore);
+
+        final SSLContext sslContext = SSLContext.getInstance("TLS");
+        sslContext.init(null, tmf.getTrustManagers(), null);
+
+        httpsUrlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
+    }
+}
diff --git a/src/main/java/org/acra/security/DefaultSSLSetupFactory.java b/src/main/java/org/acra/security/DefaultSSLSetupFactory.java
new file mode 100644
index 0000000000..37a058795d
--- /dev/null
+++ b/src/main/java/org/acra/security/DefaultSSLSetupFactory.java
@@ -0,0 +1,35 @@
+/*
+ *  Copyright 2016
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.acra.security;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+
+import org.acra.config.ACRAConfiguration;
+
+import javax.net.ssl.HttpsURLConnection;
+
+/**
+ * Default SSLSetupFactory. Creates DefaultSSLSetup class instance.
+ *
+ * @author Karol Szklarski
+ */
+public class DefaultSSLSetupFactory implements SSLSetupFactory {
+    @Override
+    public SSLSetup create(@NonNull HttpsURLConnection httpsUrlConnection, @NonNull Context context, @NonNull ACRAConfiguration config) {
+        return new DefaultSSLSetup(httpsUrlConnection, context, config);
+    }
+}
diff --git a/src/main/java/org/acra/security/SSLSetup.java b/src/main/java/org/acra/security/SSLSetup.java
new file mode 100644
index 0000000000..6c59663546
--- /dev/null
+++ b/src/main/java/org/acra/security/SSLSetup.java
@@ -0,0 +1,28 @@
+/*
+ *  Copyright 2016
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.acra.security;
+
+import java.security.GeneralSecurityException;
+
+/**
+ * The interface can be used to provide a SSL setup for HttpsURLConnection.
+ *
+ * @author Karol Szklarski
+ */
+public interface SSLSetup {
+
+    void setup() throws GeneralSecurityException;
+}
diff --git a/src/main/java/org/acra/security/SSLSetupFactory.java b/src/main/java/org/acra/security/SSLSetupFactory.java
new file mode 100644
index 0000000000..e91a70d653
--- /dev/null
+++ b/src/main/java/org/acra/security/SSLSetupFactory.java
@@ -0,0 +1,33 @@
+/*
+ *  Copyright 2016
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.acra.security;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+
+import org.acra.config.ACRAConfiguration;
+
+import javax.net.ssl.HttpsURLConnection;
+
+/**
+ * The interface can be used to provide a factory for SSL setup for HttpsURLConnection.
+ *
+ * @author Karol Szklarski
+ */
+public interface SSLSetupFactory {
+
+    SSLSetup create(@NonNull HttpsURLConnection httpsUrlConnection, @NonNull Context context, @NonNull ACRAConfiguration config);
+}
diff --git a/src/main/java/org/acra/util/HttpRequest.java b/src/main/java/org/acra/util/HttpRequest.java
index c5b0eb5be5..df596e1d2b 100644
--- a/src/main/java/org/acra/util/HttpRequest.java
+++ b/src/main/java/org/acra/util/HttpRequest.java
@@ -12,7 +12,6 @@
 
 import org.acra.ACRA;
 import org.acra.config.ACRAConfiguration;
-import org.acra.security.KeyStoreHelper;
 import org.acra.sender.HttpSender.Method;
 import org.acra.sender.HttpSender.Type;
 
@@ -24,12 +23,9 @@
 import java.net.URL;
 import java.net.URLEncoder;
 import java.security.GeneralSecurityException;
-import java.security.KeyStore;
 import java.util.Map;
 
 import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.TrustManagerFactory;
 
 import ch.acra.acra.BuildConfig;
 
@@ -86,19 +82,13 @@ public void send(@NonNull Context context, @NonNull URL url, @NonNull Method met
         if (urlConnection instanceof HttpsURLConnection) {
             try {
                 final HttpsURLConnection httpsUrlConnection = (HttpsURLConnection) urlConnection;
-
-                final String algorithm = TrustManagerFactory.getDefaultAlgorithm();
-                final TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
-                final KeyStore keyStore = KeyStoreHelper.getKeyStore(context, config);
-
-                tmf.init(keyStore);
-
-                final SSLContext sslContext = SSLContext.getInstance("TLS");
-                sslContext.init(null, tmf.getTrustManagers(), null);
-
-                httpsUrlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
+                config.sslSetupFactoryClass().newInstance().create(httpsUrlConnection, context, config).setup();
             } catch (GeneralSecurityException e) {
                 ACRA.log.e(LOG_TAG, "Could not configure SSL for ACRA request to " + url, e);
+            } catch (InstantiationException e) {
+                ACRA.log.e(LOG_TAG, "Could not configure SSL for ACRA request to " + url, e);
+            } catch (IllegalAccessException e) {
+                ACRA.log.e(LOG_TAG, "Could not configure SSL for ACRA request to " + url, e);
             }
         }