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 92feb2d28..d1cbc63da 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 @@ -133,11 +133,11 @@ public interface CryptoComponent { * given password. The ciphertext will be decryptable using the same * password after the app restarts. * - * @param keyStoreConfig Configures the use of a stored key to strengthen - * the password-based key. If null, no stored key will be used + * @param keyStrengthener Used to strengthen the password-based key. If + * null, the password-based key will not be strengthened */ byte[] encryptWithPassword(byte[] plaintext, String password, - @Nullable KeyStoreConfig keyStoreConfig); + @Nullable KeyStrengthener keyStrengthener); /** * Decrypts and authenticates the given ciphertext that has been read from @@ -145,20 +145,19 @@ public interface CryptoComponent { * given password. Returns null if the ciphertext cannot be decrypted and * authenticated (for example, if the password is wrong). * - * @param keyStoreConfig Configures the use of a stored key to strengthen - * the password-based key. If null, or if no stored key was used when - * encrypting the ciphertext, then no stored key will be used + * @param keyStrengthener Used to strengthen the password-based key. If + * null, or if strengthening was not used when encrypting the ciphertext, + * the password-based key will not be strengthened */ @Nullable byte[] decryptWithPassword(byte[] ciphertext, String password, - @Nullable KeyStoreConfig keyStoreConfig); + @Nullable KeyStrengthener keyStrengthener); /** - * 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. + * Returns true if the given ciphertext was encrypted using a strengthened + * key. The validity of the ciphertext is not checked. */ - boolean isEncryptedWithStoredKey(byte[] ciphertext); + boolean isEncryptedWithStrengthenedKey(byte[] ciphertext); /** * Encrypts the given plaintext to the given public key. diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/KeyStoreConfig.java b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/KeyStoreConfig.java deleted file mode 100644 index 5533e994d..000000000 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/KeyStoreConfig.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.briarproject.bramble.api.crypto; - -import org.briarproject.bramble.api.nullsafety.NotNullByDefault; - -import java.security.spec.AlgorithmParameterSpec; -import java.util.List; - -/** - * Configures the use of a stored key to strengthen password-based encryption. - * The key may be stored in a hardware security module, but this is not - * guaranteed. See - * {@link CryptoComponent#encryptWithPassword(byte[], String, KeyStoreConfig)} - * and - * {@link CryptoComponent#decryptWithPassword(byte[], String, KeyStoreConfig)}. - */ -@NotNullByDefault -public interface KeyStoreConfig { - - String getKeyStoreType(); - - String getKeyAlias(); - - String getProviderName(); - - String getMacAlgorithmName(); - - /** - * Returns a list of {@link AlgorithmParameterSpec AlgorithmParameterSpecs} - * to use for key generation, in order of preference. - */ - List getParameterSpecs(); -} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/KeyStrengthener.java b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/KeyStrengthener.java new file mode 100644 index 000000000..5913aae5d --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/crypto/KeyStrengthener.java @@ -0,0 +1,23 @@ +package org.briarproject.bramble.api.crypto; + +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. + */ +@NotNullByDefault +public interface KeyStrengthener { + + /** + * Returns true if the strengthener has been initialised. + */ + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + boolean isInitialised(); + + /** + * Initialises the strengthener if necessary and returns a strong key + * derived from the given key. + */ + SecretKey strengthenKey(SecretKey k); +} diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseConfig.java b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseConfig.java index 36982595d..16d587c1d 100644 --- a/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseConfig.java +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/db/DatabaseConfig.java @@ -1,6 +1,6 @@ package org.briarproject.bramble.api.db; -import org.briarproject.bramble.api.crypto.KeyStoreConfig; +import org.briarproject.bramble.api.crypto.KeyStrengthener; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import java.io.File; @@ -21,9 +21,9 @@ public interface DatabaseConfig { File getDatabaseKeyDirectory(); /** - * Returns a {@link KeyStoreConfig} for strengthening the encryption of the - * database key, or null if no keystore should be used. + * Returns a {@link KeyStrengthener} for strengthening the encryption of + * the database key, or null if no strengthener should be used. */ @Nullable - KeyStoreConfig getKeyStoreConfig(); + KeyStrengthener getKeyStrengthener(); } 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 bec84eda6..c89353fa9 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,7 +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.KeyStrengthener; import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.identity.Identity; @@ -178,7 +178,7 @@ class AccountManagerImpl implements AccountManager { private boolean encryptAndStoreDatabaseKey(SecretKey key, String password) { byte[] plaintext = key.getBytes(); byte[] ciphertext = crypto.encryptWithPassword(plaintext, password, - databaseConfig.getKeyStoreConfig()); + databaseConfig.getKeyStrengthener()); return storeEncryptedDatabaseKey(toHexString(ciphertext)); } @@ -211,19 +211,19 @@ class AccountManagerImpl implements AccountManager { return null; } byte[] ciphertext = fromHexString(hex); - KeyStoreConfig keyStoreConfig = databaseConfig.getKeyStoreConfig(); + KeyStrengthener keyStrengthener = databaseConfig.getKeyStrengthener(); byte[] plaintext = crypto.decryptWithPassword(ciphertext, password, - keyStoreConfig); + keyStrengthener); if (plaintext == null) { LOG.info("Failed to decrypt database key"); return null; } 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"); + // 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"); encryptAndStoreDatabaseKey(key, password); } return key; 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 d4a1785fa..6763193ac 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 @@ -9,7 +9,7 @@ import org.briarproject.bramble.api.crypto.AgreementPublicKey; import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.KeyPair; import org.briarproject.bramble.api.crypto.KeyParser; -import org.briarproject.bramble.api.crypto.KeyStoreConfig; +import org.briarproject.bramble.api.crypto.KeyStrengthener; import org.briarproject.bramble.api.crypto.PrivateKey; import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.SecretKey; @@ -25,28 +25,20 @@ import org.spongycastle.crypto.digests.Blake2bDigest; import org.whispersystems.curve25519.Curve25519; import org.whispersystems.curve25519.Curve25519KeyPair; -import java.io.IOException; 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.Provider; import java.security.SecureRandom; import java.security.Security; -import java.security.spec.AlgorithmParameterSpec; import java.util.logging.Logger; import javax.annotation.Nullable; -import javax.crypto.KeyGenerator; -import javax.crypto.Mac; import javax.inject.Inject; import static java.lang.System.arraycopy; 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_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.LogUtils.logDuration; import static org.briarproject.bramble.util.LogUtils.now; @@ -61,7 +53,7 @@ class CryptoComponentImpl implements CryptoComponent { private static final int STORAGE_IV_BYTES = 24; // 196 bits private static final int PBKDF_SALT_BYTES = 32; // 256 bits private static final byte PBKDF_FORMAT_SCRYPT = 0; - private static final byte PBKDF_FORMAT_SCRYPT_KEYSTORE = 1; + private static final byte PBKDF_FORMAT_SCRYPT_STRENGTHENED = 1; private final SecureRandom secureRandom; private final PasswordBasedKdf passwordBasedKdf; @@ -322,7 +314,7 @@ class CryptoComponentImpl implements CryptoComponent { @Override public byte[] encryptWithPassword(byte[] input, String password, - @Nullable KeyStoreConfig keyStoreConfig) { + @Nullable KeyStrengthener keyStrengthener) { AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher(); int macBytes = cipher.getMacBytes(); // Generate a random salt @@ -332,8 +324,7 @@ class CryptoComponentImpl implements CryptoComponent { int cost = passwordBasedKdf.chooseCostParameter(); // Derive the encryption key from the password SecretKey key = passwordBasedKdf.deriveKey(password, salt, cost); - if (keyStoreConfig != null) - key = requireNonNull(deriveKey(key, keyStoreConfig, true)); + if (keyStrengthener != null) key = keyStrengthener.strengthenKey(key); // Generate a random IV byte[] iv = new byte[STORAGE_IV_BYTES]; secureRandom.nextBytes(iv); @@ -344,8 +335,8 @@ class CryptoComponentImpl implements CryptoComponent { byte[] output = new byte[outputLen]; int outputOff = 0; // Format version - byte formatVersion = keyStoreConfig == null - ? PBKDF_FORMAT_SCRYPT : PBKDF_FORMAT_SCRYPT_KEYSTORE; + byte formatVersion = keyStrengthener == null + ? PBKDF_FORMAT_SCRYPT : PBKDF_FORMAT_SCRYPT_STRENGTHENED; output[outputOff] = formatVersion; outputOff++; // Salt @@ -367,65 +358,10 @@ 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 = null; - Entry e = ks.getEntry(config.getKeyAlias(), null); - if (e == null) { - if (!generateIfMissing) { - LOG.warning("Key not found in keystore"); - return null; - } - // Try the parameter specs in order of preference - for (AlgorithmParameterSpec spec : config.getParameterSpecs()) { - try { - KeyGenerator kg = KeyGenerator.getInstance( - config.getMacAlgorithmName(), - config.getProviderName()); - kg.init(spec); - storedKey = kg.generateKey(); - break; - } catch (Exception e1) { - if (LOG.isLoggable(INFO)) - LOG.info("Could not generate key: " + e1); - // Fall back to next spec - } - } - if (storedKey == null) throw new IllegalArgumentException(); - 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 @Nullable public byte[] decryptWithPassword(byte[] input, String password, - @Nullable KeyStoreConfig keyStoreConfig) { + @Nullable KeyStrengthener keyStrengthener) { AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher(); int macBytes = cipher.getMacBytes(); // The input contains the format version, salt, cost parameter, IV, @@ -439,11 +375,9 @@ class CryptoComponentImpl implements CryptoComponent { inputOff++; // Check whether we support this format version if (formatVersion != PBKDF_FORMAT_SCRYPT && - formatVersion != PBKDF_FORMAT_SCRYPT_KEYSTORE) { + formatVersion != PBKDF_FORMAT_SCRYPT_STRENGTHENED) { return null; } - boolean useKeyStore = keyStoreConfig != null && - formatVersion == PBKDF_FORMAT_SCRYPT_KEYSTORE; // Salt byte[] salt = new byte[PBKDF_SALT_BYTES]; arraycopy(input, inputOff, salt, 0, salt.length); @@ -459,9 +393,10 @@ class CryptoComponentImpl implements CryptoComponent { inputOff += iv.length; // Derive the decryption key from the password SecretKey key = passwordBasedKdf.deriveKey(password, salt, (int) cost); - if (useKeyStore) { - key = deriveKey(key, keyStoreConfig, false); - if (key == null) return null; + if (formatVersion == PBKDF_FORMAT_SCRYPT_STRENGTHENED) { + if (keyStrengthener == null || !keyStrengthener.isInitialised()) + return null; // Can't derive the same strengthened key + key = keyStrengthener.strengthenKey(key); } // Initialise the cipher try { @@ -481,9 +416,9 @@ class CryptoComponentImpl implements CryptoComponent { } @Override - public boolean isEncryptedWithStoredKey(byte[] ciphertext) { + public boolean isEncryptedWithStrengthenedKey(byte[] ciphertext) { return ciphertext.length > 0 && - ciphertext[0] == PBKDF_FORMAT_SCRYPT_KEYSTORE; + ciphertext[0] == PBKDF_FORMAT_SCRYPT_STRENGTHENED; } @Override 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 d06bfa633..4b6b63e7e 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 @@ -1,7 +1,7 @@ package org.briarproject.bramble.account; import org.briarproject.bramble.api.crypto.CryptoComponent; -import org.briarproject.bramble.api.crypto.KeyStoreConfig; +import org.briarproject.bramble.api.crypto.KeyStrengthener; import org.briarproject.bramble.api.crypto.SecretKey; import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.identity.Identity; @@ -40,8 +40,8 @@ public class AccountManagerImplTest extends BrambleMockTestCase { private final DatabaseConfig databaseConfig = context.mock(DatabaseConfig.class); - private final KeyStoreConfig keyStoreConfig = - context.mock(KeyStoreConfig.class); + private final KeyStrengthener keyStrengthener = + context.mock(KeyStrengthener.class); private final CryptoComponent crypto = context.mock(CryptoComponent.class); private final IdentityManager identityManager = context.mock(IdentityManager.class); @@ -71,8 +71,8 @@ public class AccountManagerImplTest extends BrambleMockTestCase { will(returnValue(dbDir)); allowing(databaseConfig).getDatabaseKeyDirectory(); will(returnValue(keyDir)); - allowing(databaseConfig).getKeyStoreConfig(); - will(returnValue(keyStoreConfig)); + allowing(databaseConfig).getKeyStrengthener(); + will(returnValue(keyStrengthener)); }}); accountManager = @@ -95,7 +95,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase { public void testSignInReturnsFalseIfPasswordIsWrong() throws Exception { context.checking(new Expectations() {{ oneOf(crypto).decryptWithPassword(encryptedKey, password, - keyStoreConfig); + keyStrengthener); will(returnValue(null)); }}); @@ -116,9 +116,9 @@ public class AccountManagerImplTest extends BrambleMockTestCase { public void testSignInReturnsTrueIfPasswordIsRight() throws Exception { context.checking(new Expectations() {{ oneOf(crypto).decryptWithPassword(encryptedKey, password, - keyStoreConfig); + keyStrengthener); will(returnValue(key.getBytes())); - oneOf(crypto).isEncryptedWithStoredKey(encryptedKey); + oneOf(crypto).isEncryptedWithStrengthenedKey(encryptedKey); will(returnValue(true)); }}); @@ -142,12 +142,12 @@ public class AccountManagerImplTest extends BrambleMockTestCase { public void testSignInReEncryptsKey() throws Exception { context.checking(new Expectations() {{ oneOf(crypto).decryptWithPassword(encryptedKey, password, - keyStoreConfig); + keyStrengthener); will(returnValue(key.getBytes())); - oneOf(crypto).isEncryptedWithStoredKey(encryptedKey); + oneOf(crypto).isEncryptedWithStrengthenedKey(encryptedKey); will(returnValue(false)); oneOf(crypto).encryptWithPassword(key.getBytes(), password, - keyStoreConfig); + keyStrengthener); will(returnValue(newEncryptedKey)); }}); @@ -297,7 +297,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase { oneOf(crypto).generateSecretKey(); will(returnValue(key)); oneOf(crypto).encryptWithPassword(key.getBytes(), password, - keyStoreConfig); + keyStrengthener); will(returnValue(encryptedKey)); }}); @@ -327,7 +327,7 @@ public class AccountManagerImplTest extends BrambleMockTestCase { throws Exception { context.checking(new Expectations() {{ oneOf(crypto).decryptWithPassword(encryptedKey, password, - keyStoreConfig); + keyStrengthener); will(returnValue(null)); }}); @@ -345,12 +345,12 @@ public class AccountManagerImplTest extends BrambleMockTestCase { throws Exception { context.checking(new Expectations() {{ oneOf(crypto).decryptWithPassword(encryptedKey, password, - keyStoreConfig); + keyStrengthener); will(returnValue(key.getBytes())); - oneOf(crypto).isEncryptedWithStoredKey(encryptedKey); + oneOf(crypto).isEncryptedWithStrengthenedKey(encryptedKey); will(returnValue(true)); oneOf(crypto).encryptWithPassword(key.getBytes(), newPassword, - keyStoreConfig); + keyStrengthener); will(returnValue(newEncryptedKey)); }}); diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/TestDatabaseConfig.java b/bramble-core/src/test/java/org/briarproject/bramble/test/TestDatabaseConfig.java index f285f1c80..ed4afd622 100644 --- a/bramble-core/src/test/java/org/briarproject/bramble/test/TestDatabaseConfig.java +++ b/bramble-core/src/test/java/org/briarproject/bramble/test/TestDatabaseConfig.java @@ -1,6 +1,6 @@ package org.briarproject.bramble.test; -import org.briarproject.bramble.api.crypto.KeyStoreConfig; +import org.briarproject.bramble.api.crypto.KeyStrengthener; import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; @@ -30,7 +30,7 @@ public class TestDatabaseConfig implements DatabaseConfig { @Nullable @Override - public KeyStoreConfig getKeyStoreConfig() { + public KeyStrengthener getKeyStrengthener() { return null; } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AndroidDatabaseConfig.java b/briar-android/src/main/java/org/briarproject/briar/android/AndroidDatabaseConfig.java index c27cc2274..e13b1463a 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AndroidDatabaseConfig.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AndroidDatabaseConfig.java @@ -1,6 +1,6 @@ package org.briarproject.briar.android; -import org.briarproject.bramble.api.crypto.KeyStoreConfig; +import org.briarproject.bramble.api.crypto.KeyStrengthener; import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; @@ -8,19 +8,18 @@ import java.io.File; import javax.annotation.Nullable; -import static android.os.Build.VERSION.SDK_INT; - @NotNullByDefault class AndroidDatabaseConfig implements DatabaseConfig { private final File dbDir, keyDir; @Nullable - private final KeyStoreConfig keyStoreConfig; + private final KeyStrengthener keyStrengthener; - AndroidDatabaseConfig(File dbDir, File keyDir) { + AndroidDatabaseConfig(File dbDir, File keyDir, + @Nullable KeyStrengthener keyStrengthener) { this.dbDir = dbDir; this.keyDir = keyDir; - keyStoreConfig = SDK_INT >= 23 ? new AndroidKeyStoreConfig() : null; + this.keyStrengthener = keyStrengthener; } @Override @@ -35,7 +34,7 @@ class AndroidDatabaseConfig implements DatabaseConfig { @Nullable @Override - public KeyStoreConfig getKeyStoreConfig() { - return keyStoreConfig; + public KeyStrengthener getKeyStrengthener() { + return keyStrengthener; } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AndroidKeyStoreConfig.java b/briar-android/src/main/java/org/briarproject/briar/android/AndroidKeyStoreConfig.java deleted file mode 100644 index 5f9b4b1e8..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/AndroidKeyStoreConfig.java +++ /dev/null @@ -1,66 +0,0 @@ -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 java.util.List; - -import androidx.annotation.RequiresApi; - -import static android.os.Build.VERSION.SDK_INT; -import static android.security.keystore.KeyProperties.PURPOSE_SIGN; -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; - -@RequiresApi(23) -@NotNullByDefault -class AndroidKeyStoreConfig implements KeyStoreConfig { - - private final List specs; - - AndroidKeyStoreConfig() { - KeyGenParameterSpec noStrongBox = - new KeyGenParameterSpec.Builder("db", PURPOSE_SIGN) - .setKeySize(256) - .build(); - if (SDK_INT >= 28) { - // Prefer StrongBox if available - KeyGenParameterSpec strongBox = - new KeyGenParameterSpec.Builder("db", PURPOSE_SIGN) - .setIsStrongBoxBacked(true) - .setKeySize(256) - .build(); - specs = asList(strongBox, noStrongBox); - } else { - specs = singletonList(noStrongBox); - } - } - - @Override - public String getKeyStoreType() { - return "AndroidKeyStore"; - } - - @Override - public String getKeyAlias() { - return "db"; - } - - @Override - public String getProviderName() { - return "AndroidKeyStore"; - } - - @Override - public String getMacAlgorithmName() { - return "HmacSHA256"; - } - - @Override - public List getParameterSpecs() { - return specs; - } -} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AndroidKeyStrengthener.java b/briar-android/src/main/java/org/briarproject/briar/android/AndroidKeyStrengthener.java new file mode 100644 index 000000000..db44062f9 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/AndroidKeyStrengthener.java @@ -0,0 +1,118 @@ +package org.briarproject.briar.android; + +import android.security.keystore.KeyGenParameterSpec; + +import org.briarproject.bramble.api.crypto.KeyStrengthener; +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.KeyStore.Entry; +import java.security.KeyStore.SecretKeyEntry; +import java.security.spec.AlgorithmParameterSpec; +import java.util.List; +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.annotation.concurrent.GuardedBy; +import javax.crypto.KeyGenerator; +import javax.crypto.Mac; + +import androidx.annotation.RequiresApi; + +import static android.os.Build.VERSION.SDK_INT; +import static android.security.keystore.KeyProperties.KEY_ALGORITHM_HMAC_SHA256; +import static android.security.keystore.KeyProperties.PURPOSE_SIGN; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static java.util.logging.Level.INFO; +import static java.util.logging.Logger.getLogger; + +@RequiresApi(23) +@NotNullByDefault +class AndroidKeyStrengthener implements KeyStrengthener { + + private static final Logger LOG = + getLogger(AndroidKeyStrengthener.class.getName()); + + private static final String KEY_STORE_TYPE = "AndroidKeyStore"; + private static final String PROVIDER_NAME = "AndroidKeyStore"; + private static final String KEY_ALIAS = "db"; + private static final int KEY_BITS = 256; + + private final List specs; + + AndroidKeyStrengthener() { + KeyGenParameterSpec noStrongBox = + new KeyGenParameterSpec.Builder(KEY_ALIAS, PURPOSE_SIGN) + .setKeySize(KEY_BITS) + .build(); + if (SDK_INT >= 28) { + // Prefer StrongBox if available + KeyGenParameterSpec strongBox = + new KeyGenParameterSpec.Builder(KEY_ALIAS, PURPOSE_SIGN) + .setIsStrongBoxBacked(true) + .setKeySize(KEY_BITS) + .build(); + specs = asList(strongBox, noStrongBox); + } else { + specs = singletonList(noStrongBox); + } + } + + @GuardedBy("this") + @Nullable + private javax.crypto.SecretKey storedKey = null; + + @Override + public synchronized boolean isInitialised() { + if (storedKey != null) return true; + try { + KeyStore ks = KeyStore.getInstance(KEY_STORE_TYPE); + ks.load(null); + Entry entry = ks.getEntry(KEY_ALIAS, null); + if (entry instanceof SecretKeyEntry) { + storedKey = ((SecretKeyEntry) entry).getSecretKey(); + LOG.info("Loaded key from keystore"); + return true; + } + return false; + } catch (GeneralSecurityException | IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public synchronized SecretKey strengthenKey(SecretKey k) { + try { + if (!isInitialised()) initialise(); + // Use the input key and the stored key to derive the output key + Mac mac = Mac.getInstance(KEY_ALGORITHM_HMAC_SHA256); + mac.init(storedKey); + return new SecretKey(mac.doFinal(k.getBytes())); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } + } + + private synchronized void initialise() throws GeneralSecurityException { + // Try the parameter specs in order of preference + for (AlgorithmParameterSpec spec : specs) { + try { + KeyGenerator kg = KeyGenerator.getInstance( + KEY_ALGORITHM_HMAC_SHA256, PROVIDER_NAME); + kg.init(spec); + storedKey = kg.generateKey(); + LOG.info("Stored key in keystore"); + return; + } catch (Exception e) { + if (LOG.isLoggable(INFO)) + LOG.info("Could not generate key: " + e); + // Fall back to next spec + } + } + throw new GeneralSecurityException("Could not generate key"); + } +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java index 5da45a9c5..41c728e5e 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AppModule.java @@ -10,6 +10,7 @@ import com.vanniktech.emoji.RecentEmoji; import org.briarproject.bramble.api.FeatureFlags; import org.briarproject.bramble.api.battery.BatteryManager; import org.briarproject.bramble.api.crypto.CryptoComponent; +import org.briarproject.bramble.api.crypto.KeyStrengthener; import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.event.EventBus; @@ -56,6 +57,7 @@ import dagger.Module; import dagger.Provides; import static android.content.Context.MODE_PRIVATE; +import static android.os.Build.VERSION.SDK_INT; import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_ONION_ADDRESS; @@ -101,7 +103,9 @@ public class AppModule { File dbDir = app.getApplicationContext().getDir("db", MODE_PRIVATE); File keyDir = app.getApplicationContext().getDir("key", MODE_PRIVATE); StrictMode.setThreadPolicy(tp); - return new AndroidDatabaseConfig(dbDir, keyDir); + KeyStrengthener keyStrengthener = SDK_INT >= 23 + ? new AndroidKeyStrengthener() : null; + return new AndroidDatabaseConfig(dbDir, keyDir, keyStrengthener); } @Provides diff --git a/briar-headless/src/main/java/org/briarproject/briar/headless/HeadlessDatabaseConfig.kt b/briar-headless/src/main/java/org/briarproject/briar/headless/HeadlessDatabaseConfig.kt index 6dca9ae10..620bdc826 100644 --- a/briar-headless/src/main/java/org/briarproject/briar/headless/HeadlessDatabaseConfig.kt +++ b/briar-headless/src/main/java/org/briarproject/briar/headless/HeadlessDatabaseConfig.kt @@ -1,6 +1,6 @@ package org.briarproject.briar.headless -import org.briarproject.bramble.api.crypto.KeyStoreConfig +import org.briarproject.bramble.api.crypto.KeyStrengthener import org.briarproject.bramble.api.db.DatabaseConfig import java.io.File @@ -11,5 +11,5 @@ internal class HeadlessDatabaseConfig(private val dbDir: File, private val keyDi override fun getDatabaseKeyDirectory() = keyDir - override fun getKeyStoreConfig(): KeyStoreConfig? = null + override fun getKeyStrengthener(): KeyStrengthener? = null }