<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.openintents.safe" \r
android:versionCode="1" \r
- android:versionName="0.7.0">
+ android:versionName="1.0.0-beta1">
<application android:icon="@drawable/icon_safe" android:allowClearUserData="true" android:debuggable="true" android:label="@string/app_name">\r
\r
<meta-data android:name="org.openintents.metadata.COMMENTS"\r
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />\r
- </intent-filter>
-
+ </intent-filter>
+ </activity>
+
+ <activity class=".IntentHandler" android:name="IntentHandler" android:label="@string/app_name"
+ android:permission="org.openintents.safe.ACCESS_INTENTS">
<intent-filter android:label="@string/intent_encrypt">
<action android:name="org.openintents.action.ENCRYPT" />
<category android:name="android.intent.category.DEFAULT" />
<intent-filter android:label="@string/intent_set_password">
<action android:name="org.openintents.action.SET_PASSWORD" />
<category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
-
+ </intent-filter>
</activity>
+
<activity class=".PassGen" android:name="PassGen" android:label="@string/app_name" />
<activity class=".CategoryEdit" android:name="CategoryEdit" android:label="@string/app_name" />
<activity class=".PassView" android:name="PassView" android:label="@string/app_name" />
</service>
</application>
+ <permission android:name="org.openintents.safe.ACCESS_INTENTS"
+ android:protectionLevel="dangerous"
+ android:label="@string/oi_safe_permission_intents"
+ android:description="@string/oi_safe_permission_intents_descr" >
+ </permission>
+
<permission android:name="org.openintents.safe.ACCESS_SERVICE"
android:process=":remote" android:exported="true"
android:protectionLevel="signature"
- android:label="@string/permlab_service"
- android:description="@string/permdesc_service">
+ android:label="@string/oi_safe_permission_service"
+ android:description="@string/oi_safe_permission_service_descr">
</permission>
+ <uses-permission android:name="org.openintents.safe.ACCESS_INTENTS" />
<uses-permission android:name="org.openintents.safe.ACCESS_SERVICE" />
</manifest>
\ No newline at end of file
<h2>Backup and Restore</h2>
<p>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.</p>
<h2>Import and Export</h2>
put some data into the OI Safe and perform an export first, so
that you can see what the CSV should look like.</p>
-<p>Use the name 'passwordsafe.csv' for your filename. Connect the
- phone to your computer and copy 'passwordsafe.csv' onto the phone.
+<p>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.</p>
<p>Once the CSV file is in place, use Menu -> Import to import the file.
the same time.</p>
<p>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.
<div class="warning">WARNING: The exported file is not encrypted!!
After exporting and working with the file, you should delete it
<p>Any of the following actions will cause OI Safe to lock:</p>
<ul>
+ <li>The auto lock timer expires. This may be set to 1, 5 or 30 minutes.
+ The default is 5 minutes.</li>
<li>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.</li>
<li>You select <i>Lock</i> from the Menu on the Categories screen.</li>
- <li>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.</li>
</ul>
<p>Really, the easiest way to lock is to just hit the power button on the phone briefly and
android:layout_width="wrap_content"\r
android:layout_height="wrap_content" /> \r
\r
+ <Button android:id="@+id/restore_button" \r
+ android:layout_below="@id/pass_confirm"\r
+ android:layout_toRightOf="@id/continue_button"\r
+ android:visibility="invisible" \r
+ android:text="@string/restore"\r
+ android:layout_width="wrap_content"\r
+ android:layout_height="wrap_content" /> \r
</RelativeLayout>
\ No newline at end of file
android:text="@string/continue_text"\r
android:layout_width="wrap_content"\r
android:layout_height="wrap_content" /> \r
+ <Button android:id="@+id/restore_button" \r
+ android:visibility="invisible" \r
+ android:text="@string/restore"\r
+ android:layout_width="wrap_content"\r
+ android:layout_height="wrap_content" /> \r
</LinearLayout>
\ No newline at end of file
android:layout_width="wrap_content"\r
android:layout_height="wrap_content" \r
android:text="@string/last_edited" />\r
+\r
+ <TextView android:id="@+id/uniquename"\r
+ android:layout_width="wrap_content"\r
+ android:layout_height="wrap_content" \r
+ android:text="" />\r
+\r
+ <TextView android:id="@+id/packageaccess"\r
+ android:layout_width="wrap_content"\r
+ android:layout_height="wrap_content" \r
+ android:text="" />\r
</LinearLayout>\r
</ScrollView>
\ No newline at end of file
<string name="open">Open</string>\r
<string name="database_version_error_title">Database Version Error</string>\r
<string name="database_version_error_msg">Sorry, but the version of the database is not supported. You must install the version associated with this database.</string>\r
- <string name="permlab_service">access stored password</string>\r
- <string name="permdesc_service">Allows the application to encrypt and decrypt text, and access the passwords they have stored in OI Safe.</string> \r
<string name="move">Move</string>\r
<string name="move_select">Select Category</string>\r
<string name="moved_to">Moved to</string>\r
<string name="preferences">Preferences</string>\r
<string name="pref_title_external_access">Allow external access</string>\r
- <string name="pref_summary_external_access">Allow other applications to secure your data with OI Safe?</string>\r
+ <string name="pref_summary_external_access">Allow other applications to secure your data with OI Safe</string>\r
<string name="dialog_title_external_access">OI Safe: Another application requests access.</string>\r
<string name="dialog_summary_external_access">Allow other applications to secure your data with OI Safe.</string>\r
<string name="dialog_comment_external_access">This can also be set in OI Safe\'s Menu / Preferences.</string>\r
<string name="pref_title_lock_timeout">Auto lock timeout</string>\r
- <string name="pref_summary_lock_timeout">Minutes before timeout occurs and safe is locked.</string>\r
+ <string name="pref_summary_lock_timeout">Minutes before timeout occurs and safe is locked</string>\r
<string name="pref_dialog_title_lock_timeout">Auto lock timeout</string>\r
<string name="intent_decrypt">Decrypt (OI Safe)</string>\r
<string name="intent_encrypt">Encrypt (OI Safe)</string>\r
<string name="entry_saved">Entry saved</string>\r
<string name="last_edited">Last edited</string>\r
<string name="last_edited_unknown">unknown</string>\r
+ <string name="crypto_error">Crypto error:</string>\r
+ <string name="uniquename">Unique name</string>\r
+ <string name="package_access">Package access</string>\r
+ <string name="not_found">not found</string>\r
+ <string name="dialog_title_first_time_warning">New master key</string>\r
+ <string name="dialog_summary_first_time_warning">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.</string>\r
</resources>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- \r
+ * Copyright (C) 2007-2009 OpenIntents.org\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ -->\r
+ \r
+<resources>\r
+ <!-- OI Safe -->\r
+ <string name="oi_safe_permission_service">access the master password</string>\r
+ <string name="oi_safe_permission_service_descr">Allows the application to access the master key used for encryption and decryption. Should never be used except by OI Safe.</string> \r
+ <string name="oi_safe_permission_intents">access stored password</string>\r
+ <string name="oi_safe_permission_intents_descr">Allows the application to encrypt and decrypt text, and access the passwords they have stored in OI Safe.</string> \r
+</resources> \r
*/
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;
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;
private TextView remoteAsk;
private EditText confirmPass;
private String PBEKey;
+ private String salt;
private String masterKey;
private CryptoHelper ch;
private boolean firstTime = false;
dbHelper = new DBHelper(this);
- ch = new CryptoHelper(CryptoHelper.EncryptionStrong);
+ ch = new CryptoHelper();
if (dbHelper.needsUpgrade()) {
switch (dbHelper.fetchVersion()) {
case 2:
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) {
// 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) {
.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,
// 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);
}
});
}
*
* @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());
}
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();
+ }
+ }
+
}
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<CategoryEntry> crows;
crows = dbHelper.fetchAllCategoryRows();
HashMap<Long, ArrayList<String>> packageAccess=dbHelper.fetchPackageAccessAll();
*/
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);
@Override
protected void onPause() {
super.onPause();
- Log.d(TAG, "onPause");
+ if (debug) Log.d(TAG, "onPause");
dbHelper.close();
dbHelper = null;
}
@Override
protected void onResume() {
super.onResume();
- Log.d(TAG, "onResume");
+ if (debug) Log.d(TAG, "onResume");
if (dbHelper == null) {
dbHelper = new DBHelper(this);
}
}
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);
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);
}
}
*
*/
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) {
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;
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;
private Thread backupThread=null;
+ private static String salt;
private static String masterKey;
private List<CategoryEntry> rows;
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() {
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<String> items = new ArrayList<String>();
if (dbHelper==null) {
return super.onCreateOptionsMenu(menu);
}
+ static void setSalt(String saltIn) {
+ salt = saltIn;
+ }
+
+ static String getSalt() {
+ return salt;
+ }
+
static void setMasterKey(String key) {
masterKey = key;
}
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);
}
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<Long, String> categories = new HashMap<Long, String>();
Log.e(TAG,e.toString());
continue;
}
+ entry.id=0;
dbHelper.addPassword(entry);
newEntries++;
}
if(masterKey == null) {
masterKey = "";
}
- ch.setPassword(masterKey);
+ try {
+ ch.init(CryptoHelper.EncryptionMedium,salt);
+ ch.setPassword(masterKey);
+ } catch (CryptoHelperException e1) {
+ e1.printStackTrace();
+ return null;
+ }
HashMap<String,Long> categories = new HashMap<String,Long>();
List<CategoryEntry> rows;
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?
} catch (CryptoHelperException e) {
Log.e(TAG, e.toString());
+ Toast.makeText(this,getString(R.string.crypto_error)
+ + e.getMessage(), Toast.LENGTH_SHORT).show();
}
dbHelper.close();
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;
}
}
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;
}
}
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;
}
}
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;
}
}
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;
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());
import java.security.MessageDigest;\r
import java.security.NoSuchAlgorithmException;\r
import java.security.NoSuchProviderException;\r
+import java.security.SecureRandom;\r
import java.security.spec.InvalidKeySpecException;\r
\r
import javax.crypto.BadPaddingException;\r
import javax.crypto.spec.PBEKeySpec;\r
import javax.crypto.spec.PBEParameterSpec;\r
\r
-//import org.bouncycastle.jce.provider.BouncyCastleProvider;\r
-\r
import android.util.Log;\r
\r
/**\r
*/\r
public class CryptoHelper {\r
\r
+ private static final boolean debug = true;\r
private static String TAG = "CryptoHelper";\r
protected static PBEKeySpec pbeKeySpec;\r
protected static PBEParameterSpec pbeParamSpec;\r
protected static Cipher pbeCipher;\r
private boolean status=false; // status of the last encrypt/decrypt\r
\r
- private static final byte[] salt = {\r
- (byte)0xfc, (byte)0x76, (byte)0x80, (byte)0xae,\r
- (byte)0xfd, (byte)0x82, (byte)0xbe, (byte)0xee,\r
- };\r
+ private static byte[] salt = null; \r
\r
private static final int count = 20;\r
\r
* Constructor which defaults to a medium encryption level.\r
*/\r
public CryptoHelper() {\r
- initialize(EncryptionMedium);\r
+// initialize(EncryptionMedium);\r
}\r
/**\r
* Constructor which allows the specification of the encryption level.\r
* \r
- * @param Strength encryption strength\r
+ * @param strength encryption strength\r
+ * @param salt salt to be used\r
*/\r
- public CryptoHelper(int Strength) {\r
- initialize(Strength);\r
+ public void init(int strength, String salt) throws CryptoHelperException {\r
+ try {\r
+ setSalt(salt);\r
+ initialize(strength);\r
+ } catch (CryptoHelperException e) {\r
+ e.printStackTrace();\r
+ throw e;\r
+ }\r
}\r
/**\r
* Initialize the class. Sets the encryption level for the instance\r
Log.e(TAG,"CryptoHelper(): "+e.toString()); \r
}\r
}\r
+\r
+ /**\r
+ * Generate a random salt for use with the cipher.\r
+ * \r
+ * @author Randy McEoin\r
+ * @return String version of the 8 byte salt\r
+ */\r
+ public static String generateSalt() throws NoSuchAlgorithmException {\r
+ byte[] salt = new byte[8];\r
+ SecureRandom sr;\r
+ try {\r
+ sr = SecureRandom.getInstance("SHA1PRNG");\r
+ sr.nextBytes(salt);\r
+ if (debug) Log.d(TAG,"generateSalt: salt="+salt.toString());\r
+ } catch (NoSuchAlgorithmException e) {\r
+ e.printStackTrace();\r
+ throw e;\r
+ }\r
+ return toHexString(salt);\r
+ }\r
/**\r
* @author Isaac Potoczny-Jones\r
* \r
* @return null if failure, otherwise hex string version of key\r
*/\r
- public static String generateMasterKey () {\r
+ public static String generateMasterKey () throws NoSuchAlgorithmException {\r
try {\r
KeyGenerator keygen;\r
keygen = KeyGenerator.getInstance("AES");\r
return toHexString(genDesKey.getEncoded());\r
} catch (NoSuchAlgorithmException e) {\r
Log.e(TAG,"generateMasterKey(): "+e.toString());\r
+ throw e;\r
}\r
- return null; //error case.\r
}\r
\r
/**\r
* @throws Exception\r
*/\r
public void setPassword(String pass) {\r
+ if (debug) Log.d(TAG,"setPassword("+pass+")");\r
password = pass;\r
pbeKeySpec = new PBEKeySpec(password.toCharArray());\r
try {\r
}\r
}\r
\r
+ private void setSalt(String saltIn) throws CryptoHelperException {\r
+ if (saltIn==null) {\r
+ String msg = "Salt must not be null.";\r
+ throw new CryptoHelperException(msg);\r
+ }\r
+ byte[] byteSaltIn=hexStringToBytes(saltIn);\r
+\r
+ if (byteSaltIn.length != 8) {\r
+ String msg = "Salt must be 8 bytes in length.";\r
+ throw new CryptoHelperException(msg);\r
+ }\r
+ salt=byteSaltIn;\r
+ if (debug) Log.d(TAG,"setSalt: salt="+toHexString(salt));\r
+ }\r
/**\r
* encrypt a string\r
* \r
public String encrypt(String plaintext) throws CryptoHelperException {\r
status=false; // assume failure\r
if(password == null) {\r
- String msg = "Must call setPassword before runing encrypt.";\r
+ String msg = "Must call setPassword before running encrypt.";\r
+ throw new CryptoHelperException(msg);\r
+ }\r
+ if (salt==null) {\r
+ String msg = "Must call setSalt before running encrypt.";\r
throw new CryptoHelperException(msg);\r
}\r
byte[] ciphertext = {};\r
String msg = "Must call setPassword before running decrypt.";\r
throw new CryptoHelperException(msg);\r
}\r
+ if (salt==null) {\r
+ String msg = "Must call setSalt before running decrypt.";\r
+ throw new CryptoHelperException(msg);\r
+ }\r
\r
if ((ciphertext==null) || (ciphertext=="")) {\r
return "";\r
public boolean getStatus() {\r
return status;\r
}\r
+ \r
+\r
+ /**\r
+ * encrypt a string using a random session key\r
+ * \r
+ * @author Peli\r
+ * \r
+ * @param plaintext\r
+ * @return encrypted String\r
+ * @throws Exception\r
+ */\r
+ public String encryptWithSessionKey(String plaintext) throws CryptoHelperException {\r
+ Log.i(TAG, "Encrypt with session key");\r
+ status=false; // assume failure\r
+ if(password == null) {\r
+ String msg = "Must call setPassword before runing encrypt.";\r
+ throw new CryptoHelperException(msg);\r
+ }\r
+ byte[] cipherSessionKey = {};\r
+ byte[] ciphertext = {};\r
+ \r
+ // First create a session key\r
+ SecretKey sessionKey = null;\r
+ byte[] sessionKeyEncoded = null;\r
+ String sessionKeyString = null;\r
+ try {\r
+ KeyGenerator keygen;\r
+ keygen = KeyGenerator.getInstance("AES");\r
+ keygen.init(256); // needs 96 bytes\r
+ //keygen.init(128); // needs 64 bytes\r
+ sessionKey = keygen.generateKey();\r
+ sessionKeyEncoded = sessionKey.getEncoded();\r
+ sessionKeyString = new String(sessionKeyEncoded);\r
+ } catch (NoSuchAlgorithmException e) {\r
+ Log.e(TAG,"generateMasterKey(): "+e.toString());\r
+ }\r
+\r
+ // Convert this to a Pbe key\r
+ PBEKeySpec sessionPbeKeySpec = new PBEKeySpec(sessionKeyString.toCharArray());\r
+ SecretKey sessionPbeKey = null;\r
+ try {\r
+ sessionPbeKey = keyFac.generateSecret(sessionPbeKeySpec);\r
+ } catch (InvalidKeySpecException e) {\r
+ Log.e(TAG,"setPassword(): "+e.toString());\r
+ }\r
+ \r
+ // Encrypt the session key using the master key\r
+ try {\r
+ pbeCipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);\r
+ cipherSessionKey = pbeCipher.doFinal(sessionKeyEncoded);\r
+ } catch (IllegalBlockSizeException e) {\r
+ Log.e(TAG,"encryptWithSessionKey(): "+e.toString());\r
+ } catch (BadPaddingException e) {\r
+ Log.e(TAG,"encryptWithSessionKey(): "+e.toString());\r
+ } catch (InvalidKeyException e) {\r
+ Log.e(TAG,"encryptWithSessionKey(): "+e.toString());\r
+ } catch (InvalidAlgorithmParameterException e) {\r
+ Log.e(TAG,"encryptWithSessionKey(): "+e.toString());\r
+ }\r
+ \r
+ // Now encrypt the text using the session key\r
+ try {\r
+ pbeCipher.init(Cipher.ENCRYPT_MODE, sessionPbeKey, pbeParamSpec);\r
+ ciphertext = pbeCipher.doFinal(plaintext.getBytes());\r
+ status=true;\r
+ } catch (IllegalBlockSizeException e) {\r
+ Log.e(TAG,"encryptWithSessionKey2(): "+e.toString());\r
+ } catch (BadPaddingException e) {\r
+ Log.e(TAG,"encryptWithSessionKey2(): "+e.toString());\r
+ } catch (InvalidKeyException e) {\r
+ Log.e(TAG,"encryptWithSessionKey2(): "+e.toString());\r
+ } catch (InvalidAlgorithmParameterException e) {\r
+ Log.e(TAG,"encryptWithSessionKey2(): "+e.toString());\r
+ }\r
+ \r
+ String stringCipherVersion = "A";\r
+ String stringCipherSessionKey = toHexString(cipherSessionKey);\r
+ String stringCiphertext=toHexString(ciphertext);\r
+ Log.i(TAG, "Length: " + stringCipherSessionKey.length() + ", " + stringCipherSessionKey);\r
+ \r
+ StringBuilder sb = new StringBuilder(stringCipherVersion.length() \r
+ + stringCipherSessionKey.length() \r
+ + stringCiphertext.length());\r
+ sb.append(stringCipherVersion);\r
+ sb.append(stringCipherSessionKey);\r
+ sb.append(stringCiphertext);\r
+ return sb.toString();\r
+ }\r
+\r
+ /**\r
+ * unencrypt encrypted string previously encrypted with\r
+ * encryptWithSessionKey()\r
+ * \r
+ * @author Peli\r
+ * \r
+ * @param ciphertext\r
+ * @return decrypted String\r
+ * @throws Exception\r
+ */\r
+ public String decryptWithSessionKey(String ciphertext) throws CryptoHelperException {\r
+ status=false; // assume failure\r
+ if(password == null) {\r
+ String msg = "Must call setPassword before running decrypt.";\r
+ throw new CryptoHelperException(msg);\r
+ }\r
+ \r
+ if ((ciphertext==null) || (ciphertext=="")) {\r
+ return "";\r
+ }\r
+ String cipherVersion = null;\r
+ String cipherSessionKey = null;\r
+ \r
+ // Split cipher into session key and text\r
+ try {\r
+ cipherVersion = ciphertext.substring(0,1);\r
+ if (cipherVersion.equals("A")) {\r
+ cipherSessionKey = ciphertext.substring(1,97); // 64 if init(128) had been chosen\r
+ ciphertext = ciphertext.substring(97);\r
+ } else {\r
+ Log.e(TAG, "Unknown cipher version" + cipherVersion);\r
+ return "";\r
+ }\r
+ } catch (IndexOutOfBoundsException e) {\r
+ Log.e(TAG, "Invalid ciphertext (with session key)");\r
+ return "";\r
+ }\r
+ \r
+ // Decrypt the session key\r
+ byte[] byteCipherSessionKey=hexStringToBytes(cipherSessionKey);\r
+ byte[] byteSessionKey = {};\r
+ \r
+ try {\r
+ pbeCipher.init(Cipher.DECRYPT_MODE, pbeKey, pbeParamSpec);\r
+ byteSessionKey = pbeCipher.doFinal(byteCipherSessionKey);\r
+ status=true;\r
+ } catch (IllegalBlockSizeException e) {\r
+ Log.e(TAG,"decrypt(): "+e.toString());\r
+ } catch (BadPaddingException e) {\r
+ Log.e(TAG,"decrypt(): "+e.toString());\r
+ } catch (InvalidKeyException e) {\r
+ Log.e(TAG,"decrypt(): "+e.toString());\r
+ } catch (InvalidAlgorithmParameterException e) {\r
+ Log.e(TAG,"decrypt(): "+e.toString());\r
+ }\r
+\r
+ // Convert the session key into a Pbe key\r
+ String stringSessionKey = new String(byteSessionKey);\r
+ PBEKeySpec sessionPbeKeySpec = new PBEKeySpec(stringSessionKey.toCharArray());\r
+ SecretKey sessionPbeKey = null;\r
+ try {\r
+ sessionPbeKey = keyFac.generateSecret(sessionPbeKeySpec);\r
+ } catch (InvalidKeySpecException e) {\r
+ Log.e(TAG,"setPassword(): "+e.toString());\r
+ }\r
+\r
+ // Use the session key to decrypt the text\r
+ byte[] byteCiphertext=hexStringToBytes(ciphertext);\r
+ byte[] plaintext = {};\r
+ \r
+ try {\r
+ pbeCipher.init(Cipher.DECRYPT_MODE, sessionPbeKey, pbeParamSpec);\r
+ plaintext = pbeCipher.doFinal(byteCiphertext);\r
+ status=true;\r
+ } catch (IllegalBlockSizeException e) {\r
+ Log.e(TAG,"decrypt(): "+e.toString());\r
+ } catch (BadPaddingException e) {\r
+ Log.e(TAG,"decrypt(): "+e.toString());\r
+ } catch (InvalidKeyException e) {\r
+ Log.e(TAG,"decrypt(): "+e.toString());\r
+ } catch (InvalidAlgorithmParameterException e) {\r
+ Log.e(TAG,"decrypt(): "+e.toString());\r
+ }\r
+ \r
+ return new String(plaintext);\r
+ }\r
}\r
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";
"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;
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());
}
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 ////////////////
/**
*
}
/**
+ * 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);
-/* $Id$\r
+/* $Id: FrontDoor.java 1805 2009-01-20 04:05:01Z rmceoin $\r
* \r
* Copyright 2007-2008 Steven Osborn\r
*\r
*/\r
package org.openintents.safe;\r
\r
-\r
-import java.util.ArrayList;\r
-\r
-import org.openintents.intents.CryptoIntents;\r
-import org.openintents.safe.dialog.DialogHostingActivity;\r
-import org.openintents.safe.service.ServiceDispatch;\r
-import org.openintents.safe.service.ServiceDispatchImpl;\r
-\r
import android.app.Activity;\r
-import android.content.ComponentName;\r
-import android.content.Context;\r
import android.content.Intent;\r
-import android.content.ServiceConnection;\r
-import android.content.SharedPreferences;\r
import android.os.Bundle;\r
-import android.os.IBinder;\r
-import android.os.RemoteException;\r
-import android.preference.PreferenceManager;\r
-import android.util.Log;\r
-import android.widget.Toast;\r
\r
\r
/**\r
* FrontDoor Activity\r
* \r
- * This activity just acts as a splash screen and gets the password from the\r
- * user that will be used to decrypt/encrypt password entries.\r
+ * This activity just makes sure we're entering the front door, not calling encrypt/decrypt\r
+ * or other intents.\r
* \r
* @author Steven Osborn - http://steven.bitsetters.com\r
*/\r
public class FrontDoor extends Activity {\r
\r
- private static final boolean debug = false;\r
- private static String TAG = "FrontDoor";\r
- \r
- private static final int REQUEST_CODE_ASK_PASSWORD = 1;\r
- private static final int REQUEST_CODE_ALLOW_EXTERNAL_ACCESS = 2;\r
- \r
-\r
- private DBHelper dbHelper;\r
- private String masterKey;\r
- private CryptoHelper ch;\r
-\r
- // service elements\r
- private ServiceDispatch service;\r
- private ServiceDispatchConnection conn;\r
-\r
- SharedPreferences mPreferences;\r
- //public static String SERVICE_NAME = "org.openintents.safe.service.ServiceDispatchImpl";\r
- \r
/** Called when the activity is first created. */\r
- @Override\r
- public void onCreate(Bundle icicle) {\r
- super.onCreate(icicle);\r
- mPreferences = PreferenceManager.getDefaultSharedPreferences(this);\r
- initService(); // start up the PWS service so other applications can query.\r
- }\r
-\r
- //currently only handles result from askPassword function.\r
- protected void onActivityResult (int requestCode, int resultCode, Intent data) {\r
- if (resultCode == RESULT_OK) {\r
- switch (requestCode) {\r
- case REQUEST_CODE_ASK_PASSWORD:\r
- masterKey = data.getStringExtra("masterKey");\r
- String timeout = mPreferences.getString(Preferences.PREFERENCE_LOCK_TIMEOUT, Preferences.PREFERENCE_LOCK_TIMEOUT_DEFAULT_VALUE); \r
- int timeoutMinutes=5; // default to 5\r
- try {\r
- timeoutMinutes = Integer.valueOf(timeout);\r
- } catch (NumberFormatException e) {\r
- Log.d(TAG,"why is lock_timeout busted?");\r
- }\r
- try {\r
- service.setTimeoutMinutes(timeoutMinutes);\r
- service.setPassword(masterKey); // should already be connected.\r
- } catch (RemoteException e1) {\r
- // TODO Auto-generated catch block\r
- e1.printStackTrace();\r
- }\r
-\r
- boolean externalAccess = mPreferences.getBoolean(Preferences.PREFERENCE_ALLOW_EXTERNAL_ACCESS, false);\r
- boolean isLocal = isIntentLocal();\r
- \r
- if (isLocal || externalAccess) {\r
- actionDispatch();\r
- } else {\r
- // ask first\r
- showDialogAllowExternalAccess();\r
- }\r
- break;\r
- case REQUEST_CODE_ALLOW_EXTERNAL_ACCESS:\r
-\r
- actionDispatch();\r
- break;\r
- }\r
- \r
- } else { // resultCode == RESULT_CANCELED\r
- setResult(RESULT_CANCELED);\r
- finish();\r
- }\r
- }\r
-\r
- /**\r
- * \r
- */\r
- private void showDialogAllowExternalAccess() {\r
- Intent i = new Intent(this, DialogHostingActivity.class);\r
- i.putExtra(DialogHostingActivity.EXTRA_DIALOG_ID, DialogHostingActivity.DIALOG_ID_ALLOW_EXTERNAL_ACCESS);\r
- this.startActivityForResult(i, REQUEST_CODE_ALLOW_EXTERNAL_ACCESS);\r
- }\r
- \r
- protected void actionDispatch () { \r
- final Intent thisIntent = getIntent();\r
- final String action = thisIntent.getAction();\r
- Intent callbackIntent = getIntent(); \r
- int callbackResult = RESULT_CANCELED;\r
- PassList.setMasterKey(masterKey);\r
- CategoryList.setMasterKey(masterKey);\r
- if (ch == null) {\r
- ch = new CryptoHelper(CryptoHelper.EncryptionMedium);\r
- ch.setPassword(masterKey);\r
- }\r
-\r
- boolean externalAccess = mPreferences.getBoolean(Preferences.PREFERENCE_ALLOW_EXTERNAL_ACCESS, false);\r
-\r
- if (action == null || action.equals(Intent.ACTION_MAIN)){\r
- //TODO: When launched from debugger, action is null. Other such cases?\r
- Intent i = new Intent(getApplicationContext(),\r
- CategoryList.class);\r
- startActivity(i);\r
- } else if (externalAccess){\r
-\r
- // which action?\r
- if (action.equals (CryptoIntents.ACTION_ENCRYPT)) {\r
- callbackResult = encryptIntent(thisIntent, callbackIntent);\r
- } else if (action.equals (CryptoIntents.ACTION_DECRYPT)) {\r
- callbackResult = decryptIntent(thisIntent, callbackIntent);\r
- } else if (action.equals (CryptoIntents.ACTION_GET_PASSWORD)\r
- || action.equals (CryptoIntents.ACTION_SET_PASSWORD)) {\r
- try {\r
- callbackIntent = getSetPassword (thisIntent, callbackIntent);\r
- callbackResult = RESULT_OK;\r
- } catch (CryptoHelperException e) {\r
- Log.e(TAG, e.toString(), e);\r
- Toast.makeText(FrontDoor.this,\r
- "There was a crypto error while retreiving the requested password: " + e.getMessage(),\r
- Toast.LENGTH_SHORT).show();\r
- } catch (Exception e) {\r
- Log.e(TAG, e.toString(), e);\r
- //TODO: Turn this into a proper error dialog.\r
- Toast.makeText(FrontDoor.this,\r
- "There was an error in retreiving the requested password: " + e.getMessage(),\r
- Toast.LENGTH_SHORT).show();\r
- }\r
- }\r
- setResult(callbackResult, callbackIntent);\r
- }\r
- finish();\r
- }\r
-\r
-\r
- /**\r
- * Encrypt all supported fields in the intent and return the result in callbackIntent.\r
- * \r
- * @param thisIntent\r
- * @param callbackIntent\r
- * @return callbackResult\r
- */\r
- private int encryptIntent(final Intent thisIntent, Intent callbackIntent) {\r
- if (debug)\r
- Log.d(TAG, "encryptIntent()");\r
- \r
- int callbackResult = RESULT_CANCELED;\r
- try {\r
- if (thisIntent.hasExtra(CryptoIntents.EXTRA_TEXT)) {\r
- // get the body text out of the extras.\r
- String inputBody = thisIntent.getStringExtra (CryptoIntents.EXTRA_TEXT);\r
- String outputBody = "";\r
- outputBody = ch.encrypt (inputBody);\r
- // stash the encrypted text in the extra\r
- callbackIntent.putExtra(CryptoIntents.EXTRA_TEXT, outputBody);\r
- }\r
- \r
- if (thisIntent.hasExtra(CryptoIntents.EXTRA_TEXT_ARRAY)) {\r
- String[] in = thisIntent.getStringArrayExtra(CryptoIntents.EXTRA_TEXT_ARRAY);\r
- String[] out = new String[in.length];\r
- for (int i = 0; i < in.length; i++) {\r
- if (in[i] != null) {\r
- out[i] = ch.encrypt(in[i]);\r
- }\r
- }\r
- callbackIntent.putExtra(CryptoIntents.EXTRA_TEXT_ARRAY, out);\r
- }\r
- \r
- // Support for binary fields could be added here (like images?)\r
- \r
- callbackResult = RESULT_OK;\r
- } catch (CryptoHelperException e) {\r
- Log.e(TAG, e.toString());\r
- }\r
- return callbackResult;\r
- }\r
-\r
- /**\r
- * Decrypt all supported fields in the intent and return the result in callbackIntent.\r
- * \r
- * @param thisIntent\r
- * @param callbackIntent\r
- * @return callbackResult\r
- */\r
- private int decryptIntent(final Intent thisIntent, Intent callbackIntent) {\r
- int callbackResult = RESULT_CANCELED;\r
- try {\r
-\r
- if (thisIntent.hasExtra(CryptoIntents.EXTRA_TEXT)) {\r
- // get the body text out of the extras.\r
- String inputBody = thisIntent.getStringExtra (CryptoIntents.EXTRA_TEXT);\r
- String outputBody = "";\r
- outputBody = ch.decrypt (inputBody);\r
- // stash the encrypted text in the extra\r
- callbackIntent.putExtra(CryptoIntents.EXTRA_TEXT, outputBody);\r
- }\r
- \r
- if (thisIntent.hasExtra(CryptoIntents.EXTRA_TEXT_ARRAY)) {\r
- String[] in = thisIntent.getStringArrayExtra(CryptoIntents.EXTRA_TEXT_ARRAY);\r
- String[] out = new String[in.length];\r
- for (int i = 0; i < in.length; i++) {\r
- if (in[i] != null) {\r
- out[i] = ch.decrypt(in[i]);\r
- }\r
- }\r
- callbackIntent.putExtra(CryptoIntents.EXTRA_TEXT_ARRAY, out);\r
- }\r
- \r
- // Support for binary fields could be added here (like images?)\r
- \r
- callbackResult = RESULT_OK;\r
- } catch (CryptoHelperException e) {\r
- Log.e(TAG, e.toString());\r
- }\r
- return callbackResult;\r
- }\r
- \r
- private Intent getSetPassword (Intent thisIntent, Intent callbackIntent) throws CryptoHelperException, Exception {\r
- String action = thisIntent.getAction();\r
- //TODO: Consider moving this elsewhere. Maybe DBHelper? Also move strings to resource.\r
- //DBHelper dbHelper = new DBHelper(this);\r
- Log.d(TAG, "GET_or_SET_PASSWORD");\r
- String username = null;\r
- String password = null;\r
-\r
- String clearUniqueName = thisIntent.getStringExtra (CryptoIntents.EXTRA_UNIQUE_NAME);\r
-\r
- if (clearUniqueName == null) throw new Exception ("EXTRA_UNIQUE_NAME not set.");\r
- \r
- if (dbHelper == null) {\r
- // Need to open DBHelper here, because\r
- // onResume() is called after onActivityResult()\r
- dbHelper = new DBHelper(this);\r
- }\r
-\r
- String uniqueName = ch.encrypt(clearUniqueName);\r
- PassEntry row = dbHelper.fetchPassword(uniqueName);\r
- boolean passExists = row.id > 1;\r
-\r
- String clearCallingPackage = getCallingPackage();\r
- String callingPackage = ch.encrypt (clearCallingPackage);\r
- if (passExists) { // check for permission to access this password.\r
- ArrayList<String> packageAccess = dbHelper.fetchPackageAccess(row.id);\r
- if (! PassEntry.checkPackageAccess(packageAccess, callingPackage)) {\r
- throw new Exception ("It is currently not permissible for this application to request this password.");\r
- }\r
- /*TODO: check if this package is in the package_access table corresponding to this password:\r
- * "Application 'org.syntaxpolice.ServiceTest' wants to access the\r
- password for 'opensocial'.\r
- [ ] Grant access this time.\r
- [ ] Always grant access.\r
- [ ] Always grant access to all passwords in org.syntaxpolice.ServiceTest category?\r
- [ ] Don't grant access"\r
- */\r
- }\r
- \r
- if (action.equals (CryptoIntents.ACTION_GET_PASSWORD)) {\r
- if (passExists) {\r
- username = ch.decrypt(row.username);\r
- password = ch.decrypt(row.password);\r
- } else throw new Exception ("Could not find password with the unique name: " + clearUniqueName);\r
-\r
- // stashing the return values:\r
- callbackIntent.putExtra(CryptoIntents.EXTRA_USERNAME, username);\r
- callbackIntent.putExtra(CryptoIntents.EXTRA_PASSWORD, password);\r
- } else if (action.equals (CryptoIntents.ACTION_SET_PASSWORD)) {\r
- String clearUsername = thisIntent.getStringExtra (CryptoIntents.EXTRA_USERNAME);\r
- String clearPassword = thisIntent.getStringExtra (CryptoIntents.EXTRA_PASSWORD);\r
- if (clearPassword == null) {\r
- throw new Exception ("PASSWORD extra must be set.");\r
- } \r
- row.username = ch.encrypt(clearUsername == null ? "" : clearUsername);\r
- row.password = ch.encrypt(clearPassword);\r
- // since this package is setting the password, it automatically gets access to it:\r
- if (passExists) { //exists already \r
- if (clearUsername.equals("") && clearPassword.equals("")) {\r
- dbHelper.deletePassword(row.id);\r
- } else {\r
- dbHelper.updatePassword(row.id, row);\r
- }\r
- } else {// add a new one\r
- row.uniqueName = uniqueName;\r
- row.description=uniqueName; //for display purposes\r
- // TODO: Should we send these fields in extras also? If so, probably not using \r
- // the openintents namespace? If another application were to implement a keystore\r
- // they might not want to use these.\r
- row.website = ""; \r
- row.note = "";\r
-\r
- String category = ch.encrypt("Application Data");\r
- CategoryEntry c = new CategoryEntry();\r
- c.name = category;\r
- row.category = dbHelper.addCategory(c); //doesn't add category if it already exists\r
- row.id = dbHelper.addPassword(row);\r
- } \r
- dbHelper.addPackageAccess(row.id, callingPackage);//already encrypted\r
- \r
- }\r
- return (callbackIntent);\r
- }\r
- \r
- @Override\r
- protected void onPause() {\r
- super.onPause();\r
-\r
- if (debug)\r
- Log.d(TAG, "onPause()");\r
-\r
- releaseService();\r
- dbHelper.close();\r
- dbHelper = null;\r
- }\r
-\r
- @Override\r
- protected void onResume() {\r
- super.onPause();\r
-\r
- if (debug)\r
- Log.d(TAG, "onResume()");\r
- if (dbHelper == null) {\r
- dbHelper = new DBHelper(this);\r
- }\r
-\r
- }\r
- \r
- @Override\r
- protected void onDestroy() {\r
- super.onDestroy();\r
- releaseService();\r
- }\r
-\r
-\r
- //--------------------------- service stuff ------------\r
- private void initService() {\r
-\r
- boolean isLocal = isIntentLocal();\r
- conn = new ServiceDispatchConnection(isLocal);\r
- Intent i = new Intent();\r
- i.setClass(this, ServiceDispatchImpl.class);\r
- startService(i);\r
- bindService( i, conn, Context.BIND_AUTO_CREATE);\r
- }\r
-\r
- /**\r
- * @return\r
- */\r
- private boolean isIntentLocal() {\r
- String action = getIntent().getAction();\r
- boolean isLocal = action == null || action.equals(Intent.ACTION_MAIN);\r
- return isLocal;\r
- }\r
-\r
- private void releaseService() {\r
- if (conn != null ) {\r
- unbindService( conn );\r
- conn = null;\r
- }\r
- }\r
-\r
- class ServiceDispatchConnection implements ServiceConnection\r
- {\r
- boolean askPassIsLocal = false;\r
- public ServiceDispatchConnection (Boolean isLocal) {\r
- askPassIsLocal = isLocal;\r
- }\r
- public void onServiceConnected(ComponentName className, \r
- IBinder boundService )\r
- {\r
- service = ServiceDispatch.Stub.asInterface((IBinder)boundService);\r
- \r
- boolean promptforpassword = getIntent().getBooleanExtra(CryptoIntents.EXTRA_PROMPT, true);\r
- if (debug) Log.d(TAG, "Prompt for password: " + promptforpassword);\r
- try {\r
- if (service.getPassword() == null) {\r
- if (promptforpassword) {\r
- if (debug) Log.d(TAG, "ask for password");\r
- // the service isn't running\r
- Intent askPass = new Intent(getApplicationContext(),\r
- AskPassword.class);\r
- \r
- final Intent thisIntent = getIntent();\r
- String inputBody = thisIntent.getStringExtra (CryptoIntents.EXTRA_TEXT);\r
- \r
- askPass.putExtra (CryptoIntents.EXTRA_TEXT, inputBody);\r
- askPass.putExtra (AskPassword.EXTRA_IS_LOCAL, askPassIsLocal);\r
- //TODO: Is there a way to make sure all the extras are set? \r
- startActivityForResult (askPass, REQUEST_CODE_ASK_PASSWORD);\r
- } else {\r
- if (debug) Log.d(TAG, "ask for password");\r
- // Don't prompt but cancel\r
- setResult(RESULT_CANCELED);\r
- finish();\r
- }\r
-\r
- } else {\r
- if (debug) Log.d(TAG, "service already started");\r
- //service already started, so don't need to ask pw.\r
-\r
- boolean externalAccess = mPreferences.getBoolean(Preferences.PREFERENCE_ALLOW_EXTERNAL_ACCESS, false);\r
- \r
- if (askPassIsLocal || externalAccess) {\r
- masterKey = service.getPassword();\r
- actionDispatch();\r
- } else {\r
- showDialogAllowExternalAccess();\r
- }\r
- }\r
- } catch (RemoteException e) {\r
- Log.d(TAG, e.toString());\r
- }\r
- Log.d( TAG,"onServiceConnected" );\r
- }\r
- \r
- public void onServiceDisconnected(ComponentName className)\r
- {\r
- service = null;\r
- Log.d( TAG,"onServiceDisconnected" );\r
- }\r
- };\r
-\r
-}\r
+ @Override\r
+ public void onCreate(Bundle icicle) {\r
+ super.onCreate(icicle);\r
+ final Intent thisIntent = getIntent();\r
+ final String action = thisIntent.getAction();\r
+ if (action == null || action.equals(Intent.ACTION_MAIN)){\r
+ //TODO: When launched from debugger, action is null. Other such cases?\r
+ Intent i = new Intent(getApplicationContext(),\r
+ IntentHandler.class);\r
+ startActivity(i);\r
+ } // otherwise, do not start intents, those must be protected by permissions\r
+ finish();\r
+ }\r
+}
\ No newline at end of file
--- /dev/null
+/* $Id$\r
+ * \r
+ * Copyright 2007-2008 Steven Osborn\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package org.openintents.safe;\r
+\r
+\r
+import java.util.ArrayList;\r
+\r
+import org.openintents.intents.CryptoIntents;\r
+import org.openintents.safe.dialog.DialogHostingActivity;\r
+import org.openintents.safe.service.ServiceDispatch;\r
+import org.openintents.safe.service.ServiceDispatchImpl;\r
+\r
+import android.app.Activity;\r
+import android.content.ComponentName;\r
+import android.content.Context;\r
+import android.content.Intent;\r
+import android.content.ServiceConnection;\r
+import android.content.SharedPreferences;\r
+import android.os.Bundle;\r
+import android.os.IBinder;\r
+import android.os.RemoteException;\r
+import android.preference.PreferenceManager;\r
+import android.util.Log;\r
+import android.widget.Toast;\r
+\r
+\r
+/**\r
+ * FrontDoor Activity\r
+ * \r
+ * This activity just acts as a splash screen and gets the password from the\r
+ * user that will be used to decrypt/encrypt password entries.\r
+ * \r
+ * @author Steven Osborn - http://steven.bitsetters.com\r
+ */\r
+public class IntentHandler extends Activity {\r
+\r
+ private static final boolean debug = !false;\r
+ private static String TAG = "IntentHandler";\r
+ \r
+ private static final int REQUEST_CODE_ASK_PASSWORD = 1;\r
+ private static final int REQUEST_CODE_ALLOW_EXTERNAL_ACCESS = 2;\r
+ \r
+\r
+ private DBHelper dbHelper;\r
+ private String salt;\r
+ private String masterKey;\r
+ private CryptoHelper ch;\r
+\r
+ // service elements\r
+ private ServiceDispatch service;\r
+ private ServiceDispatchConnection conn;\r
+ private Intent mServiceIntent;\r
+\r
+ SharedPreferences mPreferences;\r
+ //public static String SERVICE_NAME = "org.openintents.safe.service.ServiceDispatchImpl";\r
+ \r
+ /** Called when the activity is first created. */\r
+ @Override\r
+ public void onCreate(Bundle icicle) {\r
+ super.onCreate(icicle);\r
+ mServiceIntent = null;\r
+ mPreferences = PreferenceManager.getDefaultSharedPreferences(this);\r
+ initService(); // start up the PWS service so other applications can query.\r
+ }\r
+\r
+ \r
+ //currently only handles result from askPassword function.\r
+ protected void onActivityResult (int requestCode, int resultCode, Intent data) {\r
+ if (debug) Log.d(TAG, "onActivityResult: requestCode == " + requestCode + ", resultCode == " + resultCode);\r
+ \r
+ switch (requestCode) {\r
+ case REQUEST_CODE_ASK_PASSWORD:\r
+ if (resultCode == RESULT_OK) {\r
+\r
+ if (service == null) {\r
+ mServiceIntent = data;\r
+ // setServiceParametersFromExtrasAndDispatchAction() is called in onServiceConnected.\r
+ return;\r
+ }\r
+ \r
+ setServiceParametersFromExtrasAndDispatchAction(data);\r
+ \r
+ } else { // resultCode == RESULT_CANCELED\r
+ setResult(RESULT_CANCELED);\r
+ finish();\r
+ }\r
+ break;\r
+ case REQUEST_CODE_ALLOW_EXTERNAL_ACCESS:\r
+ // Check again, regardless whether user pressed "OK" or "Cancel".\r
+ // Also, DialogHostingActivity never returns a resultCode different than\r
+ // RESULT_CANCELED.\r
+ if (service == null) {\r
+ if (debug) Log.i(TAG, "actionDispatch called later");\r
+ // actionDispatch() is called in onServiceConnected.\r
+ } else if (salt == null) {\r
+ try {\r
+ salt = service.getSalt();\r
+ masterKey = service.getPassword();\r
+ actionDispatch();\r
+ } catch (RemoteException e) {\r
+ Log.d(TAG, e.toString());\r
+ // Not successful...\r
+ finish();\r
+ }\r
+ } else {\r
+ if (debug) Log.i(TAG, "actionDispatch called right now");\r
+ actionDispatch();\r
+ }\r
+ break;\r
+ }\r
+ \r
+ }\r
+\r
+ /**\r
+ * @param data\r
+ */\r
+ private void setServiceParametersFromExtrasAndDispatchAction(Intent data) {\r
+ salt = data.getStringExtra("salt");\r
+ masterKey = data.getStringExtra("masterKey");\r
+ String timeout = mPreferences.getString(Preferences.PREFERENCE_LOCK_TIMEOUT, Preferences.PREFERENCE_LOCK_TIMEOUT_DEFAULT_VALUE); \r
+ int timeoutMinutes=5; // default to 5\r
+ try {\r
+ timeoutMinutes = Integer.valueOf(timeout);\r
+ } catch (NumberFormatException e) {\r
+ Log.d(TAG,"why is lock_timeout busted?");\r
+ }\r
+ \r
+ try {\r
+ service.setTimeoutMinutes(timeoutMinutes);\r
+ service.setSalt(salt);\r
+ service.setPassword(masterKey); // should already be connected.\r
+ } catch (RemoteException e1) {\r
+ // TODO Auto-generated catch block\r
+ e1.printStackTrace();\r
+ }\r
+\r
+ boolean externalAccess = mPreferences.getBoolean(Preferences.PREFERENCE_ALLOW_EXTERNAL_ACCESS, false);\r
+ boolean isLocal = isIntentLocal();\r
+ \r
+ if (isLocal || externalAccess) {\r
+ actionDispatch();\r
+ } else {\r
+ // ask first\r
+ if (debug) Log.d(TAG, "onActivityResult: showDialogAllowExternalAccess()");\r
+ showDialogAllowExternalAccess();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * \r
+ */\r
+ private void showDialogAllowExternalAccess() {\r
+ Intent i = new Intent(this, DialogHostingActivity.class);\r
+ i.putExtra(DialogHostingActivity.EXTRA_DIALOG_ID, DialogHostingActivity.DIALOG_ID_ALLOW_EXTERNAL_ACCESS);\r
+ this.startActivityForResult(i, REQUEST_CODE_ALLOW_EXTERNAL_ACCESS);\r
+ }\r
+ \r
+ protected void actionDispatch () { \r
+ final Intent thisIntent = getIntent();\r
+ final String action = thisIntent.getAction();\r
+ Intent callbackIntent = getIntent(); \r
+ int callbackResult = RESULT_CANCELED;\r
+ PassList.setSalt(salt);\r
+ CategoryList.setSalt(salt);\r
+ PassList.setMasterKey(masterKey);\r
+ CategoryList.setMasterKey(masterKey);\r
+ if (ch == null) {\r
+ ch = new CryptoHelper();\r
+ }\r
+ try {\r
+ ch.init(CryptoHelper.EncryptionMedium,salt);\r
+ ch.setPassword(masterKey);\r
+ } catch (CryptoHelperException e1) {\r
+ e1.printStackTrace();\r
+ Toast.makeText(this, getString(R.string.crypto_error)\r
+ + e1.getMessage(), Toast.LENGTH_SHORT).show();\r
+ return;\r
+ }\r
+\r
+ boolean externalAccess = mPreferences.getBoolean(Preferences.PREFERENCE_ALLOW_EXTERNAL_ACCESS, false);\r
+\r
+ if (action == null || action.equals(Intent.ACTION_MAIN)){\r
+ //TODO: When launched from debugger, action is null. Other such cases?\r
+ Intent i = new Intent(getApplicationContext(),\r
+ CategoryList.class);\r
+ startActivity(i);\r
+ } else if (externalAccess){\r
+\r
+ // which action?\r
+ if (action.equals (CryptoIntents.ACTION_ENCRYPT)) {\r
+ callbackResult = encryptIntent(thisIntent, callbackIntent);\r
+ } else if (action.equals (CryptoIntents.ACTION_DECRYPT)) {\r
+ callbackResult = decryptIntent(thisIntent, callbackIntent);\r
+ } else if (action.equals (CryptoIntents.ACTION_GET_PASSWORD)\r
+ || action.equals (CryptoIntents.ACTION_SET_PASSWORD)) {\r
+ try {\r
+ callbackIntent = getSetPassword (thisIntent, callbackIntent);\r
+ callbackResult = RESULT_OK;\r
+ } catch (CryptoHelperException e) {\r
+ Log.e(TAG, e.toString(), e);\r
+ Toast.makeText(IntentHandler.this,\r
+ "There was a crypto error while retreiving the requested password: " + e.getMessage(),\r
+ Toast.LENGTH_SHORT).show();\r
+ } catch (Exception e) {\r
+ Log.e(TAG, e.toString(), e);\r
+ //TODO: Turn this into a proper error dialog.\r
+ Toast.makeText(IntentHandler.this,\r
+ "There was an error in retreiving the requested password: " + e.getMessage(),\r
+ Toast.LENGTH_SHORT).show();\r
+ }\r
+ }\r
+ setResult(callbackResult, callbackIntent);\r
+ }\r
+ finish();\r
+ }\r
+\r
+\r
+ /**\r
+ * Encrypt all supported fields in the intent and return the result in callbackIntent.\r
+ * \r
+ * This is supposed to be called by outside functions, so we encrypt using a random session key.\r
+ * \r
+ * @param thisIntent\r
+ * @param callbackIntent\r
+ * @return callbackResult\r
+ */\r
+ private int encryptIntent(final Intent thisIntent, Intent callbackIntent) {\r
+ if (debug)\r
+ Log.d(TAG, "encryptIntent()");\r
+ \r
+ int callbackResult = RESULT_CANCELED;\r
+ try {\r
+ if (thisIntent.hasExtra(CryptoIntents.EXTRA_TEXT)) {\r
+ // get the body text out of the extras.\r
+ String inputBody = thisIntent.getStringExtra (CryptoIntents.EXTRA_TEXT);\r
+ String outputBody = "";\r
+ outputBody = ch.encryptWithSessionKey (inputBody);\r
+ // stash the encrypted text in the extra\r
+ callbackIntent.putExtra(CryptoIntents.EXTRA_TEXT, outputBody);\r
+ }\r
+ \r
+ if (thisIntent.hasExtra(CryptoIntents.EXTRA_TEXT_ARRAY)) {\r
+ String[] in = thisIntent.getStringArrayExtra(CryptoIntents.EXTRA_TEXT_ARRAY);\r
+ String[] out = new String[in.length];\r
+ for (int i = 0; i < in.length; i++) {\r
+ if (in[i] != null) {\r
+ out[i] = ch.encryptWithSessionKey(in[i]);\r
+ }\r
+ }\r
+ callbackIntent.putExtra(CryptoIntents.EXTRA_TEXT_ARRAY, out);\r
+ }\r
+ \r
+ // Support for binary fields could be added here (like images?)\r
+ \r
+ callbackResult = RESULT_OK;\r
+ } catch (CryptoHelperException e) {\r
+ Log.e(TAG, e.toString());\r
+ }\r
+ return callbackResult;\r
+ }\r
+\r
+ /**\r
+ * Decrypt all supported fields in the intent and return the result in callbackIntent.\r
+ * \r
+ * @param thisIntent\r
+ * @param callbackIntent\r
+ * @return callbackResult\r
+ */\r
+ private int decryptIntent(final Intent thisIntent, Intent callbackIntent) {\r
+ int callbackResult = RESULT_CANCELED;\r
+ try {\r
+\r
+ if (thisIntent.hasExtra(CryptoIntents.EXTRA_TEXT)) {\r
+ // get the body text out of the extras.\r
+ String inputBody = thisIntent.getStringExtra (CryptoIntents.EXTRA_TEXT);\r
+ String outputBody = "";\r
+ outputBody = ch.decryptWithSessionKey (inputBody);\r
+ // stash the encrypted text in the extra\r
+ callbackIntent.putExtra(CryptoIntents.EXTRA_TEXT, outputBody);\r
+ }\r
+ \r
+ if (thisIntent.hasExtra(CryptoIntents.EXTRA_TEXT_ARRAY)) {\r
+ String[] in = thisIntent.getStringArrayExtra(CryptoIntents.EXTRA_TEXT_ARRAY);\r
+ String[] out = new String[in.length];\r
+ for (int i = 0; i < in.length; i++) {\r
+ if (in[i] != null) {\r
+ out[i] = ch.decryptWithSessionKey(in[i]);\r
+ }\r
+ }\r
+ callbackIntent.putExtra(CryptoIntents.EXTRA_TEXT_ARRAY, out);\r
+ }\r
+ \r
+ // Support for binary fields could be added here (like images?)\r
+ \r
+ callbackResult = RESULT_OK;\r
+ } catch (CryptoHelperException e) {\r
+ Log.e(TAG, e.toString());\r
+ }\r
+ return callbackResult;\r
+ }\r
+ \r
+ private Intent getSetPassword (Intent thisIntent, Intent callbackIntent) throws CryptoHelperException, Exception {\r
+ String action = thisIntent.getAction();\r
+ //TODO: Consider moving this elsewhere. Maybe DBHelper? Also move strings to resource.\r
+ //DBHelper dbHelper = new DBHelper(this);\r
+ if (debug) Log.d(TAG, "GET_or_SET_PASSWORD");\r
+ String username = null;\r
+ String password = null;\r
+\r
+ String clearUniqueName = thisIntent.getStringExtra (CryptoIntents.EXTRA_UNIQUE_NAME);\r
+\r
+ if (clearUniqueName == null) throw new Exception ("EXTRA_UNIQUE_NAME not set.");\r
+ \r
+ if (dbHelper == null) {\r
+ // Need to open DBHelper here, because\r
+ // onResume() is called after onActivityResult()\r
+ dbHelper = new DBHelper(this);\r
+ }\r
+\r
+ String uniqueName = ch.encrypt(clearUniqueName);\r
+ PassEntry row = dbHelper.fetchPassword(uniqueName);\r
+ boolean passExists = row.id > 1;\r
+\r
+ String clearCallingPackage = getCallingPackage();\r
+ String callingPackage = ch.encrypt (clearCallingPackage);\r
+ if (passExists) { // check for permission to access this password.\r
+ ArrayList<String> packageAccess = dbHelper.fetchPackageAccess(row.id);\r
+ if (! PassEntry.checkPackageAccess(packageAccess, callingPackage)) {\r
+ throw new Exception ("It is currently not permissible for this application to request this password.");\r
+ }\r
+ /*TODO: check if this package is in the package_access table corresponding to this password:\r
+ * "Application 'org.syntaxpolice.ServiceTest' wants to access the\r
+ password for 'opensocial'.\r
+ [ ] Grant access this time.\r
+ [ ] Always grant access.\r
+ [ ] Always grant access to all passwords in org.syntaxpolice.ServiceTest category?\r
+ [ ] Don't grant access"\r
+ */\r
+ }\r
+ \r
+ if (action.equals (CryptoIntents.ACTION_GET_PASSWORD)) {\r
+ if (passExists) {\r
+ username = ch.decrypt(row.username);\r
+ password = ch.decrypt(row.password);\r
+ } else throw new Exception ("Could not find password with the unique name: " + clearUniqueName);\r
+\r
+ // stashing the return values:\r
+ callbackIntent.putExtra(CryptoIntents.EXTRA_USERNAME, username);\r
+ callbackIntent.putExtra(CryptoIntents.EXTRA_PASSWORD, password);\r
+ } else if (action.equals (CryptoIntents.ACTION_SET_PASSWORD)) {\r
+ String clearUsername = thisIntent.getStringExtra (CryptoIntents.EXTRA_USERNAME);\r
+ String clearPassword = thisIntent.getStringExtra (CryptoIntents.EXTRA_PASSWORD);\r
+ if (clearPassword == null) {\r
+ throw new Exception ("PASSWORD extra must be set.");\r
+ } \r
+ row.username = ch.encrypt(clearUsername == null ? "" : clearUsername);\r
+ row.password = ch.encrypt(clearPassword);\r
+ // since this package is setting the password, it automatically gets access to it:\r
+ if (passExists) { //exists already \r
+ if (clearUsername.equals("") && clearPassword.equals("")) {\r
+ dbHelper.deletePassword(row.id);\r
+ } else {\r
+ dbHelper.updatePassword(row.id, row);\r
+ }\r
+ } else {// add a new one\r
+ row.uniqueName = uniqueName;\r
+ row.description=uniqueName; //for display purposes\r
+ // TODO: Should we send these fields in extras also? If so, probably not using \r
+ // the openintents namespace? If another application were to implement a keystore\r
+ // they might not want to use these.\r
+ row.website = ""; \r
+ row.note = "";\r
+\r
+ String category = ch.encrypt("Application Data");\r
+ CategoryEntry c = new CategoryEntry();\r
+ c.name = category;\r
+ row.category = dbHelper.addCategory(c); //doesn't add category if it already exists\r
+ row.id = 0; // entry is truly new\r
+ row.id = dbHelper.addPassword(row);\r
+ } \r
+ ArrayList<String> packageAccess = dbHelper.fetchPackageAccess(row.id);\r
+ if (! PassEntry.checkPackageAccess(packageAccess, callingPackage)) {\r
+ dbHelper.addPackageAccess(row.id, callingPackage);\r
+ }\r
+ \r
+ }\r
+ return (callbackIntent);\r
+ }\r
+ \r
+ @Override\r
+ protected void onPause() {\r
+ super.onPause();\r
+\r
+ if (debug)\r
+ Log.d(TAG, "onPause()");\r
+\r
+ releaseService();\r
+ dbHelper.close();\r
+ dbHelper = null;\r
+ }\r
+\r
+ @Override\r
+ protected void onResume() {\r
+ super.onPause();\r
+\r
+ if (debug)\r
+ Log.d(TAG, "onResume()");\r
+ if (dbHelper == null) {\r
+ dbHelper = new DBHelper(this);\r
+ }\r
+\r
+ }\r
+ \r
+ @Override\r
+ protected void onDestroy() {\r
+ super.onDestroy();\r
+ releaseService();\r
+ }\r
+\r
+\r
+ //--------------------------- service stuff ------------\r
+ private void initService() {\r
+\r
+ boolean isLocal = isIntentLocal();\r
+ conn = new ServiceDispatchConnection(isLocal);\r
+ Intent i = new Intent();\r
+ i.setClass(this, ServiceDispatchImpl.class);\r
+ startService(i);\r
+ bindService( i, conn, Context.BIND_AUTO_CREATE);\r
+ }\r
+\r
+ /**\r
+ * @return\r
+ */\r
+ private boolean isIntentLocal() {\r
+ String action = getIntent().getAction();\r
+ boolean isLocal = action == null || action.equals(Intent.ACTION_MAIN);\r
+ return isLocal;\r
+ }\r
+\r
+ private void releaseService() {\r
+ if (conn != null ) {\r
+ unbindService( conn );\r
+ conn = null;\r
+ }\r
+ }\r
+\r
+ class ServiceDispatchConnection implements ServiceConnection\r
+ {\r
+ boolean askPassIsLocal = false;\r
+ public ServiceDispatchConnection (Boolean isLocal) {\r
+ askPassIsLocal = isLocal;\r
+ }\r
+ public void onServiceConnected(ComponentName className, \r
+ IBinder boundService )\r
+ {\r
+ service = ServiceDispatch.Stub.asInterface((IBinder)boundService);\r
+ \r
+ if (mServiceIntent != null) {\r
+ setServiceParametersFromExtrasAndDispatchAction(mServiceIntent);\r
+ mServiceIntent = null;\r
+ return;\r
+ }\r
+ \r
+ boolean promptforpassword = getIntent().getBooleanExtra(CryptoIntents.EXTRA_PROMPT, true);\r
+ if (debug) Log.d(TAG, "Prompt for password: " + promptforpassword);\r
+ try {\r
+ if (service.getPassword() == null) {\r
+ if (promptforpassword) {\r
+ if (debug) Log.d(TAG, "ask for password");\r
+ // the service isn't running\r
+ Intent askPass = new Intent(getApplicationContext(),\r
+ AskPassword.class);\r
+ \r
+ final Intent thisIntent = getIntent();\r
+ String inputBody = thisIntent.getStringExtra (CryptoIntents.EXTRA_TEXT);\r
+ \r
+ askPass.putExtra (CryptoIntents.EXTRA_TEXT, inputBody);\r
+ askPass.putExtra (AskPassword.EXTRA_IS_LOCAL, askPassIsLocal);\r
+ //TODO: Is there a way to make sure all the extras are set? \r
+ startActivityForResult (askPass, REQUEST_CODE_ASK_PASSWORD);\r
+ } else {\r
+ if (debug) Log.d(TAG, "ask for password");\r
+ // Don't prompt but cancel\r
+ setResult(RESULT_CANCELED);\r
+ finish();\r
+ }\r
+\r
+ } else {\r
+ if (debug) Log.d(TAG, "service already started");\r
+ //service already started, so don't need to ask pw.\r
+\r
+ boolean externalAccess = mPreferences.getBoolean(Preferences.PREFERENCE_ALLOW_EXTERNAL_ACCESS, false);\r
+ \r
+ if (askPassIsLocal || externalAccess) {\r
+ salt = service.getSalt();\r
+ masterKey = service.getPassword();\r
+ actionDispatch();\r
+ } else {\r
+ if (debug) Log.d(TAG, "onServiceConnected: showDialogAllowExternalAccess()");\r
+ showDialogAllowExternalAccess();\r
+ }\r
+ }\r
+ } catch (RemoteException e) {\r
+ Log.d(TAG, e.toString());\r
+ }\r
+ if (debug) Log.d( TAG,"onServiceConnected" );\r
+ }\r
+ \r
+ public void onServiceDisconnected(ComponentName className)\r
+ {\r
+ service = null;\r
+ if (debug) Log.d( TAG,"onServiceDisconnected" );\r
+ }\r
+ };\r
+\r
+}\r
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);
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();
+ }
}
}
});
entryEdited = true;
if (RowId == null || RowId == -1) {
+ entry.id = 0; // brand new entry
RowId = dbHelper.addPassword(entry);
} else {
PassEntry storedEntry = dbHelper.fetchPassword (RowId);
public String username;\r
public String website;\r
public String uniqueName;\r
- // public ArrayList<String> packageAccess;\r
+ public ArrayList<String> packageAccess;\r
public String note;\r
public String plainPassword;\r
public String plainDescription;\r
private static Long CategoryId=null;
private Intent restartTimerIntent;
+ private static String salt;
private static String masterKey;
private List<PassEntry> rows;
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<String> items = new ArrayList<String>();
rows = dbHelper.fetchAllRows(CategoryId);
return super.onCreateOptionsMenu(menu);
}
+ static void setSalt(String saltIn) {
+ salt = saltIn;
+ }
+
+ static String getSalt() {
+ return salt;
+ }
+
static void setMasterKey(String key) {
masterKey = key;
}
@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();
}
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;
}
*/
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;
*/
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;
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;
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);
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;
String cryptUsername = row.username;
String cryptPass = row.password;
String cryptNote = row.note;
+ String cryptUniqueName = row.uniqueName;
+ ArrayList<String> packageAccess = dbHelper.fetchPackageAccess(row.id);
try {
descriptionText.setText(ch.decrypt(cryptDesc));
websiteText.setText(ch.decrypt(cryptWebsite));
} 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());
}
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) {
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;
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) {
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();
if (debug) Log.d(TAG,"onResume()");
- if (!CategoryList.isSignedIn()) {
+ if ((!firstTime) && (!CategoryList.isSignedIn())) {
Intent frontdoor = new Intent(this, FrontDoor.class);
startActivity(frontdoor);
finish();
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),
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 {
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()) {
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();
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);
- */
}
}
package org.openintents.safe;
import java.util.ArrayList;
+import java.util.Arrays;
import android.util.Log;
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<CategoryEntry> categoryEntries = new ArrayList<CategoryEntry>();
private PassEntry currentEntry = null;
+ private String currentRowID;
+ private String currentPackageAccess;
private ArrayList<PassEntry> passEntries = new ArrayList<PassEntry>();
private int totalEntries = 0;
public void setDate(String extractedDate) {
date = extractedDate;
}
+ public String getSalt() {
+ return salt;
+ }
+ public void setSalt(String extractedSalt) {
+ salt = extractedSalt;
+ }
public String getMasterKeyEncrypted() {
return masterKeyEncrypted;
}
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<String>(Arrays.asList(packages));
+ if (debug) Log.d(TAG,"packageAccess="+currentEntry.packageAccess.toString());
+ }
passEntries.add(currentEntry);
totalEntries++;
}
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) {
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;
+ }
+ }
}
// ===========================================================
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();
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;
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")) {
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;
}
}
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")) {
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")) {
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;
}
}
* <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));
}
}
}
\r
public class DialogHostingActivity extends Activity {\r
\r
- private static final String TAG = "FilenameActivity";\r
+ private static final String TAG = "DialogHostingActivity";\r
+ private static final boolean debug = false;\r
\r
public static final int DIALOG_ID_SAVE = 1;\r
public static final int DIALOG_ID_OPEN = 2;\r
public static final int DIALOG_ID_NO_FILE_MANAGER_AVAILABLE = 3;\r
public static final int DIALOG_ID_ALLOW_EXTERNAL_ACCESS = 4;\r
+ public static final int DIALOG_ID_FIRST_TIME_WARNING = 5;\r
\r
public static final String EXTRA_DIALOG_ID = "org.openintents.notepad.extra.dialog_id";\r
+\r
+ /**\r
+ * Whether dialog is simply pausing while hidden by another activity\r
+ * or when configuration changes.\r
+ * If this is false, then we can safely finish this activity if a dialog\r
+ * gets dismissed.\r
+ */\r
+ private boolean mIsPausing = false;\r
\r
EditText mEditText;\r
\r
@Override\r
protected void onCreate(Bundle savedInstanceState) {\r
super.onCreate(savedInstanceState);\r
+ if (debug) Log.d(TAG, "onCreate");\r
\r
Intent i = getIntent();\r
- if (i != null) {\r
+ if (i != null && savedInstanceState == null) {\r
+ if (debug) Log.d(TAG, "new dialog");\r
int dialogId = i.getIntExtra(EXTRA_DIALOG_ID, 0);\r
switch (dialogId) {\r
case DIALOG_ID_SAVE:\r
Log.i(TAG, "Show allow access dialog");\r
showDialog(DIALOG_ID_ALLOW_EXTERNAL_ACCESS);\r
break;\r
+ case DIALOG_ID_FIRST_TIME_WARNING:\r
+ Log.i(TAG, "Show first time warning dialog");\r
+ showDialog(DIALOG_ID_FIRST_TIME_WARNING);\r
+ break;\r
}\r
}\r
- \r
- \r
}\r
\r
\r
\r
@Override\r
protected Dialog onCreateDialog(int id) {\r
+ if (debug) Log.d(TAG, "onCreateDialog");\r
\r
+ Dialog dialog = null;\r
+ \r
switch (id) {\r
case DIALOG_ID_SAVE:\r
- return new FilenameDialog(this);\r
+ dialog = new FilenameDialog(this);\r
+ break;\r
case DIALOG_ID_OPEN:\r
- return new FilenameDialog(this);\r
+ dialog = new FilenameDialog(this);\r
+ break;\r
case DIALOG_ID_NO_FILE_MANAGER_AVAILABLE:\r
Log.i(TAG, "fmd - create");\r
- return new GetFromMarketDialog(this, \r
+ dialog = new GetFromMarketDialog(this, \r
RD.string.filemanager_not_available,\r
RD.string.filemanager_get_oi_filemanager,\r
RD.string.filemanager_market_uri);\r
+ break;\r
case DIALOG_ID_ALLOW_EXTERNAL_ACCESS:\r
- return new AllowExternalAccessDialog(this);\r
+ dialog = new AllowExternalAccessDialog(this);\r
+ break;\r
+ case DIALOG_ID_FIRST_TIME_WARNING:\r
+ dialog = new FirstTimeWarningDialog(this);\r
+ break;\r
+ }\r
+ if (dialog == null) {\r
+ dialog = super.onCreateDialog(id);\r
+ }\r
+ if (dialog != null) {\r
+ dialog.setOnDismissListener(mDismissListener);\r
}\r
- return null;\r
+ return dialog;\r
}\r
\r
@Override\r
protected void onPrepareDialog(int id, Dialog dialog) {\r
- FilenameDialog fd;\r
+ super.onPrepareDialog(id, dialog);\r
\r
- dialog.setOnDismissListener(mDismissListener);\r
+ if (debug) Log.d(TAG, "onPrepareDialog");\r
+ \r
+ //dialog.setOnDismissListener(mDismissListener);\r
\r
switch (id) {\r
case DIALOG_ID_SAVE:\r
- fd = (FilenameDialog) dialog;\r
- //fd.setTitle(R.string.menu_save_to_sdcard);\r
- \r
break;\r
case DIALOG_ID_OPEN:\r
- fd = (FilenameDialog) dialog;\r
- //fd.setTitle(R.string.menu_open_from_sdcard);\r
break;\r
- \r
case DIALOG_ID_NO_FILE_MANAGER_AVAILABLE:\r
- Log.i(TAG, "fmd - prepare");\r
- /*\r
- GetFileManagerFromMarketDialog gd = (GetFileManagerFromMarketDialog) dialog;\r
- gd.setMessageResource(R.string.filemanager_not_available);\r
- gd.setInfoResources(R.string.filemanager_get_oi_filemanager, \r
- R.string.filemanager_market_uri, \r
- R.drawable.ic_launcher_folder_small, \r
- R.string.update_error);\r
- */\r
break;\r
}\r
}\r
OnDismissListener mDismissListener = new OnDismissListener() {\r
\r
public void onDismiss(DialogInterface dialoginterface) {\r
- DialogHostingActivity.this.finish();\r
+ if (debug) Log.d(TAG, "Dialog dismissed. Pausing: " + mIsPausing);\r
+ if (!mIsPausing) {\r
+ if (debug) Log.d(TAG, "finish");\r
+ // Dialog has been dismissed by user.\r
+ DialogHostingActivity.this.finish();\r
+ } else {\r
+ // Probably just a screen orientation change. Don't finish yet.\r
+ // Dialog has been dismissed by system.\r
+ }\r
}\r
\r
};\r
+\r
+ @Override\r
+ protected void onSaveInstanceState(Bundle outState) {\r
+ super.onSaveInstanceState(outState);\r
+ if (debug) Log.d(TAG, "onSaveInstanceState");\r
+ mIsPausing = true;\r
+ if (debug) Log.d(TAG, "onSaveInstanceState. Pausing: " + mIsPausing);\r
+ }\r
+ \r
+ \r
\r
}\r
--- /dev/null
+package org.openintents.safe.dialog;\r
+\r
+import org.openintents.safe.Preferences;\r
+import org.openintents.safe.R;\r
+\r
+import android.app.AlertDialog;\r
+import android.content.Context;\r
+import android.content.DialogInterface;\r
+import android.content.SharedPreferences;\r
+import android.content.DialogInterface.OnClickListener;\r
+import android.os.Bundle;\r
+import android.preference.PreferenceManager;\r
+import android.view.LayoutInflater;\r
+import android.view.View;\r
+import android.widget.CheckBox;\r
+\r
+public class FirstTimeWarningDialog extends AlertDialog implements OnClickListener {\r
+ private static final String TAG = "FilenameDialog";\r
+\r
+ private static final String BUNDLE_TAGS = "tags";\r
+ \r
+ protected static final int DIALOG_ID_NO_FILE_MANAGER_AVAILABLE = 2;\r
+ \r
+ Context mContext;\r
+ \r
+ CheckBox mCheckBox;\r
+ \r
+ public FirstTimeWarningDialog(Context context) {\r
+ super(context);\r
+ mContext = context;\r
+ \r
+ setTitle(context.getText(R.string.dialog_title_first_time_warning));\r
+ setButton(context.getText(android.R.string.ok), (OnClickListener) null);\r
+ //setButton2(context.getText(android.R.string.cancel), (OnClickListener) null);\r
+ setIcon(android.R.drawable.ic_dialog_alert);\r
+ setMessage(context.getText(R.string.dialog_summary_first_time_warning));\r
+\r
+ }\r
+ \r
+ \r
+ public void onClick(DialogInterface dialog, int which) {\r
+ if (which == BUTTON1) {\r
+ // User pressed OK\r
+ }\r
+ \r
+ }\r
+}\r
package org.openintents.safe.service;\r
\r
interface ServiceDispatch {\r
+ void setSalt (String saltIn);\r
+ String getSalt ();\r
void setPassword (String masterPasswordIn);\r
String getPassword ();\r
String encrypt (String clearText);\r
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;
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();
}
}
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
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();
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);