Introduced IDatabaseLoader

(kdb not yet working)
This commit is contained in:
Philipp Crocoll
2014-01-25 19:38:12 -08:00
parent ee4d40eb32
commit 6e0645559d
19 changed files with 255 additions and 65 deletions

View File

@@ -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))

View 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; }
}
}

View 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; }
}
}

View File

@@ -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)