mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Compare commits
2 Commits
beta-1.5.4
...
export-acc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4dbdd724ad | ||
|
|
0122282a72 |
@@ -5,6 +5,9 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
/**
|
||||
* Interface for strengthening a password-based key, for example by using a
|
||||
* key stored in a key management service or hardware security module.
|
||||
*
|
||||
* TODO: Remove after a reasonable migration period unless we can work around
|
||||
* Android keymaster bugs. Added 2020-02-24
|
||||
*/
|
||||
@NotNullByDefault
|
||||
public interface KeyStrengthener {
|
||||
|
||||
@@ -179,8 +179,9 @@ class AccountManagerImpl implements AccountManager {
|
||||
@GuardedBy("stateChangeLock")
|
||||
private boolean encryptAndStoreDatabaseKey(SecretKey key, String password) {
|
||||
byte[] plaintext = key.getBytes();
|
||||
byte[] ciphertext = crypto.encryptWithPassword(plaintext, password,
|
||||
databaseConfig.getKeyStrengthener());
|
||||
// Don't use a key strengthener as the Android keymaster isn't reliable
|
||||
byte[] ciphertext =
|
||||
crypto.encryptWithPassword(plaintext, password, null);
|
||||
return storeEncryptedDatabaseKey(toHexString(ciphertext));
|
||||
}
|
||||
|
||||
@@ -197,13 +198,13 @@ class AccountManagerImpl implements AccountManager {
|
||||
@Override
|
||||
public void signIn(String password) throws DecryptionException {
|
||||
synchronized (stateChangeLock) {
|
||||
databaseKey = loadAndDecryptDatabaseKey(password);
|
||||
databaseKey = loadAndDecryptDatabaseKey(password, false);
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("stateChangeLock")
|
||||
private SecretKey loadAndDecryptDatabaseKey(String password)
|
||||
throws DecryptionException {
|
||||
private SecretKey loadAndDecryptDatabaseKey(String password,
|
||||
boolean changing) throws DecryptionException {
|
||||
String hex = loadEncryptedDatabaseKey();
|
||||
if (hex == null) {
|
||||
LOG.warning("Failed to load encrypted database key");
|
||||
@@ -214,11 +215,11 @@ class AccountManagerImpl implements AccountManager {
|
||||
byte[] plaintext = crypto.decryptWithPassword(ciphertext, password,
|
||||
keyStrengthener);
|
||||
SecretKey key = new SecretKey(plaintext);
|
||||
// If the DB key was encrypted with a weak key and a key strengthener
|
||||
// is now available, re-encrypt the DB key with a strengthened key
|
||||
if (keyStrengthener != null &&
|
||||
!crypto.isEncryptedWithStrengthenedKey(ciphertext)) {
|
||||
LOG.info("Re-encrypting database key with strengthened key");
|
||||
// If the DB key was encrypted with a hardware-backed key, re-encrypt
|
||||
// it without the hardware-backed key so keymaster bugs don't delete
|
||||
// the user's account
|
||||
if (!changing && crypto.isEncryptedWithStrengthenedKey(ciphertext)) {
|
||||
LOG.info("Re-encrypting database key without strengthened key");
|
||||
encryptAndStoreDatabaseKey(key, password);
|
||||
}
|
||||
return key;
|
||||
@@ -228,7 +229,7 @@ class AccountManagerImpl implements AccountManager {
|
||||
public void changePassword(String oldPassword, String newPassword)
|
||||
throws DecryptionException {
|
||||
synchronized (stateChangeLock) {
|
||||
SecretKey key = loadAndDecryptDatabaseKey(oldPassword);
|
||||
SecretKey key = loadAndDecryptDatabaseKey(oldPassword, true);
|
||||
encryptAndStoreDatabaseKey(key, newPassword);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
|
||||
keyStrengthener);
|
||||
will(returnValue(key.getBytes()));
|
||||
oneOf(crypto).isEncryptedWithStrengthenedKey(encryptedKey);
|
||||
will(returnValue(true));
|
||||
will(returnValue(false));
|
||||
}});
|
||||
|
||||
storeDatabaseKey(keyFile, encryptedKeyHex);
|
||||
@@ -160,9 +160,8 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
|
||||
keyStrengthener);
|
||||
will(returnValue(key.getBytes()));
|
||||
oneOf(crypto).isEncryptedWithStrengthenedKey(encryptedKey);
|
||||
will(returnValue(false));
|
||||
oneOf(crypto).encryptWithPassword(key.getBytes(), password,
|
||||
keyStrengthener);
|
||||
will(returnValue(true));
|
||||
oneOf(crypto).encryptWithPassword(key.getBytes(), password, null);
|
||||
will(returnValue(newEncryptedKey));
|
||||
}});
|
||||
|
||||
@@ -262,8 +261,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
|
||||
oneOf(identityManager).registerIdentity(identity);
|
||||
oneOf(crypto).generateSecretKey();
|
||||
will(returnValue(key));
|
||||
oneOf(crypto).encryptWithPassword(key.getBytes(), password,
|
||||
keyStrengthener);
|
||||
oneOf(crypto).encryptWithPassword(key.getBytes(), password, null);
|
||||
will(returnValue(encryptedKey));
|
||||
}});
|
||||
|
||||
@@ -323,10 +321,8 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
|
||||
oneOf(crypto).decryptWithPassword(encryptedKey, password,
|
||||
keyStrengthener);
|
||||
will(returnValue(key.getBytes()));
|
||||
oneOf(crypto).isEncryptedWithStrengthenedKey(encryptedKey);
|
||||
will(returnValue(true));
|
||||
oneOf(crypto).encryptWithPassword(key.getBytes(), newPassword,
|
||||
keyStrengthener);
|
||||
null);
|
||||
will(returnValue(newEncryptedKey));
|
||||
}});
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package org.briarproject.briar.android.account;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.widget.Toast;
|
||||
|
||||
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.logging.Logger;
|
||||
|
||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||
import static android.os.Environment.DIRECTORY_DOWNLOADS;
|
||||
import static android.os.Environment.getExternalStoragePublicDirectory;
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.IoUtils.copyAndClose;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
import static org.briarproject.briar.android.BriarApplication.ENTRY_ACTIVITY;
|
||||
import static org.briarproject.briar.android.navdrawer.NavDrawerActivity.SIGN_OUT_URI;
|
||||
|
||||
public class AccountUtils {
|
||||
|
||||
private static final Logger LOG = getLogger(AccountUtils.class.getName());
|
||||
|
||||
private static final String[] BACKUP_DIRS =
|
||||
{"app_db", "app_key", "shared_prefs"};
|
||||
|
||||
public static void exportAccount(Context ctx) {
|
||||
try {
|
||||
File dataDir = getDataDir(ctx);
|
||||
File backupDir = getBackupDir(ctx);
|
||||
for (String name : BACKUP_DIRS) {
|
||||
copyRecursively(new File(dataDir, name),
|
||||
new File(backupDir, name));
|
||||
}
|
||||
Toast.makeText(ctx, "Account exported to "
|
||||
+ backupDir.getCanonicalPath(), LENGTH_LONG).show();
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
Toast.makeText(ctx, "Export failed", LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
public static void importAccount(Context ctx) {
|
||||
try {
|
||||
File dataDir = getDataDir(ctx);
|
||||
File backupDir = getBackupDir(ctx);
|
||||
for (String name : BACKUP_DIRS) {
|
||||
copyRecursively(new File(backupDir, name),
|
||||
new File(dataDir, name));
|
||||
}
|
||||
Toast.makeText(ctx, "Account imported from "
|
||||
+ backupDir.getCanonicalPath(), LENGTH_LONG).show();
|
||||
Intent intent = new Intent(ctx, ENTRY_ACTIVITY);
|
||||
intent.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||
intent.setData(SIGN_OUT_URI);
|
||||
ctx.startActivity(intent);
|
||||
} catch (IOException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
Toast.makeText(ctx, "Import failed", LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
private static File getDataDir(Context ctx) {
|
||||
return new File(ctx.getApplicationInfo().dataDir);
|
||||
}
|
||||
|
||||
private static File getBackupDir(Context ctx) {
|
||||
File downloads = getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS);
|
||||
return new File(downloads, ctx.getPackageName());
|
||||
}
|
||||
|
||||
private static void copyRecursively(File src, File dest)
|
||||
throws IOException {
|
||||
if (src.isDirectory()) {
|
||||
if (!dest.isDirectory() && !dest.mkdirs()) throw new IOException();
|
||||
File[] children = src.listFiles();
|
||||
if (children == null) throw new IOException();
|
||||
for (File child : children) {
|
||||
copyRecursively(child, new File(dest, child.getName()));
|
||||
}
|
||||
} else if (src.isFile()) {
|
||||
InputStream in = new FileInputStream(src);
|
||||
OutputStream out = new FileOutputStream(dest);
|
||||
copyAndClose(in, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import org.briarproject.bramble.plugin.tor.CircumventionProvider;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.Localizer;
|
||||
import org.briarproject.briar.android.account.AccountUtils;
|
||||
import org.briarproject.briar.android.util.UiUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -251,9 +252,23 @@ public class SettingsFragment extends PreferenceFragmentCompat
|
||||
throw new RuntimeException("Boom!");
|
||||
}
|
||||
);
|
||||
findPreference("pref_key_export").setOnPreferenceClickListener(
|
||||
preference -> {
|
||||
AccountUtils.exportAccount(requireContext());
|
||||
return true;
|
||||
}
|
||||
);
|
||||
findPreference("pref_key_import").setOnPreferenceClickListener(
|
||||
preference -> {
|
||||
AccountUtils.importAccount(requireContext());
|
||||
return true;
|
||||
}
|
||||
);
|
||||
} else {
|
||||
findPreference("pref_key_explode").setVisible(false);
|
||||
findPreference("pref_key_test_data").setVisible(false);
|
||||
findPreference("pref_key_export").setVisible(false);
|
||||
findPreference("pref_key_import").setVisible(false);
|
||||
PreferenceGroup testing =
|
||||
findPreference("pref_key_explode").getParent();
|
||||
if (testing == null) throw new AssertionError();
|
||||
|
||||
@@ -232,6 +232,16 @@
|
||||
android:title="Crash"
|
||||
app:iconSpaceReserved="false"/>
|
||||
|
||||
<Preference
|
||||
android:key="pref_key_export"
|
||||
android:title="Export Account to SD Card"
|
||||
app:iconSpaceReserved="false"/>
|
||||
|
||||
<Preference
|
||||
android:key="pref_key_import"
|
||||
android:title="Import Account from SD Card"
|
||||
app:iconSpaceReserved="false"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
||||
|
||||
Reference in New Issue
Block a user