diff --git a/src/KeePassLib2Android/IDatabaseLoader.cs b/src/KeePassLib2Android/IDatabaseFormat.cs
similarity index 77%
rename from src/KeePassLib2Android/IDatabaseLoader.cs
rename to src/KeePassLib2Android/IDatabaseFormat.cs
index ee879f98..5a594c83 100644
--- a/src/KeePassLib2Android/IDatabaseLoader.cs
+++ b/src/KeePassLib2Android/IDatabaseFormat.cs
@@ -4,7 +4,7 @@ using KeePassLib.Keys;
namespace KeePassLib
{
- public interface IDatabaseLoader
+ public interface IDatabaseFormat
{
void PopulateDatabaseFromStream(PwDatabase db, CompositeKey key, Stream s, IStatusLogger slLogger);
@@ -12,5 +12,6 @@ namespace KeePassLib
bool CanWrite { get; }
string SuccessMessage { get; }
+ void Save(PwDatabase kpDatabase, Stream stream);
}
}
\ No newline at end of file
diff --git a/src/KeePassLib2Android/KeePassLib2Android.csproj b/src/KeePassLib2Android/KeePassLib2Android.csproj
index e67d9b2a..e952a8ed 100644
--- a/src/KeePassLib2Android/KeePassLib2Android.csproj
+++ b/src/KeePassLib2Android/KeePassLib2Android.csproj
@@ -12,6 +12,8 @@
Resources\Resource.designer.cs
Resource
KeePassLib2Android
+
+ v4.0
True
@@ -54,7 +56,7 @@
-
+
diff --git a/src/KeePassLib2Android/Keys/CompositeKey.cs b/src/KeePassLib2Android/Keys/CompositeKey.cs
index 44188189..0b31eb3d 100644
--- a/src/KeePassLib2Android/Keys/CompositeKey.cs
+++ b/src/KeePassLib2Android/Keys/CompositeKey.cs
@@ -151,6 +151,11 @@ namespace KeePassLib.Keys
return null;
}
+
+ public T GetUserKey() where T : IUserKey
+ {
+ return (T) GetUserKey(typeof (T));
+ }
#endif
///
diff --git a/src/KeePassLib2Android/PwDatabase.cs b/src/KeePassLib2Android/PwDatabase.cs
index 876d44d4..4bfe8628 100644
--- a/src/KeePassLib2Android/PwDatabase.cs
+++ b/src/KeePassLib2Android/PwDatabase.cs
@@ -564,12 +564,12 @@ namespace KeePassLib
/// IO connection to load the database from.
/// sKey used to open the specified database.
/// Logger, which gets all status messages.
- ///
+ ///
public void Open(IOConnectionInfo ioSource, CompositeKey pwKey,
- IStatusLogger slLogger, IDatabaseLoader loader)
+ IStatusLogger slLogger, IDatabaseFormat format)
{
Open(IOConnection.OpenRead(ioSource), UrlUtil.StripExtension(
- UrlUtil.GetFileName(ioSource.Path)), ioSource, pwKey, slLogger , loader);
+ UrlUtil.GetFileName(ioSource.Path)), ioSource, pwKey, slLogger , format);
}
@@ -577,7 +577,7 @@ namespace KeePassLib
/// Open a database provided as Stream.
///
public void Open(Stream s, string fileNameWithoutPathAndExt, IOConnectionInfo ioSource, CompositeKey pwKey,
- IStatusLogger slLogger, IDatabaseLoader loader)
+ IStatusLogger slLogger, IDatabaseFormat format)
{
Debug.Assert(s != null);
if (s == null) throw new ArgumentNullException("s");
@@ -600,10 +600,10 @@ namespace KeePassLib
m_bModified = false;
- loader.PopulateDatabaseFromStream(this, pwKey, s, slLogger);
+ format.PopulateDatabaseFromStream(this, pwKey, s, slLogger);
- m_pbHashOfLastIO = loader.HashOfLastStream;
- m_pbHashOfFileOnDisk = loader.HashOfLastStream;
+ m_pbHashOfLastIO = format.HashOfLastStream;
+ m_pbHashOfFileOnDisk = format.HashOfLastStream;
Debug.Assert(m_pbHashOfFileOnDisk != null);
m_bDatabaseOpened = true;
diff --git a/src/Kp2aBusinessLogic/IKp2aApp.cs b/src/Kp2aBusinessLogic/IKp2aApp.cs
index 1b37cde4..60a4a46f 100644
--- a/src/Kp2aBusinessLogic/IKp2aApp.cs
+++ b/src/Kp2aBusinessLogic/IKp2aApp.cs
@@ -27,7 +27,7 @@ namespace keepass2android
/// Loads the specified data as the currently open database, as unlocked.
///
void LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, CompositeKey compKey,
- ProgressDialogStatusLogger statusLogger, IDatabaseLoader databaseLoader);
+ ProgressDialogStatusLogger statusLogger, IDatabaseFormat databaseFormat);
///
/// Returns the current database
diff --git a/src/Kp2aBusinessLogic/Kp2aBusinessLogic.csproj b/src/Kp2aBusinessLogic/Kp2aBusinessLogic.csproj
index 4b2ebfca..91f53beb 100644
--- a/src/Kp2aBusinessLogic/Kp2aBusinessLogic.csproj
+++ b/src/Kp2aBusinessLogic/Kp2aBusinessLogic.csproj
@@ -58,8 +58,8 @@
-
-
+
+
diff --git a/src/Kp2aBusinessLogic/database/Database.cs b/src/Kp2aBusinessLogic/database/Database.cs
index 6b2f0302..a6baec77 100644
--- a/src/Kp2aBusinessLogic/database/Database.cs
+++ b/src/Kp2aBusinessLogic/database/Database.cs
@@ -15,8 +15,10 @@ This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file
along with Keepass2Android. If not, see .
*/
+using System;
using System.Collections.Generic;
using System.IO;
+using System.Runtime.Serialization;
using Android.Content;
using Java.Lang;
using KeePassLib;
@@ -73,8 +75,9 @@ namespace keepass2android
private bool _loaded;
private bool _reloadRequested;
+ private IDatabaseFormat _databaseFormat;
- public bool ReloadRequested
+ public bool ReloadRequested
{
get { return _reloadRequested; }
set { _reloadRequested = value; }
@@ -99,14 +102,14 @@ namespace keepass2android
///
/// Do not call this method directly. Call App.Kp2a.LoadDatabase instead.
///
- public void LoadData(IKp2aApp app, IOConnectionInfo iocInfo, MemoryStream databaseData, CompositeKey compositeKey, ProgressDialogStatusLogger status, IDatabaseLoader databaseLoader)
+ public void LoadData(IKp2aApp app, IOConnectionInfo iocInfo, MemoryStream databaseData, CompositeKey compositeKey, ProgressDialogStatusLogger status, IDatabaseFormat databaseFormat)
{
PwDatabase pwDatabase = new PwDatabase();
IFileStorage fileStorage = _app.GetFileStorage(iocInfo);
Stream s = databaseData ?? fileStorage.OpenFileForRead(iocInfo);
var fileVersion = _app.GetFileStorage(iocInfo).GetCurrentFileVersionFast(iocInfo);
- PopulateDatabaseFromStream(pwDatabase, s, iocInfo, compositeKey, status, databaseLoader);
+ PopulateDatabaseFromStream(pwDatabase, s, iocInfo, compositeKey, status, databaseFormat);
LastFileVersion = fileVersion;
status.UpdateSubMessage("");
@@ -119,7 +122,9 @@ namespace keepass2android
KpDatabase = pwDatabase;
SearchHelper = new SearchDbHelper(app);
- CanWrite = databaseLoader.CanWrite && !fileStorage.IsReadOnly(iocInfo);
+ _databaseFormat = databaseFormat;
+
+ CanWrite = databaseFormat.CanWrite && !fileStorage.IsReadOnly(iocInfo);
}
///
@@ -127,11 +132,11 @@ namespace keepass2android
///
public bool CanWrite { get; set; }
- protected virtual void PopulateDatabaseFromStream(PwDatabase pwDatabase, Stream s, IOConnectionInfo iocInfo, CompositeKey compositeKey, ProgressDialogStatusLogger status, IDatabaseLoader databaseLoader)
+ protected virtual void PopulateDatabaseFromStream(PwDatabase pwDatabase, Stream s, IOConnectionInfo iocInfo, CompositeKey compositeKey, ProgressDialogStatusLogger status, IDatabaseFormat databaseFormat)
{
IFileStorage fileStorage = _app.GetFileStorage(iocInfo);
var filename = fileStorage.GetFilenameWithoutPathAndExt(iocInfo);
- pwDatabase.Open(s, filename, iocInfo, compositeKey, status, databaseLoader);
+ pwDatabase.Open(s, filename, iocInfo, compositeKey, status, databaseFormat);
}
@@ -163,12 +168,13 @@ namespace keepass2android
}
- public virtual void SaveData(Context ctx) {
+ public void SaveData(Context ctx) {
KpDatabase.UseFileTransactions = _app.GetBooleanPreference(PreferenceKey.UseFileTransactions);
using (IWriteTransaction trans = _app.GetFileStorage(Ioc).OpenWriteTransaction(Ioc, KpDatabase.UseFileTransactions))
{
- KpDatabase.Save(trans.OpenFile(), null);
+ _databaseFormat.Save(KpDatabase, trans.OpenFile());
+
trans.CommitWrite();
}
@@ -186,7 +192,7 @@ namespace keepass2android
{
if (Entries.ContainsKey(e.Uuid))
{
- throw new DuplicateUuidsException();
+ throw new DuplicateUuidsException("Same UUID for entries '"+Entries[e.Uuid].Strings.ReadSafe(PwDefs.TitleField)+"' and '"+e.Strings.ReadSafe(PwDefs.TitleField)+"'.");
}
}
@@ -196,11 +202,12 @@ namespace keepass2android
{
if (checkForDuplicateUuids)
{
-
+ /*Disable check. Annoying for users, no problem for KP2A.
if (Groups.ContainsKey(g.Uuid))
{
throw new DuplicateUuidsException();
}
+ * */
}
Groups[g.Uuid] = g;
PopulateGlobals(g);
@@ -236,8 +243,22 @@ namespace keepass2android
}
- internal class DuplicateUuidsException : Exception
+ [Serializable]
+ public class DuplicateUuidsException : Exception
{
+ public DuplicateUuidsException()
+ {
+ }
+
+ public DuplicateUuidsException(string message) : base(message)
+ {
+ }
+
+ protected DuplicateUuidsException(
+ SerializationInfo info,
+ StreamingContext context) : base(info, context)
+ {
+ }
}
}
diff --git a/src/Kp2aBusinessLogic/database/KdbDatabaseFormat.cs b/src/Kp2aBusinessLogic/database/KdbDatabaseFormat.cs
new file mode 100644
index 00000000..f17d6905
--- /dev/null
+++ b/src/Kp2aBusinessLogic/database/KdbDatabaseFormat.cs
@@ -0,0 +1,364 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography;
+#if !EXCLUDE_KEYTRANSFORM
+using Android.App;
+using Com.Keepassdroid.Database;
+using Com.Keepassdroid.Database.Exception;
+#endif
+using Com.Keepassdroid.Database.Save;
+using Java.Util;
+using KeePassLib;
+using KeePassLib.Cryptography;
+using KeePassLib.Cryptography.Cipher;
+using KeePassLib.Interfaces;
+using KeePassLib.Keys;
+using KeePassLib.Security;
+using Exception = System.Exception;
+using PwIcon = KeePassLib.PwIcon;
+using Random = System.Random;
+
+namespace keepass2android
+{
+ class KdbDatabaseFormat: IDatabaseFormat
+ {
+ private Dictionary _groupData = new Dictionary();
+ private static readonly DateTime _expireNever = new DateTime(2999,12,28,23,59,59);
+
+ public void PopulateDatabaseFromStream(PwDatabase db, CompositeKey key, Stream s, IStatusLogger slLogger)
+ {
+ #if !EXCLUDE_KEYTRANSFORM
+ 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));
+ MemoryStream keyfileStream = null;
+ if (passwordKeyfile != null)
+ {
+ keyfileStream = new MemoryStream(passwordKeyfile.RawFileData.ReadData());
+ }
+
+
+ try
+ {
+ var dbv3 = importer.OpenDatabase(hashingStream, password, keyfileStream);
+
+ db.Name = dbv3.Name;
+ db.RootGroup = ConvertGroup(dbv3.RootGroup);
+ if (dbv3.Algorithm == PwEncryptionAlgorithm.Rjindal)
+ {
+ db.DataCipherUuid = StandardAesEngine.AesUuid;
+ }
+ else
+ {
+ db.DataCipherUuid = new PwUuid(TwofishCipher.TwofishCipherEngine.TwofishCipherUuidBytes);
+ }
+
+
+ }
+ 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
+#else
+ throw new Exception("Kdb is excluded with Key transform!");
+#endif
+ }
+
+ #if !EXCLUDE_KEYTRANSFORM
+
+ private PwGroup ConvertGroup(PwGroupV3 groupV3)
+ {
+ PwGroup pwGroup = new PwGroup(true, false);
+ pwGroup.Name = groupV3.Name;
+
+ pwGroup.CreationTime = ConvertTime(groupV3.TCreation);
+ pwGroup.LastAccessTime = ConvertTime(groupV3.TLastAccess);
+ pwGroup.LastModificationTime = ConvertTime(groupV3.TLastMod);
+ pwGroup.Expires = !PwGroupV3.NeverExpire.Equals(groupV3.TExpire);
+ if (pwGroup.Expires)
+ pwGroup.ExpiryTime = ConvertTime(groupV3.TExpire);
+
+ if (groupV3.Icon != null)
+ pwGroup.IconId = (PwIcon) groupV3.Icon.IconId;
+ _groupData.Add(pwGroup.Uuid, new AdditionalGroupData
+ {
+ Flags = groupV3.Flags,
+ Id = groupV3.Id.Id
+ });
+
+
+ for (int i = 0; i < groupV3.ChildGroups.Count;i++)
+ {
+ pwGroup.AddGroup(ConvertGroup(groupV3.GetGroupAt(i)), true);
+ }
+ for (int i = 0; i < groupV3.ChildEntries.Count; i++)
+ {
+ var entry = groupV3.GetEntryAt(i);
+ if (entry.IsMetaStream)
+ continue;
+ pwGroup.AddEntry(ConvertEntry(entry), true);
+ }
+
+ return pwGroup;
+ }
+
+ private PwEntry ConvertEntry(PwEntryV3 fromEntry)
+ {
+ PwEntry toEntry = new PwEntry(false, false);
+ toEntry.Uuid = new PwUuid(fromEntry.Uuid.ToArray());
+ String modTime = Android.Text.Format.DateFormat.GetTimeFormat(Application.Context).Format(fromEntry.TCreation.JDate);
+ Android.Util.Log.Debug("KP2A", modTime);
+ toEntry.CreationTime = ConvertTime(fromEntry.TCreation);
+ toEntry.LastAccessTime = ConvertTime(fromEntry.TLastAccess);
+ toEntry.LastModificationTime = ConvertTime(fromEntry.TLastMod);
+
+ toEntry.ExpiryTime = ConvertTime(fromEntry.TExpire);
+
+ toEntry.ExpiryTime = new DateTime(toEntry.ExpiryTime.Year, toEntry.ExpiryTime.Month, toEntry.ExpiryTime.Day, toEntry.ExpiryTime.Hour, toEntry.ExpiryTime.Minute, toEntry.ExpiryTime.Second);
+ toEntry.Expires = !(Math.Abs((toEntry.ExpiryTime - _expireNever).TotalMilliseconds) < 500);
+
+ if (fromEntry.Icon != null)
+ toEntry.IconId = (PwIcon) fromEntry.Icon.IconId;
+ SetFieldIfAvailable(toEntry, PwDefs.TitleField, false, fromEntry.Title);
+ SetFieldIfAvailable(toEntry, PwDefs.UserNameField, false, fromEntry.Username);
+ SetFieldIfAvailable(toEntry, PwDefs.UrlField, false, fromEntry.Url);
+ SetFieldIfAvailable(toEntry, PwDefs.PasswordField, true, fromEntry.Password);
+ SetFieldIfAvailable(toEntry, PwDefs.NotesField, true, fromEntry.Additional);
+
+ if ((fromEntry.GetBinaryData() != null) && (fromEntry.GetBinaryData().Length > 0))
+ {
+ toEntry.Binaries.Set(fromEntry.BinaryDesc, new ProtectedBinary(true, fromEntry.GetBinaryData()));
+ }
+ return toEntry;
+ }
+
+ private void SetFieldIfAvailable(PwEntry pwEntry, string fieldName, bool makeProtected, string value)
+ {
+ if (value != null)
+ {
+ pwEntry.Strings.Set(fieldName, new ProtectedString(makeProtected, value));
+ }
+
+ }
+
+ private DateTime ConvertTime(PwDate date)
+ {
+ if (date == null)
+ return PwDefs.DtDefaultNow;
+
+ return JavaTimeToCSharp(date.JDate.Time);
+ }
+
+ private DateTime JavaTimeToCSharp(long javatime)
+ {
+
+ var utcTime = new DateTime(1970, 1, 1).AddMilliseconds(javatime);
+ return TimeZoneInfo.ConvertTimeFromUtc(utcTime, TimeZoneInfo.Local);
+
+ }
+#endif
+ public byte[] HashOfLastStream { get; private set; }
+ public bool CanWrite { get { return true; } }
+ public string SuccessMessage { get
+ {
+ return "";
+ } }
+
+ public void Save(PwDatabase kpDatabase, Stream stream)
+ {
+ PwDatabaseV3 db =new PwDatabaseV3();
+ KcpPassword pwd = kpDatabase.MasterKey.GetUserKey();
+ string password = pwd != null ? pwd.Password.ReadString() : "";
+ KcpKeyFile keyfile = kpDatabase.MasterKey.GetUserKey();
+ Stream keyfileContents = null;
+ if (keyfile != null)
+ {
+ keyfileContents = new MemoryStream(keyfile.RawFileData.ReadData());
+ }
+ db.SetMasterKey(password, keyfileContents);
+ db.NumRounds = (long) kpDatabase.KeyEncryptionRounds;
+ db.Name = kpDatabase.Name;
+ if (kpDatabase.DataCipherUuid.Equals(StandardAesEngine.AesUuid))
+ {
+ db.Algorithm = PwEncryptionAlgorithm.Rjindal;
+ }
+ else
+ {
+ db.Algorithm = PwEncryptionAlgorithm.Twofish;
+ }
+
+ //create groups
+ db.Groups.Clear();
+ var fromGroups = kpDatabase.RootGroup.GetGroups(true);
+ Dictionary groupV3s = new Dictionary(fromGroups.Count());
+ foreach (PwGroup g in fromGroups)
+ {
+ if (g == kpDatabase.RootGroup)
+ continue;
+ PwGroupV3 groupV3 = ConvertGroup(g, db);
+ db.Groups.Add(groupV3);
+ groupV3s[groupV3.Id.Id] = groupV3;
+ }
+
+ //traverse again and assign parents
+ db.RootGroup = new PwGroupV3() { Level = -1};
+ AssignParent(kpDatabase.RootGroup, db, groupV3s);
+
+
+
+ foreach (PwEntry e in kpDatabase.RootGroup.GetEntries(true))
+ {
+ PwEntryV3 entryV3 = ConvertEntry(e, db);
+ entryV3.Parent = groupV3s[_groupData[e.ParentGroup.Uuid].Id];
+ entryV3.Parent.ChildEntries.Add(entryV3);
+ entryV3.GroupId = entryV3.Parent.Id.Id;
+ db.Entries.Add(entryV3);
+ }
+
+
+ PwDbV3Output output = new PwDbV3Output(db, stream);
+ output.Output();
+ }
+
+ private void AssignParent(PwGroup kpParent, PwDatabaseV3 dbV3, Dictionary groupV3s)
+ {
+ PwGroupV3 parentV3;
+ if (kpParent.ParentGroup == null)
+ {
+ parentV3 = dbV3.RootGroup;
+ }
+ else
+ {
+ parentV3 = groupV3s[_groupData[kpParent.Uuid].Id];
+ }
+
+ foreach (PwGroup g in kpParent.Groups)
+ {
+ PwGroupV3 groupV3 = groupV3s[_groupData[g.Uuid].Id];
+
+ parentV3.ChildGroups.Add(groupV3);
+ groupV3.Parent = parentV3;
+
+ AssignParent(g, dbV3, groupV3s);
+ }
+ }
+
+ private PwGroupV3 ConvertGroup(PwGroup fromGroup, PwDatabaseV3 dbTo)
+ {
+ PwGroupV3 toGroup = new PwGroupV3();
+ toGroup.Name = fromGroup.Name;
+
+ toGroup.TCreation = new PwDate(ConvertTime(fromGroup.CreationTime));
+ toGroup.TLastAccess= new PwDate(ConvertTime(fromGroup.LastAccessTime));
+ toGroup.TLastMod = new PwDate(ConvertTime(fromGroup.LastModificationTime));
+ if (fromGroup.Expires)
+ {
+ toGroup.TExpire = new PwDate(ConvertTime(fromGroup.ExpiryTime));
+ }
+ else
+ {
+ toGroup.TExpire = new PwDate(PwGroupV3.NeverExpire);
+ }
+
+ toGroup.Icon = dbTo.IconFactory.GetIcon((int) fromGroup.IconId);
+ AdditionalGroupData groupData;
+ if (_groupData.TryGetValue(fromGroup.Uuid, out groupData))
+ {
+ toGroup.Id = new PwGroupIdV3(groupData.Id);
+ toGroup.Flags = groupData.Flags;
+ }
+ else
+ {
+ //group was added
+ do
+ {
+ toGroup.Id = new PwGroupIdV3(new Random().Next());
+ } while (_groupData.Values.Any(gd => gd.Id == toGroup.Id.Id)); //retry if id already exists
+ //store to block new id and reuse when saving again (without loading in between)
+ _groupData[fromGroup.Uuid] = new AdditionalGroupData
+ {
+ Id = toGroup.Id.Id
+ };
+
+ }
+
+ return toGroup;
+ }
+
+ private PwEntryV3 ConvertEntry(PwEntry fromEntry, PwDatabaseV3 dbTo)
+ {
+ PwEntryV3 toEntry = new PwEntryV3();
+ toEntry.Uuid = fromEntry.Uuid.UuidBytes;
+ toEntry.CreationTime = ConvertTime(fromEntry.CreationTime);
+ toEntry.LastAccessTime = ConvertTime(fromEntry.LastAccessTime);
+ toEntry.LastModificationTime = ConvertTime(fromEntry.LastModificationTime);
+
+ if (fromEntry.Expires)
+ {
+ toEntry.ExpiryTime = ConvertTime(fromEntry.ExpiryTime);
+ }
+ else
+ {
+ toEntry.ExpiryTime = ConvertTime(_expireNever);
+ }
+
+
+ toEntry.Icon = dbTo.IconFactory.GetIcon((int) fromEntry.IconId);
+ toEntry.SetTitle(GetString(fromEntry, PwDefs.TitleField), dbTo);
+ toEntry.SetUsername(GetString(fromEntry, PwDefs.UserNameField), dbTo);
+ toEntry.SetUrl(GetString(fromEntry, PwDefs.UrlField), dbTo);
+ toEntry.SetPassword(GetString(fromEntry, PwDefs.PasswordField), dbTo);
+ toEntry.SetNotes(GetString(fromEntry, PwDefs.NotesField), dbTo);
+ if (fromEntry.Binaries.Any())
+ {
+ var binaryData = fromEntry.Binaries.First().Value.ReadData();
+ toEntry.SetBinaryData(binaryData, 0, binaryData.Length);
+ }
+
+ return toEntry;
+ }
+
+ private string GetString(PwEntry fromEntry, string id)
+ {
+ ProtectedString protectedString = fromEntry.Strings.Get(id);
+ if (protectedString == null)
+ return null;
+ return protectedString.ReadString();
+ }
+
+ private Date ConvertTime(DateTime dateTime)
+ {
+ long timestamp = (long)(dateTime.ToUniversalTime().Subtract(new DateTime(1970, 1, 1))).TotalMilliseconds;
+ return new Date(timestamp);
+ }
+ }
+
+
+ internal class AdditionalGroupData
+ {
+ public int Id { get; set; }
+ public int Flags { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Kp2aBusinessLogic/database/KdbDatabaseLoader.cs b/src/Kp2aBusinessLogic/database/KdbDatabaseLoader.cs
deleted file mode 100644
index 15831a9a..00000000
--- a/src/Kp2aBusinessLogic/database/KdbDatabaseLoader.cs
+++ /dev/null
@@ -1,176 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Security.Cryptography;
-#if !EXCLUDE_KEYTRANSFORM
-using Com.Keepassdroid.Database;
-using Com.Keepassdroid.Database.Exception;
-#endif
-using KeePassLib;
-using KeePassLib.Cryptography;
-using KeePassLib.Interfaces;
-using KeePassLib.Keys;
-using KeePassLib.Security;
-using Exception = System.Exception;
-using PwIcon = KeePassLib.PwIcon;
-
-namespace keepass2android
-{
- class KdbDatabaseLoader: IDatabaseLoader
- {
- private Dictionary _groupData = new Dictionary();
-
- public void PopulateDatabaseFromStream(PwDatabase db, CompositeKey key, Stream s, IStatusLogger slLogger)
- {
- #if !EXCLUDE_KEYTRANSFORM
- 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));
- MemoryStream keyfileStream = null;
- if (passwordKeyfile != null)
- {
- keyfileStream = new MemoryStream(passwordKeyfile.RawFileData.ReadData());
- }
-
-
- try
- {
- var dbv3 = importer.OpenDatabase(hashingStream, password, keyfileStream);
-
- db.Name = dbv3.Name;
- db.RootGroup = ConvertGroup(dbv3.RootGroup);
-
- }
- 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
-#else
- throw new Exception("Kdb is excluded with Key transform!");
-#endif
- }
-
- #if !EXCLUDE_KEYTRANSFORM
-
- private PwGroup ConvertGroup(PwGroupV3 groupV3)
- {
- PwGroup pwGroup = new PwGroup(true, false);
- pwGroup.Name = groupV3.Name;
-
- pwGroup.CreationTime = ConvertTime(groupV3.TCreation);
- pwGroup.LastAccessTime = ConvertTime(groupV3.TLastAccess);
- pwGroup.LastModificationTime = ConvertTime(groupV3.TLastMod);
- pwGroup.Expires = !PwGroupV3.NeverExpire.Equals(groupV3.TExpire);
- if (pwGroup.Expires)
- pwGroup.ExpiryTime = ConvertTime(groupV3.TExpire);
-
- if (groupV3.Icon != null)
- pwGroup.IconId = (PwIcon) groupV3.Icon.IconId;
- _groupData.Add(pwGroup.Uuid, new AdditionalGroupData
- {
- Flags = groupV3.Flags,
- Id = groupV3.Id.Id
- });
-
-
- for (int i = 0; i < groupV3.ChildGroups.Count;i++)
- {
- pwGroup.AddGroup(ConvertGroup(groupV3.GetGroupAt(i)), true);
- }
- for (int i = 0; i < groupV3.ChildEntries.Count; i++)
- {
- var entry = groupV3.GetEntryAt(i);
- if (entry.IsMetaStream)
- continue;
- pwGroup.AddEntry(ConvertEntry(entry), true);
- }
-
- return pwGroup;
- }
-
- private PwEntry ConvertEntry(PwEntryV3 entryV3)
- {
- PwEntry pwEntry = new PwEntry(false, false);
- pwEntry.Uuid = new PwUuid(entryV3.Uuid.ToArray());
- pwEntry.CreationTime = ConvertTime(entryV3.TCreation);
- pwEntry.LastAccessTime = ConvertTime(entryV3.TLastAccess);
- pwEntry.LastModificationTime = ConvertTime(entryV3.TLastMod);
-
- pwEntry.Expires = entryV3.Expires();
- if (pwEntry.Expires)
- pwEntry.ExpiryTime = ConvertTime(entryV3.TExpire);
-
- if (entryV3.Icon != null)
- pwEntry.IconId = (PwIcon) entryV3.Icon.IconId;
- SetFieldIfAvailable(pwEntry, PwDefs.TitleField, false, entryV3.Title);
- SetFieldIfAvailable(pwEntry, PwDefs.UserNameField, false, entryV3.Username);
- SetFieldIfAvailable(pwEntry, PwDefs.UrlField, false, entryV3.Url);
- SetFieldIfAvailable(pwEntry, PwDefs.PasswordField, true, entryV3.Password);
- SetFieldIfAvailable(pwEntry, PwDefs.NotesField, true, entryV3.Additional);
-
- if ((entryV3.GetBinaryData() != null) && (entryV3.GetBinaryData().Length > 0))
- {
- pwEntry.Binaries.Set(entryV3.BinaryDesc, new ProtectedBinary(true, entryV3.GetBinaryData()));
- }
- return pwEntry;
- }
-
- private void SetFieldIfAvailable(PwEntry pwEntry, string fieldName, bool makeProtected, string value)
- {
- if (value != null)
- {
- pwEntry.Strings.Set(fieldName, new ProtectedString(makeProtected, value));
- }
-
- }
-
- private DateTime ConvertTime(PwDate date)
- {
- if (date == null)
- return PwDefs.DtDefaultNow;
- return JavaTimeToCSharp(date.JDate.Time);
- }
-
- private DateTime JavaTimeToCSharp(long javatime)
- {
- return new DateTime(1970, 1, 1).AddMilliseconds(javatime);
-
- }
-#endif
- public byte[] HashOfLastStream { get; private set; }
- public bool CanWrite { get { return false; } }
- public string SuccessMessage { get
- {
- return
- ".kdb-support is read-only. Export as .kdbx if you want to modify the database. This app is for use with Keepass 2.x!";
- } }
- }
-
-
- internal class AdditionalGroupData
- {
- public int Id { get; set; }
- public int Flags { get; set; }
- }
-}
\ No newline at end of file
diff --git a/src/Kp2aBusinessLogic/database/KdbxDatabaseLoader.cs b/src/Kp2aBusinessLogic/database/KdbxDatabaseFormat.cs
similarity index 77%
rename from src/Kp2aBusinessLogic/database/KdbxDatabaseLoader.cs
rename to src/Kp2aBusinessLogic/database/KdbxDatabaseFormat.cs
index 33bb312b..8db4e88c 100644
--- a/src/Kp2aBusinessLogic/database/KdbxDatabaseLoader.cs
+++ b/src/Kp2aBusinessLogic/database/KdbxDatabaseFormat.cs
@@ -6,11 +6,11 @@ using KeePassLib.Serialization;
namespace keepass2android
{
- public class KdbxDatabaseLoader : IDatabaseLoader
+ public class KdbxDatabaseFormat : IDatabaseFormat
{
private readonly KdbxFormat _format;
- public KdbxDatabaseLoader(KdbxFormat format)
+ public KdbxDatabaseFormat(KdbxFormat format)
{
_format = format;
}
@@ -29,5 +29,9 @@ namespace keepass2android
public byte[] HashOfLastStream { get; private set; }
public bool CanWrite { get { return true; } }
public string SuccessMessage { get { return null; } }
+ public void Save(PwDatabase kpDatabase, Stream stream)
+ {
+ kpDatabase.Save(stream, null);
+ }
}
}
\ No newline at end of file
diff --git a/src/Kp2aBusinessLogic/database/edit/LoadDB.cs b/src/Kp2aBusinessLogic/database/edit/LoadDB.cs
index 5100c508..8f2dc0be 100644
--- a/src/Kp2aBusinessLogic/database/edit/LoadDB.cs
+++ b/src/Kp2aBusinessLogic/database/edit/LoadDB.cs
@@ -32,7 +32,7 @@ namespace keepass2android
private readonly string _keyfileOrProvider;
private readonly IKp2aApp _app;
private readonly bool _rememberKeyfile;
- IDatabaseLoader _loader;
+ IDatabaseFormat _format;
public LoadDb(IKp2aApp app, IOConnectionInfo ioc, Task databaseData, CompositeKey compositeKey, String keyfileOrProvider, OnFinish finish): base(finish)
{
@@ -68,7 +68,7 @@ namespace keepass2android
}
//ok, try to load the database. Let's start with Kdbx format and retry later if that is the wrong guess:
- _loader = new KdbxDatabaseLoader(KdbpFile.GetFormatToUse(_ioc));
+ _format = new KdbxDatabaseFormat(KdbpFile.GetFormatToUse(_ioc));
TryLoad(databaseStream);
}
catch (KeyFileException)
@@ -92,7 +92,7 @@ namespace keepass2android
catch (DuplicateUuidsException e)
{
Kp2aLog.Log("Exception: " + e);
- Finish(false, _app.GetResourceString(UiStringKey.DuplicateUuidsError)+" " + _app.GetResourceString(UiStringKey.DuplicateUuidsErrorAdditional));
+ Finish(false, _app.GetResourceString(UiStringKey.DuplicateUuidsError)+" " +e.Message+ _app.GetResourceString(UiStringKey.DuplicateUuidsErrorAdditional));
return;
}
catch (Exception e)
@@ -118,14 +118,14 @@ namespace keepass2android
//now let's go:
try
{
- _app.LoadDatabase(_ioc, workingCopy, _compositeKey, StatusLogger, _loader);
+ _app.LoadDatabase(_ioc, workingCopy, _compositeKey, StatusLogger, _format);
SaveFileData(_ioc, _keyfileOrProvider);
Kp2aLog.Log("LoadDB OK");
- Finish(true, _loader.SuccessMessage);
+ Finish(true, _format.SuccessMessage);
}
catch (OldFormatException)
{
- _loader = new KdbDatabaseLoader();
+ _format = new KdbDatabaseFormat();
TryLoad(databaseStream);
}
catch (InvalidCompositeKeyException)
diff --git a/src/Kp2aUnitTests/TestKp2aApp.cs b/src/Kp2aUnitTests/TestKp2aApp.cs
index fe20db4b..f6123abf 100644
--- a/src/Kp2aUnitTests/TestKp2aApp.cs
+++ b/src/Kp2aUnitTests/TestKp2aApp.cs
@@ -51,9 +51,9 @@ namespace Kp2aUnitTests
throw new NotImplementedException();
}
- public void LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, CompositeKey compKey, ProgressDialogStatusLogger statusLogger, IDatabaseLoader databaseLoader)
+ public void LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, CompositeKey compKey, ProgressDialogStatusLogger statusLogger, IDatabaseFormat databaseFormat)
{
- _db.LoadData(this, ioConnectionInfo, memoryStream, compKey, statusLogger, databaseLoader);
+ _db.LoadData(this, ioConnectionInfo, memoryStream, compKey, statusLogger, databaseFormat);
}
public Database GetDb()
{
diff --git a/src/TwofishCipher/TwofishCipherEngine.cs b/src/TwofishCipher/TwofishCipherEngine.cs
index d3af677f..dcd862e4 100644
--- a/src/TwofishCipher/TwofishCipherEngine.cs
+++ b/src/TwofishCipher/TwofishCipherEngine.cs
@@ -39,7 +39,7 @@ namespace TwofishCipher
private PwUuid m_uuidCipher;
- private static readonly byte[] TwofishCipherUuidBytes = new byte[]{
+ public static readonly byte[] TwofishCipherUuidBytes = new byte[]{
0xAD, 0x68, 0xF2, 0x9F, 0x57, 0x6F, 0x4B, 0xB9,
0xA3, 0x6A, 0xD4, 0x7A, 0xF9, 0x65, 0x34, 0x6C
};
diff --git a/src/java/JavaFileStorage/bin/javafilestorage.jar b/src/java/JavaFileStorage/bin/javafilestorage.jar
index 026fe827..a21a9fc8 100644
Binary files a/src/java/JavaFileStorage/bin/javafilestorage.jar and b/src/java/JavaFileStorage/bin/javafilestorage.jar differ
diff --git a/src/java/Keepass2AndroidPluginSDK/bin/keepass2androidpluginsdk.jar b/src/java/Keepass2AndroidPluginSDK/bin/keepass2androidpluginsdk.jar
index 1d6dc4b0..3ee76d98 100644
Binary files a/src/java/Keepass2AndroidPluginSDK/bin/keepass2androidpluginsdk.jar and b/src/java/Keepass2AndroidPluginSDK/bin/keepass2androidpluginsdk.jar differ
diff --git a/src/keepass2android/app/App.cs b/src/keepass2android/app/App.cs
index 2d4682df..0a5c2af4 100644
--- a/src/keepass2android/app/App.cs
+++ b/src/keepass2android/app/App.cs
@@ -146,9 +146,9 @@ namespace keepass2android
- public void LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, CompositeKey compositeKey, ProgressDialogStatusLogger statusLogger, IDatabaseLoader databaseLoader)
+ public void LoadDatabase(IOConnectionInfo ioConnectionInfo, MemoryStream memoryStream, CompositeKey compositeKey, ProgressDialogStatusLogger statusLogger, IDatabaseFormat databaseFormat)
{
- _db.LoadData(this, ioConnectionInfo, memoryStream, compositeKey, statusLogger, databaseLoader);
+ _db.LoadData(this, ioConnectionInfo, memoryStream, compositeKey, statusLogger, databaseFormat);
UpdateOngoingNotification();
}