From 21112ce092e178dff5ec8edae31bd60f03898f8b Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 4 Feb 2021 10:08:59 -0300 Subject: [PATCH] Encrypt logs before handing them to crash report process --- .../bramble/util/AndroidUtils.java | 5 ++ .../bramble/api/reporting/DevConfig.java | 2 + briar-android/build.gradle | 1 + .../briar/android/AndroidComponent.java | 5 ++ .../briarproject/briar/android/AppModule.java | 9 ++- .../briar/android/BriarApplication.java | 5 -- .../briar/android/BriarApplicationImpl.java | 21 ++--- .../android/logging/BriefLogFormatter.java | 11 +++ .../android/logging/CachingLogHandler.java | 4 + .../briar/android/logging/LogDecrypter.java | 16 ++++ .../android/logging/LogDecrypterImpl.java | 61 +++++++++++++++ .../briar/android/logging/LogEncrypter.java | 16 ++++ .../android/logging/LogEncrypterImpl.java | 77 +++++++++++++++++++ .../briar/android/logging/LoggingModule.java | 29 +++++++ .../reporting/BriarExceptionHandler.java | 25 ++++-- .../reporting/BriarReportCollector.java | 21 ++--- .../reporting/CrashReportActivity.java | 4 +- .../android/reporting/DevReportModule.java | 4 + .../android/reporting/ReportViewModel.java | 44 +++++++++-- .../briar/android/util/UiUtils.java | 6 +- .../logging/LogEncryptionDecryptionTest.java | 66 ++++++++++++++++ .../android/logging/LoggingComponent.java | 29 +++++++ .../android/logging/LoggingTestModule.java | 52 +++++++++++++ 23 files changed, 459 insertions(+), 54 deletions(-) create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/logging/LogDecrypter.java create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/logging/LogDecrypterImpl.java create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/logging/LogEncrypter.java create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/logging/LogEncrypterImpl.java create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/logging/LoggingModule.java create mode 100644 briar-android/src/test/java/org/briarproject/briar/android/logging/LogEncryptionDecryptionTest.java create mode 100644 briar-android/src/test/java/org/briarproject/briar/android/logging/LoggingComponent.java create mode 100644 briar-android/src/test/java/org/briarproject/briar/android/logging/LoggingTestModule.java diff --git a/bramble-android/src/main/java/org/briarproject/bramble/util/AndroidUtils.java b/bramble-android/src/main/java/org/briarproject/bramble/util/AndroidUtils.java index 4d5c8ea13..908d5bfb7 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/util/AndroidUtils.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/util/AndroidUtils.java @@ -31,6 +31,7 @@ public class AndroidUtils { private static final String FAKE_BLUETOOTH_ADDRESS = "02:00:00:00:00:00"; private static final String STORED_REPORTS = "dev-reports"; + private static final String STORED_LOGCAT = "dev-logcat"; public static Collection getSupportedArchitectures() { List abis = new ArrayList<>(); @@ -107,6 +108,10 @@ public class AndroidUtils { return ctx.getDir(STORED_REPORTS, MODE_PRIVATE); } + public static File getLogcatFile(Context ctx) { + return new File(ctx.getFilesDir(), STORED_LOGCAT); + } + /** * Returns an array of supported content types for image attachments. * GIFs can't be compressed on API < 24 so they're not supported. diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/reporting/DevConfig.java b/bramble-api/src/main/java/org/briarproject/bramble/api/reporting/DevConfig.java index a97cfc4fa..8c7c89000 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/reporting/DevConfig.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/reporting/DevConfig.java @@ -13,4 +13,6 @@ public interface DevConfig { String getDevOnionAddress(); File getReportDir(); + + File getLogcatFile(); } diff --git a/briar-android/build.gradle b/briar-android/build.gradle index 4fe9053d6..2814b7952 100644 --- a/briar-android/build.gradle +++ b/briar-android/build.gradle @@ -136,6 +136,7 @@ dependencies { testImplementation "org.jmock:jmock:$jmockVersion" testImplementation "org.jmock:jmock-junit4:$jmockVersion" testImplementation "org.jmock:jmock-legacy:$jmockVersion" + testAnnotationProcessor "com.google.dagger:dagger-compiler:2.24" androidTestImplementation project(path: ':bramble-api', configuration: 'testOutput') androidTestImplementation 'androidx.test.ext:junit:1.1.2' diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java index 366ebb318..86db83f29 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java @@ -34,6 +34,7 @@ import org.briarproject.briar.BriarCoreModule; import org.briarproject.briar.android.attachment.AttachmentModule; import org.briarproject.briar.android.attachment.media.MediaModule; import org.briarproject.briar.android.conversation.glide.BriarModelLoader; +import org.briarproject.briar.android.logging.CachingLogHandler; import org.briarproject.briar.android.login.SignInReminderReceiver; import org.briarproject.briar.android.view.EmojiTextInputView; import org.briarproject.briar.api.android.AndroidNotificationManager; @@ -179,6 +180,10 @@ public interface AndroidComponent AndroidWakeLockManager wakeLockManager(); + CachingLogHandler logHandler(); + + Thread.UncaughtExceptionHandler exceptionHandler(); + void inject(SignInReminderReceiver briarService); void inject(BriarService briarService); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java index d2c4bac64..760c3af82 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java @@ -33,12 +33,13 @@ import org.briarproject.briar.android.account.SetupModule; import org.briarproject.briar.android.contact.ContactListModule; import org.briarproject.briar.android.forum.ForumModule; import org.briarproject.briar.android.keyagreement.ContactExchangeModule; +import org.briarproject.briar.android.logging.LoggingModule; import org.briarproject.briar.android.login.LoginModule; import org.briarproject.briar.android.navdrawer.NavDrawerModule; import org.briarproject.briar.android.privategroup.conversation.GroupConversationModule; -import org.briarproject.briar.android.settings.SettingsModule; import org.briarproject.briar.android.privategroup.list.GroupListModule; import org.briarproject.briar.android.reporting.DevReportModule; +import org.briarproject.briar.android.settings.SettingsModule; import org.briarproject.briar.android.sharing.SharingModule; import org.briarproject.briar.android.test.TestAvatarCreatorImpl; import org.briarproject.briar.android.viewmodel.ViewModelModule; @@ -74,6 +75,7 @@ import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; SetupModule.class, DozeHelperModule.class, ContactExchangeModule.class, + LoggingModule.class, LoginModule.class, NavDrawerModule.class, ViewModelModule.class, @@ -192,6 +194,11 @@ public class AppModule { public File getReportDir() { return AndroidUtils.getReportDir(app.getApplicationContext()); } + + @Override + public File getLogcatFile() { + return AndroidUtils.getLogcatFile(app.getApplicationContext()); + } }; return devConfig; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/BriarApplication.java b/briar-android/src/main/java/org/briarproject/briar/android/BriarApplication.java index 09946b3a7..b113a195a 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/BriarApplication.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/BriarApplication.java @@ -6,9 +6,6 @@ import android.content.SharedPreferences; import org.briarproject.bramble.BrambleApplication; import org.briarproject.briar.android.navdrawer.NavDrawerActivity; -import java.util.Collection; -import java.util.logging.LogRecord; - /** * This exists so that the Application object will not necessarily be cast * directly to the Briar application object. @@ -17,8 +14,6 @@ public interface BriarApplication extends BrambleApplication { Class ENTRY_ACTIVITY = NavDrawerActivity.class; - Collection getRecentLogRecords(); - AndroidComponent getApplicationComponent(); SharedPreferences getDefaultSharedPreferences(); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java index 64022ec6f..58b8b05b5 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java @@ -20,12 +20,10 @@ import org.briarproject.bramble.BrambleCoreEagerSingletons; import org.briarproject.briar.BriarCoreEagerSingletons; import org.briarproject.briar.R; import org.briarproject.briar.android.logging.CachingLogHandler; -import org.briarproject.briar.android.reporting.BriarExceptionHandler; import org.briarproject.briar.android.util.UiUtils; -import java.util.Collection; +import java.lang.Thread.UncaughtExceptionHandler; import java.util.logging.Handler; -import java.util.logging.LogRecord; import java.util.logging.Logger; import androidx.annotation.NonNull; @@ -42,16 +40,11 @@ public class BriarApplicationImpl extends Application private static final Logger LOG = getLogger(BriarApplicationImpl.class.getName()); - private final CachingLogHandler logHandler = new CachingLogHandler(); - private final BriarExceptionHandler exceptionHandler = - new BriarExceptionHandler(this); - private AndroidComponent applicationComponent; private volatile SharedPreferences prefs; @Override protected void attachBaseContext(Context base) { - Thread.setDefaultUncaughtExceptionHandler(exceptionHandler); if (prefs == null) prefs = PreferenceManager.getDefaultSharedPreferences(base); // Loading the language needs to be done here. @@ -67,6 +60,11 @@ public class BriarApplicationImpl extends Application if (IS_DEBUG_BUILD) enableStrictMode(); + applicationComponent = createApplicationComponent(); + UncaughtExceptionHandler exceptionHandler = + applicationComponent.exceptionHandler(); + Thread.setDefaultUncaughtExceptionHandler(exceptionHandler); + Logger rootLogger = getLogger(""); Handler[] handlers = rootLogger.getHandlers(); // Disable the Android logger for release builds @@ -78,12 +76,12 @@ public class BriarApplicationImpl extends Application // Restore the default handlers after the level raising handler for (Handler handler : handlers) rootLogger.addHandler(handler); } + CachingLogHandler logHandler = applicationComponent.logHandler(); rootLogger.addHandler(logHandler); rootLogger.setLevel(IS_DEBUG_BUILD ? FINE : INFO); LOG.info("Created"); - applicationComponent = createApplicationComponent(); EmojiManager.install(new GoogleEmojiProvider()); } @@ -136,11 +134,6 @@ public class BriarApplicationImpl extends Application return applicationComponent; } - @Override - public Collection getRecentLogRecords() { - return logHandler.getRecentLogRecords(); - } - @Override public AndroidComponent getApplicationComponent() { return applicationComponent; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/logging/BriefLogFormatter.java b/briar-android/src/main/java/org/briarproject/briar/android/logging/BriefLogFormatter.java index 00906c93e..0256cf1b4 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/logging/BriefLogFormatter.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/logging/BriefLogFormatter.java @@ -4,6 +4,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.util.Collection; import java.util.Date; import java.util.TimeZone; import java.util.logging.Formatter; @@ -17,6 +18,16 @@ import static java.util.Locale.US; @NotNullByDefault public class BriefLogFormatter extends Formatter { + public static String formatLog(Formatter formatter, + Collection logRecords) { + StringBuilder sb = new StringBuilder(); + for (LogRecord record : logRecords) { + String formatted = formatter.format(record); + sb.append(formatted).append('\n'); + } + return sb.toString(); + } + private final Object lock = new Object(); private final DateFormat dateFormat; // Locking: lock private final Date date; // Locking: lock diff --git a/briar-android/src/main/java/org/briarproject/briar/android/logging/CachingLogHandler.java b/briar-android/src/main/java/org/briarproject/briar/android/logging/CachingLogHandler.java index baa51e3f6..5037105e1 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/logging/CachingLogHandler.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/logging/CachingLogHandler.java @@ -21,6 +21,10 @@ public class CachingLogHandler extends Handler { // Locking: lock private final Queue recent = new LinkedList<>(); + // package-private constructor + CachingLogHandler() { + } + @Override public void publish(LogRecord record) { synchronized (lock) { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/logging/LogDecrypter.java b/briar-android/src/main/java/org/briarproject/briar/android/logging/LogDecrypter.java new file mode 100644 index 000000000..2295838de --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/logging/LogDecrypter.java @@ -0,0 +1,16 @@ +package org.briarproject.briar.android.logging; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.util.AndroidUtils; + +import androidx.annotation.Nullable; + +@NotNullByDefault +public interface LogDecrypter { + /** + * Returns decrypted log records from {@link AndroidUtils#getLogcatFile} + * or null if there was an error reading the logs. + */ + @Nullable + String decryptLogs(@Nullable byte[] logKey); +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/logging/LogDecrypterImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/logging/LogDecrypterImpl.java new file mode 100644 index 000000000..61cfaa136 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/logging/LogDecrypterImpl.java @@ -0,0 +1,61 @@ +package org.briarproject.briar.android.logging; + +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.reporting.DevConfig; +import org.briarproject.bramble.api.transport.StreamReaderFactory; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Scanner; +import java.util.logging.Logger; + +import javax.inject.Inject; + +import androidx.annotation.Nullable; + +import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.util.LogUtils.logException; + +@NotNullByDefault +class LogDecrypterImpl implements LogDecrypter { + + private static final Logger LOG = + getLogger(LogDecrypterImpl.class.getName()); + + private final DevConfig devConfig; + private final StreamReaderFactory streamReaderFactory; + + @Inject + LogDecrypterImpl(DevConfig devConfig, + StreamReaderFactory streamReaderFactory) { + this.devConfig = devConfig; + this.streamReaderFactory = streamReaderFactory; + } + + @Nullable + @Override + public String decryptLogs(@Nullable byte[] logKey) { + if (logKey == null) return null; + SecretKey key = new SecretKey(logKey); + File logFile = devConfig.getLogcatFile(); + try (InputStream in = new FileInputStream(logFile)) { + InputStream reader = + streamReaderFactory.createLogStreamReader(in, key); + Scanner s = new Scanner(reader); + StringBuilder sb = new StringBuilder(); + while (s.hasNextLine()) sb.append(s.nextLine()).append("\n"); + s.close(); + return sb.toString(); + } catch (IOException e) { + logException(LOG, WARNING, e); + return null; + } finally { + //noinspection ResultOfMethodCallIgnored + logFile.delete(); + } + } +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/logging/LogEncrypter.java b/briar-android/src/main/java/org/briarproject/briar/android/logging/LogEncrypter.java new file mode 100644 index 000000000..354e22ecc --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/logging/LogEncrypter.java @@ -0,0 +1,16 @@ +package org.briarproject.briar.android.logging; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.util.AndroidUtils; + +import androidx.annotation.Nullable; + +@NotNullByDefault +public interface LogEncrypter { + /** + * Writes encrypted log records to {@link AndroidUtils#getLogcatFile} + * and returns the encryption key if everything went fine. + */ + @Nullable + byte[] encryptLogs(); +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/logging/LogEncrypterImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/logging/LogEncrypterImpl.java new file mode 100644 index 000000000..f868f6ed3 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/logging/LogEncrypterImpl.java @@ -0,0 +1,77 @@ +package org.briarproject.briar.android.logging; + +import org.briarproject.bramble.api.crypto.CryptoComponent; +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.reporting.DevConfig; +import org.briarproject.bramble.api.transport.StreamWriter; +import org.briarproject.bramble.api.transport.StreamWriterFactory; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.logging.Formatter; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +import javax.inject.Inject; + +import androidx.annotation.Nullable; + +import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.util.LogUtils.logException; + +@NotNullByDefault +class LogEncrypterImpl implements LogEncrypter { + + private static final Logger LOG = + getLogger(LogEncrypterImpl.class.getName()); + + private final DevConfig devConfig; + private final CachingLogHandler logHandler; + private final CryptoComponent crypto; + private final StreamWriterFactory streamWriterFactory; + + @Inject + LogEncrypterImpl(DevConfig devConfig, + CachingLogHandler logHandler, + CryptoComponent crypto, + StreamWriterFactory streamWriterFactory) { + this.devConfig = devConfig; + this.logHandler = logHandler; + this.crypto = crypto; + this.streamWriterFactory = streamWriterFactory; + } + + @Nullable + @Override + public byte[] encryptLogs() { + SecretKey logKey = crypto.generateSecretKey(); + File logFile = devConfig.getLogcatFile(); + try (OutputStream out = new FileOutputStream(logFile)) { + StreamWriter streamWriter = + streamWriterFactory.createLogStreamWriter(out, logKey); + Writer writer = + new OutputStreamWriter(streamWriter.getOutputStream()); + writeLogString(writer); + writer.close(); + return logKey.getBytes(); + } catch (IOException e) { + logException(LOG, WARNING, e); + return null; + } + } + + private void writeLogString(Writer writer) throws IOException { + Formatter formatter = new BriefLogFormatter(); + for (LogRecord record : logHandler.getRecentLogRecords()) { + String formatted = formatter.format(record); + writer.append(formatted).append('\n'); + } + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/logging/LoggingModule.java b/briar-android/src/main/java/org/briarproject/briar/android/logging/LoggingModule.java new file mode 100644 index 000000000..e3566e68e --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/logging/LoggingModule.java @@ -0,0 +1,29 @@ +package org.briarproject.briar.android.logging; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +@Module +public class LoggingModule { + + @Provides + @Singleton + CachingLogHandler provideCachingLogHandler() { + return new CachingLogHandler(); + } + + @Provides + @Singleton + LogEncrypter provideLogEncrypter(LogEncrypterImpl logEncrypter) { + return logEncrypter; + } + + @Provides + @Singleton + LogDecrypter provideLogDecrypter(LogDecrypterImpl logDecrypter) { + return logDecrypter; + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/reporting/BriarExceptionHandler.java b/briar-android/src/main/java/org/briarproject/briar/android/reporting/BriarExceptionHandler.java index 3568bc8b4..740ebbddd 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/reporting/BriarExceptionHandler.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/reporting/BriarExceptionHandler.java @@ -1,29 +1,40 @@ package org.briarproject.briar.android.reporting; -import android.content.Context; +import android.app.Application; import android.os.Process; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.briar.android.logging.LogEncrypter; import java.lang.Thread.UncaughtExceptionHandler; +import javax.inject.Inject; + import static org.briarproject.briar.android.util.UiUtils.startDevReportActivity; @NotNullByDefault -public class BriarExceptionHandler implements UncaughtExceptionHandler { +class BriarExceptionHandler implements UncaughtExceptionHandler { - private final Context ctx; + private final Application app; + private final LogEncrypter logEncrypter; private final long appStartTime; - public BriarExceptionHandler(Context ctx) { - this.ctx = ctx; - this.appStartTime = System.currentTimeMillis(); + @Inject + BriarExceptionHandler(Application app, LogEncrypter logEncrypter) { + this.app = app; + this.logEncrypter = logEncrypter; + appStartTime = System.currentTimeMillis(); } @Override public void uncaughtException(Thread t, Throwable e) { + // encrypt logs to disk before handing over to new process + // the intent has limited space, so we can't reliably store them there. + byte[] logKey = logEncrypter.encryptLogs(); + // activity runs in its own process, so we can kill the old one - startDevReportActivity(ctx, CrashReportActivity.class, e, appStartTime); + startDevReportActivity(app.getApplicationContext(), + CrashReportActivity.class, e, appStartTime, logKey); Process.killProcess(Process.myPid()); System.exit(10); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/reporting/BriarReportCollector.java b/briar-android/src/main/java/org/briarproject/briar/android/reporting/BriarReportCollector.java index 2a8565629..723edf10a 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/reporting/BriarReportCollector.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/reporting/BriarReportCollector.java @@ -25,8 +25,6 @@ import org.briarproject.bramble.api.Pair; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.briar.BuildConfig; import org.briarproject.briar.R; -import org.briarproject.briar.android.BriarApplication; -import org.briarproject.briar.android.logging.BriefLogFormatter; import org.briarproject.briar.android.reporting.ReportData.MultiReportInfo; import org.briarproject.briar.android.reporting.ReportData.ReportItem; import org.briarproject.briar.android.reporting.ReportData.SingleReportInfo; @@ -41,8 +39,6 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.text.SimpleDateFormat; import java.util.Date; -import java.util.logging.Formatter; -import java.util.logging.LogRecord; import javax.annotation.concurrent.Immutable; @@ -74,8 +70,8 @@ class BriarReportCollector { this.ctx = ctx; } - public ReportData collectReportData(@Nullable Throwable t, - long appStartTime) { + ReportData collectReportData(@Nullable Throwable t, long appStartTime, + String logs) { ReportData reportData = new ReportData() .add(getBasicInfo(t)) .add(getDeviceInfo()); @@ -86,7 +82,7 @@ class BriarReportCollector { .add(getStorage()) .add(getConnectivity()) .add(getBuildConfig()) - .add(getLogcat()) + .add(getLogcat(logs)) .add(getDeviceFeatures()); } @@ -313,15 +309,8 @@ class BriarReportCollector { buildConfig); } - private ReportItem getLogcat() { - BriarApplication app = (BriarApplication) ctx.getApplicationContext(); - StringBuilder sb = new StringBuilder(); - Formatter formatter = new BriefLogFormatter(); - for (LogRecord record : app.getRecentLogRecords()) { - sb.append(formatter.format(record)).append('\n'); - } - return new ReportItem("Logcat", R.string.dev_report_logcat, - sb.toString()); + private ReportItem getLogcat(String logs) { + return new ReportItem("Logcat", R.string.dev_report_logcat, logs); } private ReportItem getDeviceFeatures() { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/reporting/CrashReportActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/reporting/CrashReportActivity.java index 6c3f34bd7..7023de94f 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/reporting/CrashReportActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/reporting/CrashReportActivity.java @@ -35,6 +35,7 @@ public class CrashReportActivity extends BaseActivity public static final String EXTRA_THROWABLE = "throwable"; public static final String EXTRA_APP_START_TIME = "appStartTime"; + public static final String EXTRA_APP_LOGCAT = "logcat"; @Inject ViewModelProvider.Factory viewModelFactory; @@ -56,7 +57,8 @@ public class CrashReportActivity extends BaseActivity Intent intent = getIntent(); Throwable t = (Throwable) intent.getSerializableExtra(EXTRA_THROWABLE); long appStartTime = intent.getLongExtra(EXTRA_APP_START_TIME, -1); - viewModel.init(t, appStartTime); + byte[] logKey = intent.getByteArrayExtra(EXTRA_APP_LOGCAT); + viewModel.init(t, appStartTime, logKey); viewModel.getShowReport().observeEvent(this, show -> { if (show) displayFragment(true); }); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/reporting/DevReportModule.java b/briar-android/src/main/java/org/briarproject/briar/android/reporting/DevReportModule.java index 440879e86..c4d9dfa77 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/reporting/DevReportModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/reporting/DevReportModule.java @@ -15,4 +15,8 @@ public abstract class DevReportModule { @ViewModelKey(ReportViewModel.class) abstract ViewModel bindReportViewModel(ReportViewModel reportViewModel); + @Binds + abstract Thread.UncaughtExceptionHandler bindUncaughtExceptionHandler( + BriarExceptionHandler handler); + } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/reporting/ReportViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/reporting/ReportViewModel.java index d9f8bee9b..a7648fa00 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/reporting/ReportViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/reporting/ReportViewModel.java @@ -11,6 +11,9 @@ import org.briarproject.bramble.api.plugin.TorConstants; import org.briarproject.bramble.api.reporting.DevReporter; import org.briarproject.bramble.util.AndroidUtils; import org.briarproject.briar.R; +import org.briarproject.briar.android.logging.BriefLogFormatter; +import org.briarproject.briar.android.logging.CachingLogHandler; +import org.briarproject.briar.android.logging.LogDecrypter; import org.briarproject.briar.android.reporting.ReportData.MultiReportInfo; import org.briarproject.briar.android.viewmodel.LiveEvent; import org.briarproject.briar.android.viewmodel.MutableLiveEvent; @@ -19,6 +22,7 @@ import org.json.JSONException; import java.io.File; import java.io.FileNotFoundException; import java.util.UUID; +import java.util.logging.Formatter; import java.util.logging.Logger; import javax.inject.Inject; @@ -36,13 +40,16 @@ import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty; +import static org.briarproject.briar.android.logging.BriefLogFormatter.formatLog; @NotNullByDefault -public class ReportViewModel extends AndroidViewModel { +class ReportViewModel extends AndroidViewModel { private static final Logger LOG = getLogger(ReportViewModel.class.getName()); + private final CachingLogHandler logHandler; + private final LogDecrypter logDecrypter; private final BriarReportCollector collector; private final DevReporter reporter; private final PluginManager pluginManager; @@ -58,18 +65,39 @@ public class ReportViewModel extends AndroidViewModel { private boolean isFeedback; @Inject - public ReportViewModel(@NonNull Application application, - DevReporter reporter, PluginManager pluginManager) { + ReportViewModel(@NonNull Application application, + CachingLogHandler logHandler, + LogDecrypter logDecrypter, + DevReporter reporter, + PluginManager pluginManager) { super(application); - this.collector = new BriarReportCollector(application); + collector = new BriarReportCollector(application); + this.logHandler = logHandler; + this.logDecrypter = logDecrypter; this.reporter = reporter; this.pluginManager = pluginManager; } - void init(@Nullable Throwable t, long appStartTime) { + void init(@Nullable Throwable t, long appStartTime, + @Nullable byte[] logKey) { isFeedback = t == null; if (reportData.getValue() == null) new SingleShotAndroidExecutor(() -> { - ReportData data = collector.collectReportData(t, appStartTime); + String decryptedLogs; + if (isFeedback) { + Formatter formatter = new BriefLogFormatter(); + decryptedLogs = + formatLog(formatter, logHandler.getRecentLogRecords()); + } else { + decryptedLogs = logDecrypter.decryptLogs(logKey); + if (decryptedLogs == null) { + // error decrypting logs, get logs from this process + Formatter formatter = new BriefLogFormatter(); + decryptedLogs = formatLog(formatter, + logHandler.getRecentLogRecords()); + } + } + ReportData data = + collector.collectReportData(t, appStartTime, decryptedLogs); reportData.postValue(data); }).start(); } @@ -110,8 +138,8 @@ public class ReportViewModel extends AndroidViewModel { } /** - * The content of the report - * that will be loaded after {@link #init(Throwable, long)} was called. + * The content of the report that will be loaded after + * {@link #init(Throwable, long, byte[])} was called. */ LiveData getReportData() { return reportData; diff --git a/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java b/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java index 0039dbd28..5cb811502 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/util/UiUtils.java @@ -97,6 +97,7 @@ import static java.util.concurrent.TimeUnit.DAYS; import static org.briarproject.bramble.util.AndroidUtils.getSupportedImageContentTypes; import static org.briarproject.briar.BuildConfig.APPLICATION_ID; import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE; +import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_APP_LOGCAT; import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_APP_START_TIME; import static org.briarproject.briar.android.reporting.CrashReportActivity.EXTRA_THROWABLE; @@ -357,16 +358,17 @@ public class UiUtils { } public static void triggerFeedback(Context ctx) { - startDevReportActivity(ctx, FeedbackActivity.class, null, null); + startDevReportActivity(ctx, FeedbackActivity.class, null, null, null); } public static void startDevReportActivity(Context ctx, Class activity, @Nullable Throwable t, - @Nullable Long appStartTime) { + @Nullable Long appStartTime, @Nullable byte[] logKey) { final Intent dialogIntent = new Intent(ctx, activity); dialogIntent.setFlags(FLAG_ACTIVITY_NEW_TASK); dialogIntent.putExtra(EXTRA_THROWABLE, t); dialogIntent.putExtra(EXTRA_APP_START_TIME, appStartTime); + dialogIntent.putExtra(EXTRA_APP_LOGCAT, logKey); ctx.startActivity(dialogIntent); } diff --git a/briar-android/src/test/java/org/briarproject/briar/android/logging/LogEncryptionDecryptionTest.java b/briar-android/src/test/java/org/briarproject/briar/android/logging/LogEncryptionDecryptionTest.java new file mode 100644 index 000000000..f08426579 --- /dev/null +++ b/briar-android/src/test/java/org/briarproject/briar/android/logging/LogEncryptionDecryptionTest.java @@ -0,0 +1,66 @@ +package org.briarproject.briar.android.logging; + +import org.briarproject.bramble.test.BrambleMockTestCase; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.IOException; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +import static java.util.logging.Level.FINE; +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.SEVERE; +import static java.util.logging.Level.WARNING; +import static org.briarproject.bramble.util.StringUtils.getRandomString; +import static org.briarproject.briar.android.logging.BriefLogFormatter.formatLog; +import static org.junit.Assert.assertEquals; + +public class LogEncryptionDecryptionTest extends BrambleMockTestCase { + + @ClassRule + public static TemporaryFolder folder = new TemporaryFolder(); + + private final SecureRandom random; + private final CachingLogHandler cachingLogHandler; + private final LogEncrypter logEncrypter; + private final LogDecrypter logDecrypter; + private final BriefLogFormatter logFormatter = new BriefLogFormatter(); + + public LogEncryptionDecryptionTest() throws IOException { + LoggingComponent loggingComponent = DaggerLoggingComponent.builder() + .loggingTestModule(new LoggingTestModule(folder.newFile())) + .build(); + random = loggingComponent.random(); + logEncrypter = loggingComponent.logEncrypter(); + logDecrypter = loggingComponent.logDecrypter(); + cachingLogHandler = loggingComponent.cachingLogHandler(); + } + + @Test + public void testEncryptedMatchesDecrypted() { + ArrayList logRecords = + new ArrayList<>(random.nextInt(99) + 1); + for (int i = 0; i < logRecords.size(); i++) { + LogRecord logRecord = getRandomLogRecord(); + cachingLogHandler.publish(logRecord); + logRecords.add(logRecord); + } + byte[] logKey = logEncrypter.encryptLogs(); + assertEquals(formatLog(logFormatter, logRecords), + logDecrypter.decryptLogs(logKey)); + } + + private LogRecord getRandomLogRecord() { + Level[] levels = {SEVERE, WARNING, INFO, FINE}; + Level level = levels[random.nextInt(levels.length)]; + LogRecord logRecord = + new LogRecord(level, getRandomString(random.nextInt(128) + 1)); + logRecord.setLoggerName(getRandomString(random.nextInt(23) + 1)); + return logRecord; + } + +} diff --git a/briar-android/src/test/java/org/briarproject/briar/android/logging/LoggingComponent.java b/briar-android/src/test/java/org/briarproject/briar/android/logging/LoggingComponent.java new file mode 100644 index 000000000..c3799d41e --- /dev/null +++ b/briar-android/src/test/java/org/briarproject/briar/android/logging/LoggingComponent.java @@ -0,0 +1,29 @@ +package org.briarproject.briar.android.logging; + +import org.briarproject.bramble.BrambleCoreModule; +import org.briarproject.bramble.test.TestSecureRandomModule; + +import java.security.SecureRandom; + +import javax.inject.Singleton; + +import dagger.Component; + +@Singleton +@Component(modules = { + BrambleCoreModule.class, + TestSecureRandomModule.class, + LoggingModule.class, + LoggingTestModule.class, +}) +public interface LoggingComponent { + + SecureRandom random(); + + CachingLogHandler cachingLogHandler(); + + LogEncrypter logEncrypter(); + + LogDecrypter logDecrypter(); + +} diff --git a/briar-android/src/test/java/org/briarproject/briar/android/logging/LoggingTestModule.java b/briar-android/src/test/java/org/briarproject/briar/android/logging/LoggingTestModule.java new file mode 100644 index 000000000..a4bbb6165 --- /dev/null +++ b/briar-android/src/test/java/org/briarproject/briar/android/logging/LoggingTestModule.java @@ -0,0 +1,52 @@ +package org.briarproject.briar.android.logging; + +import org.briarproject.bramble.api.crypto.PublicKey; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.reporting.DevConfig; + +import java.io.File; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +@Module +class LoggingTestModule { + + private final File logFile; + + LoggingTestModule(File logFile) { + this.logFile = logFile; + } + + @Provides + @Singleton + DevConfig provideDevConfig() { + @NotNullByDefault + DevConfig devConfig = new DevConfig() { + @Override + public PublicKey getDevPublicKey() { + throw new UnsupportedOperationException(); + + } + + @Override + public String getDevOnionAddress() { + throw new UnsupportedOperationException(); + } + + @Override + public File getReportDir() { + throw new UnsupportedOperationException(); + } + + @Override + public File getLogcatFile() { + return logFile; + } + }; + return devConfig; + } + +}