diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoComponent.java b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoComponent.java index 1cfffa3c2..92feb2d28 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoComponent.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/CryptoComponent.java @@ -153,6 +153,13 @@ public interface CryptoComponent { byte[] decryptWithPassword(byte[] ciphertext, String password, @Nullable KeyStoreConfig keyStoreConfig); + /** + * Returns true if the given ciphertext was encrypted using a stored key + * to strengthen the password-based key. The validity of the ciphertext is + * not checked. + */ + boolean isEncryptedWithStoredKey(byte[] ciphertext); + /** * Encrypts the given plaintext to the given public key. */ diff --git a/bramble-core/src/main/java/org/briarproject/bramble/account/AccountManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/account/AccountManagerImpl.java index 351572215..bec84eda6 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/account/AccountManagerImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/account/AccountManagerImpl.java @@ -2,6 +2,7 @@ package org.briarproject.bramble.account; import org.briarproject.bramble.api.account.AccountManager; import org.briarproject.bramble.api.crypto.CryptoComponent; +import org.briarproject.bramble.api.crypto.KeyStoreConfig; import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.identity.Identity; @@ -210,13 +211,22 @@ class AccountManagerImpl implements AccountManager { return null; } byte[] ciphertext = fromHexString(hex); + KeyStoreConfig keyStoreConfig = databaseConfig.getKeyStoreConfig(); byte[] plaintext = crypto.decryptWithPassword(ciphertext, password, - databaseConfig.getKeyStoreConfig()); + keyStoreConfig); if (plaintext == null) { LOG.info("Failed to decrypt database key"); return null; } - return new SecretKey(plaintext); + SecretKey key = new SecretKey(plaintext); + // If the DB key was encrypted without using a stored key and a stored + // key is now available, re-encrypt the DB key with the stored key + if (keyStoreConfig != null && + !crypto.isEncryptedWithStoredKey(ciphertext)) { + LOG.info("Re-encrypting database key with stored key"); + encryptAndStoreDatabaseKey(key, password); + } + return key; } @Override diff --git a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java index 27052d737..0ae209b1b 100644 --- a/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java +++ b/bramble-core/src/main/java/org/briarproject/bramble/crypto/CryptoComponentImpl.java @@ -467,6 +467,12 @@ class CryptoComponentImpl implements CryptoComponent { } } + @Override + public boolean isEncryptedWithStoredKey(byte[] ciphertext) { + return ciphertext.length > 0 && + ciphertext[0] == PBKDF_FORMAT_SCRYPT_KEYSTORE; + } + @Override public byte[] encryptToKey(PublicKey publicKey, byte[] plaintext) { try { diff --git a/bramble-core/src/test/java/org/briarproject/bramble/account/AccountManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/account/AccountManagerImplTest.java index 49fe8b202..d06bfa633 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/account/AccountManagerImplTest.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/account/AccountManagerImplTest.java @@ -118,6 +118,8 @@ public class AccountManagerImplTest extends BrambleMockTestCase { oneOf(crypto).decryptWithPassword(encryptedKey, password, keyStoreConfig); will(returnValue(key.getBytes())); + oneOf(crypto).isEncryptedWithStoredKey(encryptedKey); + will(returnValue(true)); }}); storeDatabaseKey(keyFile, encryptedKeyHex); @@ -136,6 +138,35 @@ public class AccountManagerImplTest extends BrambleMockTestCase { assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile)); } + @Test + public void testSignInReEncryptsKey() throws Exception { + context.checking(new Expectations() {{ + oneOf(crypto).decryptWithPassword(encryptedKey, password, + keyStoreConfig); + will(returnValue(key.getBytes())); + oneOf(crypto).isEncryptedWithStoredKey(encryptedKey); + will(returnValue(false)); + oneOf(crypto).encryptWithPassword(key.getBytes(), password, + keyStoreConfig); + will(returnValue(newEncryptedKey)); + }}); + + storeDatabaseKey(keyFile, encryptedKeyHex); + storeDatabaseKey(keyBackupFile, encryptedKeyHex); + + assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile)); + assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile)); + + assertTrue(accountManager.signIn(password)); + assertTrue(accountManager.hasDatabaseKey()); + SecretKey decrypted = accountManager.getDatabaseKey(); + assertNotNull(decrypted); + assertArrayEquals(key.getBytes(), decrypted.getBytes()); + + assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyFile)); + assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyBackupFile)); + } + @Test public void testDbKeyIsLoadedFromPrimaryFile() throws Exception { storeDatabaseKey(keyFile, encryptedKeyHex); @@ -316,6 +347,8 @@ public class AccountManagerImplTest extends BrambleMockTestCase { oneOf(crypto).decryptWithPassword(encryptedKey, password, keyStoreConfig); will(returnValue(key.getBytes())); + oneOf(crypto).isEncryptedWithStoredKey(encryptedKey); + will(returnValue(true)); oneOf(crypto).encryptWithPassword(key.getBytes(), newPassword, keyStoreConfig); will(returnValue(newEncryptedKey));