Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
<string name="no_volumes">No EncFS volumes configured. Choose \&quot;Import Volume\&quot; or \&quot;Create Volume\&quot; from the menu to add volumes.</string>
<string name="menu_select">Select</string>
<string name="pwd_dialog_title_str">Enter password:</string>
<string name="pin_dialog_title_str">Enter PIN:</string>
<string name="set_pin_dialog_title_str">Set PIN (leave empty for no PIN):</string>
<string name="error_wrong_pin">Wrong PIN entered!</string>
<string name="error_wrong_pin_three_times">Wrong PIN entered three times! Volume password has been deleted.</string>
<string name="btn_ok_str">OK</string>
<string name="pbkdf_dialog_title_str">Deriving password key</string>
<string name="pbkdf_dialog_msg_str">Can take VERY long for some volumes. Consider enabling password key caching in Settings.</string>
Expand Down
90 changes: 85 additions & 5 deletions src/org/mrpdaemon/android/encdroid/DBHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public class DBHelper extends SQLiteOpenHelper {
public static final String DB_NAME = "volume.db";

// Database version
public static final int DB_VERSION = 4;
public static final int DB_VERSION = 5;

// Volume table name
public static final String DB_TABLE = "volumes";
Expand All @@ -51,6 +51,8 @@ public class DBHelper extends SQLiteOpenHelper {
public static final String DB_COL_CONFIGPATH = "configPath";
public static final String DB_COL_TYPE = "type";
public static final String DB_COL_KEY = "key";
public static final String DB_COL_PIN = "pin";
public static final String DB_COL_PINATTEMPTS = "pinAttempts"; // counts unsuccessful PIN attempts

private static final String[] NO_ARGS = {};

Expand All @@ -67,19 +69,25 @@ public DBHelper(EDApplication application) {
public void onCreate(SQLiteDatabase db) {
String sqlCmd = "CREATE TABLE " + DB_TABLE + " (" + DB_COL_ID
+ " int primary key, " + DB_COL_NAME + " text, " + DB_COL_PATH
+ " text, " + DB_COL_KEY + " text, " + DB_COL_TYPE + " int, "
+ " text, " + DB_COL_KEY + " text, " + DB_COL_PIN + " text, "+ DB_COL_PINATTEMPTS + " int, "+ DB_COL_TYPE + " int, "
+ DB_COL_CONFIGPATH + " text)";
Log.d(TAG, "onCreate() executing SQL: " + sqlCmd);
db.execSQL(sqlCmd);
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Adding column DB_COL_CONFIGPATH on upgrade
if (oldVersion == 3) {
// Adding missing columns on upgrade
if (oldVersion == 3 || oldVersion == 4) {
Log.d(TAG, "onUpgrade() Upgrading DB");
if(oldVersion == 3) {
db.execSQL("ALTER TABLE " + DB_TABLE + " ADD COLUMN "
+ DB_COL_CONFIGPATH + " TEXT");
}
db.execSQL("ALTER TABLE " + DB_TABLE + " ADD COLUMN "
+ DB_COL_PIN + " TEXT");
db.execSQL("ALTER TABLE " + DB_TABLE + " ADD COLUMN "
+ DB_COL_CONFIGPATH + " TEXT");
+ DB_COL_PINATTEMPTS + " INT");
} else {
db.execSQL("DROP TABLE IF EXISTS " + DB_TABLE);
Log.d(TAG, "onUpgrade() recreating DB");
Expand Down Expand Up @@ -140,7 +148,29 @@ public void cacheKey(Volume volume, byte[] key) {
db.update(DB_TABLE, values, DB_COL_NAME + "=? AND " + DB_COL_PATH
+ "=?", new String[] { volume.getName(), volume.getPath() });
}

public void setPIN(Volume volume, String pin) {
SQLiteDatabase db = getWritableDatabase();

Log.d(TAG, "setPIN() for volume" + volume.getName());

ContentValues values = new ContentValues();
values.put(DB_COL_PIN, pin);
db.update(DB_TABLE, values, DB_COL_NAME + "=? AND " + DB_COL_PATH
+ "=?", new String[] { volume.getName(), volume.getPath() });
}

public void setPINAttempts(Volume volume, int newVal) {
SQLiteDatabase db = getWritableDatabase();

Log.d(TAG, "setPINAttempts() " + volume.getName() + " to " + newVal);

ContentValues values = new ContentValues();
values.put(DB_COL_PINATTEMPTS, newVal);
db.update(DB_TABLE, values, DB_COL_NAME + "=? AND " + DB_COL_PATH
+ "=?", new String[] { volume.getName(), volume.getPath() });
}

public void clearKey(Volume volume) {
SQLiteDatabase db = getWritableDatabase();

Expand All @@ -151,6 +181,18 @@ public void clearKey(Volume volume) {
db.update(DB_TABLE, values, DB_COL_NAME + "=? AND " + DB_COL_PATH
+ "=?", new String[] { volume.getName(), volume.getPath() });
}

public void clearPIN(Volume volume) {
SQLiteDatabase db = getWritableDatabase();

Log.d(TAG, "clearPIN() for volume" + volume.getName());

ContentValues values = new ContentValues();
values.putNull(DB_COL_PIN);
values.putNull(DB_COL_PINATTEMPTS);
db.update(DB_TABLE, values, DB_COL_NAME + "=? AND " + DB_COL_PATH
+ "=?", new String[] { volume.getName(), volume.getPath() });
}

public void clearAllKeys() {
SQLiteDatabase db = getWritableDatabase();
Expand All @@ -159,6 +201,15 @@ public void clearAllKeys() {

db.execSQL("UPDATE " + DB_TABLE + " SET " + DB_COL_KEY + " = NULL");
}

public void clearAllPINs() {
SQLiteDatabase db = getWritableDatabase();

Log.d(TAG, "clearAllPINs()");

db.execSQL("UPDATE " + DB_TABLE + " SET " + DB_COL_PIN + " = NULL");
db.execSQL("UPDATE " + DB_TABLE + " SET " + DB_COL_PINATTEMPTS + " = 0");
}

public byte[] getCachedKey(Volume volume) {
SQLiteDatabase db = getReadableDatabase();
Expand All @@ -174,9 +225,38 @@ public byte[] getCachedKey(Volume volume) {
return Base64.decode(keyStr, Base64.DEFAULT);
}
}
return null;
}

public String getPIN(Volume volume) {
SQLiteDatabase db = getReadableDatabase();

Cursor cursor = db.query(DB_TABLE, NO_ARGS, DB_COL_NAME + "=? AND "
+ DB_COL_PATH + "=?",
new String[] { volume.getName(), volume.getPath() }, null,
null, null);

if (cursor.moveToFirst()) {
String pin = cursor.getString(cursor.getColumnIndex(DB_COL_PIN));
return pin;
}
return null;
}

public int getPINAttempts(Volume volume) {
SQLiteDatabase db = getReadableDatabase();

Cursor cursor = db.query(DB_TABLE, NO_ARGS, DB_COL_NAME + "=? AND "
+ DB_COL_PATH + "=?",
new String[] { volume.getName(), volume.getPath() }, null,
null, null);

if (cursor.moveToFirst()) {
int pinAttempts = cursor.getInt((cursor.getColumnIndex(DB_COL_PINATTEMPTS)));
return pinAttempts;
}
return 0;
}

public List<Volume> getVolumes() {
ArrayList<Volume> volumes = new ArrayList<Volume>();
Expand Down
2 changes: 2 additions & 0 deletions src/org/mrpdaemon/android/encdroid/EDPreferenceActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ public void onSharedPreferenceChanged(SharedPreferences prefs,
Log.d(TAG, "Key caching disabled, clearing cached keys.");
// Need to clear all cached keys
mApp.getDbHelper().clearAllKeys();
// Might need to clear all saved PINs as well
mApp.getDbHelper().clearAllPINs();
} else {
Log.d(TAG, "Key caching enabled.");
}
Expand Down
98 changes: 88 additions & 10 deletions src/org/mrpdaemon/android/encdroid/VolumeListActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ public class VolumeListActivity extends ListActivity implements
private final static int DIALOG_VOL_DELETE = 5;
private final static int DIALOG_FS_TYPE = 6;
private final static int DIALOG_ERROR = 7;
private final static int DIALOG_VOL_PIN = 8;
private final static int DIALOG_VOL_SETPIN = 9;

// Volume operation types
private final static int VOLUME_OP_IMPORT = 0;
Expand Down Expand Up @@ -336,6 +338,20 @@ protected void onPrepareDialog(int id, final Dialog dialog) {
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
}
}
case DIALOG_VOL_PIN:
if (id == DIALOG_VOL_PIN) {
if (input != null) {
input.setInputType(InputType.TYPE_NUMBER_VARIATION_PASSWORD
| InputType.TYPE_CLASS_NUMBER);
}
}
case DIALOG_VOL_SETPIN:
if (id == DIALOG_VOL_SETPIN) {
if (input != null) {
input.setInputType(InputType.TYPE_NUMBER_VARIATION_PASSWORD
| InputType.TYPE_CLASS_NUMBER);
}
}
case DIALOG_VOL_CREATEPASS:
case DIALOG_VOL_NAME:
case DIALOG_VOL_CREATE:
Expand Down Expand Up @@ -418,6 +434,8 @@ protected Dialog onCreateDialog(int id) {

switch (id) {
case DIALOG_VOL_PASS: // Password dialog
case DIALOG_VOL_PIN: // Enter PIN dialog
case DIALOG_VOL_SETPIN: // Set PIN dialog
if (mSelectedVolume == null) {
// Can happen when restoring a killed activity
return null;
Expand All @@ -429,8 +447,17 @@ protected Dialog onCreateDialog(int id) {

// Hide password input
input.setTransformationMethod(new PasswordTransformationMethod());

alertBuilder.setTitle(getString(R.string.pwd_dialog_title_str));

String titleString;
if(id == DIALOG_VOL_PIN) {
titleString = getString(R.string.pin_dialog_title_str);
} else if (id == DIALOG_VOL_SETPIN) {
titleString = getString(R.string.set_pin_dialog_title_str);
} else {
titleString = getString(R.string.pwd_dialog_title_str);
}

alertBuilder.setTitle(titleString);
alertBuilder.setView(input);
alertBuilder.setPositiveButton(getString(R.string.btn_ok_str),
new DialogInterface.OnClickListener() {
Expand All @@ -455,6 +482,17 @@ public void onClick(DialogInterface dialog,
addTaskFragment(unlockTask);
unlockTask.startTask();
break;
case DIALOG_VOL_PIN:
// Unlock with cached pass and PIN
unlockSelectedVolume(value.toString());
break;
case DIALOG_VOL_SETPIN:

if(value.length() > 0) {
mApp.getDbHelper().setPIN(mSelectedVolume,value.toString());
}
launchVolumeBrowser(mSelectedVolIdx);
break;
case DIALOG_VOL_CREATEPASS:
// Launch async task to create volume
TaskFragment createTask = new CreateVolumeTaskFragment(
Expand Down Expand Up @@ -805,14 +843,52 @@ private void renameVolume(Volume volume, String newName) {
* Unlock the currently selected volume
*/
private void unlockSelectedVolume() {
unlockSelectedVolume(null);
}

/**
* Unlock the currently selected volume using a PIN
*/
private void unlockSelectedVolume(String userPin) {
mVolumeFileSystem = mSelectedVolume.getFileSystem();

// If key caching is enabled, see if a key is cached

// If key caching is enabled, see if a PIN is set and a key is cached
String savedPin = null;
byte[] cachedKey = null;

if (mPrefs.getBoolean("cache_key", false)) {
cachedKey = mApp.getDbHelper().getCachedKey(mSelectedVolume);

savedPin = mApp.getDbHelper().getPIN(mSelectedVolume);

if ((savedPin == null) || ((userPin != null) && userPin.equals(savedPin.toString()))) {
// all is well, give out cachedKey
cachedKey = mApp.getDbHelper().getCachedKey(mSelectedVolume);
if(savedPin != null) {
mApp.getDbHelper().setPINAttempts(mSelectedVolume, 0);
}
} else if (userPin == null) {
// no PIN given, ask user for one
showDialog(DIALOG_VOL_PIN);
return;
} else {
// a wrong PIN was given
int pinAttempts = mApp.getDbHelper().getPINAttempts(mSelectedVolume);

// we tolerate 3 wrong attempts, after that we delete the cached key
if(pinAttempts < 2) {
mApp.getDbHelper().setPINAttempts(mSelectedVolume, pinAttempts+1);
mErrDialogText = getString(R.string.error_wrong_pin);
} else {
mApp.getDbHelper().clearKey(mSelectedVolume);
mApp.getDbHelper().clearPIN(mSelectedVolume);
mErrDialogText = getString(R.string.error_wrong_pin_three_times);
}
showDialog(DIALOG_ERROR);
return;
}
}


// pin is ok or missing but no key is cached
if (cachedKey == null) {
showDialog(DIALOG_VOL_PASS);
} else {
Expand Down Expand Up @@ -842,16 +918,16 @@ private class UnlockVolumeTaskFragment extends TaskFragment {

// Volume type
private FileSystem mFileSystem;

// Cached key
private byte[] mCachedKey;

// Path of the volume to unlock
private String mVolumePath;

// Password for unlocking (optional if cached key is given)
private String mPassword;

// Optional custom config path
private String mConfigPath;

Expand Down Expand Up @@ -1371,9 +1447,11 @@ public void onTaskResult(int taskId, Object result) {
byte[] keyToCache = ovtr.volume.getDerivedKeyData();
mApp.getDbHelper().cacheKey(mSelectedVolume,
keyToCache);
// Ask user for a pin for this volume
showDialog(DIALOG_VOL_SETPIN);
break;
}
}

launchVolumeBrowser(mSelectedVolIdx);
}
}
Expand Down