/*
This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin.
Keepass2Android is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Keepass2Android is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Keepass2Android. If not, see .
*/
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;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Java.Net;
using Android.Preferences;
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;
using String = System.String;
namespace keepass2android
{
[Activity (Label = "@string/app_name",
ConfigurationChanges=ConfigChanges.Orientation|ConfigChanges.KeyboardHidden,
LaunchMode = LaunchMode.SingleInstance,
Theme="@style/Base")]
public class PasswordActivity : LockingActivity {
enum KeyProviders
{
//int values correspond to indices in passwordSpinner
None = 0,
KeyFile = 1,
Otp = 2,
OtpRecovery = 3
}
public const String KeyDefaultFilename = "defaultFileName";
public const String KeyFilename = "fileName";
private const String KeyKeyfile = "keyFile";
public const String KeyServerusername = "serverCredUser";
public const String KeyServerpassword = "serverCredPwd";
public const String KeyServercredmode = "serverCredRememberMode";
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 _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 List _pendingOtps = new List();
KeyProviders KeyProviderType
{
get
{
if (_keyFileOrProvider == null)
return KeyProviders.None;
if (_keyFileOrProvider == KeyProviderIdOtp)
return KeyProviders.Otp;
if (_keyFileOrProvider == KeyProviderIdOtpRecovery)
return KeyProviders.OtpRecovery;
return KeyProviders.KeyFile;
}
}
private bool _rememberKeyfile;
ISharedPreferences _prefs;
private bool _starting;
private OtpInfo _otpInfo;
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)
{
}
public PasswordActivity()
{
}
public static void PutIoConnectionToIntent(IOConnectionInfo ioc, Intent i)
{
i.PutExtra(KeyFilename, ioc.Path);
i.PutExtra(KeyServerusername, ioc.UserName);
i.PutExtra(KeyServerpassword, ioc.Password);
i.PutExtra(KeyServercredmode, (int)ioc.CredSaveMode);
}
public static void SetIoConnectionFromIntent(IOConnectionInfo ioc, Intent i)
{
ioc.Path = i.GetStringExtra(KeyFilename);
ioc.UserName = i.GetStringExtra(KeyServerusername) ?? "";
ioc.Password = i.GetStringExtra(KeyServerpassword) ?? "";
ioc.CredSaveMode = (IOCredSaveMode)i.GetIntExtra(KeyServercredmode, (int) IOCredSaveMode.NoSave);
}
public static void Launch(Activity act, String fileName, AppTask appTask) {
File dbFile = new File(fileName);
if ( ! dbFile.Exists() ) {
throw new FileNotFoundException();
}
Intent i = new Intent(act, typeof(PasswordActivity));
i.SetFlags(ActivityFlags.ClearTask | ActivityFlags.NewTask);
i.PutExtra(KeyFilename, fileName);
appTask.ToIntent(i);
act.StartActivityForResult(i, 0);
}
public static void Launch(Activity act, IOConnectionInfo ioc, AppTask appTask)
{
if (ioc.IsLocalFile())
{
Launch(act, ioc.Path, appTask);
return;
}
Intent i = new Intent(act, typeof(PasswordActivity));
PutIoConnectionToIntent(ioc, i);
i.SetFlags(ActivityFlags.ClearTask);
appTask.ToIntent(i);
act.StartActivityForResult(i, 0);
}
public void LaunchNextActivity()
{
AppTask.AfterUnlockDatabase(this);
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
Kp2aLog.Log("PasswordActivity.OnActivityResult "+resultCode+"/"+requestCode);
//NOTE: original code from k eepassdroid used switch ((Android.App.Result)requestCode) { (but doesn't work here, although k eepassdroid works)
switch(resultCode) {
case KeePass.ExitNormal: // Returned to this screen using the Back key, treat as locking the database
App.Kp2a.LockDatabase();
break;
case KeePass.ExitLock:
// The database has already been locked, and the quick unlock screen will be shown if appropriate
break;
case KeePass.ExitCloseAfterTaskComplete:
// Do not lock the database
SetResult(KeePass.ExitCloseAfterTaskComplete);
Finish();
break;
case KeePass.ExitClose:
SetResult(KeePass.ExitClose);
Finish();
break;
case KeePass.ExitReloadDb:
//if the activity was killed, fill password/keyfile so the user can directly hit load again
if (App.Kp2a.GetDb().Loaded)
{
if (App.Kp2a.GetDb().KpDatabase.MasterKey.ContainsType(typeof(KcpPassword)))
{
KcpPassword kcpPassword = (KcpPassword)App.Kp2a.GetDb().KpDatabase.MasterKey.GetUserKey(typeof(KcpPassword));
String password = kcpPassword.Password.ReadString();
SetEditText(Resource.Id.password, password);
}
if (App.Kp2a.GetDb().KpDatabase.MasterKey.ContainsType(typeof(KcpKeyFile)))
{
KcpKeyFile kcpKeyfile = (KcpKeyFile)App.Kp2a.GetDb().KpDatabase.MasterKey.GetUserKey(typeof(KcpKeyFile));
SetEditText(Resource.Id.pass_keyfile, kcpKeyfile.Path);
}
}
App.Kp2a.LockDatabase(false);
break;
case Result.Ok: // Key file browse dialog OK'ed.
if (requestCode == Intents.RequestCodeFileBrowseForKeyfile) {
string filename = Util.IntentToFilename(data, this);
if (filename != null) {
if (filename.StartsWith("file://")) {
filename = filename.Substring(7);
}
filename = URLDecoder.Decode(filename);
EditText fn = (EditText) FindViewById(Resource.Id.pass_keyfile);
fn.Text = filename;
}
}
break;
case (Result)FileStorageResults.FileUsagePrepared:
if (requestCode == RequestCodePrepareDbFile)
PerformLoadDatabase();
if (requestCode == RequestCodePrepareOtpAuxFile)
LoadOtpFile();
break;
}
}
private void LoadOtpFile()
{
new LoadingDialog