Merge branch '1696-keystore-crash' into 'master'

Show a dialog instead of crashing if a hardware-backed key can't be loaded

Closes #1696

See merge request briar/briar!1233
This commit is contained in:
Torsten Grote
2020-03-12 12:12:42 +00:00
26 changed files with 459 additions and 294 deletions

View File

@@ -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.DecryptionException;
import org.briarproject.bramble.api.crypto.KeyStrengthener;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig;
@@ -17,6 +18,7 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.logging.Logger;
import javax.annotation.Nullable;
@@ -24,6 +26,7 @@ import javax.annotation.concurrent.GuardedBy;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_CIPHERTEXT;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.StringUtils.fromHexString;
import static org.briarproject.bramble.util.StringUtils.toHexString;
@@ -95,7 +98,7 @@ class AccountManagerImpl implements AccountManager {
}
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(
new FileInputStream(f), "UTF-8"));
new FileInputStream(f), Charset.forName("UTF-8")));
String key = reader.readLine();
reader.close();
return key;
@@ -147,7 +150,7 @@ class AccountManagerImpl implements AccountManager {
@GuardedBy("stateChangeLock")
private void writeDbKeyToFile(String key, File f) throws IOException {
FileOutputStream out = new FileOutputStream(f);
out.write(key.getBytes("UTF-8"));
out.write(key.getBytes(Charset.forName("UTF-8")));
out.flush();
out.close();
}
@@ -192,31 +195,24 @@ class AccountManagerImpl implements AccountManager {
}
@Override
public boolean signIn(String password) {
public void signIn(String password) throws DecryptionException {
synchronized (stateChangeLock) {
SecretKey key = loadAndDecryptDatabaseKey(password);
if (key == null) return false;
databaseKey = key;
return true;
databaseKey = loadAndDecryptDatabaseKey(password);
}
}
@GuardedBy("stateChangeLock")
@Nullable
private SecretKey loadAndDecryptDatabaseKey(String password) {
private SecretKey loadAndDecryptDatabaseKey(String password)
throws DecryptionException {
String hex = loadEncryptedDatabaseKey();
if (hex == null) {
LOG.warning("Failed to load encrypted database key");
return null;
throw new DecryptionException(INVALID_CIPHERTEXT);
}
byte[] ciphertext = fromHexString(hex);
KeyStrengthener keyStrengthener = databaseConfig.getKeyStrengthener();
byte[] plaintext = crypto.decryptWithPassword(ciphertext, password,
keyStrengthener);
if (plaintext == null) {
LOG.info("Failed to decrypt database key");
return null;
}
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
@@ -229,10 +225,11 @@ class AccountManagerImpl implements AccountManager {
}
@Override
public boolean changePassword(String oldPassword, String newPassword) {
public void changePassword(String oldPassword, String newPassword)
throws DecryptionException {
synchronized (stateChangeLock) {
SecretKey key = loadAndDecryptDatabaseKey(oldPassword);
return key != null && encryptAndStoreDatabaseKey(key, newPassword);
encryptAndStoreDatabaseKey(key, newPassword);
}
}
}

View File

@@ -7,6 +7,7 @@ import net.i2p.crypto.eddsa.KeyPairGenerator;
import org.briarproject.bramble.api.crypto.AgreementPrivateKey;
import org.briarproject.bramble.api.crypto.AgreementPublicKey;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.DecryptionException;
import org.briarproject.bramble.api.crypto.KeyPair;
import org.briarproject.bramble.api.crypto.KeyParser;
import org.briarproject.bramble.api.crypto.KeyStrengthener;
@@ -39,6 +40,9 @@ 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.crypto.DecryptionResult.INVALID_CIPHERTEXT;
import static org.briarproject.bramble.api.crypto.DecryptionResult.INVALID_PASSWORD;
import static org.briarproject.bramble.api.crypto.DecryptionResult.KEY_STRENGTHENER_ERROR;
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;
@@ -359,16 +363,17 @@ class CryptoComponentImpl implements CryptoComponent {
}
@Override
@Nullable
public byte[] decryptWithPassword(byte[] input, String password,
@Nullable KeyStrengthener keyStrengthener) {
@Nullable KeyStrengthener keyStrengthener)
throws DecryptionException {
AuthenticatedCipher cipher = new XSalsa20Poly1305AuthenticatedCipher();
int macBytes = cipher.getMacBytes();
// The input contains the format version, salt, cost parameter, IV,
// ciphertext and MAC
if (input.length < 1 + PBKDF_SALT_BYTES + INT_32_BYTES
+ STORAGE_IV_BYTES + macBytes)
return null; // Invalid input
+ STORAGE_IV_BYTES + macBytes) {
throw new DecryptionException(INVALID_CIPHERTEXT);
}
int inputOff = 0;
// Format version
byte formatVersion = input[inputOff];
@@ -376,7 +381,7 @@ class CryptoComponentImpl implements CryptoComponent {
// Check whether we support this format version
if (formatVersion != PBKDF_FORMAT_SCRYPT &&
formatVersion != PBKDF_FORMAT_SCRYPT_STRENGTHENED) {
return null;
throw new DecryptionException(INVALID_CIPHERTEXT);
}
// Salt
byte[] salt = new byte[PBKDF_SALT_BYTES];
@@ -385,8 +390,9 @@ class CryptoComponentImpl implements CryptoComponent {
// Cost parameter
long cost = ByteUtils.readUint32(input, inputOff);
inputOff += INT_32_BYTES;
if (cost < 2 || cost > Integer.MAX_VALUE)
return null; // Invalid cost parameter
if (cost < 2 || cost > Integer.MAX_VALUE) {
throw new DecryptionException(INVALID_CIPHERTEXT);
}
// IV
byte[] iv = new byte[STORAGE_IV_BYTES];
arraycopy(input, inputOff, iv, 0, iv.length);
@@ -394,8 +400,10 @@ class CryptoComponentImpl implements CryptoComponent {
// Derive the decryption key from the password
SecretKey key = passwordBasedKdf.deriveKey(password, salt, (int) cost);
if (formatVersion == PBKDF_FORMAT_SCRYPT_STRENGTHENED) {
if (keyStrengthener == null || !keyStrengthener.isInitialised())
return null; // Can't derive the same strengthened key
if (keyStrengthener == null || !keyStrengthener.isInitialised()) {
// Can't derive the same strengthened key
throw new DecryptionException(KEY_STRENGTHENER_ERROR);
}
key = keyStrengthener.strengthenKey(key);
}
// Initialise the cipher
@@ -411,7 +419,7 @@ class CryptoComponentImpl implements CryptoComponent {
cipher.process(input, inputOff, inputLen, output, 0);
return output;
} catch (GeneralSecurityException e) {
return null; // Invalid ciphertext
throw new DecryptionException(INVALID_PASSWORD);
}
}