diff --git a/src/Kp2aBusinessLogic/IKp2aApp.cs b/src/Kp2aBusinessLogic/IKp2aApp.cs
index 40e340bd..1f35e232 100644
--- a/src/Kp2aBusinessLogic/IKp2aApp.cs
+++ b/src/Kp2aBusinessLogic/IKp2aApp.cs
@@ -3,6 +3,7 @@ using Android.App;
using System.IO;
using Android.Content;
using Android.OS;
+using KeePassLib.Keys;
using KeePassLib.Serialization;
using keepass2android.Io;
@@ -22,7 +23,7 @@ namespace keepass2android
///
/// Loads the specified data as the currently open database, as unlocked.
///
- void LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, string s, string keyFile, ProgressDialogStatusLogger statusLogger);
+ void LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, CompositeKey compKey, ProgressDialogStatusLogger statusLogger);
///
/// Returns the current database
diff --git a/src/Kp2aBusinessLogic/Io/BuiltInFileStorage.cs b/src/Kp2aBusinessLogic/Io/BuiltInFileStorage.cs
index f0c678df..1b0a94d2 100644
--- a/src/Kp2aBusinessLogic/Io/BuiltInFileStorage.cs
+++ b/src/Kp2aBusinessLogic/Io/BuiltInFileStorage.cs
@@ -107,16 +107,6 @@ namespace keepass2android.Io
}
}
- public bool CompleteIoId()
- {
- throw new NotImplementedException();
- }
-
- public bool? FileExists()
- {
- throw new NotImplementedException();
- }
-
public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc)
{
return UrlUtil.StripExtension(
@@ -207,5 +197,19 @@ namespace keepass2android.Io
parent += "/";
return parent + newFilename;
}
+
+ public IOConnectionInfo GetParentPath(IOConnectionInfo ioc)
+ {
+ return IoUtil.GetParentPath(ioc);
+ }
+
+ public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename)
+ {
+ IOConnectionInfo res = folderPath.CloneDeep();
+ if (!res.Path.EndsWith("/"))
+ res.Path += "/";
+ res.Path += filename;
+ return res;
+ }
}
}
\ No newline at end of file
diff --git a/src/Kp2aBusinessLogic/Io/CachingFileStorage.cs b/src/Kp2aBusinessLogic/Io/CachingFileStorage.cs
index e0815c27..c11a27f6 100644
--- a/src/Kp2aBusinessLogic/Io/CachingFileStorage.cs
+++ b/src/Kp2aBusinessLogic/Io/CachingFileStorage.cs
@@ -400,16 +400,6 @@ namespace keepass2android.Io
return new CachedWriteTransaction(ioc, useFileTransaction, this);
}
- public bool CompleteIoId()
- {
- throw new NotImplementedException();
- }
-
- public bool? FileExists()
- {
- throw new NotImplementedException();
- }
-
public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc)
{
return UrlUtil.StripExtension(
@@ -487,6 +477,54 @@ namespace keepass2android.Io
return _cachedStorage.CreateFilePath(parent, newFilename);
}
+ public IOConnectionInfo GetParentPath(IOConnectionInfo ioc)
+ {
+ return _cachedStorage.GetParentPath(ioc);
+ }
+
+ public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename)
+ {
+ try
+ {
+ IOConnectionInfo res = _cachedStorage.GetFilePath(folderPath, filename);
+ //some file storage implementations require accessing the network to determine the file path (e.g. because
+ //they might contain file ids). In this case, we need to cache the result to enable cached access to such files
+ StoreFilePath(folderPath, filename, res);
+ return res;
+ }
+ catch (Exception)
+ {
+ IOConnectionInfo res;
+ if (!TryGetCachedFilePath(folderPath, filename, out res)) throw;
+ return res;
+ }
+
+ }
+
+ private void StoreFilePath(IOConnectionInfo folderPath, string filename, IOConnectionInfo res)
+ {
+ File.WriteAllText(CachedFilePath(GetPseudoIoc(folderPath, filename)) + ".filepath", res.Path);
+ }
+
+ private IOConnectionInfo GetPseudoIoc(IOConnectionInfo folderPath, string filename)
+ {
+ IOConnectionInfo res = folderPath.CloneDeep();
+ if (!res.Path.EndsWith("/"))
+ res.Path += "/";
+ res.Path += filename;
+ return res;
+ }
+
+ private bool TryGetCachedFilePath(IOConnectionInfo folderPath, string filename, out IOConnectionInfo res)
+ {
+ res = folderPath.CloneDeep();
+ string filePathCache = CachedFilePath(GetPseudoIoc(folderPath, filename)) + ".filepath";
+ if (!File.Exists(filePathCache))
+ return false;
+ res.Path = File.ReadAllText(filePathCache);
+ return true;
+ }
+
public string GetBaseVersionHash(IOConnectionInfo ioc)
{
diff --git a/src/Kp2aBusinessLogic/Io/IFileStorage.cs b/src/Kp2aBusinessLogic/Io/IFileStorage.cs
index a02ab301..84ee9a14 100644
--- a/src/Kp2aBusinessLogic/Io/IFileStorage.cs
+++ b/src/Kp2aBusinessLogic/Io/IFileStorage.cs
@@ -82,19 +82,6 @@ namespace keepass2android.Io
/// if true, force to use file system level transaction. This might be ignored if the file storage has built in transaction support
IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction);
- ///
- /// brings up a dialog to query credentials or something like this.
- ///
- /// true if success, false if error or cancelled by user
- bool CompleteIoId( /*in/out ioId*/);
-
-
- ///
- /// Checks whether the given file exists.
- ///
- /// true if it exists, false if not. Null if the check couldn't be performed (e.g. because no credentials available or no connection established.)
- bool? FileExists( /*ioId*/);
-
string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc);
///
@@ -135,10 +122,10 @@ namespace keepass2android.Io
void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, string protocolId);
///
- /// Initiates the process for choosing a file in the given file storage.
+ /// Initiates the process for using a file in the given file storage.
/// The file storage should either call OnImmediateResult or StartFileUsageProcess
/// If alwaysReturnSuccess is true, the activity should be finished with ResultCode Ok.
- /// This can make sense if a higher-level file storage has the file cached by still wants to
+ /// This can make sense if a higher-level file storage has the file cached but still wants to
/// give the cached storage the chance to initialize file access.
///
void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode, bool alwaysReturnSuccess);
@@ -157,6 +144,17 @@ namespace keepass2android.Io
//returns the path of a file "newFilename" in the folder "parent"
//this may create the file if this is required to get a path (if a UUID is part of the file path)
string CreateFilePath(string parent, string newFilename);
+
+ ///
+ /// returns the parent folder of ioc
+ ///
+ IOConnectionInfo GetParentPath(IOConnectionInfo ioc);
+
+ ///
+ /// returns the file path of the file "filename" in the folderPath.
+ ///
+ /// The method may throw FileNotFoundException or not in case the file doesn't exist.
+ IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename);
}
public interface IWriteTransaction: IDisposable
diff --git a/src/Kp2aBusinessLogic/Io/IoUtil.cs b/src/Kp2aBusinessLogic/Io/IoUtil.cs
index c4a83a79..d0af2930 100644
--- a/src/Kp2aBusinessLogic/Io/IoUtil.cs
+++ b/src/Kp2aBusinessLogic/Io/IoUtil.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Text;
using Java.IO;
+using KeePassLib.Serialization;
namespace keepass2android.Io
{
@@ -30,5 +31,20 @@ namespace keepass2android.Io
}
+ public static IOConnectionInfo GetParentPath(IOConnectionInfo ioc)
+ {
+ var iocParent = ioc.CloneDeep();
+ if (iocParent.Path.EndsWith("/"))
+ iocParent.Path = iocParent.Path.Substring(0, iocParent.Path.Length - 1);
+
+ int slashPos = iocParent.Path.LastIndexOf("/", StringComparison.Ordinal);
+ if (slashPos == -1)
+ iocParent.Path = "";
+ else
+ {
+ iocParent.Path = iocParent.Path.Substring(0, slashPos);
+ }
+ return iocParent;
+ }
}
}
diff --git a/src/Kp2aBusinessLogic/Io/JavaFileStorage.cs b/src/Kp2aBusinessLogic/Io/JavaFileStorage.cs
index 3093c20d..beeca50d 100644
--- a/src/Kp2aBusinessLogic/Io/JavaFileStorage.cs
+++ b/src/Kp2aBusinessLogic/Io/JavaFileStorage.cs
@@ -151,16 +151,6 @@ namespace keepass2android.Io
}
}
- public bool CompleteIoId()
- {
- throw new NotImplementedException();
- }
-
- public bool? FileExists()
- {
- throw new NotImplementedException();
- }
-
public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc)
{
return UrlUtil.StripExtension(
diff --git a/src/Kp2aBusinessLogic/database/Database.cs b/src/Kp2aBusinessLogic/database/Database.cs
index 88bfbccf..ef9053c8 100644
--- a/src/Kp2aBusinessLogic/database/Database.cs
+++ b/src/Kp2aBusinessLogic/database/Database.cs
@@ -82,29 +82,14 @@ namespace keepass2android
}
-
+
///
/// Do not call this method directly. Call App.Kp2a.LoadDatabase instead.
///
- public void LoadData(IKp2aApp app, IOConnectionInfo iocInfo, MemoryStream databaseData, String password, String keyfile, ProgressDialogStatusLogger status)
+ public void LoadData(IKp2aApp app, IOConnectionInfo iocInfo, MemoryStream databaseData, CompositeKey compositeKey, ProgressDialogStatusLogger status)
{
PwDatabase pwDatabase = new PwDatabase();
- CompositeKey compositeKey = new CompositeKey();
- compositeKey.AddUserKey(new KcpPassword(password));
- if (!String.IsNullOrEmpty(keyfile))
- {
-
- try
- {
- compositeKey.AddUserKey(new KcpKeyFile(keyfile));
- } catch (Exception e)
- {
- Kp2aLog.Log(e.ToString());
- throw new KeyFileException();
- }
- }
-
IFileStorage fileStorage = _app.GetFileStorage(iocInfo);
var filename = fileStorage.GetFilenameWithoutPathAndExt(iocInfo);
try
@@ -115,7 +100,9 @@ namespace keepass2android
}
catch (InvalidCompositeKeyException)
{
- if ((password == "") && (keyfile != null))
+ KcpPassword passwordKey = (KcpPassword)compositeKey.GetUserKey(typeof(KcpPassword));
+
+ if ((passwordKey != null) && (passwordKey.Password.ReadString() == "") && (compositeKey.UserKeyCount > 1))
{
//if we don't get a password, we don't know whether this means "empty password" or "no password"
//retry without password:
diff --git a/src/Kp2aBusinessLogic/database/edit/LoadDB.cs b/src/Kp2aBusinessLogic/database/edit/LoadDB.cs
index ed0046fe..6c5e6619 100644
--- a/src/Kp2aBusinessLogic/database/edit/LoadDB.cs
+++ b/src/Kp2aBusinessLogic/database/edit/LoadDB.cs
@@ -19,6 +19,7 @@ using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
+using KeePassLib.Keys;
using KeePassLib.Serialization;
namespace keepass2android
@@ -26,20 +27,20 @@ namespace keepass2android
public class LoadDb : RunnableOnFinish {
private readonly IOConnectionInfo _ioc;
private readonly Task _databaseData;
- private readonly String _pass;
- private readonly String _key;
+ private readonly CompositeKey _compositeKey;
+ private readonly string _keyfileOrProvider;
private readonly IKp2aApp _app;
private readonly bool _rememberKeyfile;
- public LoadDb(IKp2aApp app, IOConnectionInfo ioc, Task databaseData, String pass, String key, OnFinish finish): base(finish)
+ public LoadDb(IKp2aApp app, IOConnectionInfo ioc, Task databaseData, CompositeKey compositeKey, String keyfileOrProvider, OnFinish finish): base(finish)
{
_app = app;
_ioc = ioc;
_databaseData = databaseData;
- _pass = pass;
- _key = key;
+ _compositeKey = compositeKey;
+ _keyfileOrProvider = keyfileOrProvider;
+
-
_rememberKeyfile = app.GetBooleanPreference(PreferenceKey.remember_keyfile);
}
@@ -50,8 +51,8 @@ namespace keepass2android
{
StatusLogger.UpdateMessage(UiStringKey.loading_database);
MemoryStream memoryStream = _databaseData == null ? null : _databaseData.Result;
- _app.LoadDatabase(_ioc, memoryStream, _pass, _key, StatusLogger);
- SaveFileData(_ioc, _key);
+ _app.LoadDatabase(_ioc, memoryStream, _compositeKey, StatusLogger);
+ SaveFileData(_ioc, _keyfileOrProvider);
}
catch (KeyFileException)
@@ -88,13 +89,13 @@ namespace keepass2android
Finish(true);
}
- private void SaveFileData(IOConnectionInfo ioc, String key) {
+ private void SaveFileData(IOConnectionInfo ioc, String keyfileOrProvider) {
if (!_rememberKeyfile)
{
- key = "";
+ keyfileOrProvider = "";
}
- _app.StoreOpenedFileAsRecent(ioc, key);
+ _app.StoreOpenedFileAsRecent(ioc, keyfileOrProvider);
}
diff --git a/src/Kp2aUnitTests/TestFileStorage.cs b/src/Kp2aUnitTests/TestFileStorage.cs
index a5d0e524..eff83b10 100644
--- a/src/Kp2aUnitTests/TestFileStorage.cs
+++ b/src/Kp2aUnitTests/TestFileStorage.cs
@@ -85,15 +85,6 @@ namespace Kp2aUnitTests
}
}
- public bool CompleteIoId()
- {
- throw new NotImplementedException();
- }
-
- public bool? FileExists()
- {
- throw new NotImplementedException();
- }
public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc)
{
diff --git a/src/Kp2aUnitTests/TestKp2aApp.cs b/src/Kp2aUnitTests/TestKp2aApp.cs
index 3ca08ecf..78a2fdd8 100644
--- a/src/Kp2aUnitTests/TestKp2aApp.cs
+++ b/src/Kp2aUnitTests/TestKp2aApp.cs
@@ -38,7 +38,7 @@ namespace Kp2aUnitTests
public void LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, string password, string keyFile,
ProgressDialogStatusLogger statusLogger)
{
- _db.LoadData(this, ioConnectionInfo, memoryStream, password, keyFile, statusLogger);
+ _db.LoadData(this, ioConnectionInfo, memoryStream, password, statusLogger);
}
diff --git a/src/java/kp2akeytransform/bin/kp2akeytransform.jar b/src/java/kp2akeytransform/bin/kp2akeytransform.jar
index 2c343a20..6e586dc1 100644
Binary files a/src/java/kp2akeytransform/bin/kp2akeytransform.jar and b/src/java/kp2akeytransform/bin/kp2akeytransform.jar differ
diff --git a/src/keepass2android/PasswordActivity.cs b/src/keepass2android/PasswordActivity.cs
index a180b70a..a109f145 100644
--- a/src/keepass2android/PasswordActivity.cs
+++ b/src/keepass2android/PasswordActivity.cs
@@ -16,13 +16,16 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file
*/
using System;
+using System.Collections.Generic;
using System.Threading.Tasks;
using Android.App;
using Android.Content;
+using Android.Database;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
+using Java.Lang;
using Java.Net;
using Android.Preferences;
using Java.IO;
@@ -31,8 +34,14 @@ using Android.Content.PM;
using KeePassLib.Keys;
using KeePassLib.Serialization;
using KeePassLib.Utility;
+using OtpKeyProv;
using keepass2android.Io;
+using keepass2android.Utils;
+using Exception = System.Exception;
using MemoryStream = System.IO.MemoryStream;
+using Object = Java.Lang.Object;
+using Process = Android.OS.Process;
+using String = System.String;
namespace keepass2android
{
@@ -41,6 +50,15 @@ namespace keepass2android
Theme="@style/Base")]
public class PasswordActivity : LockingActivity {
+
+ enum KeyProviders
+ {
+ //int values correspond to indices in passwordSpinner
+ None = 0,
+ KeyFile = 1,
+ Otp = 2
+ }
+
bool _showPassword;
public const String KeyDefaultFilename = "defaultFileName";
@@ -53,14 +71,37 @@ namespace keepass2android
private const String ViewIntent = "android.intent.action.VIEW";
private const string ShowpasswordKey = "ShowPassword";
+ private const string KeyProviderIdOtp = "KP2A-OTP";
private Task _loadDbTask;
private IOConnectionInfo _ioConnection;
- private String _keyFile;
+ private String _keyFileOrProvider;
+
+ internal AppTask AppTask;
+ private bool _killOnDestroy;
+ private string _password = "";
+ private const int RequestCodePrepareDbFile = 1000;
+ private const int RequestCodePrepareOtpAuxFile = 1001;
+
+
+ KeyProviders KeyProviderType
+ {
+ get
+ {
+ if (_keyFileOrProvider == null)
+ return KeyProviders.None;
+ if (_keyFileOrProvider == KeyProviderIdOtp)
+ return KeyProviders.Otp;
+ return KeyProviders.KeyFile;
+ }
+ }
+
private bool _rememberKeyfile;
ISharedPreferences _prefs;
private bool _starting;
+ private OtpInfo _otpInfo;
+ private readonly int[] _otpTextViewIds = new int[] {Resource.Id.otp1, Resource.Id.otp2, Resource.Id.otp3, Resource.Id.otp4, Resource.Id.otp5, Resource.Id.otp6};
public PasswordActivity (IntPtr javaReference, JniHandleOwnership transfer)
: base(javaReference, transfer)
@@ -170,6 +211,7 @@ namespace keepass2android
KcpKeyFile kcpKeyfile = (KcpKeyFile)App.Kp2a.GetDb().KpDatabase.MasterKey.GetUserKey(typeof(KcpKeyFile));
SetEditText(Resource.Id.pass_keyfile, kcpKeyfile.Path);
+ _keyFileOrProvider = kcpKeyfile.Path;
}
}
App.Kp2a.LockDatabase(false);
@@ -186,18 +228,62 @@ namespace keepass2android
EditText fn = (EditText) FindViewById(Resource.Id.pass_keyfile);
fn.Text = filename;
+ _keyFileOrProvider = filename;
}
}
break;
case (Result)FileStorageResults.FileUsagePrepared:
- PeformLoadDatabase();
+ if (requestCode == RequestCodePrepareDbFile)
+ PerformLoadDatabase();
+ if (requestCode == RequestCodePrepareOtpAuxFile)
+ LoadOtpFile();
break;
}
}
- internal AppTask AppTask;
- private bool _killOnDestroy;
+ private void LoadOtpFile()
+ {
+ new LoadingDialog