diff --git a/briar-android/AndroidManifest.xml b/briar-android/AndroidManifest.xml
index 6020c390a..c2bedce07 100644
--- a/briar-android/AndroidManifest.xml
+++ b/briar-android/AndroidManifest.xml
@@ -30,7 +30,7 @@
+ android:label="@string/setup_title" >
Briar
Briar is running
Touch to quit.
+ Briar Setup
+ Choose your nickname:
+ Choose your password:
+ Confirm your password:
+ Password must be at least %1$d characters long.
+ Enter your password:
+ Wrong password, try again
Contacts
Messages
Groups
@@ -29,14 +36,14 @@
Connecting via %1$s\u2026
Connecting via Bluetooth\u2026
Connection failed
- Please check that you are both using the same network.
+ Please check that you are both using the same network
Try again
Connected to contact
Your confirmation code is
Please enter your contact\'s confirmation code:
Waiting for contact\u2026
Codes do not match
- This could mean that someone is trying to interfere with your connection.
+ This could mean that someone is trying to interfere with your connection
Contact added
Done
Messages
@@ -67,7 +74,6 @@
New blog\u2026
New nickname\u2026
Create an Identity
- Choose your nickname:
Create
You don\'t have any contacts. Add a contact now?
Add
diff --git a/briar-android/src/net/sf/briar/android/HomeScreenActivity.java b/briar-android/src/net/sf/briar/android/HomeScreenActivity.java
index 4f13b65fc..d71d29f33 100644
--- a/briar-android/src/net/sf/briar/android/HomeScreenActivity.java
+++ b/briar-android/src/net/sf/briar/android/HomeScreenActivity.java
@@ -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);
}
}
diff --git a/briar-android/src/net/sf/briar/android/SetupActivity.java b/briar-android/src/net/sf/briar/android/SetupActivity.java
index a612cc7b2..a761e7b7f 100644
--- a/briar-android/src/net/sf/briar/android/SetupActivity.java
+++ b/briar-android/src/net/sf/briar/android/SetupActivity.java
@@ -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;
- }
}
diff --git a/briar-android/src/net/sf/briar/android/SplashScreenActivity.java b/briar-android/src/net/sf/briar/android/SplashScreenActivity.java
index 54d529c8c..2c1e60005 100644
--- a/briar-android/src/net/sf/briar/android/SplashScreenActivity.java
+++ b/briar-android/src/net/sf/briar/android/SplashScreenActivity.java
@@ -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);
diff --git a/briar-android/src/net/sf/briar/android/blogs/CreateBlogActivity.java b/briar-android/src/net/sf/briar/android/blogs/CreateBlogActivity.java
index e62dff274..c3025a62d 100644
--- a/briar-android/src/net/sf/briar/android/blogs/CreateBlogActivity.java
+++ b/briar-android/src/net/sf/briar/android/blogs/CreateBlogActivity.java
@@ -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);
diff --git a/briar-android/src/net/sf/briar/android/groups/CreateGroupActivity.java b/briar-android/src/net/sf/briar/android/groups/CreateGroupActivity.java
index 028d0cccd..e6dab7ed0 100644
--- a/briar-android/src/net/sf/briar/android/groups/CreateGroupActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/CreateGroupActivity.java
@@ -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);
diff --git a/briar-android/src/net/sf/briar/android/helloworld/HelloWorldModule.java b/briar-android/src/net/sf/briar/android/helloworld/HelloWorldModule.java
index b95de675f..1880ece37 100644
--- a/briar-android/src/net/sf/briar/android/helloworld/HelloWorldModule.java
+++ b/briar-android/src/net/sf/briar/android/helloworld/HelloWorldModule.java
@@ -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() {
diff --git a/briar-android/src/net/sf/briar/android/identity/CreateIdentityActivity.java b/briar-android/src/net/sf/briar/android/identity/CreateIdentityActivity.java
index d91a2f952..e0d9493ec 100644
--- a/briar-android/src/net/sf/briar/android/identity/CreateIdentityActivity.java
+++ b/briar-android/src/net/sf/briar/android/identity/CreateIdentityActivity.java
@@ -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);
diff --git a/briar-android/src/net/sf/briar/android/messages/WritePrivateMessageActivity.java b/briar-android/src/net/sf/briar/android/messages/WritePrivateMessageActivity.java
index 728a28e5f..866c4bdaa 100644
--- a/briar-android/src/net/sf/briar/android/messages/WritePrivateMessageActivity.java
+++ b/briar-android/src/net/sf/briar/android/messages/WritePrivateMessageActivity.java
@@ -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);
diff --git a/briar-api/src/net/sf/briar/api/db/DatabaseConfig.java b/briar-api/src/net/sf/briar/api/db/DatabaseConfig.java
index 15f27b645..26f42f99d 100644
--- a/briar-api/src/net/sf/briar/api/db/DatabaseConfig.java
+++ b/briar-api/src/net/sf/briar/api/db/DatabaseConfig.java
@@ -8,7 +8,9 @@ public interface DatabaseConfig {
File getDatabaseDirectory();
- char[] getPassword();
+ void setEncryptionKey(byte[] key);
+
+ byte[] getEncryptionKey();
long getMaxSize();
}
diff --git a/briar-core/src/net/sf/briar/crypto/CryptoComponentImpl.java b/briar-core/src/net/sf/briar/crypto/CryptoComponentImpl.java
index 7526c3708..c846af33a 100644
--- a/briar-core/src/net/sf/briar/crypto/CryptoComponentImpl.java
+++ b/briar-core/src/net/sf/briar/crypto/CryptoComponentImpl.java
@@ -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
diff --git a/briar-core/src/net/sf/briar/db/H2Database.java b/briar-core/src/net/sf/briar/db/H2Database.java
index 18fe2bdd7..5203f6483 100644
--- a/briar-core/src/net/sf/briar/db/H2Database.java
+++ b/briar-core/src/net/sf/briar/db/H2Database.java
@@ -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;
+ }
}
diff --git a/briar-core/src/net/sf/briar/db/JdbcDatabase.java b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
index 742cb6cdb..d33c66950 100644
--- a/briar-core/src/net/sf/briar/db/JdbcDatabase.java
+++ b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
@@ -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 {
// 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 connections =
@@ -372,18 +370,16 @@ abstract class JdbcDatabase implements Database {
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 {
abortTransaction(txn);
throw e;
}
- return reopen;
}
private void createTables(Connection txn) throws DbException {
diff --git a/briar-core/src/net/sf/briar/util/StringUtils.java b/briar-core/src/net/sf/briar/util/StringUtils.java
index 0b3e674fd..a7d71ea8a 100644
--- a/briar-core/src/net/sf/briar/util/StringUtils.java
+++ b/briar-core/src/net/sf/briar/util/StringUtils.java
@@ -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. */
diff --git a/briar-tests/src/net/sf/briar/TestDatabaseConfig.java b/briar-tests/src/net/sf/briar/TestDatabaseConfig.java
index e21336822..1d01590cf 100644
--- a/briar-tests/src/net/sf/briar/TestDatabaseConfig.java
+++ b/briar-tests/src/net/sf/briar/TestDatabaseConfig.java
@@ -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() {