mirror of
https://github.com/moparisthebest/keepass2android
synced 2024-11-25 02:32:26 -05:00
extended implementation of OTP
This commit is contained in:
parent
66cd05b9f4
commit
aeaba47573
@ -60,7 +60,7 @@ namespace keepass2android.Io
|
||||
/// </summary>
|
||||
public class CachingFileStorage: IFileStorage
|
||||
{
|
||||
private readonly IFileStorage _cachedStorage;
|
||||
protected readonly IFileStorage _cachedStorage;
|
||||
private readonly ICacheSupervisor _cacheSupervisor;
|
||||
private readonly string _streamCacheDir;
|
||||
|
||||
@ -179,14 +179,21 @@ namespace keepass2android.Io
|
||||
{
|
||||
if (TryUpdateRemoteFile(localData, ioc, true, hash))
|
||||
_cacheSupervisor.UpdatedRemoteFileOnLoad(ioc);
|
||||
return File.OpenRead(cachedFilePath);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//conflict: both files changed.
|
||||
return OpenFileForReadWithConflict(ioc, cachedFilePath);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected virtual Stream OpenFileForReadWithConflict(IOConnectionInfo ioc, string cachedFilePath)
|
||||
{
|
||||
//signal that we're loading from local
|
||||
_cacheSupervisor.NotifyOpenFromLocalDueToConflict(ioc);
|
||||
}
|
||||
return File.OpenRead(cachedFilePath);
|
||||
}
|
||||
|
||||
@ -214,19 +221,6 @@ namespace keepass2android.Io
|
||||
|
||||
private Stream OpenFileForReadWhenNoLocalChanges(IOConnectionInfo ioc, string cachedFilePath)
|
||||
{
|
||||
//open stream:
|
||||
using (Stream file = _cachedStorage.OpenFileForRead(ioc))
|
||||
{
|
||||
//copy to cache:
|
||||
//note: we might use the file version to check if it's already in the cache and if copying is required.
|
||||
//However, this is safer.
|
||||
string fileHash;
|
||||
using (HashingStreamEx cachedFile = new HashingStreamEx(File.Create(cachedFilePath), true, new SHA256Managed()))
|
||||
{
|
||||
file.CopyTo(cachedFile);
|
||||
cachedFile.Close();
|
||||
fileHash = MemUtil.ByteArrayToHexString(cachedFile.Hash);
|
||||
}
|
||||
|
||||
//remember current hash
|
||||
string previousHash = null;
|
||||
@ -234,9 +228,10 @@ namespace keepass2android.Io
|
||||
if (File.Exists(baseVersionFilePath))
|
||||
previousHash = File.ReadAllText(baseVersionFilePath);
|
||||
|
||||
//save hash in cache files:
|
||||
File.WriteAllText(VersionFilePath(ioc), fileHash);
|
||||
File.WriteAllText(baseVersionFilePath, fileHash);
|
||||
|
||||
|
||||
//copy to cache:
|
||||
var fileHash = UpdateCacheFromRemote(ioc, cachedFilePath);
|
||||
|
||||
//notify supervisor what we did:
|
||||
if (previousHash != fileHash)
|
||||
@ -245,8 +240,35 @@ namespace keepass2android.Io
|
||||
_cacheSupervisor.LoadedFromRemoteInSync(ioc);
|
||||
|
||||
return File.OpenRead(cachedFilePath);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// copies the file in ioc to the local cache. Updates the cache version files and returns the new file hash.
|
||||
/// </summary>
|
||||
protected string UpdateCacheFromRemote(IOConnectionInfo ioc, string cachedFilePath)
|
||||
{
|
||||
//note: we might use the file version to check if it's already in the cache and if copying is required.
|
||||
//However, this is safer.
|
||||
string fileHash;
|
||||
|
||||
//open stream:
|
||||
using (Stream remoteFile = _cachedStorage.OpenFileForRead(ioc))
|
||||
{
|
||||
|
||||
using (HashingStreamEx cachedFile = new HashingStreamEx(File.Create(cachedFilePath), true, new SHA256Managed()))
|
||||
{
|
||||
remoteFile.CopyTo(cachedFile);
|
||||
cachedFile.Close();
|
||||
fileHash = MemUtil.ByteArrayToHexString(cachedFile.Hash);
|
||||
}
|
||||
}
|
||||
|
||||
//save hash in cache files:
|
||||
File.WriteAllText(VersionFilePath(ioc), fileHash);
|
||||
File.WriteAllText(BaseVersionFilePath(ioc), fileHash);
|
||||
return fileHash;
|
||||
}
|
||||
|
||||
private bool TryUpdateRemoteFile(Stream cachedData, IOConnectionInfo ioc, bool useFileTransaction, string hash)
|
||||
@ -266,7 +288,7 @@ namespace keepass2android.Io
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateRemoteFile(Stream cachedData, IOConnectionInfo ioc, bool useFileTransaction, string hash)
|
||||
protected void UpdateRemoteFile(Stream cachedData, IOConnectionInfo ioc, bool useFileTransaction, string hash)
|
||||
{
|
||||
//try to write to remote:
|
||||
using (
|
||||
|
@ -44,6 +44,8 @@ namespace keepass2android
|
||||
CheckingDatabaseForChanges,
|
||||
RemoteDatabaseUnchanged,
|
||||
CannotMoveGroupHere,
|
||||
ErrorOcurred
|
||||
ErrorOcurred,
|
||||
SynchronizingOtpAuxFile,
|
||||
SavingOtpAuxFile
|
||||
}
|
||||
}
|
@ -44,6 +44,13 @@ namespace keepass2android
|
||||
return KpDatabase == null ? null : KpDatabase.IOConnectionInfo;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// if an OTP key was used, this property tells the location of the OTP auxiliary file.
|
||||
/// Must be set after loading.
|
||||
/// </summary>
|
||||
public IOConnectionInfo OtpAuxFileIoc { get; set; }
|
||||
|
||||
public string LastFileVersion;
|
||||
public SearchDbHelper SearchHelper;
|
||||
|
||||
@ -192,6 +199,7 @@ namespace keepass2android
|
||||
KpDatabase = null;
|
||||
_loaded = false;
|
||||
_reloadRequested = false;
|
||||
OtpAuxFileIoc = null;
|
||||
}
|
||||
|
||||
public void MarkAllGroupsAsDirty() {
|
||||
|
@ -25,6 +25,7 @@ using Android.Widget;
|
||||
using KeePassLib;
|
||||
using Android.Preferences;
|
||||
using KeePassLib.Interfaces;
|
||||
using KeePassLib.Serialization;
|
||||
using KeePassLib.Utility;
|
||||
using keepass2android.Io;
|
||||
using keepass2android.database.edit;
|
||||
@ -367,6 +368,37 @@ namespace keepass2android
|
||||
return base.OnOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
class SyncOtpAuxFile: OnFinish
|
||||
{
|
||||
private readonly IOConnectionInfo _ioc;
|
||||
|
||||
public SyncOtpAuxFile(IOConnectionInfo ioc)
|
||||
{
|
||||
_ioc = ioc;
|
||||
}
|
||||
|
||||
public override void Run()
|
||||
{
|
||||
if (Handler != null)
|
||||
{
|
||||
Handler.Post(DoSyncOtpAuxFile);
|
||||
}
|
||||
else
|
||||
DoSyncOtpAuxFile();
|
||||
base.Run();
|
||||
}
|
||||
|
||||
private void DoSyncOtpAuxFile()
|
||||
{
|
||||
StatusLogger.UpdateMessage(UiStringKey.SynchronizingOtpAuxFile);
|
||||
//simply open the file. The file storage does a complete sync.
|
||||
using (App.Kp2a.GetOtpAuxFileStorage(_ioc).OpenFileForRead(_ioc))
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Synchronize()
|
||||
{
|
||||
var filestorage = App.Kp2a.GetFileStorage(App.Kp2a.GetDb().Ioc);
|
||||
|
@ -3,6 +3,7 @@ using Android.App;
|
||||
using Android.Content;
|
||||
using Android.Content.PM;
|
||||
using Android.OS;
|
||||
using Android.Widget;
|
||||
using Java.Util.Regex;
|
||||
|
||||
namespace keepass2android
|
||||
@ -54,8 +55,30 @@ namespace keepass2android
|
||||
|
||||
|
||||
i.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
|
||||
i.PutExtra(Intents.OtpExtraKey, GetOtpFromIntent(Intent));
|
||||
try
|
||||
{
|
||||
string otp = GetOtpFromIntent(Intent);
|
||||
if (otp == null)
|
||||
throw new Exception("Otp must not be null!");
|
||||
i.PutExtra(Intents.OtpExtraKey, otp);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Kp2aLog.Log(e.ToString());
|
||||
Toast.MakeText(this, "No Yubikey OTP found!", ToastLength.Long).Show();
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (App.Kp2a.GetDb().Loaded)
|
||||
{
|
||||
Toast.MakeText(this, GetString(Resource.String.otp_discarded_because_db_open), ToastLength.Long).Show();
|
||||
}
|
||||
else
|
||||
{
|
||||
StartActivity(i);
|
||||
}
|
||||
|
||||
Finish();
|
||||
|
||||
}
|
||||
|
@ -17,7 +17,10 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using System.Xml.Serialization;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.Database;
|
||||
@ -27,15 +30,17 @@ using Android.Views;
|
||||
using Android.Widget;
|
||||
using Java.Net;
|
||||
using Android.Preferences;
|
||||
using Java.IO;
|
||||
using Android.Text;
|
||||
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 File = Java.IO.File;
|
||||
using FileNotFoundException = Java.IO.FileNotFoundException;
|
||||
using MemoryStream = System.IO.MemoryStream;
|
||||
using Object = Java.Lang.Object;
|
||||
using Process = Android.OS.Process;
|
||||
@ -55,11 +60,10 @@ namespace keepass2android
|
||||
//int values correspond to indices in passwordSpinner
|
||||
None = 0,
|
||||
KeyFile = 1,
|
||||
Otp = 2
|
||||
Otp = 2,
|
||||
OtpRecovery = 3
|
||||
}
|
||||
|
||||
bool _showPassword;
|
||||
|
||||
public const String KeyDefaultFilename = "defaultFileName";
|
||||
|
||||
public const String KeyFilename = "fileName";
|
||||
@ -71,19 +75,23 @@ namespace keepass2android
|
||||
private const String ViewIntent = "android.intent.action.VIEW";
|
||||
private const string ShowpasswordKey = "ShowPassword";
|
||||
private const string KeyProviderIdOtp = "KP2A-OTP";
|
||||
private const string KeyProviderIdOtpRecovery = "KP2A-OTPSecret";
|
||||
|
||||
private const int RequestCodePrepareDbFile = 1000;
|
||||
private const int RequestCodePrepareOtpAuxFile = 1001;
|
||||
|
||||
|
||||
private Task<MemoryStream> _loadDbTask;
|
||||
private IOConnectionInfo _ioConnection;
|
||||
private String _keyFileOrProvider;
|
||||
bool _showPassword;
|
||||
|
||||
internal AppTask AppTask;
|
||||
private bool _killOnDestroy;
|
||||
private string _password = "";
|
||||
//OTPs which should be entered into the OTP fields as soon as these become visible
|
||||
private readonly List<String> _pendingOtps = new List<string>();
|
||||
private List<String> _pendingOtps = new List<string>();
|
||||
|
||||
private const int RequestCodePrepareDbFile = 1000;
|
||||
private const int RequestCodePrepareOtpAuxFile = 1001;
|
||||
|
||||
|
||||
KeyProviders KeyProviderType
|
||||
@ -94,6 +102,8 @@ namespace keepass2android
|
||||
return KeyProviders.None;
|
||||
if (_keyFileOrProvider == KeyProviderIdOtp)
|
||||
return KeyProviders.Otp;
|
||||
if (_keyFileOrProvider == KeyProviderIdOtpRecovery)
|
||||
return KeyProviders.OtpRecovery;
|
||||
return KeyProviders.KeyFile;
|
||||
}
|
||||
}
|
||||
@ -103,7 +113,12 @@ namespace keepass2android
|
||||
|
||||
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};
|
||||
private readonly int[] _otpTextViewIds = new[] {Resource.Id.otp1, Resource.Id.otp2, Resource.Id.otp3, Resource.Id.otp4, Resource.Id.otp5, Resource.Id.otp6};
|
||||
private const string OtpInfoKey = "OtpInfoKey";
|
||||
private const string EnteredOtpsKey = "EnteredOtpsKey";
|
||||
private const string PendingOtpsKey = "PendingOtpsKey";
|
||||
private const string PasswordKey = "PasswordKey";
|
||||
private const string KeyFileOrProviderKey = "KeyFileOrProviderKey";
|
||||
|
||||
public PasswordActivity (IntPtr javaReference, JniHandleOwnership transfer)
|
||||
: base(javaReference, transfer)
|
||||
@ -217,7 +232,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);
|
||||
@ -234,7 +249,7 @@ namespace keepass2android
|
||||
|
||||
EditText fn = (EditText) FindViewById(Resource.Id.pass_keyfile);
|
||||
fn.Text = filename;
|
||||
_keyFileOrProvider = filename;
|
||||
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -265,6 +280,17 @@ namespace keepass2android
|
||||
Toast.MakeText(this, GetString(Resource.String.CouldntLoadOtpAuxFile), ToastLength.Long).Show();
|
||||
return;
|
||||
}
|
||||
|
||||
IList<string> prefilledOtps = _pendingOtps;
|
||||
ShowOtpEntry(prefilledOtps);
|
||||
_pendingOtps.Clear();
|
||||
|
||||
}
|
||||
).Execute();
|
||||
}
|
||||
|
||||
private void ShowOtpEntry(IList<string> prefilledOtps)
|
||||
{
|
||||
FindViewById(Resource.Id.otpInitView).Visibility = ViewStates.Gone;
|
||||
FindViewById(Resource.Id.otpEntry).Visibility = ViewStates.Visible;
|
||||
int c = 0;
|
||||
@ -273,9 +299,9 @@ namespace keepass2android
|
||||
{
|
||||
c++;
|
||||
var otpTextView = FindViewById<EditText>(otpId);
|
||||
if (c <= _pendingOtps.Count)
|
||||
if (c <= prefilledOtps.Count)
|
||||
{
|
||||
otpTextView.Text = _pendingOtps[c-1];
|
||||
otpTextView.Text = prefilledOtps[c - 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -289,27 +315,19 @@ namespace keepass2android
|
||||
}
|
||||
else
|
||||
{
|
||||
otpTextView.TextChanged += (sender, args) =>
|
||||
{
|
||||
UpdateOkButtonState();
|
||||
};
|
||||
otpTextView.TextChanged += (sender, args) => { UpdateOkButtonState(); };
|
||||
}
|
||||
}
|
||||
_pendingOtps.Clear();
|
||||
|
||||
}
|
||||
).Execute();
|
||||
}
|
||||
|
||||
protected override void OnCreate(Bundle savedInstanceState)
|
||||
{
|
||||
base.OnCreate(savedInstanceState);
|
||||
if (savedInstanceState != null)
|
||||
_showPassword = savedInstanceState.GetBoolean(ShowpasswordKey, false);
|
||||
|
||||
Intent i = Intent;
|
||||
|
||||
AppTask = AppTask.GetTaskInOnCreate(savedInstanceState, Intent);
|
||||
|
||||
Intent i = Intent;
|
||||
String action = i.Action;
|
||||
|
||||
_prefs = PreferenceManager.GetDefaultSharedPreferences(this);
|
||||
@ -320,84 +338,11 @@ namespace keepass2android
|
||||
|
||||
if (action != null && action.Equals(ViewIntent))
|
||||
{
|
||||
//started from "view" intent (e.g. from file browser)
|
||||
_ioConnection.Path = i.DataString;
|
||||
|
||||
if (! _ioConnection.Path.Substring(0, 7).Equals("file://"))
|
||||
{
|
||||
//TODO: this might no longer be required as we can handle http(s) and ftp as well (but we need server credentials therefore)
|
||||
Toast.MakeText(this, Resource.String.error_can_not_handle_uri, ToastLength.Long).Show();
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
_ioConnection.Path = URLDecoder.Decode(_ioConnection.Path.Substring(7));
|
||||
|
||||
if (_ioConnection.Path.Length == 0)
|
||||
{
|
||||
// No file name
|
||||
Toast.MakeText(this, Resource.String.FileNotFound, ToastLength.Long).Show();
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
File dbFile = new File(_ioConnection.Path);
|
||||
if (! dbFile.Exists())
|
||||
{
|
||||
// File does not exist
|
||||
Toast.MakeText(this, Resource.String.FileNotFound, ToastLength.Long).Show();
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
_keyFileOrProvider = GetKeyFile(_ioConnection.Path);
|
||||
|
||||
if (!GetIocFromViewIntent(i)) return;
|
||||
}
|
||||
else if ((action != null) && (action.Equals(Intents.StartWithOtp)))
|
||||
{
|
||||
//create called after detecting an OTP via NFC
|
||||
//this means the Activity was not on the back stack before, i.e. no database has been selected
|
||||
|
||||
_ioConnection = null;
|
||||
|
||||
//see if we can get a database from recent:
|
||||
if (App.Kp2a.FileDbHelper.HasRecentFiles())
|
||||
{
|
||||
ICursor filesCursor = App.Kp2a.FileDbHelper.FetchAllFiles();
|
||||
StartManagingCursor(filesCursor);
|
||||
filesCursor.MoveToFirst();
|
||||
IOConnectionInfo ioc = App.Kp2a.FileDbHelper.CursorToIoc(filesCursor);
|
||||
if (App.Kp2a.GetFileStorage(ioc).RequiresSetup(ioc) == false)
|
||||
{
|
||||
IFileStorage fileStorage = App.Kp2a.GetFileStorage(ioc);
|
||||
|
||||
if (!fileStorage.RequiresCredentials(ioc))
|
||||
{
|
||||
//ok, we can use this file
|
||||
_ioConnection = ioc;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_ioConnection == null)
|
||||
{
|
||||
//We need to go to FileSelectActivity first.
|
||||
//For security reasons: discard the OTP (otherwise the user might not select a database now and forget
|
||||
//about the OTP, but it would still be stored in the Intents and later be passed to PasswordActivity again.
|
||||
|
||||
Toast.MakeText(this, GetString(Resource.String.otp_discarded_because_no_db), ToastLength.Long).Show();
|
||||
GoToFileSelectActivity();
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
//user obviously wants to use OTP:
|
||||
_keyFileOrProvider = KeyProviderIdOtp;
|
||||
|
||||
//remember the OTP for later use
|
||||
_pendingOtps.Add(Intent.GetStringExtra(Intents.OtpExtraKey));
|
||||
Intent.RemoveExtra(Intents.OtpExtraKey);
|
||||
if (!GetIocFromOtpIntent(savedInstanceState, i)) return;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -419,9 +364,10 @@ namespace keepass2android
|
||||
|
||||
|
||||
SetContentView(Resource.Layout.password);
|
||||
PopulateView();
|
||||
InitializeFilenameView();
|
||||
|
||||
EditText passwordEdit = FindViewById<EditText>(Resource.Id.password);
|
||||
if (KeyProviderType == KeyProviders.KeyFile)
|
||||
SetEditText(Resource.Id.pass_keyfile, _keyFileOrProvider);
|
||||
|
||||
FindViewById<EditText>(Resource.Id.pass_keyfile).TextChanged +=
|
||||
(sender, args) =>
|
||||
@ -437,20 +383,167 @@ namespace keepass2android
|
||||
UpdateOkButtonState();
|
||||
};
|
||||
|
||||
FindViewById<EditText>(Resource.Id.pass_otpsecret).TextChanged += (sender, args) => UpdateOkButtonState();
|
||||
|
||||
|
||||
EditText passwordEdit = FindViewById<EditText>(Resource.Id.password);
|
||||
passwordEdit.RequestFocus();
|
||||
Window.SetSoftInputMode(SoftInput.StateVisible);
|
||||
|
||||
InitializeOkButton();
|
||||
|
||||
InitializePasswordModeSpinner();
|
||||
|
||||
InitializeOtpSecretSpinner();
|
||||
|
||||
UpdateOkButtonState();
|
||||
|
||||
InitializeTogglePasswordButton();
|
||||
InitializeKeyfileBrowseButton();
|
||||
|
||||
InitializeQuickUnlockCheckbox();
|
||||
|
||||
RestoreState(savedInstanceState);
|
||||
}
|
||||
|
||||
private void InitializeOtpSecretSpinner()
|
||||
{
|
||||
Spinner spinner = FindViewById<Spinner>(Resource.Id.otpsecret_format_spinner);
|
||||
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(this, Android.Resource.Layout.SimpleSpinnerDropDownItem, EncodingUtil.Formats);
|
||||
spinner.Adapter = spinnerArrayAdapter;
|
||||
}
|
||||
|
||||
private bool GetIocFromOtpIntent(Bundle savedInstanceState, Intent i)
|
||||
{
|
||||
//create called after detecting an OTP via NFC
|
||||
//this means the Activity was not on the back stack before, i.e. no database has been selected
|
||||
|
||||
_ioConnection = null;
|
||||
|
||||
//see if we can get a database from recent:
|
||||
if (App.Kp2a.FileDbHelper.HasRecentFiles())
|
||||
{
|
||||
ICursor filesCursor = App.Kp2a.FileDbHelper.FetchAllFiles();
|
||||
StartManagingCursor(filesCursor);
|
||||
filesCursor.MoveToFirst();
|
||||
IOConnectionInfo ioc = App.Kp2a.FileDbHelper.CursorToIoc(filesCursor);
|
||||
if (App.Kp2a.GetFileStorage(ioc).RequiresSetup(ioc) == false)
|
||||
{
|
||||
IFileStorage fileStorage = App.Kp2a.GetFileStorage(ioc);
|
||||
|
||||
if (!fileStorage.RequiresCredentials(ioc))
|
||||
{
|
||||
//ok, we can use this file
|
||||
_ioConnection = ioc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_ioConnection == null)
|
||||
{
|
||||
//We need to go to FileSelectActivity first.
|
||||
//For security reasons: discard the OTP (otherwise the user might not select a database now and forget
|
||||
//about the OTP, but it would still be stored in the Intents and later be passed to PasswordActivity again.
|
||||
|
||||
Toast.MakeText(this, GetString(Resource.String.otp_discarded_because_no_db), ToastLength.Long).Show();
|
||||
GoToFileSelectActivity();
|
||||
Finish();
|
||||
return false;
|
||||
}
|
||||
|
||||
//user obviously wants to use OTP:
|
||||
_keyFileOrProvider = KeyProviderIdOtp;
|
||||
|
||||
if (savedInstanceState == null) //only when not re-creating
|
||||
{
|
||||
//remember the OTP for later use
|
||||
_pendingOtps.Add(i.GetStringExtra(Intents.OtpExtraKey));
|
||||
i.RemoveExtra(Intents.OtpExtraKey);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool GetIocFromViewIntent(Intent i)
|
||||
{
|
||||
//started from "view" intent (e.g. from file browser)
|
||||
_ioConnection.Path = i.DataString;
|
||||
|
||||
if (! _ioConnection.Path.Substring(0, 7).Equals("file://"))
|
||||
{
|
||||
//TODO: this might no longer be required as we can handle http(s) and ftp as well (but we need server credentials therefore)
|
||||
Toast.MakeText(this, Resource.String.error_can_not_handle_uri, ToastLength.Long).Show();
|
||||
Finish();
|
||||
return false;
|
||||
}
|
||||
|
||||
_ioConnection.Path = URLDecoder.Decode(_ioConnection.Path.Substring(7));
|
||||
|
||||
if (_ioConnection.Path.Length == 0)
|
||||
{
|
||||
// No file name
|
||||
Toast.MakeText(this, Resource.String.FileNotFound, ToastLength.Long).Show();
|
||||
Finish();
|
||||
return false;
|
||||
}
|
||||
|
||||
File dbFile = new File(_ioConnection.Path);
|
||||
if (! dbFile.Exists())
|
||||
{
|
||||
// File does not exist
|
||||
Toast.MakeText(this, Resource.String.FileNotFound, ToastLength.Long).Show();
|
||||
Finish();
|
||||
return false;
|
||||
}
|
||||
|
||||
_keyFileOrProvider = GetKeyFile(_ioConnection.Path);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void InitializeOkButton()
|
||||
{
|
||||
Button confirmButton = (Button) FindViewById(Resource.Id.pass_ok);
|
||||
confirmButton.Click += (sender, e) =>
|
||||
{
|
||||
App.Kp2a.GetFileStorage(_ioConnection)
|
||||
.PrepareFileUsage(new FileStorageSetupInitiatorActivity(this, OnActivityResult, null), _ioConnection, RequestCodePrepareDbFile, false);
|
||||
.PrepareFileUsage(new FileStorageSetupInitiatorActivity(this, OnActivityResult, null), _ioConnection,
|
||||
RequestCodePrepareDbFile, false);
|
||||
};
|
||||
}
|
||||
|
||||
private void InitializeTogglePasswordButton()
|
||||
{
|
||||
ImageButton btnTogglePassword = (ImageButton) FindViewById(Resource.Id.toggle_password);
|
||||
btnTogglePassword.Click += (sender, e) =>
|
||||
{
|
||||
_showPassword = !_showPassword;
|
||||
MakePasswordMaskedOrVisible();
|
||||
};
|
||||
}
|
||||
|
||||
private void InitializeKeyfileBrowseButton()
|
||||
{
|
||||
ImageButton browse = (ImageButton) FindViewById(Resource.Id.browse_button);
|
||||
browse.Click += (sender, evt) =>
|
||||
{
|
||||
string filename = null;
|
||||
if (!String.IsNullOrEmpty(_ioConnection.Path))
|
||||
{
|
||||
File keyfile = new File(_ioConnection.Path);
|
||||
File parent = keyfile.ParentFile;
|
||||
if (parent != null)
|
||||
{
|
||||
filename = parent.AbsolutePath;
|
||||
}
|
||||
}
|
||||
Util.ShowBrowseDialog(filename, this, Intents.RequestCodeFileBrowseForKeyfile, false);
|
||||
};
|
||||
}
|
||||
|
||||
private void InitializePasswordModeSpinner()
|
||||
{
|
||||
Spinner passwordModeSpinner = FindViewById<Spinner>(Resource.Id.password_mode_spinner);
|
||||
if (passwordModeSpinner != null)
|
||||
{
|
||||
|
||||
UpdateKeyProviderUiState();
|
||||
passwordModeSpinner.SetSelection((int) KeyProviderType);
|
||||
passwordModeSpinner.ItemSelected += (sender, args) =>
|
||||
@ -466,16 +559,20 @@ namespace keepass2android
|
||||
case 2:
|
||||
_keyFileOrProvider = KeyProviderIdOtp;
|
||||
break;
|
||||
case 3:
|
||||
_keyFileOrProvider = KeyProviderIdOtpRecovery;
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Unexpected position "+args.Position+" / " + ((ICursor)((AdapterView)sender).GetItemAtPosition(args.Position)).GetString(1));
|
||||
|
||||
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);
|
||||
.PrepareFileUsage(new FileStorageSetupInitiatorActivity(this, OnActivityResult, null), _ioConnection,
|
||||
RequestCodePrepareOtpAuxFile, false);
|
||||
};
|
||||
}
|
||||
else
|
||||
@ -483,51 +580,35 @@ namespace keepass2android
|
||||
//android 2.x
|
||||
//TODO test
|
||||
}
|
||||
|
||||
|
||||
UpdateOkButtonState();
|
||||
|
||||
|
||||
|
||||
/*CheckBox checkBox = (CheckBox) FindViewById(Resource.Id.show_password);
|
||||
// Show or hide password
|
||||
checkBox.CheckedChange += delegate(object sender, CompoundButton.CheckedChangeEventArgs e) {
|
||||
|
||||
TextView password = (TextView) FindViewById(Resource.Id.password);
|
||||
if ( e.IsChecked ) {
|
||||
password.InputType = InputTypes.ClassText | InputTypes.TextVariationVisiblePassword;
|
||||
} else {
|
||||
password.InputType = InputTypes.ClassText | InputTypes.TextVariationPassword;
|
||||
}
|
||||
};
|
||||
*/
|
||||
ImageButton btnTogglePassword = (ImageButton)FindViewById(Resource.Id.toggle_password);
|
||||
btnTogglePassword.Click += (sender, e) =>
|
||||
|
||||
private void RestoreState(Bundle savedInstanceState)
|
||||
{
|
||||
_showPassword = !_showPassword;
|
||||
if (savedInstanceState != null)
|
||||
{
|
||||
_showPassword = savedInstanceState.GetBoolean(ShowpasswordKey, false);
|
||||
MakePasswordMaskedOrVisible();
|
||||
};
|
||||
|
||||
_keyFileOrProvider = FindViewById<EditText>(Resource.Id.pass_keyfile).Text = savedInstanceState.GetString(KeyFileOrProviderKey);
|
||||
_password = FindViewById<EditText>(Resource.Id.password).Text = savedInstanceState.GetString(PasswordKey);
|
||||
|
||||
_pendingOtps = new List<string>(savedInstanceState.GetStringArrayList(PendingOtpsKey));
|
||||
|
||||
ImageButton browse = (ImageButton)FindViewById(Resource.Id.browse_button);
|
||||
browse.Click += (sender, evt) =>
|
||||
string otpInfoString = savedInstanceState.GetString(OtpInfoKey);
|
||||
if (otpInfoString != null)
|
||||
{
|
||||
string filename = null;
|
||||
if (!String.IsNullOrEmpty(_ioConnection.Path))
|
||||
{
|
||||
File keyfile = new File(_ioConnection.Path);
|
||||
File parent = keyfile.ParentFile;
|
||||
if (parent != null)
|
||||
{
|
||||
filename = parent.AbsolutePath;
|
||||
|
||||
XmlSerializer xs = new XmlSerializer(typeof(OtpInfo));
|
||||
_otpInfo = (OtpInfo)xs.Deserialize(new StringReader(otpInfoString));
|
||||
|
||||
var enteredOtps = savedInstanceState.GetStringArrayList(EnteredOtpsKey);
|
||||
|
||||
ShowOtpEntry(enteredOtps);
|
||||
}
|
||||
|
||||
UpdateKeyProviderUiState();
|
||||
|
||||
}
|
||||
Util.ShowBrowseDialog(filename, this, Intents.RequestCodeFileBrowseForKeyfile, false);
|
||||
|
||||
};
|
||||
|
||||
RetrieveSettings();
|
||||
}
|
||||
|
||||
private void UpdateOkButtonState()
|
||||
@ -562,6 +643,9 @@ namespace keepass2android
|
||||
|
||||
FindViewById(Resource.Id.pass_ok).Enabled = enabled;
|
||||
break;
|
||||
case KeyProviders.OtpRecovery:
|
||||
FindViewById(Resource.Id.pass_ok).Enabled = FindViewById<EditText>(Resource.Id.pass_otpsecret).Text != "" && _password != "";
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
@ -575,6 +659,10 @@ namespace keepass2android
|
||||
FindViewById(Resource.Id.otpView).Visibility = KeyProviderType == KeyProviders.Otp
|
||||
? ViewStates.Visible
|
||||
: ViewStates.Gone;
|
||||
|
||||
FindViewById(Resource.Id.otpSecretLine).Visibility = KeyProviderType == KeyProviders.OtpRecovery
|
||||
? ViewStates.Visible
|
||||
: ViewStates.Gone;
|
||||
if (KeyProviderType == KeyProviders.Otp)
|
||||
{
|
||||
FindViewById(Resource.Id.otps_pending).Visibility = _pendingOtps.Count > 0 ? ViewStates.Visible : ViewStates.Gone;
|
||||
@ -597,7 +685,8 @@ namespace keepass2android
|
||||
catch (Exception e)
|
||||
{
|
||||
Kp2aLog.Log(e.ToString());
|
||||
throw new KeyFileException();
|
||||
Toast.MakeText(this, App.Kp2a.GetResourceString(UiStringKey.keyfile_does_not_exist), ToastLength.Long).Show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (KeyProviderType == KeyProviders.Otp)
|
||||
@ -605,26 +694,34 @@ namespace keepass2android
|
||||
|
||||
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);
|
||||
}
|
||||
var lOtps = GetOtpsFromUI();
|
||||
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();
|
||||
Toast.MakeText(this, GetString(Resource.String.OtpKeyError), ToastLength.Long).Show();
|
||||
|
||||
return;
|
||||
}
|
||||
compositeKey.AddUserKey(new KcpCustomKey(OathHotpKeyProv.Name, _otpInfo.Secret, true));
|
||||
}
|
||||
else if (KeyProviderType == KeyProviders.OtpRecovery)
|
||||
{
|
||||
Spinner stpDataFmtSpinner = FindViewById<Spinner>(Resource.Id.otpsecret_format_spinner);
|
||||
EditText secretEdit = FindViewById<EditText>(Resource.Id.pass_otpsecret);
|
||||
|
||||
byte[] pbSecret = EncodingUtil.ParseKey(secretEdit.Text, (OtpDataFmt)stpDataFmtSpinner.SelectedItemPosition);
|
||||
if (pbSecret != null)
|
||||
{
|
||||
compositeKey.AddUserKey(new KcpCustomKey(OathHotpKeyProv.Name, pbSecret, true));
|
||||
}
|
||||
else
|
||||
{
|
||||
Toast.MakeText(this, Resource.String.CouldntParseOtpSecret, ToastLength.Long).Show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox cbQuickUnlock = (CheckBox) FindViewById(Resource.Id.enable_quickunlock);
|
||||
App.Kp2a.SetQuickUnlockEnabled(cbQuickUnlock.Checked);
|
||||
@ -642,6 +739,18 @@ namespace keepass2android
|
||||
new ProgressTask(App.Kp2a, this, task).Run();
|
||||
}
|
||||
|
||||
private List<string> GetOtpsFromUI()
|
||||
{
|
||||
List<string> lOtps = new List<string>();
|
||||
foreach (int otpId in _otpTextViewIds)
|
||||
{
|
||||
string otpText = FindViewById<EditText>(otpId).Text;
|
||||
if (!String.IsNullOrEmpty(otpText))
|
||||
lOtps.Add(otpText);
|
||||
}
|
||||
return lOtps;
|
||||
}
|
||||
|
||||
private void CreateOtpSecret(List<string> lOtps)
|
||||
{
|
||||
byte[] pbSecret;
|
||||
@ -785,13 +894,32 @@ namespace keepass2android
|
||||
base.OnSaveInstanceState(outState);
|
||||
AppTask.ToBundle(outState);
|
||||
outState.PutBoolean(ShowpasswordKey, _showPassword);
|
||||
//TODO:
|
||||
// * save OTP state
|
||||
|
||||
outState.PutString(KeyFileOrProviderKey, _keyFileOrProvider);
|
||||
outState.PutString(PasswordKey, _password);
|
||||
outState.PutStringArrayList(PendingOtpsKey, _pendingOtps);
|
||||
if (_otpInfo != null)
|
||||
{
|
||||
outState.PutStringArrayList(EnteredOtpsKey, GetOtpsFromUI());
|
||||
|
||||
var sw = new StringWriter();
|
||||
|
||||
var xws = OtpInfo.XmlWriterSettings();
|
||||
|
||||
XmlWriter xw = XmlWriter.Create(sw, xws);
|
||||
|
||||
XmlSerializer xs = new XmlSerializer(typeof(OtpInfo));
|
||||
xs.Serialize(xw, _otpInfo);
|
||||
|
||||
xw.Close();
|
||||
|
||||
outState.PutString(OtpInfoKey, sw.ToString());
|
||||
}
|
||||
|
||||
//more OTP TODO:
|
||||
// * NfcOtp: Ask for close when db open
|
||||
// * Caching of aux file
|
||||
// * -> implement IFileStorage in JavaFileStorage based on ListFiles
|
||||
// * -> Sync
|
||||
}
|
||||
|
||||
protected override void OnNewIntent(Intent intent)
|
||||
@ -896,7 +1024,7 @@ namespace keepass2android
|
||||
}
|
||||
}
|
||||
|
||||
private void RetrieveSettings() {
|
||||
private void InitializeQuickUnlockCheckbox() {
|
||||
CheckBox cbQuickUnlock = (CheckBox)FindViewById(Resource.Id.enable_quickunlock);
|
||||
cbQuickUnlock.Checked = _prefs.GetBoolean(GetString(Resource.String.QuickUnlockDefaultEnabled_key), true);
|
||||
}
|
||||
@ -909,7 +1037,7 @@ namespace keepass2android
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulateView() {
|
||||
private void InitializeFilenameView() {
|
||||
SetEditText(Resource.Id.filename, App.Kp2a.GetFileStorage(_ioConnection).GetDisplayName(_ioConnection));
|
||||
if (App.Kp2a.FileDbHelper.NumberOfRecentFiles() < 2)
|
||||
{
|
||||
@ -919,8 +1047,7 @@ namespace keepass2android
|
||||
{
|
||||
FindViewById(Resource.Id.filename_group).Visibility = ViewStates.Visible;
|
||||
}
|
||||
if (KeyProviderType == KeyProviders.KeyFile)
|
||||
SetEditText(Resource.Id.pass_keyfile, _keyFileOrProvider);
|
||||
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
@ -937,15 +1064,6 @@ namespace keepass2android
|
||||
}
|
||||
*/
|
||||
|
||||
private void ErrorMessage(int resId)
|
||||
{
|
||||
Toast.MakeText(this, resId, ToastLength.Long).Show();
|
||||
}
|
||||
|
||||
private String GetEditText(int resId) {
|
||||
return Util.GetEditText(this, resId);
|
||||
}
|
||||
|
||||
private void SetEditText(int resId, String str) {
|
||||
TextView te = (TextView) FindViewById(resId);
|
||||
//assert(te == null);
|
||||
@ -1002,13 +1120,40 @@ namespace keepass2android
|
||||
|
||||
|
||||
public override void Run() {
|
||||
|
||||
if (_act.KeyProviderType == KeyProviders.Otp)
|
||||
{
|
||||
try
|
||||
{
|
||||
StatusLogger.UpdateMessage(UiStringKey.SavingOtpAuxFile);
|
||||
|
||||
if (!OathHotpKeyProv.CreateAuxFile(_act._otpInfo, new KeyProviderQueryContext(_act._ioConnection, false, false)))
|
||||
Toast.MakeText(_act, _act.GetString(Resource.String.ErrorUpdatingOtpAuxFile), ToastLength.Long).Show();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Kp2aLog.Log(e.Message);
|
||||
|
||||
Toast.MakeText(_act, _act.GetString(Resource.String.ErrorUpdatingOtpAuxFile)+" "+e.Message, ToastLength.Long).Show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ( Success )
|
||||
{
|
||||
_act.SetEditText(Resource.Id.password, "");
|
||||
_act.SetEditText(Resource.Id.pass_otpsecret, "");
|
||||
foreach (int otpId in _act._otpTextViewIds)
|
||||
{
|
||||
_act.SetEditText(otpId, "");
|
||||
}
|
||||
|
||||
_act.LaunchNextActivity();
|
||||
|
||||
GC.Collect(); // Ensure temporary memory used while loading is collected - it will contain sensitive data such as username and password, and also the large data of the encrypted database file
|
||||
if ((_act.KeyProviderType == KeyProviders.Otp) || (_act.KeyProviderType == KeyProviders.OtpRecovery))
|
||||
App.Kp2a.GetDb().OtpAuxFileIoc = OathHotpKeyProv.GetAuxFileIoc(_act._ioConnection);
|
||||
|
||||
GC.Collect(); // Ensure temporary memory used while loading is collected
|
||||
}
|
||||
else
|
||||
{
|
||||
|
1119
src/keepass2android/Resources/Resource.designer.cs
generated
1119
src/keepass2android/Resources/Resource.designer.cs
generated
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
>
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="12dip"
|
||||
android:layout_marginRight="12dip"
|
||||
android:layout_marginBottom="12dip"
|
||||
@ -51,7 +56,7 @@
|
||||
android:id="@+id/password_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="" />
|
||||
android:text="@string/master_key_type" />
|
||||
<Spinner
|
||||
android:id="@+id/password_mode_spinner"
|
||||
android:layout_width="fill_parent"
|
||||
@ -108,6 +113,7 @@
|
||||
android:id="@+id/otpInitView"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
<Button
|
||||
android:id="@+id/init_otp"
|
||||
@ -169,6 +175,24 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/otpSecretLine"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<EditText
|
||||
android:id="@+id/pass_otpsecret"
|
||||
android:layout_width="0px"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:layout_weight="1"
|
||||
android:hint="@string/otpsecret_hint" />
|
||||
<Spinner
|
||||
android:id="@+id/otpsecret_format_spinner"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
/>
|
||||
</LinearLayout>
|
||||
<Button
|
||||
android:id="@+id/pass_ok"
|
||||
@ -186,3 +210,4 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/enable_quickunlock" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
@ -143,6 +143,7 @@
|
||||
<string name="omitbackup_summary">Omit \'Backup\' and Recycle Bin group from search results</string>
|
||||
<string name="pass_filename">KeePass database filename</string>
|
||||
<string name="password_title">Enter database password</string>
|
||||
<string name="master_key_type">Select master key type:</string>
|
||||
<string name="progress_create">Creating new database…</string>
|
||||
<string name="create_database">Create database</string>
|
||||
<string name="progress_title">Working…</string>
|
||||
@ -299,9 +300,17 @@
|
||||
<string name="UpdatedRemoteFileOnLoad">Updated remote file.</string>
|
||||
<string name="NotifyOpenFromLocalDueToConflict">Opened local file due to conflict with changes in remote file. Use Synchronize menu to merge.</string>
|
||||
<string name="LoadedFromRemoteInSync">Remote file and cache are synchronized.</string>
|
||||
<string name="UpdatedCachedFileOnLoad">Updated local cache copy of database.</string>
|
||||
<string name="UpdatedCachedFileOnLoad">Updated local cache copy of %1$s.</string>
|
||||
<string name="RemoteDatabaseUnchanged">No changes detected.</string>
|
||||
|
||||
<string name="ResolvedCacheConflictByUsingRemoteOtpAux">Updated cached OTP auxiliary file: Remote counter was higher.</string>
|
||||
<string name="ResolvedCacheConflictByUsingLocalOtpAux">Updated remote OTP auxiliary file: Local counter was higher.</string>
|
||||
|
||||
<string name="SynchronizingOtpAuxFile">Synchronizing OTP auxiliary file…</string>
|
||||
|
||||
<string name="database_file">database file</string>
|
||||
<string name="otp_aux_file">OTP auxiliary file</string>
|
||||
|
||||
<string name="ErrorOcurred">An error occured:</string>
|
||||
|
||||
<string name="synchronize_database_menu">Synchronize database…</string>
|
||||
@ -343,14 +352,19 @@
|
||||
|
||||
<string name="error_adding_keyfile">Error while adding the keyfile!</string>
|
||||
|
||||
<string name="init_otp">Enter OTPs…</string>
|
||||
<string name="init_otp">Load OTP auxiliary file…</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="otp_discarded_because_no_db">Please select database first. OTP is discarded for security reasons.</string>
|
||||
<string name="otp_discarded_no_space">OTP discarded: All OTPs already entered!</string>
|
||||
<string name="otp_discarded_because_db_open">Please close database first. OTP is discarded.</string>
|
||||
<string name="otps_pending">(One or more OTPs already available)</string>
|
||||
|
||||
<string name="otpsecret_hint">OTP secret (e.g. 01 23 ab cd…)</string>
|
||||
<string name="CouldntParseOtpSecret">Error parsing OTP secret!</string>
|
||||
<string name="OtpKeyError">Failed to create OTP key! Make sure you have entered the correct OTPs.</string>
|
||||
<string name="ErrorUpdatingOtpAuxFile">Error updating OTP auxiliary file!</string>
|
||||
<string name="SavingOtpAuxFile">Saving auxiliary OTP file…</string>
|
||||
|
||||
<string name="loading">Loading…</string>
|
||||
|
||||
@ -477,5 +491,6 @@ Initial public release
|
||||
<item>Password only</item>
|
||||
<item>Password + Key file</item>
|
||||
<item>Password + OTP</item>
|
||||
<item>Password + OTP secret (recovery mode)</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
|
@ -49,13 +49,19 @@ namespace OtpKeyProv
|
||||
private static IOConnectionInfo GetAuxFileIoc(KeyProviderQueryContext ctx)
|
||||
{
|
||||
IOConnectionInfo ioc = ctx.DatabaseIOInfo.CloneDeep();
|
||||
IFileStorage fileStorage = App.Kp2a.GetOtpAuxFileStorage(ioc);
|
||||
IOConnectionInfo iocAux = fileStorage.GetFilePath(fileStorage.GetParentPath(ioc),
|
||||
fileStorage.GetFilenameWithoutPathAndExt(ioc) + AuxFileExt);
|
||||
var iocAux = GetAuxFileIoc(ioc);
|
||||
|
||||
return iocAux;
|
||||
}
|
||||
|
||||
public static IOConnectionInfo GetAuxFileIoc(IOConnectionInfo databaseIoc)
|
||||
{
|
||||
IFileStorage fileStorage = App.Kp2a.GetOtpAuxFileStorage(databaseIoc);
|
||||
IOConnectionInfo iocAux = fileStorage.GetFilePath(fileStorage.GetParentPath(databaseIoc),
|
||||
fileStorage.GetFilenameWithoutPathAndExt(databaseIoc) + AuxFileExt);
|
||||
return iocAux;
|
||||
}
|
||||
|
||||
public static OtpInfo LoadOtpInfo(KeyProviderQueryContext ctx)
|
||||
{
|
||||
return OtpInfo.Load(GetAuxFileIoc(ctx));
|
||||
@ -130,7 +136,7 @@ namespace OtpKeyProv
|
||||
}
|
||||
|
||||
|
||||
private static bool CreateAuxFile(OtpInfo otpInfo,
|
||||
public static bool CreateAuxFile(OtpInfo otpInfo,
|
||||
KeyProviderQueryContext ctx)
|
||||
{
|
||||
otpInfo.Type = ProvType;
|
||||
|
@ -0,0 +1,70 @@
|
||||
using System.IO;
|
||||
using System.Xml.Serialization;
|
||||
using KeePassLib.Serialization;
|
||||
using OtpKeyProv;
|
||||
using keepass2android.Io;
|
||||
|
||||
namespace keepass2android.addons.OtpKeyProv
|
||||
{
|
||||
/// <summary>
|
||||
/// Class which provides caching for OtpInfo-files. This is an extension to CachingFileStorage required to handle conflicts directly when loading.
|
||||
/// </summary>
|
||||
class OtpAuxCachingFileStorage: CachingFileStorage
|
||||
{
|
||||
private readonly IOtpAuxCacheSupervisor _cacheSupervisor;
|
||||
|
||||
internal interface IOtpAuxCacheSupervisor: ICacheSupervisor
|
||||
{
|
||||
/// <summary>
|
||||
/// called when there was a conflict which was resolved by using the remote file.
|
||||
/// </summary>
|
||||
void ResolvedCacheConflictByUsingRemote(IOConnectionInfo ioc);
|
||||
|
||||
/// <summary>
|
||||
/// called when there was a conflict which was resolved by using the local file.
|
||||
/// </summary>
|
||||
void ResolvedCacheConflictByUsingLocal(IOConnectionInfo ioc);
|
||||
}
|
||||
|
||||
|
||||
public OtpAuxCachingFileStorage(IFileStorage cachedStorage, string cacheDir, IOtpAuxCacheSupervisor cacheSupervisor)
|
||||
: base(cachedStorage, cacheDir, cacheSupervisor)
|
||||
{
|
||||
_cacheSupervisor = cacheSupervisor;
|
||||
}
|
||||
|
||||
protected override Stream OpenFileForReadWithConflict(IOConnectionInfo ioc, string cachedFilePath)
|
||||
{
|
||||
OtpInfo remoteOtpInfo, localOtpInfo;
|
||||
//load both files
|
||||
XmlSerializer xs = new XmlSerializer(typeof (OtpInfo));
|
||||
localOtpInfo = (OtpInfo) xs.Deserialize(File.OpenRead(cachedFilePath));
|
||||
using (Stream remoteStream = _cachedStorage.OpenFileForRead(ioc))
|
||||
{
|
||||
remoteOtpInfo = (OtpInfo) xs.Deserialize(remoteStream);
|
||||
}
|
||||
|
||||
//see which OtpInfo has the bigger Counter value and use this one:
|
||||
if (localOtpInfo.Counter > remoteOtpInfo.Counter)
|
||||
{
|
||||
//overwrite the remote file
|
||||
UpdateRemoteFile(File.OpenRead(cachedFilePath),
|
||||
ioc,
|
||||
App.Kp2a.GetBooleanPreference(PreferenceKey.UseFileTransactions),
|
||||
GetBaseVersionHash(ioc)
|
||||
);
|
||||
|
||||
_cacheSupervisor.ResolvedCacheConflictByUsingRemote(ioc);
|
||||
}
|
||||
else
|
||||
{
|
||||
//overwrite the local file:
|
||||
UpdateCacheFromRemote(ioc, cachedFilePath);
|
||||
_cacheSupervisor.ResolvedCacheConflictByUsingLocal(ioc);
|
||||
}
|
||||
|
||||
//now return the local file in any way:
|
||||
return File.OpenRead(cachedFilePath);
|
||||
}
|
||||
}
|
||||
}
|
@ -197,18 +197,8 @@ namespace OtpKeyProv
|
||||
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";
|
||||
|
||||
XmlWriter xw = XmlWriter.Create(trans.OpenFile(), xws);
|
||||
|
||||
XmlSerializer xs = new XmlSerializer(typeof (OtpInfo));
|
||||
xs.Serialize(xw, otpInfo);
|
||||
|
||||
xw.Close();
|
||||
var stream = trans.OpenFile();
|
||||
WriteToStream(otpInfo, stream);
|
||||
trans.CommitWrite();
|
||||
}
|
||||
return true;
|
||||
@ -222,6 +212,31 @@ namespace OtpKeyProv
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static void WriteToStream(OtpInfo otpInfo, Stream stream)
|
||||
{
|
||||
var xws = XmlWriterSettings();
|
||||
|
||||
XmlWriter xw = XmlWriter.Create(stream, xws);
|
||||
|
||||
XmlSerializer xs = new XmlSerializer(typeof (OtpInfo));
|
||||
xs.Serialize(xw, otpInfo);
|
||||
|
||||
xw.Close();
|
||||
}
|
||||
|
||||
public static XmlWriterSettings XmlWriterSettings()
|
||||
{
|
||||
XmlWriterSettings xws = new XmlWriterSettings
|
||||
{
|
||||
CloseOutput = true,
|
||||
Encoding = StrUtil.Utf8,
|
||||
Indent = true,
|
||||
IndentChars = "\t"
|
||||
};
|
||||
return xws;
|
||||
}
|
||||
|
||||
public void EncryptSecret()
|
||||
{
|
||||
if(m_pbSecret == null) throw new InvalidOperationException();
|
||||
|
@ -32,6 +32,7 @@ using Android.Preferences;
|
||||
using TwofishCipher;
|
||||
#endif
|
||||
using keepass2android.Io;
|
||||
using keepass2android.addons.OtpKeyProv;
|
||||
|
||||
namespace keepass2android
|
||||
{
|
||||
@ -294,8 +295,11 @@ namespace keepass2android
|
||||
|
||||
builder.SetNegativeButton(GetResourceString(noString), noHandler);
|
||||
|
||||
if (cancelHandler != null)
|
||||
{
|
||||
builder.SetNeutralButton(ctx.GetString(Android.Resource.String.Cancel),
|
||||
cancelHandler);
|
||||
}
|
||||
|
||||
Dialog dialog = builder.Create();
|
||||
dialog.Show();
|
||||
@ -447,7 +451,7 @@ namespace keepass2android
|
||||
return _db;
|
||||
}
|
||||
|
||||
void ShowToast(string message)
|
||||
internal void ShowToast(string message)
|
||||
{
|
||||
var handler = new Handler(Looper.MainLooper);
|
||||
handler.Post(() => { Toast.MakeText(Application.Context, message, ToastLength.Long).Show(); });
|
||||
@ -466,7 +470,8 @@ namespace keepass2android
|
||||
|
||||
public void UpdatedCachedFileOnLoad(IOConnectionInfo ioc)
|
||||
{
|
||||
ShowToast(Application.Context.GetString(Resource.String.UpdatedCachedFileOnLoad));
|
||||
ShowToast(Application.Context.GetString(Resource.String.UpdatedCachedFileOnLoad,
|
||||
new Java.Lang.Object[] { Application.Context.GetString(Resource.String.database_file) }));
|
||||
}
|
||||
|
||||
public void UpdatedRemoteFileOnLoad(IOConnectionInfo ioc)
|
||||
@ -510,7 +515,7 @@ namespace keepass2android
|
||||
|
||||
if (DatabaseCacheEnabled)
|
||||
{
|
||||
return new CachingFileStorage(innerFileStorage, Application.Context.CacheDir.Path, this);
|
||||
return new OtpAuxCachingFileStorage(innerFileStorage, Application.Context.CacheDir.Path, new OtpAuxCacheSupervisor(this));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
59
src/keepass2android/app/OtpAuxCacheSupervisor.cs
Normal file
59
src/keepass2android/app/OtpAuxCacheSupervisor.cs
Normal file
@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using Android.App;
|
||||
using KeePassLib.Serialization;
|
||||
using keepass2android.addons.OtpKeyProv;
|
||||
|
||||
namespace keepass2android
|
||||
{
|
||||
public class OtpAuxCacheSupervisor : OtpAuxCachingFileStorage.IOtpAuxCacheSupervisor
|
||||
{
|
||||
private readonly Kp2aApp _app;
|
||||
|
||||
public OtpAuxCacheSupervisor(Kp2aApp app)
|
||||
{
|
||||
_app = app;
|
||||
}
|
||||
|
||||
public void CouldntSaveToRemote(IOConnectionInfo ioc, Exception ex)
|
||||
{
|
||||
_app.CouldntSaveToRemote(ioc, ex);
|
||||
}
|
||||
|
||||
public void CouldntOpenFromRemote(IOConnectionInfo ioc, Exception ex)
|
||||
{
|
||||
_app.CouldntOpenFromRemote(ioc, ex);
|
||||
}
|
||||
|
||||
public void UpdatedCachedFileOnLoad(IOConnectionInfo ioc)
|
||||
{
|
||||
_app.ShowToast(Application.Context.GetString(Resource.String.UpdatedCachedFileOnLoad,
|
||||
new Java.Lang.Object[] { Application.Context.GetString(Resource.String.otp_aux_file) }));
|
||||
}
|
||||
|
||||
public void UpdatedRemoteFileOnLoad(IOConnectionInfo ioc)
|
||||
{
|
||||
_app.UpdatedRemoteFileOnLoad(ioc);
|
||||
}
|
||||
|
||||
public void NotifyOpenFromLocalDueToConflict(IOConnectionInfo ioc)
|
||||
{
|
||||
//must not be called . Conflicts should be resolved.
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
public void LoadedFromRemoteInSync(IOConnectionInfo ioc)
|
||||
{
|
||||
_app.LoadedFromRemoteInSync(ioc);
|
||||
}
|
||||
|
||||
public void ResolvedCacheConflictByUsingRemote(IOConnectionInfo ioc)
|
||||
{
|
||||
_app.ShowToast(Application.Context.GetString(Resource.String.ResolvedCacheConflictByUsingRemoteOtpAux));
|
||||
}
|
||||
|
||||
public void ResolvedCacheConflictByUsingLocal(IOConnectionInfo ioc)
|
||||
{
|
||||
_app.ShowToast(Application.Context.GetString(Resource.String.ResolvedCacheConflictByUsingLocalOtpAux));
|
||||
}
|
||||
}
|
||||
}
|
@ -373,7 +373,7 @@ namespace keepass2android
|
||||
#if !EXCLUDE_FILECHOOSER
|
||||
StartFileChooser(ioc.Path);
|
||||
#else
|
||||
LaunchPasswordActivityForIoc(new IOConnectionInfo { Path = "/mnt/sdcard/keepass/yubi2.kdbx"});
|
||||
LaunchPasswordActivityForIoc(new IOConnectionInfo { Path = "/mnt/sdcard/keepass/yubi.kdbx"});
|
||||
#endif
|
||||
}
|
||||
if ((resultCode == Result.Canceled) && (data != null) && (data.HasExtra("EXTRA_ERROR_MESSAGE")))
|
||||
|
@ -84,9 +84,11 @@
|
||||
<ItemGroup>
|
||||
<Compile Include="addons\OtpKeyProv\EncodingUtil.cs" />
|
||||
<Compile Include="addons\OtpKeyProv\OathHotpKeyProv.cs" />
|
||||
<Compile Include="addons\OtpKeyProv\OtpAuxCachingFileStorage.cs" />
|
||||
<Compile Include="addons\OtpKeyProv\OtpInfo.cs" />
|
||||
<Compile Include="addons\OtpKeyProv\OtpUtil.cs" />
|
||||
<Compile Include="app\NoFileStorageFoundException.cs" />
|
||||
<Compile Include="app\OtpAuxCacheSupervisor.cs" />
|
||||
<Compile Include="CreateDatabaseActivity.cs" />
|
||||
<Compile Include="fileselect\FileChooserFileProvider.cs" />
|
||||
<Compile Include="fileselect\FileStorageSetupActivity.cs" />
|
||||
|
Loading…
Reference in New Issue
Block a user