Move encrypted key, account deletion into AccountManager.

This commit is contained in:
akwizgran
2018-07-23 10:18:38 +01:00
parent cb29c9bf32
commit f9495b49d6
34 changed files with 418 additions and 462 deletions

View File

@@ -1,21 +1,140 @@
package org.briarproject.bramble.account;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
@MethodsNotNullByDefault
@ParametersNotNullByDefault
class AccountManagerImpl implements AccountManager {
private static final Logger LOG =
Logger.getLogger(AccountManagerImpl.class.getName());
private static final String DB_KEY_FILENAME = "db.key";
private static final String DB_KEY_BACKUP_FILENAME = "db.key.bak";
private final DatabaseConfig databaseConfig;
private final File dbKeyFile, dbKeyBackupFile;
@Nullable
private volatile SecretKey databaseKey = null;
@Inject
AccountManagerImpl(DatabaseConfig databaseConfig) {
this.databaseConfig = databaseConfig;
File keyDir = databaseConfig.getDatabaseKeyDirectory();
dbKeyFile = new File(keyDir, DB_KEY_FILENAME);
dbKeyBackupFile = new File(keyDir, DB_KEY_BACKUP_FILENAME);
}
@Override
public boolean hasDatabaseKey() {
return databaseConfig.getEncryptionKey() != null;
return databaseKey != null;
}
@Override
@Nullable
public SecretKey getDatabaseKey() {
return databaseKey;
}
@Override
public void setDatabaseKey(SecretKey k) {
databaseKey = k;
}
@Override
@Nullable
public String getEncryptedDatabaseKey() {
String key = readDbKeyFromFile(dbKeyFile);
if (key == null) {
LOG.info("No database key in primary file");
key = readDbKeyFromFile(dbKeyBackupFile);
if (key == null) LOG.info("No database key in backup file");
else LOG.warning("Found database key in backup file");
} else {
LOG.info("Found database key in primary file");
}
return key;
}
@Nullable
private String readDbKeyFromFile(File f) {
if (!f.exists()) {
LOG.info("Key file does not exist");
return null;
}
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(
new FileInputStream(f), "UTF-8"));
String key = reader.readLine();
reader.close();
return key;
} catch (IOException e) {
logException(LOG, WARNING, e);
return null;
}
}
@Override
public boolean storeEncryptedDatabaseKey(String hex) {
LOG.info("Storing database key in file");
// Create the directory if necessary
if (databaseConfig.getDatabaseKeyDirectory().mkdirs())
LOG.info("Created database key directory");
// If only the backup file exists, rename it so we don't overwrite it
if (dbKeyBackupFile.exists() && !dbKeyFile.exists()) {
if (dbKeyBackupFile.renameTo(dbKeyFile))
LOG.info("Renamed old backup");
else LOG.warning("Failed to rename old backup");
}
try {
// Write to the backup file
writeDbKeyToFile(hex, dbKeyBackupFile);
LOG.info("Stored database key in backup file");
// Delete the old primary file, if it exists
if (dbKeyFile.exists()) {
if (dbKeyFile.delete()) LOG.info("Deleted primary file");
else LOG.warning("Failed to delete primary file");
}
// The backup file becomes the new primary
if (dbKeyBackupFile.renameTo(dbKeyFile)) {
LOG.info("Renamed backup file to primary");
} else {
LOG.warning("Failed to rename backup file to primary");
return false; // Don't overwrite our only copy
}
// Write a second copy to the backup file
writeDbKeyToFile(hex, dbKeyBackupFile);
LOG.info("Stored second copy of database key in backup file");
return true;
} catch (IOException e) {
logException(LOG, WARNING, e);
return false;
}
}
private void writeDbKeyToFile(String key, File f) throws IOException {
FileOutputStream out = new FileOutputStream(f);
out.write(key.getBytes("UTF-8"));
out.flush();
out.close();
}
}

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.db;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DataTooNewException;
import org.briarproject.bramble.api.db.DataTooOldException;
import org.briarproject.bramble.api.db.DbException;
@@ -48,7 +49,8 @@ interface Database<T> {
* @throws DataTooOldException if the data uses an older schema than the
* current code and cannot be migrated
*/
boolean open(@Nullable MigrationListener listener) throws DbException;
boolean open(SecretKey key, @Nullable MigrationListener listener)
throws DbException;
/**
* Prevents new transactions from starting, waits for all current
@@ -641,7 +643,7 @@ interface Database<T> {
* Marks the given transport keys as usable for outgoing streams.
*/
void setTransportKeysActive(T txn, TransportId t, KeySetId k)
throws DbException;
throws DbException;
/**
* Updates the transmission count and expiry time of the given message

View File

@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.contact.event.ContactStatusChangedEvent;
import org.briarproject.bramble.api.contact.event.ContactVerifiedEvent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.ContactExistsException;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
@@ -103,9 +104,9 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
}
@Override
public boolean open(@Nullable MigrationListener listener)
public boolean open(SecretKey key, @Nullable MigrationListener listener)
throws DbException {
boolean reopened = db.open(listener);
boolean reopened = db.open(key, listener);
shutdown.addShutdownHook(() -> {
try {
close();

View File

@@ -32,6 +32,9 @@ class H2Database extends JdbcDatabase {
private final DatabaseConfig config;
private final String url;
@Nullable
private volatile SecretKey key = null;
@Inject
H2Database(DatabaseConfig config, Clock clock) {
super(HASH_TYPE, SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE,
@@ -44,11 +47,12 @@ class H2Database extends JdbcDatabase {
}
@Override
public boolean open(@Nullable MigrationListener listener)
public boolean open(SecretKey key, @Nullable MigrationListener listener)
throws DbException {
this.key = key;
boolean reopen = config.databaseExists();
if (!reopen) config.getDatabaseDirectory().mkdirs();
super.open("org.h2.Driver", reopen, listener);
super.open("org.h2.Driver", reopen, key, listener);
return reopen;
}
@@ -63,7 +67,7 @@ class H2Database extends JdbcDatabase {
}
@Override
public long getFreeSpace() throws DbException {
public long getFreeSpace() {
File dir = config.getDatabaseDirectory();
long maxSize = config.getMaxSize();
long free = dir.getFreeSpace();
@@ -88,7 +92,7 @@ class H2Database extends JdbcDatabase {
@Override
protected Connection createConnection() throws SQLException {
SecretKey key = config.getEncryptionKey();
SecretKey key = this.key;
if (key == null) throw new IllegalStateException();
Properties props = new Properties();
props.setProperty("user", "user");

View File

@@ -33,6 +33,9 @@ class HyperSqlDatabase extends JdbcDatabase {
private final DatabaseConfig config;
private final String url;
@Nullable
private volatile SecretKey key = null;
@Inject
HyperSqlDatabase(DatabaseConfig config, Clock clock) {
super(HASH_TYPE, SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE,
@@ -46,10 +49,12 @@ class HyperSqlDatabase extends JdbcDatabase {
}
@Override
public boolean open(@Nullable MigrationListener listener) throws DbException {
public boolean open(SecretKey key, @Nullable MigrationListener listener)
throws DbException {
this.key = key;
boolean reopen = config.databaseExists();
if (!reopen) config.getDatabaseDirectory().mkdirs();
super.open("org.hsqldb.jdbc.JDBCDriver", reopen, listener);
super.open("org.hsqldb.jdbc.JDBCDriver", reopen, key, listener);
return reopen;
}
@@ -93,7 +98,7 @@ class HyperSqlDatabase extends JdbcDatabase {
@Override
protected Connection createConnection() throws SQLException {
SecretKey key = config.getEncryptionKey();
SecretKey key = this.key;
if (key == null) throw new IllegalStateException();
String hex = StringUtils.toHexString(key.getBytes());
return DriverManager.getConnection(url + ";crypt_key=" + hex);

View File

@@ -328,7 +328,7 @@ abstract class JdbcDatabase implements Database<Connection> {
this.clock = clock;
}
protected void open(String driverClass, boolean reopen,
protected void open(String driverClass, boolean reopen, SecretKey key,
@Nullable MigrationListener listener) throws DbException {
// Load the JDBC driver
try {

View File

@@ -1,5 +1,7 @@
package org.briarproject.bramble.lifecycle;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DataTooNewException;
import org.briarproject.bramble.api.db.DataTooOldException;
import org.briarproject.bramble.api.db.DatabaseComponent;
@@ -55,6 +57,7 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
private final List<Service> services;
private final List<Client> clients;
private final List<ExecutorService> executors;
private final AccountManager accountManager;
private final IdentityManager identityManager;
private final Semaphore startStopSemaphore = new Semaphore(1);
private final CountDownLatch dbLatch = new CountDownLatch(1);
@@ -65,9 +68,10 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
@Inject
LifecycleManagerImpl(DatabaseComponent db, EventBus eventBus,
IdentityManager identityManager) {
AccountManager accountManager, IdentityManager identityManager) {
this.db = db;
this.eventBus = eventBus;
this.accountManager = accountManager;
this.identityManager = identityManager;
services = new CopyOnWriteArrayList<>();
clients = new CopyOnWriteArrayList<>();
@@ -104,7 +108,9 @@ class LifecycleManagerImpl implements LifecycleManager, MigrationListener {
LOG.info("Starting services");
long start = now();
boolean reopened = db.open(this);
SecretKey key = accountManager.getDatabaseKey();
if (key == null) throw new IllegalStateException();
boolean reopened = db.open(key, this);
if (reopened) logDuration(LOG, "Reopening database", start);
else logDuration(LOG, "Creating database", start);
identityManager.storeLocalAuthor();