mirror of
https://github.com/moparisthebest/keepass2android
synced 2024-12-23 07:28:48 -05:00
added UI for opening a database with OTPs. Some TODOs and things not yet working!
This commit is contained in:
parent
59eace5834
commit
c686cbeeb3
@ -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
|
||||
/// <summary>
|
||||
/// Loads the specified data as the currently open database, as unlocked.
|
||||
/// </summary>
|
||||
void LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, string s, string keyFile, ProgressDialogStatusLogger statusLogger);
|
||||
void LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, CompositeKey compKey, ProgressDialogStatusLogger statusLogger);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current database
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
{
|
||||
|
@ -82,19 +82,6 @@ namespace keepass2android.Io
|
||||
/// <param name="useFileTransaction">if true, force to use file system level transaction. This might be ignored if the file storage has built in transaction support</param>
|
||||
IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction);
|
||||
|
||||
/// <summary>
|
||||
/// brings up a dialog to query credentials or something like this.
|
||||
/// </summary>
|
||||
/// <returns>true if success, false if error or cancelled by user</returns>
|
||||
bool CompleteIoId( /*in/out ioId*/);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the given file exists.
|
||||
/// </summary>
|
||||
/// <returns>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.)</returns>
|
||||
bool? FileExists( /*ioId*/);
|
||||
|
||||
string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc);
|
||||
|
||||
/// <summary>
|
||||
@ -135,10 +122,10 @@ namespace keepass2android.Io
|
||||
void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, string protocolId);
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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);
|
||||
|
||||
/// <summary>
|
||||
/// returns the parent folder of ioc
|
||||
/// </summary>
|
||||
IOConnectionInfo GetParentPath(IOConnectionInfo ioc);
|
||||
|
||||
/// <summary>
|
||||
/// returns the file path of the file "filename" in the folderPath.
|
||||
/// </summary>
|
||||
/// The method may throw FileNotFoundException or not in case the file doesn't exist.
|
||||
IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename);
|
||||
}
|
||||
|
||||
public interface IWriteTransaction: IDisposable
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -82,29 +82,14 @@ namespace keepass2android
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Do not call this method directly. Call App.Kp2a.LoadDatabase instead.
|
||||
/// </summary>
|
||||
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:
|
||||
|
@ -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<MemoryStream> _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<MemoryStream> databaseData, String pass, String key, OnFinish finish): base(finish)
|
||||
public LoadDb(IKp2aApp app, IOConnectionInfo ioc, Task<MemoryStream> 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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -85,15 +85,6 @@ namespace Kp2aUnitTests
|
||||
}
|
||||
}
|
||||
|
||||
public bool CompleteIoId()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public bool? FileExists()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc)
|
||||
{
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
Binary file not shown.
@ -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<MemoryStream> _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<object, object, object>(this, true,
|
||||
//doInBackground
|
||||
delegate
|
||||
{
|
||||
_otpInfo = OathHotpKeyProv.LoadOtpInfo(new KeyProviderQueryContext(_ioConnection, false, false));
|
||||
return null;
|
||||
},
|
||||
//onPostExecute
|
||||
delegate
|
||||
{
|
||||
if (_otpInfo == null)
|
||||
{
|
||||
Toast.MakeText(this, GetString(Resource.String.CouldntLoadOtpAuxFile), ToastLength.Long).Show();
|
||||
return;
|
||||
}
|
||||
FindViewById(Resource.Id.init_otp).Visibility = ViewStates.Gone;
|
||||
FindViewById(Resource.Id.otpEntry).Visibility = ViewStates.Visible;
|
||||
int c = 0;
|
||||
foreach (int otpId in _otpTextViewIds)
|
||||
{
|
||||
c++;
|
||||
var otpTextView = FindViewById<EditText>(otpId);
|
||||
otpTextView.Text = "";
|
||||
otpTextView.Hint = GetString(Resource.String.otp_hint, new Object[] {c});
|
||||
otpTextView.SetFilters(new IInputFilter[] {new InputFilterLengthFilter((int)_otpInfo.OtpLength) });
|
||||
if (c > _otpInfo.OtpsRequired)
|
||||
{
|
||||
otpTextView.Visibility = ViewStates.Gone;
|
||||
}
|
||||
else
|
||||
{
|
||||
otpTextView.TextChanged += (sender, args) =>
|
||||
{
|
||||
UpdateOkButtonState();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
).Execute();
|
||||
}
|
||||
|
||||
protected override void OnCreate(Bundle savedInstanceState)
|
||||
{
|
||||
@ -245,15 +331,15 @@ namespace keepass2android
|
||||
return;
|
||||
}
|
||||
|
||||
_keyFile = GetKeyFile(_ioConnection.Path);
|
||||
_keyFileOrProvider = GetKeyFile(_ioConnection.Path);
|
||||
|
||||
} else
|
||||
{
|
||||
SetIoConnectionFromIntent(_ioConnection, i);
|
||||
_keyFile = i.GetStringExtra(KeyKeyfile);
|
||||
if (string.IsNullOrEmpty(_keyFile))
|
||||
_keyFileOrProvider = i.GetStringExtra(KeyKeyfile);
|
||||
if (string.IsNullOrEmpty(_keyFileOrProvider))
|
||||
{
|
||||
_keyFile = GetKeyFile(_ioConnection.Path);
|
||||
_keyFileOrProvider = GetKeyFile(_ioConnection.Path);
|
||||
}
|
||||
}
|
||||
|
||||
@ -271,6 +357,19 @@ namespace keepass2android
|
||||
|
||||
EditText passwordEdit = FindViewById<EditText>(Resource.Id.password);
|
||||
|
||||
FindViewById<EditText>(Resource.Id.pass_keyfile).TextChanged +=
|
||||
(sender, args) =>
|
||||
{
|
||||
_keyFileOrProvider = FindViewById<EditText>(Resource.Id.pass_keyfile).Text;
|
||||
UpdateOkButtonState();
|
||||
};
|
||||
|
||||
FindViewById<EditText>(Resource.Id.password).TextChanged +=
|
||||
(sender, args) =>
|
||||
{
|
||||
_password = FindViewById<EditText>(Resource.Id.password).Text;
|
||||
UpdateOkButtonState();
|
||||
};
|
||||
|
||||
passwordEdit.RequestFocus();
|
||||
Window.SetSoftInputMode(SoftInput.StateVisible);
|
||||
@ -279,10 +378,48 @@ namespace keepass2android
|
||||
confirmButton.Click += (sender, e) =>
|
||||
{
|
||||
App.Kp2a.GetFileStorage(_ioConnection)
|
||||
.PrepareFileUsage(new FileStorageSetupInitiatorActivity(this, OnActivityResult, null), _ioConnection, 0, false);
|
||||
.PrepareFileUsage(new FileStorageSetupInitiatorActivity(this, OnActivityResult, null), _ioConnection, RequestCodePrepareDbFile, false);
|
||||
};
|
||||
|
||||
Spinner passwordModeSpinner = FindViewById<Spinner>(Resource.Id.password_mode_spinner);
|
||||
if (passwordModeSpinner != null)
|
||||
{
|
||||
|
||||
UpdateKeyProviderUiState();
|
||||
passwordModeSpinner.SetSelection((int) KeyProviderType);
|
||||
passwordModeSpinner.ItemSelected += (sender, args) =>
|
||||
{
|
||||
switch (args.Position)
|
||||
{
|
||||
case 0:
|
||||
_keyFileOrProvider = null;
|
||||
break;
|
||||
case 1:
|
||||
_keyFileOrProvider = "";
|
||||
break;
|
||||
case 2:
|
||||
_keyFileOrProvider = KeyProviderIdOtp;
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Unexpected position "+args.Position+" / " + ((ICursor)((AdapterView)sender).GetItemAtPosition(args.Position)).GetString(1));
|
||||
|
||||
}
|
||||
UpdateKeyProviderUiState();
|
||||
};
|
||||
FindViewById(Resource.Id.init_otp).Click += (sender, args) =>
|
||||
{
|
||||
App.Kp2a.GetOtpAuxFileStorage(_ioConnection)
|
||||
.PrepareFileUsage(new FileStorageSetupInitiatorActivity(this, OnActivityResult, null), _ioConnection, RequestCodePrepareOtpAuxFile, false);
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
//android 2.x
|
||||
//TODO test
|
||||
}
|
||||
|
||||
|
||||
UpdateOkButtonState();
|
||||
|
||||
|
||||
|
||||
@ -327,14 +464,96 @@ namespace keepass2android
|
||||
RetrieveSettings();
|
||||
}
|
||||
|
||||
private void PeformLoadDatabase()
|
||||
private void UpdateOkButtonState()
|
||||
{
|
||||
String pass = GetEditText(Resource.Id.password);
|
||||
String key = GetEditText(Resource.Id.pass_keyfile);
|
||||
if (pass.Length == 0 && key.Length == 0)
|
||||
switch (KeyProviderType)
|
||||
{
|
||||
ErrorMessage(Resource.String.error_nopass);
|
||||
return;
|
||||
case KeyProviders.None:
|
||||
FindViewById(Resource.Id.pass_ok).Enabled = true;
|
||||
break;
|
||||
case KeyProviders.KeyFile:
|
||||
FindViewById(Resource.Id.pass_ok).Enabled = _keyFileOrProvider != "" || _password != "";
|
||||
break;
|
||||
case KeyProviders.Otp:
|
||||
|
||||
bool enabled = true;
|
||||
if (_otpInfo == null)
|
||||
enabled = false;
|
||||
else
|
||||
{
|
||||
int c = 0;
|
||||
foreach (int otpId in _otpTextViewIds)
|
||||
{
|
||||
c++;
|
||||
var otpTextView = FindViewById<EditText>(otpId);
|
||||
if ((c <= _otpInfo.OtpsRequired) && (otpTextView.Text == ""))
|
||||
{
|
||||
enabled = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FindViewById(Resource.Id.pass_ok).Enabled = enabled;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateKeyProviderUiState()
|
||||
{
|
||||
FindViewById(Resource.Id.keyfileLine).Visibility = KeyProviderType == KeyProviders.KeyFile
|
||||
? ViewStates.Visible
|
||||
: ViewStates.Gone;
|
||||
FindViewById(Resource.Id.otpView).Visibility = KeyProviderType == KeyProviders.Otp
|
||||
? ViewStates.Visible
|
||||
: ViewStates.Gone;
|
||||
UpdateOkButtonState();
|
||||
}
|
||||
|
||||
private void PerformLoadDatabase()
|
||||
{
|
||||
//no need to check for validity of password because if this method is called, the Ok button was enabled (i.e. there was a valid password)
|
||||
CompositeKey compositeKey = new CompositeKey();
|
||||
compositeKey.AddUserKey(new KcpPassword(_password));
|
||||
if (KeyProviderType == KeyProviders.KeyFile)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
compositeKey.AddUserKey(new KcpKeyFile(_keyFileOrProvider));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Kp2aLog.Log(e.ToString());
|
||||
throw new KeyFileException();
|
||||
}
|
||||
}
|
||||
else if (KeyProviderType == KeyProviders.Otp)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
List<string> lOtps = new List<string>();
|
||||
foreach (int otpId in _otpTextViewIds)
|
||||
{
|
||||
string otpText = FindViewById<EditText>(otpId).Text;
|
||||
if (!String.IsNullOrEmpty(otpText))
|
||||
lOtps.Add(otpText);
|
||||
}
|
||||
CreateOtpSecret(lOtps);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
const string strMain = "Failed to create OTP key!";
|
||||
const string strLine1 = "Make sure you've entered the correct OTPs.";
|
||||
|
||||
Toast.MakeText(this, strMain + " " + strLine1, ToastLength.Long).Show();
|
||||
|
||||
return;
|
||||
}
|
||||
compositeKey.AddUserKey(new KcpCustomKey(OathHotpKeyProv.Name, _otpInfo.Secret, true));
|
||||
}
|
||||
|
||||
CheckBox cbQuickUnlock = (CheckBox) FindViewById(Resource.Id.enable_quickunlock);
|
||||
@ -345,7 +564,7 @@ namespace keepass2android
|
||||
MakePasswordMaskedOrVisible();
|
||||
|
||||
Handler handler = new Handler();
|
||||
LoadDb task = new LoadDb(App.Kp2a, _ioConnection, _loadDbTask, pass, key, new AfterLoad(handler, this));
|
||||
LoadDb task = new LoadDb(App.Kp2a, _ioConnection, _loadDbTask, compositeKey, _keyFileOrProvider, new AfterLoad(handler, this));
|
||||
_loadDbTask = null; // prevent accidental re-use
|
||||
|
||||
SetNewDefaultFile();
|
||||
@ -353,6 +572,44 @@ namespace keepass2android
|
||||
new ProgressTask(App.Kp2a, this, task).Run();
|
||||
}
|
||||
|
||||
private void CreateOtpSecret(List<string> lOtps)
|
||||
{
|
||||
byte[] pbSecret;
|
||||
if (!string.IsNullOrEmpty(_otpInfo.EncryptedSecret)) // < v2.0
|
||||
{
|
||||
byte[] pbKey32 = OtpUtil.KeyFromOtps(lOtps.ToArray(), 0,
|
||||
lOtps.Count, Convert.FromBase64String(
|
||||
_otpInfo.TransformationKey), _otpInfo.TransformationRounds);
|
||||
if (pbKey32 == null) throw new InvalidOperationException();
|
||||
|
||||
pbSecret = OtpUtil.DecryptData(_otpInfo.EncryptedSecret,
|
||||
pbKey32, Convert.FromBase64String(_otpInfo.EncryptionIV));
|
||||
if (pbSecret == null) throw new InvalidOperationException();
|
||||
|
||||
_otpInfo.Secret = pbSecret;
|
||||
_otpInfo.Counter += (ulong) _otpInfo.OtpsRequired;
|
||||
}
|
||||
else // >= v2.0, supporting look-ahead
|
||||
{
|
||||
bool bSuccess = false;
|
||||
for (int i = 0; i < _otpInfo.EncryptedSecrets.Count; ++i)
|
||||
{
|
||||
OtpEncryptedData d = _otpInfo.EncryptedSecrets[i];
|
||||
pbSecret = OtpUtil.DecryptSecret(d, lOtps.ToArray(), 0,
|
||||
lOtps.Count);
|
||||
if (pbSecret != null)
|
||||
{
|
||||
_otpInfo.Secret = pbSecret;
|
||||
_otpInfo.Counter += ((ulong) _otpInfo.OtpsRequired +
|
||||
(ulong) i);
|
||||
bSuccess = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!bSuccess) throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
private void MakePasswordMaskedOrVisible()
|
||||
{
|
||||
TextView password = (TextView) FindViewById(Resource.Id.password);
|
||||
@ -521,13 +778,9 @@ namespace keepass2android
|
||||
|
||||
private String GetKeyFile(String filename) {
|
||||
if ( _rememberKeyfile ) {
|
||||
FileDbHelper dbHelp = App.Kp2a.FileDbHelper;
|
||||
|
||||
String keyfile = dbHelp.GetFileByName(filename);
|
||||
|
||||
return keyfile;
|
||||
return App.Kp2a.FileDbHelper.GetKeyFileForFile(filename);
|
||||
} else {
|
||||
return "";
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -541,7 +794,8 @@ namespace keepass2android
|
||||
{
|
||||
FindViewById(Resource.Id.filename_group).Visibility = ViewStates.Visible;
|
||||
}
|
||||
SetEditText(Resource.Id.pass_keyfile, _keyFile);
|
||||
if (KeyProviderType == KeyProviders.KeyFile)
|
||||
SetEditText(Resource.Id.pass_keyfile, _keyFileOrProvider);
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
|
6687
src/keepass2android/Resources/Resource.designer.cs
generated
6687
src/keepass2android/Resources/Resource.designer.cs
generated
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="12dip"
|
||||
android:layout_marginRight="12dip"
|
||||
android:layout_marginBottom="12dip">
|
||||
android:layout_marginBottom="12dip"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
<RelativeLayout
|
||||
android:id="@+id/filename_group"
|
||||
android:layout_width="fill_parent"
|
||||
@ -49,13 +51,18 @@
|
||||
android:id="@+id/password_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/filename_group"
|
||||
android:text="" />
|
||||
<LinearLayout
|
||||
<Spinner
|
||||
android:id="@+id/password_mode_spinner"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:entries="@array/password_modes"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/passwordLine"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/password_label"
|
||||
android:orientation="horizontal">
|
||||
<EditText
|
||||
android:id="@+id/password"
|
||||
@ -71,11 +78,10 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_menu_view" />
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
<LinearLayout
|
||||
android:id="@+id/keyfileLine"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/passwordLine"
|
||||
android:orientation="horizontal">
|
||||
<EditText
|
||||
android:id="@+id/pass_keyfile"
|
||||
@ -90,22 +96,82 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_launcher_folder_small" />
|
||||
</LinearLayout>
|
||||
<Button
|
||||
<LinearLayout
|
||||
android:id="@+id/otpView"
|
||||
android:layout_marginLeft="12dip"
|
||||
android:layout_marginRight="12dip"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
<Button
|
||||
android:id="@+id/init_otp"
|
||||
android:text="@string/init_otp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
<LinearLayout
|
||||
android:id="@+id/otpEntry"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
<TextView
|
||||
android:id="@+id/otp_expl"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/otp_explanation" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/otp1"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="93317749"
|
||||
android:singleLine="true" />
|
||||
<EditText
|
||||
android:id="@+id/otp2"
|
||||
android:text="54719327"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true" />
|
||||
<EditText
|
||||
android:id="@+id/otp3"
|
||||
android:text="49844651"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true" />
|
||||
<EditText
|
||||
android:id="@+id/otp4"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true" />
|
||||
<EditText
|
||||
android:id="@+id/otp5"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true" />
|
||||
<EditText
|
||||
android:id="@+id/otp6"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
<Button
|
||||
android:id="@+id/pass_ok"
|
||||
android:text="@android:string/ok"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/keyfileLine" />
|
||||
android:layout_height="wrap_content"/>
|
||||
<Button
|
||||
android:id="@+id/kill_app"
|
||||
android:text="@string/kill_app_label"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/pass_ok" />
|
||||
android:layout_height="wrap_content" />
|
||||
<CheckBox
|
||||
android:id="@+id/enable_quickunlock"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/kill_app"
|
||||
android:text="@string/enable_quickunlock" />
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
@ -343,6 +343,14 @@
|
||||
|
||||
<string name="error_adding_keyfile">Error while adding the keyfile!</string>
|
||||
|
||||
<string name="init_otp">Enter OTPs…</string>
|
||||
<string name="otp_explanation">Enter the next One-time-passwords (OTPs). Swipe your Yubikey NEO at the back of your device to enter via NFC.</string>
|
||||
<string name="otp_hint">OTP %1$d</string>
|
||||
<string name="CouldntLoadOtpAuxFile">Could not load auxiliary OTP file!</string>
|
||||
|
||||
|
||||
<string name="loading">Loading…</string>
|
||||
|
||||
<string name="ChangeLog_title">Change log</string>
|
||||
|
||||
<string name="ChangeLog_0_9_2">
|
||||
@ -462,4 +470,9 @@ Initial public release
|
||||
<item>Remember username only</item>
|
||||
<item>Remember username and password</item>
|
||||
</string-array>
|
||||
<string-array name="password_modes">
|
||||
<item>Password only</item>
|
||||
<item>Password + Key file</item>
|
||||
<item>Password + OTP</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
|
164
src/keepass2android/Utils/LoadingDialog.cs
Normal file
164
src/keepass2android/Utils/LoadingDialog.cs
Normal file
@ -0,0 +1,164 @@
|
||||
using System;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Runtime;
|
||||
using Java.Lang;
|
||||
using Exception = System.Exception;
|
||||
using Object = Java.Lang.Object;
|
||||
|
||||
namespace keepass2android.Utils
|
||||
{
|
||||
public class LoadingDialog<TParams, TProgress, TResult> : AsyncTask<TParams, TProgress, TResult>
|
||||
{
|
||||
private readonly Context _context;
|
||||
private readonly string _message;
|
||||
private readonly bool _cancelable;
|
||||
readonly Func<Object[], Object> _doInBackground;
|
||||
readonly Action<Object> _onPostExecute;
|
||||
|
||||
private ProgressDialog mDialog;
|
||||
/**
|
||||
* Default is {@code 500}ms
|
||||
*/
|
||||
private int mDelayTime = 500;
|
||||
/**
|
||||
* Flag to use along with {@link #mDelayTime}
|
||||
*/
|
||||
private bool mFinished = false;
|
||||
|
||||
private Exception mLastException;
|
||||
|
||||
|
||||
public LoadingDialog(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
|
||||
{
|
||||
}
|
||||
|
||||
public LoadingDialog(Context context, string message, bool cancelable, Func<Object[], Object> doInBackground,
|
||||
Action<Object> onPostExecute)
|
||||
{
|
||||
_context = context;
|
||||
_message = message;
|
||||
_cancelable = cancelable;
|
||||
_doInBackground = doInBackground;
|
||||
_onPostExecute = onPostExecute;
|
||||
Initialize();
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
mDialog = new ProgressDialog(_context);
|
||||
mDialog.SetMessage(_message);
|
||||
mDialog.Indeterminate = true;
|
||||
mDialog.SetCancelable(_cancelable);
|
||||
if (_cancelable)
|
||||
{
|
||||
mDialog.SetCanceledOnTouchOutside(true);
|
||||
mDialog.CancelEvent += (sender, args) => mDialog.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public LoadingDialog(Context context, bool cancelable, Func<Object[], Object> doInBackground, Action<Object> onPostExecute)
|
||||
{
|
||||
_message = context.GetString(Resource.String.loading);
|
||||
_context = context;
|
||||
_cancelable = cancelable;
|
||||
_doInBackground = doInBackground;
|
||||
_onPostExecute = onPostExecute;
|
||||
Initialize();
|
||||
}
|
||||
|
||||
protected override void OnPreExecute()
|
||||
{
|
||||
new Handler().PostDelayed(() =>
|
||||
{
|
||||
if (!mFinished)
|
||||
{
|
||||
try
|
||||
{
|
||||
/*
|
||||
* sometime the activity has been finished before we
|
||||
* show this dialog, it will raise error
|
||||
*/
|
||||
mDialog.Show();
|
||||
}
|
||||
catch (Exception t)
|
||||
{
|
||||
Kp2aLog.Log(t.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
, mDelayTime);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If you override this method, you must call {@code super.onCancelled()} at
|
||||
* beginning of the method.
|
||||
*/
|
||||
protected override void OnCancelled() {
|
||||
DoFinish();
|
||||
base.OnCancelled();
|
||||
}// onCancelled()
|
||||
|
||||
private void DoFinish() {
|
||||
mFinished = true;
|
||||
try {
|
||||
/*
|
||||
* Sometime the activity has been finished before we dismiss this
|
||||
* dialog, it will raise error.
|
||||
*/
|
||||
mDialog.Dismiss();
|
||||
} catch (Exception e)
|
||||
{
|
||||
Kp2aLog.Log(e.ToString());
|
||||
}
|
||||
}// doFinish()
|
||||
|
||||
|
||||
/**
|
||||
* Sets last exception. This method is useful in case an exception raises
|
||||
* inside {@link #doInBackground(Void...)}
|
||||
*
|
||||
* @param t
|
||||
* {@link Throwable}
|
||||
*/
|
||||
protected void SetLastException(Exception e) {
|
||||
mLastException = e;
|
||||
}// setLastException()
|
||||
|
||||
/**
|
||||
* Gets last exception.
|
||||
*
|
||||
* @return {@link Throwable}
|
||||
*/
|
||||
protected Exception GetLastException() {
|
||||
return mLastException;
|
||||
}// getLastException()
|
||||
|
||||
|
||||
protected override Object DoInBackground(params Object[] @params)
|
||||
{
|
||||
return _doInBackground(@params);
|
||||
}
|
||||
|
||||
protected override TResult RunInBackground(params TParams[] @params)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override void OnPostExecute(Object result)
|
||||
{
|
||||
DoFinish();
|
||||
|
||||
if (_onPostExecute != null)
|
||||
_onPostExecute(result);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -20,7 +20,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using System.Diagnostics;
|
||||
|
||||
using KeePassLib.Utility;
|
||||
@ -39,9 +38,8 @@ namespace OtpKeyProv
|
||||
FmtHex, FmtBase64, FmtBase32, FmtUtf8, FmtDec
|
||||
};
|
||||
|
||||
public static OtpDataFmt? GetOtpDataFormat(ComboBox cmb)
|
||||
public static OtpDataFmt? GetOtpDataFormat(String strFmt)
|
||||
{
|
||||
string strFmt = (cmb.SelectedItem as string);
|
||||
if(strFmt == null) return null; // No assert
|
||||
|
||||
if(strFmt == FmtHex) return OtpDataFmt.Hex;
|
||||
|
@ -1,4 +1,6 @@
|
||||
/*
|
||||
This file was modified my Philipp Crocoll, 2013. Based on:
|
||||
|
||||
OtpKeyProv Plugin
|
||||
Copyright (C) 2011-2012 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
|
||||
@ -19,81 +21,49 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using System.Diagnostics;
|
||||
|
||||
using OtpKeyProv.Forms;
|
||||
|
||||
using KeePass.UI;
|
||||
|
||||
using KeePassLib.Keys;
|
||||
using KeePassLib.Serialization;
|
||||
using KeePassLib.Utility;
|
||||
using keepass2android;
|
||||
using keepass2android.Io;
|
||||
|
||||
namespace OtpKeyProv
|
||||
{
|
||||
public sealed class OathHotpKeyProv : KeyProvider
|
||||
public sealed class OathHotpKeyProv
|
||||
/*removed base class KeyProvider because "synchronous" interface is not suitable on Android*/
|
||||
{
|
||||
private const string AuxFileExt = ".otp.xml";
|
||||
private const string ProvType = "OATH HOTP / RFC 4226";
|
||||
private const string ProvVersion = "2.0"; // File version, not OtpKeyProv version
|
||||
|
||||
public override string Name
|
||||
public static string Name
|
||||
{
|
||||
get { return "One-Time Passwords (OATH HOTP)"; }
|
||||
}
|
||||
|
||||
public override bool SecureDesktopCompatible
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override byte[] GetKey(KeyProviderQueryContext ctx)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(ctx.CreatingNewKey) return Create(ctx);
|
||||
return Open(ctx);
|
||||
}
|
||||
catch(Exception ex) { MessageService.ShowWarning(ex.Message); }
|
||||
public const string ShortProductName = "OtpKeyProv";
|
||||
public const string ProductName = "OtpKeyProv KeePass Plugin";
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static IOConnectionInfo GetAuxFileIoc(KeyProviderQueryContext ctx)
|
||||
{
|
||||
IOConnectionInfo ioc = ctx.DatabaseIOInfo.CloneDeep();
|
||||
ioc.Path = UrlUtil.StripExtension(ioc.Path) + AuxFileExt;
|
||||
return ioc;
|
||||
IFileStorage fileStorage = App.Kp2a.GetOtpAuxFileStorage(ioc);
|
||||
IOConnectionInfo iocAux = fileStorage.GetFilePath(fileStorage.GetParentPath(ioc),
|
||||
fileStorage.GetFilenameWithoutPathAndExt(ioc) + AuxFileExt);
|
||||
|
||||
return iocAux;
|
||||
}
|
||||
|
||||
private static byte[] Create(KeyProviderQueryContext ctx)
|
||||
public static OtpInfo LoadOtpInfo(KeyProviderQueryContext ctx)
|
||||
{
|
||||
IOConnectionInfo iocPrev = GetAuxFileIoc(ctx);
|
||||
OtpInfo otpInfo = OtpInfo.Load(iocPrev);
|
||||
if(otpInfo == null) otpInfo = new OtpInfo();
|
||||
|
||||
OtpKeyCreationForm dlg = new OtpKeyCreationForm();
|
||||
dlg.InitEx(otpInfo, ctx);
|
||||
|
||||
if(UIUtil.ShowDialogAndDestroy(dlg) != DialogResult.OK)
|
||||
return null;
|
||||
|
||||
if(!CreateAuxFile(otpInfo, ctx)) return null;
|
||||
return otpInfo.Secret;
|
||||
return OtpInfo.Load(GetAuxFileIoc(ctx));
|
||||
}
|
||||
|
||||
private static byte[] Open(KeyProviderQueryContext ctx)
|
||||
/*
|
||||
private static byte[] Open(KeyProviderQueryContext ctx, OtpInfo otpInfo)
|
||||
{
|
||||
IOConnectionInfo ioc = GetAuxFileIoc(ctx);
|
||||
OtpInfo otpInfo = OtpInfo.Load(ioc);
|
||||
if(otpInfo == null)
|
||||
{
|
||||
MessageService.ShowWarning("Failed to load auxiliary OTP info file:",
|
||||
ioc.GetDisplayName());
|
||||
return null;
|
||||
}
|
||||
if(otpInfo.Type != ProvType)
|
||||
{
|
||||
MessageService.ShowWarning("Unknown OTP generator type!");
|
||||
@ -108,13 +78,72 @@ namespace OtpKeyProv
|
||||
if(!CreateAuxFile(otpInfo, ctx)) return null;
|
||||
return otpInfo.Secret;
|
||||
}
|
||||
* */
|
||||
|
||||
/// <summary>
|
||||
/// Sets the "Secret" field in otpInfo based on the list of entered OTPs (lOtps) or the entered secret itself which is in format fmt
|
||||
/// </summary>
|
||||
/// based on the code in OtpKeyPromptForm.cs
|
||||
public void SetSecret(OtpInfo otpInfo, List<string> lOtps, string secret, OtpDataFmt? fmt)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] pbSecret = EncodingUtil.ParseKey(secret,
|
||||
(fmt.HasValue ? fmt.Value : OtpDataFmt.Hex));
|
||||
if (pbSecret != null)
|
||||
{
|
||||
otpInfo.Secret = pbSecret;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(otpInfo.EncryptedSecret)) // < v2.0
|
||||
{
|
||||
byte[] pbKey32 = OtpUtil.KeyFromOtps(lOtps.ToArray(), 0,
|
||||
lOtps.Count, Convert.FromBase64String(
|
||||
otpInfo.TransformationKey), otpInfo.TransformationRounds);
|
||||
if (pbKey32 == null) throw new InvalidOperationException();
|
||||
|
||||
pbSecret = OtpUtil.DecryptData(otpInfo.EncryptedSecret,
|
||||
pbKey32, Convert.FromBase64String(otpInfo.EncryptionIV));
|
||||
if (pbSecret == null) throw new InvalidOperationException();
|
||||
|
||||
otpInfo.Secret = pbSecret;
|
||||
otpInfo.Counter += (ulong) otpInfo.OtpsRequired;
|
||||
}
|
||||
else // >= v2.0, supporting look-ahead
|
||||
{
|
||||
bool bSuccess = false;
|
||||
for (int i = 0; i < otpInfo.EncryptedSecrets.Count; ++i)
|
||||
{
|
||||
OtpEncryptedData d = otpInfo.EncryptedSecrets[i];
|
||||
pbSecret = OtpUtil.DecryptSecret(d, lOtps.ToArray(), 0,
|
||||
lOtps.Count);
|
||||
if (pbSecret != null)
|
||||
{
|
||||
otpInfo.Secret = pbSecret;
|
||||
otpInfo.Counter += ((ulong) otpInfo.OtpsRequired +
|
||||
(ulong) i);
|
||||
bSuccess = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!bSuccess) throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//todo
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static bool CreateAuxFile(OtpInfo otpInfo,
|
||||
KeyProviderQueryContext ctx)
|
||||
{
|
||||
otpInfo.Type = ProvType;
|
||||
otpInfo.Version = ProvVersion;
|
||||
otpInfo.Generator = OtpKeyProvExt.ProductName;
|
||||
otpInfo.Generator = ProductName;
|
||||
|
||||
otpInfo.EncryptSecret();
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
/*
|
||||
This file was modified my Philipp Crocoll, 2013. Based on:
|
||||
|
||||
OtpKeyProv Plugin
|
||||
Copyright (C) 2011-2012 Dominik Reichl <dominik.reichl@t-online.de>
|
||||
|
||||
@ -31,6 +33,7 @@ using KeePassLib.Cryptography;
|
||||
using KeePassLib.Keys;
|
||||
using KeePassLib.Serialization;
|
||||
using KeePassLib.Utility;
|
||||
using keepass2android;
|
||||
|
||||
namespace OtpKeyProv
|
||||
{
|
||||
@ -168,12 +171,15 @@ namespace OtpKeyProv
|
||||
|
||||
try
|
||||
{
|
||||
sIn = IOConnection.OpenRead(ioc);
|
||||
sIn = App.Kp2a.GetOtpAuxFileStorage(ioc).OpenFileForRead(ioc);
|
||||
|
||||
XmlSerializer xs = new XmlSerializer(typeof(OtpInfo));
|
||||
return (OtpInfo)xs.Deserialize(sIn);
|
||||
XmlSerializer xs = new XmlSerializer(typeof (OtpInfo));
|
||||
return (OtpInfo) xs.Deserialize(sIn);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Kp2aLog.Log(e.ToString());
|
||||
}
|
||||
catch(Exception) { }
|
||||
finally
|
||||
{
|
||||
if(sIn != null) sIn.Close();
|
||||
@ -188,20 +194,23 @@ namespace OtpKeyProv
|
||||
|
||||
try
|
||||
{
|
||||
sOut = IOConnection.OpenWrite(ioc);
|
||||
using (var trans = App.Kp2a.GetOtpAuxFileStorage(ioc)
|
||||
.OpenWriteTransaction(ioc, App.Kp2a.GetBooleanPreference(PreferenceKey.UseFileTransactions)))
|
||||
{
|
||||
XmlWriterSettings xws = new XmlWriterSettings();
|
||||
xws.CloseOutput = true;
|
||||
xws.Encoding = StrUtil.Utf8;
|
||||
xws.Indent = true;
|
||||
xws.IndentChars = "\t";
|
||||
|
||||
XmlWriterSettings xws = new XmlWriterSettings();
|
||||
xws.CloseOutput = true;
|
||||
xws.Encoding = StrUtil.Utf8;
|
||||
xws.Indent = true;
|
||||
xws.IndentChars = "\t";
|
||||
XmlWriter xw = XmlWriter.Create(trans.OpenFile(), xws);
|
||||
|
||||
XmlWriter xw = XmlWriter.Create(sOut, xws);
|
||||
XmlSerializer xs = new XmlSerializer(typeof (OtpInfo));
|
||||
xs.Serialize(xw, otpInfo);
|
||||
|
||||
XmlSerializer xs = new XmlSerializer(typeof(OtpInfo));
|
||||
xs.Serialize(xw, otpInfo);
|
||||
|
||||
xw.Close();
|
||||
xw.Close();
|
||||
trans.CommitWrite();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch(Exception) { Debug.Assert(false); }
|
||||
|
@ -107,9 +107,9 @@ namespace keepass2android
|
||||
Application.Context.SendBroadcast(new Intent(Intents.DatabaseLocked));
|
||||
}
|
||||
|
||||
public void LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, string password, string keyFile, ProgressDialogStatusLogger statusLogger)
|
||||
public void LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, CompositeKey compositeKey, ProgressDialogStatusLogger statusLogger)
|
||||
{
|
||||
_db.LoadData(this, ioConnectionInfo, memoryStream, password, keyFile, statusLogger);
|
||||
_db.LoadData(this, ioConnectionInfo, memoryStream, compositeKey, statusLogger);
|
||||
|
||||
UpdateOngoingNotification();
|
||||
}
|
||||
@ -354,10 +354,9 @@ namespace keepass2android
|
||||
{
|
||||
IFileStorage innerFileStorage = GetCloudFileStorage(iocInfo);
|
||||
|
||||
var prefs = PreferenceManager.GetDefaultSharedPreferences(Application.Context);
|
||||
|
||||
if (prefs.GetBoolean(Application.Context.Resources.GetString(Resource.String.UseOfflineCache_key), true))
|
||||
if (DatabaseCacheEnabled)
|
||||
{
|
||||
//TODO
|
||||
return new CachingFileStorage(innerFileStorage, Application.Context.CacheDir.Path, this);
|
||||
}
|
||||
else
|
||||
@ -494,6 +493,42 @@ namespace keepass2android
|
||||
{
|
||||
return GetFileStorage(new IOConnectionInfo() {Path = protocolId + "://"});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// returns a file storage object to be used when accessing the auxiliary OTP file
|
||||
/// </summary>
|
||||
/// The reason why this requires a different file storage is the different caching behavior.
|
||||
public IFileStorage GetOtpAuxFileStorage(IOConnectionInfo iocInfo)
|
||||
{
|
||||
|
||||
if (iocInfo.IsLocalFile())
|
||||
return new BuiltInFileStorage();
|
||||
else
|
||||
{
|
||||
IFileStorage innerFileStorage = GetCloudFileStorage(iocInfo);
|
||||
|
||||
|
||||
if (DatabaseCacheEnabled)
|
||||
{
|
||||
return new CachingFileStorage(innerFileStorage, Application.Context.CacheDir.Path, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
return innerFileStorage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool DatabaseCacheEnabled
|
||||
{
|
||||
get
|
||||
{
|
||||
var prefs = PreferenceManager.GetDefaultSharedPreferences(Application.Context);
|
||||
bool cacheEnabled = prefs.GetBoolean(Application.Context.Resources.GetString(Resource.String.UseOfflineCache_key),
|
||||
true);
|
||||
return cacheEnabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -230,12 +230,12 @@ namespace keepass2android
|
||||
|
||||
}
|
||||
|
||||
public String GetFileByName(String name) {
|
||||
public String GetKeyFileForFile(String name) {
|
||||
ICursor cursor = mDb.Query(true, FileTable, GetColumnList(),
|
||||
KeyFileFilename + "= ?", new[] {name}, null, null, null, null);
|
||||
|
||||
if ( cursor == null ) {
|
||||
return "";
|
||||
return null;
|
||||
}
|
||||
|
||||
String keyfileFilename;
|
||||
@ -244,9 +244,11 @@ namespace keepass2android
|
||||
keyfileFilename = cursor.GetString(cursor.GetColumnIndexOrThrow(KeyFileKeyfile));
|
||||
} else {
|
||||
// Cursor is empty
|
||||
keyfileFilename = "";
|
||||
keyfileFilename = null;
|
||||
}
|
||||
cursor.Close();
|
||||
if (keyfileFilename == "")
|
||||
return null;
|
||||
return keyfileFilename;
|
||||
}
|
||||
|
||||
|
@ -373,7 +373,7 @@ namespace keepass2android
|
||||
#if !EXCLUDE_FILECHOOSER
|
||||
StartFileChooser(ioc.Path);
|
||||
#else
|
||||
LaunchPasswordActivityForIoc(new IOConnectionInfo { Path = "/mnt/sdcard/keepass/keepass.kdbx"});
|
||||
LaunchPasswordActivityForIoc(new IOConnectionInfo { Path = "/mnt/sdcard/keepass/yubi2.kdbx"});
|
||||
#endif
|
||||
}
|
||||
if ((resultCode == Result.Canceled) && (data != null) && (data.HasExtra("EXTRA_ERROR_MESSAGE")))
|
||||
|
@ -82,6 +82,10 @@
|
||||
<Reference Include="Mono.Android.Support.v4" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="addons\OtpKeyProv\EncodingUtil.cs" />
|
||||
<Compile Include="addons\OtpKeyProv\OathHotpKeyProv.cs" />
|
||||
<Compile Include="addons\OtpKeyProv\OtpInfo.cs" />
|
||||
<Compile Include="addons\OtpKeyProv\OtpUtil.cs" />
|
||||
<Compile Include="app\NoFileStorageFoundException.cs" />
|
||||
<Compile Include="CreateDatabaseActivity.cs" />
|
||||
<Compile Include="fileselect\FileChooserFileProvider.cs" />
|
||||
@ -102,6 +106,7 @@
|
||||
<Compile Include="search\SearchProvider.cs" />
|
||||
<Compile Include="services\OngoingNotificationsService.cs" />
|
||||
<Compile Include="settings\DatabaseSettingsActivity.cs" />
|
||||
<Compile Include="Utils\LoadingDialog.cs" />
|
||||
<Compile Include="Utils\Util.cs" />
|
||||
<Compile Include="intents\Intents.cs" />
|
||||
<Compile Include="timeout\TimeoutHelper.cs" />
|
||||
@ -629,7 +634,9 @@
|
||||
<AndroidResource Include="Resources\layout-v14\SaveButton.xml" />
|
||||
<AndroidResource Include="Resources\layout-v14\generate_password.xml" />
|
||||
<AndroidResource Include="Resources\layout-v14\icon_picker.xml" />
|
||||
<AndroidResource Include="Resources\layout-v14\password.xml" />
|
||||
<AndroidResource Include="Resources\layout-v14\password.xml">
|
||||
<SubType>Designer</SubType>
|
||||
</AndroidResource>
|
||||
<AndroidResource Include="Resources\layout\InViewButton.xml" />
|
||||
<AndroidResource Include="Resources\drawable\collections_collection.png" />
|
||||
<AndroidResource Include="Resources\drawable\collections_new_label.png" />
|
||||
|
Loading…
Reference in New Issue
Block a user