Introduced IDatabaseLoader
(kdb not yet working)
This commit is contained in:
@@ -3,6 +3,7 @@ using Android.App;
|
||||
using System.IO;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using KeePassLib;
|
||||
using KeePassLib.Keys;
|
||||
using KeePassLib.Serialization;
|
||||
using keepass2android.Io;
|
||||
@@ -23,7 +24,7 @@ namespace keepass2android
|
||||
/// <summary>
|
||||
/// Loads the specified data as the currently open database, as unlocked.
|
||||
/// </summary>
|
||||
void LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, CompositeKey compKey, ProgressDialogStatusLogger statusLogger);
|
||||
void LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, CompositeKey compKey, ProgressDialogStatusLogger statusLogger, IDatabaseLoader databaseLoader);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current database
|
||||
|
||||
@@ -93,34 +93,15 @@ namespace keepass2android
|
||||
/// <summary>
|
||||
/// Do not call this method directly. Call App.Kp2a.LoadDatabase instead.
|
||||
/// </summary>
|
||||
public void LoadData(IKp2aApp app, IOConnectionInfo iocInfo, MemoryStream databaseData, CompositeKey compositeKey, ProgressDialogStatusLogger status)
|
||||
public void LoadData(IKp2aApp app, IOConnectionInfo iocInfo, MemoryStream databaseData, CompositeKey compositeKey, ProgressDialogStatusLogger status, IDatabaseLoader databaseLoader)
|
||||
{
|
||||
PwDatabase pwDatabase = new PwDatabase();
|
||||
|
||||
IFileStorage fileStorage = _app.GetFileStorage(iocInfo);
|
||||
var filename = fileStorage.GetFilenameWithoutPathAndExt(iocInfo);
|
||||
try
|
||||
{
|
||||
var fileVersion = _app.GetFileStorage(iocInfo).GetCurrentFileVersionFast(iocInfo);
|
||||
pwDatabase.Open(databaseData ?? fileStorage.OpenFileForRead(iocInfo), filename, iocInfo, compositeKey, status);
|
||||
LastFileVersion = fileVersion;
|
||||
}
|
||||
catch (InvalidCompositeKeyException)
|
||||
{
|
||||
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:
|
||||
compositeKey.RemoveUserKey(compositeKey.GetUserKey(typeof (KcpPassword)));
|
||||
var fileVersion = _app.GetFileStorage(iocInfo).GetCurrentFileVersionFast(iocInfo);
|
||||
//don't reuse the memory stream databaseData: it's already closed.
|
||||
//We could try to avoid reading the file again here, but probably the case is rare enough so this is ok.
|
||||
pwDatabase.Open(fileStorage.OpenFileForRead(iocInfo), filename, iocInfo, compositeKey, status);
|
||||
LastFileVersion = fileVersion; }
|
||||
else throw;
|
||||
}
|
||||
Stream s = databaseData ?? fileStorage.OpenFileForRead(iocInfo);
|
||||
var fileVersion = _app.GetFileStorage(iocInfo).GetCurrentFileVersionFast(iocInfo);
|
||||
PopulateDatabaseFromStream(pwDatabase, s, iocInfo, compositeKey, status, databaseLoader);
|
||||
LastFileVersion = fileVersion;
|
||||
|
||||
status.UpdateSubMessage("");
|
||||
|
||||
@@ -133,7 +114,14 @@ namespace keepass2android
|
||||
SearchHelper = new SearchDbHelper(app);
|
||||
}
|
||||
|
||||
|
||||
protected virtual void PopulateDatabaseFromStream(PwDatabase pwDatabase, Stream s, IOConnectionInfo iocInfo, CompositeKey compositeKey, ProgressDialogStatusLogger status, IDatabaseLoader databaseLoader)
|
||||
{
|
||||
IFileStorage fileStorage = _app.GetFileStorage(iocInfo);
|
||||
var filename = fileStorage.GetFilenameWithoutPathAndExt(iocInfo);
|
||||
pwDatabase.Open(s, filename, iocInfo, compositeKey, status, databaseLoader);
|
||||
}
|
||||
|
||||
|
||||
public PwGroup SearchForText(String str) {
|
||||
PwGroup group = SearchHelper.SearchForText(this, str);
|
||||
|
||||
@@ -162,7 +150,7 @@ namespace keepass2android
|
||||
}
|
||||
|
||||
|
||||
public void SaveData(Context ctx) {
|
||||
public virtual void SaveData(Context ctx) {
|
||||
|
||||
KpDatabase.UseFileTransactions = _app.GetBooleanPreference(PreferenceKey.UseFileTransactions);
|
||||
using (IWriteTransaction trans = _app.GetFileStorage(Ioc).OpenWriteTransaction(Ioc, KpDatabase.UseFileTransactions))
|
||||
|
||||
76
src/Kp2aBusinessLogic/database/KdbDatabaseLoader.cs
Normal file
76
src/Kp2aBusinessLogic/database/KdbDatabaseLoader.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using Android.Content;
|
||||
using Com.Keepassdroid.Database;
|
||||
using Com.Keepassdroid.Database.Exception;
|
||||
using Java.Lang;
|
||||
using KeePassLib;
|
||||
using KeePassLib.Cryptography;
|
||||
using KeePassLib.Cryptography.Cipher;
|
||||
using KeePassLib.Interfaces;
|
||||
using KeePassLib.Keys;
|
||||
using Exception = System.Exception;
|
||||
|
||||
namespace keepass2android
|
||||
{
|
||||
class KdbDatabaseLoader: IDatabaseLoader
|
||||
{
|
||||
private Context _ctx;
|
||||
|
||||
public KdbDatabaseLoader(Context ctx)
|
||||
{
|
||||
_ctx = ctx;
|
||||
}
|
||||
|
||||
public void PopulateDatabaseFromStream(PwDatabase db, CompositeKey key, Stream s, IStatusLogger slLogger)
|
||||
{
|
||||
var importer = new Com.Keepassdroid.Database.Load.ImporterV3();
|
||||
|
||||
var hashingStream = new HashingStreamEx(s, false, new SHA256Managed());
|
||||
|
||||
string password = "";//no need to distinguish between null and "" because empty passwords are invalid (and null is not allowed)
|
||||
KcpPassword passwordKey = (KcpPassword)key.GetUserKey(typeof(KcpPassword));
|
||||
if (passwordKey != null)
|
||||
{
|
||||
password = passwordKey.Password.ReadString();
|
||||
}
|
||||
|
||||
KcpKeyFile passwordKeyfile = (KcpKeyFile)key.GetUserKey(typeof(KcpKeyFile));
|
||||
string keyfile = "";
|
||||
if (passwordKeyfile != null)
|
||||
{
|
||||
keyfile = passwordKeyfile.Path;
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
var dbv3 = importer.OpenDatabase(hashingStream, password, keyfile);
|
||||
|
||||
db.Name = dbv3.Name;
|
||||
}
|
||||
catch (InvalidPasswordException e) {
|
||||
|
||||
return;
|
||||
}
|
||||
catch (Java.IO.FileNotFoundException e)
|
||||
{
|
||||
throw new FileNotFoundException(
|
||||
e.Message, e);
|
||||
}
|
||||
catch (Java.Lang.Exception e)
|
||||
{
|
||||
throw new Exception(e.LocalizedMessage ??
|
||||
e.Message ??
|
||||
e.GetType().Name, e);
|
||||
}
|
||||
|
||||
HashOfLastStream = hashingStream.Hash;
|
||||
if (HashOfLastStream == null)
|
||||
throw new Exception("hashing didn't work"); //todo remove
|
||||
}
|
||||
|
||||
public byte[] HashOfLastStream { get; private set; }
|
||||
}
|
||||
}
|
||||
31
src/Kp2aBusinessLogic/database/KdbxDatabaseLoader.cs
Normal file
31
src/Kp2aBusinessLogic/database/KdbxDatabaseLoader.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.IO;
|
||||
using KeePassLib;
|
||||
using KeePassLib.Interfaces;
|
||||
using KeePassLib.Keys;
|
||||
using KeePassLib.Serialization;
|
||||
|
||||
namespace keepass2android
|
||||
{
|
||||
public class KdbxDatabaseLoader : IDatabaseLoader
|
||||
{
|
||||
private readonly KdbxFormat _format;
|
||||
|
||||
public KdbxDatabaseLoader(KdbxFormat format)
|
||||
{
|
||||
_format = format;
|
||||
}
|
||||
|
||||
public void PopulateDatabaseFromStream(PwDatabase db, CompositeKey key, Stream s, IStatusLogger slLogger)
|
||||
{
|
||||
KdbxFile kdbx = new KdbxFile(db);
|
||||
kdbx.DetachBinaries = db.DetachBinaries;
|
||||
|
||||
kdbx.Load(s, _format, slLogger);
|
||||
HashOfLastStream = kdbx.HashOfFileOnDisk;
|
||||
s.Close();
|
||||
|
||||
}
|
||||
|
||||
public byte[] HashOfLastStream { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Android.App;
|
||||
using KeePassLib;
|
||||
using KeePassLib.Keys;
|
||||
using KeePassLib.Serialization;
|
||||
|
||||
@@ -50,10 +52,24 @@ namespace keepass2android
|
||||
try
|
||||
{
|
||||
StatusLogger.UpdateMessage(UiStringKey.loading_database);
|
||||
MemoryStream memoryStream = _databaseData == null ? null : _databaseData.Result;
|
||||
_app.LoadDatabase(_ioc, memoryStream, _compositeKey, StatusLogger);
|
||||
SaveFileData(_ioc, _keyfileOrProvider);
|
||||
//get the stream data into a single stream variable (databaseStream) regardless whether its preloaded or not:
|
||||
MemoryStream preloadedMemoryStream = _databaseData == null ? null : _databaseData.Result;
|
||||
MemoryStream databaseStream;
|
||||
if (preloadedMemoryStream != null)
|
||||
databaseStream = preloadedMemoryStream;
|
||||
else
|
||||
{
|
||||
using (Stream s = _app.GetFileStorage(_ioc).OpenFileForRead(_ioc))
|
||||
{
|
||||
databaseStream = new MemoryStream();
|
||||
s.CopyTo(databaseStream);
|
||||
databaseStream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
}
|
||||
|
||||
//ok, try to load the database. Let's start with Kdbx format and retry later if that is the wrong guess:
|
||||
IDatabaseLoader loader = new KdbxDatabaseLoader(KdbpFile.GetFormatToUse(_ioc));
|
||||
TryLoad(databaseStream, loader);
|
||||
}
|
||||
catch (KeyFileException)
|
||||
{
|
||||
@@ -73,11 +89,6 @@ namespace keepass2android
|
||||
Finish(false, _app.GetResourceString(UiStringKey.ErrorOcurred) + " " + message);
|
||||
return;
|
||||
}
|
||||
catch (OldFormatException )
|
||||
{
|
||||
Finish(false, "Cannot open Keepass 1.x database. As explained in the app description, Keepass2Android is for Keepass 2 only! Please use the desktop application to convert your database to the new file format!");
|
||||
return;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Kp2aLog.Log("Exception: " + e);
|
||||
@@ -85,10 +96,48 @@ namespace keepass2android
|
||||
return;
|
||||
}
|
||||
|
||||
Kp2aLog.Log("LoadDB OK");
|
||||
Finish(true);
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void TryLoad(MemoryStream databaseStream, IDatabaseLoader loader)
|
||||
{
|
||||
//create a copy of the stream so we can try again if we get an exception which indicates we should change parameters
|
||||
//This is not optimal in terms of (short-time) memory usage but is hard to avoid because the Keepass library closes streams also in case of errors.
|
||||
//Alternatives would involve increased traffic (if file is on remote) and slower loading times, so this seems to be the best choice.
|
||||
MemoryStream workingCopy = new MemoryStream();
|
||||
databaseStream.CopyTo(workingCopy);
|
||||
workingCopy.Seek(0, SeekOrigin.Begin);
|
||||
//reset stream if we need to reuse it later:
|
||||
databaseStream.Seek(0, SeekOrigin.Begin);
|
||||
//now let's go:
|
||||
try
|
||||
{
|
||||
_app.LoadDatabase(_ioc, workingCopy, _compositeKey, StatusLogger, loader);
|
||||
SaveFileData(_ioc, _keyfileOrProvider);
|
||||
Kp2aLog.Log("LoadDB OK");
|
||||
Finish(true);
|
||||
}
|
||||
catch (OldFormatException)
|
||||
{
|
||||
TryLoad(databaseStream, new KdbDatabaseLoader(Application.Context));
|
||||
}
|
||||
catch (InvalidCompositeKeyException)
|
||||
{
|
||||
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:
|
||||
_compositeKey.RemoveUserKey(passwordKey);
|
||||
//retry:
|
||||
TryLoad(databaseStream, loader);
|
||||
}
|
||||
else throw;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void SaveFileData(IOConnectionInfo ioc, String keyfileOrProvider) {
|
||||
|
||||
if (!_rememberKeyfile)
|
||||
|
||||
Reference in New Issue
Block a user