diff --git a/briar-android/AndroidManifest.xml b/briar-android/AndroidManifest.xml index 6020c390ae7b5cfb696f052f4b240fb24ef1124e..c2bedce0776881464f6b5e45fc3fb0f46683a398 100644 --- a/briar-android/AndroidManifest.xml +++ b/briar-android/AndroidManifest.xml @@ -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" diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml index 3fda7ccda57bf9a2c69ef56971b4f9699595c82e..e76728dc08554dc0268a3744b9807b32567ea046 100644 --- a/briar-android/res/values/strings.xml +++ b/briar-android/res/values/strings.xml @@ -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> diff --git a/briar-android/src/net/sf/briar/android/HomeScreenActivity.java b/briar-android/src/net/sf/briar/android/HomeScreenActivity.java index 4f13b65fc2a371d2a7e80bc0468deb9dad3be32f..d71d29f33705a63870c8ac346f35ac5b6d5c6ef8 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 a612cc7b28818f0a85399a09fcd342a4429fad7c..a761e7b7f595fb4783d2d5b7c7417e3f0fca09b0 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 54d529c8c996cfbd18fd4317575cccbef6fc19f3..2c1e6000566a50e3ae1ab459ffde337530de21ee 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 e62dff274a37f50f31a6d4e3d91beb5ec80752aa..c3025a62d42f07fb56217f59bf93df0520d25c2b 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 028d0cccd93f5eb95dd134afbe7fbf1a5694d926..e6dab7ed0269620112a49091e63acde229cf8053 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 b95de675f116a8269d4b200eeb64b35e774f7ca5..1880ece374d10bfb3eccb3ebbd06ddb01af8476e 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 d91a2f95247d34be31d35baa3530498d3e4a128b..e0d9493ece11783b611919b27da536db7bc3b2ff 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 728a28e5f431de89aa11391b5eebb7113f6e49ba..866c4bdaa51fba5e42c4f92f28f7ee1f2d27db74 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 15f27b645e3afcf7838c99e53e6370dd401655c0..26f42f99d96d2ed8813c58ac94fabf0228fc0162 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 7526c3708da9fba24eece4402d02f33c9883c330..c846af33a55d6d2c39e4d60ffeb19f005373121e 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 18fe2bdd7638ce7db6b7d73b59b35d6e74d95030..5203f64830c1c01b05c1fc1b58f7aba9ce011d45 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 742cb6cdb14b89308192421be8543e977e449c51..d33c66950ed114611268b634c25b995e186e26cb 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<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 { diff --git a/briar-core/src/net/sf/briar/util/StringUtils.java b/briar-core/src/net/sf/briar/util/StringUtils.java index 0b3e674fd86046a29c7b8314c4565098b81077d7..a7d71ea8ac4655c7afa9bf0fa8b4db6fc26bd4f2 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 e21336822f4b724f5ff279ecc15de9a97700c614..1d01590cf4cf0b4b0560c99cb7c766a362017541 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() {