Store database key in a file rather than shared prefs.

This commit is contained in:
akwizgran
2018-05-18 14:47:53 +01:00
parent cfdbd29cb4
commit 21dae824a6
10 changed files with 251 additions and 67 deletions

View File

@@ -0,0 +1,35 @@
package org.briarproject.briar.android;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
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 javax.annotation.Nullable;
import static junit.framework.Assert.assertTrue;
@NotNullByDefault
public class TestDatabaseKeyUtils {
public static void storeDatabaseKey(File f, String hex) throws IOException {
assertTrue(f.getParentFile().mkdirs());
FileOutputStream out = new FileOutputStream(f);
out.write(hex.getBytes("UTF-8"));
out.flush();
out.close();
}
@Nullable
public static String loadDatabaseKey(File f) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(
new FileInputStream(f), "UTF-8"));
String hex = reader.readLine();
reader.close();
return hex;
}
}

View File

@@ -8,14 +8,23 @@ import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.ImmediateExecutor;
import org.jmock.Expectations;
import org.junit.After;
import org.junit.Test;
import java.io.File;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.briarproject.bramble.util.StringUtils.toHexString;
import static org.briarproject.briar.android.TestDatabaseKeyUtils.loadDatabaseKey;
import static org.briarproject.briar.android.TestDatabaseKeyUtils.storeDatabaseKey;
public class PasswordControllerImplTest extends BrambleMockTestCase {
@@ -26,62 +35,84 @@ public class PasswordControllerImplTest extends BrambleMockTestCase {
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
private final PasswordStrengthEstimator estimator =
context.mock(PasswordStrengthEstimator.class);
private final SharedPreferences.Editor editor =
context.mock(SharedPreferences.Editor.class);
private final Executor cryptoExecutor = new ImmediateExecutor();
private final String oldPassword = "some.old.pass";
private final String newPassword = "some.new.pass";
private final String oldEncryptedHex = "010203";
private final String newEncryptedHex = "020304";
private final byte[] oldEncryptedBytes = new byte[] {1, 2, 3};
private final byte[] newEncryptedBytes = new byte[] {2, 3, 4};
private final byte[] keyBytes = getSecretKey().getBytes();
private final byte[] oldEncryptedKey = getRandomBytes(123);
private final byte[] newEncryptedKey = getRandomBytes(123);
private final byte[] key = getSecretKey().getBytes();
private final File testDir = getTestDirectory();
private final File keyDir = new File(testDir, "key");
private final File keyFile = new File(keyDir, "db.key");
private final File keyBackupFile = new File(keyDir, "db.key.bak");
@Test
public void testChangePasswordReturnsTrue() {
public void testChangePasswordReturnsTrue() throws Exception {
context.checking(new Expectations() {{
// Look up the encrypted DB key
oneOf(briarPrefs).getString("key", null);
will(returnValue(oldEncryptedHex));
will(returnValue(null));
allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir));
// Decrypt and re-encrypt the key
oneOf(crypto).decryptWithPassword(oldEncryptedBytes, oldPassword);
will(returnValue(keyBytes));
oneOf(crypto).encryptWithPassword(keyBytes, newPassword);
will(returnValue(newEncryptedBytes));
// Store the re-encrypted key
oneOf(briarPrefs).edit();
will(returnValue(editor));
oneOf(editor).putString("key", newEncryptedHex);
will(returnValue(editor));
oneOf(editor).commit();
oneOf(crypto).decryptWithPassword(oldEncryptedKey, oldPassword);
will(returnValue(key));
oneOf(crypto).encryptWithPassword(key, newPassword);
will(returnValue(newEncryptedKey));
}});
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
storeDatabaseKey(keyFile, toHexString(oldEncryptedKey));
PasswordControllerImpl p = new PasswordControllerImpl(briarPrefs,
databaseConfig, cryptoExecutor, crypto, estimator);
AtomicBoolean capturedResult = new AtomicBoolean(false);
p.changePassword(oldPassword, newPassword, capturedResult::set);
assertTrue(capturedResult.get());
assertTrue(keyFile.exists());
assertFalse(keyBackupFile.exists());
assertEquals(toHexString(newEncryptedKey), loadDatabaseKey(keyFile));
}
@Test
public void testChangePasswordReturnsFalseIfOldPasswordIsWrong() {
public void testChangePasswordReturnsFalseIfOldPasswordIsWrong()
throws Exception {
context.checking(new Expectations() {{
// Look up the encrypted DB key
oneOf(briarPrefs).getString("key", null);
will(returnValue(oldEncryptedHex));
will(returnValue(null));
allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir));
// Try to decrypt the key - the password is wrong
oneOf(crypto).decryptWithPassword(oldEncryptedBytes, oldPassword);
oneOf(crypto).decryptWithPassword(oldEncryptedKey, oldPassword);
will(returnValue(null));
}});
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
storeDatabaseKey(keyFile, toHexString(oldEncryptedKey));
PasswordControllerImpl p = new PasswordControllerImpl(briarPrefs,
databaseConfig, cryptoExecutor, crypto, estimator);
AtomicBoolean capturedResult = new AtomicBoolean(true);
p.changePassword(oldPassword, newPassword, capturedResult::set);
assertFalse(capturedResult.get());
assertTrue(keyFile.exists());
assertFalse(keyBackupFile.exists());
assertEquals(toHexString(oldEncryptedKey), loadDatabaseKey(keyFile));
}
@After
public void tearDown() {
deleteTestDirectory(testDir);
}
}

View File

@@ -9,7 +9,6 @@ import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.ImmediateExecutor;
import org.briarproject.bramble.test.TestUtils;
import org.jmock.Expectations;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.After;
@@ -19,10 +18,17 @@ import java.io.File;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.briarproject.bramble.util.StringUtils.toHexString;
import static org.briarproject.briar.android.TestDatabaseKeyUtils.loadDatabaseKey;
public class SetupControllerImplTest extends BrambleMockTestCase {
@@ -33,18 +39,18 @@ public class SetupControllerImplTest extends BrambleMockTestCase {
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
private final PasswordStrengthEstimator estimator =
context.mock(PasswordStrengthEstimator.class);
private final SharedPreferences.Editor editor =
context.mock(SharedPreferences.Editor.class);
private final SetupActivity setupActivity;
private final Executor cryptoExecutor = new ImmediateExecutor();
private final String authorName = getRandomString(MAX_AUTHOR_NAME_LENGTH);
private final String password = "some.strong.pass";
private final String encryptedHex = "010203";
private final byte[] encryptedBytes = new byte[] {1, 2, 3};
private final byte[] encryptedKey = getRandomBytes(123);
private final SecretKey key = getSecretKey();
private final File testDir = TestUtils.getTestDirectory();
private final File testDir = getTestDirectory();
private final File keyDir = new File(testDir, "key");
private final File keyFile = new File(keyDir, "db.key");
private final File keyBackupFile = new File(keyDir, "db.key.bak");
public SetupControllerImplTest() {
context.setImposteriser(ClassImposteriser.INSTANCE);
@@ -53,7 +59,7 @@ public class SetupControllerImplTest extends BrambleMockTestCase {
@Test
@SuppressWarnings("ResultOfMethodCallIgnored")
public void testCreateAccount() {
public void testCreateAccount() throws Exception {
context.checking(new Expectations() {{
// Allow the contents of the data directory to be logged
allowing(setupActivity).getApplicationInfo();
@@ -76,15 +82,15 @@ public class SetupControllerImplTest extends BrambleMockTestCase {
oneOf(databaseConfig).setEncryptionKey(key);
// Encrypt the key with the password
oneOf(crypto).encryptWithPassword(key.getBytes(), password);
will(returnValue(encryptedBytes));
will(returnValue(encryptedKey));
// Store the encrypted key
oneOf(briarPrefs).edit();
will(returnValue(editor));
oneOf(editor).putString("key", encryptedHex);
will(returnValue(editor));
oneOf(editor).commit();
allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir));
}});
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
SetupControllerImpl s = new SetupControllerImpl(briarPrefs,
databaseConfig, cryptoExecutor, crypto, estimator);
s.setSetupActivity(setupActivity);
@@ -94,10 +100,14 @@ public class SetupControllerImplTest extends BrambleMockTestCase {
s.setPassword(password);
s.createAccount(result -> called.set(true));
assertTrue(called.get());
assertTrue(keyFile.exists());
assertFalse(keyBackupFile.exists());
assertEquals(toHexString(encryptedKey), loadDatabaseKey(keyFile));
}
@After
public void tearDown() {
TestUtils.deleteTestDirectory(testDir);
deleteTestDirectory(testDir);
}
}