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(); }