UI for setting and entering the password that encrypts the database key.

This commit is contained in:
akwizgran
2013-04-16 14:49:14 +01:00
parent e343c9f4bb
commit 374bff2fb6
16 changed files with 328 additions and 87 deletions

View File

@@ -30,7 +30,7 @@
</activity>
<activity
android:name=".android.SetupActivity"
android:label="@string/app_name" >
android:label="@string/setup_title" >
</activity>
<activity
android:name=".android.SplashScreenActivity"

View File

@@ -3,6 +3,13 @@
<string name="app_name">Briar</string>
<string name="notification_title">Briar is running</string>
<string name="notification_text">Touch to quit.</string>
<string name="setup_title">Briar Setup</string>
<string name="choose_nickname">Choose your nickname:</string>
<string name="choose_password">Choose your password:</string>
<string name="confirm_password">Confirm your password:</string>
<string name="format_min_password">Password must be at least %1$d characters long.</string>
<string name="enter_password">Enter your password:</string>
<string name="try_again">Wrong password, try again</string>
<string name="contact_list_button">Contacts</string>
<string name="messages_button">Messages</string>
<string name="groups_button">Groups</string>
@@ -29,14 +36,14 @@
<string name="format_connecting_wifi">Connecting via %1$s\u2026</string>
<string name="connecting_bluetooth">Connecting via Bluetooth\u2026</string>
<string name="connection_failed">Connection failed</string>
<string name="check_same_network">Please check that you are both using the same network.</string>
<string name="check_same_network">Please check that you are both using the same network</string>
<string name="try_again_button">Try again</string>
<string name="connected_to_contact">Connected to contact</string>
<string name="your_confirmation_code">Your confirmation code is</string>
<string name="enter_confirmation_code">Please enter your contact\'s confirmation code:</string>
<string name="waiting_for_contact">Waiting for contact\u2026</string>
<string name="codes_do_not_match">Codes do not match</string>
<string name="interfering">This could mean that someone is trying to interfere with your connection.</string>
<string name="interfering">This could mean that someone is trying to interfere with your connection</string>
<string name="contact_added">Contact added</string>
<string name="done_button">Done</string>
<string name="messages_title">Messages</string>
@@ -67,7 +74,6 @@
<string name="new_blog_item">New blog\u2026</string>
<string name="create_nickname_item">New nickname\u2026</string>
<string name="create_identity_title">Create an Identity</string>
<string name="choose_nickname">Choose your nickname:</string>
<string name="create_button">Create</string>
<string name="no_contacts">You don\'t have any contacts. Add a contact now?</string>
<string name="add_button">Add</string>

View File

@@ -1,10 +1,18 @@
package net.sf.briar.android;
import static android.text.InputType.TYPE_CLASS_TEXT;
import static android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD;
import static android.view.Gravity.CENTER;
import static android.view.Gravity.CENTER_HORIZONTAL;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.inputmethod.InputMethodManager.HIDE_IMPLICIT_ONLY;
import static android.widget.LinearLayout.VERTICAL;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static net.sf.briar.android.widgets.CommonLayoutParams.MATCH_MATCH;
import static net.sf.briar.android.widgets.CommonLayoutParams.WRAP_WRAP;
import static net.sf.briar.api.messaging.Rating.GOOD;
import java.util.ArrayList;
@@ -22,20 +30,31 @@ import net.sf.briar.android.messages.ConversationListActivity;
import net.sf.briar.api.LocalAuthor;
import net.sf.briar.api.android.DatabaseUiExecutor;
import net.sf.briar.api.android.ReferenceManager;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.CryptoExecutor;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DatabaseConfig;
import net.sf.briar.api.db.DbException;
import net.sf.briar.util.StringUtils;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.IBinder;
import android.text.Editable;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.GridView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import com.google.inject.Inject;
@@ -48,10 +67,17 @@ public class HomeScreenActivity extends BriarActivity {
new BriarServiceConnection();
@Inject private ReferenceManager referenceManager = null;
@Inject private DatabaseConfig databaseConfig = null;
@Inject @DatabaseUiExecutor private Executor dbUiExecutor = null;
@Inject @CryptoExecutor private Executor cryptoExecutor = null;
private boolean bound = false;
private TextView tryAgain = null;
private Button continueButton = null;
private ProgressBar progress = null;
// Fields that are accessed from background threads must be volatile
@Inject private volatile DatabaseComponent db = null;
@Inject private volatile CryptoComponent crypto = null;
@Override
public void onCreate(Bundle state) {
@@ -62,20 +88,23 @@ public class HomeScreenActivity extends BriarActivity {
if(quit) {
// The activity was launched from the notification bar
showSpinner();
bindService();
quit();
} else if(handle != -1) {
// The activity was launched from the setup wizard
showSpinner();
startService(new Intent(BriarService.class.getName()));
bindService();
storeLocalAuthor(referenceManager.removeReference(handle,
LocalAuthor.class));
} else {
} else if(databaseConfig.getEncryptionKey() == null) {
// The activity was launched from the splash screen
showPasswordPrompt();
} else {
// The activity has been launched before
showButtons();
bindService();
}
// Start the service and bind to it
startService(new Intent(BriarService.class.getName()));
bindService(new Intent(BriarService.class.getName()),
serviceConnection, 0);
}
private void showSpinner() {
@@ -88,6 +117,11 @@ public class HomeScreenActivity extends BriarActivity {
setContentView(layout);
}
private void bindService() {
bound = bindService(new Intent(BriarService.class.getName()),
serviceConnection, 0);
}
private void quit() {
new Thread() {
@Override
@@ -145,6 +179,110 @@ public class HomeScreenActivity extends BriarActivity {
});
}
private void showPasswordPrompt() {
SharedPreferences prefs = getSharedPreferences("db", MODE_PRIVATE);
String hex = prefs.getString("key", null);
if(hex == null) throw new IllegalStateException();
final byte[] encrypted = StringUtils.fromHexString(hex);
LinearLayout layout = new LinearLayout(this);
layout.setLayoutParams(MATCH_MATCH);
layout.setOrientation(VERTICAL);
layout.setGravity(CENTER_HORIZONTAL);
TextView enterPassword = new TextView(this);
enterPassword.setGravity(CENTER);
enterPassword.setTextSize(18);
enterPassword.setPadding(10, 10, 10, 10);
enterPassword.setText(R.string.enter_password);
layout.addView(enterPassword);
final EditText passwordEntry = new EditText(this);
passwordEntry.setMaxLines(1);
passwordEntry.setPadding(10, 0, 10, 10);
int inputType = TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD;
passwordEntry.setInputType(inputType);
passwordEntry.setOnEditorActionListener(new OnEditorActionListener() {
public boolean onEditorAction(TextView v, int action, KeyEvent e) {
validatePassword(encrypted, passwordEntry.getText());
return true;
}
});
layout.addView(passwordEntry);
tryAgain = new TextView(this);
tryAgain.setGravity(CENTER);
tryAgain.setTextSize(14);
tryAgain.setPadding(10, 10, 10, 10);
tryAgain.setText(R.string.try_again);
tryAgain.setVisibility(GONE);
layout.addView(tryAgain);
continueButton = new Button(this);
continueButton.setLayoutParams(WRAP_WRAP);
continueButton.setText(R.string.continue_button);
continueButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
validatePassword(encrypted, passwordEntry.getText());
}
});
layout.addView(continueButton);
progress = new ProgressBar(this);
progress.setLayoutParams(WRAP_WRAP);
progress.setIndeterminate(true);
progress.setVisibility(GONE);
layout.addView(progress);
setContentView(layout);
}
private void validatePassword(final byte[] encrypted, Editable e) {
if(tryAgain == null || continueButton == null || progress == null)
return;
// Hide the soft keyboard
Object o = getSystemService(INPUT_METHOD_SERVICE);
((InputMethodManager) o).toggleSoftInput(HIDE_IMPLICIT_ONLY, 0);
// Replace the button with a progress bar
continueButton.setVisibility(GONE);
progress.setVisibility(VISIBLE);
// Decrypt the database key in a background thread
int length = e.length();
final char[] password = new char[length];
e.getChars(0, length, password, 0);
e.delete(0, length);
cryptoExecutor.execute(new Runnable() {
public void run() {
byte[] key = crypto.decryptWithPassword(encrypted, password);
if(key == null) {
tryAgain();
} else {
databaseConfig.setEncryptionKey(key);
showButtonsAndStartService();
}
}
});
}
private void tryAgain() {
runOnUiThread(new Runnable() {
public void run() {
tryAgain.setVisibility(VISIBLE);
continueButton.setVisibility(VISIBLE);
progress.setVisibility(GONE);
}
});
}
private void showButtonsAndStartService() {
runOnUiThread(new Runnable() {
public void run() {
showButtons();
startService(new Intent(BriarService.class.getName()));
bindService();
}
});
}
private void showButtons() {
ListView.LayoutParams matchMatch =
new ListView.LayoutParams(MATCH_PARENT, MATCH_PARENT);
@@ -227,6 +365,7 @@ public class HomeScreenActivity extends BriarActivity {
quitButton.setText(R.string.quit_button);
quitButton.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
showSpinner();
quit();
}
});
@@ -264,6 +403,6 @@ public class HomeScreenActivity extends BriarActivity {
@Override
public void onDestroy() {
super.onDestroy();
unbindService(serviceConnection);
if(bound) unbindService(serviceConnection);
}
}

View File

@@ -3,17 +3,18 @@ package net.sf.briar.android;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.text.InputType.TYPE_CLASS_TEXT;
import static android.text.InputType.TYPE_TEXT_FLAG_CAP_WORDS;
import static android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD;
import static android.view.Gravity.CENTER;
import static android.view.Gravity.CENTER_HORIZONTAL;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static android.view.inputmethod.InputMethodManager.HIDE_IMPLICIT_ONLY;
import static android.widget.LinearLayout.VERTICAL;
import static net.sf.briar.android.widgets.CommonLayoutParams.MATCH_MATCH;
import static net.sf.briar.android.widgets.CommonLayoutParams.WRAP_WRAP;
import java.io.IOException;
import java.security.KeyPair;
import java.util.Arrays;
import java.util.concurrent.Executor;
import net.sf.briar.R;
@@ -22,31 +23,36 @@ import net.sf.briar.api.LocalAuthor;
import net.sf.briar.api.android.ReferenceManager;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.CryptoExecutor;
import net.sf.briar.api.db.DatabaseConfig;
import net.sf.briar.util.StringUtils;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Bundle;
import android.view.KeyEvent;
import android.text.Editable;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import com.google.inject.Inject;
public class SetupActivity extends BriarActivity
implements OnEditorActionListener, OnClickListener {
public class SetupActivity extends BriarActivity implements OnClickListener {
private static final int MIN_PASSWORD_LENGTH = 8;
@Inject @CryptoExecutor private Executor cryptoExecutor;
private EditText nicknameEntry = null;
private Button createButton = null;
private EditText passwordEntry = null, passwordConfirmation = null;
private Button continueButton = null;
private ProgressBar progress = null;
// Fields that are accessed from background threads must be volatile
@Inject private volatile CryptoComponent crypto;
@Inject private volatile DatabaseConfig databaseConfig;
@Inject private volatile AuthorFactory authorFactory;
@Inject private volatile ReferenceManager referenceManager;
@@ -69,24 +75,66 @@ implements OnEditorActionListener, OnClickListener {
@Override
protected void onTextChanged(CharSequence text, int start,
int lengthBefore, int lengthAfter) {
if(createButton != null)
createButton.setEnabled(lengthAfter > 0);
enableOrDisableContinueButton();
}
};
nicknameEntry.setTextSize(18);
nicknameEntry.setMaxLines(1);
nicknameEntry.setPadding(10, 10, 10, 10);
int inputType = TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_CAP_WORDS;
nicknameEntry.setInputType(inputType);
nicknameEntry.setOnEditorActionListener(this);
layout.addView(nicknameEntry);
createButton = new Button(this);
createButton.setLayoutParams(WRAP_WRAP);
createButton.setText(R.string.create_button);
createButton.setEnabled(false);
createButton.setOnClickListener(this);
layout.addView(createButton);
TextView choosePassword = new TextView(this);
choosePassword.setGravity(CENTER);
choosePassword.setTextSize(18);
choosePassword.setPadding(10, 10, 10, 10);
choosePassword.setText(R.string.choose_password);
layout.addView(choosePassword);
passwordEntry = new EditText(this) {
@Override
protected void onTextChanged(CharSequence text, int start,
int lengthBefore, int lengthAfter) {
enableOrDisableContinueButton();
}
};
passwordEntry.setMaxLines(1);
inputType = TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD;
passwordEntry.setInputType(inputType);
layout.addView(passwordEntry);
TextView confirmPassword = new TextView(this);
confirmPassword.setGravity(CENTER);
confirmPassword.setTextSize(18);
confirmPassword.setPadding(10, 10, 10, 10);
confirmPassword.setText(R.string.confirm_password);
layout.addView(confirmPassword);
passwordConfirmation = new EditText(this) {
@Override
protected void onTextChanged(CharSequence text, int start,
int lengthBefore, int lengthAfter) {
enableOrDisableContinueButton();
}
};
passwordConfirmation.setMaxLines(1);
inputType = TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD;
passwordConfirmation.setInputType(inputType);
layout.addView(passwordConfirmation);
TextView minPasswordLength = new TextView(this);
minPasswordLength.setGravity(CENTER);
minPasswordLength.setTextSize(14);
minPasswordLength.setPadding(10, 10, 10, 10);
String format = getResources().getString(R.string.format_min_password);
minPasswordLength.setText(String.format(format, MIN_PASSWORD_LENGTH));
layout.addView(minPasswordLength);
continueButton = new Button(this);
continueButton.setLayoutParams(WRAP_WRAP);
continueButton.setText(R.string.continue_button);
continueButton.setEnabled(false);
continueButton.setOnClickListener(this);
layout.addView(continueButton);
progress = new ProgressBar(this);
progress.setLayoutParams(WRAP_WRAP);
@@ -97,20 +145,42 @@ implements OnEditorActionListener, OnClickListener {
setContentView(layout);
}
public boolean onEditorAction(TextView textView, int actionId, KeyEvent e) {
validateNickname();
return true;
private void enableOrDisableContinueButton() {
if(nicknameEntry == null || passwordEntry == null ||
passwordConfirmation == null || continueButton == null) return;
boolean nicknameNotEmpty = nicknameEntry.getText().length() > 0;
char[] firstPassword = getChars(passwordEntry.getText());
char[] secondPassword = getChars(passwordConfirmation.getText());
boolean passwordLength = firstPassword.length >= MIN_PASSWORD_LENGTH;
boolean passwordsMatch = Arrays.equals(firstPassword, secondPassword);
for(int i = 0; i < firstPassword.length; i++) firstPassword[i] = 0;
for(int i = 0; i < secondPassword.length; i++) secondPassword[i] = 0;
boolean valid = nicknameNotEmpty && passwordLength && passwordsMatch;
continueButton.setEnabled(valid);
}
private char[] getChars(Editable e) {
int length = e.length();
char[] c = new char[length];
e.getChars(0, length, c, 0);
return c;
}
public void onClick(View view) {
if(!validateNickname()) return;
final String nickname = nicknameEntry.getText().toString();
final char[] password = getChars(passwordEntry.getText());
delete(passwordEntry.getText());
delete(passwordConfirmation.getText());
// Replace the button with a progress bar
createButton.setVisibility(GONE);
continueButton.setVisibility(GONE);
progress.setVisibility(VISIBLE);
// Create the identity in a background thread
// Store the DB key and create the identity in a background thread
cryptoExecutor.execute(new Runnable() {
public void run() {
byte[] key = crypto.generateSecretKey().getEncoded();
byte[] encrypted = crypto.encryptWithPassword(key, password);
storeEncryptedDatabaseKey(encrypted);
databaseConfig.setEncryptionKey(key);
KeyPair keyPair = crypto.generateSignatureKeyPair();
final byte[] publicKey = keyPair.getPublic().getEncoded();
final byte[] privateKey = keyPair.getPrivate().getEncoded();
@@ -127,6 +197,17 @@ implements OnEditorActionListener, OnClickListener {
});
}
private void delete(Editable e) {
e.delete(0, e.length());
}
private void storeEncryptedDatabaseKey(byte[] encrypted) {
SharedPreferences prefs = getSharedPreferences("db", MODE_PRIVATE);
Editor editor = prefs.edit();
editor.putString("key", StringUtils.toHexString(encrypted));
editor.commit();
}
private void showHomeScreen(final long handle) {
runOnUiThread(new Runnable() {
public void run() {
@@ -139,12 +220,4 @@ implements OnEditorActionListener, OnClickListener {
}
});
}
private boolean validateNickname() {
if(nicknameEntry.getText().toString().equals("")) return false;
// Hide the soft keyboard
Object o = getSystemService(INPUT_METHOD_SERVICE);
((InputMethodManager) o).toggleSoftInput(HIDE_IMPLICIT_ONLY, 0);
return true;
}
}

View File

@@ -2,7 +2,7 @@ package net.sf.briar.android;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.view.Gravity.CENTER;
import net.sf.briar.android.widgets.CommonLayoutParams;
import static net.sf.briar.android.widgets.CommonLayoutParams.MATCH_MATCH;
import net.sf.briar.api.db.DatabaseConfig;
import roboguice.RoboGuice;
import roboguice.activity.RoboSplashActivity;
@@ -24,7 +24,7 @@ public class SplashScreenActivity extends RoboSplashActivity {
public void onCreate(Bundle state) {
super.onCreate(null);
LinearLayout layout = new LinearLayout(this);
layout.setLayoutParams(CommonLayoutParams.MATCH_MATCH);
layout.setLayoutParams(MATCH_MATCH);
layout.setGravity(CENTER);
ProgressBar spinner = new ProgressBar(this);
spinner.setIndeterminate(true);

View File

@@ -99,9 +99,8 @@ SelectContactsDialog.Listener {
enableOrDisableCreateButton();
}
};
nameEntry.setTextSize(18);
nameEntry.setMaxLines(1);
nameEntry.setPadding(10, 10, 10, 10);
nameEntry.setPadding(10, 0, 10, 10);
nameEntry.setInputType(TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_CAP_SENTENCES);
nameEntry.setOnEditorActionListener(this);
layout.addView(nameEntry);

View File

@@ -94,9 +94,8 @@ SelectContactsDialog.Listener {
enableOrDisableCreateButton();
}
};
nameEntry.setTextSize(18);
nameEntry.setMaxLines(1);
nameEntry.setPadding(10, 10, 10, 10);
nameEntry.setPadding(10, 0, 10, 10);
nameEntry.setInputType(TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_CAP_SENTENCES);
nameEntry.setOnEditorActionListener(this);
layout.addView(nameEntry);

View File

@@ -35,6 +35,8 @@ public class HelloWorldModule extends AbstractModule {
final File dir = app.getApplicationContext().getDir("db", MODE_PRIVATE);
return new DatabaseConfig() {
private volatile byte[] key = null;
public boolean databaseExists() {
return dir.isDirectory() && dir.listFiles().length > 0;
}
@@ -43,8 +45,12 @@ public class HelloWorldModule extends AbstractModule {
return dir;
}
public char[] getPassword() {
return "foo bar".toCharArray();
public void setEncryptionKey(byte[] key) {
this.key = key;
}
public byte[] getEncryptionKey() {
return key;
}
public long getMaxSize() {

View File

@@ -85,12 +85,11 @@ implements OnEditorActionListener, OnClickListener {
protected void onTextChanged(CharSequence text, int start,
int lengthBefore, int lengthAfter) {
if(createButton != null)
createButton.setEnabled(lengthAfter > 0);
createButton.setEnabled(getText().length() > 0);
}
};
nicknameEntry.setTextSize(18);
nicknameEntry.setMaxLines(1);
nicknameEntry.setPadding(10, 10, 10, 10);
nicknameEntry.setPadding(10, 0, 10, 10);
int inputType = TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_CAP_WORDS;
nicknameEntry.setInputType(inputType);
nicknameEntry.setOnEditorActionListener(this);

View File

@@ -96,7 +96,6 @@ implements OnItemSelectedListener, OnClickListener {
from = new TextView(this);
from.setTextSize(18);
from.setMaxLines(1);
from.setPadding(10, 10, 10, 10);
from.setText(R.string.from);
header.addView(from);

View File

@@ -8,7 +8,9 @@ public interface DatabaseConfig {
File getDatabaseDirectory();
char[] getPassword();
void setEncryptionKey(byte[] key);
byte[] getEncryptionKey();
long getMaxSize();
}

View File

@@ -65,7 +65,7 @@ class CryptoComponentImpl implements CryptoComponent {
private static final String STORAGE_CIPHER_ALGO = "AES/GCM/NoPadding";
private static final int STORAGE_IV_BYTES = 16; // 128 bits
private static final int PBKDF_SALT_BYTES = 16; // 128 bits
private static final int PBKDF_ITERATIONS = 10 * 1000; // FIXME: How many?
private static final int PBKDF_ITERATIONS = 1000;
private static final String KEY_DERIVATION_ALGO = "AES/CTR/NoPadding";
private static final int KEY_DERIVATION_IV_BYTES = 16; // 128 bits

View File

@@ -5,13 +5,13 @@ import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Properties;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.db.DatabaseConfig;
import net.sf.briar.api.db.DbException;
import net.sf.briar.util.FileUtils;
import net.sf.briar.util.StringUtils;
import com.google.inject.Inject;
@@ -23,23 +23,23 @@ class H2Database extends JdbcDatabase {
private static final String COUNTER_TYPE = "INT NOT NULL AUTO_INCREMENT";
private static final String SECRET_TYPE = "BINARY(32)";
private final File dir;
private final DatabaseConfig config;
private final String url;
private final char[] password;
private final long maxSize;
@Inject
H2Database(DatabaseConfig config, Clock clock) {
super(HASH_TYPE, BINARY_TYPE, COUNTER_TYPE, SECRET_TYPE, config, clock);
dir = config.getDatabaseDirectory();
url = "jdbc:h2:split:" + new File(dir, "db").getPath()
super(HASH_TYPE, BINARY_TYPE, COUNTER_TYPE, SECRET_TYPE, clock);
this.config = config;
String path = new File(config.getDatabaseDirectory(), "db").getPath();
url = "jdbc:h2:split:" + path
+ ";CIPHER=AES;MULTI_THREADED=1;DB_CLOSE_ON_EXIT=false";
password = config.getPassword();
maxSize = config.getMaxSize();
}
public boolean open() throws DbException, IOException {
return super.open("org.h2.Driver");
boolean reopen = config.databaseExists();
if(!reopen) config.getDatabaseDirectory().mkdirs();
super.open("org.h2.Driver", reopen);
return reopen;
}
public void close() throws DbException {
@@ -52,6 +52,8 @@ class H2Database extends JdbcDatabase {
}
public long getFreeSpace() throws DbException {
File dir = config.getDatabaseDirectory();
long maxSize = config.getMaxSize();
try {
long free = FileUtils.getFreeSpace(dir);
long used = getDiskSpace(dir);
@@ -73,14 +75,28 @@ class H2Database extends JdbcDatabase {
@Override
protected Connection createConnection() throws SQLException {
char[] passwordCopy = password.clone();
char[] password = encodePassword(config.getEncryptionKey());
Properties props = new Properties();
props.setProperty("user", "user");
props.put("password", passwordCopy);
props.put("password", password);
try {
return DriverManager.getConnection(url, props);
} finally {
Arrays.fill(passwordCopy, (char) 0);
for(int i = 0; i < password.length; i++) password[i] = 0;
}
}
private char[] encodePassword(byte[] key) {
// The database password is the hex-encoded key
char[] hex = StringUtils.toHexChars(key);
// Separate the database password from the user password with a space
char[] user = "password".toCharArray();
char[] combined = new char[hex.length + 1 + user.length];
System.arraycopy(hex, 0, combined, 0, hex.length);
combined[hex.length] = ' ';
System.arraycopy(user, 0, combined, hex.length + 1, user.length);
// Erase the hex-encoded key
for(int i = 0; i < hex.length; i++) hex[i] = 0;
return combined;
}
}

View File

@@ -34,7 +34,6 @@ import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportId;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.db.DatabaseConfig;
import net.sf.briar.api.db.DbClosedException;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.GroupMessageHeader;
@@ -360,7 +359,6 @@ abstract class JdbcDatabase implements Database<Connection> {
// Different database libraries use different names for certain types
private final String hashType, binaryType, counterType, secretType;
private final DatabaseConfig config;
private final Clock clock;
private final LinkedList<Connection> connections =
@@ -372,18 +370,16 @@ abstract class JdbcDatabase implements Database<Connection> {
protected abstract Connection createConnection() throws SQLException;
JdbcDatabase(String hashType, String binaryType, String counterType,
String secretType, DatabaseConfig config, Clock clock) {
String secretType, Clock clock) {
this.hashType = hashType;
this.binaryType = binaryType;
this.counterType = counterType;
this.secretType = secretType;
this.config = config;
this.clock = clock;
}
protected boolean open(String driverClass) throws DbException, IOException {
boolean reopen = config.databaseExists();
if(!reopen) config.getDatabaseDirectory().mkdirs();
protected void open(String driverClass, boolean reopen) throws DbException,
IOException {
// Load the JDBC driver
try {
Class.forName(driverClass);
@@ -399,7 +395,6 @@ abstract class JdbcDatabase implements Database<Connection> {
abortTransaction(txn);
throw e;
}
return reopen;
}
private void createTables(Connection txn) throws DbException {

View File

@@ -29,16 +29,19 @@ public class StringUtils {
else return s;
}
/** Converts the given byte array to a hex character array. */
public static char[] toHexChars(byte[] bytes) {
char[] hex = new char[bytes.length * 2];
for(int i = 0, j = 0; i < bytes.length; i++) {
hex[j++] = HEX[(bytes[i] >> 4) & 0xF];
hex[j++] = HEX[bytes[i] & 0xF];
}
return hex;
}
/** Converts the given byte array to a hex string. */
public static String toHexString(byte[] bytes) {
StringBuilder s = new StringBuilder(bytes.length * 2);
for(byte b : bytes) {
int high = (b >> 4) & 0xF;
s.append(HEX[high]);
int low = b & 0xF;
s.append(HEX[low]);
}
return s.toString();
return new String(toHexChars(bytes));
}
/** Converts the given hex string to a byte array. */

View File

@@ -8,6 +8,7 @@ public class TestDatabaseConfig implements DatabaseConfig {
private final File dir;
private final long maxSize;
private volatile byte[] key = new byte[] { 'f', 'o', 'o' };
public TestDatabaseConfig(File dir, long maxSize) {
this.dir = dir;
@@ -22,8 +23,12 @@ public class TestDatabaseConfig implements DatabaseConfig {
return dir;
}
public char[] getPassword() {
return "foo bar".toCharArray();
public void setEncryptionKey(byte[] key) {
this.key = key;
}
public byte[] getEncryptionKey() {
return key;
}
public long getMaxSize() {