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); } }