Encrypt logs on disk, store encryption key in DB.

This commit is contained in:
akwizgran
2018-10-23 14:18:31 +01:00
parent 61407c3e06
commit d9b4c013bb
12 changed files with 190 additions and 29 deletions

View File

@@ -1,8 +1,16 @@
package org.briarproject.bramble.api; package org.briarproject.bramble.api;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.Hashtable; import java.util.Hashtable;
import java.util.Map; 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<String, String> { public abstract class StringMap extends Hashtable<String, String> {
protected StringMap(Map<String, String> m) { protected StringMap(Map<String, String> m) {
@@ -52,4 +60,19 @@ public abstract class StringMap extends Hashtable<String, String> {
public void putLong(String key, long value) { public void putLong(String key, long value) {
put(key, String.valueOf(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));
}
} }

View File

@@ -19,4 +19,10 @@ public interface StreamDecrypterFactory {
*/ */
StreamDecrypter createContactExchangeStreamDecrypter(InputStream in, StreamDecrypter createContactExchangeStreamDecrypter(InputStream in,
SecretKey headerKey); SecretKey headerKey);
/**
* Creates a {@link StreamDecrypter} for decrypting a log stream.
*/
StreamDecrypter createLogStreamDecrypter(InputStream in,
SecretKey headerKey);
} }

View File

@@ -17,6 +17,12 @@ public interface StreamEncrypterFactory {
* Creates a {@link StreamEncrypter} for encrypting a contact exchange * Creates a {@link StreamEncrypter} for encrypting a contact exchange
* stream. * 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); SecretKey headerKey);
} }

View File

@@ -16,8 +16,13 @@ public interface StreamReaderFactory {
/** /**
* Creates an {@link InputStream InputStream} for reading from a contact * Creates an {@link InputStream InputStream} for reading from a contact
* exchangestream. * exchange stream.
*/ */
InputStream createContactExchangeStreamReader(InputStream in, InputStream createContactExchangeStreamReader(InputStream in,
SecretKey headerKey); SecretKey headerKey);
/**
* Creates an {@link InputStream} for reading from a log stream.
*/
InputStream createLogStreamReader(InputStream in, SecretKey headerKey);
} }

View File

@@ -9,15 +9,18 @@ import java.io.OutputStream;
public interface StreamWriterFactory { public interface StreamWriterFactory {
/** /**
* Creates an {@link OutputStream OutputStream} for writing to a * Creates a {@link StreamWriter} for writing to a transport stream.
* transport stream
*/ */
StreamWriter createStreamWriter(OutputStream out, StreamContext ctx); StreamWriter createStreamWriter(OutputStream out, StreamContext ctx);
/** /**
* Creates an {@link OutputStream OutputStream} for writing to a contact * Creates a {@link StreamWriter} for writing to a contact exchange stream.
* exchange stream.
*/ */
StreamWriter createContactExchangeStreamWriter(OutputStream out, StreamWriter createContactExchangeStreamWriter(OutputStream out,
SecretKey headerKey); SecretKey headerKey);
/**
* Creates a {@link StreamWriter} for writing to a log stream.
*/
StreamWriter createLogStreamWriter(OutputStream out, SecretKey headerKey);
} }

View File

@@ -36,4 +36,10 @@ class StreamDecrypterFactoryImpl implements StreamDecrypterFactory {
SecretKey headerKey) { SecretKey headerKey) {
return new StreamDecrypterImpl(in, cipherProvider.get(), 0, headerKey); return new StreamDecrypterImpl(in, cipherProvider.get(), 0, headerKey);
} }
@Override
public StreamDecrypter createLogStreamDecrypter(InputStream in,
SecretKey headerKey) {
return createContactExchangeStreamDecrypter(in, headerKey);
}
} }

View File

@@ -51,7 +51,7 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
} }
@Override @Override
public StreamEncrypter createContactExchangeStreamDecrypter( public StreamEncrypter createContactExchangeStreamEncrypter(
OutputStream out, SecretKey headerKey) { OutputStream out, SecretKey headerKey) {
AuthenticatedCipher cipher = cipherProvider.get(); AuthenticatedCipher cipher = cipherProvider.get();
byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH]; byte[] streamHeaderNonce = new byte[STREAM_HEADER_NONCE_LENGTH];
@@ -60,4 +60,10 @@ class StreamEncrypterFactoryImpl implements StreamEncrypterFactory {
return new StreamEncrypterImpl(out, cipher, 0, null, streamHeaderNonce, return new StreamEncrypterImpl(out, cipher, 0, null, streamHeaderNonce,
headerKey, frameKey); headerKey, frameKey);
} }
@Override
public StreamEncrypter createLogStreamEncrypter(OutputStream out,
SecretKey headerKey) {
return createContactExchangeStreamEncrypter(out, headerKey);
}
} }

View File

@@ -24,15 +24,21 @@ class StreamReaderFactoryImpl implements StreamReaderFactory {
@Override @Override
public InputStream createStreamReader(InputStream in, StreamContext ctx) { public InputStream createStreamReader(InputStream in, StreamContext ctx) {
return new StreamReaderImpl( return new StreamReaderImpl(streamDecrypterFactory
streamDecrypterFactory.createStreamDecrypter(in, ctx)); .createStreamDecrypter(in, ctx));
} }
@Override @Override
public InputStream createContactExchangeStreamReader(InputStream in, public InputStream createContactExchangeStreamReader(InputStream in,
SecretKey headerKey) { SecretKey headerKey) {
return new StreamReaderImpl( return new StreamReaderImpl(streamDecrypterFactory
streamDecrypterFactory.createContactExchangeStreamDecrypter(in, .createContactExchangeStreamDecrypter(in, headerKey));
headerKey)); }
@Override
public InputStream createLogStreamReader(InputStream in,
SecretKey headerKey) {
return new StreamReaderImpl(streamDecrypterFactory
.createLogStreamDecrypter(in, headerKey));
} }
} }

View File

@@ -26,15 +26,21 @@ class StreamWriterFactoryImpl implements StreamWriterFactory {
@Override @Override
public StreamWriter createStreamWriter(OutputStream out, public StreamWriter createStreamWriter(OutputStream out,
StreamContext ctx) { StreamContext ctx) {
return new StreamWriterImpl( return new StreamWriterImpl(streamEncrypterFactory
streamEncrypterFactory.createStreamEncrypter(out, ctx)); .createStreamEncrypter(out, ctx));
} }
@Override @Override
public StreamWriter createContactExchangeStreamWriter(OutputStream out, public StreamWriter createContactExchangeStreamWriter(OutputStream out,
SecretKey headerKey) { SecretKey headerKey) {
return new StreamWriterImpl( return new StreamWriterImpl(streamEncrypterFactory
streamEncrypterFactory.createContactExchangeStreamDecrypter(out, .createContactExchangeStreamEncrypter(out, headerKey));
headerKey)); }
@Override
public StreamWriter createLogStreamWriter(OutputStream out,
SecretKey headerKey) {
return new StreamWriterImpl(streamEncrypterFactory
.createLogStreamEncrypter(out, headerKey));
} }
} }

View File

@@ -1,6 +1,7 @@
package org.briarproject.briar.api.logging; package org.briarproject.briar.api.logging;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.settings.Settings;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -10,7 +11,25 @@ import java.util.logging.Handler;
@NotNullByDefault @NotNullByDefault
public interface PersistentLogManager { 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; 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<String> getPersistedLog(File dir) throws IOException; Collection<String> getPersistedLog(File dir) throws IOException;
} }

View File

@@ -1,9 +1,12 @@
package org.briarproject.briar.logging; package org.briarproject.briar.logging;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.briar.api.logging.PersistentLogManager; import org.briarproject.briar.api.logging.PersistentLogManager;
import java.util.logging.Formatter; import java.util.logging.Formatter;
import javax.inject.Singleton;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
@@ -16,8 +19,11 @@ public class LoggingModule {
} }
@Provides @Provides
@Singleton
PersistentLogManager providePersistentLogManager( PersistentLogManager providePersistentLogManager(
PersistentLogManagerImpl logManager) { LifecycleManager lifecycleManager,
return logManager; PersistentLogManagerImpl persistentLogManager) {
lifecycleManager.registerOpenDatabaseHook(persistentLogManager);
return persistentLogManager;
} }
} }

View File

@@ -1,14 +1,26 @@
package org.briarproject.briar.logging; 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.IoExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager.OpenDatabaseHook;
import org.briarproject.bramble.api.lifecycle.ShutdownManager; import org.briarproject.bramble.api.lifecycle.ShutdownManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; 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.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 org.briarproject.briar.api.logging.PersistentLogManager;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@@ -21,16 +33,20 @@ import java.util.logging.Handler;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.logging.StreamHandler; import java.util.logging.StreamHandler;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe; import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject; import javax.inject.Inject;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
@ThreadSafe @ThreadSafe
@NotNullByDefault @NotNullByDefault
class PersistentLogManagerImpl implements PersistentLogManager { class PersistentLogManagerImpl implements PersistentLogManager,
OpenDatabaseHook {
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(PersistentLogManagerImpl.class.getName()); Logger.getLogger(PersistentLogManagerImpl.class.getName());
@@ -42,16 +58,47 @@ class PersistentLogManagerImpl implements PersistentLogManager {
private final ScheduledExecutorService scheduler; private final ScheduledExecutorService scheduler;
private final Executor ioExecutor; private final Executor ioExecutor;
private final ShutdownManager shutdownManager; private final ShutdownManager shutdownManager;
private final DatabaseComponent db;
private final StreamReaderFactory streamReaderFactory;
private final StreamWriterFactory streamWriterFactory;
private final Formatter formatter; private final Formatter formatter;
private final SecretKey logKey;
@Nullable
private volatile SecretKey oldLogKey = null;
@Inject @Inject
PersistentLogManagerImpl(@Scheduler ScheduledExecutorService scheduler, PersistentLogManagerImpl(
@IoExecutor Executor ioExecutor, ShutdownManager shutdownManager, @Scheduler ScheduledExecutorService scheduler,
Formatter formatter) { @IoExecutor Executor ioExecutor,
ShutdownManager shutdownManager,
DatabaseComponent db,
StreamReaderFactory streamReaderFactory,
StreamWriterFactory streamWriterFactory,
Formatter formatter,
CryptoComponent crypto) {
this.scheduler = scheduler; this.scheduler = scheduler;
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
this.shutdownManager = shutdownManager; this.shutdownManager = shutdownManager;
this.db = db;
this.streamReaderFactory = streamReaderFactory;
this.streamWriterFactory = streamWriterFactory;
this.formatter = formatter; 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 @Override
@@ -64,11 +111,24 @@ class PersistentLogManagerImpl implements PersistentLogManager {
LOG.warning("Failed to rename log file"); LOG.warning("Failed to rename log file");
try { try {
OutputStream out = new FileOutputStream(logFile); 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(() -> scheduler.scheduleWithFixedDelay(() ->
ioExecutor.execute(handler::flush), ioExecutor.execute(handler::flush),
FLUSH_INTERVAL_MS, FLUSH_INTERVAL_MS, MILLISECONDS); 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; return handler;
} catch (SecurityException e) { } catch (SecurityException e) {
throw new IOException(e); throw new IOException(e);
@@ -77,14 +137,23 @@ class PersistentLogManagerImpl implements PersistentLogManager {
@Override @Override
public Collection<String> getPersistedLog(File dir) throws IOException { public Collection<String> 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); File oldLogFile = new File(dir, OLD_LOG_FILE);
if (oldLogFile.exists()) { if (oldLogFile.exists()) {
LOG.info("Reading old log file"); LOG.info("Reading old log file");
List<String> lines = new ArrayList<>(); List<String> lines = new ArrayList<>();
Scanner s = new Scanner(oldLogFile); try (InputStream in = new FileInputStream(oldLogFile)) {
while (s.hasNextLine()) lines.add(s.nextLine()); InputStream reader = streamReaderFactory
s.close(); .createLogStreamReader(in, oldLogKey);
return lines; Scanner s = new Scanner(reader);
while (s.hasNextLine()) lines.add(s.nextLine());
s.close();
return lines;
}
} else { } else {
LOG.info("Old log file does not exist"); LOG.info("Old log file does not exist");
return emptyList(); return emptyList();