mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-17 21:29:54 +01:00
Use Android keystore for encrypting DB key.
Only for new accounts on API 23+.
This commit is contained in:
@@ -16,6 +16,7 @@ import java.util.Set;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static android.os.Build.VERSION.SDK_INT;
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
@@ -73,7 +74,7 @@ class AndroidAccountManager extends AccountManagerImpl
|
|||||||
return PreferenceManager.getDefaultSharedPreferences(appContext);
|
return PreferenceManager.getDefaultSharedPreferences(appContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locking: stateChangeLock
|
@GuardedBy("stateChangeLock")
|
||||||
private void deleteAppData(SharedPreferences... clear) {
|
private void deleteAppData(SharedPreferences... clear) {
|
||||||
// Clear and commit shared preferences
|
// Clear and commit shared preferences
|
||||||
for (SharedPreferences prefs : clear) {
|
for (SharedPreferences prefs : clear) {
|
||||||
|
|||||||
@@ -133,7 +133,8 @@ public interface CryptoComponent {
|
|||||||
* given password. The ciphertext will be decryptable using the same
|
* given password. The ciphertext will be decryptable using the same
|
||||||
* password after the app restarts.
|
* password after the app restarts.
|
||||||
*/
|
*/
|
||||||
byte[] encryptWithPassword(byte[] plaintext, String password);
|
byte[] encryptWithPassword(byte[] plaintext, String password,
|
||||||
|
@Nullable KeyStoreConfig keyStoreConfig);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypts and authenticates the given ciphertext that has been read from
|
* Decrypts and authenticates the given ciphertext that has been read from
|
||||||
@@ -142,7 +143,8 @@ public interface CryptoComponent {
|
|||||||
* authenticated (for example, if the password is wrong).
|
* authenticated (for example, if the password is wrong).
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
byte[] decryptWithPassword(byte[] ciphertext, String password);
|
byte[] decryptWithPassword(byte[] ciphertext, String password,
|
||||||
|
@Nullable KeyStoreConfig keyStoreConfig);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypts the given plaintext to the given public key.
|
* Encrypts the given plaintext to the given public key.
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package org.briarproject.bramble.api.crypto;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.security.spec.AlgorithmParameterSpec;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public interface KeyStoreConfig {
|
||||||
|
|
||||||
|
String getKeyStoreType();
|
||||||
|
|
||||||
|
String getAlias();
|
||||||
|
|
||||||
|
String getProviderName();
|
||||||
|
|
||||||
|
String getMacAlgorithmName();
|
||||||
|
|
||||||
|
AlgorithmParameterSpec getParameterSpec();
|
||||||
|
}
|
||||||
@@ -1,13 +1,19 @@
|
|||||||
package org.briarproject.bramble.api.db;
|
package org.briarproject.bramble.api.db;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.crypto.KeyStoreConfig;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public interface DatabaseConfig {
|
public interface DatabaseConfig {
|
||||||
|
|
||||||
File getDatabaseDirectory();
|
File getDatabaseDirectory();
|
||||||
|
|
||||||
File getDatabaseKeyDirectory();
|
File getDatabaseKeyDirectory();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
KeyStoreConfig getKeyStoreConfig();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import java.io.InputStreamReader;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import javax.annotation.concurrent.GuardedBy;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
@@ -68,9 +69,10 @@ class AccountManagerImpl implements AccountManager {
|
|||||||
return databaseKey;
|
return databaseKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locking: stateChangeLock
|
// Package access for testing
|
||||||
|
@GuardedBy("stateChangeLock")
|
||||||
@Nullable
|
@Nullable
|
||||||
protected String loadEncryptedDatabaseKey() {
|
String loadEncryptedDatabaseKey() {
|
||||||
String key = readDbKeyFromFile(dbKeyFile);
|
String key = readDbKeyFromFile(dbKeyFile);
|
||||||
if (key == null) {
|
if (key == null) {
|
||||||
LOG.info("No database key in primary file");
|
LOG.info("No database key in primary file");
|
||||||
@@ -83,7 +85,7 @@ class AccountManagerImpl implements AccountManager {
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locking: stateChangeLock
|
@GuardedBy("stateChangeLock")
|
||||||
@Nullable
|
@Nullable
|
||||||
private String readDbKeyFromFile(File f) {
|
private String readDbKeyFromFile(File f) {
|
||||||
if (!f.exists()) {
|
if (!f.exists()) {
|
||||||
@@ -102,8 +104,9 @@ class AccountManagerImpl implements AccountManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locking: stateChangeLock
|
// Package access for testing
|
||||||
protected boolean storeEncryptedDatabaseKey(String hex) {
|
@GuardedBy("stateChangeLock")
|
||||||
|
boolean storeEncryptedDatabaseKey(String hex) {
|
||||||
LOG.info("Storing database key in file");
|
LOG.info("Storing database key in file");
|
||||||
// Create the directory if necessary
|
// Create the directory if necessary
|
||||||
if (databaseConfig.getDatabaseKeyDirectory().mkdirs())
|
if (databaseConfig.getDatabaseKeyDirectory().mkdirs())
|
||||||
@@ -140,7 +143,7 @@ class AccountManagerImpl implements AccountManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locking: stateChangeLock
|
@GuardedBy("stateChangeLock")
|
||||||
private void writeDbKeyToFile(String key, File f) throws IOException {
|
private void writeDbKeyToFile(String key, File f) throws IOException {
|
||||||
FileOutputStream out = new FileOutputStream(f);
|
FileOutputStream out = new FileOutputStream(f);
|
||||||
out.write(key.getBytes("UTF-8"));
|
out.write(key.getBytes("UTF-8"));
|
||||||
@@ -170,10 +173,11 @@ class AccountManagerImpl implements AccountManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locking: stateChangeLock
|
@GuardedBy("stateChangeLock")
|
||||||
private boolean encryptAndStoreDatabaseKey(SecretKey key, String password) {
|
private boolean encryptAndStoreDatabaseKey(SecretKey key, String password) {
|
||||||
byte[] plaintext = key.getBytes();
|
byte[] plaintext = key.getBytes();
|
||||||
byte[] ciphertext = crypto.encryptWithPassword(plaintext, password);
|
byte[] ciphertext = crypto.encryptWithPassword(plaintext, password,
|
||||||
|
databaseConfig.getKeyStoreConfig());
|
||||||
return storeEncryptedDatabaseKey(toHexString(ciphertext));
|
return storeEncryptedDatabaseKey(toHexString(ciphertext));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +201,7 @@ class AccountManagerImpl implements AccountManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Locking: stateChangeLock
|
@GuardedBy("stateChangeLock")
|
||||||
@Nullable
|
@Nullable
|
||||||
private SecretKey loadAndDecryptDatabaseKey(String password) {
|
private SecretKey loadAndDecryptDatabaseKey(String password) {
|
||||||
String hex = loadEncryptedDatabaseKey();
|
String hex = loadEncryptedDatabaseKey();
|
||||||
@@ -206,7 +210,8 @@ class AccountManagerImpl implements AccountManager {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
byte[] ciphertext = fromHexString(hex);
|
byte[] ciphertext = fromHexString(hex);
|
||||||
byte[] plaintext = crypto.decryptWithPassword(ciphertext, password);
|
byte[] plaintext = crypto.decryptWithPassword(ciphertext, password,
|
||||||
|
databaseConfig.getKeyStoreConfig());
|
||||||
if (plaintext == null) {
|
if (plaintext == null) {
|
||||||
LOG.info("Failed to decrypt database key");
|
LOG.info("Failed to decrypt database key");
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import org.briarproject.bramble.api.crypto.AgreementPublicKey;
|
|||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||||
import org.briarproject.bramble.api.crypto.KeyParser;
|
import org.briarproject.bramble.api.crypto.KeyParser;
|
||||||
|
import org.briarproject.bramble.api.crypto.KeyStoreConfig;
|
||||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||||
import org.briarproject.bramble.api.crypto.PublicKey;
|
import org.briarproject.bramble.api.crypto.PublicKey;
|
||||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||||
@@ -24,7 +25,11 @@ import org.spongycastle.crypto.digests.Blake2bDigest;
|
|||||||
import org.whispersystems.curve25519.Curve25519;
|
import org.whispersystems.curve25519.Curve25519;
|
||||||
import org.whispersystems.curve25519.Curve25519KeyPair;
|
import org.whispersystems.curve25519.Curve25519KeyPair;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.KeyStore.Entry;
|
||||||
|
import java.security.KeyStore.SecretKeyEntry;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.Provider;
|
import java.security.Provider;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
@@ -32,12 +37,15 @@ import java.security.Security;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
import javax.crypto.KeyGenerator;
|
||||||
|
import javax.crypto.Mac;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import static java.lang.System.arraycopy;
|
import static java.lang.System.arraycopy;
|
||||||
import static java.util.logging.Level.INFO;
|
import static java.util.logging.Level.INFO;
|
||||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_AGREEMENT;
|
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_AGREEMENT;
|
||||||
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_SIGNATURE;
|
import static org.briarproject.bramble.api.crypto.CryptoConstants.KEY_TYPE_SIGNATURE;
|
||||||
|
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||||
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
|
import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
|
||||||
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
import static org.briarproject.bramble.util.LogUtils.logDuration;
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
@@ -51,7 +59,8 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
private static final int SIGNATURE_KEY_PAIR_BITS = 256;
|
private static final int SIGNATURE_KEY_PAIR_BITS = 256;
|
||||||
private static final int STORAGE_IV_BYTES = 24; // 196 bits
|
private static final int STORAGE_IV_BYTES = 24; // 196 bits
|
||||||
private static final int PBKDF_SALT_BYTES = 32; // 256 bits
|
private static final int PBKDF_SALT_BYTES = 32; // 256 bits
|
||||||
private static final int PBKDF_FORMAT_SCRYPT = 0;
|
private static final byte PBKDF_FORMAT_SCRYPT = 0;
|
||||||
|
private static final byte PBKDF_FORMAT_SCRYPT_KEYSTORE = 1;
|
||||||
|
|
||||||
private final SecureRandom secureRandom;
|
private final SecureRandom secureRandom;
|
||||||
private final PasswordBasedKdf passwordBasedKdf;
|
private final PasswordBasedKdf passwordBasedKdf;
|
||||||
@@ -311,7 +320,8 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] encryptWithPassword(byte[] input, String password) {
|
public byte[] encryptWithPassword(byte[] input, String password,
|
||||||
|
@Nullable KeyStoreConfig keyStoreConfig) {
|
||||||
AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher();
|
AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher();
|
||||||
int macBytes = cipher.getMacBytes();
|
int macBytes = cipher.getMacBytes();
|
||||||
// Generate a random salt
|
// Generate a random salt
|
||||||
@@ -319,8 +329,10 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
secureRandom.nextBytes(salt);
|
secureRandom.nextBytes(salt);
|
||||||
// Calibrate the KDF
|
// Calibrate the KDF
|
||||||
int cost = passwordBasedKdf.chooseCostParameter();
|
int cost = passwordBasedKdf.chooseCostParameter();
|
||||||
// Derive the key from the password
|
// Derive the encryption key from the password
|
||||||
SecretKey key = passwordBasedKdf.deriveKey(password, salt, cost);
|
SecretKey key = passwordBasedKdf.deriveKey(password, salt, cost);
|
||||||
|
if (keyStoreConfig != null)
|
||||||
|
key = requireNonNull(deriveKey(key, keyStoreConfig, true));
|
||||||
// Generate a random IV
|
// Generate a random IV
|
||||||
byte[] iv = new byte[STORAGE_IV_BYTES];
|
byte[] iv = new byte[STORAGE_IV_BYTES];
|
||||||
secureRandom.nextBytes(iv);
|
secureRandom.nextBytes(iv);
|
||||||
@@ -331,7 +343,9 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
byte[] output = new byte[outputLen];
|
byte[] output = new byte[outputLen];
|
||||||
int outputOff = 0;
|
int outputOff = 0;
|
||||||
// Format version
|
// Format version
|
||||||
output[outputOff] = PBKDF_FORMAT_SCRYPT;
|
byte formatVersion = keyStoreConfig == null
|
||||||
|
? PBKDF_FORMAT_SCRYPT : PBKDF_FORMAT_SCRYPT_KEYSTORE;
|
||||||
|
output[outputOff] = formatVersion;
|
||||||
outputOff++;
|
outputOff++;
|
||||||
// Salt
|
// Salt
|
||||||
arraycopy(salt, 0, output, outputOff, salt.length);
|
arraycopy(salt, 0, output, outputOff, salt.length);
|
||||||
@@ -352,9 +366,53 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derives a key from the given key and another key stored in a keystore.
|
||||||
|
*
|
||||||
|
* @param generateIfMissing Whether the stored key should be generated and
|
||||||
|
* stored if it doesn't already exist
|
||||||
|
* @return The derived key, or null if the stored key doesn't exist and
|
||||||
|
* {@code generateIfMissing} is false
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private SecretKey deriveKey(SecretKey key, KeyStoreConfig config,
|
||||||
|
boolean generateIfMissing) {
|
||||||
|
try {
|
||||||
|
// Load the keystore
|
||||||
|
KeyStore ks = KeyStore.getInstance(config.getKeyStoreType());
|
||||||
|
ks.load(null);
|
||||||
|
// Load or generate the stored key
|
||||||
|
javax.crypto.SecretKey storedKey;
|
||||||
|
Entry e = ks.getEntry(config.getAlias(), null);
|
||||||
|
if (e == null) {
|
||||||
|
if (!generateIfMissing) {
|
||||||
|
LOG.warning("Key not found in keystore");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
KeyGenerator kg = KeyGenerator.getInstance(
|
||||||
|
config.getMacAlgorithmName(), config.getProviderName());
|
||||||
|
kg.init(config.getParameterSpec());
|
||||||
|
storedKey = kg.generateKey();
|
||||||
|
LOG.info("Stored key in keystore");
|
||||||
|
} else {
|
||||||
|
if (!(e instanceof SecretKeyEntry))
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
storedKey = ((SecretKeyEntry) e).getSecretKey();
|
||||||
|
LOG.info("Loaded key from keystore");
|
||||||
|
}
|
||||||
|
// Use the input key and the stored key to derive the output key
|
||||||
|
Mac mac = Mac.getInstance(config.getMacAlgorithmName());
|
||||||
|
mac.init(storedKey);
|
||||||
|
return new SecretKey(mac.doFinal(key.getBytes()));
|
||||||
|
} catch (GeneralSecurityException | IOException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public byte[] decryptWithPassword(byte[] input, String password) {
|
public byte[] decryptWithPassword(byte[] input, String password,
|
||||||
|
@Nullable KeyStoreConfig keyStoreConfig) {
|
||||||
AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher();
|
AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher();
|
||||||
int macBytes = cipher.getMacBytes();
|
int macBytes = cipher.getMacBytes();
|
||||||
// The input contains the format version, salt, cost parameter, IV,
|
// The input contains the format version, salt, cost parameter, IV,
|
||||||
@@ -366,8 +424,13 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
// Format version
|
// Format version
|
||||||
byte formatVersion = input[inputOff];
|
byte formatVersion = input[inputOff];
|
||||||
inputOff++;
|
inputOff++;
|
||||||
if (formatVersion != PBKDF_FORMAT_SCRYPT)
|
// Check whether we support this format version
|
||||||
return null; // Unknown format
|
if (formatVersion != PBKDF_FORMAT_SCRYPT &&
|
||||||
|
formatVersion != PBKDF_FORMAT_SCRYPT_KEYSTORE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
boolean useKeyStore = keyStoreConfig != null &&
|
||||||
|
formatVersion == PBKDF_FORMAT_SCRYPT_KEYSTORE;
|
||||||
// Salt
|
// Salt
|
||||||
byte[] salt = new byte[PBKDF_SALT_BYTES];
|
byte[] salt = new byte[PBKDF_SALT_BYTES];
|
||||||
arraycopy(input, inputOff, salt, 0, salt.length);
|
arraycopy(input, inputOff, salt, 0, salt.length);
|
||||||
@@ -381,8 +444,12 @@ class CryptoComponentImpl implements CryptoComponent {
|
|||||||
byte[] iv = new byte[STORAGE_IV_BYTES];
|
byte[] iv = new byte[STORAGE_IV_BYTES];
|
||||||
arraycopy(input, inputOff, iv, 0, iv.length);
|
arraycopy(input, inputOff, iv, 0, iv.length);
|
||||||
inputOff += iv.length;
|
inputOff += iv.length;
|
||||||
// Derive the key from the password
|
// Derive the decryption key from the password
|
||||||
SecretKey key = passwordBasedKdf.deriveKey(password, salt, (int) cost);
|
SecretKey key = passwordBasedKdf.deriveKey(password, salt, (int) cost);
|
||||||
|
if (useKeyStore) {
|
||||||
|
key = deriveKey(key, keyStoreConfig, false);
|
||||||
|
if (key == null) return null;
|
||||||
|
}
|
||||||
// Initialise the cipher
|
// Initialise the cipher
|
||||||
try {
|
try {
|
||||||
cipher.init(false, key, iv);
|
cipher.init(false, key, iv);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.bramble.account;
|
package org.briarproject.bramble.account;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
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.crypto.SecretKey;
|
||||||
import org.briarproject.bramble.api.db.DatabaseConfig;
|
import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||||
import org.briarproject.bramble.api.identity.Identity;
|
import org.briarproject.bramble.api.identity.Identity;
|
||||||
@@ -39,6 +40,8 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
|
|||||||
|
|
||||||
private final DatabaseConfig databaseConfig =
|
private final DatabaseConfig databaseConfig =
|
||||||
context.mock(DatabaseConfig.class);
|
context.mock(DatabaseConfig.class);
|
||||||
|
private final KeyStoreConfig keyStoreConfig =
|
||||||
|
context.mock(KeyStoreConfig.class);
|
||||||
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
private final CryptoComponent crypto = context.mock(CryptoComponent.class);
|
||||||
private final IdentityManager identityManager =
|
private final IdentityManager identityManager =
|
||||||
context.mock(IdentityManager.class);
|
context.mock(IdentityManager.class);
|
||||||
@@ -68,6 +71,8 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
|
|||||||
will(returnValue(dbDir));
|
will(returnValue(dbDir));
|
||||||
allowing(databaseConfig).getDatabaseKeyDirectory();
|
allowing(databaseConfig).getDatabaseKeyDirectory();
|
||||||
will(returnValue(keyDir));
|
will(returnValue(keyDir));
|
||||||
|
allowing(databaseConfig).getKeyStoreConfig();
|
||||||
|
will(returnValue(keyStoreConfig));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
accountManager =
|
accountManager =
|
||||||
@@ -89,7 +94,8 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testSignInReturnsFalseIfPasswordIsWrong() throws Exception {
|
public void testSignInReturnsFalseIfPasswordIsWrong() throws Exception {
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(crypto).decryptWithPassword(encryptedKey, password);
|
oneOf(crypto).decryptWithPassword(encryptedKey, password,
|
||||||
|
keyStoreConfig);
|
||||||
will(returnValue(null));
|
will(returnValue(null));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
@@ -109,7 +115,8 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testSignInReturnsTrueIfPasswordIsRight() throws Exception {
|
public void testSignInReturnsTrueIfPasswordIsRight() throws Exception {
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(crypto).decryptWithPassword(encryptedKey, password);
|
oneOf(crypto).decryptWithPassword(encryptedKey, password,
|
||||||
|
keyStoreConfig);
|
||||||
will(returnValue(key.getBytes()));
|
will(returnValue(key.getBytes()));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
@@ -258,7 +265,8 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
|
|||||||
oneOf(identityManager).registerIdentity(identity);
|
oneOf(identityManager).registerIdentity(identity);
|
||||||
oneOf(crypto).generateSecretKey();
|
oneOf(crypto).generateSecretKey();
|
||||||
will(returnValue(key));
|
will(returnValue(key));
|
||||||
oneOf(crypto).encryptWithPassword(key.getBytes(), password);
|
oneOf(crypto).encryptWithPassword(key.getBytes(), password,
|
||||||
|
keyStoreConfig);
|
||||||
will(returnValue(encryptedKey));
|
will(returnValue(encryptedKey));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
@@ -287,7 +295,8 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
|
|||||||
public void testChangePasswordReturnsFalseIfPasswordIsWrong()
|
public void testChangePasswordReturnsFalseIfPasswordIsWrong()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(crypto).decryptWithPassword(encryptedKey, password);
|
oneOf(crypto).decryptWithPassword(encryptedKey, password,
|
||||||
|
keyStoreConfig);
|
||||||
will(returnValue(null));
|
will(returnValue(null));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
@@ -304,9 +313,11 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
|
|||||||
public void testChangePasswordReturnsTrueIfPasswordIsRight()
|
public void testChangePasswordReturnsTrueIfPasswordIsRight()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
context.checking(new Expectations() {{
|
context.checking(new Expectations() {{
|
||||||
oneOf(crypto).decryptWithPassword(encryptedKey, password);
|
oneOf(crypto).decryptWithPassword(encryptedKey, password,
|
||||||
|
keyStoreConfig);
|
||||||
will(returnValue(key.getBytes()));
|
will(returnValue(key.getBytes()));
|
||||||
oneOf(crypto).encryptWithPassword(key.getBytes(), newPassword);
|
oneOf(crypto).encryptWithPassword(key.getBytes(), newPassword,
|
||||||
|
keyStoreConfig);
|
||||||
will(returnValue(newEncryptedKey));
|
will(returnValue(newEncryptedKey));
|
||||||
}});
|
}});
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ public class PasswordBasedEncryptionTest extends BrambleTestCase {
|
|||||||
public void testEncryptionAndDecryption() {
|
public void testEncryptionAndDecryption() {
|
||||||
byte[] input = TestUtils.getRandomBytes(1234);
|
byte[] input = TestUtils.getRandomBytes(1234);
|
||||||
String password = "password";
|
String password = "password";
|
||||||
byte[] ciphertext = crypto.encryptWithPassword(input, password);
|
byte[] ciphertext = crypto.encryptWithPassword(input, password, null);
|
||||||
byte[] output = crypto.decryptWithPassword(ciphertext, password);
|
byte[] output = crypto.decryptWithPassword(ciphertext, password, null);
|
||||||
assertArrayEquals(input, output);
|
assertArrayEquals(input, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,11 +30,11 @@ public class PasswordBasedEncryptionTest extends BrambleTestCase {
|
|||||||
public void testInvalidCiphertextReturnsNull() {
|
public void testInvalidCiphertextReturnsNull() {
|
||||||
byte[] input = TestUtils.getRandomBytes(1234);
|
byte[] input = TestUtils.getRandomBytes(1234);
|
||||||
String password = "password";
|
String password = "password";
|
||||||
byte[] ciphertext = crypto.encryptWithPassword(input, password);
|
byte[] ciphertext = crypto.encryptWithPassword(input, password, null);
|
||||||
// Modify the ciphertext
|
// Modify the ciphertext
|
||||||
int position = new Random().nextInt(ciphertext.length);
|
int position = new Random().nextInt(ciphertext.length);
|
||||||
ciphertext[position] = (byte) (ciphertext[position] ^ 0xFF);
|
ciphertext[position] = (byte) (ciphertext[position] ^ 0xFF);
|
||||||
byte[] output = crypto.decryptWithPassword(ciphertext, password);
|
byte[] output = crypto.decryptWithPassword(ciphertext, password, null);
|
||||||
assertNull(output);
|
assertNull(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package org.briarproject.bramble.test;
|
package org.briarproject.bramble.test;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.crypto.KeyStoreConfig;
|
||||||
import org.briarproject.bramble.api.db.DatabaseConfig;
|
import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class TestDatabaseConfig implements DatabaseConfig {
|
public class TestDatabaseConfig implements DatabaseConfig {
|
||||||
|
|
||||||
@@ -24,4 +27,10 @@ public class TestDatabaseConfig implements DatabaseConfig {
|
|||||||
public File getDatabaseKeyDirectory() {
|
public File getDatabaseKeyDirectory() {
|
||||||
return keyDir;
|
return keyDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public KeyStoreConfig getKeyStoreConfig() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,26 @@
|
|||||||
package org.briarproject.briar.android;
|
package org.briarproject.briar.android;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.crypto.KeyStoreConfig;
|
||||||
import org.briarproject.bramble.api.db.DatabaseConfig;
|
import org.briarproject.bramble.api.db.DatabaseConfig;
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
|
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
class AndroidDatabaseConfig implements DatabaseConfig {
|
class AndroidDatabaseConfig implements DatabaseConfig {
|
||||||
|
|
||||||
private final File dbDir, keyDir;
|
private final File dbDir, keyDir;
|
||||||
|
@Nullable
|
||||||
|
private final KeyStoreConfig keyStoreConfig;
|
||||||
|
|
||||||
AndroidDatabaseConfig(File dbDir, File keyDir) {
|
AndroidDatabaseConfig(File dbDir, File keyDir) {
|
||||||
this.dbDir = dbDir;
|
this.dbDir = dbDir;
|
||||||
this.keyDir = keyDir;
|
this.keyDir = keyDir;
|
||||||
|
keyStoreConfig = SDK_INT >= 23 ? new AndroidKeyStoreConfig() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -24,4 +32,10 @@ class AndroidDatabaseConfig implements DatabaseConfig {
|
|||||||
public File getDatabaseKeyDirectory() {
|
public File getDatabaseKeyDirectory() {
|
||||||
return keyDir;
|
return keyDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public KeyStoreConfig getKeyStoreConfig() {
|
||||||
|
return keyStoreConfig;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package org.briarproject.briar.android;
|
||||||
|
|
||||||
|
import android.security.keystore.KeyGenParameterSpec;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.crypto.KeyStoreConfig;
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
import java.security.spec.AlgorithmParameterSpec;
|
||||||
|
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
|
import static android.security.keystore.KeyProperties.PURPOSE_SIGN;
|
||||||
|
import static android.security.keystore.KeyProperties.PURPOSE_VERIFY;
|
||||||
|
|
||||||
|
@RequiresApi(23)
|
||||||
|
@NotNullByDefault
|
||||||
|
class AndroidKeyStoreConfig implements KeyStoreConfig {
|
||||||
|
|
||||||
|
private final KeyGenParameterSpec spec;
|
||||||
|
|
||||||
|
AndroidKeyStoreConfig() {
|
||||||
|
int purposes = PURPOSE_SIGN | PURPOSE_VERIFY;
|
||||||
|
spec = new KeyGenParameterSpec.Builder("db", purposes)
|
||||||
|
.setKeySize(256)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getKeyStoreType() {
|
||||||
|
return "AndroidKeyStore";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAlias() {
|
||||||
|
return "db";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProviderName() {
|
||||||
|
return "AndroidKeyStore";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMacAlgorithmName() {
|
||||||
|
return "HmacSHA256";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AlgorithmParameterSpec getParameterSpec() {
|
||||||
|
return spec;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,15 @@
|
|||||||
package org.briarproject.briar.headless
|
package org.briarproject.briar.headless
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.crypto.KeyStoreConfig
|
||||||
import org.briarproject.bramble.api.db.DatabaseConfig
|
import org.briarproject.bramble.api.db.DatabaseConfig
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
internal class HeadlessDatabaseConfig(private val dbDir: File, private val keyDir: File) :
|
internal class HeadlessDatabaseConfig(private val dbDir: File, private val keyDir: File) :
|
||||||
DatabaseConfig {
|
DatabaseConfig {
|
||||||
|
|
||||||
override fun getDatabaseDirectory(): File {
|
override fun getDatabaseDirectory() = dbDir
|
||||||
return dbDir
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getDatabaseKeyDirectory(): File {
|
override fun getDatabaseKeyDirectory() = keyDir
|
||||||
return keyDir
|
|
||||||
}
|
override fun getKeyStoreConfig(): KeyStoreConfig? = null
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user