diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/StringMap.java b/bramble-api/src/main/java/org/briarproject/bramble/api/StringMap.java index bda2a94d2..978b83e4b 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/StringMap.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/StringMap.java @@ -1,8 +1,16 @@ package org.briarproject.bramble.api; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + import java.util.Hashtable; import java.util.Map; +import javax.annotation.Nullable; + +import static org.briarproject.bramble.util.StringUtils.fromHexString; +import static org.briarproject.bramble.util.StringUtils.toHexString; + +@NotNullByDefault public abstract class StringMap extends Hashtable { protected StringMap(Map m) { @@ -52,4 +60,19 @@ public abstract class StringMap extends Hashtable { public void putLong(String key, long value) { put(key, String.valueOf(value)); } + + @Nullable + public byte[] getBytes(String key) { + String s = get(key); + if (s == null) return null; + try { + return fromHexString(s); + } catch (IllegalArgumentException e) { + return null; + } + } + + public void putBytes(String key, byte[] value) { + put(key, toHexString(value)); + } } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/StreamDecrypterFactory.java b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/StreamDecrypterFactory.java index beecd1789..47e97ae00 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/StreamDecrypterFactory.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/StreamDecrypterFactory.java @@ -19,4 +19,10 @@ public interface StreamDecrypterFactory { */ StreamDecrypter createContactExchangeStreamDecrypter(InputStream in, SecretKey headerKey); + + /** + * Creates a {@link StreamDecrypter} for decrypting a log stream. + */ + StreamDecrypter createLogStreamDecrypter(InputStream in, + SecretKey headerKey); } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/StreamEncrypterFactory.java b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/StreamEncrypterFactory.java index 03ad5e143..4b68c0441 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/StreamEncrypterFactory.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/StreamEncrypterFactory.java @@ -17,6 +17,12 @@ public interface StreamEncrypterFactory { * Creates a {@link StreamEncrypter} for encrypting a contact exchange * stream. */ - StreamEncrypter createContactExchangeStreamDecrypter(OutputStream out, + StreamEncrypter createContactExchangeStreamEncrypter(OutputStream out, + SecretKey headerKey); + + /** + * Creates a {@link StreamEncrypter} for encrypting a log stream. + */ + StreamEncrypter createLogStreamEncrypter(OutputStream out, SecretKey headerKey); } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/StreamReaderFactory.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/StreamReaderFactory.java index 87823679b..dc60242f8 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/StreamReaderFactory.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/StreamReaderFactory.java @@ -16,8 +16,13 @@ public interface StreamReaderFactory { /** * Creates an {@link InputStream InputStream} for reading from a contact - * exchangestream. + * exchange stream. */ InputStream createContactExchangeStreamReader(InputStream in, SecretKey headerKey); + + /** + * Creates an {@link InputStream} for reading from a log stream. + */ + InputStream createLogStreamReader(InputStream in, SecretKey headerKey); } diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/StreamWriterFactory.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/StreamWriterFactory.java index 1ddde0c0a..9dff5b2e2 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/StreamWriterFactory.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/StreamWriterFactory.java @@ -9,15 +9,18 @@ import java.io.OutputStream; public interface StreamWriterFactory { /** - * Creates an {@link OutputStream OutputStream} for writing to a - * transport stream + * Creates a {@link StreamWriter} for writing to a transport stream. */ StreamWriter createStreamWriter(OutputStream out, StreamContext ctx); /** - * Creates an {@link OutputStream OutputStream} for writing to a contact - * exchange stream. + * Creates a {@link StreamWriter} for writing to a contact exchange stream. */ StreamWriter createContactExchangeStreamWriter(OutputStream out, SecretKey headerKey); + + /** + * Creates a {@link StreamWriter} for writing to a log stream. + */ + StreamWriter createLogStreamWriter(OutputStream out, SecretKey headerKey); } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamDecrypterFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamDecrypterFactoryImpl.java index aac3e504e..093136662 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamDecrypterFactoryImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamDecrypterFactoryImpl.java @@ -36,4 +36,10 @@ class StreamDecrypterFactoryImpl implements StreamDecrypterFactory { SecretKey headerKey) { return new StreamDecrypterImpl(in, cipherProvider.get(), 0, headerKey); } + + @Override + public StreamDecrypter createLogStreamDecrypter(InputStream in, + SecretKey headerKey) { + return createContactExchangeStreamDecrypter(in, headerKey); + } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterFactoryImpl.java index 984d5e826..ae8db9fb7 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterFactoryImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/StreamEncrypterFactoryImpl.java @@ -51,7 +51,7 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory { } @Override - public StreamEncrypter createContactExchangeStreamDecrypter( + public StreamEncrypter createContactExchangeStreamEncrypter( OutputStream out, SecretKey headerKey) { AuthenticatedCipher cipher = cipherProvider.get(); byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH]; @@ -60,4 +60,10 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory { return new StreamEncrypterImpl(out, cipher, 0, null, streamHeaderNonce, headerKey, frameKey); } + + @Override + public StreamEncrypter createLogStreamEncrypter(OutputStream out, + SecretKey headerKey) { + return createContactExchangeStreamEncrypter(out, headerKey); + } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/StreamReaderFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/StreamReaderFactoryImpl.java index 4d6475b23..2b8596047 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/transport/StreamReaderFactoryImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/StreamReaderFactoryImpl.java @@ -24,15 +24,21 @@ class StreamReaderFactoryImpl implements StreamReaderFactory { @Override public InputStream createStreamReader(InputStream in, StreamContext ctx) { - return new StreamReaderImpl( - streamDecrypterFactory.createStreamDecrypter(in, ctx)); + return new StreamReaderImpl(streamDecrypterFactory + .createStreamDecrypter(in, ctx)); } @Override public InputStream createContactExchangeStreamReader(InputStream in, SecretKey headerKey) { - return new StreamReaderImpl( - streamDecrypterFactory.createContactExchangeStreamDecrypter(in, - headerKey)); + return new StreamReaderImpl(streamDecrypterFactory + .createContactExchangeStreamDecrypter(in, headerKey)); + } + + @Override + public InputStream createLogStreamReader(InputStream in, + SecretKey headerKey) { + return new StreamReaderImpl(streamDecrypterFactory + .createLogStreamDecrypter(in, headerKey)); } } diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/StreamWriterFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/StreamWriterFactoryImpl.java index 3410e8446..bb6f28351 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/transport/StreamWriterFactoryImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/StreamWriterFactoryImpl.java @@ -26,15 +26,21 @@ class StreamWriterFactoryImpl implements StreamWriterFactory { @Override public StreamWriter createStreamWriter(OutputStream out, StreamContext ctx) { - return new StreamWriterImpl( - streamEncrypterFactory.createStreamEncrypter(out, ctx)); + return new StreamWriterImpl(streamEncrypterFactory + .createStreamEncrypter(out, ctx)); } @Override public StreamWriter createContactExchangeStreamWriter(OutputStream out, SecretKey headerKey) { - return new StreamWriterImpl( - streamEncrypterFactory.createContactExchangeStreamDecrypter(out, - headerKey)); + return new StreamWriterImpl(streamEncrypterFactory + .createContactExchangeStreamEncrypter(out, headerKey)); + } + + @Override + public StreamWriter createLogStreamWriter(OutputStream out, + SecretKey headerKey) { + return new StreamWriterImpl(streamEncrypterFactory + .createLogStreamEncrypter(out, headerKey)); } } \ No newline at end of file diff --git a/briar-api/src/main/java/org/briarproject/briar/api/logging/PersistentLogManager.java b/briar-api/src/main/java/org/briarproject/briar/api/logging/PersistentLogManager.java index 661454527..a0e44375f 100644 --- a/briar-api/src/main/java/org/briarproject/briar/api/logging/PersistentLogManager.java +++ b/briar-api/src/main/java/org/briarproject/briar/api/logging/PersistentLogManager.java @@ -1,6 +1,7 @@ package org.briarproject.briar.api.logging; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.settings.Settings; import java.io.File; import java.io.IOException; @@ -10,7 +11,25 @@ import java.util.logging.Handler; @NotNullByDefault public interface PersistentLogManager { + /** + * The namespace of the (@link Settings) where the log key is stored. + */ + String LOG_SETTINGS_NAMESPACE = "log"; + + /** + * The {@link Settings} key under which the log key is stored. + */ + String LOG_KEY_KEY = "logKey"; + + /** + * Creates and returns a persistent log handler that stores its logs in + * the given directory. + */ Handler createLogHandler(File dir) throws IOException; + /** + * Loads and returns the persistent log entries stored in the given + * directory, or an empty list if no log entries are found. + */ Collection getPersistedLog(File dir) throws IOException; } diff --git a/briar-core/src/main/java/org/briarproject/briar/logging/LoggingModule.java b/briar-core/src/main/java/org/briarproject/briar/logging/LoggingModule.java index 7d69cf188..6d876afd8 100644 --- a/briar-core/src/main/java/org/briarproject/briar/logging/LoggingModule.java +++ b/briar-core/src/main/java/org/briarproject/briar/logging/LoggingModule.java @@ -1,9 +1,12 @@ package org.briarproject.briar.logging; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.briar.api.logging.PersistentLogManager; import java.util.logging.Formatter; +import javax.inject.Singleton; + import dagger.Module; import dagger.Provides; @@ -16,8 +19,11 @@ public class LoggingModule { } @Provides + @Singleton PersistentLogManager providePersistentLogManager( - PersistentLogManagerImpl logManager) { - return logManager; + LifecycleManager lifecycleManager, + PersistentLogManagerImpl persistentLogManager) { + lifecycleManager.registerOpenDatabaseHook(persistentLogManager); + return persistentLogManager; } } diff --git a/briar-core/src/main/java/org/briarproject/briar/logging/PersistentLogManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/logging/PersistentLogManagerImpl.java index bbbed14e3..64c8816a0 100644 --- a/briar-core/src/main/java/org/briarproject/briar/logging/PersistentLogManagerImpl.java +++ b/briar-core/src/main/java/org/briarproject/briar/logging/PersistentLogManagerImpl.java @@ -1,14 +1,26 @@ package org.briarproject.briar.logging; +import org.briarproject.bramble.api.crypto.CryptoComponent; +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.db.DatabaseComponent; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.db.Transaction; import org.briarproject.bramble.api.lifecycle.IoExecutor; +import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook; import org.briarproject.bramble.api.lifecycle.ShutdownManager; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.system.Scheduler; +import org.briarproject.bramble.api.transport.StreamReaderFactory; +import org.briarproject.bramble.api.transport.StreamWriter; +import org.briarproject.bramble.api.transport.StreamWriterFactory; import org.briarproject.briar.api.logging.PersistentLogManager; import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collection; @@ -21,16 +33,20 @@ import java.util.logging.Handler; import java.util.logging.Logger; import java.util.logging.StreamHandler; +import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; import javax.inject.Inject; import static java.util.Collections.emptyList; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.logging.Level.WARNING; +import static org.briarproject.bramble.util.LogUtils.logException; @ThreadSafe @NotNullByDefault -class PersistentLogManagerImpl implements PersistentLogManager { +class PersistentLogManagerImpl implements PersistentLogManager, + OpenDatabaseHook { private static final Logger LOG = Logger.getLogger(PersistentLogManagerImpl.class.getName()); @@ -42,16 +58,47 @@ class PersistentLogManagerImpl implements PersistentLogManager { private final ScheduledExecutorService scheduler; private final Executor ioExecutor; private final ShutdownManager shutdownManager; + private final DatabaseComponent db; + private final StreamReaderFactory streamReaderFactory; + private final StreamWriterFactory streamWriterFactory; private final Formatter formatter; + private final SecretKey logKey; + + @Nullable + private volatile SecretKey oldLogKey = null; @Inject - PersistentLogManagerImpl(@Scheduler ScheduledExecutorService scheduler, - @IoExecutor Executor ioExecutor, ShutdownManager shutdownManager, - Formatter formatter) { + PersistentLogManagerImpl( + @Scheduler ScheduledExecutorService scheduler, + @IoExecutor Executor ioExecutor, + ShutdownManager shutdownManager, + DatabaseComponent db, + StreamReaderFactory streamReaderFactory, + StreamWriterFactory streamWriterFactory, + Formatter formatter, + CryptoComponent crypto) { this.scheduler = scheduler; this.ioExecutor = ioExecutor; this.shutdownManager = shutdownManager; + this.db = db; + this.streamReaderFactory = streamReaderFactory; + this.streamWriterFactory = streamWriterFactory; this.formatter = formatter; + logKey = crypto.generateSecretKey(); + } + + @Override + public void onDatabaseOpened(Transaction txn) throws DbException { + Settings s = db.getSettings(txn, LOG_SETTINGS_NAMESPACE); + // Load the old log key, if any + byte[] oldKeyBytes = s.getBytes(LOG_KEY_KEY); + if (oldKeyBytes != null && oldKeyBytes.length == SecretKey.LENGTH) { + LOG.info("Loaded old log key"); + oldLogKey = new SecretKey(oldKeyBytes); + } + // Store the current log key + s.putBytes(LOG_KEY_KEY, logKey.getBytes()); + db.mergeSettings(txn, s, LOG_SETTINGS_NAMESPACE); } @Override @@ -64,11 +111,24 @@ class PersistentLogManagerImpl implements PersistentLogManager { LOG.warning("Failed to rename log file"); try { OutputStream out = new FileOutputStream(logFile); - StreamHandler handler = new StreamHandler(out, formatter); + StreamWriter writer = + streamWriterFactory.createLogStreamWriter(out, logKey); + StreamHandler handler = + new StreamHandler(writer.getOutputStream(), formatter); + // Flush the log periodically in case we're killed without getting + // the chance to run shutdown hooks scheduler.scheduleWithFixedDelay(() -> ioExecutor.execute(handler::flush), FLUSH_INTERVAL_MS, FLUSH_INTERVAL_MS, MILLISECONDS); - shutdownManager.addShutdownHook(handler::flush); + // Flush the log and terminate the stream at shutdown + shutdownManager.addShutdownHook(() -> { + handler.flush(); + try { + writer.sendEndOfStream(); + } catch (IOException e) { + logException(LOG, WARNING, e); + } + }); return handler; } catch (SecurityException e) { throw new IOException(e); @@ -77,14 +137,23 @@ class PersistentLogManagerImpl implements PersistentLogManager { @Override public Collection getPersistedLog(File dir) throws IOException { + SecretKey oldLogKey = this.oldLogKey; + if (oldLogKey == null) { + LOG.info("Old log key has not been loaded"); + return emptyList(); + } File oldLogFile = new File(dir, OLD_LOG_FILE); if (oldLogFile.exists()) { LOG.info("Reading old log file"); List lines = new ArrayList<>(); - Scanner s = new Scanner(oldLogFile); - while (s.hasNextLine()) lines.add(s.nextLine()); - s.close(); - return lines; + try (InputStream in = new FileInputStream(oldLogFile)) { + InputStream reader = streamReaderFactory + .createLogStreamReader(in, oldLogKey); + Scanner s = new Scanner(reader); + while (s.hasNextLine()) lines.add(s.nextLine()); + s.close(); + return lines; + } } else { LOG.info("Old log file does not exist"); return emptyList();