You should backup your database of passwords from time to time. To do this
- use Menu -> Backup. This will create a file named 'passwordsafe.xml' on
+ use Menu -> Backup. This will create a file named 'oisafe.xml' on
the sdcard. Connect your phone to a computer and allow the USB mounting.
- Look for 'passwordsafe.xml' and copy that to a safe location on your computer.
+ Look for 'oisafe.xml' and copy that to a safe location on your computer.
The file is encrypted using the same encryption as the database on your phone.
Import and Export
@@ -94,8 +94,8 @@ body {
put some data into the OI Safe and perform an export first, so
that you can see what the CSV should look like.
-
Use the name 'passwordsafe.csv' for your filename. Connect the
- phone to your computer and copy 'passwordsafe.csv' onto the phone.
+
Use the name 'oisafe.csv' for your filename. Connect the
+ phone to your computer and copy 'oisafe.csv' onto the phone.
The file must be in the base directory of the phone's USB storage.
Once the CSV file is in place, use Menu -> Import to import the file.
@@ -104,7 +104,7 @@ body {
the same time.
Exporting is a snap. Just use Menu -> Export. This will create the
- file 'passwordsafe.csv'. Connect your phone to a computer to retrieve
+ file 'oisafe.csv'. Connect your phone to a computer to retrieve
this file.
WARNING: The exported file is not encrypted!!
After exporting and working with the file, you should delete it
@@ -128,15 +128,14 @@ body {
Any of the following actions will cause OI Safe to lock:
+
The auto lock timer expires. This may be set to 1, 5 or 30 minutes.
+ The default is 5 minutes.
The screen turns off. This is a default behavior of the phone and is controlled
in the phone's Settings -> Sound & display -> Screen timeout.
The default timeout is 1 minute.
After 1 minute the phone will go to sleep. When this happens, OI Safe will
lock itself.
You select Lock from the Menu on the Categories screen.
-
Android only allows six active applications. Once you launch the seventh,
- the oldest used application will be killed. If OI Safe is killed, than
- the next time you launch it, it will be locked.
Really, the easiest way to lock is to just hit the power button on the phone briefly and
diff --git a/res/layout-land/front_door.xml b/res/layout-land/front_door.xml
index 173f049..6d3ef86 100644
--- a/res/layout-land/front_door.xml
+++ b/res/layout-land/front_door.xml
@@ -88,4 +88,11 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
+
\ No newline at end of file
diff --git a/res/layout/front_door.xml b/res/layout/front_door.xml
index 233390b..09d78c6 100644
--- a/res/layout/front_door.xml
+++ b/res/layout/front_door.xml
@@ -77,4 +77,9 @@
android:text="@string/continue_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
+
\ No newline at end of file
diff --git a/res/layout/pass_view.xml b/res/layout/pass_view.xml
index 0302e41..d05799f 100644
--- a/res/layout/pass_view.xml
+++ b/res/layout/pass_view.xml
@@ -97,6 +97,16 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/last_edited" />
+
+
+
+
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index ac188d5..61abaf4 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -117,19 +117,17 @@
OpenDatabase Version ErrorSorry, but the version of the database is not supported. You must install the version associated with this database.
- access stored password
- Allows the application to encrypt and decrypt text, and access the passwords they have stored in OI Safe.MoveSelect CategoryMoved toPreferencesAllow external access
- Allow other applications to secure your data with OI Safe?
+ Allow other applications to secure your data with OI SafeOI Safe: Another application requests access.Allow other applications to secure your data with OI Safe.This can also be set in OI Safe\'s Menu / Preferences.Auto lock timeout
- Minutes before timeout occurs and safe is locked.
+ Minutes before timeout occurs and safe is lockedAuto lock timeoutDecrypt (OI Safe)Encrypt (OI Safe)
@@ -151,4 +149,10 @@
Entry savedLast editedunknown
+ Crypto error:
+ Unique name
+ Package access
+ not found
+ New master key
+ A new random master key has been created. Use menu > backup and store this key in a safe place. Without this key you may loose encrypted data.
diff --git a/res/values/strings_permissions.xml b/res/values/strings_permissions.xml
new file mode 100644
index 0000000..9939767
--- /dev/null
+++ b/res/values/strings_permissions.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+ access the master password
+ Allows the application to access the master key used for encryption and decryption. Should never be used except by OI Safe.
+ access stored password
+ Allows the application to encrypt and decrypt text, and access the passwords they have stored in OI Safe.
+
diff --git a/src/org/openintents/safe/AskPassword.java b/src/org/openintents/safe/AskPassword.java
index 315f05a..f5a4ca9 100644
--- a/src/org/openintents/safe/AskPassword.java
+++ b/src/org/openintents/safe/AskPassword.java
@@ -16,7 +16,11 @@
*/
package org.openintents.safe;
+import java.io.File;
+import java.security.NoSuchAlgorithmException;
+
import org.openintents.distribution.EulaActivity;
+import org.openintents.safe.dialog.DialogHostingActivity;
import org.openintents.util.VersionUtils;
import android.app.Activity;
@@ -49,6 +53,8 @@ public class AskPassword extends Activity {
private static String TAG = "AskPassword";
public static String EXTRA_IS_LOCAL = "org.openintents.safe.bundle.EXTRA_IS_REMOTE";
+ public static final int REQUEST_RESTORE = 0;
+
private EditText pbeKey;
private DBHelper dbHelper;
private TextView introText;
@@ -56,6 +62,7 @@ public class AskPassword extends Activity {
private TextView remoteAsk;
private EditText confirmPass;
private String PBEKey;
+ private String salt;
private String masterKey;
private CryptoHelper ch;
private boolean firstTime = false;
@@ -78,7 +85,7 @@ public class AskPassword extends Activity {
dbHelper = new DBHelper(this);
- ch = new CryptoHelper(CryptoHelper.EncryptionStrong);
+ ch = new CryptoHelper();
if (dbHelper.needsUpgrade()) {
switch (dbHelper.fetchVersion()) {
case 2:
@@ -101,12 +108,14 @@ public class AskPassword extends Activity {
remoteAsk = (TextView) findViewById(R.id.remote);
confirmPass = (EditText) findViewById(R.id.pass_confirm);
confirmText = (TextView) findViewById(R.id.confirm_lbl);
+ salt = dbHelper.fetchSalt();
masterKey = dbHelper.fetchMasterKey();
if (masterKey.length() == 0) {
firstTime = true;
introText.setVisibility(View.VISIBLE);
confirmText.setVisibility(View.VISIBLE);
confirmPass.setVisibility(View.VISIBLE);
+ checkForBackup();
}
if (! isLocal) {
if (remoteAsk != null) {
@@ -122,7 +131,6 @@ public class AskPassword extends Activity {
// For this version of CryptoHelper, we use the user-entered password.
// All other versions should be instantiated with the generated master
// password.
- ch.setPassword(PBEKey);
// Password must be at least 4 characters
if (PBEKey.length() < 4) {
@@ -150,15 +158,29 @@ public class AskPassword extends Activity {
.show();
return;
}
- masterKey = CryptoHelper.generateMasterKey();
+ try {
+ salt = CryptoHelper.generateSalt();
+ masterKey = CryptoHelper.generateMasterKey();
+ } catch (NoSuchAlgorithmException e1) {
+ e1.printStackTrace();
+ Toast.makeText(AskPassword.this,getString(R.string.crypto_error)
+ + e1.getMessage(), Toast.LENGTH_SHORT).show();
+ return;
+ }
if (debug) Log.i(TAG, "Saving Password: " + masterKey);
try {
+ ch.init(CryptoHelper.EncryptionStrong,salt);
+ ch.setPassword(PBEKey);
String encryptedMasterKey = ch.encrypt(masterKey);
+ dbHelper.storeSalt(salt);
dbHelper.storeMasterKey(encryptedMasterKey);
} catch (CryptoHelperException e) {
Log.e(TAG, e.toString());
+ Toast.makeText(AskPassword.this,getString(R.string.crypto_error)
+ + e.getMessage(), Toast.LENGTH_SHORT).show();
+ return;
}
- } else if (!checkUserPassword()) {
+ } else if (!checkUserPassword(PBEKey)) {
// Check the user's password and display a
// message if it's wrong
Toast.makeText(AskPassword.this, R.string.invalid_password,
@@ -175,9 +197,35 @@ public class AskPassword extends Activity {
// Return the master key to our caller. We no longer need the
// user-entered PBEKey. The master key is used for everything
// from here on out.
+ if (debug) Log.d(TAG,"calbackintent: masterKey="+masterKey+" salt="+salt);
callbackIntent.putExtra("masterKey", masterKey);
+ callbackIntent.putExtra("salt", salt);
setResult(RESULT_OK, callbackIntent);
+
finish();
+
+ }
+ });
+ }
+
+ private void checkForBackup() {
+ String filename=CategoryList.BACKUP_FILENAME;
+ File restoreFile=new File(filename);
+ if (!restoreFile.exists()) {
+ return;
+ }
+ Button restoreButton = (Button) findViewById(R.id.restore_button);
+ if (restoreButton == null) {
+ if (debug) Log.d(TAG, "layout not created yet");
+ return;
+ }
+ restoreButton.setVisibility(View.VISIBLE);
+ restoreButton.setOnClickListener(new View.OnClickListener() {
+
+ public void onClick(View arg0) {
+ Intent restore = new Intent(AskPassword.this, Restore.class);
+ restore.putExtra(Restore.KEY_FIRST_TIME, true);
+ startActivityForResult(restore,REQUEST_RESTORE);
}
});
}
@@ -224,11 +272,15 @@ public class AskPassword extends Activity {
*
* @return
*/
- private boolean checkUserPassword() {
+ private boolean checkUserPassword(String password) {
String encryptedMasterKey = dbHelper.fetchMasterKey();
String decryptedMasterKey = "";
+ if (debug) Log.d(TAG,"checkUserPassword: encryptedMasterKey="+encryptedMasterKey);
try {
+ ch.init(CryptoHelper.EncryptionStrong,salt);
+ ch.setPassword(password);
decryptedMasterKey = ch.decrypt(encryptedMasterKey);
+ if (debug) Log.d(TAG,"decryptedMasterKey="+decryptedMasterKey);
} catch (CryptoHelperException e) {
Log.e(TAG, e.toString());
}
@@ -239,4 +291,19 @@ public class AskPassword extends Activity {
masterKey=null;
return false;
}
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent i) {
+ super.onActivityResult(requestCode, resultCode, i);
+
+ if ((requestCode== REQUEST_RESTORE) && (resultCode == RESULT_OK)) {
+ Log.d(TAG,"returning masterkey: "+CategoryList.getMasterKey());
+ Intent callbackIntent = new Intent();
+ callbackIntent.putExtra("salt", CategoryList.getSalt());
+ callbackIntent.putExtra("masterKey", CategoryList.getMasterKey());
+ setResult(RESULT_OK, callbackIntent);
+ finish();
+ }
+ }
+
}
diff --git a/src/org/openintents/safe/Backup.java b/src/org/openintents/safe/Backup.java
index 31a9e62..febfb0a 100644
--- a/src/org/openintents/safe/Backup.java
+++ b/src/org/openintents/safe/Backup.java
@@ -72,7 +72,12 @@ public class Backup {
serializer.startTag(null, "MasterKey");
serializer.text(masterKeyEncrypted);
serializer.endTag(null, "MasterKey");
-
+
+ String salt = dbHelper.fetchSalt();
+ serializer.startTag(null, "Salt");
+ serializer.text(salt);
+ serializer.endTag(null, "Salt");
+
List crows;
crows = dbHelper.fetchAllCategoryRows();
HashMap> packageAccess=dbHelper.fetchPackageAccessAll();
diff --git a/src/org/openintents/safe/CategoryEdit.java b/src/org/openintents/safe/CategoryEdit.java
index f387999..9bb38af 100644
--- a/src/org/openintents/safe/CategoryEdit.java
+++ b/src/org/openintents/safe/CategoryEdit.java
@@ -32,19 +32,28 @@ import android.widget.Toast;
*/
public class CategoryEdit extends Activity {
+ private static final boolean debug = false;
+ private static String TAG = "CategoryEdit";
+
private EditText nameText;
private Long RowId;
private DBHelper dbHelper=null;
private CryptoHelper ch;
- private static String TAG = "CategoryEdit";
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
- Log.d(TAG, "onCreate");
+ if (debug) Log.d(TAG, "onCreate");
ch = new CryptoHelper();
- ch.setPassword(PassList.getMasterKey());
+ try {
+ ch.init(CryptoHelper.EncryptionMedium,PassList.getSalt());
+ ch.setPassword(PassList.getMasterKey());
+ } catch (CryptoHelperException e1) {
+ e1.printStackTrace();
+ Toast.makeText(this,getString(R.string.crypto_error)
+ + e1.getMessage(), Toast.LENGTH_SHORT).show();
+ }
if (dbHelper == null){
dbHelper = new DBHelper(this);
@@ -98,7 +107,7 @@ public class CategoryEdit extends Activity {
@Override
protected void onPause() {
super.onPause();
- Log.d(TAG, "onPause");
+ if (debug) Log.d(TAG, "onPause");
dbHelper.close();
dbHelper = null;
}
@@ -106,7 +115,7 @@ public class CategoryEdit extends Activity {
@Override
protected void onResume() {
super.onResume();
- Log.d(TAG, "onResume");
+ if (debug) Log.d(TAG, "onResume");
if (dbHelper == null) {
dbHelper = new DBHelper(this);
}
@@ -119,11 +128,11 @@ public class CategoryEdit extends Activity {
}
private void saveState() {
- Log.d(TAG, "saveState");
+ if (debug) Log.d(TAG, "saveState");
CategoryEntry entry = new CategoryEntry();
String namePlain = nameText.getText().toString();
- Log.d(TAG, "name: " + namePlain);
+ if (debug) Log.d(TAG, "name: " + namePlain);
try {
entry.name = ch.encrypt(namePlain);
@@ -133,11 +142,11 @@ public class CategoryEdit extends Activity {
if(RowId == null || RowId == -1) {
- Log.d(TAG, "addCategory");
+ if (debug) Log.d(TAG, "addCategory");
dbHelper.addCategory(entry);
} else {
- Log.d(TAG, "updateCategory");
- Log.d(TAG, "RowId: " + String.valueOf(RowId));
+ if (debug) Log.d(TAG, "updateCategory");
+ if (debug) Log.d(TAG, "RowId: " + String.valueOf(RowId));
dbHelper.updateCategory(RowId, entry);
}
}
@@ -146,7 +155,7 @@ public class CategoryEdit extends Activity {
*
*/
private void populateFields() {
- Log.d(TAG, "populateFields");
+ if (debug) Log.d(TAG, "populateFields");
if (RowId != null) {
CategoryEntry row = dbHelper.fetchCategory(RowId);
if (row.id > -1) {
diff --git a/src/org/openintents/safe/CategoryList.java b/src/org/openintents/safe/CategoryList.java
index eb2c17a..85095e7 100644
--- a/src/org/openintents/safe/CategoryList.java
+++ b/src/org/openintents/safe/CategoryList.java
@@ -31,6 +31,7 @@ import java.util.Set;
import org.openintents.distribution.AboutDialog;
import org.openintents.intents.AboutMiniIntents;
import org.openintents.intents.CryptoIntents;
+import org.openintents.safe.dialog.DialogHostingActivity;
import org.openintents.safe.service.ServiceDispatchImpl;
import org.openintents.util.IntentUtils;
@@ -44,9 +45,11 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.preference.PreferenceManager;
import android.util.Log;
import android.view.ContextMenu;
import android.view.Menu;
@@ -117,6 +120,7 @@ public class CategoryList extends ListActivity {
private Thread backupThread=null;
+ private static String salt;
private static String masterKey;
private List rows;
@@ -241,7 +245,27 @@ public class CategoryList extends ListActivity {
startActivity(frontdoor);
finish();
}
+
+ showFirstTimeWarningDialog();
}
+
+ /**
+ *
+ */
+ private void showFirstTimeWarningDialog() {
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
+ boolean firstTimeWarning = sp.getBoolean(Preferences.PREFERENCE_FIRST_TIME_WARNING, false);
+
+ if (!firstTimeWarning) {
+ Intent i = new Intent(this, DialogHostingActivity.class);
+ i.putExtra(DialogHostingActivity.EXTRA_DIALOG_ID, DialogHostingActivity.DIALOG_ID_FIRST_TIME_WARNING);
+ startActivity(i);
+
+ SharedPreferences.Editor editor = sp.edit();
+ editor.putBoolean(Preferences.PREFERENCE_FIRST_TIME_WARNING, true);
+ editor.commit();
+ }
+ }
@Override
protected void onPause() {
@@ -360,7 +384,15 @@ public class CategoryList extends ListActivity {
if(masterKey == null) {
masterKey = "";
}
- ch.setPassword(masterKey);
+ try {
+ ch.init(CryptoHelper.EncryptionMedium,salt);
+ ch.setPassword(masterKey);
+ } catch (CryptoHelperException e1) {
+ e1.printStackTrace();
+ Toast.makeText(this,getString(R.string.crypto_error)
+ + e1.getMessage(), Toast.LENGTH_SHORT).show();
+ return;
+ }
List items = new ArrayList();
if (dbHelper==null) {
@@ -449,6 +481,14 @@ public class CategoryList extends ListActivity {
return super.onCreateOptionsMenu(menu);
}
+ static void setSalt(String saltIn) {
+ salt = saltIn;
+ }
+
+ static String getSalt() {
+ return salt;
+ }
+
static void setMasterKey(String key) {
masterKey = key;
}
@@ -641,11 +681,14 @@ public class CategoryList extends ListActivity {
if(masterKey == null) {
masterKey = "";
}
+ ch.init(CryptoHelper.EncryptionMedium,salt);
ch.setPassword(masterKey);
entry.name = ch.encrypt(namePlain);
} catch(CryptoHelperException e) {
Log.e(TAG,e.toString());
+ Toast.makeText(this,getString(R.string.crypto_error)
+ + e.getMessage(), Toast.LENGTH_SHORT).show();
}
return dbHelper.addCategory(entry);
}
@@ -668,7 +711,15 @@ public class CategoryList extends ListActivity {
if(masterKey == null) {
masterKey = "";
}
- ch.setPassword(masterKey);
+ try {
+ ch.init(CryptoHelper.EncryptionMedium,salt);
+ ch.setPassword(masterKey);
+ } catch (CryptoHelperException e1) {
+ e1.printStackTrace();
+ Toast.makeText(this,getString(R.string.crypto_error)
+ + e1.getMessage(), Toast.LENGTH_SHORT).show();
+ return false;
+ }
HashMap categories = new HashMap();
@@ -964,6 +1015,7 @@ public class CategoryList extends ListActivity {
Log.e(TAG,e.toString());
continue;
}
+ entry.id=0;
dbHelper.addPassword(entry);
newEntries++;
}
@@ -994,7 +1046,13 @@ public class CategoryList extends ListActivity {
if(masterKey == null) {
masterKey = "";
}
- ch.setPassword(masterKey);
+ try {
+ ch.init(CryptoHelper.EncryptionMedium,salt);
+ ch.setPassword(masterKey);
+ } catch (CryptoHelperException e1) {
+ e1.printStackTrace();
+ return null;
+ }
HashMap categories = new HashMap();
List rows;
diff --git a/src/org/openintents/safe/ChangePass.java b/src/org/openintents/safe/ChangePass.java
index b64dca6..0e06de3 100644
--- a/src/org/openintents/safe/ChangePass.java
+++ b/src/org/openintents/safe/ChangePass.java
@@ -191,11 +191,12 @@ public class ChangePass extends Activity {
DBHelper dbHelper= new DBHelper(this);
- CryptoHelper ch = new CryptoHelper(CryptoHelper.EncryptionStrong);
+ CryptoHelper ch = new CryptoHelper();
String encryptedMasterKey = dbHelper.fetchMasterKey();
String decryptedMasterKey = "";
try {
+ ch.init(CryptoHelper.EncryptionStrong, dbHelper.fetchSalt());
ch.setPassword(oldPass);
decryptedMasterKey = ch.decrypt(encryptedMasterKey);
if (ch.getStatus()==true) { // successful decryption?
@@ -214,6 +215,8 @@ public class ChangePass extends Activity {
} catch (CryptoHelperException e) {
Log.e(TAG, e.toString());
+ Toast.makeText(this,getString(R.string.crypto_error)
+ + e.getMessage(), Toast.LENGTH_SHORT).show();
}
dbHelper.close();
@@ -255,8 +258,8 @@ public class ChangePass extends Activity {
row.plainName = ch.decrypt(row.name);
} catch (CryptoHelperException e) {
if (debug) Log.e(TAG,e.toString());
- Toast.makeText(ChangePass.this, e.toString(),
- Toast.LENGTH_SHORT).show();
+ Toast.makeText(this,getString(R.string.crypto_error)
+ + e.getMessage(), Toast.LENGTH_SHORT).show();
return;
}
}
@@ -270,8 +273,8 @@ public class ChangePass extends Activity {
row.plainNote = ch.decrypt(row.note);
} catch (CryptoHelperException e) {
if (debug) Log.e(TAG,e.toString());
- Toast.makeText(ChangePass.this, e.toString(),
- Toast.LENGTH_SHORT).show();
+ Toast.makeText(this,getString(R.string.crypto_error)
+ + e.getMessage(), Toast.LENGTH_SHORT).show();
return;
}
}
@@ -287,8 +290,8 @@ public class ChangePass extends Activity {
row.name = ch.encrypt(row.plainName);
} catch (CryptoHelperException e) {
if (debug) Log.e(TAG,e.toString());
- Toast.makeText(ChangePass.this, e.toString(),
- Toast.LENGTH_SHORT).show();
+ Toast.makeText(this,getString(R.string.crypto_error)
+ + e.getMessage(), Toast.LENGTH_SHORT).show();
return;
}
}
@@ -302,8 +305,8 @@ public class ChangePass extends Activity {
row.note = ch.encrypt(row.plainNote);
} catch (CryptoHelperException e) {
if (debug) Log.e(TAG,e.toString());
- Toast.makeText(ChangePass.this, e.toString(),
- Toast.LENGTH_SHORT).show();
+ Toast.makeText(this,getString(R.string.crypto_error)
+ + e.getMessage(), Toast.LENGTH_SHORT).show();
return;
}
}
@@ -331,8 +334,8 @@ public class ChangePass extends Activity {
dbHelper.storeMasterKey(cryptKey);
} catch (CryptoHelperException e) {
Log.e(TAG, e.toString());
- Toast.makeText(ChangePass.this, e.toString(),
- Toast.LENGTH_SHORT).show();
+ Toast.makeText(this,getString(R.string.crypto_error)
+ + e.getMessage(), Toast.LENGTH_SHORT).show();
dbHelper.rollback();
dbHelper.close();
return;
@@ -359,10 +362,11 @@ public class ChangePass extends Activity {
DBHelper dbHelper= new DBHelper(this);
String confirmKey = dbHelper.fetchMasterKey();
- CryptoHelper ch = new CryptoHelper(CryptoHelper.EncryptionStrong);
- ch.setPassword(pass);
+ CryptoHelper ch = new CryptoHelper();
try {
+ ch.init(CryptoHelper.EncryptionStrong, dbHelper.fetchSalt());
+ ch.setPassword(pass);
ch.decrypt(confirmKey);
} catch (CryptoHelperException e) {
Log.e(TAG, e.toString());
diff --git a/src/org/openintents/safe/CryptoHelper.java b/src/org/openintents/safe/CryptoHelper.java
index 3da0d18..802e665 100644
--- a/src/org/openintents/safe/CryptoHelper.java
+++ b/src/org/openintents/safe/CryptoHelper.java
@@ -24,6 +24,7 @@ import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import javax.crypto.BadPaddingException;
@@ -36,8 +37,6 @@ import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
-//import org.bouncycastle.jce.provider.BouncyCastleProvider;
-
import android.util.Log;
/**
@@ -53,6 +52,7 @@ import android.util.Log;
*/
public class CryptoHelper {
+ private static final boolean debug = true;
private static String TAG = "CryptoHelper";
protected static PBEKeySpec pbeKeySpec;
protected static PBEParameterSpec pbeParamSpec;
@@ -71,10 +71,7 @@ public class CryptoHelper {
protected static Cipher pbeCipher;
private boolean status=false; // status of the last encrypt/decrypt
- private static final byte[] salt = {
- (byte)0xfc, (byte)0x76, (byte)0x80, (byte)0xae,
- (byte)0xfd, (byte)0x82, (byte)0xbe, (byte)0xee,
- };
+ private static byte[] salt = null;
private static final int count = 20;
@@ -82,15 +79,22 @@ public class CryptoHelper {
* Constructor which defaults to a medium encryption level.
*/
public CryptoHelper() {
- initialize(EncryptionMedium);
+// initialize(EncryptionMedium);
}
/**
* Constructor which allows the specification of the encryption level.
*
- * @param Strength encryption strength
+ * @param strength encryption strength
+ * @param salt salt to be used
*/
- public CryptoHelper(int Strength) {
- initialize(Strength);
+ public void init(int strength, String salt) throws CryptoHelperException {
+ try {
+ setSalt(salt);
+ initialize(strength);
+ } catch (CryptoHelperException e) {
+ e.printStackTrace();
+ throw e;
+ }
}
/**
* Initialize the class. Sets the encryption level for the instance
@@ -117,12 +121,32 @@ public class CryptoHelper {
Log.e(TAG,"CryptoHelper(): "+e.toString());
}
}
+
+ /**
+ * Generate a random salt for use with the cipher.
+ *
+ * @author Randy McEoin
+ * @return String version of the 8 byte salt
+ */
+ public static String generateSalt() throws NoSuchAlgorithmException {
+ byte[] salt = new byte[8];
+ SecureRandom sr;
+ try {
+ sr = SecureRandom.getInstance("SHA1PRNG");
+ sr.nextBytes(salt);
+ if (debug) Log.d(TAG,"generateSalt: salt="+salt.toString());
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ throw e;
+ }
+ return toHexString(salt);
+ }
/**
* @author Isaac Potoczny-Jones
*
* @return null if failure, otherwise hex string version of key
*/
- public static String generateMasterKey () {
+ public static String generateMasterKey () throws NoSuchAlgorithmException {
try {
KeyGenerator keygen;
keygen = KeyGenerator.getInstance("AES");
@@ -131,8 +155,8 @@ public class CryptoHelper {
return toHexString(genDesKey.getEncoded());
} catch (NoSuchAlgorithmException e) {
Log.e(TAG,"generateMasterKey(): "+e.toString());
+ throw e;
}
- return null; //error case.
}
/**
@@ -218,6 +242,7 @@ public class CryptoHelper {
* @throws Exception
*/
public void setPassword(String pass) {
+ if (debug) Log.d(TAG,"setPassword("+pass+")");
password = pass;
pbeKeySpec = new PBEKeySpec(password.toCharArray());
try {
@@ -235,6 +260,20 @@ public class CryptoHelper {
}
}
+ private void setSalt(String saltIn) throws CryptoHelperException {
+ if (saltIn==null) {
+ String msg = "Salt must not be null.";
+ throw new CryptoHelperException(msg);
+ }
+ byte[] byteSaltIn=hexStringToBytes(saltIn);
+
+ if (byteSaltIn.length != 8) {
+ String msg = "Salt must be 8 bytes in length.";
+ throw new CryptoHelperException(msg);
+ }
+ salt=byteSaltIn;
+ if (debug) Log.d(TAG,"setSalt: salt="+toHexString(salt));
+ }
/**
* encrypt a string
*
@@ -245,7 +284,11 @@ public class CryptoHelper {
public String encrypt(String plaintext) throws CryptoHelperException {
status=false; // assume failure
if(password == null) {
- String msg = "Must call setPassword before runing encrypt.";
+ String msg = "Must call setPassword before running encrypt.";
+ throw new CryptoHelperException(msg);
+ }
+ if (salt==null) {
+ String msg = "Must call setSalt before running encrypt.";
throw new CryptoHelperException(msg);
}
byte[] ciphertext = {};
@@ -281,6 +324,10 @@ public class CryptoHelper {
String msg = "Must call setPassword before running decrypt.";
throw new CryptoHelperException(msg);
}
+ if (salt==null) {
+ String msg = "Must call setSalt before running decrypt.";
+ throw new CryptoHelperException(msg);
+ }
if ((ciphertext==null) || (ciphertext=="")) {
return "";
@@ -313,4 +360,179 @@ public class CryptoHelper {
public boolean getStatus() {
return status;
}
+
+
+ /**
+ * encrypt a string using a random session key
+ *
+ * @author Peli
+ *
+ * @param plaintext
+ * @return encrypted String
+ * @throws Exception
+ */
+ public String encryptWithSessionKey(String plaintext) throws CryptoHelperException {
+ Log.i(TAG, "Encrypt with session key");
+ status=false; // assume failure
+ if(password == null) {
+ String msg = "Must call setPassword before runing encrypt.";
+ throw new CryptoHelperException(msg);
+ }
+ byte[] cipherSessionKey = {};
+ byte[] ciphertext = {};
+
+ // First create a session key
+ SecretKey sessionKey = null;
+ byte[] sessionKeyEncoded = null;
+ String sessionKeyString = null;
+ try {
+ KeyGenerator keygen;
+ keygen = KeyGenerator.getInstance("AES");
+ keygen.init(256); // needs 96 bytes
+ //keygen.init(128); // needs 64 bytes
+ sessionKey = keygen.generateKey();
+ sessionKeyEncoded = sessionKey.getEncoded();
+ sessionKeyString = new String(sessionKeyEncoded);
+ } catch (NoSuchAlgorithmException e) {
+ Log.e(TAG,"generateMasterKey(): "+e.toString());
+ }
+
+ // Convert this to a Pbe key
+ PBEKeySpec sessionPbeKeySpec = new PBEKeySpec(sessionKeyString.toCharArray());
+ SecretKey sessionPbeKey = null;
+ try {
+ sessionPbeKey = keyFac.generateSecret(sessionPbeKeySpec);
+ } catch (InvalidKeySpecException e) {
+ Log.e(TAG,"setPassword(): "+e.toString());
+ }
+
+ // Encrypt the session key using the master key
+ try {
+ pbeCipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);
+ cipherSessionKey = pbeCipher.doFinal(sessionKeyEncoded);
+ } catch (IllegalBlockSizeException e) {
+ Log.e(TAG,"encryptWithSessionKey(): "+e.toString());
+ } catch (BadPaddingException e) {
+ Log.e(TAG,"encryptWithSessionKey(): "+e.toString());
+ } catch (InvalidKeyException e) {
+ Log.e(TAG,"encryptWithSessionKey(): "+e.toString());
+ } catch (InvalidAlgorithmParameterException e) {
+ Log.e(TAG,"encryptWithSessionKey(): "+e.toString());
+ }
+
+ // Now encrypt the text using the session key
+ try {
+ pbeCipher.init(Cipher.ENCRYPT_MODE, sessionPbeKey, pbeParamSpec);
+ ciphertext = pbeCipher.doFinal(plaintext.getBytes());
+ status=true;
+ } catch (IllegalBlockSizeException e) {
+ Log.e(TAG,"encryptWithSessionKey2(): "+e.toString());
+ } catch (BadPaddingException e) {
+ Log.e(TAG,"encryptWithSessionKey2(): "+e.toString());
+ } catch (InvalidKeyException e) {
+ Log.e(TAG,"encryptWithSessionKey2(): "+e.toString());
+ } catch (InvalidAlgorithmParameterException e) {
+ Log.e(TAG,"encryptWithSessionKey2(): "+e.toString());
+ }
+
+ String stringCipherVersion = "A";
+ String stringCipherSessionKey = toHexString(cipherSessionKey);
+ String stringCiphertext=toHexString(ciphertext);
+ Log.i(TAG, "Length: " + stringCipherSessionKey.length() + ", " + stringCipherSessionKey);
+
+ StringBuilder sb = new StringBuilder(stringCipherVersion.length()
+ + stringCipherSessionKey.length()
+ + stringCiphertext.length());
+ sb.append(stringCipherVersion);
+ sb.append(stringCipherSessionKey);
+ sb.append(stringCiphertext);
+ return sb.toString();
+ }
+
+ /**
+ * unencrypt encrypted string previously encrypted with
+ * encryptWithSessionKey()
+ *
+ * @author Peli
+ *
+ * @param ciphertext
+ * @return decrypted String
+ * @throws Exception
+ */
+ public String decryptWithSessionKey(String ciphertext) throws CryptoHelperException {
+ status=false; // assume failure
+ if(password == null) {
+ String msg = "Must call setPassword before running decrypt.";
+ throw new CryptoHelperException(msg);
+ }
+
+ if ((ciphertext==null) || (ciphertext=="")) {
+ return "";
+ }
+ String cipherVersion = null;
+ String cipherSessionKey = null;
+
+ // Split cipher into session key and text
+ try {
+ cipherVersion = ciphertext.substring(0,1);
+ if (cipherVersion.equals("A")) {
+ cipherSessionKey = ciphertext.substring(1,97); // 64 if init(128) had been chosen
+ ciphertext = ciphertext.substring(97);
+ } else {
+ Log.e(TAG, "Unknown cipher version" + cipherVersion);
+ return "";
+ }
+ } catch (IndexOutOfBoundsException e) {
+ Log.e(TAG, "Invalid ciphertext (with session key)");
+ return "";
+ }
+
+ // Decrypt the session key
+ byte[] byteCipherSessionKey=hexStringToBytes(cipherSessionKey);
+ byte[] byteSessionKey = {};
+
+ try {
+ pbeCipher.init(Cipher.DECRYPT_MODE, pbeKey, pbeParamSpec);
+ byteSessionKey = pbeCipher.doFinal(byteCipherSessionKey);
+ status=true;
+ } catch (IllegalBlockSizeException e) {
+ Log.e(TAG,"decrypt(): "+e.toString());
+ } catch (BadPaddingException e) {
+ Log.e(TAG,"decrypt(): "+e.toString());
+ } catch (InvalidKeyException e) {
+ Log.e(TAG,"decrypt(): "+e.toString());
+ } catch (InvalidAlgorithmParameterException e) {
+ Log.e(TAG,"decrypt(): "+e.toString());
+ }
+
+ // Convert the session key into a Pbe key
+ String stringSessionKey = new String(byteSessionKey);
+ PBEKeySpec sessionPbeKeySpec = new PBEKeySpec(stringSessionKey.toCharArray());
+ SecretKey sessionPbeKey = null;
+ try {
+ sessionPbeKey = keyFac.generateSecret(sessionPbeKeySpec);
+ } catch (InvalidKeySpecException e) {
+ Log.e(TAG,"setPassword(): "+e.toString());
+ }
+
+ // Use the session key to decrypt the text
+ byte[] byteCiphertext=hexStringToBytes(ciphertext);
+ byte[] plaintext = {};
+
+ try {
+ pbeCipher.init(Cipher.DECRYPT_MODE, sessionPbeKey, pbeParamSpec);
+ plaintext = pbeCipher.doFinal(byteCiphertext);
+ status=true;
+ } catch (IllegalBlockSizeException e) {
+ Log.e(TAG,"decrypt(): "+e.toString());
+ } catch (BadPaddingException e) {
+ Log.e(TAG,"decrypt(): "+e.toString());
+ } catch (InvalidKeyException e) {
+ Log.e(TAG,"decrypt(): "+e.toString());
+ } catch (InvalidAlgorithmParameterException e) {
+ Log.e(TAG,"decrypt(): "+e.toString());
+ }
+
+ return new String(plaintext);
+ }
}
diff --git a/src/org/openintents/safe/DBHelper.java b/src/org/openintents/safe/DBHelper.java
index 1815310..276e833 100644
--- a/src/org/openintents/safe/DBHelper.java
+++ b/src/org/openintents/safe/DBHelper.java
@@ -44,6 +44,7 @@ public class DBHelper {
private static final String TABLE_PASSWORDS = "passwords";
private static final String TABLE_CATEGORIES = "categories";
private static final String TABLE_MASTER_KEY = "master_key";
+ private static final String TABLE_SALT = "salt";
private static final String TABLE_PACKAGE_ACCESS = "package_access";
private static final int DATABASE_VERSION = 4;
private static String TAG = "DBHelper";
@@ -89,6 +90,10 @@ public class DBHelper {
"create table " + TABLE_MASTER_KEY + " ("
+ "encryptedkey text not null);";
+ private static final String SALT_CREATE =
+ "create table " + TABLE_SALT + " ("
+ + "salt text not null);";
+
private SQLiteDatabase db;
private static boolean needsPrePopulation=false;
private static boolean needsUpgrade=false;
@@ -148,6 +153,7 @@ public class DBHelper {
db.execSQL(PASSWORDS_CREATE);
db.execSQL(PACKAGE_ACCESS_CREATE);
db.execSQL(MASTER_KEY_CREATE);
+ db.execSQL(SALT_CREATE);
} catch (SQLException e)
{
Log.d(TAG,"SQLite exception: " + e.getLocalizedMessage());
@@ -214,6 +220,49 @@ public class DBHelper {
}
return version;
}
+
+////////// Salt Functions ////////////////
+
+ /**
+ * Store the salt
+ *
+ * @return String version of salt
+ */
+ public String fetchSalt() {
+ String salt="";
+ try {
+ Cursor c = db.query(true, TABLE_SALT, new String[] {"salt"},
+ null, null, null, null, null,null);
+ if(c.getCount() > 0) {
+ c.moveToFirst();
+ salt=c.getString(0);
+ }
+ c.close();
+ } catch (SQLException e)
+ {
+ Log.d(TAG,"SQLite exception: " + e.getLocalizedMessage());
+ }
+ return salt;
+ }
+
+ /**
+ * Store the salt into the database.
+ *
+ * @param salt String version of the salt
+ */
+ public void storeSalt(String salt) {
+ ContentValues args = new ContentValues();
+ try {
+ db.delete(TABLE_SALT, "1=1", null);
+ args.put("salt", salt);
+ db.insert(TABLE_SALT, null, args);
+ } catch (SQLException e)
+ {
+ Log.d(TAG,"SQLite exception: " + e.getLocalizedMessage());
+ }
+ }
+
+////////// Master Key Functions ////////////////
/**
*
@@ -634,12 +683,19 @@ public class DBHelper {
}
/**
+ * Add a password entry to the database.
+ * PassEntry.id should be set to 0, unless a specific
+ * row id is desired.
*
- * @param entry
+ * @param entry PassEntry
+ * @return long row id of newly added entry
*/
public long addPassword(PassEntry entry) {
long id = -1;
ContentValues initialValues = new ContentValues();
+ if (entry.id!=0) {
+ initialValues.put("id", entry.id);
+ }
initialValues.put("category", entry.category);
initialValues.put("password", entry.password);
initialValues.put("description", entry.description);
diff --git a/src/org/openintents/safe/FrontDoor.java b/src/org/openintents/safe/FrontDoor.java
index c247177..58f69f0 100644
--- a/src/org/openintents/safe/FrontDoor.java
+++ b/src/org/openintents/safe/FrontDoor.java
@@ -1,4 +1,4 @@
-/* $Id$
+/* $Id: FrontDoor.java 1805 2009-01-20 04:05:01Z rmceoin $
*
* Copyright 2007-2008 Steven Osborn
*
@@ -16,449 +16,33 @@
*/
package org.openintents.safe;
-
-import java.util.ArrayList;
-
-import org.openintents.intents.CryptoIntents;
-import org.openintents.safe.dialog.DialogHostingActivity;
-import org.openintents.safe.service.ServiceDispatch;
-import org.openintents.safe.service.ServiceDispatchImpl;
-
import android.app.Activity;
-import android.content.ComponentName;
-import android.content.Context;
import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.SharedPreferences;
import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.preference.PreferenceManager;
-import android.util.Log;
-import android.widget.Toast;
/**
* FrontDoor Activity
*
- * This activity just acts as a splash screen and gets the password from the
- * user that will be used to decrypt/encrypt password entries.
+ * This activity just makes sure we're entering the front door, not calling encrypt/decrypt
+ * or other intents.
*
* @author Steven Osborn - http://steven.bitsetters.com
*/
public class FrontDoor extends Activity {
- private static final boolean debug = false;
- private static String TAG = "FrontDoor";
-
- private static final int REQUEST_CODE_ASK_PASSWORD = 1;
- private static final int REQUEST_CODE_ALLOW_EXTERNAL_ACCESS = 2;
-
-
- private DBHelper dbHelper;
- private String masterKey;
- private CryptoHelper ch;
-
- // service elements
- private ServiceDispatch service;
- private ServiceDispatchConnection conn;
-
- SharedPreferences mPreferences;
- //public static String SERVICE_NAME = "org.openintents.safe.service.ServiceDispatchImpl";
-
/** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
- initService(); // start up the PWS service so other applications can query.
- }
-
- //currently only handles result from askPassword function.
- protected void onActivityResult (int requestCode, int resultCode, Intent data) {
- if (resultCode == RESULT_OK) {
- switch (requestCode) {
- case REQUEST_CODE_ASK_PASSWORD:
- masterKey = data.getStringExtra("masterKey");
- String timeout = mPreferences.getString(Preferences.PREFERENCE_LOCK_TIMEOUT, Preferences.PREFERENCE_LOCK_TIMEOUT_DEFAULT_VALUE);
- int timeoutMinutes=5; // default to 5
- try {
- timeoutMinutes = Integer.valueOf(timeout);
- } catch (NumberFormatException e) {
- Log.d(TAG,"why is lock_timeout busted?");
- }
- try {
- service.setTimeoutMinutes(timeoutMinutes);
- service.setPassword(masterKey); // should already be connected.
- } catch (RemoteException e1) {
- // TODO Auto-generated catch block
- e1.printStackTrace();
- }
-
- boolean externalAccess = mPreferences.getBoolean(Preferences.PREFERENCE_ALLOW_EXTERNAL_ACCESS, false);
- boolean isLocal = isIntentLocal();
-
- if (isLocal || externalAccess) {
- actionDispatch();
- } else {
- // ask first
- showDialogAllowExternalAccess();
- }
- break;
- case REQUEST_CODE_ALLOW_EXTERNAL_ACCESS:
-
- actionDispatch();
- break;
- }
-
- } else { // resultCode == RESULT_CANCELED
- setResult(RESULT_CANCELED);
- finish();
- }
- }
-
- /**
- *
- */
- private void showDialogAllowExternalAccess() {
- Intent i = new Intent(this, DialogHostingActivity.class);
- i.putExtra(DialogHostingActivity.EXTRA_DIALOG_ID, DialogHostingActivity.DIALOG_ID_ALLOW_EXTERNAL_ACCESS);
- this.startActivityForResult(i, REQUEST_CODE_ALLOW_EXTERNAL_ACCESS);
- }
-
- protected void actionDispatch () {
- final Intent thisIntent = getIntent();
- final String action = thisIntent.getAction();
- Intent callbackIntent = getIntent();
- int callbackResult = RESULT_CANCELED;
- PassList.setMasterKey(masterKey);
- CategoryList.setMasterKey(masterKey);
- if (ch == null) {
- ch = new CryptoHelper(CryptoHelper.EncryptionMedium);
- ch.setPassword(masterKey);
- }
-
- boolean externalAccess = mPreferences.getBoolean(Preferences.PREFERENCE_ALLOW_EXTERNAL_ACCESS, false);
-
- if (action == null || action.equals(Intent.ACTION_MAIN)){
- //TODO: When launched from debugger, action is null. Other such cases?
- Intent i = new Intent(getApplicationContext(),
- CategoryList.class);
- startActivity(i);
- } else if (externalAccess){
-
- // which action?
- if (action.equals (CryptoIntents.ACTION_ENCRYPT)) {
- callbackResult = encryptIntent(thisIntent, callbackIntent);
- } else if (action.equals (CryptoIntents.ACTION_DECRYPT)) {
- callbackResult = decryptIntent(thisIntent, callbackIntent);
- } else if (action.equals (CryptoIntents.ACTION_GET_PASSWORD)
- || action.equals (CryptoIntents.ACTION_SET_PASSWORD)) {
- try {
- callbackIntent = getSetPassword (thisIntent, callbackIntent);
- callbackResult = RESULT_OK;
- } catch (CryptoHelperException e) {
- Log.e(TAG, e.toString(), e);
- Toast.makeText(FrontDoor.this,
- "There was a crypto error while retreiving the requested password: " + e.getMessage(),
- Toast.LENGTH_SHORT).show();
- } catch (Exception e) {
- Log.e(TAG, e.toString(), e);
- //TODO: Turn this into a proper error dialog.
- Toast.makeText(FrontDoor.this,
- "There was an error in retreiving the requested password: " + e.getMessage(),
- Toast.LENGTH_SHORT).show();
- }
- }
- setResult(callbackResult, callbackIntent);
- }
- finish();
- }
-
-
- /**
- * Encrypt all supported fields in the intent and return the result in callbackIntent.
- *
- * @param thisIntent
- * @param callbackIntent
- * @return callbackResult
- */
- private int encryptIntent(final Intent thisIntent, Intent callbackIntent) {
- if (debug)
- Log.d(TAG, "encryptIntent()");
-
- int callbackResult = RESULT_CANCELED;
- try {
- if (thisIntent.hasExtra(CryptoIntents.EXTRA_TEXT)) {
- // get the body text out of the extras.
- String inputBody = thisIntent.getStringExtra (CryptoIntents.EXTRA_TEXT);
- String outputBody = "";
- outputBody = ch.encrypt (inputBody);
- // stash the encrypted text in the extra
- callbackIntent.putExtra(CryptoIntents.EXTRA_TEXT, outputBody);
- }
-
- if (thisIntent.hasExtra(CryptoIntents.EXTRA_TEXT_ARRAY)) {
- String[] in = thisIntent.getStringArrayExtra(CryptoIntents.EXTRA_TEXT_ARRAY);
- String[] out = new String[in.length];
- for (int i = 0; i < in.length; i++) {
- if (in[i] != null) {
- out[i] = ch.encrypt(in[i]);
- }
- }
- callbackIntent.putExtra(CryptoIntents.EXTRA_TEXT_ARRAY, out);
- }
-
- // Support for binary fields could be added here (like images?)
-
- callbackResult = RESULT_OK;
- } catch (CryptoHelperException e) {
- Log.e(TAG, e.toString());
- }
- return callbackResult;
- }
-
- /**
- * Decrypt all supported fields in the intent and return the result in callbackIntent.
- *
- * @param thisIntent
- * @param callbackIntent
- * @return callbackResult
- */
- private int decryptIntent(final Intent thisIntent, Intent callbackIntent) {
- int callbackResult = RESULT_CANCELED;
- try {
-
- if (thisIntent.hasExtra(CryptoIntents.EXTRA_TEXT)) {
- // get the body text out of the extras.
- String inputBody = thisIntent.getStringExtra (CryptoIntents.EXTRA_TEXT);
- String outputBody = "";
- outputBody = ch.decrypt (inputBody);
- // stash the encrypted text in the extra
- callbackIntent.putExtra(CryptoIntents.EXTRA_TEXT, outputBody);
- }
-
- if (thisIntent.hasExtra(CryptoIntents.EXTRA_TEXT_ARRAY)) {
- String[] in = thisIntent.getStringArrayExtra(CryptoIntents.EXTRA_TEXT_ARRAY);
- String[] out = new String[in.length];
- for (int i = 0; i < in.length; i++) {
- if (in[i] != null) {
- out[i] = ch.decrypt(in[i]);
- }
- }
- callbackIntent.putExtra(CryptoIntents.EXTRA_TEXT_ARRAY, out);
- }
-
- // Support for binary fields could be added here (like images?)
-
- callbackResult = RESULT_OK;
- } catch (CryptoHelperException e) {
- Log.e(TAG, e.toString());
- }
- return callbackResult;
- }
-
- private Intent getSetPassword (Intent thisIntent, Intent callbackIntent) throws CryptoHelperException, Exception {
- String action = thisIntent.getAction();
- //TODO: Consider moving this elsewhere. Maybe DBHelper? Also move strings to resource.
- //DBHelper dbHelper = new DBHelper(this);
- Log.d(TAG, "GET_or_SET_PASSWORD");
- String username = null;
- String password = null;
-
- String clearUniqueName = thisIntent.getStringExtra (CryptoIntents.EXTRA_UNIQUE_NAME);
-
- if (clearUniqueName == null) throw new Exception ("EXTRA_UNIQUE_NAME not set.");
-
- if (dbHelper == null) {
- // Need to open DBHelper here, because
- // onResume() is called after onActivityResult()
- dbHelper = new DBHelper(this);
- }
-
- String uniqueName = ch.encrypt(clearUniqueName);
- PassEntry row = dbHelper.fetchPassword(uniqueName);
- boolean passExists = row.id > 1;
-
- String clearCallingPackage = getCallingPackage();
- String callingPackage = ch.encrypt (clearCallingPackage);
- if (passExists) { // check for permission to access this password.
- ArrayList packageAccess = dbHelper.fetchPackageAccess(row.id);
- if (! PassEntry.checkPackageAccess(packageAccess, callingPackage)) {
- throw new Exception ("It is currently not permissible for this application to request this password.");
- }
- /*TODO: check if this package is in the package_access table corresponding to this password:
- * "Application 'org.syntaxpolice.ServiceTest' wants to access the
- password for 'opensocial'.
- [ ] Grant access this time.
- [ ] Always grant access.
- [ ] Always grant access to all passwords in org.syntaxpolice.ServiceTest category?
- [ ] Don't grant access"
- */
- }
-
- if (action.equals (CryptoIntents.ACTION_GET_PASSWORD)) {
- if (passExists) {
- username = ch.decrypt(row.username);
- password = ch.decrypt(row.password);
- } else throw new Exception ("Could not find password with the unique name: " + clearUniqueName);
-
- // stashing the return values:
- callbackIntent.putExtra(CryptoIntents.EXTRA_USERNAME, username);
- callbackIntent.putExtra(CryptoIntents.EXTRA_PASSWORD, password);
- } else if (action.equals (CryptoIntents.ACTION_SET_PASSWORD)) {
- String clearUsername = thisIntent.getStringExtra (CryptoIntents.EXTRA_USERNAME);
- String clearPassword = thisIntent.getStringExtra (CryptoIntents.EXTRA_PASSWORD);
- if (clearPassword == null) {
- throw new Exception ("PASSWORD extra must be set.");
- }
- row.username = ch.encrypt(clearUsername == null ? "" : clearUsername);
- row.password = ch.encrypt(clearPassword);
- // since this package is setting the password, it automatically gets access to it:
- if (passExists) { //exists already
- if (clearUsername.equals("") && clearPassword.equals("")) {
- dbHelper.deletePassword(row.id);
- } else {
- dbHelper.updatePassword(row.id, row);
- }
- } else {// add a new one
- row.uniqueName = uniqueName;
- row.description=uniqueName; //for display purposes
- // TODO: Should we send these fields in extras also? If so, probably not using
- // the openintents namespace? If another application were to implement a keystore
- // they might not want to use these.
- row.website = "";
- row.note = "";
-
- String category = ch.encrypt("Application Data");
- CategoryEntry c = new CategoryEntry();
- c.name = category;
- row.category = dbHelper.addCategory(c); //doesn't add category if it already exists
- row.id = dbHelper.addPassword(row);
- }
- dbHelper.addPackageAccess(row.id, callingPackage);//already encrypted
-
- }
- return (callbackIntent);
- }
-
- @Override
- protected void onPause() {
- super.onPause();
-
- if (debug)
- Log.d(TAG, "onPause()");
-
- releaseService();
- dbHelper.close();
- dbHelper = null;
- }
-
- @Override
- protected void onResume() {
- super.onPause();
-
- if (debug)
- Log.d(TAG, "onResume()");
- if (dbHelper == null) {
- dbHelper = new DBHelper(this);
- }
-
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- releaseService();
- }
-
-
- //--------------------------- service stuff ------------
- private void initService() {
-
- boolean isLocal = isIntentLocal();
- conn = new ServiceDispatchConnection(isLocal);
- Intent i = new Intent();
- i.setClass(this, ServiceDispatchImpl.class);
- startService(i);
- bindService( i, conn, Context.BIND_AUTO_CREATE);
- }
-
- /**
- * @return
- */
- private boolean isIntentLocal() {
- String action = getIntent().getAction();
- boolean isLocal = action == null || action.equals(Intent.ACTION_MAIN);
- return isLocal;
- }
-
- private void releaseService() {
- if (conn != null ) {
- unbindService( conn );
- conn = null;
- }
- }
-
- class ServiceDispatchConnection implements ServiceConnection
- {
- boolean askPassIsLocal = false;
- public ServiceDispatchConnection (Boolean isLocal) {
- askPassIsLocal = isLocal;
- }
- public void onServiceConnected(ComponentName className,
- IBinder boundService )
- {
- service = ServiceDispatch.Stub.asInterface((IBinder)boundService);
-
- boolean promptforpassword = getIntent().getBooleanExtra(CryptoIntents.EXTRA_PROMPT, true);
- if (debug) Log.d(TAG, "Prompt for password: " + promptforpassword);
- try {
- if (service.getPassword() == null) {
- if (promptforpassword) {
- if (debug) Log.d(TAG, "ask for password");
- // the service isn't running
- Intent askPass = new Intent(getApplicationContext(),
- AskPassword.class);
-
- final Intent thisIntent = getIntent();
- String inputBody = thisIntent.getStringExtra (CryptoIntents.EXTRA_TEXT);
-
- askPass.putExtra (CryptoIntents.EXTRA_TEXT, inputBody);
- askPass.putExtra (AskPassword.EXTRA_IS_LOCAL, askPassIsLocal);
- //TODO: Is there a way to make sure all the extras are set?
- startActivityForResult (askPass, REQUEST_CODE_ASK_PASSWORD);
- } else {
- if (debug) Log.d(TAG, "ask for password");
- // Don't prompt but cancel
- setResult(RESULT_CANCELED);
- finish();
- }
-
- } else {
- if (debug) Log.d(TAG, "service already started");
- //service already started, so don't need to ask pw.
-
- boolean externalAccess = mPreferences.getBoolean(Preferences.PREFERENCE_ALLOW_EXTERNAL_ACCESS, false);
-
- if (askPassIsLocal || externalAccess) {
- masterKey = service.getPassword();
- actionDispatch();
- } else {
- showDialogAllowExternalAccess();
- }
- }
- } catch (RemoteException e) {
- Log.d(TAG, e.toString());
- }
- Log.d( TAG,"onServiceConnected" );
- }
-
- public void onServiceDisconnected(ComponentName className)
- {
- service = null;
- Log.d( TAG,"onServiceDisconnected" );
- }
- };
-
-}
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ final Intent thisIntent = getIntent();
+ final String action = thisIntent.getAction();
+ if (action == null || action.equals(Intent.ACTION_MAIN)){
+ //TODO: When launched from debugger, action is null. Other such cases?
+ Intent i = new Intent(getApplicationContext(),
+ IntentHandler.class);
+ startActivity(i);
+ } // otherwise, do not start intents, those must be protected by permissions
+ finish();
+ }
+}
\ No newline at end of file
diff --git a/src/org/openintents/safe/IntentHandler.java b/src/org/openintents/safe/IntentHandler.java
new file mode 100644
index 0000000..092b65e
--- /dev/null
+++ b/src/org/openintents/safe/IntentHandler.java
@@ -0,0 +1,531 @@
+/* $Id$
+ *
+ * Copyright 2007-2008 Steven Osborn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.openintents.safe;
+
+
+import java.util.ArrayList;
+
+import org.openintents.intents.CryptoIntents;
+import org.openintents.safe.dialog.DialogHostingActivity;
+import org.openintents.safe.service.ServiceDispatch;
+import org.openintents.safe.service.ServiceDispatchImpl;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import android.widget.Toast;
+
+
+/**
+ * FrontDoor Activity
+ *
+ * This activity just acts as a splash screen and gets the password from the
+ * user that will be used to decrypt/encrypt password entries.
+ *
+ * @author Steven Osborn - http://steven.bitsetters.com
+ */
+public class IntentHandler extends Activity {
+
+ private static final boolean debug = !false;
+ private static String TAG = "IntentHandler";
+
+ private static final int REQUEST_CODE_ASK_PASSWORD = 1;
+ private static final int REQUEST_CODE_ALLOW_EXTERNAL_ACCESS = 2;
+
+
+ private DBHelper dbHelper;
+ private String salt;
+ private String masterKey;
+ private CryptoHelper ch;
+
+ // service elements
+ private ServiceDispatch service;
+ private ServiceDispatchConnection conn;
+ private Intent mServiceIntent;
+
+ SharedPreferences mPreferences;
+ //public static String SERVICE_NAME = "org.openintents.safe.service.ServiceDispatchImpl";
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ mServiceIntent = null;
+ mPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+ initService(); // start up the PWS service so other applications can query.
+ }
+
+
+ //currently only handles result from askPassword function.
+ protected void onActivityResult (int requestCode, int resultCode, Intent data) {
+ if (debug) Log.d(TAG, "onActivityResult: requestCode == " + requestCode + ", resultCode == " + resultCode);
+
+ switch (requestCode) {
+ case REQUEST_CODE_ASK_PASSWORD:
+ if (resultCode == RESULT_OK) {
+
+ if (service == null) {
+ mServiceIntent = data;
+ // setServiceParametersFromExtrasAndDispatchAction() is called in onServiceConnected.
+ return;
+ }
+
+ setServiceParametersFromExtrasAndDispatchAction(data);
+
+ } else { // resultCode == RESULT_CANCELED
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ break;
+ case REQUEST_CODE_ALLOW_EXTERNAL_ACCESS:
+ // Check again, regardless whether user pressed "OK" or "Cancel".
+ // Also, DialogHostingActivity never returns a resultCode different than
+ // RESULT_CANCELED.
+ if (service == null) {
+ if (debug) Log.i(TAG, "actionDispatch called later");
+ // actionDispatch() is called in onServiceConnected.
+ } else if (salt == null) {
+ try {
+ salt = service.getSalt();
+ masterKey = service.getPassword();
+ actionDispatch();
+ } catch (RemoteException e) {
+ Log.d(TAG, e.toString());
+ // Not successful...
+ finish();
+ }
+ } else {
+ if (debug) Log.i(TAG, "actionDispatch called right now");
+ actionDispatch();
+ }
+ break;
+ }
+
+ }
+
+ /**
+ * @param data
+ */
+ private void setServiceParametersFromExtrasAndDispatchAction(Intent data) {
+ salt = data.getStringExtra("salt");
+ masterKey = data.getStringExtra("masterKey");
+ String timeout = mPreferences.getString(Preferences.PREFERENCE_LOCK_TIMEOUT, Preferences.PREFERENCE_LOCK_TIMEOUT_DEFAULT_VALUE);
+ int timeoutMinutes=5; // default to 5
+ try {
+ timeoutMinutes = Integer.valueOf(timeout);
+ } catch (NumberFormatException e) {
+ Log.d(TAG,"why is lock_timeout busted?");
+ }
+
+ try {
+ service.setTimeoutMinutes(timeoutMinutes);
+ service.setSalt(salt);
+ service.setPassword(masterKey); // should already be connected.
+ } catch (RemoteException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+
+ boolean externalAccess = mPreferences.getBoolean(Preferences.PREFERENCE_ALLOW_EXTERNAL_ACCESS, false);
+ boolean isLocal = isIntentLocal();
+
+ if (isLocal || externalAccess) {
+ actionDispatch();
+ } else {
+ // ask first
+ if (debug) Log.d(TAG, "onActivityResult: showDialogAllowExternalAccess()");
+ showDialogAllowExternalAccess();
+ }
+ }
+
+ /**
+ *
+ */
+ private void showDialogAllowExternalAccess() {
+ Intent i = new Intent(this, DialogHostingActivity.class);
+ i.putExtra(DialogHostingActivity.EXTRA_DIALOG_ID, DialogHostingActivity.DIALOG_ID_ALLOW_EXTERNAL_ACCESS);
+ this.startActivityForResult(i, REQUEST_CODE_ALLOW_EXTERNAL_ACCESS);
+ }
+
+ protected void actionDispatch () {
+ final Intent thisIntent = getIntent();
+ final String action = thisIntent.getAction();
+ Intent callbackIntent = getIntent();
+ int callbackResult = RESULT_CANCELED;
+ PassList.setSalt(salt);
+ CategoryList.setSalt(salt);
+ PassList.setMasterKey(masterKey);
+ CategoryList.setMasterKey(masterKey);
+ if (ch == null) {
+ ch = new CryptoHelper();
+ }
+ try {
+ ch.init(CryptoHelper.EncryptionMedium,salt);
+ ch.setPassword(masterKey);
+ } catch (CryptoHelperException e1) {
+ e1.printStackTrace();
+ Toast.makeText(this, getString(R.string.crypto_error)
+ + e1.getMessage(), Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ boolean externalAccess = mPreferences.getBoolean(Preferences.PREFERENCE_ALLOW_EXTERNAL_ACCESS, false);
+
+ if (action == null || action.equals(Intent.ACTION_MAIN)){
+ //TODO: When launched from debugger, action is null. Other such cases?
+ Intent i = new Intent(getApplicationContext(),
+ CategoryList.class);
+ startActivity(i);
+ } else if (externalAccess){
+
+ // which action?
+ if (action.equals (CryptoIntents.ACTION_ENCRYPT)) {
+ callbackResult = encryptIntent(thisIntent, callbackIntent);
+ } else if (action.equals (CryptoIntents.ACTION_DECRYPT)) {
+ callbackResult = decryptIntent(thisIntent, callbackIntent);
+ } else if (action.equals (CryptoIntents.ACTION_GET_PASSWORD)
+ || action.equals (CryptoIntents.ACTION_SET_PASSWORD)) {
+ try {
+ callbackIntent = getSetPassword (thisIntent, callbackIntent);
+ callbackResult = RESULT_OK;
+ } catch (CryptoHelperException e) {
+ Log.e(TAG, e.toString(), e);
+ Toast.makeText(IntentHandler.this,
+ "There was a crypto error while retreiving the requested password: " + e.getMessage(),
+ Toast.LENGTH_SHORT).show();
+ } catch (Exception e) {
+ Log.e(TAG, e.toString(), e);
+ //TODO: Turn this into a proper error dialog.
+ Toast.makeText(IntentHandler.this,
+ "There was an error in retreiving the requested password: " + e.getMessage(),
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+ setResult(callbackResult, callbackIntent);
+ }
+ finish();
+ }
+
+
+ /**
+ * Encrypt all supported fields in the intent and return the result in callbackIntent.
+ *
+ * This is supposed to be called by outside functions, so we encrypt using a random session key.
+ *
+ * @param thisIntent
+ * @param callbackIntent
+ * @return callbackResult
+ */
+ private int encryptIntent(final Intent thisIntent, Intent callbackIntent) {
+ if (debug)
+ Log.d(TAG, "encryptIntent()");
+
+ int callbackResult = RESULT_CANCELED;
+ try {
+ if (thisIntent.hasExtra(CryptoIntents.EXTRA_TEXT)) {
+ // get the body text out of the extras.
+ String inputBody = thisIntent.getStringExtra (CryptoIntents.EXTRA_TEXT);
+ String outputBody = "";
+ outputBody = ch.encryptWithSessionKey (inputBody);
+ // stash the encrypted text in the extra
+ callbackIntent.putExtra(CryptoIntents.EXTRA_TEXT, outputBody);
+ }
+
+ if (thisIntent.hasExtra(CryptoIntents.EXTRA_TEXT_ARRAY)) {
+ String[] in = thisIntent.getStringArrayExtra(CryptoIntents.EXTRA_TEXT_ARRAY);
+ String[] out = new String[in.length];
+ for (int i = 0; i < in.length; i++) {
+ if (in[i] != null) {
+ out[i] = ch.encryptWithSessionKey(in[i]);
+ }
+ }
+ callbackIntent.putExtra(CryptoIntents.EXTRA_TEXT_ARRAY, out);
+ }
+
+ // Support for binary fields could be added here (like images?)
+
+ callbackResult = RESULT_OK;
+ } catch (CryptoHelperException e) {
+ Log.e(TAG, e.toString());
+ }
+ return callbackResult;
+ }
+
+ /**
+ * Decrypt all supported fields in the intent and return the result in callbackIntent.
+ *
+ * @param thisIntent
+ * @param callbackIntent
+ * @return callbackResult
+ */
+ private int decryptIntent(final Intent thisIntent, Intent callbackIntent) {
+ int callbackResult = RESULT_CANCELED;
+ try {
+
+ if (thisIntent.hasExtra(CryptoIntents.EXTRA_TEXT)) {
+ // get the body text out of the extras.
+ String inputBody = thisIntent.getStringExtra (CryptoIntents.EXTRA_TEXT);
+ String outputBody = "";
+ outputBody = ch.decryptWithSessionKey (inputBody);
+ // stash the encrypted text in the extra
+ callbackIntent.putExtra(CryptoIntents.EXTRA_TEXT, outputBody);
+ }
+
+ if (thisIntent.hasExtra(CryptoIntents.EXTRA_TEXT_ARRAY)) {
+ String[] in = thisIntent.getStringArrayExtra(CryptoIntents.EXTRA_TEXT_ARRAY);
+ String[] out = new String[in.length];
+ for (int i = 0; i < in.length; i++) {
+ if (in[i] != null) {
+ out[i] = ch.decryptWithSessionKey(in[i]);
+ }
+ }
+ callbackIntent.putExtra(CryptoIntents.EXTRA_TEXT_ARRAY, out);
+ }
+
+ // Support for binary fields could be added here (like images?)
+
+ callbackResult = RESULT_OK;
+ } catch (CryptoHelperException e) {
+ Log.e(TAG, e.toString());
+ }
+ return callbackResult;
+ }
+
+ private Intent getSetPassword (Intent thisIntent, Intent callbackIntent) throws CryptoHelperException, Exception {
+ String action = thisIntent.getAction();
+ //TODO: Consider moving this elsewhere. Maybe DBHelper? Also move strings to resource.
+ //DBHelper dbHelper = new DBHelper(this);
+ if (debug) Log.d(TAG, "GET_or_SET_PASSWORD");
+ String username = null;
+ String password = null;
+
+ String clearUniqueName = thisIntent.getStringExtra (CryptoIntents.EXTRA_UNIQUE_NAME);
+
+ if (clearUniqueName == null) throw new Exception ("EXTRA_UNIQUE_NAME not set.");
+
+ if (dbHelper == null) {
+ // Need to open DBHelper here, because
+ // onResume() is called after onActivityResult()
+ dbHelper = new DBHelper(this);
+ }
+
+ String uniqueName = ch.encrypt(clearUniqueName);
+ PassEntry row = dbHelper.fetchPassword(uniqueName);
+ boolean passExists = row.id > 1;
+
+ String clearCallingPackage = getCallingPackage();
+ String callingPackage = ch.encrypt (clearCallingPackage);
+ if (passExists) { // check for permission to access this password.
+ ArrayList packageAccess = dbHelper.fetchPackageAccess(row.id);
+ if (! PassEntry.checkPackageAccess(packageAccess, callingPackage)) {
+ throw new Exception ("It is currently not permissible for this application to request this password.");
+ }
+ /*TODO: check if this package is in the package_access table corresponding to this password:
+ * "Application 'org.syntaxpolice.ServiceTest' wants to access the
+ password for 'opensocial'.
+ [ ] Grant access this time.
+ [ ] Always grant access.
+ [ ] Always grant access to all passwords in org.syntaxpolice.ServiceTest category?
+ [ ] Don't grant access"
+ */
+ }
+
+ if (action.equals (CryptoIntents.ACTION_GET_PASSWORD)) {
+ if (passExists) {
+ username = ch.decrypt(row.username);
+ password = ch.decrypt(row.password);
+ } else throw new Exception ("Could not find password with the unique name: " + clearUniqueName);
+
+ // stashing the return values:
+ callbackIntent.putExtra(CryptoIntents.EXTRA_USERNAME, username);
+ callbackIntent.putExtra(CryptoIntents.EXTRA_PASSWORD, password);
+ } else if (action.equals (CryptoIntents.ACTION_SET_PASSWORD)) {
+ String clearUsername = thisIntent.getStringExtra (CryptoIntents.EXTRA_USERNAME);
+ String clearPassword = thisIntent.getStringExtra (CryptoIntents.EXTRA_PASSWORD);
+ if (clearPassword == null) {
+ throw new Exception ("PASSWORD extra must be set.");
+ }
+ row.username = ch.encrypt(clearUsername == null ? "" : clearUsername);
+ row.password = ch.encrypt(clearPassword);
+ // since this package is setting the password, it automatically gets access to it:
+ if (passExists) { //exists already
+ if (clearUsername.equals("") && clearPassword.equals("")) {
+ dbHelper.deletePassword(row.id);
+ } else {
+ dbHelper.updatePassword(row.id, row);
+ }
+ } else {// add a new one
+ row.uniqueName = uniqueName;
+ row.description=uniqueName; //for display purposes
+ // TODO: Should we send these fields in extras also? If so, probably not using
+ // the openintents namespace? If another application were to implement a keystore
+ // they might not want to use these.
+ row.website = "";
+ row.note = "";
+
+ String category = ch.encrypt("Application Data");
+ CategoryEntry c = new CategoryEntry();
+ c.name = category;
+ row.category = dbHelper.addCategory(c); //doesn't add category if it already exists
+ row.id = 0; // entry is truly new
+ row.id = dbHelper.addPassword(row);
+ }
+ ArrayList packageAccess = dbHelper.fetchPackageAccess(row.id);
+ if (! PassEntry.checkPackageAccess(packageAccess, callingPackage)) {
+ dbHelper.addPackageAccess(row.id, callingPackage);
+ }
+
+ }
+ return (callbackIntent);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ if (debug)
+ Log.d(TAG, "onPause()");
+
+ releaseService();
+ dbHelper.close();
+ dbHelper = null;
+ }
+
+ @Override
+ protected void onResume() {
+ super.onPause();
+
+ if (debug)
+ Log.d(TAG, "onResume()");
+ if (dbHelper == null) {
+ dbHelper = new DBHelper(this);
+ }
+
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ releaseService();
+ }
+
+
+ //--------------------------- service stuff ------------
+ private void initService() {
+
+ boolean isLocal = isIntentLocal();
+ conn = new ServiceDispatchConnection(isLocal);
+ Intent i = new Intent();
+ i.setClass(this, ServiceDispatchImpl.class);
+ startService(i);
+ bindService( i, conn, Context.BIND_AUTO_CREATE);
+ }
+
+ /**
+ * @return
+ */
+ private boolean isIntentLocal() {
+ String action = getIntent().getAction();
+ boolean isLocal = action == null || action.equals(Intent.ACTION_MAIN);
+ return isLocal;
+ }
+
+ private void releaseService() {
+ if (conn != null ) {
+ unbindService( conn );
+ conn = null;
+ }
+ }
+
+ class ServiceDispatchConnection implements ServiceConnection
+ {
+ boolean askPassIsLocal = false;
+ public ServiceDispatchConnection (Boolean isLocal) {
+ askPassIsLocal = isLocal;
+ }
+ public void onServiceConnected(ComponentName className,
+ IBinder boundService )
+ {
+ service = ServiceDispatch.Stub.asInterface((IBinder)boundService);
+
+ if (mServiceIntent != null) {
+ setServiceParametersFromExtrasAndDispatchAction(mServiceIntent);
+ mServiceIntent = null;
+ return;
+ }
+
+ boolean promptforpassword = getIntent().getBooleanExtra(CryptoIntents.EXTRA_PROMPT, true);
+ if (debug) Log.d(TAG, "Prompt for password: " + promptforpassword);
+ try {
+ if (service.getPassword() == null) {
+ if (promptforpassword) {
+ if (debug) Log.d(TAG, "ask for password");
+ // the service isn't running
+ Intent askPass = new Intent(getApplicationContext(),
+ AskPassword.class);
+
+ final Intent thisIntent = getIntent();
+ String inputBody = thisIntent.getStringExtra (CryptoIntents.EXTRA_TEXT);
+
+ askPass.putExtra (CryptoIntents.EXTRA_TEXT, inputBody);
+ askPass.putExtra (AskPassword.EXTRA_IS_LOCAL, askPassIsLocal);
+ //TODO: Is there a way to make sure all the extras are set?
+ startActivityForResult (askPass, REQUEST_CODE_ASK_PASSWORD);
+ } else {
+ if (debug) Log.d(TAG, "ask for password");
+ // Don't prompt but cancel
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+
+ } else {
+ if (debug) Log.d(TAG, "service already started");
+ //service already started, so don't need to ask pw.
+
+ boolean externalAccess = mPreferences.getBoolean(Preferences.PREFERENCE_ALLOW_EXTERNAL_ACCESS, false);
+
+ if (askPassIsLocal || externalAccess) {
+ salt = service.getSalt();
+ masterKey = service.getPassword();
+ actionDispatch();
+ } else {
+ if (debug) Log.d(TAG, "onServiceConnected: showDialogAllowExternalAccess()");
+ showDialogAllowExternalAccess();
+ }
+ }
+ } catch (RemoteException e) {
+ Log.d(TAG, e.toString());
+ }
+ if (debug) Log.d( TAG,"onServiceConnected" );
+ }
+
+ public void onServiceDisconnected(ComponentName className)
+ {
+ service = null;
+ if (debug) Log.d( TAG,"onServiceDisconnected" );
+ }
+ };
+
+}
diff --git a/src/org/openintents/safe/PassEdit.java b/src/org/openintents/safe/PassEdit.java
index 456257f..725e751 100644
--- a/src/org/openintents/safe/PassEdit.java
+++ b/src/org/openintents/safe/PassEdit.java
@@ -73,7 +73,14 @@ public class PassEdit extends Activity {
setTitle(title);
ch = new CryptoHelper();
- ch.setPassword(PassList.getMasterKey());
+ try {
+ ch.init(CryptoHelper.EncryptionMedium,PassList.getSalt());
+ ch.setPassword(PassList.getMasterKey());
+ } catch (CryptoHelperException e1) {
+ e1.printStackTrace();
+ Toast.makeText(this,getString(R.string.crypto_error)
+ + e1.getMessage(), Toast.LENGTH_SHORT).show();
+ }
if (dbHelper == null) {
dbHelper = new DBHelper(this);
@@ -107,13 +114,21 @@ public class PassEdit extends Activity {
cb.setText(passwordText.getText().toString());
Intent i = new Intent(Intent.ACTION_VIEW);
- Uri u = Uri.parse(websiteText.getText().toString());
+ String link = websiteText.getText().toString();
+ Uri u = Uri.parse(link);
i.setData(u);
try {
startActivity(i);
} catch (ActivityNotFoundException e) {
- Toast.makeText(PassEdit.this, R.string.invalid_website,
- Toast.LENGTH_SHORT).show();
+ // Let's try to catch the most common mistake: omitting http:
+ u = Uri.parse("http://" + link);
+ i.setData(u);
+ try {
+ startActivity(i);
+ } catch (ActivityNotFoundException e2) {
+ Toast.makeText(PassEdit.this, R.string.invalid_website,
+ Toast.LENGTH_SHORT).show();
+ }
}
}
});
@@ -179,6 +194,7 @@ public class PassEdit extends Activity {
entryEdited = true;
if (RowId == null || RowId == -1) {
+ entry.id = 0; // brand new entry
RowId = dbHelper.addPassword(entry);
} else {
PassEntry storedEntry = dbHelper.fetchPassword (RowId);
diff --git a/src/org/openintents/safe/PassEntry.java b/src/org/openintents/safe/PassEntry.java
index ca873b0..6a7112a 100644
--- a/src/org/openintents/safe/PassEntry.java
+++ b/src/org/openintents/safe/PassEntry.java
@@ -31,7 +31,7 @@ public class PassEntry extends Object {
public String username;
public String website;
public String uniqueName;
- // public ArrayList packageAccess;
+ public ArrayList packageAccess;
public String note;
public String plainPassword;
public String plainDescription;
diff --git a/src/org/openintents/safe/PassList.java b/src/org/openintents/safe/PassList.java
index 1e284f8..4ac3646 100644
--- a/src/org/openintents/safe/PassList.java
+++ b/src/org/openintents/safe/PassList.java
@@ -78,6 +78,7 @@ public class PassList extends ListActivity {
private static Long CategoryId=null;
private Intent restartTimerIntent;
+ private static String salt;
private static String masterKey;
private List rows;
@@ -203,7 +204,15 @@ public class PassList extends ListActivity {
if(masterKey == null) {
masterKey = "";
}
- ch.setPassword(masterKey);
+ try {
+ ch.init(CryptoHelper.EncryptionMedium, salt);
+ ch.setPassword(masterKey);
+ } catch (CryptoHelperException e1) {
+ e1.printStackTrace();
+ Toast.makeText(this,getString(R.string.crypto_error)
+ + e1.getMessage(), Toast.LENGTH_SHORT).show();
+ return;
+ }
List items = new ArrayList();
rows = dbHelper.fetchAllRows(CategoryId);
@@ -263,6 +272,14 @@ public class PassList extends ListActivity {
return super.onCreateOptionsMenu(menu);
}
+ static void setSalt(String saltIn) {
+ salt = saltIn;
+ }
+
+ static String getSalt() {
+ return salt;
+ }
+
static void setMasterKey(String key) {
masterKey = key;
}
@@ -400,12 +417,14 @@ public class PassList extends ListActivity {
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent i) {
super.onActivityResult(requestCode, resultCode, i);
+ //Log.d(TAG, "onActivityResult. requestCode: " + requestCode + ", resultCode: " + resultCode);
if (dbHelper == null) {
dbHelper = new DBHelper(this);
}
if (((requestCode==REQUEST_VIEW_PASSWORD)&&(PassView.entryEdited)) ||
((requestCode==REQUEST_EDIT_PASSWORD)&&(PassEdit.entryEdited)) ||
+ ((requestCode==REQUEST_ADD_PASSWORD)&&(PassEdit.entryEdited)) ||
(resultCode==RESULT_OK)) {
fillData();
}
@@ -423,11 +442,14 @@ public class PassList extends ListActivity {
if (ch==null) {
ch=new CryptoHelper();
}
- ch.setPassword(masterKey);
- try {
+ try {
+ ch.init(CryptoHelper.EncryptionMedium, salt);
+ ch.setPassword(masterKey);
category.plainName = ch.decrypt(category.name);
} catch (CryptoHelperException e) {
Log.e(TAG,e.toString());
+ Toast.makeText(this,getString(R.string.crypto_error)
+ + e.getMessage(), Toast.LENGTH_SHORT).show();
}
return category.plainName;
}
diff --git a/src/org/openintents/safe/PassView.java b/src/org/openintents/safe/PassView.java
index a45c666..8554341 100644
--- a/src/org/openintents/safe/PassView.java
+++ b/src/org/openintents/safe/PassView.java
@@ -16,12 +16,17 @@
*/
package org.openintents.safe;
+import java.util.ArrayList;
+
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.Bundle;
import android.text.ClipboardManager;
@@ -41,7 +46,7 @@ import android.widget.Toast;
*/
public class PassView extends Activity {
- private static boolean debug = true;
+ private static boolean debug = false;
private static String TAG = "PassView";
public static final int EDIT_PASSWORD_INDEX = Menu.FIRST;
@@ -55,6 +60,8 @@ public class PassView extends Activity {
private EditText websiteText;
private EditText noteText;
private TextView lastEditedText;
+ private TextView uniqueNameText;
+ private TextView packageAccessText;
private Long RowId;
private Long CategoryId;
private DBHelper dbHelper = null;
@@ -70,7 +77,14 @@ public class PassView extends Activity {
setTitle(title);
ch = new CryptoHelper();
- ch.setPassword(PassList.getMasterKey());
+ try {
+ ch.init(CryptoHelper.EncryptionMedium, PassList.getSalt());
+ ch.setPassword(PassList.getMasterKey());
+ } catch (CryptoHelperException e1) {
+ e1.printStackTrace();
+ Toast.makeText(this,getString(R.string.crypto_error)
+ + e1.getMessage(), Toast.LENGTH_SHORT).show();
+ }
if (dbHelper == null) {
dbHelper = new DBHelper(this);
@@ -84,6 +98,8 @@ public class PassView extends Activity {
passwordText = (EditText) findViewById(R.id.password);
noteText = (EditText) findViewById(R.id.note);
lastEditedText = (TextView) findViewById(R.id.last_edited);
+ uniqueNameText = (TextView) findViewById(R.id.uniquename);
+ packageAccessText = (TextView) findViewById(R.id.packageaccess);
entryEdited=false;
@@ -268,6 +284,8 @@ public class PassView extends Activity {
String cryptUsername = row.username;
String cryptPass = row.password;
String cryptNote = row.note;
+ String cryptUniqueName = row.uniqueName;
+ ArrayList packageAccess = dbHelper.fetchPackageAccess(row.id);
try {
descriptionText.setText(ch.decrypt(cryptDesc));
websiteText.setText(ch.decrypt(cryptWebsite));
@@ -280,7 +298,33 @@ public class PassView extends Activity {
} else {
lastEdited=getString(R.string.last_edited_unknown);
}
- lastEditedText.setText(getString(R.string.last_edited)+" "+lastEdited);
+ lastEditedText.setText(getString(R.string.last_edited)+": "+lastEdited);
+ if (cryptUniqueName!=null) {
+ String plainUniqueName=ch.decrypt(cryptUniqueName);
+ if (plainUniqueName!="") {
+ uniqueNameText.setText(getString(R.string.uniquename)+
+ ": "+plainUniqueName);
+ }
+ }
+ String packages="";
+ if (packageAccess!=null) {
+ for (String packageName : packageAccess) {
+ String plainPackageName=ch.decrypt(packageName);
+ PackageManager pm=getPackageManager();
+ String appLabel="";
+ try {
+ ApplicationInfo ai=pm.getApplicationInfo(plainPackageName,0);
+ appLabel=pm.getApplicationLabel(ai).toString();
+ } catch (NameNotFoundException e) {
+ appLabel="("+getString(R.string.not_found)+")";
+ }
+ packages+=plainPackageName+" "+appLabel+" ";
+ }
+ }
+ if (packages!="") {
+ packageAccessText.setText(getString(R.string.package_access)+
+ ": "+packages);
+ }
} catch (CryptoHelperException e) {
Log.e(TAG, e.toString());
}
diff --git a/src/org/openintents/safe/Preferences.java b/src/org/openintents/safe/Preferences.java
index 7256b7f..b55165b 100644
--- a/src/org/openintents/safe/Preferences.java
+++ b/src/org/openintents/safe/Preferences.java
@@ -8,6 +8,7 @@ public class Preferences extends PreferenceActivity {
public static final String PREFERENCE_ALLOW_EXTERNAL_ACCESS = "external_access";
public static final String PREFERENCE_LOCK_TIMEOUT = "lock_timeout";
public static final String PREFERENCE_LOCK_TIMEOUT_DEFAULT_VALUE = "5";
+ public static final String PREFERENCE_FIRST_TIME_WARNING = "first_time_warning";
@Override
protected void onCreate(Bundle savedInstanceState) {
diff --git a/src/org/openintents/safe/Restore.java b/src/org/openintents/safe/Restore.java
index 0cef10b..a14f545 100644
--- a/src/org/openintents/safe/Restore.java
+++ b/src/org/openintents/safe/Restore.java
@@ -33,7 +33,9 @@ import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.os.Bundle;
+import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.view.animation.Animation;
@@ -52,6 +54,9 @@ public class Restore extends Activity {
private String masterKey="";
private String filename=null;
private RestoreDataSet restoreDataSet=null;
+ private boolean firstTime=false;
+
+ public static final String KEY_FIRST_TIME = "first_time"; // Intent keys
@Override
public void onCreate(Bundle icicle) {
@@ -59,7 +64,13 @@ public class Restore extends Activity {
if (debug) Log.d(TAG,"onCreate()");
- if (!CategoryList.isSignedIn()) {
+ firstTime = icicle != null ? icicle.getBoolean(Restore.KEY_FIRST_TIME) : false;
+ if (firstTime == false) {
+ Bundle extras = getIntent().getExtras();
+ firstTime = extras != null ? extras.getBoolean(Restore.KEY_FIRST_TIME) : false;
+ }
+
+ if ((!firstTime) && (!CategoryList.isSignedIn())) {
Intent frontdoor = new Intent(this, FrontDoor.class);
startActivity(frontdoor);
finish();
@@ -116,7 +127,7 @@ public class Restore extends Activity {
if (debug) Log.d(TAG,"onResume()");
- if (!CategoryList.isSignedIn()) {
+ if ((!firstTime) && (!CategoryList.isSignedIn())) {
Intent frontdoor = new Intent(this, FrontDoor.class);
startActivity(frontdoor);
finish();
@@ -195,15 +206,20 @@ public class Restore extends Activity {
Toast.LENGTH_LONG).show();
return false;
}
- CryptoHelper ch=new CryptoHelper(CryptoHelper.EncryptionStrong);
- ch.setPassword(masterPassword);
+ CryptoHelper ch=new CryptoHelper();
+ String salt=restoreDataSet.getSalt();
String masterKeyEncrypted=restoreDataSet.getMasterKeyEncrypted();
masterKey="";
try {
+ ch.init(CryptoHelper.EncryptionStrong, salt);
+ ch.setPassword(masterPassword);
masterKey = ch.decrypt(masterKeyEncrypted);
} catch (CryptoHelperException e) {
Log.e(TAG,e.toString());
+ Toast.makeText(this, getString(R.string.crypto_error)
+ + e.getMessage(), Toast.LENGTH_SHORT).show();
+ return false;
}
if (ch.getStatus()==false) {
Toast.makeText(Restore.this, getString(R.string.restore_decrypt_error),
@@ -214,8 +230,16 @@ public class Restore extends Activity {
return false;
}
- ch=new CryptoHelper(CryptoHelper.EncryptionMedium);
- ch.setPassword(masterKey);
+ ch=new CryptoHelper();
+ try {
+ ch.init(CryptoHelper.EncryptionMedium, salt);
+ ch.setPassword(masterKey);
+ } catch (CryptoHelperException e1) {
+ e1.printStackTrace();
+ Toast.makeText(this, getString(R.string.crypto_error)
+ + e1.getMessage(), Toast.LENGTH_SHORT).show();
+ return false;
+ }
String firstCategory="";
try {
@@ -261,7 +285,10 @@ public class Restore extends Activity {
dbHelper.beginTransaction();
dbHelper.deleteDatabase();
+ dbHelper.storeSalt(restoreDataSet.getSalt());
dbHelper.storeMasterKey(restoreDataSet.getMasterKeyEncrypted());
+ CategoryList.setSalt(restoreDataSet.getSalt());
+ PassList.setSalt(restoreDataSet.getSalt());
CategoryList.setMasterKey(masterKey);
PassList.setMasterKey(masterKey);
for (CategoryEntry category : restoreDataSet.getCategories()) {
@@ -271,7 +298,13 @@ public class Restore extends Activity {
int totalPasswords=0;
for (PassEntry password : restoreDataSet.getPass()) {
totalPasswords++;
- dbHelper.addPassword(password);
+ long rowid=dbHelper.addPassword(password);
+ if (password.packageAccess!=null) {
+ for (String packageName : password.packageAccess) {
+ if (debug) Log.d(TAG,"packageName="+packageName);
+ dbHelper.addPackageAccess(rowid, packageName);
+ }
+ }
}
dbHelper.commit();
dbHelper.close();
@@ -279,14 +312,16 @@ public class Restore extends Activity {
Toast.makeText(Restore.this, getString(R.string.restore_complete)+
" "+Integer.toString(totalPasswords),
Toast.LENGTH_LONG).show();
+
+
+ // Don't need to show warning anymore to back up, because user has used
+ // restore already.
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
+ SharedPreferences.Editor editor = sp.edit();
+ editor.putBoolean(Preferences.PREFERENCE_FIRST_TIME_WARNING, true);
+ editor.commit();
setResult(RESULT_OK);
finish();
-
- /*
- Message m = new Message();
- m.what = CategoryList.MSG_FILLDATA;
- myViewHandler.sendMessage(m);
- */
}
}
diff --git a/src/org/openintents/safe/RestoreDataSet.java b/src/org/openintents/safe/RestoreDataSet.java
index 5bfab40..67c7491 100644
--- a/src/org/openintents/safe/RestoreDataSet.java
+++ b/src/org/openintents/safe/RestoreDataSet.java
@@ -17,6 +17,7 @@
package org.openintents.safe;
import java.util.ArrayList;
+import java.util.Arrays;
import android.util.Log;
@@ -27,11 +28,14 @@ public class RestoreDataSet {
private int version = 0;
private String date = null;
+ private String salt = null;
private String masterKeyEncrypted = null;
private Long currentCategoryId = new Long(0);
private CategoryEntry currentCategory = null;
private ArrayList categoryEntries = new ArrayList();
private PassEntry currentEntry = null;
+ private String currentRowID;
+ private String currentPackageAccess;
private ArrayList passEntries = new ArrayList();
private int totalEntries = 0;
@@ -47,6 +51,12 @@ public class RestoreDataSet {
public void setDate(String extractedDate) {
date = extractedDate;
}
+ public String getSalt() {
+ return salt;
+ }
+ public void setSalt(String extractedSalt) {
+ salt = extractedSalt;
+ }
public String getMasterKeyEncrypted() {
return masterKeyEncrypted;
}
@@ -74,23 +84,36 @@ public class RestoreDataSet {
public void newEntry() {
currentEntry = new PassEntry();
currentEntry.category = currentCategoryId;
+ currentRowID="";
currentEntry.description="";
currentEntry.website="";
currentEntry.username="";
currentEntry.password="";
currentEntry.note="";
+ currentEntry.uniqueName="";
+ currentEntry.packageAccess=null;
+ currentPackageAccess="";
}
public void storeEntry() {
// only add an entry if we had all the fields
if (debug) Log.d(TAG,currentEntry.description+" "+currentEntry.website+" "+
currentEntry.username+" "+currentEntry.password+" "+
- currentEntry.note);
+ currentEntry.note+" "+currentPackageAccess);
if ((currentEntry != null) &&
- (currentEntry.description!="") &&
- (currentEntry.website!="") &&
- (currentEntry.username!="") &&
- (currentEntry.password!="") &&
- (currentEntry.note!="")) {
+ (currentEntry.description!="")) {
+ try {
+ currentEntry.id=Long.parseLong(currentRowID);
+ } catch (NumberFormatException e) {
+ currentEntry.id=0;
+ }
+ if (currentPackageAccess!="") {
+ // strip the brackets [ and ]
+ String packageList = currentPackageAccess.substring(1,
+ currentPackageAccess.length()-1);
+ String[] packages = packageList.split(",");
+ currentEntry.packageAccess=new ArrayList(Arrays.asList(packages));
+ if (debug) Log.d(TAG,"packageAccess="+currentEntry.packageAccess.toString());
+ }
passEntries.add(currentEntry);
totalEntries++;
}
@@ -99,6 +122,12 @@ public class RestoreDataSet {
public int getTotalEntries() {
return totalEntries;
}
+ public void setRowID(String extractedRowID) {
+ if (debug) Log.d(TAG,"setRowID("+extractedRowID+")");
+ if (currentEntry != null) {
+ currentRowID += extractedRowID;
+ }
+ }
public void setDescription(String extractedDescription) {
if (debug) Log.d(TAG,"setDescription("+extractedDescription+")");
if (currentEntry != null) {
@@ -126,4 +155,15 @@ public class RestoreDataSet {
currentEntry.note += extractedNote;
}
}
+ public void setUniqueName(String extractedUniqueName) {
+ if (currentEntry != null) {
+ currentEntry.uniqueName += extractedUniqueName;
+ }
+ }
+ public void setPackageAccess(String extractedPackageAccess) {
+ if (debug) Log.d(TAG,"setPackageAccess("+extractedPackageAccess+")");
+ if (currentPackageAccess != null) {
+ currentPackageAccess += extractedPackageAccess;
+ }
+ }
}
diff --git a/src/org/openintents/safe/RestoreHandler.java b/src/org/openintents/safe/RestoreHandler.java
index 0dc766c..c31067d 100644
--- a/src/org/openintents/safe/RestoreHandler.java
+++ b/src/org/openintents/safe/RestoreHandler.java
@@ -32,14 +32,18 @@ public class RestoreHandler extends DefaultHandler {
// ===========================================================
private boolean in_oisafe = false;
+ private boolean in_salt = false;
private boolean in_masterkey = false;
private boolean in_category = false;
private boolean in_entry = false;
+ private boolean in_rowid = false;
private boolean in_description = false;
private boolean in_website = false;
private boolean in_username = false;
private boolean in_password = false;
private boolean in_note = false;
+ private boolean in_uniquename = false;
+ private boolean in_packageaccess = false;
private RestoreDataSet myRestoreDataSet = new RestoreDataSet();
@@ -85,6 +89,11 @@ public class RestoreHandler extends DefaultHandler {
if (debug) Log.d(TAG,"found OISafe "+version+" date "+date);
+ }else if (in_oisafe && localName.equals("Salt")) {
+ in_salt = true;
+
+ if (debug) Log.d(TAG,"found Salt");
+
}else if (in_oisafe && localName.equals("MasterKey")) {
in_masterkey = true;
@@ -105,6 +114,8 @@ public class RestoreHandler extends DefaultHandler {
if (debug) Log.d(TAG,"found Entry");
+ }else if (in_entry && localName.equals("RowID")) {
+ in_rowid = true;
}else if (in_entry && localName.equals("Description")) {
in_description = true;
}else if (in_entry && localName.equals("Website")) {
@@ -115,6 +126,10 @@ public class RestoreHandler extends DefaultHandler {
in_password = true;
}else if (in_entry && localName.equals("Note")) {
in_note = true;
+ }else if (in_entry && localName.equals("UniqueName")) {
+ in_uniquename = true;
+ }else if (in_entry && localName.equals("PackageAccess")) {
+ in_packageaccess = true;
}
}
@@ -127,6 +142,8 @@ public class RestoreHandler extends DefaultHandler {
if (localName.equals("OISafe")) {
in_oisafe = false;
+ }else if (in_oisafe && localName.equals("Salt")) {
+ in_salt = false;
}else if (in_oisafe && localName.equals("MasterKey")) {
in_masterkey = false;
}else if (in_oisafe && localName.equals("Category")) {
@@ -139,6 +156,8 @@ public class RestoreHandler extends DefaultHandler {
myRestoreDataSet.storeEntry();
+ }else if (in_entry && localName.equals("RowID")) {
+ in_rowid = false;
}else if (in_entry && localName.equals("Description")) {
in_description = false;
}else if (in_entry && localName.equals("Website")) {
@@ -149,6 +168,10 @@ public class RestoreHandler extends DefaultHandler {
in_password = false;
}else if (in_entry && localName.equals("Note")) {
in_note = false;
+ }else if (in_entry && localName.equals("UniqueName")) {
+ in_uniquename = false;
+ }else if (in_entry && localName.equals("PackageAccess")) {
+ in_packageaccess = false;
}
}
@@ -156,23 +179,26 @@ public class RestoreHandler extends DefaultHandler {
* <tag>characters</tag> */
@Override
public void characters(char ch[], int start, int length) {
- if (in_masterkey){
+ if (in_salt){
+ myRestoreDataSet.setSalt(new String(ch, start, length));
+ } else if (in_masterkey){
myRestoreDataSet.setMasterKeyEncrypted(new String(ch, start, length));
- }
- if (in_description){
+ } else if (in_rowid){
+ myRestoreDataSet.setRowID(new String(ch, start, length));
+ } else if (in_description){
myRestoreDataSet.setDescription(new String(ch, start, length));
- }
- if (in_website){
+ } else if (in_website){
myRestoreDataSet.setWebsite(new String(ch, start, length));
- }
- if (in_username){
+ } else if (in_username){
myRestoreDataSet.setUsername(new String(ch, start, length));
- }
- if (in_password){
+ } else if (in_password){
myRestoreDataSet.setPassword(new String(ch, start, length));
- }
- if (in_note){
+ } else if (in_note){
myRestoreDataSet.setNote(new String(ch, start, length));
+ } else if (in_uniquename){
+ myRestoreDataSet.setUniqueName(new String(ch, start, length));
+ } else if (in_packageaccess){
+ myRestoreDataSet.setPackageAccess(new String(ch, start, length));
}
}
}
diff --git a/src/org/openintents/safe/dialog/DialogHostingActivity.java b/src/org/openintents/safe/dialog/DialogHostingActivity.java
index ff42d42..0c1a697 100644
--- a/src/org/openintents/safe/dialog/DialogHostingActivity.java
+++ b/src/org/openintents/safe/dialog/DialogHostingActivity.java
@@ -16,23 +16,35 @@ import android.widget.EditText;
public class DialogHostingActivity extends Activity {
- private static final String TAG = "FilenameActivity";
+ private static final String TAG = "DialogHostingActivity";
+ private static final boolean debug = false;
public static final int DIALOG_ID_SAVE = 1;
public static final int DIALOG_ID_OPEN = 2;
public static final int DIALOG_ID_NO_FILE_MANAGER_AVAILABLE = 3;
public static final int DIALOG_ID_ALLOW_EXTERNAL_ACCESS = 4;
+ public static final int DIALOG_ID_FIRST_TIME_WARNING = 5;
public static final String EXTRA_DIALOG_ID = "org.openintents.notepad.extra.dialog_id";
+
+ /**
+ * Whether dialog is simply pausing while hidden by another activity
+ * or when configuration changes.
+ * If this is false, then we can safely finish this activity if a dialog
+ * gets dismissed.
+ */
+ private boolean mIsPausing = false;
EditText mEditText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ if (debug) Log.d(TAG, "onCreate");
Intent i = getIntent();
- if (i != null) {
+ if (i != null && savedInstanceState == null) {
+ if (debug) Log.d(TAG, "new dialog");
int dialogId = i.getIntExtra(EXTRA_DIALOG_ID, 0);
switch (dialogId) {
case DIALOG_ID_SAVE:
@@ -50,10 +62,12 @@ public class DialogHostingActivity extends Activity {
Log.i(TAG, "Show allow access dialog");
showDialog(DIALOG_ID_ALLOW_EXTERNAL_ACCESS);
break;
+ case DIALOG_ID_FIRST_TIME_WARNING:
+ Log.i(TAG, "Show first time warning dialog");
+ showDialog(DIALOG_ID_FIRST_TIME_WARNING);
+ break;
}
}
-
-
}
@@ -101,51 +115,54 @@ public class DialogHostingActivity extends Activity {
@Override
protected Dialog onCreateDialog(int id) {
+ if (debug) Log.d(TAG, "onCreateDialog");
+ Dialog dialog = null;
+
switch (id) {
case DIALOG_ID_SAVE:
- return new FilenameDialog(this);
+ dialog = new FilenameDialog(this);
+ break;
case DIALOG_ID_OPEN:
- return new FilenameDialog(this);
+ dialog = new FilenameDialog(this);
+ break;
case DIALOG_ID_NO_FILE_MANAGER_AVAILABLE:
Log.i(TAG, "fmd - create");
- return new GetFromMarketDialog(this,
+ dialog = new GetFromMarketDialog(this,
RD.string.filemanager_not_available,
RD.string.filemanager_get_oi_filemanager,
RD.string.filemanager_market_uri);
+ break;
case DIALOG_ID_ALLOW_EXTERNAL_ACCESS:
- return new AllowExternalAccessDialog(this);
+ dialog = new AllowExternalAccessDialog(this);
+ break;
+ case DIALOG_ID_FIRST_TIME_WARNING:
+ dialog = new FirstTimeWarningDialog(this);
+ break;
+ }
+ if (dialog == null) {
+ dialog = super.onCreateDialog(id);
+ }
+ if (dialog != null) {
+ dialog.setOnDismissListener(mDismissListener);
}
- return null;
+ return dialog;
}
@Override
protected void onPrepareDialog(int id, Dialog dialog) {
- FilenameDialog fd;
+ super.onPrepareDialog(id, dialog);
- dialog.setOnDismissListener(mDismissListener);
+ if (debug) Log.d(TAG, "onPrepareDialog");
+
+ //dialog.setOnDismissListener(mDismissListener);
switch (id) {
case DIALOG_ID_SAVE:
- fd = (FilenameDialog) dialog;
- //fd.setTitle(R.string.menu_save_to_sdcard);
-
break;
case DIALOG_ID_OPEN:
- fd = (FilenameDialog) dialog;
- //fd.setTitle(R.string.menu_open_from_sdcard);
break;
-
case DIALOG_ID_NO_FILE_MANAGER_AVAILABLE:
- Log.i(TAG, "fmd - prepare");
- /*
- GetFileManagerFromMarketDialog gd = (GetFileManagerFromMarketDialog) dialog;
- gd.setMessageResource(R.string.filemanager_not_available);
- gd.setInfoResources(R.string.filemanager_get_oi_filemanager,
- R.string.filemanager_market_uri,
- R.drawable.ic_launcher_folder_small,
- R.string.update_error);
- */
break;
}
}
@@ -153,9 +170,27 @@ public class DialogHostingActivity extends Activity {
OnDismissListener mDismissListener = new OnDismissListener() {
public void onDismiss(DialogInterface dialoginterface) {
- DialogHostingActivity.this.finish();
+ if (debug) Log.d(TAG, "Dialog dismissed. Pausing: " + mIsPausing);
+ if (!mIsPausing) {
+ if (debug) Log.d(TAG, "finish");
+ // Dialog has been dismissed by user.
+ DialogHostingActivity.this.finish();
+ } else {
+ // Probably just a screen orientation change. Don't finish yet.
+ // Dialog has been dismissed by system.
+ }
}
};
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ if (debug) Log.d(TAG, "onSaveInstanceState");
+ mIsPausing = true;
+ if (debug) Log.d(TAG, "onSaveInstanceState. Pausing: " + mIsPausing);
+ }
+
+
}
diff --git a/src/org/openintents/safe/dialog/FirstTimeWarningDialog.java b/src/org/openintents/safe/dialog/FirstTimeWarningDialog.java
new file mode 100644
index 0000000..a1ef3b5
--- /dev/null
+++ b/src/org/openintents/safe/dialog/FirstTimeWarningDialog.java
@@ -0,0 +1,47 @@
+package org.openintents.safe.dialog;
+
+import org.openintents.safe.Preferences;
+import org.openintents.safe.R;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.content.DialogInterface.OnClickListener;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.CheckBox;
+
+public class FirstTimeWarningDialog extends AlertDialog implements OnClickListener {
+ private static final String TAG = "FilenameDialog";
+
+ private static final String BUNDLE_TAGS = "tags";
+
+ protected static final int DIALOG_ID_NO_FILE_MANAGER_AVAILABLE = 2;
+
+ Context mContext;
+
+ CheckBox mCheckBox;
+
+ public FirstTimeWarningDialog(Context context) {
+ super(context);
+ mContext = context;
+
+ setTitle(context.getText(R.string.dialog_title_first_time_warning));
+ setButton(context.getText(android.R.string.ok), (OnClickListener) null);
+ //setButton2(context.getText(android.R.string.cancel), (OnClickListener) null);
+ setIcon(android.R.drawable.ic_dialog_alert);
+ setMessage(context.getText(R.string.dialog_summary_first_time_warning));
+
+ }
+
+
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == BUTTON1) {
+ // User pressed OK
+ }
+
+ }
+}
diff --git a/src/org/openintents/safe/service/ServiceDispatch.aidl b/src/org/openintents/safe/service/ServiceDispatch.aidl
index ba06008..02d6e7b 100644
--- a/src/org/openintents/safe/service/ServiceDispatch.aidl
+++ b/src/org/openintents/safe/service/ServiceDispatch.aidl
@@ -17,6 +17,8 @@
package org.openintents.safe.service;
interface ServiceDispatch {
+ void setSalt (String saltIn);
+ String getSalt ();
void setPassword (String masterPasswordIn);
String getPassword ();
String encrypt (String clearText);
diff --git a/src/org/openintents/safe/service/ServiceDispatchImpl.java b/src/org/openintents/safe/service/ServiceDispatchImpl.java
index 97a1c29..50ee468 100644
--- a/src/org/openintents/safe/service/ServiceDispatchImpl.java
+++ b/src/org/openintents/safe/service/ServiceDispatchImpl.java
@@ -35,8 +35,10 @@ import android.util.Log;
import android.os.CountDownTimer;
public class ServiceDispatchImpl extends Service {
+ private static boolean debug = true;
private static String TAG = "ServiceDispatchIMPL";
private CryptoHelper ch;
+ private String salt;
private String masterKey;
private CountDownTimer t;
private int timeoutMinutes = 5;
@@ -57,7 +59,10 @@ public class ServiceDispatchImpl extends Service {
mIntentReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(CryptoIntents.ACTION_RESTART_TIMER)) {
+ if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
+ if (debug) Log.d(TAG,"caught ACTION_SCREEN_OFF");
+ stopSelf();
+ } else if (intent.getAction().equals(CryptoIntents.ACTION_RESTART_TIMER)) {
restartTimer();
}
}
@@ -65,9 +70,10 @@ public class ServiceDispatchImpl extends Service {
IntentFilter filter = new IntentFilter();
filter.addAction (CryptoIntents.ACTION_RESTART_TIMER);
+ filter.addAction (Intent.ACTION_SCREEN_OFF);
registerReceiver(mIntentReceiver, filter);
- Log.d( TAG,"onCreate" );
+ if (debug) Log.d( TAG,"onCreate" );
}
@Override
@@ -81,29 +87,29 @@ public class ServiceDispatchImpl extends Service {
Intent intent = new Intent(CryptoIntents.ACTION_CRYPTO_LOGGED_OUT);
sendBroadcast(intent);
- Log.d( TAG,"onDestroy" );
+ if (debug) Log.d( TAG,"onDestroy" );
}
private void startTimer () {
- Log.d(TAG,"startTimer with timeoutUntilStop="+timeoutUntilStop);
+ if (debug) Log.d(TAG,"startTimer with timeoutUntilStop="+timeoutUntilStop);
t = new CountDownTimer(timeoutUntilStop, timeoutUntilStop) {
public void onTick(long millisUntilFinished) {
//doing nothing.
- Log.d(TAG, "tick: " + millisUntilFinished );
+ if (debug) Log.d(TAG, "tick: " + millisUntilFinished );
}
public void onFinish() {
- Log.d(TAG,"onFinish()");
+ if (debug) Log.d(TAG,"onFinish()");
stopSelf(); // countdown is over, stop the service.
}
};
t.start();
- Log.d(TAG, "Timer started with: " + timeoutUntilStop );
+ if (debug) Log.d(TAG, "Timer started with: " + timeoutUntilStop );
}
public void restartTimer () {
// must be started with startTimer first.
- Log.d(TAG,"timer restarted");
+ if (debug) Log.d(TAG,"timer restarted");
if (t != null) {
t.cancel();
t.start();
@@ -138,10 +144,24 @@ public class ServiceDispatchImpl extends Service {
return (clearText);
}
+ public void setSalt (String saltIn){
+ salt = saltIn;
+ }
+
+ public String getSalt() {
+ return salt;
+ }
+
public void setPassword (String masterKeyIn){
startTimer(); //should be initial timer start
- ch = new CryptoHelper(CryptoHelper.EncryptionMedium);
- ch.setPassword(masterKeyIn);
+ ch = new CryptoHelper();
+ try {
+ ch.init(CryptoHelper.EncryptionMedium, salt);
+ ch.setPassword(masterKeyIn);
+ } catch (CryptoHelperException e) {
+ e.printStackTrace();
+ return;
+ }
masterKey = masterKeyIn;
ServiceNotification.setNotification(ServiceDispatchImpl.this);
--
2.50.1