Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Making custom SSL configuration possible #555

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/main/java/org/acra/annotation/ReportsCrashes.java
Original file line number Diff line number Diff line change
@@ -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
*/
12 changes: 10 additions & 2 deletions src/main/java/org/acra/config/ACRAConfiguration.java
Original file line number Diff line number Diff line change
@@ -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;
49 changes: 47 additions & 2 deletions src/main/java/org/acra/config/ConfigurationBuilder.java
Original file line number Diff line number Diff line change
@@ -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) {
60 changes: 60 additions & 0 deletions src/main/java/org/acra/security/DefaultSSLSetup.java
Original file line number Diff line number Diff line change
@@ -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());
}
}
35 changes: 35 additions & 0 deletions src/main/java/org/acra/security/DefaultSSLSetupFactory.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
28 changes: 28 additions & 0 deletions src/main/java/org/acra/security/SSLSetup.java
Original file line number Diff line number Diff line change
@@ -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;
}
33 changes: 33 additions & 0 deletions src/main/java/org/acra/security/SSLSetupFactory.java
Original file line number Diff line number Diff line change
@@ -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);
}
20 changes: 5 additions & 15 deletions src/main/java/org/acra/util/HttpRequest.java
Original file line number Diff line number Diff line change
@@ -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);
}
}