From d5b78452424583323d03268c62659fdf3175e614 Mon Sep 17 00:00:00 2001 From: Geoffroy Bonneville Date: Fri, 20 Oct 2017 20:02:52 +0200 Subject: [PATCH] WIP Update lib to 2.37 --- ModernKeePass/Common/DatabaseHelper.cs | 1 + .../Controls/OpenDatabaseUserControl.xaml.cs | 2 + ModernKeePass/Package.appxmanifest | 2 +- ModernKeePass/Pages/AboutPage.xaml | 35 +- ModernKeePass/ViewModels/AboutVm.cs | 5 +- .../Collections/AutoTypeConfig.cs | 2 +- .../Collections/ProtectedBinaryDictionary.cs | 2 +- .../Collections/ProtectedBinarySet.cs | 174 ++++ .../Collections/ProtectedStringDictionary.cs | 11 +- ModernKeePassLib/Collections/PwObjectList.cs | 2 +- ModernKeePassLib/Collections/PwObjectPool.cs | 156 ++- .../Collections/StringDictionaryEx.cs | 58 +- .../Collections/VariantDictionary.cs | 415 ++++++++ .../Cryptography/Cipher/ChaCha20Cipher.cs | 254 +++++ .../Cryptography/Cipher/ChaCha20Engine.cs | 177 ++++ .../Cryptography/Cipher/CipherPool.cs | 15 +- .../Cryptography/Cipher/CtrBlockCipher.cs | 109 ++ .../Cryptography/Cipher/ICipherEngine.cs | 23 +- .../Cryptography/Cipher/Salsa20Cipher.cs | 244 ++--- .../Cryptography/Cipher/StandardAesEngine.cs | 112 +-- ModernKeePassLib/Cryptography/CryptoRandom.cs | 350 ++++--- .../Cryptography/CryptoRandomStream.cs | 169 ++-- ModernKeePassLib/Cryptography/CryptoUtil.cs | 191 ++++ ModernKeePassLib/Cryptography/Hash/Blake2b.cs | 280 ++++++ .../Cryptography/HashingStreamEx.cs | 91 +- ModernKeePassLib/Cryptography/HmacOtp.cs | 16 +- .../KeyDerivation/AesKdf.GCrypt.cs | 402 ++++++++ .../Cryptography/KeyDerivation/AesKdf.cs | 206 ++++ .../KeyDerivation/Argon2Kdf.Core.cs | 636 ++++++++++++ .../Cryptography/KeyDerivation/Argon2Kdf.cs | 144 +++ .../Cryptography/KeyDerivation/KdfEngine.cs | 142 +++ .../KeyDerivation/KdfParameters.cs | 80 ++ .../Cryptography/KeyDerivation/KdfPool.cs | 96 ++ .../CharSetBasedGenerator.cs | 6 +- .../PasswordGenerator/CustomPwGenerator.cs | 2 +- .../CustomPwGeneratorPool.cs | 2 +- .../PatternBasedGenerator.cs | 4 +- .../PasswordGenerator/PwCharSet.cs | 2 +- .../PasswordGenerator/PwGenerator.cs | 57 +- .../PasswordGenerator/PwProfile.cs | 2 +- .../Cryptography/PopularPasswords.cs | 2 +- .../Cryptography/QualityEstimation.cs | 6 +- ModernKeePassLib/Cryptography/SelfTest.cs | 692 +++++++++++-- ModernKeePassLib/Delegates/Handlers.cs | 2 +- ModernKeePassLib/Interfaces/IDeepCloneable.cs | 2 +- ModernKeePassLib/Interfaces/IStatusLogger.cs | 2 +- ModernKeePassLib/Interfaces/IStructureItem.cs | 2 +- ModernKeePassLib/Interfaces/ITimeLogger.cs | 2 +- ModernKeePassLib/Interfaces/IUIOperations.cs | 2 +- .../Interfaces/IXmlSerializerEx.cs | 2 +- ModernKeePassLib/Keys/CompositeKey.cs | 319 ++---- ModernKeePassLib/Keys/IUserKey.cs | 2 +- ModernKeePassLib/Keys/KcpCustomKey.cs | 17 +- ModernKeePassLib/Keys/KcpKeyFile.cs | 49 +- ModernKeePassLib/Keys/KcpPassword.cs | 38 +- ModernKeePassLib/Keys/KcpUserAccount.cs | 63 +- ModernKeePassLib/Keys/KeyProvider.cs | 2 +- ModernKeePassLib/Keys/KeyProviderPool.cs | 2 +- ModernKeePassLib/Keys/KeyValidator.cs | 2 +- ModernKeePassLib/Keys/KeyValidatorPool.cs | 2 +- ModernKeePassLib/Keys/UserKeyType.cs | 2 +- ModernKeePassLib/ModernKeePassLib.csproj | 21 +- ModernKeePassLib/ModernKeePassLib.nuspec | 6 +- ModernKeePassLib/Properties/AssemblyInfo.cs | 2 +- ModernKeePassLib/PwCustomIcon.cs | 80 +- ModernKeePassLib/PwDatabase.cs | 942 +++++++++++------- ModernKeePassLib/PwDefs.cs | 94 +- ModernKeePassLib/PwDeletedObject.cs | 2 +- ModernKeePassLib/PwEntry.cs | 73 +- ModernKeePassLib/PwEnums.cs | 57 +- ModernKeePassLib/PwGroup.cs | 310 +++--- ModernKeePassLib/PwUuid.cs | 2 +- ModernKeePassLib/Resources/KLRes.Generated.cs | 173 +++- ModernKeePassLib/Security/ProtectedBinary.cs | 88 +- ModernKeePassLib/Security/ProtectedString.cs | 18 +- ModernKeePassLib/Security/XorredBuffer.cs | 2 +- .../Serialization/BinaryReaderEx.cs | 23 +- ModernKeePassLib/Serialization/FileLock.cs | 93 +- .../Serialization/FileTransactionEx.cs | 68 +- .../Serialization/HashedBlockStream.cs | 129 +-- .../Serialization/HmacBlockStream.cs | 356 +++++++ .../Serialization/IOConnection.cs | 317 ++++-- .../Serialization/IOConnectionInfo.cs | 41 +- .../Serialization/IocProperties.cs | 192 ++++ .../Serialization/IocPropertyInfo.cs | 99 ++ .../Serialization/IocPropertyInfoPool.cs | 123 +++ .../Serialization/KdbxFile.Read.Streamed.cs | 229 ++++- .../Serialization/KdbxFile.Read.cs | 408 +++++--- .../Serialization/KdbxFile.Write.cs | 498 ++++++--- ModernKeePassLib/Serialization/KdbxFile.cs | 273 +++-- .../Serialization/OldFormatException.cs | 4 +- .../Translation/KPControlCustomization.cs | 8 +- .../Translation/KPFormCustomization.cs | 3 +- ModernKeePassLib/Translation/KPStringTable.cs | 2 +- .../Translation/KPStringTableItem.cs | 2 +- ModernKeePassLib/Translation/KPTranslation.cs | 107 +- .../Translation/KPTranslationProperties.cs | 2 +- ModernKeePassLib/Utility/AppLogEx.cs | 11 +- ModernKeePassLib/Utility/GfxUtil.cs | 392 +++++++- ModernKeePassLib/Utility/MemUtil.cs | 291 ++++-- ModernKeePassLib/Utility/MessageService.cs | 98 +- ModernKeePassLib/Utility/MonoWorkarounds.cs | 282 +++++- ModernKeePassLib/Utility/StrUtil.cs | 192 +++- ModernKeePassLib/Utility/TimeUtil.cs | 174 +++- ModernKeePassLib/Utility/UrlUtil.cs | 63 +- 105 files changed, 9829 insertions(+), 2410 deletions(-) create mode 100644 ModernKeePassLib/Collections/ProtectedBinarySet.cs create mode 100644 ModernKeePassLib/Collections/VariantDictionary.cs create mode 100644 ModernKeePassLib/Cryptography/Cipher/ChaCha20Cipher.cs create mode 100644 ModernKeePassLib/Cryptography/Cipher/ChaCha20Engine.cs create mode 100644 ModernKeePassLib/Cryptography/Cipher/CtrBlockCipher.cs create mode 100644 ModernKeePassLib/Cryptography/CryptoUtil.cs create mode 100644 ModernKeePassLib/Cryptography/Hash/Blake2b.cs create mode 100644 ModernKeePassLib/Cryptography/KeyDerivation/AesKdf.GCrypt.cs create mode 100644 ModernKeePassLib/Cryptography/KeyDerivation/AesKdf.cs create mode 100644 ModernKeePassLib/Cryptography/KeyDerivation/Argon2Kdf.Core.cs create mode 100644 ModernKeePassLib/Cryptography/KeyDerivation/Argon2Kdf.cs create mode 100644 ModernKeePassLib/Cryptography/KeyDerivation/KdfEngine.cs create mode 100644 ModernKeePassLib/Cryptography/KeyDerivation/KdfParameters.cs create mode 100644 ModernKeePassLib/Cryptography/KeyDerivation/KdfPool.cs create mode 100644 ModernKeePassLib/Serialization/HmacBlockStream.cs create mode 100644 ModernKeePassLib/Serialization/IocProperties.cs create mode 100644 ModernKeePassLib/Serialization/IocPropertyInfo.cs create mode 100644 ModernKeePassLib/Serialization/IocPropertyInfoPool.cs diff --git a/ModernKeePass/Common/DatabaseHelper.cs b/ModernKeePass/Common/DatabaseHelper.cs index 294276f..020770c 100644 --- a/ModernKeePass/Common/DatabaseHelper.cs +++ b/ModernKeePass/Common/DatabaseHelper.cs @@ -33,6 +33,7 @@ namespace ModernKeePass.Common } } + public string Open(string password, bool createNew = false) { var key = new CompositeKey(); diff --git a/ModernKeePass/Controls/OpenDatabaseUserControl.xaml.cs b/ModernKeePass/Controls/OpenDatabaseUserControl.xaml.cs index 68f8fcf..3f96e98 100644 --- a/ModernKeePass/Controls/OpenDatabaseUserControl.xaml.cs +++ b/ModernKeePass/Controls/OpenDatabaseUserControl.xaml.cs @@ -53,7 +53,9 @@ namespace ModernKeePass.Controls var app = (App)Application.Current; StatusTextBlock.Text = app.Database.Open(PasswordBox.Password, CreateNew); if (app.Database.Status == DatabaseHelper.DatabaseStatus.Opened) + { ValidationChecked?.Invoke(this, new PasswordEventArgs(app.Database.RootGroup)); + } } private void PasswordBox_KeyDown(object sender, KeyRoutedEventArgs e) diff --git a/ModernKeePass/Package.appxmanifest b/ModernKeePass/Package.appxmanifest index 01ce4f2..1c69bb4 100644 --- a/ModernKeePass/Package.appxmanifest +++ b/ModernKeePass/Package.appxmanifest @@ -1,6 +1,6 @@  - + ModernKeePass wismna diff --git a/ModernKeePass/Pages/AboutPage.xaml b/ModernKeePass/Pages/AboutPage.xaml index defdbdc..fff7697 100644 --- a/ModernKeePass/Pages/AboutPage.xaml +++ b/ModernKeePass/Pages/AboutPage.xaml @@ -1,18 +1,37 @@  + + + + - About - - A modern password manager for the Windows Store - Homepage: https://github.com/wismna/ModernKeePass - Credits: - Dominik Reichl for the KeePass application and file format - ArtjomP for his PCL adapatation of the KeePass Library + + + + + + + + + + + + + + + + + + + + + diff --git a/ModernKeePass/ViewModels/AboutVm.cs b/ModernKeePass/ViewModels/AboutVm.cs index 94d929e..ffe8d16 100644 --- a/ModernKeePass/ViewModels/AboutVm.cs +++ b/ModernKeePass/ViewModels/AboutVm.cs @@ -9,10 +9,9 @@ namespace ModernKeePass.ViewModels get { var package = Package.Current; - var packageId = package.Id; - var version = packageId.Version; + var version = package.Id.Version; - return $"ModernKeePass version {version.Major}.{version.Minor}"; + return $"{package.DisplayName} version {version.Major}.{version.Minor}"; } } } diff --git a/ModernKeePassLib/Collections/AutoTypeConfig.cs b/ModernKeePassLib/Collections/AutoTypeConfig.cs index c6b2396..8427c05 100644 --- a/ModernKeePassLib/Collections/AutoTypeConfig.cs +++ b/ModernKeePassLib/Collections/AutoTypeConfig.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/Collections/ProtectedBinaryDictionary.cs b/ModernKeePassLib/Collections/ProtectedBinaryDictionary.cs index 5dd132c..51d6dd7 100644 --- a/ModernKeePassLib/Collections/ProtectedBinaryDictionary.cs +++ b/ModernKeePassLib/Collections/ProtectedBinaryDictionary.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/Collections/ProtectedBinarySet.cs b/ModernKeePassLib/Collections/ProtectedBinarySet.cs new file mode 100644 index 0000000..fedb86f --- /dev/null +++ b/ModernKeePassLib/Collections/ProtectedBinarySet.cs @@ -0,0 +1,174 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +using ModernKeePassLib.Delegates; +using ModernKeePassLib.Security; + +namespace ModernKeePassLib.Collections +{ + internal sealed class ProtectedBinarySet : IEnumerable> + { + private Dictionary m_d = + new Dictionary(); + + public ProtectedBinarySet() + { + } + + IEnumerator IEnumerable.GetEnumerator() + { + return m_d.GetEnumerator(); + } + + public IEnumerator> GetEnumerator() + { + return m_d.GetEnumerator(); + } + + public void Clear() + { + m_d.Clear(); + } + + private int GetFreeID() + { + int i = m_d.Count; + while(m_d.ContainsKey(i)) { ++i; } + Debug.Assert(i == m_d.Count); // m_d.Count should be free + return i; + } + + public ProtectedBinary Get(int iID) + { + ProtectedBinary pb; + if(m_d.TryGetValue(iID, out pb)) return pb; + + // Debug.Assert(false); // No assert + return null; + } + + public int Find(ProtectedBinary pb) + { + if(pb == null) { Debug.Assert(false); return -1; } + + // Fast search by reference + foreach(KeyValuePair kvp in m_d) + { + if(object.ReferenceEquals(pb, kvp.Value)) + { + Debug.Assert(pb.Equals(kvp.Value)); + return kvp.Key; + } + } + + // Slow search by content + foreach(KeyValuePair kvp in m_d) + { + if(pb.Equals(kvp.Value)) return kvp.Key; + } + + // Debug.Assert(false); // No assert + return -1; + } + + public void Set(int iID, ProtectedBinary pb) + { + if(iID < 0) { Debug.Assert(false); return; } + if(pb == null) { Debug.Assert(false); return; } + + m_d[iID] = pb; + } + + public void Add(ProtectedBinary pb) + { + if(pb == null) { Debug.Assert(false); return; } + + int i = Find(pb); + if(i >= 0) return; // Exists already + + i = GetFreeID(); + m_d[i] = pb; + } + + public void AddFrom(ProtectedBinaryDictionary d) + { + if(d == null) { Debug.Assert(false); return; } + + foreach(KeyValuePair kvp in d) + { + Add(kvp.Value); + } + } + + public void AddFrom(PwGroup pg) + { + if(pg == null) { Debug.Assert(false); return; } + + EntryHandler eh = delegate(PwEntry pe) + { + if(pe == null) { Debug.Assert(false); return true; } + + AddFrom(pe.Binaries); + foreach(PwEntry peHistory in pe.History) + { + if(peHistory == null) { Debug.Assert(false); continue; } + AddFrom(peHistory.Binaries); + } + + return true; + }; + + pg.TraverseTree(TraversalMethod.PreOrder, null, eh); + } + + public ProtectedBinary[] ToArray() + { + int n = m_d.Count; + ProtectedBinary[] v = new ProtectedBinary[n]; + + foreach(KeyValuePair kvp in m_d) + { + if((kvp.Key < 0) || (kvp.Key >= n)) + { + Debug.Assert(false); + throw new InvalidOperationException(); + } + + v[kvp.Key] = kvp.Value; + } + + for(int i = 0; i < n; ++i) + { + if(v[i] == null) + { + Debug.Assert(false); + throw new InvalidOperationException(); + } + } + + return v; + } + } +} diff --git a/ModernKeePassLib/Collections/ProtectedStringDictionary.cs b/ModernKeePassLib/Collections/ProtectedStringDictionary.cs index 00c418b..bd00b4c 100644 --- a/ModernKeePassLib/Collections/ProtectedStringDictionary.cs +++ b/ModernKeePassLib/Collections/ProtectedStringDictionary.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -283,11 +283,7 @@ namespace ModernKeePassLib.Collections public List GetKeys() { - List v = new List(); - - foreach(string strKey in m_vStrings.Keys) v.Add(strKey); - - return v; + return new List(m_vStrings.Keys); } public void EnableProtection(string strField, bool bProtect) @@ -299,7 +295,8 @@ namespace ModernKeePassLib.Collections { byte[] pbData = ps.ReadUtf8(); Set(strField, new ProtectedString(bProtect, pbData)); - MemUtil.ZeroByteArray(pbData); + + if(bProtect) MemUtil.ZeroByteArray(pbData); } } } diff --git a/ModernKeePassLib/Collections/PwObjectList.cs b/ModernKeePassLib/Collections/PwObjectList.cs index 7820aa3..6607269 100644 --- a/ModernKeePassLib/Collections/PwObjectList.cs +++ b/ModernKeePassLib/Collections/PwObjectList.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/Collections/PwObjectPool.cs b/ModernKeePassLib/Collections/PwObjectPool.cs index 5bad39e..fb148d3 100644 --- a/ModernKeePassLib/Collections/PwObjectPool.cs +++ b/ModernKeePassLib/Collections/PwObjectPool.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,12 +18,14 @@ */ using System; +using System.Collections; using System.Collections.Generic; -using System.Text; using System.Diagnostics; +using System.Text; using ModernKeePassLib.Delegates; using ModernKeePassLib.Interfaces; +using ModernKeePassLib.Utility; #if KeePassLibSD using KeePassLibSD; @@ -77,4 +79,154 @@ namespace ModernKeePassLib.Collections return true; } } + + internal sealed class PwObjectPoolEx + { + private Dictionary m_dUuidToId = + new Dictionary(); + private Dictionary m_dIdToItem = + new Dictionary(); + + private PwObjectPoolEx() + { + } + + public static PwObjectPoolEx FromGroup(PwGroup pg) + { + PwObjectPoolEx p = new PwObjectPoolEx(); + + if(pg == null) { Debug.Assert(false); return p; } + + ulong uFreeId = 2; // 0 = "not found", 1 is a hole + + p.m_dUuidToId[pg.Uuid] = uFreeId; + p.m_dIdToItem[uFreeId] = pg; + uFreeId += 2; // Make hole + + p.AddGroupRec(pg, ref uFreeId); + return p; + } + + private void AddGroupRec(PwGroup pg, ref ulong uFreeId) + { + if(pg == null) { Debug.Assert(false); return; } + + ulong uId = uFreeId; + + // Consecutive entries must have consecutive IDs + foreach(PwEntry pe in pg.Entries) + { + Debug.Assert(!m_dUuidToId.ContainsKey(pe.Uuid)); + Debug.Assert(!m_dIdToItem.ContainsValue(pe)); + + m_dUuidToId[pe.Uuid] = uId; + m_dIdToItem[uId] = pe; + ++uId; + } + ++uId; // Make hole + + // Consecutive groups must have consecutive IDs + foreach(PwGroup pgSub in pg.Groups) + { + Debug.Assert(!m_dUuidToId.ContainsKey(pgSub.Uuid)); + Debug.Assert(!m_dIdToItem.ContainsValue(pgSub)); + + m_dUuidToId[pgSub.Uuid] = uId; + m_dIdToItem[uId] = pgSub; + ++uId; + } + ++uId; // Make hole + + foreach(PwGroup pgSub in pg.Groups) + { + AddGroupRec(pgSub, ref uId); + } + + uFreeId = uId; + } + + public ulong GetIdByUuid(PwUuid pwUuid) + { + if(pwUuid == null) { Debug.Assert(false); return 0; } + + ulong uId; + m_dUuidToId.TryGetValue(pwUuid, out uId); + return uId; + } + + public IStructureItem GetItemByUuid(PwUuid pwUuid) + { + if(pwUuid == null) { Debug.Assert(false); return null; } + + ulong uId; + if(!m_dUuidToId.TryGetValue(pwUuid, out uId)) return null; + Debug.Assert(uId != 0); + + return GetItemById(uId); + } + + public IStructureItem GetItemById(ulong uId) + { + IStructureItem p; + m_dIdToItem.TryGetValue(uId, out p); + return p; + } + } + + internal sealed class PwObjectBlock : IEnumerable + where T : class, ITimeLogger, IStructureItem, IDeepCloneable + { + private List m_l = new List(); + + public T PrimaryItem + { + get { return ((m_l.Count > 0) ? m_l[0] : null); } + } + + private DateTime m_dtLocationChanged = TimeUtil.SafeMinValueUtc; + public DateTime LocationChanged + { + get { return m_dtLocationChanged; } + } + + private PwObjectPoolEx m_poolAssoc = null; + public PwObjectPoolEx PoolAssoc + { + get { return m_poolAssoc; } + } + + public PwObjectBlock() + { + } + +#if DEBUG + public override string ToString() + { + return ("PwObjectBlock, Count = " + m_l.Count.ToString()); + } +#endif + + IEnumerator IEnumerable.GetEnumerator() + { + return m_l.GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + return m_l.GetEnumerator(); + } + + public void Add(T t, DateTime dtLoc, PwObjectPoolEx pool) + { + if(t == null) { Debug.Assert(false); return; } + + m_l.Add(t); + + if(dtLoc > m_dtLocationChanged) + { + m_dtLocationChanged = dtLoc; + m_poolAssoc = pool; + } + } + } } diff --git a/ModernKeePassLib/Collections/StringDictionaryEx.cs b/ModernKeePassLib/Collections/StringDictionaryEx.cs index b6f8232..d4e12f2 100644 --- a/ModernKeePassLib/Collections/StringDictionaryEx.cs +++ b/ModernKeePassLib/Collections/StringDictionaryEx.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -32,14 +32,14 @@ using KeePassLibSD; namespace ModernKeePassLib.Collections { public sealed class StringDictionaryEx : IDeepCloneable, - IEnumerable> + IEnumerable>, IEquatable { - private SortedDictionary m_vDict = + private SortedDictionary m_dict = new SortedDictionary(); public int Count { - get { return m_vDict.Count; } + get { return m_dict.Count; } } public StringDictionaryEx() @@ -48,39 +48,53 @@ namespace ModernKeePassLib.Collections IEnumerator IEnumerable.GetEnumerator() { - return m_vDict.GetEnumerator(); + return m_dict.GetEnumerator(); } public IEnumerator> GetEnumerator() { - return m_vDict.GetEnumerator(); + return m_dict.GetEnumerator(); } public StringDictionaryEx CloneDeep() { - StringDictionaryEx plNew = new StringDictionaryEx(); + StringDictionaryEx sdNew = new StringDictionaryEx(); - foreach(KeyValuePair kvpStr in m_vDict) - plNew.Set(kvpStr.Key, kvpStr.Value); + foreach(KeyValuePair kvp in m_dict) + sdNew.m_dict[kvp.Key] = kvp.Value; // Strings are immutable - return plNew; + return sdNew; + } + + public bool Equals(StringDictionaryEx sdOther) + { + if(sdOther == null) { Debug.Assert(false); return false; } + + if(m_dict.Count != sdOther.m_dict.Count) return false; + + foreach(KeyValuePair kvp in sdOther.m_dict) + { + string str = Get(kvp.Key); + if((str == null) || (str != kvp.Value)) return false; + } + + return true; } public string Get(string strName) { - Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); + if(strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); } string s; - if(m_vDict.TryGetValue(strName, out s)) return s; - + if(m_dict.TryGetValue(strName, out s)) return s; return null; } public bool Exists(string strName) { - Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); + if(strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); } - return m_vDict.ContainsKey(strName); + return m_dict.ContainsKey(strName); } /// @@ -92,25 +106,25 @@ namespace ModernKeePassLib.Collections /// parameters is null. public void Set(string strField, string strNewValue) { - Debug.Assert(strField != null); if(strField == null) throw new ArgumentNullException("strField"); - Debug.Assert(strNewValue != null); if(strNewValue == null) throw new ArgumentNullException("strNewValue"); + if(strField == null) { Debug.Assert(false); throw new ArgumentNullException("strField"); } + if(strNewValue == null) { Debug.Assert(false); throw new ArgumentNullException("strNewValue"); } - m_vDict[strField] = strNewValue; + m_dict[strField] = strNewValue; } /// /// Delete a string. /// /// Name of the string field to delete. - /// Returns true if the field has been successfully - /// removed, otherwise the return value is false. + /// Returns true, if the field has been successfully + /// removed. Otherwise, the return value is false. /// Thrown if the input /// parameter is null. public bool Remove(string strField) { - Debug.Assert(strField != null); if(strField == null) throw new ArgumentNullException("strField"); + if(strField == null) { Debug.Assert(false); throw new ArgumentNullException("strField"); } - return m_vDict.Remove(strField); + return m_dict.Remove(strField); } } } diff --git a/ModernKeePassLib/Collections/VariantDictionary.cs b/ModernKeePassLib/Collections/VariantDictionary.cs new file mode 100644 index 0000000..6268e63 --- /dev/null +++ b/ModernKeePassLib/Collections/VariantDictionary.cs @@ -0,0 +1,415 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +using ModernKeePassLib.Resources; +using ModernKeePassLib.Utility; + +namespace ModernKeePassLib.Collections +{ + public class VariantDictionary + { + private const ushort VdVersion = 0x0100; + private const ushort VdmCritical = 0xFF00; + private const ushort VdmInfo = 0x00FF; + + private Dictionary m_d = new Dictionary(); + + private enum VdType : byte + { + None = 0, + + // Byte = 0x02, + // UInt16 = 0x03, + UInt32 = 0x04, + UInt64 = 0x05, + + // Signed mask: 0x08 + Bool = 0x08, + // SByte = 0x0A, + // Int16 = 0x0B, + Int32 = 0x0C, + Int64 = 0x0D, + + // Float = 0x10, + // Double = 0x11, + // Decimal = 0x12, + + // Char = 0x17, // 16-bit Unicode character + String = 0x18, + + // Array mask: 0x40 + ByteArray = 0x42 + } + + public int Count + { + get { return m_d.Count; } + } + + public VariantDictionary() + { + Debug.Assert((VdmCritical & VdmInfo) == ushort.MinValue); + Debug.Assert((VdmCritical | VdmInfo) == ushort.MaxValue); + } + + private bool Get(string strName, out T t) + { + t = default(T); + + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return false; } + + object o; + if(!m_d.TryGetValue(strName, out o)) return false; // No assert + + if(o == null) { Debug.Assert(false); return false; } + if(o.GetType() != typeof(T)) { Debug.Assert(false); return false; } + + t = (T)o; + return true; + } + + private void SetStruct(string strName, T t) + where T : struct + { + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return; } + +#if DEBUG + T tEx; + Get(strName, out tEx); // Assert same type +#endif + + m_d[strName] = t; + } + + private void SetRef(string strName, T t) + where T : class + { + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return; } + if(t == null) { Debug.Assert(false); return; } + +#if DEBUG + T tEx; + Get(strName, out tEx); // Assert same type +#endif + + m_d[strName] = t; + } + + public bool Remove(string strName) + { + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return false; } + + return m_d.Remove(strName); + } + + public void CopyTo(VariantDictionary d) + { + if(d == null) { Debug.Assert(false); return; } + + // Do not clear the target + foreach(KeyValuePair kvp in m_d) + { + d.m_d[kvp.Key] = kvp.Value; + } + } + + public Type GetTypeOf(string strName) + { + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return null; } + + object o; + m_d.TryGetValue(strName, out o); + if(o == null) return null; // No assert + + return o.GetType(); + } + + public uint GetUInt32(string strName, uint uDefault) + { + uint u; + if(Get(strName, out u)) return u; + return uDefault; + } + + public void SetUInt32(string strName, uint uValue) + { + SetStruct(strName, uValue); + } + + public ulong GetUInt64(string strName, ulong uDefault) + { + ulong u; + if(Get(strName, out u)) return u; + return uDefault; + } + + public void SetUInt64(string strName, ulong uValue) + { + SetStruct(strName, uValue); + } + + public bool GetBool(string strName, bool bDefault) + { + bool b; + if(Get(strName, out b)) return b; + return bDefault; + } + + public void SetBool(string strName, bool bValue) + { + SetStruct(strName, bValue); + } + + public int GetInt32(string strName, int iDefault) + { + int i; + if(Get(strName, out i)) return i; + return iDefault; + } + + public void SetInt32(string strName, int iValue) + { + SetStruct(strName, iValue); + } + + public long GetInt64(string strName, long lDefault) + { + long l; + if(Get(strName, out l)) return l; + return lDefault; + } + + public void SetInt64(string strName, long lValue) + { + SetStruct(strName, lValue); + } + + public string GetString(string strName) + { + string str; + Get(strName, out str); + return str; + } + + public void SetString(string strName, string strValue) + { + SetRef(strName, strValue); + } + + public byte[] GetByteArray(string strName) + { + byte[] pb; + Get(strName, out pb); + return pb; + } + + public void SetByteArray(string strName, byte[] pbValue) + { + SetRef(strName, pbValue); + } + + /// + /// Create a deep copy. + /// + public virtual object Clone() + { + VariantDictionary vdNew = new VariantDictionary(); + + foreach(KeyValuePair kvp in m_d) + { + object o = kvp.Value; + if(o == null) { Debug.Assert(false); continue; } + + Type t = o.GetType(); + if(t == typeof(byte[])) + { + byte[] p = (byte[])o; + byte[] pNew = new byte[p.Length]; + if(p.Length > 0) Array.Copy(p, pNew, p.Length); + + o = pNew; + } + + vdNew.m_d[kvp.Key] = o; + } + + return vdNew; + } + + public static byte[] Serialize(VariantDictionary p) + { + if(p == null) { Debug.Assert(false); return null; } + + byte[] pbRet; + using(MemoryStream ms = new MemoryStream()) + { + MemUtil.Write(ms, MemUtil.UInt16ToBytes(VdVersion)); + + foreach(KeyValuePair kvp in p.m_d) + { + string strName = kvp.Key; + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); continue; } + byte[] pbName = StrUtil.Utf8.GetBytes(strName); + + object o = kvp.Value; + if(o == null) { Debug.Assert(false); continue; } + + Type t = o.GetType(); + VdType vt = VdType.None; + byte[] pbValue = null; + if(t == typeof(uint)) + { + vt = VdType.UInt32; + pbValue = MemUtil.UInt32ToBytes((uint)o); + } + else if(t == typeof(ulong)) + { + vt = VdType.UInt64; + pbValue = MemUtil.UInt64ToBytes((ulong)o); + } + else if(t == typeof(bool)) + { + vt = VdType.Bool; + pbValue = new byte[1]; + pbValue[0] = ((bool)o ? (byte)1 : (byte)0); + } + else if(t == typeof(int)) + { + vt = VdType.Int32; + pbValue = MemUtil.Int32ToBytes((int)o); + } + else if(t == typeof(long)) + { + vt = VdType.Int64; + pbValue = MemUtil.Int64ToBytes((long)o); + } + else if(t == typeof(string)) + { + vt = VdType.String; + pbValue = StrUtil.Utf8.GetBytes((string)o); + } + else if(t == typeof(byte[])) + { + vt = VdType.ByteArray; + pbValue = (byte[])o; + } + else { Debug.Assert(false); continue; } // Unknown type + + ms.WriteByte((byte)vt); + MemUtil.Write(ms, MemUtil.Int32ToBytes(pbName.Length)); + MemUtil.Write(ms, pbName); + MemUtil.Write(ms, MemUtil.Int32ToBytes(pbValue.Length)); + MemUtil.Write(ms, pbValue); + } + + ms.WriteByte((byte)VdType.None); + pbRet = ms.ToArray(); + } + + return pbRet; + } + + public static VariantDictionary Deserialize(byte[] pb) + { + if(pb == null) { Debug.Assert(false); return null; } + + VariantDictionary d = new VariantDictionary(); + using(MemoryStream ms = new MemoryStream(pb, false)) + { + ushort uVersion = MemUtil.BytesToUInt16(MemUtil.Read(ms, 2)); + if((uVersion & VdmCritical) > (VdVersion & VdmCritical)) + throw new FormatException(KLRes.FileNewVerReq); + + while(true) + { + int iType = ms.ReadByte(); + if(iType < 0) throw new EndOfStreamException(KLRes.FileCorrupted); + byte btType = (byte)iType; + if(btType == (byte)VdType.None) break; + + int cbName = MemUtil.BytesToInt32(MemUtil.Read(ms, 4)); + byte[] pbName = MemUtil.Read(ms, cbName); + if(pbName.Length != cbName) + throw new EndOfStreamException(KLRes.FileCorrupted); + string strName = StrUtil.Utf8.GetString(pbName, 0, pbName.Length); + + int cbValue = MemUtil.BytesToInt32(MemUtil.Read(ms, 4)); + byte[] pbValue = MemUtil.Read(ms, cbValue); + if(pbValue.Length != cbValue) + throw new EndOfStreamException(KLRes.FileCorrupted); + + switch(btType) + { + case (byte)VdType.UInt32: + if(cbValue == 4) + d.SetUInt32(strName, MemUtil.BytesToUInt32(pbValue)); + else { Debug.Assert(false); } + break; + + case (byte)VdType.UInt64: + if(cbValue == 8) + d.SetUInt64(strName, MemUtil.BytesToUInt64(pbValue)); + else { Debug.Assert(false); } + break; + + case (byte)VdType.Bool: + if(cbValue == 1) + d.SetBool(strName, (pbValue[0] != 0)); + else { Debug.Assert(false); } + break; + + case (byte)VdType.Int32: + if(cbValue == 4) + d.SetInt32(strName, MemUtil.BytesToInt32(pbValue)); + else { Debug.Assert(false); } + break; + + case (byte)VdType.Int64: + if(cbValue == 8) + d.SetInt64(strName, MemUtil.BytesToInt64(pbValue)); + else { Debug.Assert(false); } + break; + + case (byte)VdType.String: + d.SetString(strName, StrUtil.Utf8.GetString(pbValue, 0, pbValue.Length)); + break; + + case (byte)VdType.ByteArray: + d.SetByteArray(strName, pbValue); + break; + + default: + Debug.Assert(false); // Unknown type + break; + } + } + + Debug.Assert(ms.ReadByte() < 0); + } + + return d; + } + } +} diff --git a/ModernKeePassLib/Cryptography/Cipher/ChaCha20Cipher.cs b/ModernKeePassLib/Cryptography/Cipher/ChaCha20Cipher.cs new file mode 100644 index 0000000..86fe526 --- /dev/null +++ b/ModernKeePassLib/Cryptography/Cipher/ChaCha20Cipher.cs @@ -0,0 +1,254 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; + +using ModernKeePassLib.Resources; +using ModernKeePassLib.Utility; + +namespace ModernKeePassLib.Cryptography.Cipher +{ + /// + /// Implementation of the ChaCha20 cipher with a 96-bit nonce, + /// as specified in RFC 7539. + /// https://tools.ietf.org/html/rfc7539 + /// + public sealed class ChaCha20Cipher : CtrBlockCipher + { + private uint[] m_s = new uint[16]; // State + private uint[] m_x = new uint[16]; // Working buffer + + private bool m_bLargeCounter; // See constructor documentation + + private static readonly uint[] g_sigma = new uint[4] { + 0x61707865, 0x3320646E, 0x79622D32, 0x6B206574 + }; + + private const string StrNameRfc = "ChaCha20 (RFC 7539)"; + + public override int BlockSize + { + get { return 64; } + } + + public ChaCha20Cipher(byte[] pbKey32, byte[] pbIV12) : + this(pbKey32, pbIV12, false) + { + } + + /// + /// Constructor. + /// + /// Key (32 bytes). + /// Nonce (12 bytes). + /// If false, the RFC 7539 version + /// of ChaCha20 is used. In this case, only 256 GB of data can be + /// encrypted securely (because the block counter is a 32-bit variable); + /// an attempt to encrypt more data throws an exception. + /// If is true, the 32-bit + /// counter overflows to another 32-bit variable (i.e. the counter + /// effectively is a 64-bit variable), like in the original ChaCha20 + /// specification by D. J. Bernstein (which has a 64-bit counter and a + /// 64-bit nonce). To be compatible with this version, the 64-bit nonce + /// must be stored in the last 8 bytes of + /// and the first 4 bytes must be 0. + /// If the IV was generated randomly, a 12-byte IV and a large counter + /// can be used to securely encrypt more than 256 GB of data (but note + /// this is incompatible with RFC 7539 and the original specification). + public ChaCha20Cipher(byte[] pbKey32, byte[] pbIV12, bool bLargeCounter) : + base() + { + if(pbKey32 == null) throw new ArgumentNullException("pbKey32"); + if(pbKey32.Length != 32) throw new ArgumentOutOfRangeException("pbKey32"); + if(pbIV12 == null) throw new ArgumentNullException("pbIV12"); + if(pbIV12.Length != 12) throw new ArgumentOutOfRangeException("pbIV12"); + + m_bLargeCounter = bLargeCounter; + + // Key setup + m_s[4] = MemUtil.BytesToUInt32(pbKey32, 0); + m_s[5] = MemUtil.BytesToUInt32(pbKey32, 4); + m_s[6] = MemUtil.BytesToUInt32(pbKey32, 8); + m_s[7] = MemUtil.BytesToUInt32(pbKey32, 12); + m_s[8] = MemUtil.BytesToUInt32(pbKey32, 16); + m_s[9] = MemUtil.BytesToUInt32(pbKey32, 20); + m_s[10] = MemUtil.BytesToUInt32(pbKey32, 24); + m_s[11] = MemUtil.BytesToUInt32(pbKey32, 28); + m_s[0] = g_sigma[0]; + m_s[1] = g_sigma[1]; + m_s[2] = g_sigma[2]; + m_s[3] = g_sigma[3]; + + // IV setup + m_s[12] = 0; // Counter + m_s[13] = MemUtil.BytesToUInt32(pbIV12, 0); + m_s[14] = MemUtil.BytesToUInt32(pbIV12, 4); + m_s[15] = MemUtil.BytesToUInt32(pbIV12, 8); + } + + protected override void Dispose(bool bDisposing) + { + if(bDisposing) + { + MemUtil.ZeroArray(m_s); + MemUtil.ZeroArray(m_x); + } + + base.Dispose(bDisposing); + } + + protected override void NextBlock(byte[] pBlock) + { + if(pBlock == null) throw new ArgumentNullException("pBlock"); + if(pBlock.Length != 64) throw new ArgumentOutOfRangeException("pBlock"); + + // x is a local alias for the working buffer; with this, + // the compiler/runtime might remove some checks + uint[] x = m_x; + if(x == null) throw new InvalidOperationException(); + if(x.Length < 16) throw new InvalidOperationException(); + + uint[] s = m_s; + if(s == null) throw new InvalidOperationException(); + if(s.Length < 16) throw new InvalidOperationException(); + + Array.Copy(s, x, 16); + + unchecked + { + // 10 * 8 quarter rounds = 20 rounds + for(int i = 0; i < 10; ++i) + { + // Column quarter rounds + x[ 0] += x[ 4]; + x[12] = MemUtil.RotateLeft32(x[12] ^ x[ 0], 16); + x[ 8] += x[12]; + x[ 4] = MemUtil.RotateLeft32(x[ 4] ^ x[ 8], 12); + x[ 0] += x[ 4]; + x[12] = MemUtil.RotateLeft32(x[12] ^ x[ 0], 8); + x[ 8] += x[12]; + x[ 4] = MemUtil.RotateLeft32(x[ 4] ^ x[ 8], 7); + + x[ 1] += x[ 5]; + x[13] = MemUtil.RotateLeft32(x[13] ^ x[ 1], 16); + x[ 9] += x[13]; + x[ 5] = MemUtil.RotateLeft32(x[ 5] ^ x[ 9], 12); + x[ 1] += x[ 5]; + x[13] = MemUtil.RotateLeft32(x[13] ^ x[ 1], 8); + x[ 9] += x[13]; + x[ 5] = MemUtil.RotateLeft32(x[ 5] ^ x[ 9], 7); + + x[ 2] += x[ 6]; + x[14] = MemUtil.RotateLeft32(x[14] ^ x[ 2], 16); + x[10] += x[14]; + x[ 6] = MemUtil.RotateLeft32(x[ 6] ^ x[10], 12); + x[ 2] += x[ 6]; + x[14] = MemUtil.RotateLeft32(x[14] ^ x[ 2], 8); + x[10] += x[14]; + x[ 6] = MemUtil.RotateLeft32(x[ 6] ^ x[10], 7); + + x[ 3] += x[ 7]; + x[15] = MemUtil.RotateLeft32(x[15] ^ x[ 3], 16); + x[11] += x[15]; + x[ 7] = MemUtil.RotateLeft32(x[ 7] ^ x[11], 12); + x[ 3] += x[ 7]; + x[15] = MemUtil.RotateLeft32(x[15] ^ x[ 3], 8); + x[11] += x[15]; + x[ 7] = MemUtil.RotateLeft32(x[ 7] ^ x[11], 7); + + // Diagonal quarter rounds + x[ 0] += x[ 5]; + x[15] = MemUtil.RotateLeft32(x[15] ^ x[ 0], 16); + x[10] += x[15]; + x[ 5] = MemUtil.RotateLeft32(x[ 5] ^ x[10], 12); + x[ 0] += x[ 5]; + x[15] = MemUtil.RotateLeft32(x[15] ^ x[ 0], 8); + x[10] += x[15]; + x[ 5] = MemUtil.RotateLeft32(x[ 5] ^ x[10], 7); + + x[ 1] += x[ 6]; + x[12] = MemUtil.RotateLeft32(x[12] ^ x[ 1], 16); + x[11] += x[12]; + x[ 6] = MemUtil.RotateLeft32(x[ 6] ^ x[11], 12); + x[ 1] += x[ 6]; + x[12] = MemUtil.RotateLeft32(x[12] ^ x[ 1], 8); + x[11] += x[12]; + x[ 6] = MemUtil.RotateLeft32(x[ 6] ^ x[11], 7); + + x[ 2] += x[ 7]; + x[13] = MemUtil.RotateLeft32(x[13] ^ x[ 2], 16); + x[ 8] += x[13]; + x[ 7] = MemUtil.RotateLeft32(x[ 7] ^ x[ 8], 12); + x[ 2] += x[ 7]; + x[13] = MemUtil.RotateLeft32(x[13] ^ x[ 2], 8); + x[ 8] += x[13]; + x[ 7] = MemUtil.RotateLeft32(x[ 7] ^ x[ 8], 7); + + x[ 3] += x[ 4]; + x[14] = MemUtil.RotateLeft32(x[14] ^ x[ 3], 16); + x[ 9] += x[14]; + x[ 4] = MemUtil.RotateLeft32(x[ 4] ^ x[ 9], 12); + x[ 3] += x[ 4]; + x[14] = MemUtil.RotateLeft32(x[14] ^ x[ 3], 8); + x[ 9] += x[14]; + x[ 4] = MemUtil.RotateLeft32(x[ 4] ^ x[ 9], 7); + } + + for(int i = 0; i < 16; ++i) x[i] += s[i]; + + for(int i = 0; i < 16; ++i) + { + int i4 = i << 2; + uint xi = x[i]; + + pBlock[i4] = (byte)xi; + pBlock[i4 + 1] = (byte)(xi >> 8); + pBlock[i4 + 2] = (byte)(xi >> 16); + pBlock[i4 + 3] = (byte)(xi >> 24); + } + + ++s[12]; + if(s[12] == 0) + { + if(!m_bLargeCounter) + throw new InvalidOperationException( + KLRes.EncDataTooLarge.Replace(@"{PARAM}", StrNameRfc)); + ++s[13]; // Increment high half of large counter + } + } + } + + public long Seek(long lOffset, SeekOrigin so) + { + if(so != SeekOrigin.Begin) throw new NotSupportedException(); + + if((lOffset < 0) || ((lOffset & 63) != 0) || + ((lOffset >> 6) > (long)uint.MaxValue)) + throw new ArgumentOutOfRangeException("lOffset"); + + m_s[12] = (uint)(lOffset >> 6); + InvalidateBlock(); + + return lOffset; + } + } +} diff --git a/ModernKeePassLib/Cryptography/Cipher/ChaCha20Engine.cs b/ModernKeePassLib/Cryptography/Cipher/ChaCha20Engine.cs new file mode 100644 index 0000000..f22dcf9 --- /dev/null +++ b/ModernKeePassLib/Cryptography/Cipher/ChaCha20Engine.cs @@ -0,0 +1,177 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +using ModernKeePassLib.Resources; + +namespace ModernKeePassLib.Cryptography.Cipher +{ + public sealed class ChaCha20Engine : ICipherEngine2 + { + private PwUuid m_uuid = new PwUuid(new byte[] { + 0xD6, 0x03, 0x8A, 0x2B, 0x8B, 0x6F, 0x4C, 0xB5, + 0xA5, 0x24, 0x33, 0x9A, 0x31, 0xDB, 0xB5, 0x9A + }); + + public PwUuid CipherUuid + { + get { return m_uuid; } + } + + public string DisplayName + { + get + { + return ("ChaCha20 (" + KLRes.KeyBits.Replace(@"{PARAM}", + "256") + ", RFC 7539)"); + } + } + + public int KeyLength + { + get { return 32; } + } + + public int IVLength + { + get { return 12; } // 96 bits + } + + public Stream EncryptStream(Stream sPlainText, byte[] pbKey, byte[] pbIV) + { + return new ChaCha20Stream(sPlainText, true, pbKey, pbIV); + } + + public Stream DecryptStream(Stream sEncrypted, byte[] pbKey, byte[] pbIV) + { + return new ChaCha20Stream(sEncrypted, false, pbKey, pbIV); + } + } + + internal sealed class ChaCha20Stream : Stream + { + private Stream m_sBase; + private readonly bool m_bWriting; + private ChaCha20Cipher m_c; + + private byte[] m_pbBuffer = null; + + public override bool CanRead + { + get { return !m_bWriting; } + } + + public override bool CanSeek + { + get { return false; } + } + + public override bool CanWrite + { + get { return m_bWriting; } + } + + public override long Length + { + get { Debug.Assert(false); throw new NotSupportedException(); } + } + + public override long Position + { + get { Debug.Assert(false); throw new NotSupportedException(); } + set { Debug.Assert(false); throw new NotSupportedException(); } + } + + public ChaCha20Stream(Stream sBase, bool bWriting, byte[] pbKey32, + byte[] pbIV12) + { + if(sBase == null) throw new ArgumentNullException("sBase"); + + m_sBase = sBase; + m_bWriting = bWriting; + m_c = new ChaCha20Cipher(pbKey32, pbIV12); + } + + protected override void Dispose(bool bDisposing) + { + if(bDisposing) + { + if(m_sBase != null) + { + m_c.Dispose(); + m_c = null; + + //m_sBase.Close(); + m_sBase = null; + } + + m_pbBuffer = null; + } + + base.Dispose(bDisposing); + } + + public override void Flush() + { + Debug.Assert(m_sBase != null); + if(m_bWriting && (m_sBase != null)) m_sBase.Flush(); + } + + public override long Seek(long lOffset, SeekOrigin soOrigin) + { + Debug.Assert(false); + throw new NotImplementedException(); + } + + public override void SetLength(long lValue) + { + Debug.Assert(false); + throw new NotImplementedException(); + } + + public override int Read(byte[] pbBuffer, int iOffset, int nCount) + { + if(m_bWriting) throw new InvalidOperationException(); + + int cbRead = m_sBase.Read(pbBuffer, iOffset, nCount); + m_c.Decrypt(pbBuffer, iOffset, cbRead); + return cbRead; + } + + public override void Write(byte[] pbBuffer, int iOffset, int nCount) + { + if(nCount < 0) throw new ArgumentOutOfRangeException("nCount"); + if(nCount == 0) return; + + if(!m_bWriting) throw new InvalidOperationException(); + + if((m_pbBuffer == null) || (m_pbBuffer.Length < nCount)) + m_pbBuffer = new byte[nCount]; + Array.Copy(pbBuffer, iOffset, m_pbBuffer, 0, nCount); + + m_c.Encrypt(m_pbBuffer, 0, nCount); + m_sBase.Write(m_pbBuffer, 0, nCount); + } + } +} diff --git a/ModernKeePassLib/Cryptography/Cipher/CipherPool.cs b/ModernKeePassLib/Cryptography/Cipher/CipherPool.cs index 9b76314..65323f6 100644 --- a/ModernKeePassLib/Cryptography/Cipher/CipherPool.cs +++ b/ModernKeePassLib/Cryptography/Cipher/CipherPool.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -41,12 +41,17 @@ namespace ModernKeePassLib.Cryptography.Cipher { get { - if(m_poolGlobal != null) return m_poolGlobal; + CipherPool cp = m_poolGlobal; + if(cp == null) + { + cp = new CipherPool(); + cp.AddCipher(new StandardAesEngine()); + cp.AddCipher(new ChaCha20Engine()); - m_poolGlobal = new CipherPool(); - m_poolGlobal.AddCipher(new StandardAesEngine()); + m_poolGlobal = cp; + } - return m_poolGlobal; + return cp; } } diff --git a/ModernKeePassLib/Cryptography/Cipher/CtrBlockCipher.cs b/ModernKeePassLib/Cryptography/Cipher/CtrBlockCipher.cs new file mode 100644 index 0000000..e4e672b --- /dev/null +++ b/ModernKeePassLib/Cryptography/Cipher/CtrBlockCipher.cs @@ -0,0 +1,109 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +using ModernKeePassLib.Utility; + +namespace ModernKeePassLib.Cryptography.Cipher +{ + public abstract class CtrBlockCipher : IDisposable + { + private bool m_bDisposed = false; + + private byte[] m_pBlock; + private int m_iBlockPos; + + public abstract int BlockSize + { + get; + } + + public CtrBlockCipher() + { + int cb = this.BlockSize; + if(cb <= 0) throw new InvalidOperationException("this.BlockSize"); + + m_pBlock = new byte[cb]; + m_iBlockPos = cb; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool bDisposing) + { + if(bDisposing) + { + MemUtil.ZeroByteArray(m_pBlock); + m_iBlockPos = m_pBlock.Length; + + m_bDisposed = true; + } + } + + protected void InvalidateBlock() + { + m_iBlockPos = m_pBlock.Length; + } + + protected abstract void NextBlock(byte[] pBlock); + + public void Encrypt(byte[] m, int iOffset, int cb) + { + if(m_bDisposed) throw new ObjectDisposedException(null); + if(m == null) throw new ArgumentNullException("m"); + if(iOffset < 0) throw new ArgumentOutOfRangeException("iOffset"); + if(cb < 0) throw new ArgumentOutOfRangeException("cb"); + if(iOffset > (m.Length - cb)) throw new ArgumentOutOfRangeException("cb"); + + int cbBlock = m_pBlock.Length; + + while(cb > 0) + { + Debug.Assert(m_iBlockPos <= cbBlock); + if(m_iBlockPos == cbBlock) + { + NextBlock(m_pBlock); + m_iBlockPos = 0; + } + + int cbCopy = Math.Min(cbBlock - m_iBlockPos, cb); + Debug.Assert(cbCopy > 0); + + MemUtil.XorArray(m_pBlock, m_iBlockPos, m, iOffset, cbCopy); + + m_iBlockPos += cbCopy; + iOffset += cbCopy; + cb -= cbCopy; + } + } + + public void Decrypt(byte[] m, int iOffset, int cb) + { + Encrypt(m, iOffset, cb); + } + } +} diff --git a/ModernKeePassLib/Cryptography/Cipher/ICipherEngine.cs b/ModernKeePassLib/Cryptography/Cipher/ICipherEngine.cs index d529c0b..b1c790e 100644 --- a/ModernKeePassLib/Cryptography/Cipher/ICipherEngine.cs +++ b/ModernKeePassLib/Cryptography/Cipher/ICipherEngine.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -63,4 +63,25 @@ namespace ModernKeePassLib.Cryptography.Cipher /// Stream, from which the decrypted data can be read. Stream DecryptStream(Stream sEncrypted, byte[] pbKey, byte[] pbIV); } + + public interface ICipherEngine2 : ICipherEngine + { + /// + /// Length of an encryption key in bytes. + /// The base ICipherEngine assumes 32. + /// + int KeyLength + { + get; + } + + /// + /// Length of the initialization vector in bytes. + /// The base ICipherEngine assumes 16. + /// + int IVLength + { + get; + } + } } diff --git a/ModernKeePassLib/Cryptography/Cipher/Salsa20Cipher.cs b/ModernKeePassLib/Cryptography/Cipher/Salsa20Cipher.cs index c841189..dc035d8 100644 --- a/ModernKeePassLib/Cryptography/Cipher/Salsa20Cipher.cs +++ b/ModernKeePassLib/Cryptography/Cipher/Salsa20Cipher.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,182 +17,148 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -// Implementation of the Salsa20 cipher, based on the eSTREAM submission. +// Implementation of the Salsa20 cipher, based on the eSTREAM +// submission by D. J. Bernstein. using System; +using System.Collections.Generic; using System.Diagnostics; using ModernKeePassLib.Utility; namespace ModernKeePassLib.Cryptography.Cipher { - public sealed class Salsa20Cipher : IDisposable + public sealed class Salsa20Cipher : CtrBlockCipher { - private uint[] m_state = new uint[16]; + private uint[] m_s = new uint[16]; // State private uint[] m_x = new uint[16]; // Working buffer - private byte[] m_output = new byte[64]; - private int m_outputPos = 64; - - private static readonly uint[] m_sigma = new uint[4] { + private static readonly uint[] g_sigma = new uint[4] { 0x61707865, 0x3320646E, 0x79622D32, 0x6B206574 }; - public Salsa20Cipher(byte[] pbKey32, byte[] pbIV8) + public override int BlockSize { - KeySetup(pbKey32); - IvSetup(pbIV8); + get { return 64; } } - ~Salsa20Cipher() + public Salsa20Cipher(byte[] pbKey32, byte[] pbIV8) : base() { - Dispose(false); + if(pbKey32 == null) throw new ArgumentNullException("pbKey32"); + if(pbKey32.Length != 32) throw new ArgumentOutOfRangeException("pbKey32"); + if(pbIV8 == null) throw new ArgumentNullException("pbIV8"); + if(pbIV8.Length != 8) throw new ArgumentOutOfRangeException("pbIV8"); + + // Key setup + m_s[1] = MemUtil.BytesToUInt32(pbKey32, 0); + m_s[2] = MemUtil.BytesToUInt32(pbKey32, 4); + m_s[3] = MemUtil.BytesToUInt32(pbKey32, 8); + m_s[4] = MemUtil.BytesToUInt32(pbKey32, 12); + m_s[11] = MemUtil.BytesToUInt32(pbKey32, 16); + m_s[12] = MemUtil.BytesToUInt32(pbKey32, 20); + m_s[13] = MemUtil.BytesToUInt32(pbKey32, 24); + m_s[14] = MemUtil.BytesToUInt32(pbKey32, 28); + m_s[0] = g_sigma[0]; + m_s[5] = g_sigma[1]; + m_s[10] = g_sigma[2]; + m_s[15] = g_sigma[3]; + + // IV setup + m_s[6] = MemUtil.BytesToUInt32(pbIV8, 0); + m_s[7] = MemUtil.BytesToUInt32(pbIV8, 4); + m_s[8] = 0; // Counter, low + m_s[9] = 0; // Counter, high } - public void Dispose() + protected override void Dispose(bool bDisposing) { - Dispose(true); - GC.SuppressFinalize(this); + if(bDisposing) + { + MemUtil.ZeroArray(m_s); + MemUtil.ZeroArray(m_x); + } + + base.Dispose(bDisposing); } - private void Dispose(bool bDisposing) + protected override void NextBlock(byte[] pBlock) { - // Clear sensitive data - Array.Clear(m_state, 0, m_state.Length); - Array.Clear(m_x, 0, m_x.Length); - } + if(pBlock == null) throw new ArgumentNullException("pBlock"); + if(pBlock.Length != 64) throw new ArgumentOutOfRangeException("pBlock"); - private void NextOutput() - { - uint[] x = m_x; // Local alias for working buffer - - // Compiler/runtime might remove array bound checks after this + // x is a local alias for the working buffer; with this, + // the compiler/runtime might remove some checks + uint[] x = m_x; + if(x == null) throw new InvalidOperationException(); if(x.Length < 16) throw new InvalidOperationException(); - Array.Copy(m_state, x, 16); + uint[] s = m_s; + if(s == null) throw new InvalidOperationException(); + if(s.Length < 16) throw new InvalidOperationException(); + + Array.Copy(s, x, 16); unchecked { - for(int i = 0; i < 10; ++i) // (int i = 20; i > 0; i -= 2) + // 10 * 8 quarter rounds = 20 rounds + for(int i = 0; i < 10; ++i) { - x[ 4] ^= Rotl32(x[ 0] + x[12], 7); - x[ 8] ^= Rotl32(x[ 4] + x[ 0], 9); - x[12] ^= Rotl32(x[ 8] + x[ 4], 13); - x[ 0] ^= Rotl32(x[12] + x[ 8], 18); - x[ 9] ^= Rotl32(x[ 5] + x[ 1], 7); - x[13] ^= Rotl32(x[ 9] + x[ 5], 9); - x[ 1] ^= Rotl32(x[13] + x[ 9], 13); - x[ 5] ^= Rotl32(x[ 1] + x[13], 18); - x[14] ^= Rotl32(x[10] + x[ 6], 7); - x[ 2] ^= Rotl32(x[14] + x[10], 9); - x[ 6] ^= Rotl32(x[ 2] + x[14], 13); - x[10] ^= Rotl32(x[ 6] + x[ 2], 18); - x[ 3] ^= Rotl32(x[15] + x[11], 7); - x[ 7] ^= Rotl32(x[ 3] + x[15], 9); - x[11] ^= Rotl32(x[ 7] + x[ 3], 13); - x[15] ^= Rotl32(x[11] + x[ 7], 18); - x[ 1] ^= Rotl32(x[ 0] + x[ 3], 7); - x[ 2] ^= Rotl32(x[ 1] + x[ 0], 9); - x[ 3] ^= Rotl32(x[ 2] + x[ 1], 13); - x[ 0] ^= Rotl32(x[ 3] + x[ 2], 18); - x[ 6] ^= Rotl32(x[ 5] + x[ 4], 7); - x[ 7] ^= Rotl32(x[ 6] + x[ 5], 9); - x[ 4] ^= Rotl32(x[ 7] + x[ 6], 13); - x[ 5] ^= Rotl32(x[ 4] + x[ 7], 18); - x[11] ^= Rotl32(x[10] + x[ 9], 7); - x[ 8] ^= Rotl32(x[11] + x[10], 9); - x[ 9] ^= Rotl32(x[ 8] + x[11], 13); - x[10] ^= Rotl32(x[ 9] + x[ 8], 18); - x[12] ^= Rotl32(x[15] + x[14], 7); - x[13] ^= Rotl32(x[12] + x[15], 9); - x[14] ^= Rotl32(x[13] + x[12], 13); - x[15] ^= Rotl32(x[14] + x[13], 18); + x[ 4] ^= MemUtil.RotateLeft32(x[ 0] + x[12], 7); + x[ 8] ^= MemUtil.RotateLeft32(x[ 4] + x[ 0], 9); + x[12] ^= MemUtil.RotateLeft32(x[ 8] + x[ 4], 13); + x[ 0] ^= MemUtil.RotateLeft32(x[12] + x[ 8], 18); + + x[ 9] ^= MemUtil.RotateLeft32(x[ 5] + x[ 1], 7); + x[13] ^= MemUtil.RotateLeft32(x[ 9] + x[ 5], 9); + x[ 1] ^= MemUtil.RotateLeft32(x[13] + x[ 9], 13); + x[ 5] ^= MemUtil.RotateLeft32(x[ 1] + x[13], 18); + + x[14] ^= MemUtil.RotateLeft32(x[10] + x[ 6], 7); + x[ 2] ^= MemUtil.RotateLeft32(x[14] + x[10], 9); + x[ 6] ^= MemUtil.RotateLeft32(x[ 2] + x[14], 13); + x[10] ^= MemUtil.RotateLeft32(x[ 6] + x[ 2], 18); + + x[ 3] ^= MemUtil.RotateLeft32(x[15] + x[11], 7); + x[ 7] ^= MemUtil.RotateLeft32(x[ 3] + x[15], 9); + x[11] ^= MemUtil.RotateLeft32(x[ 7] + x[ 3], 13); + x[15] ^= MemUtil.RotateLeft32(x[11] + x[ 7], 18); + + x[ 1] ^= MemUtil.RotateLeft32(x[ 0] + x[ 3], 7); + x[ 2] ^= MemUtil.RotateLeft32(x[ 1] + x[ 0], 9); + x[ 3] ^= MemUtil.RotateLeft32(x[ 2] + x[ 1], 13); + x[ 0] ^= MemUtil.RotateLeft32(x[ 3] + x[ 2], 18); + + x[ 6] ^= MemUtil.RotateLeft32(x[ 5] + x[ 4], 7); + x[ 7] ^= MemUtil.RotateLeft32(x[ 6] + x[ 5], 9); + x[ 4] ^= MemUtil.RotateLeft32(x[ 7] + x[ 6], 13); + x[ 5] ^= MemUtil.RotateLeft32(x[ 4] + x[ 7], 18); + + x[11] ^= MemUtil.RotateLeft32(x[10] + x[ 9], 7); + x[ 8] ^= MemUtil.RotateLeft32(x[11] + x[10], 9); + x[ 9] ^= MemUtil.RotateLeft32(x[ 8] + x[11], 13); + x[10] ^= MemUtil.RotateLeft32(x[ 9] + x[ 8], 18); + + x[12] ^= MemUtil.RotateLeft32(x[15] + x[14], 7); + x[13] ^= MemUtil.RotateLeft32(x[12] + x[15], 9); + x[14] ^= MemUtil.RotateLeft32(x[13] + x[12], 13); + x[15] ^= MemUtil.RotateLeft32(x[14] + x[13], 18); } - for(int i = 0; i < 16; ++i) - x[i] += m_state[i]; + for(int i = 0; i < 16; ++i) x[i] += s[i]; for(int i = 0; i < 16; ++i) { - m_output[i << 2] = (byte)x[i]; - m_output[(i << 2) + 1] = (byte)(x[i] >> 8); - m_output[(i << 2) + 2] = (byte)(x[i] >> 16); - m_output[(i << 2) + 3] = (byte)(x[i] >> 24); + int i4 = i << 2; + uint xi = x[i]; + + pBlock[i4] = (byte)xi; + pBlock[i4 + 1] = (byte)(xi >> 8); + pBlock[i4 + 2] = (byte)(xi >> 16); + pBlock[i4 + 3] = (byte)(xi >> 24); } - m_outputPos = 0; - ++m_state[8]; - if(m_state[8] == 0) ++m_state[9]; - } - } - - private static uint Rotl32(uint x, int b) - { - unchecked - { - return ((x << b) | (x >> (32 - b))); - } - } - - private static uint U8To32Little(byte[] pb, int iOffset) - { - unchecked - { - return ((uint)pb[iOffset] | ((uint)pb[iOffset + 1] << 8) | - ((uint)pb[iOffset + 2] << 16) | ((uint)pb[iOffset + 3] << 24)); - } - } - - private void KeySetup(byte[] k) - { - if(k == null) throw new ArgumentNullException("k"); - if(k.Length != 32) throw new ArgumentException(); - - m_state[1] = U8To32Little(k, 0); - m_state[2] = U8To32Little(k, 4); - m_state[3] = U8To32Little(k, 8); - m_state[4] = U8To32Little(k, 12); - m_state[11] = U8To32Little(k, 16); - m_state[12] = U8To32Little(k, 20); - m_state[13] = U8To32Little(k, 24); - m_state[14] = U8To32Little(k, 28); - m_state[0] = m_sigma[0]; - m_state[5] = m_sigma[1]; - m_state[10] = m_sigma[2]; - m_state[15] = m_sigma[3]; - } - - private void IvSetup(byte[] pbIV) - { - if(pbIV == null) throw new ArgumentNullException("pbIV"); - if(pbIV.Length != 8) throw new ArgumentException(); - - m_state[6] = U8To32Little(pbIV, 0); - m_state[7] = U8To32Little(pbIV, 4); - m_state[8] = 0; - m_state[9] = 0; - } - - public void Encrypt(byte[] m, int nByteCount, bool bXor) - { - if(m == null) throw new ArgumentNullException("m"); - if(nByteCount > m.Length) throw new ArgumentException(); - - int nBytesRem = nByteCount, nOffset = 0; - while(nBytesRem > 0) - { - Debug.Assert((m_outputPos >= 0) && (m_outputPos <= 64)); - if(m_outputPos == 64) NextOutput(); - Debug.Assert(m_outputPos < 64); - - int nCopy = Math.Min(64 - m_outputPos, nBytesRem); - - if(bXor) MemUtil.XorArray(m_output, m_outputPos, m, nOffset, nCopy); - else Array.Copy(m_output, m_outputPos, m, nOffset, nCopy); - - m_outputPos += nCopy; - nBytesRem -= nCopy; - nOffset += nCopy; + ++s[8]; + if(s[8] == 0) ++s[9]; } } } diff --git a/ModernKeePassLib/Cryptography/Cipher/StandardAesEngine.cs b/ModernKeePassLib/Cryptography/Cipher/StandardAesEngine.cs index 42b27ba..ad635b2 100644 --- a/ModernKeePassLib/Cryptography/Cipher/StandardAesEngine.cs +++ b/ModernKeePassLib/Cryptography/Cipher/StandardAesEngine.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -39,12 +39,7 @@ namespace ModernKeePassLib.Cryptography.Cipher /// public sealed class StandardAesEngine : ICipherEngine { -#if !ModernKeePassLib && !KeePassRT - private const CipherMode m_rCipherMode = CipherMode.CBC; - private const PaddingMode m_rCipherPadding = PaddingMode.PKCS7; -#endif - - private static PwUuid m_uuidAes = null; + private static PwUuid g_uuidAes = null; /// /// UUID of the cipher engine. This ID uniquely identifies the @@ -54,26 +49,38 @@ namespace ModernKeePassLib.Cryptography.Cipher { get { - if(m_uuidAes == null) + PwUuid pu = g_uuidAes; + if(pu == null) { - m_uuidAes = new PwUuid(new byte[]{ + pu = new PwUuid(new byte[] { 0x31, 0xC1, 0xF2, 0xE6, 0xBF, 0x71, 0x43, 0x50, 0xBE, 0x58, 0x05, 0x21, 0x6A, 0xFC, 0x5A, 0xFF }); + g_uuidAes = pu; } - return m_uuidAes; + return pu; } } /// /// Get the UUID of this cipher engine as PwUuid object. /// - public PwUuid CipherUuid => StandardAesEngine.AesUuid; + public PwUuid CipherUuid + { + get { return StandardAesEngine.AesUuid; } + } - /// + /// /// Get a displayable name describing this cipher engine. /// - public string DisplayName { get { return KLRes.EncAlgorithmAes; } } + public string DisplayName + { + get + { + return ("AES/Rijndael (" + KLRes.KeyBits.Replace(@"{PARAM}", + "256") + ", FIPS 197)"); + } + } private static void ValidateArguments(Stream stream, bool bEncrypt, byte[] pbKey, byte[] pbIV) { @@ -90,90 +97,24 @@ namespace ModernKeePassLib.Cryptography.Cipher if(bEncrypt) { Debug.Assert(stream.CanWrite); - if(stream.CanWrite == false) throw new ArgumentException("Stream must be writable!"); + if(!stream.CanWrite) throw new ArgumentException("Stream must be writable!"); } else // Decrypt { Debug.Assert(stream.CanRead); - if(stream.CanRead == false) throw new ArgumentException("Encrypted stream must be readable!"); + if(!stream.CanRead) throw new ArgumentException("Encrypted stream must be readable!"); } } private static Stream CreateStream(Stream s, bool bEncrypt, byte[] pbKey, byte[] pbIV) { - ValidateArguments(s, bEncrypt, pbKey, pbIV); + StandardAesEngine.ValidateArguments(s, bEncrypt, pbKey, pbIV); byte[] pbLocalIV = new byte[16]; Array.Copy(pbIV, pbLocalIV, 16); byte[] pbLocalKey = new byte[32]; Array.Copy(pbKey, pbLocalKey, 32); -#if !ModernKeePassLib -//#if ModernKeePassLib - /*var provider = WinRTCrypto.SymmetricKeyAlgorithmProvider. - OpenAlgorithm(SymmetricAlgorithm.AesCbcPkcs7); - var key = provider.CreateSymmetricKey(pbLocalKey); - if (bEncrypt) - { - var encryptor = WinRTCrypto.CryptographicEngine.CreateEncryptor( - key, pbLocalIV); - return new CryptoStream(s, encryptor, CryptoStreamMode.Write); - } else - { - var decryptor = WinRTCrypto.CryptographicEngine.CreateDecryptor( - key, pbLocalIV); - return new CryptoStream(s, decryptor, CryptoStreamMode.Read); - } - */ - - var provider = SymmetricKeyAlgorithmProvider. - OpenAlgorithm(SymmetricAlgorithmNames.AesCbcPkcs7); - var key = provider.CreateSymmetricKey(CryptographicBuffer.CreateFromByteArray(pbLocalKey)); - - using (var ms = new MemoryStream()) - { - s.CopyTo(ms); - var data = CryptographicBuffer.CreateFromByteArray(ms.ToArray()); - byte[] resultByteArray; - if (bEncrypt) - { - var encrypted = CryptographicEngine.Encrypt(key, data, CryptographicBuffer.CreateFromByteArray(pbLocalIV)); - CryptographicBuffer.CopyToByteArray(encrypted, out resultByteArray); - return new MemoryStream(resultByteArray); - } - else - { - var decrypted = CryptographicEngine.Decrypt(key, data, CryptographicBuffer.CreateFromByteArray(pbLocalIV)); - CryptographicBuffer.CopyToByteArray(decrypted, out resultByteArray); - return new MemoryStream(resultByteArray, true); - } - } - -//#else - -//#if !KeePassRT -//#if !ModernKeePassLib - RijndaelManaged r = new RijndaelManaged(); - if(r.BlockSize != 128) // AES block size - { - Debug.Assert(false); - r.BlockSize = 128; - } - - r.IV = pbLocalIV; - r.KeySize = 256; - r.Key = pbLocalKey; - r.Mode = m_rCipherMode; - r.Padding = m_rCipherPadding; - - ICryptoTransform iTransform = (bEncrypt ? r.CreateEncryptor() : r.CreateDecryptor()); - Debug.Assert(iTransform != null); - if(iTransform == null) throw new SecurityException("Unable to create Rijndael transform!"); - - return new CryptoStream(s, iTransform, bEncrypt ? CryptoStreamMode.Write : - CryptoStreamMode.Read); -#else - AesEngine aes = new AesEngine(); CbcBlockCipher cbc = new CbcBlockCipher(aes); PaddedBufferedBlockCipher bc = new PaddedBufferedBlockCipher(cbc, @@ -185,19 +126,16 @@ namespace ModernKeePassLib.Cryptography.Cipher IBufferedCipher cpRead = (bEncrypt ? null : bc); IBufferedCipher cpWrite = (bEncrypt ? bc : null); return new CipherStream(s, cpRead, cpWrite); -#endif - -//#endif } public Stream EncryptStream(Stream sPlainText, byte[] pbKey, byte[] pbIV) { - return CreateStream(sPlainText, true, pbKey, pbIV); + return StandardAesEngine.CreateStream(sPlainText, true, pbKey, pbIV); } public Stream DecryptStream(Stream sEncrypted, byte[] pbKey, byte[] pbIV) { - return CreateStream(sEncrypted, false, pbKey, pbIV); + return StandardAesEngine.CreateStream(sEncrypted, false, pbKey, pbIV); } } } diff --git a/ModernKeePassLib/Cryptography/CryptoRandom.cs b/ModernKeePassLib/Cryptography/CryptoRandom.cs index 6cdd4ab..3eec91a 100644 --- a/ModernKeePassLib/Cryptography/CryptoRandom.cs +++ b/ModernKeePassLib/Cryptography/CryptoRandom.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,6 +18,7 @@ */ using System; +using System.Collections; #if ModernKeePassLib using Windows.Security.Cryptography; using ModernKeePassLib.Utility; @@ -27,37 +28,43 @@ using System.Security.Cryptography; #endif using System.IO; using System.Diagnostics; +using System.Globalization; +using ModernKeePassLib.Native; namespace ModernKeePassLib.Cryptography { /// - /// Cryptographically strong random number generator. The returned values - /// are unpredictable and cannot be reproduced. + /// Cryptographically secure pseudo-random number generator. + /// The returned values are unpredictable and cannot be reproduced. /// CryptoRandom is a singleton class. /// public sealed class CryptoRandom { private byte[] m_pbEntropyPool = new byte[64]; - private uint m_uCounter; -#if ModernKeePassLib - //private IRandomNumberGenerator m_rng = NetFxCrypto.RandomNumberGenerator; -#else - private RNGCryptoServiceProvider m_rng = new RNGCryptoServiceProvider(); -#endif + private ulong m_uCounter; private ulong m_uGeneratedBytesCount = 0; - private object m_oSyncRoot = new object(); + private static readonly object g_oSyncRoot = new object(); + private readonly object m_oSyncRoot = new object(); - private static CryptoRandom m_pInstance = null; + private static CryptoRandom g_pInstance = null; public static CryptoRandom Instance { get { - if(m_pInstance != null) return m_pInstance; + CryptoRandom cr; + lock(g_oSyncRoot) + { + cr = g_pInstance; + if(cr == null) + { + cr = new CryptoRandom(); + g_pInstance = cr; + } + } - m_pInstance = new CryptoRandom(); - return m_pInstance; + return cr; } } @@ -84,10 +91,13 @@ namespace ModernKeePassLib.Cryptography private CryptoRandom() { - Random r = new Random(); - m_uCounter = (uint)r.Next(); + // Random rWeak = new Random(); // Based on tick count + // byte[] pb = new byte[8]; + // rWeak.NextBytes(pb); + // m_uCounter = MemUtil.BytesToUInt64(pb); + m_uCounter = (ulong)DateTime.UtcNow.ToBinary(); - AddEntropy(GetSystemData(r)); + AddEntropy(GetSystemData()); AddEntropy(GetCspData()); } @@ -103,64 +113,64 @@ namespace ModernKeePassLib.Cryptography if(pbEntropy.Length == 0) { Debug.Assert(false); return; } byte[] pbNewData = pbEntropy; - if(pbEntropy.Length >= 64) + if(pbEntropy.Length > 64) { #if ModernKeePassLib - /*var shaNew = WinRTCrypto.HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha512); - pbNewData = shaNew.HashData(pbEntropy);*/ + var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256); var buffer = sha256.HashData(CryptographicBuffer.CreateFromByteArray(pbEntropy)); CryptographicBuffer.CopyToByteArray(buffer, out pbNewData); #else - -#if !KeePassLibSD - SHA512Managed shaNew = new SHA512Managed(); +#if KeePassLibSD + using(SHA256Managed shaNew = new SHA256Managed()) #else - SHA256Managed shaNew = new SHA256Managed(); + using(SHA512Managed shaNew = new SHA512Managed()) #endif - pbNewData = shaNew.ComputeHash(pbEntropy); - + { + pbNewData = shaNew.ComputeHash(pbEntropy); + } #endif - } + } - MemoryStream ms = new MemoryStream(); lock(m_oSyncRoot) { - ms.Write(m_pbEntropyPool, 0, m_pbEntropyPool.Length); - ms.Write(pbNewData, 0, pbNewData.Length); + int cbPool = m_pbEntropyPool.Length; + int cbNew = pbNewData.Length; + + byte[] pbCmp = new byte[cbPool + cbNew]; + Array.Copy(m_pbEntropyPool, pbCmp, cbPool); + Array.Copy(pbNewData, 0, pbCmp, cbPool, cbNew); + + MemUtil.ZeroByteArray(m_pbEntropyPool); - byte[] pbFinal = ms.ToArray(); #if ModernKeePassLib - /*var shaPool = WinRTCrypto.HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha512); - m_pbEntropyPool = shaPool.HashData(pbFinal);*/ var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256); - var buffer = sha256.HashData(CryptographicBuffer.CreateFromByteArray(pbFinal)); + var buffer = sha256.HashData(CryptographicBuffer.CreateFromByteArray(pbCmp)); CryptographicBuffer.CopyToByteArray(buffer, out m_pbEntropyPool); #else - -#if !KeePassLibSD - Debug.Assert(pbFinal.Length == (64 + pbNewData.Length)); - SHA512Managed shaPool = new SHA512Managed(); +#if KeePassLibSD + using(SHA256Managed shaPool = new SHA256Managed()) #else - SHA256Managed shaPool = new SHA256Managed(); + using(SHA512Managed shaPool = new SHA512Managed()) #endif - m_pbEntropyPool = shaPool.ComputeHash(pbFinal); - + { + m_pbEntropyPool = shaPool.ComputeHash(pbCmp); + } #endif - } - ms.Dispose(); + MemUtil.ZeroByteArray(pbCmp); + } } - private static byte[] GetSystemData(Random rWeak) + private static byte[] GetSystemData() { MemoryStream ms = new MemoryStream(); byte[] pb; - pb = MemUtil.UInt32ToBytes((uint)Environment.TickCount); - ms.Write(pb, 0, pb.Length); + pb = MemUtil.Int32ToBytes(Environment.TickCount); + MemUtil.Write(ms, pb); - pb = TimeUtil.PackTime(DateTime.Now); - ms.Write(pb, 0, pb.Length); + pb = MemUtil.Int64ToBytes(DateTime.UtcNow.ToBinary()); + MemUtil.Write(ms, pb); #if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT) // In try-catch for systems without GUI; @@ -168,95 +178,138 @@ namespace ModernKeePassLib.Cryptography try { Point pt = Cursor.Position; - pb = MemUtil.UInt32ToBytes((uint)pt.X); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt32ToBytes((uint)pt.Y); - ms.Write(pb, 0, pb.Length); + pb = MemUtil.Int32ToBytes(pt.X); + MemUtil.Write(ms, pb); + pb = MemUtil.Int32ToBytes(pt.Y); + MemUtil.Write(ms, pb); } catch(Exception) { } #endif - - pb = MemUtil.UInt32ToBytes((uint)rWeak.Next()); - ms.Write(pb, 0, pb.Length); - #if ModernKeePassLib - pb = MemUtil.UInt32ToBytes((uint)Environment.ProcessorCount); - ms.Write(pb, 0, pb.Length); + pb = MemUtil.UInt32ToBytes((uint)Environment.ProcessorCount); + ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt32ToBytes((uint)Environment.CurrentManagedThreadId); - ms.Write(pb, 0, pb.Length); + pb = MemUtil.UInt32ToBytes((uint)Environment.CurrentManagedThreadId); + ms.Write(pb, 0, pb.Length); #else - pb = MemUtil.UInt32ToBytes((uint)NativeLib.GetPlatformID()); - ms.Write(pb, 0, pb.Length); -#endif + pb = MemUtil.UInt32ToBytes((uint)NativeLib.GetPlatformID()); + MemUtil.Write(ms, pb); -#if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT) + try + { +#if KeePassUAP + string strOS = EnvironmentExt.OSVersion.VersionString; +#else + string strOS = Environment.OSVersion.VersionString; +#endif + AddStrHash(ms, strOS); + + pb = MemUtil.Int32ToBytes(Environment.ProcessorCount); + MemUtil.Write(ms, pb); + +#if !KeePassUAP + AddStrHash(ms, Environment.CommandLine); + + pb = MemUtil.Int64ToBytes(Environment.WorkingSet); + MemUtil.Write(ms, pb); +#endif + } + catch(Exception) { Debug.Assert(false); } + + try + { + foreach(DictionaryEntry de in Environment.GetEnvironmentVariables()) + { + AddStrHash(ms, (de.Key as string)); + AddStrHash(ms, (de.Value as string)); + } + } + catch(Exception) { Debug.Assert(false); } + +#if KeePassUAP + pb = DiagnosticsExt.GetProcessEntropy(); + MemUtil.Write(ms, pb); +#elif !KeePassLibSD Process p = null; try { - pb = MemUtil.UInt32ToBytes((uint)Environment.ProcessorCount); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)Environment.WorkingSet); - ms.Write(pb, 0, pb.Length); - - Version v = Environment.OSVersion.Version; - pb = MemUtil.UInt32ToBytes((uint)v.GetHashCode()); - ms.Write(pb, 0, pb.Length); - p = Process.GetCurrentProcess(); - pb = MemUtil.UInt64ToBytes((ulong)p.Handle.ToInt64()); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt32ToBytes((uint)p.HandleCount); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt32ToBytes((uint)p.Id); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.NonpagedSystemMemorySize64); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.PagedMemorySize64); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.PagedSystemMemorySize64); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.PeakPagedMemorySize64); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.PeakVirtualMemorySize64); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.PeakWorkingSet64); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.PrivateMemorySize64); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.StartTime.ToBinary()); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.VirtualMemorySize64); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.WorkingSet64); - ms.Write(pb, 0, pb.Length); + pb = MemUtil.Int64ToBytes(p.Handle.ToInt64()); + MemUtil.Write(ms, pb); + pb = MemUtil.Int32ToBytes(p.HandleCount); + MemUtil.Write(ms, pb); + pb = MemUtil.Int32ToBytes(p.Id); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.NonpagedSystemMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PagedMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PagedSystemMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PeakPagedMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PeakVirtualMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PeakWorkingSet64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PrivateMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.StartTime.ToBinary()); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.VirtualMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.WorkingSet64); + MemUtil.Write(ms, pb); // Not supported in Mono 1.2.6: // pb = MemUtil.UInt32ToBytes((uint)p.SessionId); - // ms.Write(pb, 0, pb.Length); + // MemUtil.Write(ms, pb); } - catch(Exception) { } + catch(Exception) { Debug.Assert(NativeLib.IsUnix()); } finally { try { if(p != null) p.Dispose(); } catch(Exception) { Debug.Assert(false); } } #endif +#endif + + try + { + CultureInfo ci = CultureInfo.CurrentCulture; + if(ci != null) + { + pb = MemUtil.Int32ToBytes(ci.GetHashCode()); + MemUtil.Write(ms, pb); + } + else { Debug.Assert(false); } + } + catch(Exception) { Debug.Assert(false); } pb = Guid.NewGuid().ToByteArray(); - ms.Write(pb, 0, pb.Length); + MemUtil.Write(ms, pb); byte[] pbAll = ms.ToArray(); ms.Dispose(); return pbAll; } + private static void AddStrHash(Stream s, string str) + { + if(s == null) { Debug.Assert(false); return; } + if(string.IsNullOrEmpty(str)) return; + + byte[] pbUtf8 = StrUtil.Utf8.GetBytes(str); + byte[] pbHash = CryptoUtil.HashSha256(pbUtf8); + MemUtil.Write(s, pbHash); + } + private byte[] GetCspData() { byte[] pbCspRandom = new byte[32]; //m_rng.GetBytes(pbCspRandom); - CryptographicBuffer.CopyToByteArray(CryptographicBuffer.GenerateRandom(32), out pbCspRandom); + CryptographicBuffer.CopyToByteArray(CryptographicBuffer.GenerateRandom(32), out pbCspRandom); return pbCspRandom; } @@ -265,39 +318,32 @@ namespace ModernKeePassLib.Cryptography if(this.GenerateRandom256Pre != null) this.GenerateRandom256Pre(this, EventArgs.Empty); - byte[] pbFinal; + byte[] pbCmp; lock(m_oSyncRoot) { - unchecked { m_uCounter += 386047; } // Prime number - byte[] pbCounter = MemUtil.UInt32ToBytes(m_uCounter); + m_uCounter += 0x74D8B29E4D38E161UL; // Prime number + byte[] pbCounter = MemUtil.UInt64ToBytes(m_uCounter); byte[] pbCspRandom = GetCspData(); - MemoryStream ms = new MemoryStream(); - ms.Write(m_pbEntropyPool, 0, m_pbEntropyPool.Length); - ms.Write(pbCounter, 0, pbCounter.Length); - ms.Write(pbCspRandom, 0, pbCspRandom.Length); - pbFinal = ms.ToArray(); - Debug.Assert(pbFinal.Length == (m_pbEntropyPool.Length + - pbCounter.Length + pbCspRandom.Length)); - ms.Dispose(); + int cbPool = m_pbEntropyPool.Length; + int cbCtr = pbCounter.Length; + int cbCsp = pbCspRandom.Length; + + pbCmp = new byte[cbPool + cbCtr + cbCsp]; + Array.Copy(m_pbEntropyPool, pbCmp, cbPool); + Array.Copy(pbCounter, 0, pbCmp, cbPool, cbCtr); + Array.Copy(pbCspRandom, 0, pbCmp, cbPool + cbCtr, cbCsp); + + MemUtil.ZeroByteArray(pbCspRandom); m_uGeneratedBytesCount += 32; } -#if ModernKeePassLib - /*var sha256 = WinRTCrypto.HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha256); - return sha256.HashData(pbFinal);*/ - var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256); - var buffer = sha256.HashData(CryptographicBuffer.CreateFromByteArray(pbFinal)); - byte[] result; - CryptographicBuffer.CopyToByteArray(buffer, out result); - return result; -#else - SHA256Managed sha256 = new SHA256Managed(); - return sha256.ComputeHash(pbFinal); -#endif - } + byte[] pbRet = CryptoUtil.HashSha256(pbCmp); + MemUtil.ZeroByteArray(pbCmp); + return pbRet; + } /// /// Get a number of cryptographically strong random bytes. @@ -308,30 +354,54 @@ namespace ModernKeePassLib.Cryptography /// random bytes. public byte[] GetRandomBytes(uint uRequestedBytes) { - if(uRequestedBytes == 0) return new byte[0]; // Allow zero-length array + if(uRequestedBytes == 0) return MemUtil.EmptyByteArray; + if(uRequestedBytes > (uint)int.MaxValue) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("uRequestedBytes"); + } - byte[] pbRes = new byte[uRequestedBytes]; - long lPos = 0; + int cbRem = (int)uRequestedBytes; + byte[] pbRes = new byte[cbRem]; + int iPos = 0; - while(uRequestedBytes != 0) + while(cbRem != 0) { byte[] pbRandom256 = GenerateRandom256(); Debug.Assert(pbRandom256.Length == 32); - long lCopy = (long)((uRequestedBytes < 32) ? uRequestedBytes : 32); + int cbCopy = Math.Min(cbRem, pbRandom256.Length); + Array.Copy(pbRandom256, 0, pbRes, iPos, cbCopy); -#if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT) - Array.Copy(pbRandom256, 0, pbRes, lPos, lCopy); -#else - Array.Copy(pbRandom256, 0, pbRes, (int)lPos, (int)lCopy); -#endif + MemUtil.ZeroByteArray(pbRandom256); - lPos += lCopy; - uRequestedBytes -= (uint)lCopy; + iPos += cbCopy; + cbRem -= cbCopy; } - Debug.Assert((int)lPos == pbRes.Length); + Debug.Assert(iPos == pbRes.Length); return pbRes; } + + private static int g_iWeakSeed = 0; + public static Random NewWeakRandom() + { + long s64 = DateTime.UtcNow.ToBinary(); + int s32 = (int)((s64 >> 32) ^ s64); + + lock(g_oSyncRoot) + { + unchecked + { + g_iWeakSeed += 0x78A8C4B7; // Prime number + s32 ^= g_iWeakSeed; + } + } + + // Prevent overflow in the Random constructor of .NET 2.0 + if(s32 == int.MinValue) s32 = int.MaxValue; + + return new Random(s32); + } } } diff --git a/ModernKeePassLib/Cryptography/CryptoRandomStream.cs b/ModernKeePassLib/Cryptography/CryptoRandomStream.cs index 8a5e9d5..5a6207c 100644 --- a/ModernKeePassLib/Cryptography/CryptoRandomStream.cs +++ b/ModernKeePassLib/Cryptography/CryptoRandomStream.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,14 +19,11 @@ using System; using System.Diagnostics; -using Windows.Security.Cryptography.Core; -#if ModernKeePassLib -using Windows.Security.Cryptography; -#else -using System.Security.Cryptography; -#endif +using Windows.Security.Cryptography; +using Windows.Security.Cryptography.Core; using ModernKeePassLib.Cryptography.Cipher; +using ModernKeePassLib.Utility; namespace ModernKeePassLib.Cryptography { @@ -42,6 +39,7 @@ namespace ModernKeePassLib.Cryptography /// /// A variant of the ARCFour algorithm (RC4 incompatible). + /// Insecure; for backward compatibility only. /// ArcFourVariant = 1, @@ -50,7 +48,12 @@ namespace ModernKeePassLib.Cryptography /// Salsa20 = 2, - Count = 3 + /// + /// ChaCha20 stream cipher algorithm. + /// + ChaCha20 = 3, + + Count = 4 } /// @@ -59,47 +62,81 @@ namespace ModernKeePassLib.Cryptography /// properties, but for the same seed always the same stream /// is produced, i.e. this class can be used as stream cipher. /// - public sealed class CryptoRandomStream + public sealed class CryptoRandomStream : IDisposable { - private CrsAlgorithm m_crsAlgorithm; + private readonly CrsAlgorithm m_crsAlgorithm; + private bool m_bDisposed = false; private byte[] m_pbState = null; private byte m_i = 0; private byte m_j = 0; private Salsa20Cipher m_salsa20 = null; + private ChaCha20Cipher m_chacha20 = null; /// /// Construct a new cryptographically secure random stream object. /// - /// Algorithm to use. + /// Algorithm to use. /// Initialization key. Must not be null and /// must contain at least 1 byte. - /// Thrown if the - /// parameter is null. - /// Thrown if the - /// parameter contains no bytes or the - /// algorithm is unknown. - public CryptoRandomStream(CrsAlgorithm genAlgorithm, byte[] pbKey) + public CryptoRandomStream(CrsAlgorithm a, byte[] pbKey) { - m_crsAlgorithm = genAlgorithm; + if(pbKey == null) { Debug.Assert(false); throw new ArgumentNullException("pbKey"); } - Debug.Assert(pbKey != null); if(pbKey == null) throw new ArgumentNullException("pbKey"); + int cbKey = pbKey.Length; + if(cbKey <= 0) + { + Debug.Assert(false); // Need at least one byte + throw new ArgumentOutOfRangeException("pbKey"); + } - uint uKeyLen = (uint)pbKey.Length; - Debug.Assert(uKeyLen != 0); if(uKeyLen == 0) throw new ArgumentException(); + m_crsAlgorithm = a; - if(genAlgorithm == CrsAlgorithm.ArcFourVariant) + if(a == CrsAlgorithm.ChaCha20) + { + byte[] pbKey32 = new byte[32]; + byte[] pbIV12 = new byte[12]; +#if ModernKeePassLib + var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256); + var buffer = sha256.HashData(CryptographicBuffer.CreateFromByteArray(pbKey)); + byte[] pbHash; + CryptographicBuffer.CopyToByteArray(buffer, out pbHash); + + Array.Copy(pbHash, pbKey32, 32); + Array.Copy(pbHash, 32, pbIV12, 0, 12); + MemUtil.ZeroByteArray(pbHash); +#else + using(SHA512Managed h = new SHA512Managed()) + { + byte[] pbHash = h.ComputeHash(pbKey); + Array.Copy(pbHash, pbKey32, 32); + Array.Copy(pbHash, 32, pbIV12, 0, 12); + MemUtil.ZeroByteArray(pbHash); + } +#endif + + m_chacha20 = new ChaCha20Cipher(pbKey32, pbIV12, true); + } + else if(a == CrsAlgorithm.Salsa20) + { + byte[] pbKey32 = CryptoUtil.HashSha256(pbKey); + byte[] pbIV8 = new byte[8] { 0xE8, 0x30, 0x09, 0x4B, + 0x97, 0x20, 0x5D, 0x2A }; // Unique constant + + m_salsa20 = new Salsa20Cipher(pbKey32, pbIV8); + } + else if(a == CrsAlgorithm.ArcFourVariant) { // Fill the state linearly m_pbState = new byte[256]; - for(uint w = 0; w < 256; ++w) m_pbState[w] = (byte)w; + for(int w = 0; w < 256; ++w) m_pbState[w] = (byte)w; unchecked { byte j = 0, t; - uint inxKey = 0; - for(uint w = 0; w < 256; ++w) // Key setup + int inxKey = 0; + for(int w = 0; w < 256; ++w) // Key setup { j += (byte)(m_pbState[w] + pbKey[inxKey]); @@ -108,34 +145,42 @@ namespace ModernKeePassLib.Cryptography m_pbState[j] = t; ++inxKey; - if(inxKey >= uKeyLen) inxKey = 0; + if(inxKey >= cbKey) inxKey = 0; } } GetRandomBytes(512); // Increases security, see cryptanalysis } - else if(genAlgorithm == CrsAlgorithm.Salsa20) - { -#if ModernKeePassLib - /*var sha256 = WinRTCrypto.HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha256); - var pbKey32 = sha256.HashData(pbKey);*/ - var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256); - var buffer = sha256.HashData(CryptographicBuffer.CreateFromByteArray(pbKey)); - byte[] pbKey32; - CryptographicBuffer.CopyToByteArray(buffer, out pbKey32); -#else - SHA256Managed sha256 = new SHA256Managed(); - byte[] pbKey32 = sha256.ComputeHash(pbKey); -#endif - byte[] pbIV = new byte[8] { 0xE8, 0x30, 0x09, 0x4B, - 0x97, 0x20, 0x5D, 0x2A }; // Unique constant - - m_salsa20 = new Salsa20Cipher(pbKey32, pbIV); - } else // Unknown algorithm { Debug.Assert(false); - throw new ArgumentException(); + throw new ArgumentOutOfRangeException("a"); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if(disposing) + { + if(m_crsAlgorithm == CrsAlgorithm.ChaCha20) + m_chacha20.Dispose(); + else if(m_crsAlgorithm == CrsAlgorithm.Salsa20) + m_salsa20.Dispose(); + else if(m_crsAlgorithm == CrsAlgorithm.ArcFourVariant) + { + MemUtil.ZeroByteArray(m_pbState); + m_i = 0; + m_j = 0; + } + else { Debug.Assert(false); } + + m_bDisposed = true; } } @@ -146,15 +191,24 @@ namespace ModernKeePassLib.Cryptography /// Returns random bytes. public byte[] GetRandomBytes(uint uRequestedCount) { - if(uRequestedCount == 0) return new byte[0]; + if(m_bDisposed) throw new ObjectDisposedException(null); - byte[] pbRet = new byte[uRequestedCount]; + if(uRequestedCount == 0) return MemUtil.EmptyByteArray; + if(uRequestedCount > (uint)int.MaxValue) + throw new ArgumentOutOfRangeException("uRequestedCount"); + int cb = (int)uRequestedCount; - if(m_crsAlgorithm == CrsAlgorithm.ArcFourVariant) + byte[] pbRet = new byte[cb]; + + if(m_crsAlgorithm == CrsAlgorithm.ChaCha20) + m_chacha20.Encrypt(pbRet, 0, cb); + else if(m_crsAlgorithm == CrsAlgorithm.Salsa20) + m_salsa20.Encrypt(pbRet, 0, cb); + else if(m_crsAlgorithm == CrsAlgorithm.ArcFourVariant) { unchecked { - for(uint w = 0; w < uRequestedCount; ++w) + for(int w = 0; w < cb; ++w) { ++m_i; m_j += m_pbState[m_i]; @@ -168,8 +222,6 @@ namespace ModernKeePassLib.Cryptography } } } - else if(m_crsAlgorithm == CrsAlgorithm.Salsa20) - m_salsa20.Encrypt(pbRet, pbRet.Length, false); else { Debug.Assert(false); } return pbRet; @@ -178,14 +230,7 @@ namespace ModernKeePassLib.Cryptography public ulong GetRandomUInt64() { byte[] pb = GetRandomBytes(8); - - unchecked - { - return ((ulong)pb[0]) | ((ulong)pb[1] << 8) | - ((ulong)pb[2] << 16) | ((ulong)pb[3] << 24) | - ((ulong)pb[4] << 32) | ((ulong)pb[5] << 40) | - ((ulong)pb[6] << 48) | ((ulong)pb[7] << 56); - } + return MemUtil.BytesToUInt64(pb); } #if CRSBENCHMARK @@ -211,8 +256,10 @@ namespace ModernKeePassLib.Cryptography int nStart = Environment.TickCount; for(int i = 0; i < nRounds; ++i) { - CryptoRandomStream c = new CryptoRandomStream(cra, pbKey); - c.GetRandomBytes((uint)nDataSize); + using(CryptoRandomStream c = new CryptoRandomStream(cra, pbKey)) + { + c.GetRandomBytes((uint)nDataSize); + } } int nEnd = Environment.TickCount; diff --git a/ModernKeePassLib/Cryptography/CryptoUtil.cs b/ModernKeePassLib/Cryptography/CryptoUtil.cs new file mode 100644 index 0000000..448965e --- /dev/null +++ b/ModernKeePassLib/Cryptography/CryptoUtil.cs @@ -0,0 +1,191 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Text; + +using Windows.Security.Cryptography; +using Windows.Security.Cryptography.Core; +using ModernKeePassLib.Native; +using ModernKeePassLib.Utility; + +namespace ModernKeePassLib.Cryptography +{ + public static class CryptoUtil + { + public static byte[] HashSha256(byte[] pbData) + { + if(pbData == null) throw new ArgumentNullException("pbData"); + + return HashSha256(pbData, 0, pbData.Length); + } + + public static byte[] HashSha256(byte[] pbData, int iOffset, int cbCount) + { + if(pbData == null) throw new ArgumentNullException("pbData"); + +#if DEBUG + byte[] pbCopy = new byte[pbData.Length]; + Array.Copy(pbData, pbCopy, pbData.Length); +#endif + + byte[] pbHash; + +#if ModernKeePassLib + var h = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256).CreateHash(); + CryptographicBuffer.CopyToByteArray(h.GetValueAndReset(), out pbHash); +#else + using(SHA256Managed h = new SHA256Managed()) + { + pbHash = h.ComputeHash(pbData, iOffset, cbCount); + } +#endif + +#if DEBUG + // Ensure the data has not been modified + Debug.Assert(MemUtil.ArraysEqual(pbData, pbCopy)); + + Debug.Assert((pbHash != null) && (pbHash.Length == 32)); + byte[] pbZero = new byte[32]; + Debug.Assert(!MemUtil.ArraysEqual(pbHash, pbZero)); +#endif + + return pbHash; + } + + /// + /// Create a cryptographic key of length + /// (in bytes) from . + /// + public static byte[] ResizeKey(byte[] pbIn, int iInOffset, + int cbIn, int cbOut) + { + if(pbIn == null) throw new ArgumentNullException("pbIn"); + if(cbOut < 0) throw new ArgumentOutOfRangeException("cbOut"); + + if(cbOut == 0) return MemUtil.EmptyByteArray; + + byte[] pbHash; + if(cbOut <= 32) pbHash = HashSha256(pbIn, iInOffset, cbIn); + else + { +#if ModernKeePassLib + var h = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha512).CreateHash(); + CryptographicBuffer.CopyToByteArray(h.GetValueAndReset(), out pbHash); +#else + using(SHA512Managed h = new SHA512Managed()) + { + pbHash = h.ComputeHash(pbIn, iInOffset, cbIn); + } +#endif + } + + if(cbOut == pbHash.Length) return pbHash; + + byte[] pbRet = new byte[cbOut]; + if(cbOut < pbHash.Length) + Array.Copy(pbHash, pbRet, cbOut); + else + { + int iPos = 0; + ulong r = 0; + while(iPos < cbOut) + { + Debug.Assert(pbHash.Length == 64); + byte[] pbR = MemUtil.UInt64ToBytes(r); +#if ModernKeePassLib + var h = MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha256).CreateHash(CryptographicBuffer.CreateFromByteArray(pbR)); + byte[] pbPart; + CryptographicBuffer.CopyToByteArray(h.GetValueAndReset(), out pbPart); + int cbCopy = Math.Min(cbOut - iPos, pbPart.Length); + Debug.Assert(cbCopy > 0); + Array.Copy(pbPart, 0, pbRet, iPos, cbCopy); + iPos += cbCopy; + ++r; + + MemUtil.ZeroByteArray(pbPart); +#else + using (HMACSHA256 h = new HMACSHA256(pbHash)) + { + byte[] pbR = MemUtil.UInt64ToBytes(r); + byte[] pbPart = h.ComputeHash(pbR); + + int cbCopy = Math.Min(cbOut - iPos, pbPart.Length); + Debug.Assert(cbCopy > 0); + + Array.Copy(pbPart, 0, pbRet, iPos, cbCopy); + iPos += cbCopy; + ++r; + + MemUtil.ZeroByteArray(pbPart); + } +#endif + } + Debug.Assert(iPos == cbOut); + } + +#if DEBUG + byte[] pbZero = new byte[pbHash.Length]; + Debug.Assert(!MemUtil.ArraysEqual(pbHash, pbZero)); +#endif + MemUtil.ZeroByteArray(pbHash); + return pbRet; + } + +#if !ModernKeePassLib + private static bool? g_obAesCsp = null; + internal static SymmetricAlgorithm CreateAes() + { + if(g_obAesCsp.HasValue) + return (g_obAesCsp.Value ? CreateAesCsp() : new RijndaelManaged()); + + SymmetricAlgorithm a = CreateAesCsp(); + g_obAesCsp = (a != null); + return (a ?? new RijndaelManaged()); + } + + private static SymmetricAlgorithm CreateAesCsp() + { + try + { + // On Windows, the CSP implementation is only minimally + // faster (and for key derivations it's not used anyway, + // as KeePass uses a native implementation based on + // CNG/BCrypt, which is much faster) + if(!NativeLib.IsUnix()) return null; + + string strFqn = Assembly.CreateQualifiedName( + "System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", + "System.Security.Cryptography.AesCryptoServiceProvider"); + + Type t = Type.GetType(strFqn); + if(t == null) return null; + + return (Activator.CreateInstance(t) as SymmetricAlgorithm); + } + catch(Exception) { Debug.Assert(false); } + + return null; + } +#endif + } +} diff --git a/ModernKeePassLib/Cryptography/Hash/Blake2b.cs b/ModernKeePassLib/Cryptography/Hash/Blake2b.cs new file mode 100644 index 0000000..ec14865 --- /dev/null +++ b/ModernKeePassLib/Cryptography/Hash/Blake2b.cs @@ -0,0 +1,280 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// This implementation is based on the official reference C +// implementation by Samuel Neves (CC0 1.0 Universal). + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +using ModernKeePassLib.Utility; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Tls; + +namespace ModernKeePassLib.Cryptography.Hash +{ + public sealed class Blake2b : IDigest + { + private const int NbRounds = 12; + private const int NbBlockBytes = 128; + private const int NbMaxOutBytes = 64; + + private static readonly ulong[] g_vIV = new ulong[8] { + 0x6A09E667F3BCC908UL, 0xBB67AE8584CAA73BUL, + 0x3C6EF372FE94F82BUL, 0xA54FF53A5F1D36F1UL, + 0x510E527FADE682D1UL, 0x9B05688C2B3E6C1FUL, + 0x1F83D9ABFB41BD6BUL, 0x5BE0CD19137E2179UL + }; + + private static readonly int[] g_vSigma = new int[NbRounds * 16] { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3, + 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4, + 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8, + 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13, + 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9, + 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11, + 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10, + 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5, + 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 + }; + + private readonly int m_cbHashLength; + + private ulong[] m_h = new ulong[8]; + private ulong[] m_t = new ulong[2]; + private ulong[] m_f = new ulong[2]; + private byte[] m_buf = new byte[NbBlockBytes]; + private int m_cbBuf = 0; + + private ulong[] m_m = new ulong[16]; + private ulong[] m_v = new ulong[16]; + + public string AlgorithmName { get; } = "Blake2b"; + public int HashSize { get; internal set; } + + public Blake2b() + { + m_cbHashLength = NbMaxOutBytes; + this.HashSize = NbMaxOutBytes * 8; // Bits + + Initialize(); + } + + public Blake2b(int cbHashLength) + { + if((cbHashLength < 0) || (cbHashLength > NbMaxOutBytes)) + throw new ArgumentOutOfRangeException("cbHashLength"); + + m_cbHashLength = cbHashLength; + this.HashSize = cbHashLength * 8; // Bits + + Initialize(); + } + + public void Initialize() + { + Debug.Assert(m_h.Length == g_vIV.Length); + Array.Copy(g_vIV, m_h, m_h.Length); + + // Fan-out = 1, depth = 1 + m_h[0] ^= 0x0000000001010000UL ^ (ulong)m_cbHashLength; + + Array.Clear(m_t, 0, m_t.Length); + Array.Clear(m_f, 0, m_f.Length); + Array.Clear(m_buf, 0, m_buf.Length); + m_cbBuf = 0; + + Array.Clear(m_m, 0, m_m.Length); + Array.Clear(m_v, 0, m_v.Length); + } + + private static void G(ulong[] v, ulong[] m, int r16, int i, + int a, int b, int c, int d) + { + int p = r16 + i; + + v[a] += v[b] + m[g_vSigma[p]]; + v[d] = MemUtil.RotateRight64(v[d] ^ v[a], 32); + v[c] += v[d]; + v[b] = MemUtil.RotateRight64(v[b] ^ v[c], 24); + v[a] += v[b] + m[g_vSigma[p + 1]]; + v[d] = MemUtil.RotateRight64(v[d] ^ v[a], 16); + v[c] += v[d]; + v[b] = MemUtil.RotateRight64(v[b] ^ v[c], 63); + } + + private void Compress(byte[] pb, int iOffset) + { + ulong[] v = m_v; + ulong[] m = m_m; + ulong[] h = m_h; + + for(int i = 0; i < 16; ++i) + m[i] = MemUtil.BytesToUInt64(pb, iOffset + (i << 3)); + + Array.Copy(h, v, 8); + v[8] = g_vIV[0]; + v[9] = g_vIV[1]; + v[10] = g_vIV[2]; + v[11] = g_vIV[3]; + v[12] = g_vIV[4] ^ m_t[0]; + v[13] = g_vIV[5] ^ m_t[1]; + v[14] = g_vIV[6] ^ m_f[0]; + v[15] = g_vIV[7] ^ m_f[1]; + + for(int r = 0; r < NbRounds; ++r) + { + int r16 = r << 4; + + G(v, m, r16, 0, 0, 4, 8, 12); + G(v, m, r16, 2, 1, 5, 9, 13); + G(v, m, r16, 4, 2, 6, 10, 14); + G(v, m, r16, 6, 3, 7, 11, 15); + G(v, m, r16, 8, 0, 5, 10, 15); + G(v, m, r16, 10, 1, 6, 11, 12); + G(v, m, r16, 12, 2, 7, 8, 13); + G(v, m, r16, 14, 3, 4, 9, 14); + } + + for(int i = 0; i < 8; ++i) + h[i] ^= v[i] ^ v[i + 8]; + } + + private void IncrementCounter(ulong cb) + { + m_t[0] += cb; + if(m_t[0] < cb) ++m_t[1]; + } + + public int GetDigestSize() + { + return HashSize; + } + + public int GetByteLength() + { + return m_buf.Length; + } + + public void Update(byte input) + { + throw new NotImplementedException(); + } + + public void BlockUpdate(byte[] array, int ibStart, int cbSize) + { + Debug.Assert(m_f[0] == 0); + + if ((m_cbBuf + cbSize) > NbBlockBytes) // Not '>=' (buffer must not be empty) + { + int cbFill = NbBlockBytes - m_cbBuf; + if (cbFill > 0) Array.Copy(array, ibStart, m_buf, m_cbBuf, cbFill); + + IncrementCounter((ulong)NbBlockBytes); + Compress(m_buf, 0); + + m_cbBuf = 0; + cbSize -= cbFill; + ibStart += cbFill; + + while (cbSize > NbBlockBytes) // Not '>=' (buffer must not be empty) + { + IncrementCounter((ulong)NbBlockBytes); + Compress(array, ibStart); + + cbSize -= NbBlockBytes; + ibStart += NbBlockBytes; + } + } + + if (cbSize > 0) + { + Debug.Assert((m_cbBuf + cbSize) <= NbBlockBytes); + + Array.Copy(array, ibStart, m_buf, m_cbBuf, cbSize); + m_cbBuf += cbSize; + } + } + + public int DoFinal(byte[] output, int outOff) + { + if (m_f[0] != 0) { Debug.Assert(false); throw new InvalidOperationException(); } + Debug.Assert(((m_t[1] == 0) && (m_t[0] == 0)) || + (m_cbBuf > 0)); // Buffer must not be empty for last block processing + + m_f[0] = ulong.MaxValue; // Indicate last block + + int cbFill = NbBlockBytes - m_cbBuf; + if (cbFill > 0) Array.Clear(m_buf, m_cbBuf, cbFill); + + IncrementCounter((ulong)m_cbBuf); + Compress(m_buf, 0); + + byte[] pbHash = new byte[NbMaxOutBytes]; + for (int i = 0; i < m_h.Length; ++i) + MemUtil.UInt64ToBytesEx(m_h[i], pbHash, i << 3); + + if (m_cbHashLength == NbMaxOutBytes) + { + output = pbHash; + return output.Length; + } + Debug.Assert(m_cbHashLength < NbMaxOutBytes); + + byte[] pbShort = new byte[m_cbHashLength]; + if (m_cbHashLength > 0) + Array.Copy(pbHash, pbShort, m_cbHashLength); + MemUtil.ZeroByteArray(pbHash); + output = pbShort; + return output.Length; + } + + public void Reset() + { + MemUtil.ZeroByteArray(m_buf); + } + + public void TransformBlock(byte[] pbBuf, int p1, int pbBufLength, byte[] p3, int p4) + { + BlockUpdate(pbBuf, p1, pbBufLength); + } + + public void TransformFinalBlock(byte[] emptyByteArray, int i, int i1) + { + DoFinal(emptyByteArray, i); + } + + public void Clear() + { + Reset(); + } + + internal byte[] ComputeHash(byte[] pbOutBuffer) + { + byte[] result = new byte[pbOutBuffer.Length]; + DoFinal(result, 0); + return result; + } + } +} diff --git a/ModernKeePassLib/Cryptography/HashingStreamEx.cs b/ModernKeePassLib/Cryptography/HashingStreamEx.cs index e58d772..e3f95cf 100644 --- a/ModernKeePassLib/Cryptography/HashingStreamEx.cs +++ b/ModernKeePassLib/Cryptography/HashingStreamEx.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,7 +19,7 @@ using System; using System.Collections.Generic; -using System.Text; +using System.Diagnostics; using System.IO; #if ModernKeePassLib using Windows.Security.Cryptography; @@ -29,7 +29,6 @@ using Org.BouncyCastle.Crypto.Digests; #else using System.Security.Cryptography; #endif -using System.Diagnostics; using System.Runtime.InteropServices.ComTypes; using ModernKeePassLib.Utility; using Org.BouncyCastle.Crypto.Tls; @@ -38,12 +37,12 @@ namespace ModernKeePassLib.Cryptography { public sealed class HashingStreamEx : Stream { - private Stream m_sBaseStream; - private bool m_bWriting; -#if ModernKeePassLib + private readonly Stream m_sBaseStream; + private readonly bool m_bWriting; + #if ModernKeePassLib //private ICryptoTransform m_hash; //private CryptographicHash m_hash; - private IDigest m_hash; + private IDigest m_hash; #else private HashAlgorithm m_hash; #endif @@ -78,7 +77,7 @@ namespace ModernKeePassLib.Cryptography public override long Position { get { return m_sBaseStream.Position; } - set { throw new NotSupportedException(); } + set { Debug.Assert(false); throw new NotSupportedException(); } } #if ModernKeePassLib @@ -88,8 +87,7 @@ namespace ModernKeePassLib.Cryptography public HashingStreamEx(Stream sBaseStream, bool bWriting, HashAlgorithm hashAlgorithm) #endif { - if(sBaseStream == null) - throw new ArgumentNullException("sBaseStream"); + if (sBaseStream == null) throw new ArgumentNullException("sBaseStream"); m_sBaseStream = sBaseStream; m_bWriting = bWriting; @@ -107,53 +105,51 @@ namespace ModernKeePassLib.Cryptography try { if(m_hash == null) m_hash = HashAlgorithm.Create(); } catch(Exception) { } #endif - if (m_hash == null) { Debug.Assert(false); return; } + if(m_hash == null) { Debug.Assert(false); return; } // Validate hash algorithm - /*if((!m_hash.CanReuseTransform) || (!m_hash.CanTransformMultipleBlocks) || - (m_hash.InputBlockSize != 1) || (m_hash.OutputBlockSize != 1)) + /*if(!m_hash.CanReuseTransform || !m_hash.CanTransformMultipleBlocks) { -#if false && DEBUG - MessageService.ShowWarning("Broken HashAlgorithm object in HashingStreamEx."); -#endif + Debug.Assert(false); m_hash = null; }*/ } - public override void Flush() - { - m_sBaseStream.Flush(); - } - #if ModernKeePassLib || KeePassRT - protected override void Dispose(bool disposing) - { - if(!disposing) return; + protected override void Dispose(bool disposing) + { + if (!disposing) return; #else public override void Close() { #endif - if(m_hash != null) - { - try - { - //m_hash.TransformFinalBlock(new byte[0], 0, 0); + if (m_hash != null) + { + try + { + //m_hash.TransformFinalBlock(new byte[0], 0, 0); #if ModernKeePassLib - //m_pbFinalHash = (m_hash as CryptographicHash).GetValueAndReset (); - //CryptographicBuffer.CopyToByteArray(m_hash.GetValueAndReset(), out m_pbFinalHash); - m_pbFinalHash = new byte[32]; - m_hash.DoFinal(m_pbFinalHash, 0); - m_hash.Reset(); + //m_pbFinalHash = (m_hash as CryptographicHash).GetValueAndReset (); + //CryptographicBuffer.CopyToByteArray(m_hash.GetValueAndReset(), out m_pbFinalHash); + m_pbFinalHash = new byte[32]; + m_hash.DoFinal(m_pbFinalHash, 0); + m_hash.Reset(); #else m_pbFinalHash = m_hash.Hash; #endif - } - catch(Exception) { Debug.Assert(false); } + } + catch (Exception) + { + Debug.Assert(false); + } - m_hash = null; - } + base.Dispose(disposing); + } + } - m_sBaseStream.Dispose(); + public override void Flush() + { + m_sBaseStream.Flush(); } public override long Seek(long lOffset, SeekOrigin soOrigin) @@ -185,12 +181,11 @@ namespace ModernKeePassLib.Cryptography #endif if((m_hash != null) && (nRead > 0)) - //m_hash.TransformBlock(pbBuffer, nOffset, nRead, pbBuffer, nOffset); - //m_hash.Append(CryptographicBuffer.CreateFromByteArray(pbBuffer)); - m_hash.BlockUpdate(pbBuffer, nOffset, nRead); + //m_hash.TransformBlock(pbBuffer, nOffset, nRead, pbBuffer, nOffset); + m_hash.BlockUpdate(pbBuffer, nOffset, nRead); #if DEBUG - Debug.Assert(MemUtil.ArraysEqual(pbBuffer, pbOrg)); + Debug.Assert(MemUtil.ArraysEqual(pbBuffer, pbOrg)); #endif return nRead; @@ -205,12 +200,12 @@ namespace ModernKeePassLib.Cryptography Array.Copy(pbBuffer, pbOrg, pbBuffer.Length); #endif - if ((m_hash != null) && (nCount > 0)) - //m_hash.TransformBlock(pbBuffer, nOffset, nCount, pbBuffer, nOffset); - //m_hash.Append(CryptographicBuffer.CreateFromByteArray(pbBuffer)); - m_hash.BlockUpdate(pbBuffer, nOffset, nCount); + if((m_hash != null) && (nCount > 0)) + //m_hash.TransformBlock(pbBuffer, nOffset, nCount, pbBuffer, nOffset); + m_hash.BlockUpdate(pbBuffer, nOffset, nCount); + #if DEBUG - Debug.Assert(MemUtil.ArraysEqual(pbBuffer, pbOrg)); + Debug.Assert(MemUtil.ArraysEqual(pbBuffer, pbOrg)); #endif m_sBaseStream.Write(pbBuffer, nOffset, nCount); diff --git a/ModernKeePassLib/Cryptography/HmacOtp.cs b/ModernKeePassLib/Cryptography/HmacOtp.cs index db34309..f7eab67 100644 --- a/ModernKeePassLib/Cryptography/HmacOtp.cs +++ b/ModernKeePassLib/Cryptography/HmacOtp.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -45,22 +45,14 @@ namespace ModernKeePassLib.Cryptography uint uCodeDigits, bool bAddChecksum, int iTruncationOffset) { byte[] pbText = MemUtil.UInt64ToBytes(uFactor); - Array.Reverse(pbText); // Big-Endian - -#if ModernKeePassLib - /*var hsha1 = WinRTCrypto.MacAlgorithmProvider.OpenAlgorithm(MacAlgorithm.HmacSha1).CreateHash(pbSecret); - hsha1.Append(pbText); - var pbHash = hsha1.GetValueAndReset();*/ + Array.Reverse(pbText); // To big-endian + var hsha1 = MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha1).CreateHash(CryptographicBuffer.CreateFromByteArray(pbSecret)); hsha1.Append(CryptographicBuffer.CreateFromByteArray(pbText)); byte[] pbHash; CryptographicBuffer.CopyToByteArray(hsha1.GetValueAndReset(), out pbHash); -#else - HMACSHA1 hsha1 = new HMACSHA1(pbSecret); - byte[] pbHash = hsha1.ComputeHash(pbText); -#endif - uint uOffset = (uint)(pbHash[pbHash.Length - 1] & 0xF); + uint uOffset = (uint)(pbHash[pbHash.Length - 1] & 0xF); if((iTruncationOffset >= 0) && (iTruncationOffset < (pbHash.Length - 4))) uOffset = (uint)iTruncationOffset; diff --git a/ModernKeePassLib/Cryptography/KeyDerivation/AesKdf.GCrypt.cs b/ModernKeePassLib/Cryptography/KeyDerivation/AesKdf.GCrypt.cs new file mode 100644 index 0000000..130634a --- /dev/null +++ b/ModernKeePassLib/Cryptography/KeyDerivation/AesKdf.GCrypt.cs @@ -0,0 +1,402 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; + +using ModernKeePassLib.Native; +using ModernKeePassLib.Utility; + +namespace ModernKeePassLib.Cryptography.KeyDerivation +{ + public sealed partial class AesKdf : KdfEngine + { + private static bool TransformKeyGCrypt(byte[] pbData32, byte[] pbSeed32, + ulong uRounds) + { + byte[] pbNewData32 = null; + try + { + if(GCryptInitLib()) + { + pbNewData32 = new byte[32]; + Array.Copy(pbData32, pbNewData32, 32); + + if(TransformKeyGCryptPriv(pbNewData32, pbSeed32, uRounds)) + { + Array.Copy(pbNewData32, pbData32, 32); + return true; + } + } + } + catch(Exception) { } + finally { if(pbNewData32 != null) MemUtil.ZeroByteArray(pbNewData32); } + + return false; + } + + private static bool TransformKeyBenchmarkGCrypt(uint uTimeMs, out ulong uRounds) + { + uRounds = 0; + + try + { + if(GCryptInitLib()) + return TransformKeyBenchmarkGCryptPriv(uTimeMs, ref uRounds); + } + catch(Exception) { } + + return false; + } + + private static bool GCryptInitLib() + { + Debug.Assert(Marshal.SizeOf(typeof(int)) == 4); // Also on 64-bit systems + Debug.Assert(Marshal.SizeOf(typeof(uint)) == 4); + + if(!NativeLib.IsUnix()) return false; // Independent of workaround state + if(!MonoWorkarounds.IsRequired(1468)) return false; // Can be turned off + + // gcry_check_version initializes the library; + // throws when LibGCrypt is not available + NativeMethods.gcry_check_version(IntPtr.Zero); + return true; + } + + // ============================================================= + // Multi-threaded implementation + + // For some reason, the following multi-threaded implementation + // is slower than the single-threaded implementation below + // (threading overhead by Mono? LibGCrypt threading issues?) + /* private sealed class GCryptTransformInfo : IDisposable + { + public IntPtr Data16; + public IntPtr Seed32; + public ulong Rounds; + public uint TimeMs; + + public bool Success = false; + + public GCryptTransformInfo(byte[] pbData32, int iDataOffset, + byte[] pbSeed32, ulong uRounds, uint uTimeMs) + { + this.Data16 = Marshal.AllocCoTaskMem(16); + Marshal.Copy(pbData32, iDataOffset, this.Data16, 16); + + this.Seed32 = Marshal.AllocCoTaskMem(32); + Marshal.Copy(pbSeed32, 0, this.Seed32, 32); + + this.Rounds = uRounds; + this.TimeMs = uTimeMs; + } + + public void Dispose() + { + if(this.Data16 != IntPtr.Zero) + { + Marshal.WriteInt64(this.Data16, 0); + Marshal.WriteInt64(this.Data16, 8, 0); + Marshal.FreeCoTaskMem(this.Data16); + this.Data16 = IntPtr.Zero; + } + + if(this.Seed32 != IntPtr.Zero) + { + Marshal.FreeCoTaskMem(this.Seed32); + this.Seed32 = IntPtr.Zero; + } + } + } + + private static GCryptTransformInfo[] GCryptRun(byte[] pbData32, + byte[] pbSeed32, ulong uRounds, uint uTimeMs, ParameterizedThreadStart fL, + ParameterizedThreadStart fR) + { + GCryptTransformInfo tiL = new GCryptTransformInfo(pbData32, 0, + pbSeed32, uRounds, uTimeMs); + GCryptTransformInfo tiR = new GCryptTransformInfo(pbData32, 16, + pbSeed32, uRounds, uTimeMs); + + Thread th = new Thread(fL); + th.Start(tiL); + + fR(tiR); + + th.Join(); + + Marshal.Copy(tiL.Data16, pbData32, 0, 16); + Marshal.Copy(tiR.Data16, pbData32, 16, 16); + + tiL.Dispose(); + tiR.Dispose(); + + if(tiL.Success && tiR.Success) + return new GCryptTransformInfo[2] { tiL, tiR }; + return null; + } + + private static bool TransformKeyGCryptPriv(byte[] pbData32, byte[] pbSeed32, + ulong uRounds) + { + return (GCryptRun(pbData32, pbSeed32, uRounds, 0, + new ParameterizedThreadStart(AesKdf.GCryptTransformTh), + new ParameterizedThreadStart(AesKdf.GCryptTransformTh)) != null); + } + + private static bool GCryptInitCipher(ref IntPtr h, GCryptTransformInfo ti) + { + NativeMethods.gcry_cipher_open(ref h, NativeMethods.GCRY_CIPHER_AES256, + NativeMethods.GCRY_CIPHER_MODE_ECB, 0); + if(h == IntPtr.Zero) { Debug.Assert(false); return false; } + + IntPtr n32 = new IntPtr(32); + if(NativeMethods.gcry_cipher_setkey(h, ti.Seed32, n32) != 0) + { + Debug.Assert(false); + return false; + } + + return true; + } + + private static void GCryptTransformTh(object o) + { + IntPtr h = IntPtr.Zero; + try + { + GCryptTransformInfo ti = (o as GCryptTransformInfo); + if(ti == null) { Debug.Assert(false); return; } + + if(!GCryptInitCipher(ref h, ti)) return; + + IntPtr n16 = new IntPtr(16); + for(ulong u = 0; u < ti.Rounds; ++u) + { + if(NativeMethods.gcry_cipher_encrypt(h, ti.Data16, n16, + IntPtr.Zero, IntPtr.Zero) != 0) + { + Debug.Assert(false); + return; + } + } + + ti.Success = true; + } + catch(Exception) { Debug.Assert(false); } + finally + { + try { if(h != IntPtr.Zero) NativeMethods.gcry_cipher_close(h); } + catch(Exception) { Debug.Assert(false); } + } + } + + private static bool TransformKeyBenchmarkGCryptPriv(uint uTimeMs, ref ulong uRounds) + { + GCryptTransformInfo[] v = GCryptRun(new byte[32], new byte[32], + 0, uTimeMs, + new ParameterizedThreadStart(AesKdf.GCryptBenchmarkTh), + new ParameterizedThreadStart(AesKdf.GCryptBenchmarkTh)); + + if(v != null) + { + ulong uL = Math.Min(v[0].Rounds, ulong.MaxValue >> 1); + ulong uR = Math.Min(v[1].Rounds, ulong.MaxValue >> 1); + uRounds = (uL + uR) / 2; + + return true; + } + return false; + } + + private static void GCryptBenchmarkTh(object o) + { + IntPtr h = IntPtr.Zero; + try + { + GCryptTransformInfo ti = (o as GCryptTransformInfo); + if(ti == null) { Debug.Assert(false); return; } + + if(!GCryptInitCipher(ref h, ti)) return; + + ulong r = 0; + IntPtr n16 = new IntPtr(16); + int tStart = Environment.TickCount; + while(true) + { + for(ulong j = 0; j < BenchStep; ++j) + { + if(NativeMethods.gcry_cipher_encrypt(h, ti.Data16, n16, + IntPtr.Zero, IntPtr.Zero) != 0) + { + Debug.Assert(false); + return; + } + } + + r += BenchStep; + if(r < BenchStep) // Overflow check + { + r = ulong.MaxValue; + break; + } + + uint tElapsed = (uint)(Environment.TickCount - tStart); + if(tElapsed > ti.TimeMs) break; + } + + ti.Rounds = r; + ti.Success = true; + } + catch(Exception) { Debug.Assert(false); } + finally + { + try { if(h != IntPtr.Zero) NativeMethods.gcry_cipher_close(h); } + catch(Exception) { Debug.Assert(false); } + } + } */ + + // ============================================================= + // Single-threaded implementation + + private static bool GCryptInitCipher(ref IntPtr h, IntPtr pSeed32) + { + NativeMethods.gcry_cipher_open(ref h, NativeMethods.GCRY_CIPHER_AES256, + NativeMethods.GCRY_CIPHER_MODE_ECB, 0); + if(h == IntPtr.Zero) { Debug.Assert(false); return false; } + + IntPtr n32 = new IntPtr(32); + if(NativeMethods.gcry_cipher_setkey(h, pSeed32, n32) != 0) + { + Debug.Assert(false); + return false; + } + + return true; + } + + private static bool GCryptBegin(byte[] pbData32, byte[] pbSeed32, + ref IntPtr h, ref IntPtr pData32, ref IntPtr pSeed32) + { + pData32 = Marshal.AllocCoTaskMem(32); + pSeed32 = Marshal.AllocCoTaskMem(32); + + Marshal.Copy(pbData32, 0, pData32, 32); + Marshal.Copy(pbSeed32, 0, pSeed32, 32); + + return GCryptInitCipher(ref h, pSeed32); + } + + private static void GCryptEnd(IntPtr h, IntPtr pData32, IntPtr pSeed32) + { + NativeMethods.gcry_cipher_close(h); + + Marshal.WriteInt64(pData32, 0); + Marshal.WriteInt64(pData32, 8, 0); + Marshal.WriteInt64(pData32, 16, 0); + Marshal.WriteInt64(pData32, 24, 0); + + Marshal.FreeCoTaskMem(pData32); + Marshal.FreeCoTaskMem(pSeed32); + } + + private static bool TransformKeyGCryptPriv(byte[] pbData32, byte[] pbSeed32, + ulong uRounds) + { + IntPtr h = IntPtr.Zero, pData32 = IntPtr.Zero, pSeed32 = IntPtr.Zero; + if(!GCryptBegin(pbData32, pbSeed32, ref h, ref pData32, ref pSeed32)) + return false; + + try + { + IntPtr n32 = new IntPtr(32); + for(ulong i = 0; i < uRounds; ++i) + { + if(NativeMethods.gcry_cipher_encrypt(h, pData32, n32, + IntPtr.Zero, IntPtr.Zero) != 0) + { + Debug.Assert(false); + return false; + } + } + + Marshal.Copy(pData32, pbData32, 0, 32); + return true; + } + catch(Exception) { Debug.Assert(false); } + finally { GCryptEnd(h, pData32, pSeed32); } + + return false; + } + + private static bool TransformKeyBenchmarkGCryptPriv(uint uTimeMs, ref ulong uRounds) + { + byte[] pbData32 = new byte[32]; + byte[] pbSeed32 = new byte[32]; + + IntPtr h = IntPtr.Zero, pData32 = IntPtr.Zero, pSeed32 = IntPtr.Zero; + if(!GCryptBegin(pbData32, pbSeed32, ref h, ref pData32, ref pSeed32)) + return false; + + uint uMaxMs = uTimeMs; + ulong uDiv = 1; + if(uMaxMs <= (uint.MaxValue >> 1)) { uMaxMs *= 2U; uDiv = 2; } + + try + { + ulong r = 0; + IntPtr n32 = new IntPtr(32); + int tStart = Environment.TickCount; + while(true) + { + for(ulong j = 0; j < BenchStep; ++j) + { + if(NativeMethods.gcry_cipher_encrypt(h, pData32, n32, + IntPtr.Zero, IntPtr.Zero) != 0) + { + Debug.Assert(false); + return false; + } + } + + r += BenchStep; + if(r < BenchStep) // Overflow check + { + r = ulong.MaxValue; + break; + } + + uint tElapsed = (uint)(Environment.TickCount - tStart); + if(tElapsed > uMaxMs) break; + } + + uRounds = r / uDiv; + return true; + } + catch(Exception) { Debug.Assert(false); } + finally { GCryptEnd(h, pData32, pSeed32); } + + return false; + } + } +} diff --git a/ModernKeePassLib/Cryptography/KeyDerivation/AesKdf.cs b/ModernKeePassLib/Cryptography/KeyDerivation/AesKdf.cs new file mode 100644 index 0000000..100568c --- /dev/null +++ b/ModernKeePassLib/Cryptography/KeyDerivation/AesKdf.cs @@ -0,0 +1,206 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Parameters; + +using ModernKeePassLib.Cryptography; +using ModernKeePassLib.Native; +using ModernKeePassLib.Utility; + +namespace ModernKeePassLib.Cryptography.KeyDerivation +{ + public sealed partial class AesKdf : KdfEngine + { + private static readonly PwUuid g_uuid = new PwUuid(new byte[] { + 0xC9, 0xD9, 0xF3, 0x9A, 0x62, 0x8A, 0x44, 0x60, + 0xBF, 0x74, 0x0D, 0x08, 0xC1, 0x8A, 0x4F, 0xEA }); + + public const string ParamRounds = "R"; // UInt64 + public const string ParamSeed = "S"; // Byte[32] + + private const ulong BenchStep = 3001; + + public override PwUuid Uuid + { + get { return g_uuid; } + } + + public override string Name + { + get { return "AES-KDF"; } + } + + public AesKdf() + { + } + + public override KdfParameters GetDefaultParameters() + { + KdfParameters p = base.GetDefaultParameters(); + p.SetUInt64(ParamRounds, PwDefs.DefaultKeyEncryptionRounds); + return p; + } + + public override void Randomize(KdfParameters p) + { + if(p == null) { Debug.Assert(false); return; } + Debug.Assert(g_uuid.Equals(p.KdfUuid)); + + byte[] pbSeed = CryptoRandom.Instance.GetRandomBytes(32); + p.SetByteArray(ParamSeed, pbSeed); + } + + public override byte[] Transform(byte[] pbMsg, KdfParameters p) + { + if(pbMsg == null) throw new ArgumentNullException("pbMsg"); + if(p == null) throw new ArgumentNullException("p"); + + Type tRounds = p.GetTypeOf(ParamRounds); + if(tRounds == null) throw new ArgumentNullException("p.Rounds"); + if(tRounds != typeof(ulong)) throw new ArgumentOutOfRangeException("p.Rounds"); + ulong uRounds = p.GetUInt64(ParamRounds, 0); + + byte[] pbSeed = p.GetByteArray(ParamSeed); + if(pbSeed == null) throw new ArgumentNullException("p.Seed"); + + if(pbMsg.Length != 32) + { + Debug.Assert(false); + pbMsg = CryptoUtil.HashSha256(pbMsg); + } + + if(pbSeed.Length != 32) + { + Debug.Assert(false); + pbSeed = CryptoUtil.HashSha256(pbSeed); + } + + return TransformKey(pbMsg, pbSeed, uRounds); + } + + private static byte[] TransformKey(byte[] pbOriginalKey32, byte[] pbKeySeed32, + ulong uNumRounds) + { + Debug.Assert((pbOriginalKey32 != null) && (pbOriginalKey32.Length == 32)); + if(pbOriginalKey32 == null) throw new ArgumentNullException("pbOriginalKey32"); + if(pbOriginalKey32.Length != 32) throw new ArgumentException(); + + Debug.Assert((pbKeySeed32 != null) && (pbKeySeed32.Length == 32)); + if(pbKeySeed32 == null) throw new ArgumentNullException("pbKeySeed32"); + if(pbKeySeed32.Length != 32) throw new ArgumentException(); + + byte[] pbNewKey = new byte[32]; + Array.Copy(pbOriginalKey32, pbNewKey, pbNewKey.Length); + + try + { + // Try to use the native library first + /*if(NativeLib.TransformKey256(pbNewKey, pbKeySeed32, uNumRounds)) + return CryptoUtil.HashSha256(pbNewKey);*/ + + if(TransformKeyGCrypt(pbNewKey, pbKeySeed32, uNumRounds)) + return CryptoUtil.HashSha256(pbNewKey); + + if(TransformKeyManaged(pbNewKey, pbKeySeed32, uNumRounds)) + return CryptoUtil.HashSha256(pbNewKey); + } + finally { MemUtil.ZeroByteArray(pbNewKey); } + + return null; + } + + internal static bool TransformKeyManaged(byte[] pbNewKey32, byte[] pbKeySeed32, + ulong uNumRounds) + { + KeyParameter kp = new KeyParameter(pbKeySeed32); + AesEngine aes = new AesEngine(); + aes.Init(true, kp); + + for(ulong i = 0; i < uNumRounds; ++i) + { + aes.ProcessBlock(pbNewKey32, 0, pbNewKey32, 0); + aes.ProcessBlock(pbNewKey32, 16, pbNewKey32, 16); + } + + return true; + } + + public override KdfParameters GetBestParameters(uint uMilliseconds) + { + KdfParameters p = GetDefaultParameters(); + ulong uRounds; + + // Try native method + /*if(NativeLib.TransformKeyBenchmark256(uMilliseconds, out uRounds)) + { + p.SetUInt64(ParamRounds, uRounds); + return p; + }*/ + + if(TransformKeyBenchmarkGCrypt(uMilliseconds, out uRounds)) + { + p.SetUInt64(ParamRounds, uRounds); + return p; + } + + byte[] pbKey = new byte[32]; + byte[] pbNewKey = new byte[32]; + for(int i = 0; i < pbKey.Length; ++i) + { + pbKey[i] = (byte)i; + pbNewKey[i] = (byte)i; + } + + KeyParameter kp = new KeyParameter(pbKey); + AesEngine aes = new AesEngine(); + aes.Init(true, kp); + + uRounds = 0; + int tStart = Environment.TickCount; + while(true) + { + for(ulong j = 0; j < BenchStep; ++j) + { + aes.ProcessBlock(pbNewKey, 0, pbNewKey, 0); + aes.ProcessBlock(pbNewKey, 16, pbNewKey, 16); + } + + uRounds += BenchStep; + if(uRounds < BenchStep) // Overflow check + { + uRounds = ulong.MaxValue; + break; + } + + uint tElapsed = (uint)(Environment.TickCount - tStart); + if(tElapsed > uMilliseconds) break; + } + + p.SetUInt64(ParamRounds, uRounds); + return p; + } + } +} diff --git a/ModernKeePassLib/Cryptography/KeyDerivation/Argon2Kdf.Core.cs b/ModernKeePassLib/Cryptography/KeyDerivation/Argon2Kdf.Core.cs new file mode 100644 index 0000000..11d5531 --- /dev/null +++ b/ModernKeePassLib/Cryptography/KeyDerivation/Argon2Kdf.Core.cs @@ -0,0 +1,636 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// This implementation is based on the official reference C +// implementation by Daniel Dinu and Dmitry Khovratovich (CC0 1.0). + +// Relative iterations (* = B2ROUND_ARRAYS \\ G_INLINED): +// * | false true +// ------+----------- +// false | 8885 9618 +// true | 9009 9636 +#define ARGON2_B2ROUND_ARRAYS +#define ARGON2_G_INLINED + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; + +using ModernKeePassLib.Cryptography.Hash; +using ModernKeePassLib.Utility; + +namespace ModernKeePassLib.Cryptography.KeyDerivation +{ + public sealed partial class Argon2Kdf : KdfEngine + { + private const ulong NbBlockSize = 1024; + private const ulong NbBlockSizeInQW = NbBlockSize / 8UL; + private const ulong NbSyncPoints = 4; + + private const int NbPreHashDigestLength = 64; + private const int NbPreHashSeedLength = NbPreHashDigestLength + 8; + +#if ARGON2_B2ROUND_ARRAYS + private static int[][] g_vFBCols = null; + private static int[][] g_vFBRows = null; +#endif + + private sealed class Argon2Ctx + { + public uint Version = 0; + + public ulong Lanes = 0; + public ulong TCost = 0; + public ulong MCost = 0; + public ulong MemoryBlocks = 0; + public ulong SegmentLength = 0; + public ulong LaneLength = 0; + + public ulong[] Mem = null; + } + + private sealed class Argon2ThreadInfo + { + public Argon2Ctx Context = null; + public ManualResetEvent Finished = new ManualResetEvent(false); + + public ulong Pass = 0; + public ulong Lane = 0; + public ulong Slice = 0; + public ulong Index = 0; + + public void Release() + { + if(this.Finished != null) + { + this.Finished.Dispose(); + this.Finished = null; + } + else { Debug.Assert(false); } + } + } + + private static byte[] Argon2d(byte[] pbMsg, byte[] pbSalt, uint uParallel, + ulong uMem, ulong uIt, int cbOut, uint uVersion, byte[] pbSecretKey, + byte[] pbAssocData) + { + pbSecretKey = (pbSecretKey ?? MemUtil.EmptyByteArray); + pbAssocData = (pbAssocData ?? MemUtil.EmptyByteArray); + +#if ARGON2_B2ROUND_ARRAYS + InitB2RoundIndexArrays(); +#endif + + Argon2Ctx ctx = new Argon2Ctx(); + ctx.Version = uVersion; + + ctx.Lanes = uParallel; + ctx.TCost = uIt; + ctx.MCost = uMem / NbBlockSize; + ctx.MemoryBlocks = Math.Max(ctx.MCost, 2UL * NbSyncPoints * ctx.Lanes); + + ctx.SegmentLength = ctx.MemoryBlocks / (ctx.Lanes * NbSyncPoints); + ctx.MemoryBlocks = ctx.SegmentLength * ctx.Lanes * NbSyncPoints; + + ctx.LaneLength = ctx.SegmentLength * NbSyncPoints; + + Debug.Assert(NbBlockSize == (NbBlockSizeInQW * +#if KeePassUAP + (ulong)Marshal.SizeOf() +#else + (ulong)Marshal.SizeOf(typeof(ulong)) +#endif + )); + ctx.Mem = new ulong[ctx.MemoryBlocks * NbBlockSizeInQW]; + + Blake2b h = new Blake2b(); + + // Initial hash + Debug.Assert(h.HashSize == (NbPreHashDigestLength * 8)); + byte[] pbBuf = new byte[4]; + MemUtil.UInt32ToBytesEx(uParallel, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + MemUtil.UInt32ToBytesEx((uint)cbOut, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + MemUtil.UInt32ToBytesEx((uint)ctx.MCost, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + MemUtil.UInt32ToBytesEx((uint)uIt, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + MemUtil.UInt32ToBytesEx(uVersion, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + MemUtil.UInt32ToBytesEx(0, pbBuf, 0); // Argon2d type = 0 + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + MemUtil.UInt32ToBytesEx((uint)pbMsg.Length, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + h.TransformBlock(pbMsg, 0, pbMsg.Length, pbMsg, 0); + MemUtil.UInt32ToBytesEx((uint)pbSalt.Length, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + h.TransformBlock(pbSalt, 0, pbSalt.Length, pbSalt, 0); + MemUtil.UInt32ToBytesEx((uint)pbSecretKey.Length, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + h.TransformBlock(pbSecretKey, 0, pbSecretKey.Length, pbSecretKey, 0); + MemUtil.UInt32ToBytesEx((uint)pbAssocData.Length, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + h.TransformBlock(pbAssocData, 0, pbAssocData.Length, pbAssocData, 0); + byte[] pbH0 = MemUtil.EmptyByteArray; + h.TransformFinalBlock(pbH0, 0, 0); + /*h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + byte[] pbH0 = h.Hash;*/ + Debug.Assert(pbH0.Length == 64); + + byte[] pbBlockHash = new byte[NbPreHashSeedLength]; + Array.Copy(pbH0, pbBlockHash, pbH0.Length); + MemUtil.ZeroByteArray(pbH0); + + FillFirstBlocks(ctx, pbBlockHash, h); + MemUtil.ZeroByteArray(pbBlockHash); + + FillMemoryBlocks(ctx); + + byte[] pbOut = FinalHash(ctx, cbOut, h); + + h.Clear(); + MemUtil.ZeroArray(ctx.Mem); + return pbOut; + } + + private static void LoadBlock(ulong[] pqDst, ulong uDstOffset, byte[] pbIn) + { + // for(ulong i = 0; i < NbBlockSizeInQW; ++i) + // pqDst[uDstOffset + i] = MemUtil.BytesToUInt64(pbIn, (int)(i << 3)); + + Debug.Assert((uDstOffset + NbBlockSizeInQW - 1UL) <= (ulong)int.MaxValue); + int iDstOffset = (int)uDstOffset; + for(int i = 0; i < (int)NbBlockSizeInQW; ++i) + pqDst[iDstOffset + i] = MemUtil.BytesToUInt64(pbIn, i << 3); + } + + private static void StoreBlock(byte[] pbDst, ulong[] pqSrc) + { + for(int i = 0; i < (int)NbBlockSizeInQW; ++i) + MemUtil.UInt64ToBytesEx(pqSrc[i], pbDst, i << 3); + } + + private static void CopyBlock(ulong[] vDst, ulong uDstOffset, ulong[] vSrc, + ulong uSrcOffset) + { + // for(ulong i = 0; i < NbBlockSizeInQW; ++i) + // vDst[uDstOffset + i] = vSrc[uSrcOffset + i]; + + // Debug.Assert((uDstOffset + NbBlockSizeInQW - 1UL) <= (ulong)int.MaxValue); + // Debug.Assert((uSrcOffset + NbBlockSizeInQW - 1UL) <= (ulong)int.MaxValue); + // int iDstOffset = (int)uDstOffset; + // int iSrcOffset = (int)uSrcOffset; + // for(int i = 0; i < (int)NbBlockSizeInQW; ++i) + // vDst[iDstOffset + i] = vSrc[iSrcOffset + i]; + + Array.Copy(vSrc, (int)uSrcOffset, vDst, (int)uDstOffset, + (int)NbBlockSizeInQW); + } + + private static void XorBlock(ulong[] vDst, ulong uDstOffset, ulong[] vSrc, + ulong uSrcOffset) + { + // for(ulong i = 0; i < NbBlockSizeInQW; ++i) + // vDst[uDstOffset + i] ^= vSrc[uSrcOffset + i]; + + Debug.Assert((uDstOffset + NbBlockSizeInQW - 1UL) <= (ulong)int.MaxValue); + Debug.Assert((uSrcOffset + NbBlockSizeInQW - 1UL) <= (ulong)int.MaxValue); + int iDstOffset = (int)uDstOffset; + int iSrcOffset = (int)uSrcOffset; + for(int i = 0; i < (int)NbBlockSizeInQW; ++i) + vDst[iDstOffset + i] ^= vSrc[iSrcOffset + i]; + } + + private static void Blake2bLong(byte[] pbOut, int cbOut, + byte[] pbIn, int cbIn, Blake2b h) + { + Debug.Assert((h != null) && (h.HashSize == (64 * 8))); + + byte[] pbOutLen = new byte[4]; + MemUtil.UInt32ToBytesEx((uint)cbOut, pbOutLen, 0); + + if(cbOut <= 64) + { + Blake2b hOut = ((cbOut == 64) ? h : new Blake2b(cbOut)); + if(cbOut == 64) hOut.Initialize(); + + hOut.TransformBlock(pbOutLen, 0, pbOutLen.Length, pbOutLen, 0); + hOut.TransformBlock(pbIn, 0, cbIn, pbIn, 0); + hOut.TransformFinalBlock(pbOut, 0, 0); + /*hOut.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + Array.Copy(hOut.Hash, pbOut, cbOut);*/ + + if(cbOut < 64) hOut.Clear(); + return; + } + + byte[] pbOutBuffer = new byte[64]; + h.Initialize(); + h.TransformBlock(pbOutLen, 0, pbOutLen.Length, pbOutLen, 0); + h.TransformBlock(pbIn, 0, cbIn, pbIn, 0); + h.TransformFinalBlock(pbOutBuffer, 0, 0); + + /*h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + Array.Copy(h.Hash, pbOutBuffer, pbOutBuffer.Length);*/ + + int ibOut = 64 / 2; + Array.Copy(pbOutBuffer, pbOut, ibOut); + int cbToProduce = cbOut - ibOut; + + h.Initialize(); + while(cbToProduce > 64) + { + byte[] pbHash = h.ComputeHash(pbOutBuffer); + Array.Copy(pbHash, pbOutBuffer, 64); + + Array.Copy(pbHash, 0, pbOut, ibOut, 64 / 2); + ibOut += 64 / 2; + cbToProduce -= 64 / 2; + + MemUtil.ZeroByteArray(pbHash); + } + + /*using(*/ + { + Blake2b hOut = new Blake2b(cbToProduce); /*) + {*/ + byte[] pbHash = hOut.ComputeHash(pbOutBuffer); + Array.Copy(pbHash, 0, pbOut, ibOut, cbToProduce); + + MemUtil.ZeroByteArray(pbHash); + //} + } + + MemUtil.ZeroByteArray(pbOutBuffer); + } + +#if !ARGON2_G_INLINED + private static ulong BlaMka(ulong x, ulong y) + { + ulong xy = (x & 0xFFFFFFFFUL) * (y & 0xFFFFFFFFUL); + return (x + y + (xy << 1)); + } + + private static void G(ulong[] v, int a, int b, int c, int d) + { + ulong va = v[a], vb = v[b], vc = v[c], vd = v[d]; + + va = BlaMka(va, vb); + vd = MemUtil.RotateRight64(vd ^ va, 32); + vc = BlaMka(vc, vd); + vb = MemUtil.RotateRight64(vb ^ vc, 24); + va = BlaMka(va, vb); + vd = MemUtil.RotateRight64(vd ^ va, 16); + vc = BlaMka(vc, vd); + vb = MemUtil.RotateRight64(vb ^ vc, 63); + + v[a] = va; + v[b] = vb; + v[c] = vc; + v[d] = vd; + } +#else + private static void G(ulong[] v, int a, int b, int c, int d) + { + ulong va = v[a], vb = v[b], vc = v[c], vd = v[d]; + + ulong xy = (va & 0xFFFFFFFFUL) * (vb & 0xFFFFFFFFUL); + va += vb + (xy << 1); + + vd = MemUtil.RotateRight64(vd ^ va, 32); + + xy = (vc & 0xFFFFFFFFUL) * (vd & 0xFFFFFFFFUL); + vc += vd + (xy << 1); + + vb = MemUtil.RotateRight64(vb ^ vc, 24); + + xy = (va & 0xFFFFFFFFUL) * (vb & 0xFFFFFFFFUL); + va += vb + (xy << 1); + + vd = MemUtil.RotateRight64(vd ^ va, 16); + + xy = (vc & 0xFFFFFFFFUL) * (vd & 0xFFFFFFFFUL); + vc += vd + (xy << 1); + + vb = MemUtil.RotateRight64(vb ^ vc, 63); + + v[a] = va; + v[b] = vb; + v[c] = vc; + v[d] = vd; + } +#endif + +#if ARGON2_B2ROUND_ARRAYS + private static void Blake2RoundNoMsg(ulong[] pbR, int[] v) + { + G(pbR, v[0], v[4], v[8], v[12]); + G(pbR, v[1], v[5], v[9], v[13]); + G(pbR, v[2], v[6], v[10], v[14]); + G(pbR, v[3], v[7], v[11], v[15]); + G(pbR, v[0], v[5], v[10], v[15]); + G(pbR, v[1], v[6], v[11], v[12]); + G(pbR, v[2], v[7], v[8], v[13]); + G(pbR, v[3], v[4], v[9], v[14]); + } +#else + private static void Blake2RoundNoMsgCols16i(ulong[] pbR, int i) + { + G(pbR, i, i + 4, i + 8, i + 12); + G(pbR, i + 1, i + 5, i + 9, i + 13); + G(pbR, i + 2, i + 6, i + 10, i + 14); + G(pbR, i + 3, i + 7, i + 11, i + 15); + G(pbR, i, i + 5, i + 10, i + 15); + G(pbR, i + 1, i + 6, i + 11, i + 12); + G(pbR, i + 2, i + 7, i + 8, i + 13); + G(pbR, i + 3, i + 4, i + 9, i + 14); + } + + private static void Blake2RoundNoMsgRows2i(ulong[] pbR, int i) + { + G(pbR, i, i + 32, i + 64, i + 96); + G(pbR, i + 1, i + 33, i + 65, i + 97); + G(pbR, i + 16, i + 48, i + 80, i + 112); + G(pbR, i + 17, i + 49, i + 81, i + 113); + G(pbR, i, i + 33, i + 80, i + 113); + G(pbR, i + 1, i + 48, i + 81, i + 96); + G(pbR, i + 16, i + 49, i + 64, i + 97); + G(pbR, i + 17, i + 32, i + 65, i + 112); + } +#endif + + private static void FillFirstBlocks(Argon2Ctx ctx, byte[] pbBlockHash, + Blake2b h) + { + byte[] pbBlock = new byte[NbBlockSize]; + + for(ulong l = 0; l < ctx.Lanes; ++l) + { + MemUtil.UInt32ToBytesEx(0, pbBlockHash, NbPreHashDigestLength); + MemUtil.UInt32ToBytesEx((uint)l, pbBlockHash, NbPreHashDigestLength + 4); + + Blake2bLong(pbBlock, (int)NbBlockSize, pbBlockHash, + NbPreHashSeedLength, h); + LoadBlock(ctx.Mem, l * ctx.LaneLength * NbBlockSizeInQW, pbBlock); + + MemUtil.UInt32ToBytesEx(1, pbBlockHash, NbPreHashDigestLength); + + Blake2bLong(pbBlock, (int)NbBlockSize, pbBlockHash, + NbPreHashSeedLength, h); + LoadBlock(ctx.Mem, (l * ctx.LaneLength + 1UL) * NbBlockSizeInQW, pbBlock); + } + + MemUtil.ZeroByteArray(pbBlock); + } + + private static ulong IndexAlpha(Argon2Ctx ctx, Argon2ThreadInfo ti, + uint uPseudoRand, bool bSameLane) + { + ulong uRefAreaSize; + if(ti.Pass == 0) + { + if(ti.Slice == 0) + { + Debug.Assert(ti.Index > 0); + uRefAreaSize = ti.Index - 1UL; + } + else + { + if(bSameLane) + uRefAreaSize = ti.Slice * ctx.SegmentLength + + ti.Index - 1UL; + else + uRefAreaSize = ti.Slice * ctx.SegmentLength - + ((ti.Index == 0UL) ? 1UL : 0UL); + } + } + else + { + if(bSameLane) + uRefAreaSize = ctx.LaneLength - ctx.SegmentLength + + ti.Index - 1UL; + else + uRefAreaSize = ctx.LaneLength - ctx.SegmentLength - + ((ti.Index == 0) ? 1UL : 0UL); + } + Debug.Assert(uRefAreaSize <= (ulong)uint.MaxValue); + + ulong uRelPos = uPseudoRand; + uRelPos = (uRelPos * uRelPos) >> 32; + uRelPos = uRefAreaSize - 1UL - ((uRefAreaSize * uRelPos) >> 32); + + ulong uStart = 0; + if(ti.Pass != 0) + uStart = (((ti.Slice + 1UL) == NbSyncPoints) ? 0UL : + ((ti.Slice + 1UL) * ctx.SegmentLength)); + Debug.Assert(uStart <= (ulong)uint.MaxValue); + + Debug.Assert(ctx.LaneLength <= (ulong)uint.MaxValue); + return ((uStart + uRelPos) % ctx.LaneLength); + } + + private static void FillMemoryBlocks(Argon2Ctx ctx) + { + int np = (int)ctx.Lanes; + Argon2ThreadInfo[] v = new Argon2ThreadInfo[np]; + + for(ulong r = 0; r < ctx.TCost; ++r) + { + for(ulong s = 0; s < NbSyncPoints; ++s) + { + for(int l = 0; l < np; ++l) + { + Argon2ThreadInfo ti = new Argon2ThreadInfo(); + ti.Context = ctx; + + ti.Pass = r; + ti.Lane = (ulong)l; + ti.Slice = s; + + /*if(!ThreadPool.QueueUserWorkItem(FillSegmentThr, ti)) + { + Debug.Assert(false); + throw new OutOfMemoryException(); + }*/ + + v[l] = ti; + } + + for(int l = 0; l < np; ++l) + { + v[l].Finished.WaitOne(); + v[l].Release(); + } + } + } + } + + private static void FillSegmentThr(object o) + { + Argon2ThreadInfo ti = (o as Argon2ThreadInfo); + if(ti == null) { Debug.Assert(false); return; } + + try + { + Argon2Ctx ctx = ti.Context; + if(ctx == null) { Debug.Assert(false); return; } + + Debug.Assert(ctx.Version >= MinVersion); + bool bCanXor = (ctx.Version >= 0x13U); + + ulong uStart = 0; + if((ti.Pass == 0) && (ti.Slice == 0)) uStart = 2; + + ulong uCur = (ti.Lane * ctx.LaneLength) + (ti.Slice * + ctx.SegmentLength) + uStart; + + ulong uPrev = (((uCur % ctx.LaneLength) == 0) ? + (uCur + ctx.LaneLength - 1UL) : (uCur - 1UL)); + + ulong[] pbR = new ulong[NbBlockSizeInQW]; + ulong[] pbTmp = new ulong[NbBlockSizeInQW]; + + for(ulong i = uStart; i < ctx.SegmentLength; ++i) + { + if((uCur % ctx.LaneLength) == 1) + uPrev = uCur - 1UL; + + ulong uPseudoRand = ctx.Mem[uPrev * NbBlockSizeInQW]; + ulong uRefLane = (uPseudoRand >> 32) % ctx.Lanes; + if((ti.Pass == 0) && (ti.Slice == 0)) + uRefLane = ti.Lane; + + ti.Index = i; + ulong uRefIndex = IndexAlpha(ctx, ti, (uint)uPseudoRand, + (uRefLane == ti.Lane)); + + ulong uRefBlockIndex = (ctx.LaneLength * uRefLane + + uRefIndex) * NbBlockSizeInQW; + ulong uCurBlockIndex = uCur * NbBlockSizeInQW; + + FillBlock(ctx.Mem, uPrev * NbBlockSizeInQW, uRefBlockIndex, + uCurBlockIndex, ((ti.Pass != 0) && bCanXor), pbR, pbTmp); + + ++uCur; + ++uPrev; + } + + MemUtil.ZeroArray(pbR); + MemUtil.ZeroArray(pbTmp); + } + catch(Exception) { Debug.Assert(false); } + + try { ti.Finished.Set(); } + catch(Exception) { Debug.Assert(false); } + } + +#if ARGON2_B2ROUND_ARRAYS + private static void InitB2RoundIndexArrays() + { + int[][] vCols = g_vFBCols; + if(vCols == null) + { + vCols = new int[8][]; + Debug.Assert(vCols.Length == 8); + int e = 0; + for(int i = 0; i < 8; ++i) + { + vCols[i] = new int[16]; + for(int j = 0; j < 16; ++j) + { + vCols[i][j] = e; + ++e; + } + } + + g_vFBCols = vCols; + } + + int[][] vRows = g_vFBRows; + if(vRows == null) + { + vRows = new int[8][]; + for(int i = 0; i < 8; ++i) + { + vRows[i] = new int[16]; + for(int j = 0; j < 16; ++j) + { + int jh = j / 2; + vRows[i][j] = (2 * i) + (16 * jh) + (j & 1); + } + } + + g_vFBRows = vRows; + } + } +#endif + + private static void FillBlock(ulong[] pMem, ulong uPrev, ulong uRef, + ulong uNext, bool bXor, ulong[] pbR, ulong[] pbTmp) + { + CopyBlock(pbR, 0, pMem, uRef); + XorBlock(pbR, 0, pMem, uPrev); + CopyBlock(pbTmp, 0, pbR, 0); + if(bXor) XorBlock(pbTmp, 0, pMem, uNext); + +#if ARGON2_B2ROUND_ARRAYS + int[][] vCols = g_vFBCols; + int[][] vRows = g_vFBRows; + for(int i = 0; i < 8; ++i) + Blake2RoundNoMsg(pbR, vCols[i]); + for(int i = 0; i < 8; ++i) + Blake2RoundNoMsg(pbR, vRows[i]); +#else + for(int i = 0; i < (8 * 16); i += 16) + Blake2RoundNoMsgCols16i(pbR, i); + for(int i = 0; i < (8 * 2); i += 2) + Blake2RoundNoMsgRows2i(pbR, i); +#endif + + CopyBlock(pMem, uNext, pbTmp, 0); + XorBlock(pMem, uNext, pbR, 0); + } + + private static byte[] FinalHash(Argon2Ctx ctx, int cbOut, Blake2b h) + { + ulong[] pqBlockHash = new ulong[NbBlockSizeInQW]; + CopyBlock(pqBlockHash, 0, ctx.Mem, (ctx.LaneLength - 1UL) * + NbBlockSizeInQW); + for(ulong l = 1; l < ctx.Lanes; ++l) + XorBlock(pqBlockHash, 0, ctx.Mem, (l * ctx.LaneLength + + ctx.LaneLength - 1UL) * NbBlockSizeInQW); + + byte[] pbBlockHashBytes = new byte[NbBlockSize]; + StoreBlock(pbBlockHashBytes, pqBlockHash); + + byte[] pbOut = new byte[cbOut]; + Blake2bLong(pbOut, cbOut, pbBlockHashBytes, (int)NbBlockSize, h); + + MemUtil.ZeroArray(pqBlockHash); + MemUtil.ZeroByteArray(pbBlockHashBytes); + return pbOut; + } + } +} diff --git a/ModernKeePassLib/Cryptography/KeyDerivation/Argon2Kdf.cs b/ModernKeePassLib/Cryptography/KeyDerivation/Argon2Kdf.cs new file mode 100644 index 0000000..f4f8ff7 --- /dev/null +++ b/ModernKeePassLib/Cryptography/KeyDerivation/Argon2Kdf.cs @@ -0,0 +1,144 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace ModernKeePassLib.Cryptography.KeyDerivation +{ + public sealed partial class Argon2Kdf : KdfEngine + { + private static readonly PwUuid g_uuid = new PwUuid(new byte[] { + 0xEF, 0x63, 0x6D, 0xDF, 0x8C, 0x29, 0x44, 0x4B, + 0x91, 0xF7, 0xA9, 0xA4, 0x03, 0xE3, 0x0A, 0x0C }); + + public const string ParamSalt = "S"; // Byte[] + public const string ParamParallelism = "P"; // UInt32 + public const string ParamMemory = "M"; // UInt64 + public const string ParamIterations = "I"; // UInt64 + public const string ParamVersion = "V"; // UInt32 + public const string ParamSecretKey = "K"; // Byte[] + public const string ParamAssocData = "A"; // Byte[] + + private const uint MinVersion = 0x10; + private const uint MaxVersion = 0x13; + + private const int MinSalt = 8; + private const int MaxSalt = int.MaxValue; // .NET limit; 2^32 - 1 in spec + + internal const ulong MinIterations = 1; + internal const ulong MaxIterations = uint.MaxValue; + + internal const ulong MinMemory = 1024 * 8; // For parallelism = 1 + // internal const ulong MaxMemory = (ulong)uint.MaxValue * 1024UL; // Spec + internal const ulong MaxMemory = int.MaxValue; // .NET limit + + internal const uint MinParallelism = 1; + internal const uint MaxParallelism = (1 << 24) - 1; + + internal const ulong DefaultIterations = 2; + internal const ulong DefaultMemory = 1024 * 1024; // 1 MB + internal const uint DefaultParallelism = 2; + + public override PwUuid Uuid + { + get { return g_uuid; } + } + + public override string Name + { + get { return "Argon2"; } + } + + public Argon2Kdf() + { + } + + public override KdfParameters GetDefaultParameters() + { + KdfParameters p = base.GetDefaultParameters(); + + p.SetUInt32(ParamVersion, MaxVersion); + + p.SetUInt64(ParamIterations, DefaultIterations); + p.SetUInt64(ParamMemory, DefaultMemory); + p.SetUInt32(ParamParallelism, DefaultParallelism); + + return p; + } + + public override void Randomize(KdfParameters p) + { + if(p == null) { Debug.Assert(false); return; } + Debug.Assert(g_uuid.Equals(p.KdfUuid)); + + byte[] pb = CryptoRandom.Instance.GetRandomBytes(32); + p.SetByteArray(ParamSalt, pb); + } + + public override byte[] Transform(byte[] pbMsg, KdfParameters p) + { + if(pbMsg == null) throw new ArgumentNullException("pbMsg"); + if(p == null) throw new ArgumentNullException("p"); + + byte[] pbSalt = p.GetByteArray(ParamSalt); + if(pbSalt == null) + throw new ArgumentNullException("p.Salt"); + if((pbSalt.Length < MinSalt) || (pbSalt.Length > MaxSalt)) + throw new ArgumentOutOfRangeException("p.Salt"); + + uint uPar = p.GetUInt32(ParamParallelism, 0); + if((uPar < MinParallelism) || (uPar > MaxParallelism)) + throw new ArgumentOutOfRangeException("p.Parallelism"); + + ulong uMem = p.GetUInt64(ParamMemory, 0); + if((uMem < MinMemory) || (uMem > MaxMemory)) + throw new ArgumentOutOfRangeException("p.Memory"); + + ulong uIt = p.GetUInt64(ParamIterations, 0); + if((uIt < MinIterations) || (uIt > MaxIterations)) + throw new ArgumentOutOfRangeException("p.Iterations"); + + uint v = p.GetUInt32(ParamVersion, 0); + if((v < MinVersion) || (v > MaxVersion)) + throw new ArgumentOutOfRangeException("p.Version"); + + byte[] pbSecretKey = p.GetByteArray(ParamSecretKey); + byte[] pbAssocData = p.GetByteArray(ParamAssocData); + + byte[] pbRet = Argon2d(pbMsg, pbSalt, uPar, uMem, uIt, + 32, v, pbSecretKey, pbAssocData); + + if(uMem > (100UL * 1024UL * 1024UL)) GC.Collect(); + return pbRet; + } + + public override KdfParameters GetBestParameters(uint uMilliseconds) + { + KdfParameters p = GetDefaultParameters(); + Randomize(p); + + MaximizeParamUInt64(p, ParamIterations, MinIterations, + MaxIterations, uMilliseconds, true); + return p; + } + } +} diff --git a/ModernKeePassLib/Cryptography/KeyDerivation/KdfEngine.cs b/ModernKeePassLib/Cryptography/KeyDerivation/KdfEngine.cs new file mode 100644 index 0000000..c67c029 --- /dev/null +++ b/ModernKeePassLib/Cryptography/KeyDerivation/KdfEngine.cs @@ -0,0 +1,142 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace ModernKeePassLib.Cryptography.KeyDerivation +{ + public abstract class KdfEngine + { + public abstract PwUuid Uuid + { + get; + } + + public abstract string Name + { + get; + } + + public virtual KdfParameters GetDefaultParameters() + { + return new KdfParameters(this.Uuid); + } + + /// + /// Generate random seeds and store them in . + /// + public virtual void Randomize(KdfParameters p) + { + Debug.Assert(p != null); + Debug.Assert(p.KdfUuid.Equals(this.Uuid)); + } + + public abstract byte[] Transform(byte[] pbMsg, KdfParameters p); + + public virtual KdfParameters GetBestParameters(uint uMilliseconds) + { + throw new NotImplementedException(); + } + + protected void MaximizeParamUInt64(KdfParameters p, string strName, + ulong uMin, ulong uMax, uint uMilliseconds, bool bInterpSearch) + { + if(p == null) { Debug.Assert(false); return; } + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return; } + if(uMin > uMax) { Debug.Assert(false); return; } + + if(uMax > (ulong.MaxValue >> 1)) + { + Debug.Assert(false); + uMax = ulong.MaxValue >> 1; + + if(uMin > uMax) { p.SetUInt64(strName, uMin); return; } + } + + byte[] pbMsg = new byte[32]; + for(int i = 0; i < pbMsg.Length; ++i) pbMsg[i] = (byte)i; + + ulong uLow = uMin; + ulong uHigh = uMin + 1UL; + long tLow = 0; + long tHigh = 0; + long tTarget = (long)uMilliseconds; + + // Determine range + while(uHigh <= uMax) + { + p.SetUInt64(strName, uHigh); + + // GC.Collect(); + Stopwatch sw = Stopwatch.StartNew(); + Transform(pbMsg, p); + sw.Stop(); + + tHigh = sw.ElapsedMilliseconds; + if(tHigh > tTarget) break; + + uLow = uHigh; + tLow = tHigh; + uHigh <<= 1; + } + if(uHigh > uMax) { uHigh = uMax; tHigh = 0; } + if(uLow > uHigh) uLow = uHigh; // Skips to end + + // Find optimal number of iterations + while((uHigh - uLow) >= 2UL) + { + ulong u = (uHigh + uLow) >> 1; // Binary search + // Interpolation search, if possible + if(bInterpSearch && (tLow > 0) && (tHigh > tTarget) && + (tLow <= tTarget)) + { + u = uLow + (((uHigh - uLow) * (ulong)(tTarget - tLow)) / + (ulong)(tHigh - tLow)); + if((u >= uLow) && (u <= uHigh)) + { + u = Math.Max(u, uLow + 1UL); + u = Math.Min(u, uHigh - 1UL); + } + else + { + Debug.Assert(false); + u = (uHigh + uLow) >> 1; + } + } + + p.SetUInt64(strName, u); + + // GC.Collect(); + Stopwatch sw = Stopwatch.StartNew(); + Transform(pbMsg, p); + sw.Stop(); + + long t = sw.ElapsedMilliseconds; + if(t == tTarget) { uLow = u; break; } + else if(t > tTarget) { uHigh = u; tHigh = t; } + else { uLow = u; tLow = t; } + } + + p.SetUInt64(strName, uLow); + } + } +} diff --git a/ModernKeePassLib/Cryptography/KeyDerivation/KdfParameters.cs b/ModernKeePassLib/Cryptography/KeyDerivation/KdfParameters.cs new file mode 100644 index 0000000..d3ec8ea --- /dev/null +++ b/ModernKeePassLib/Cryptography/KeyDerivation/KdfParameters.cs @@ -0,0 +1,80 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +using ModernKeePassLib.Collections; +using ModernKeePassLib.Utility; + +namespace ModernKeePassLib.Cryptography.KeyDerivation +{ + public sealed class KdfParameters : VariantDictionary + { + private const string ParamUuid = @"$UUID"; + + private readonly PwUuid m_puKdf; + public PwUuid KdfUuid + { + get { return m_puKdf; } + } + + public KdfParameters(PwUuid puKdf) + { + if(puKdf == null) throw new ArgumentNullException("puKdf"); + + m_puKdf = puKdf; + SetByteArray(ParamUuid, puKdf.UuidBytes); + } + + /// + /// Unsupported. + /// + public override object Clone() + { + throw new NotSupportedException(); + } + + public static byte[] SerializeExt(KdfParameters p) + { + return VariantDictionary.Serialize(p); + } + + public static KdfParameters DeserializeExt(byte[] pb) + { + VariantDictionary d = VariantDictionary.Deserialize(pb); + if(d == null) { Debug.Assert(false); return null; } + + byte[] pbUuid = d.GetByteArray(ParamUuid); + if((pbUuid == null) || (pbUuid.Length != (int)PwUuid.UuidSize)) + { + Debug.Assert(false); + return null; + } + + PwUuid pu = new PwUuid(pbUuid); + KdfParameters p = new KdfParameters(pu); + d.CopyTo(p); + return p; + } + } +} diff --git a/ModernKeePassLib/Cryptography/KeyDerivation/KdfPool.cs b/ModernKeePassLib/Cryptography/KeyDerivation/KdfPool.cs new file mode 100644 index 0000000..1829209 --- /dev/null +++ b/ModernKeePassLib/Cryptography/KeyDerivation/KdfPool.cs @@ -0,0 +1,96 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +using ModernKeePassLib.Utility; + +namespace ModernKeePassLib.Cryptography.KeyDerivation +{ + public static class KdfPool + { + private static List g_l = new List(); + + public static IEnumerable Engines + { + get + { + EnsureInitialized(); + return g_l; + } + } + + private static void EnsureInitialized() + { + if(g_l.Count > 0) return; + + g_l.Add(new AesKdf()); + g_l.Add(new Argon2Kdf()); + } + + internal static KdfParameters GetDefaultParameters() + { + EnsureInitialized(); + return g_l[0].GetDefaultParameters(); + } + + public static KdfEngine Get(PwUuid pu) + { + if(pu == null) { Debug.Assert(false); return null; } + + EnsureInitialized(); + + foreach(KdfEngine kdf in g_l) + { + if(pu.Equals(kdf.Uuid)) return kdf; + } + + return null; + } + + public static KdfEngine Get(string strName) + { + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return null; } + + EnsureInitialized(); + + foreach(KdfEngine kdf in g_l) + { + if(strName.Equals(kdf.Name, StrUtil.CaseIgnoreCmp)) return kdf; + } + + return null; + } + + public static void Add(KdfEngine kdf) + { + if(kdf == null) { Debug.Assert(false); return; } + + EnsureInitialized(); + + if(Get(kdf.Uuid) != null) { Debug.Assert(false); return; } + if(Get(kdf.Name) != null) { Debug.Assert(false); return; } + + g_l.Add(kdf); + } + } +} diff --git a/ModernKeePassLib/Cryptography/PasswordGenerator/CharSetBasedGenerator.cs b/ModernKeePassLib/Cryptography/PasswordGenerator/CharSetBasedGenerator.cs index 228d513..9136eea 100644 --- a/ModernKeePassLib/Cryptography/PasswordGenerator/CharSetBasedGenerator.cs +++ b/ModernKeePassLib/Cryptography/PasswordGenerator/CharSetBasedGenerator.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -47,7 +47,7 @@ namespace ModernKeePassLib.Cryptography.PasswordGenerator if(ch == char.MinValue) { - Array.Clear(vGenerated, 0, vGenerated.Length); + MemUtil.ZeroArray(vGenerated); return PwgError.TooFewCharacters; } @@ -57,7 +57,7 @@ namespace ModernKeePassLib.Cryptography.PasswordGenerator byte[] pbUtf8 = StrUtil.Utf8.GetBytes(vGenerated); psOut = new ProtectedString(true, pbUtf8); MemUtil.ZeroByteArray(pbUtf8); - Array.Clear(vGenerated, 0, vGenerated.Length); + MemUtil.ZeroArray(vGenerated); return PwgError.Success; } diff --git a/ModernKeePassLib/Cryptography/PasswordGenerator/CustomPwGenerator.cs b/ModernKeePassLib/Cryptography/PasswordGenerator/CustomPwGenerator.cs index c1df173..90b4c35 100644 --- a/ModernKeePassLib/Cryptography/PasswordGenerator/CustomPwGenerator.cs +++ b/ModernKeePassLib/Cryptography/PasswordGenerator/CustomPwGenerator.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/Cryptography/PasswordGenerator/CustomPwGeneratorPool.cs b/ModernKeePassLib/Cryptography/PasswordGenerator/CustomPwGeneratorPool.cs index d3a55f7..c8ca074 100644 --- a/ModernKeePassLib/Cryptography/PasswordGenerator/CustomPwGeneratorPool.cs +++ b/ModernKeePassLib/Cryptography/PasswordGenerator/CustomPwGeneratorPool.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/Cryptography/PasswordGenerator/PatternBasedGenerator.cs b/ModernKeePassLib/Cryptography/PasswordGenerator/PatternBasedGenerator.cs index 4ade1e8..0754b49 100644 --- a/ModernKeePassLib/Cryptography/PasswordGenerator/PatternBasedGenerator.cs +++ b/ModernKeePassLib/Cryptography/PasswordGenerator/PatternBasedGenerator.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -132,7 +132,7 @@ namespace ModernKeePassLib.Cryptography.PasswordGenerator byte[] pbUtf8 = StrUtil.Utf8.GetBytes(vArray); psOut = new ProtectedString(true, pbUtf8); MemUtil.ZeroByteArray(pbUtf8); - Array.Clear(vArray, 0, vArray.Length); + MemUtil.ZeroArray(vArray); vGenerated.Clear(); return PwgError.Success; diff --git a/ModernKeePassLib/Cryptography/PasswordGenerator/PwCharSet.cs b/ModernKeePassLib/Cryptography/PasswordGenerator/PwCharSet.cs index b51e752..6646cd9 100644 --- a/ModernKeePassLib/Cryptography/PasswordGenerator/PwCharSet.cs +++ b/ModernKeePassLib/Cryptography/PasswordGenerator/PwCharSet.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/Cryptography/PasswordGenerator/PwGenerator.cs b/ModernKeePassLib/Cryptography/PasswordGenerator/PwGenerator.cs index 01c5ea5..b915cf4 100644 --- a/ModernKeePassLib/Cryptography/PasswordGenerator/PwGenerator.cs +++ b/ModernKeePassLib/Cryptography/PasswordGenerator/PwGenerator.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,10 +19,13 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Text; using System.Diagnostics; - +using Windows.Security.Cryptography; +using Windows.Security.Cryptography.Core; using ModernKeePassLib.Security; +using ModernKeePassLib.Utility; namespace ModernKeePassLib.Cryptography.PasswordGenerator { @@ -46,32 +49,56 @@ namespace ModernKeePassLib.Cryptography.PasswordGenerator Debug.Assert(pwProfile != null); if(pwProfile == null) throw new ArgumentNullException("pwProfile"); - CryptoRandomStream crs = CreateCryptoStream(pbUserEntropy); PwgError e = PwgError.Unknown; + CryptoRandomStream crs = null; + byte[] pbKey = null; + try + { + crs = CreateRandomStream(pbUserEntropy, out pbKey); - if(pwProfile.GeneratorType == PasswordGeneratorType.CharSet) - e = CharSetBasedGenerator.Generate(out psOut, pwProfile, crs); - else if(pwProfile.GeneratorType == PasswordGeneratorType.Pattern) - e = PatternBasedGenerator.Generate(out psOut, pwProfile, crs); - else if(pwProfile.GeneratorType == PasswordGeneratorType.Custom) - e = GenerateCustom(out psOut, pwProfile, crs, pwAlgorithmPool); - else { Debug.Assert(false); psOut = ProtectedString.Empty; } + if(pwProfile.GeneratorType == PasswordGeneratorType.CharSet) + e = CharSetBasedGenerator.Generate(out psOut, pwProfile, crs); + else if(pwProfile.GeneratorType == PasswordGeneratorType.Pattern) + e = PatternBasedGenerator.Generate(out psOut, pwProfile, crs); + else if(pwProfile.GeneratorType == PasswordGeneratorType.Custom) + e = GenerateCustom(out psOut, pwProfile, crs, pwAlgorithmPool); + else { Debug.Assert(false); psOut = ProtectedString.Empty; } + } + finally + { + if(crs != null) crs.Dispose(); + if(pbKey != null) MemUtil.ZeroByteArray(pbKey); + } return e; } - private static CryptoRandomStream CreateCryptoStream(byte[] pbAdditionalEntropy) + private static CryptoRandomStream CreateRandomStream(byte[] pbAdditionalEntropy, + out byte[] pbKey) { - byte[] pbKey = CryptoRandom.Instance.GetRandomBytes(256); + pbKey = CryptoRandom.Instance.GetRandomBytes(128); // Mix in additional entropy + Debug.Assert(pbKey.Length >= 64); if((pbAdditionalEntropy != null) && (pbAdditionalEntropy.Length > 0)) { - for(int nKeyPos = 0; nKeyPos < pbKey.Length; ++nKeyPos) - pbKey[nKeyPos] ^= pbAdditionalEntropy[nKeyPos % pbAdditionalEntropy.Length]; +#if ModernKeePassLib + var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256); + var buffer = sha256.HashData(CryptographicBuffer.CreateFromByteArray(pbAdditionalEntropy)); + byte[] pbHash; + CryptographicBuffer.CopyToByteArray(buffer, out pbHash); + MemUtil.XorArray(pbHash, 0, pbKey, 0, pbHash.Length); + +#else + using(SHA512Managed h = new SHA512Managed()) + { + byte[] pbHash = h.ComputeHash(pbAdditionalEntropy); + MemUtil.XorArray(pbHash, 0, pbKey, 0, pbHash.Length); + } +#endif } - return new CryptoRandomStream(CrsAlgorithm.Salsa20, pbKey); + return new CryptoRandomStream(CrsAlgorithm.ChaCha20, pbKey); } internal static char GenerateCharacter(PwProfile pwProfile, diff --git a/ModernKeePassLib/Cryptography/PasswordGenerator/PwProfile.cs b/ModernKeePassLib/Cryptography/PasswordGenerator/PwProfile.cs index 3ad5265..20f38a2 100644 --- a/ModernKeePassLib/Cryptography/PasswordGenerator/PwProfile.cs +++ b/ModernKeePassLib/Cryptography/PasswordGenerator/PwProfile.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/Cryptography/PopularPasswords.cs b/ModernKeePassLib/Cryptography/PopularPasswords.cs index e5a4521..ecbf08c 100644 --- a/ModernKeePassLib/Cryptography/PopularPasswords.cs +++ b/ModernKeePassLib/Cryptography/PopularPasswords.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/Cryptography/QualityEstimation.cs b/ModernKeePassLib/Cryptography/QualityEstimation.cs index 1272d7c..375b425 100644 --- a/ModernKeePassLib/Cryptography/QualityEstimation.cs +++ b/ModernKeePassLib/Cryptography/QualityEstimation.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -281,7 +281,7 @@ namespace ModernKeePassLib.Cryptography } } - private static object m_objSyncInit = new object(); + private static readonly object m_objSyncInit = new object(); private static List m_lCharTypes = null; private static void EnsureInitialized() @@ -422,7 +422,7 @@ namespace ModernKeePassLib.Cryptography char[] vChars = StrUtil.Utf8.GetChars(pbUnprotectedUtf8); uint uResult = EstimatePasswordBits(vChars); - Array.Clear(vChars, 0, vChars.Length); + MemUtil.ZeroArray(vChars); return uResult; } diff --git a/ModernKeePassLib/Cryptography/SelfTest.cs b/ModernKeePassLib/Cryptography/SelfTest.cs index 3b71b91..3c8d842 100644 --- a/ModernKeePassLib/Cryptography/SelfTest.cs +++ b/ModernKeePassLib/Cryptography/SelfTest.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -28,30 +28,29 @@ using System.Security.Cryptography; using System.Text; using System.Globalization; using System.Diagnostics; - +using System.IO; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.Security.Cryptography.Core; using ModernKeePassLib.Cryptography.Cipher; +using ModernKeePassLib.Cryptography.Hash; +using ModernKeePassLib.Cryptography.KeyDerivation; using ModernKeePassLib.Keys; using ModernKeePassLib.Native; using ModernKeePassLib.Utility; using ModernKeePassLib.Resources; using ModernKeePassLib.Security; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Macs; +using Org.BouncyCastle.Crypto.Parameters; +using KdfParameters = Org.BouncyCastle.Crypto.Parameters.KdfParameters; namespace ModernKeePassLib.Cryptography { - /* /// - /// Return values of the SelfTest.Perform method. - /// - public enum SelfTestResult - { - Success = 0, - RijndaelEcbError = 1, - Salsa20Error = 2, - NativeKeyTransformationError = 3 - } */ - /// /// Class containing self-test methods. /// + // TODO: move all this into the Unit Tests project public static class SelfTest { /// @@ -59,47 +58,63 @@ namespace ModernKeePassLib.Cryptography /// public static void Perform() { + Random r = CryptoRandom.NewWeakRandom(); + TestFipsComplianceProblems(); // Must be the first test - TestRijndael(); - TestSalsa20(); + TestAes(); + TestSalsa20(r); + TestChaCha20(r); + TestBlake2b(r); + TestArgon2(); + TestHmac(); + TestKeyTransform(r); #if !ModernKeePassLib - TestNativeKeyTransform(); + TestNativeKeyTransform(r); #endif - TestHmacOtp(); - TestProtectedObjects(); - TestMemUtil(); + TestProtectedObjects(r); + TestMemUtil(r); TestStrUtil(); TestUrlUtil(); Debug.Assert((int)PwIcon.World == 1); Debug.Assert((int)PwIcon.Warning == 2); Debug.Assert((int)PwIcon.BlackBerry == 68); + +#if KeePassUAP + SelfTestEx.Perform(); +#endif } internal static void TestFipsComplianceProblems() { -#if !ModernKeePassLib && !KeePassRT - try { new RijndaelManaged(); } +#if !ModernKeePassLib + try { using(RijndaelManaged r = new RijndaelManaged()) { } } catch(Exception exAes) { throw new SecurityException("AES/Rijndael: " + exAes.Message); } +#endif - try { new SHA256Managed(); } +#if ModernKeePassLib + try + { + HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256); + } +#else + try { using(SHA256Managed h = new SHA256Managed()) { } } +#endif catch(Exception exSha256) { throw new SecurityException("SHA-256: " + exSha256.Message); } -#endif } - private static void TestRijndael() + private static void TestAes() { -#if !ModernKeePassLib && !KeePassRT // Test vector (official ECB test vector #356) byte[] pbIV = new byte[16]; byte[] pbTestKey = new byte[32]; @@ -114,31 +129,38 @@ namespace ModernKeePassLib.Cryptography for(i = 0; i < 16; ++i) pbTestData[i] = 0; pbTestData[0] = 0x04; - RijndaelManaged r = new RijndaelManaged(); - - if(r.BlockSize != 128) // AES block size +#if ModernKeePassLib + AesEngine r = new AesEngine(); + r.Init(true, new KeyParameter(pbTestKey)); + if(r.GetBlockSize() != pbTestData.Length) + throw new SecurityException("AES (BC)"); + r.ProcessBlock(pbTestData, 0, pbTestData, 0); +#else + SymmetricAlgorithm a = CryptoUtil.CreateAes(); + if(a.BlockSize != 128) // AES block size { Debug.Assert(false); - r.BlockSize = 128; + a.BlockSize = 128; } - r.IV = pbIV; - r.KeySize = 256; - r.Key = pbTestKey; - r.Mode = CipherMode.ECB; - ICryptoTransform iCrypt = r.CreateEncryptor(); + a.IV = pbIV; + a.KeySize = 256; + a.Key = pbTestKey; + a.Mode = CipherMode.ECB; + ICryptoTransform iCrypt = a.CreateEncryptor(); iCrypt.TransformBlock(pbTestData, 0, 16, pbTestData, 0); +#endif if(!MemUtil.ArraysEqual(pbTestData, pbReferenceCT)) - throw new SecurityException(KLRes.EncAlgorithmAes + "."); -#endif + throw new SecurityException("AES"); } - private static void TestSalsa20() + private static void TestSalsa20(Random r) { +#if DEBUG // Test values from official set 6, vector 3 - byte[] pbKey= new byte[32] { + byte[] pbKey = new byte[32] { 0x0F, 0x62, 0xB5, 0x08, 0x5B, 0xAE, 0x01, 0x54, 0xA7, 0xFA, 0x4D, 0xA0, 0xF3, 0x46, 0x99, 0xEC, 0x3F, 0x92, 0xE5, 0x38, 0x8B, 0xDE, 0x31, 0x84, @@ -153,12 +175,11 @@ namespace ModernKeePassLib.Cryptography byte[] pb = new byte[16]; Salsa20Cipher c = new Salsa20Cipher(pbKey, pbIV); - c.Encrypt(pb, pb.Length, false); + c.Encrypt(pb, 0, pb.Length); if(!MemUtil.ArraysEqual(pb, pbExpected)) throw new SecurityException("Salsa20-1"); -#if DEBUG - // Extended test in debug mode + // Extended test byte[] pbExpected2 = new byte[16] { 0xAB, 0xF3, 0x9A, 0x21, 0x0E, 0xEE, 0x89, 0x59, 0x8B, 0x71, 0x33, 0x37, 0x70, 0x56, 0xC2, 0xFE @@ -168,15 +189,15 @@ namespace ModernKeePassLib.Cryptography 0x28, 0xF5, 0x67, 0x91, 0xD5, 0xB7, 0xCE, 0x23 }; - Random r = new Random(); int nPos = Salsa20ToPos(c, r, pb.Length, 65536); - c.Encrypt(pb, pb.Length, false); + Array.Clear(pb, 0, pb.Length); + c.Encrypt(pb, 0, pb.Length); if(!MemUtil.ArraysEqual(pb, pbExpected2)) throw new SecurityException("Salsa20-2"); nPos = Salsa20ToPos(c, r, nPos + pb.Length, 131008); Array.Clear(pb, 0, pb.Length); - c.Encrypt(pb, pb.Length, true); + c.Encrypt(pb, 0, pb.Length); if(!MemUtil.ArraysEqual(pb, pbExpected3)) throw new SecurityException("Salsa20-3"); @@ -185,8 +206,8 @@ namespace ModernKeePassLib.Cryptography for(int i = 0; i < nRounds; ++i) { byte[] z = new byte[32]; - c = new Salsa20Cipher(z, BitConverter.GetBytes((long)i)); - c.Encrypt(z, z.Length, true); + c = new Salsa20Cipher(z, MemUtil.Int64ToBytes(i)); + c.Encrypt(z, 0, z.Length); d[MemUtil.ByteArrayToHexString(z)] = true; } if(d.Count != nRounds) throw new SecurityException("Salsa20-4"); @@ -203,7 +224,7 @@ namespace ModernKeePassLib.Cryptography { int x = r.Next(1, 513); int nGen = Math.Min(nTargetPos - nPos, x); - c.Encrypt(pb, nGen, r.Next(0, 2) == 0); + c.Encrypt(pb, 0, nGen); nPos += nGen; } @@ -211,34 +232,571 @@ namespace ModernKeePassLib.Cryptography } #endif + private static void TestChaCha20(Random r) + { + // ====================================================== + // Test vector from RFC 7539, section 2.3.2 + + byte[] pbKey = new byte[32]; + for(int i = 0; i < 32; ++i) pbKey[i] = (byte)i; + + byte[] pbIV = new byte[12]; + pbIV[3] = 0x09; + pbIV[7] = 0x4A; + + byte[] pbExpc = new byte[64] { + 0x10, 0xF1, 0xE7, 0xE4, 0xD1, 0x3B, 0x59, 0x15, + 0x50, 0x0F, 0xDD, 0x1F, 0xA3, 0x20, 0x71, 0xC4, + 0xC7, 0xD1, 0xF4, 0xC7, 0x33, 0xC0, 0x68, 0x03, + 0x04, 0x22, 0xAA, 0x9A, 0xC3, 0xD4, 0x6C, 0x4E, + 0xD2, 0x82, 0x64, 0x46, 0x07, 0x9F, 0xAA, 0x09, + 0x14, 0xC2, 0xD7, 0x05, 0xD9, 0x8B, 0x02, 0xA2, + 0xB5, 0x12, 0x9C, 0xD1, 0xDE, 0x16, 0x4E, 0xB9, + 0xCB, 0xD0, 0x83, 0xE8, 0xA2, 0x50, 0x3C, 0x4E + }; + + byte[] pb = new byte[64]; + + using(ChaCha20Cipher c = new ChaCha20Cipher(pbKey, pbIV)) + { + c.Seek(64, SeekOrigin.Begin); // Skip first block + c.Encrypt(pb, 0, pb.Length); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("ChaCha20-1"); + } + +#if DEBUG + // ====================================================== + // Test vector from RFC 7539, section 2.4.2 + + pbIV[3] = 0; + + pb = StrUtil.Utf8.GetBytes("Ladies and Gentlemen of the clas" + + @"s of '99: If I could offer you only one tip for " + + @"the future, sunscreen would be it."); + + pbExpc = new byte[] { + 0x6E, 0x2E, 0x35, 0x9A, 0x25, 0x68, 0xF9, 0x80, + 0x41, 0xBA, 0x07, 0x28, 0xDD, 0x0D, 0x69, 0x81, + 0xE9, 0x7E, 0x7A, 0xEC, 0x1D, 0x43, 0x60, 0xC2, + 0x0A, 0x27, 0xAF, 0xCC, 0xFD, 0x9F, 0xAE, 0x0B, + 0xF9, 0x1B, 0x65, 0xC5, 0x52, 0x47, 0x33, 0xAB, + 0x8F, 0x59, 0x3D, 0xAB, 0xCD, 0x62, 0xB3, 0x57, + 0x16, 0x39, 0xD6, 0x24, 0xE6, 0x51, 0x52, 0xAB, + 0x8F, 0x53, 0x0C, 0x35, 0x9F, 0x08, 0x61, 0xD8, + 0x07, 0xCA, 0x0D, 0xBF, 0x50, 0x0D, 0x6A, 0x61, + 0x56, 0xA3, 0x8E, 0x08, 0x8A, 0x22, 0xB6, 0x5E, + 0x52, 0xBC, 0x51, 0x4D, 0x16, 0xCC, 0xF8, 0x06, + 0x81, 0x8C, 0xE9, 0x1A, 0xB7, 0x79, 0x37, 0x36, + 0x5A, 0xF9, 0x0B, 0xBF, 0x74, 0xA3, 0x5B, 0xE6, + 0xB4, 0x0B, 0x8E, 0xED, 0xF2, 0x78, 0x5E, 0x42, + 0x87, 0x4D + }; + + byte[] pb64 = new byte[64]; + + using(ChaCha20Cipher c = new ChaCha20Cipher(pbKey, pbIV)) + { + c.Encrypt(pb64, 0, pb64.Length); // Skip first block + c.Encrypt(pb, 0, pb.Length); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("ChaCha20-2"); + } + + // ====================================================== + // Test vector from RFC 7539, appendix A.2 #2 + + Array.Clear(pbKey, 0, pbKey.Length); + pbKey[31] = 1; + + Array.Clear(pbIV, 0, pbIV.Length); + pbIV[11] = 2; + + pb = StrUtil.Utf8.GetBytes("Any submission to the IETF inten" + + "ded by the Contributor for publication as all or" + + " part of an IETF Internet-Draft or RFC and any s" + + "tatement made within the context of an IETF acti" + + "vity is considered an \"IETF Contribution\". Such " + + "statements include oral statements in IETF sessi" + + "ons, as well as written and electronic communica" + + "tions made at any time or place, which are addressed to"); + + pbExpc = MemUtil.HexStringToByteArray( + "A3FBF07DF3FA2FDE4F376CA23E82737041605D9F4F4F57BD8CFF2C1D4B7955EC" + + "2A97948BD3722915C8F3D337F7D370050E9E96D647B7C39F56E031CA5EB6250D" + + "4042E02785ECECFA4B4BB5E8EAD0440E20B6E8DB09D881A7C6132F420E527950" + + "42BDFA7773D8A9051447B3291CE1411C680465552AA6C405B7764D5E87BEA85A" + + "D00F8449ED8F72D0D662AB052691CA66424BC86D2DF80EA41F43ABF937D3259D" + + "C4B2D0DFB48A6C9139DDD7F76966E928E635553BA76C5C879D7B35D49EB2E62B" + + "0871CDAC638939E25E8A1E0EF9D5280FA8CA328B351C3C765989CBCF3DAA8B6C" + + "CC3AAF9F3979C92B3720FC88DC95ED84A1BE059C6499B9FDA236E7E818B04B0B" + + "C39C1E876B193BFE5569753F88128CC08AAA9B63D1A16F80EF2554D7189C411F" + + "5869CA52C5B83FA36FF216B9C1D30062BEBCFD2DC5BCE0911934FDA79A86F6E6" + + "98CED759C3FF9B6477338F3DA4F9CD8514EA9982CCAFB341B2384DD902F3D1AB" + + "7AC61DD29C6F21BA5B862F3730E37CFDC4FD806C22F221"); + + using(MemoryStream msEnc = new MemoryStream()) + { + using(ChaCha20Stream c = new ChaCha20Stream(msEnc, true, pbKey, pbIV)) + { + r.NextBytes(pb64); + c.Write(pb64, 0, pb64.Length); // Skip first block + + int p = 0; + while(p < pb.Length) + { + int cb = r.Next(1, pb.Length - p + 1); + c.Write(pb, p, cb); + p += cb; + } + Debug.Assert(p == pb.Length); + } + + byte[] pbEnc0 = msEnc.ToArray(); + byte[] pbEnc = MemUtil.Mid(pbEnc0, 64, pbEnc0.Length - 64); + if(!MemUtil.ArraysEqual(pbEnc, pbExpc)) + throw new SecurityException("ChaCha20-3"); + + using(MemoryStream msCT = new MemoryStream(pbEnc0, false)) + { + using(ChaCha20Stream cDec = new ChaCha20Stream(msCT, false, + pbKey, pbIV)) + { + byte[] pbPT = MemUtil.Read(cDec, pbEnc0.Length); + if(cDec.ReadByte() >= 0) + throw new SecurityException("ChaCha20-4"); + if(!MemUtil.ArraysEqual(MemUtil.Mid(pbPT, 0, 64), pb64)) + throw new SecurityException("ChaCha20-5"); + if(!MemUtil.ArraysEqual(MemUtil.Mid(pbPT, 64, pbEnc.Length), pb)) + throw new SecurityException("ChaCha20-6"); + } + } + } + + // ====================================================== + // Test vector TC8 from RFC draft by J. Strombergson: + // https://tools.ietf.org/html/draft-strombergson-chacha-test-vectors-01 + + pbKey = new byte[32] { + 0xC4, 0x6E, 0xC1, 0xB1, 0x8C, 0xE8, 0xA8, 0x78, + 0x72, 0x5A, 0x37, 0xE7, 0x80, 0xDF, 0xB7, 0x35, + 0x1F, 0x68, 0xED, 0x2E, 0x19, 0x4C, 0x79, 0xFB, + 0xC6, 0xAE, 0xBE, 0xE1, 0xA6, 0x67, 0x97, 0x5D + }; + + // The first 4 bytes are set to zero and a large counter + // is used; this makes the RFC 7539 version of ChaCha20 + // compatible with the original specification by + // D. J. Bernstein. + pbIV = new byte[12] { 0x00, 0x00, 0x00, 0x00, + 0x1A, 0xDA, 0x31, 0xD5, 0xCF, 0x68, 0x82, 0x21 + }; + + pb = new byte[128]; + + pbExpc = new byte[128] { + 0xF6, 0x3A, 0x89, 0xB7, 0x5C, 0x22, 0x71, 0xF9, + 0x36, 0x88, 0x16, 0x54, 0x2B, 0xA5, 0x2F, 0x06, + 0xED, 0x49, 0x24, 0x17, 0x92, 0x30, 0x2B, 0x00, + 0xB5, 0xE8, 0xF8, 0x0A, 0xE9, 0xA4, 0x73, 0xAF, + 0xC2, 0x5B, 0x21, 0x8F, 0x51, 0x9A, 0xF0, 0xFD, + 0xD4, 0x06, 0x36, 0x2E, 0x8D, 0x69, 0xDE, 0x7F, + 0x54, 0xC6, 0x04, 0xA6, 0xE0, 0x0F, 0x35, 0x3F, + 0x11, 0x0F, 0x77, 0x1B, 0xDC, 0xA8, 0xAB, 0x92, + + 0xE5, 0xFB, 0xC3, 0x4E, 0x60, 0xA1, 0xD9, 0xA9, + 0xDB, 0x17, 0x34, 0x5B, 0x0A, 0x40, 0x27, 0x36, + 0x85, 0x3B, 0xF9, 0x10, 0xB0, 0x60, 0xBD, 0xF1, + 0xF8, 0x97, 0xB6, 0x29, 0x0F, 0x01, 0xD1, 0x38, + 0xAE, 0x2C, 0x4C, 0x90, 0x22, 0x5B, 0xA9, 0xEA, + 0x14, 0xD5, 0x18, 0xF5, 0x59, 0x29, 0xDE, 0xA0, + 0x98, 0xCA, 0x7A, 0x6C, 0xCF, 0xE6, 0x12, 0x27, + 0x05, 0x3C, 0x84, 0xE4, 0x9A, 0x4A, 0x33, 0x32 + }; + + using(ChaCha20Cipher c = new ChaCha20Cipher(pbKey, pbIV, true)) + { + c.Decrypt(pb, 0, pb.Length); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("ChaCha20-7"); + } +#endif + } + + private static void TestBlake2b(Random r) + { +#if !ModernKeePassLib && DEBUG + Blake2b h = new Blake2b(); + + // ====================================================== + // From https://tools.ietf.org/html/rfc7693 + + byte[] pbData = StrUtil.Utf8.GetBytes("abc"); + byte[] pbExpc = new byte[64] { + 0xBA, 0x80, 0xA5, 0x3F, 0x98, 0x1C, 0x4D, 0x0D, + 0x6A, 0x27, 0x97, 0xB6, 0x9F, 0x12, 0xF6, 0xE9, + 0x4C, 0x21, 0x2F, 0x14, 0x68, 0x5A, 0xC4, 0xB7, + 0x4B, 0x12, 0xBB, 0x6F, 0xDB, 0xFF, 0xA2, 0xD1, + 0x7D, 0x87, 0xC5, 0x39, 0x2A, 0xAB, 0x79, 0x2D, + 0xC2, 0x52, 0xD5, 0xDE, 0x45, 0x33, 0xCC, 0x95, + 0x18, 0xD3, 0x8A, 0xA8, 0xDB, 0xF1, 0x92, 0x5A, + 0xB9, 0x23, 0x86, 0xED, 0xD4, 0x00, 0x99, 0x23 + }; + + byte[] pbC = h.ComputeHash(pbData); + if(!MemUtil.ArraysEqual(pbC, pbExpc)) + throw new SecurityException("Blake2b-1"); + + // ====================================================== + // Computed using the official b2sum tool + + pbExpc = new byte[64] { + 0x78, 0x6A, 0x02, 0xF7, 0x42, 0x01, 0x59, 0x03, + 0xC6, 0xC6, 0xFD, 0x85, 0x25, 0x52, 0xD2, 0x72, + 0x91, 0x2F, 0x47, 0x40, 0xE1, 0x58, 0x47, 0x61, + 0x8A, 0x86, 0xE2, 0x17, 0xF7, 0x1F, 0x54, 0x19, + 0xD2, 0x5E, 0x10, 0x31, 0xAF, 0xEE, 0x58, 0x53, + 0x13, 0x89, 0x64, 0x44, 0x93, 0x4E, 0xB0, 0x4B, + 0x90, 0x3A, 0x68, 0x5B, 0x14, 0x48, 0xB7, 0x55, + 0xD5, 0x6F, 0x70, 0x1A, 0xFE, 0x9B, 0xE2, 0xCE + }; + + pbC = h.ComputeHash(MemUtil.EmptyByteArray); + if(!MemUtil.ArraysEqual(pbC, pbExpc)) + throw new SecurityException("Blake2b-2"); + + // ====================================================== + // Computed using the official b2sum tool + + string strS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.:,;_-\r\n"; + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < 1000; ++i) sb.Append(strS); + pbData = StrUtil.Utf8.GetBytes(sb.ToString()); + + pbExpc = new byte[64] { + 0x59, 0x69, 0x8D, 0x3B, 0x83, 0xF4, 0x02, 0x4E, + 0xD8, 0x99, 0x26, 0x0E, 0xF4, 0xE5, 0x9F, 0x20, + 0xDC, 0x31, 0xEE, 0x5B, 0x45, 0xEA, 0xBB, 0xFC, + 0x1C, 0x0A, 0x8E, 0xED, 0xAA, 0x7A, 0xFF, 0x50, + 0x82, 0xA5, 0x8F, 0xBC, 0x4A, 0x46, 0xFC, 0xC5, + 0xEF, 0x44, 0x4E, 0x89, 0x80, 0x7D, 0x3F, 0x1C, + 0xC1, 0x94, 0x45, 0xBB, 0xC0, 0x2C, 0x95, 0xAA, + 0x3F, 0x08, 0x8A, 0x93, 0xF8, 0x75, 0x91, 0xB0 + }; + + int p = 0; + while(p < pbData.Length) + { + int cb = r.Next(1, pbData.Length - p + 1); + h.TransformBlock(pbData, p, cb, pbData, p); + p += cb; + } + Debug.Assert(p == pbData.Length); + + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + if(!MemUtil.ArraysEqual(h.Hash, pbExpc)) + throw new SecurityException("Blake2b-3"); + + h.Clear(); +#endif + } + + private static void TestArgon2() + { +#if DEBUG + Argon2Kdf kdf = new Argon2Kdf(); + + // ====================================================== + // From the official Argon2 1.3 reference code package + // (test vector for Argon2d 1.3); also on + // https://tools.ietf.org/html/draft-irtf-cfrg-argon2-00 + + var p = kdf.GetDefaultParameters(); + kdf.Randomize(p); + + Debug.Assert(p.GetUInt32(Argon2Kdf.ParamVersion, 0) == 0x13U); + + byte[] pbMsg = new byte[32]; + for(int i = 0; i < pbMsg.Length; ++i) pbMsg[i] = 1; + + p.SetUInt64(Argon2Kdf.ParamMemory, 32 * 1024); + p.SetUInt64(Argon2Kdf.ParamIterations, 3); + p.SetUInt32(Argon2Kdf.ParamParallelism, 4); + + byte[] pbSalt = new byte[16]; + for(int i = 0; i < pbSalt.Length; ++i) pbSalt[i] = 2; + p.SetByteArray(Argon2Kdf.ParamSalt, pbSalt); + + byte[] pbKey = new byte[8]; + for(int i = 0; i < pbKey.Length; ++i) pbKey[i] = 3; + p.SetByteArray(Argon2Kdf.ParamSecretKey, pbKey); + + byte[] pbAssoc = new byte[12]; + for(int i = 0; i < pbAssoc.Length; ++i) pbAssoc[i] = 4; + p.SetByteArray(Argon2Kdf.ParamAssocData, pbAssoc); + + byte[] pbExpc = new byte[32] { + 0x51, 0x2B, 0x39, 0x1B, 0x6F, 0x11, 0x62, 0x97, + 0x53, 0x71, 0xD3, 0x09, 0x19, 0x73, 0x42, 0x94, + 0xF8, 0x68, 0xE3, 0xBE, 0x39, 0x84, 0xF3, 0xC1, + 0xA1, 0x3A, 0x4D, 0xB9, 0xFA, 0xBE, 0x4A, 0xCB + }; + + byte[] pb = kdf.Transform(pbMsg, p); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("Argon2-1"); + + // ====================================================== + // From the official Argon2 1.3 reference code package + // (test vector for Argon2d 1.0) + + p.SetUInt32(Argon2Kdf.ParamVersion, 0x10); + + pbExpc = new byte[32] { + 0x96, 0xA9, 0xD4, 0xE5, 0xA1, 0x73, 0x40, 0x92, + 0xC8, 0x5E, 0x29, 0xF4, 0x10, 0xA4, 0x59, 0x14, + 0xA5, 0xDD, 0x1F, 0x5C, 0xBF, 0x08, 0xB2, 0x67, + 0x0D, 0xA6, 0x8A, 0x02, 0x85, 0xAB, 0xF3, 0x2B + }; + + pb = kdf.Transform(pbMsg, p); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("Argon2-2"); + + // ====================================================== + // From the official 'phc-winner-argon2-20151206.zip' + // (test vector for Argon2d 1.0) + + p.SetUInt64(Argon2Kdf.ParamMemory, 16 * 1024); + + pbExpc = new byte[32] { + 0x57, 0xB0, 0x61, 0x3B, 0xFD, 0xD4, 0x13, 0x1A, + 0x0C, 0x34, 0x88, 0x34, 0xC6, 0x72, 0x9C, 0x2C, + 0x72, 0x29, 0x92, 0x1E, 0x6B, 0xBA, 0x37, 0x66, + 0x5D, 0x97, 0x8C, 0x4F, 0xE7, 0x17, 0x5E, 0xD2 + }; + + pb = kdf.Transform(pbMsg, p); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("Argon2-3"); + +#if SELFTEST_ARGON2_LONG + // ====================================================== + // Computed using the official 'argon2' application + // (test vectors for Argon2d 1.3) + + p = kdf.GetDefaultParameters(); + + pbMsg = StrUtil.Utf8.GetBytes("ABC1234"); + + p.SetUInt64(Argon2Kdf.ParamMemory, (1 << 11) * 1024); // 2 MB + p.SetUInt64(Argon2Kdf.ParamIterations, 2); + p.SetUInt32(Argon2Kdf.ParamParallelism, 2); + + pbSalt = StrUtil.Utf8.GetBytes("somesalt"); + p.SetByteArray(Argon2Kdf.ParamSalt, pbSalt); + + pbExpc = new byte[32] { + 0x29, 0xCB, 0xD3, 0xA1, 0x93, 0x76, 0xF7, 0xA2, + 0xFC, 0xDF, 0xB0, 0x68, 0xAC, 0x0B, 0x99, 0xBA, + 0x40, 0xAC, 0x09, 0x01, 0x73, 0x42, 0xCE, 0xF1, + 0x29, 0xCC, 0xA1, 0x4F, 0xE1, 0xC1, 0xB7, 0xA3 + }; + + pb = kdf.Transform(pbMsg, p); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("Argon2-4"); + + p.SetUInt64(Argon2Kdf.ParamMemory, (1 << 10) * 1024); // 1 MB + p.SetUInt64(Argon2Kdf.ParamIterations, 3); + + pbExpc = new byte[32] { + 0x7A, 0xBE, 0x1C, 0x1C, 0x8D, 0x7F, 0xD6, 0xDC, + 0x7C, 0x94, 0x06, 0x3E, 0xD8, 0xBC, 0xD8, 0x1C, + 0x2F, 0x87, 0x84, 0x99, 0x12, 0x83, 0xFE, 0x76, + 0x00, 0x64, 0xC4, 0x58, 0xA4, 0xDA, 0x35, 0x70 + }; + + pb = kdf.Transform(pbMsg, p); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("Argon2-5"); + +#if SELFTEST_ARGON2_LONGER + p.SetUInt64(Argon2Kdf.ParamMemory, (1 << 20) * 1024); // 1 GB + p.SetUInt64(Argon2Kdf.ParamIterations, 2); + p.SetUInt32(Argon2Kdf.ParamParallelism, 3); + + pbExpc = new byte[32] { + 0xE6, 0xE7, 0xCB, 0xF5, 0x5A, 0x06, 0x93, 0x05, + 0x32, 0xBA, 0x86, 0xC6, 0x1F, 0x45, 0x17, 0x99, + 0x65, 0x41, 0x77, 0xF9, 0x30, 0x55, 0x9A, 0xE8, + 0x3D, 0x21, 0x48, 0xC6, 0x2D, 0x0C, 0x49, 0x11 + }; + + pb = kdf.Transform(pbMsg, p); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("Argon2-6"); +#endif // SELFTEST_ARGON2_LONGER +#endif // SELFTEST_ARGON2_LONG +#endif // DEBUG + } + + private static void TestHmac() + { +#if DEBUG + // Test vectors from RFC 4231 + + byte[] pbKey = new byte[20]; + for(int i = 0; i < pbKey.Length; ++i) pbKey[i] = 0x0B; + byte[] pbMsg = StrUtil.Utf8.GetBytes("Hi There"); + byte[] pbExpc = new byte[32] { + 0xB0, 0x34, 0x4C, 0x61, 0xD8, 0xDB, 0x38, 0x53, + 0x5C, 0xA8, 0xAF, 0xCE, 0xAF, 0x0B, 0xF1, 0x2B, + 0x88, 0x1D, 0xC2, 0x00, 0xC9, 0x83, 0x3D, 0xA7, + 0x26, 0xE9, 0x37, 0x6C, 0x2E, 0x32, 0xCF, 0xF7 + }; + HmacEval(pbKey, pbMsg, pbExpc, "1"); + + pbKey = new byte[131]; + for(int i = 0; i < pbKey.Length; ++i) pbKey[i] = 0xAA; + pbMsg = StrUtil.Utf8.GetBytes( + "This is a test using a larger than block-size key and " + + "a larger than block-size data. The key needs to be " + + "hashed before being used by the HMAC algorithm."); + pbExpc = new byte[32] { + 0x9B, 0x09, 0xFF, 0xA7, 0x1B, 0x94, 0x2F, 0xCB, + 0x27, 0x63, 0x5F, 0xBC, 0xD5, 0xB0, 0xE9, 0x44, + 0xBF, 0xDC, 0x63, 0x64, 0x4F, 0x07, 0x13, 0x93, + 0x8A, 0x7F, 0x51, 0x53, 0x5C, 0x3A, 0x35, 0xE2 + }; + HmacEval(pbKey, pbMsg, pbExpc, "2"); +#endif + } + +#if DEBUG + private static void HmacEval(byte[] pbKey, byte[] pbMsg, + byte[] pbExpc, string strID) + { + // WinRT +#if ModernKeePassLib + var h = MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha256).CreateHash(CryptographicBuffer.CreateFromByteArray(pbKey)); + h.Append(CryptographicBuffer.CreateFromByteArray(pbMsg)); + var pbHash = h.GetValueAndReset().ToArray(); + if (!MemUtil.ArraysEqual(pbHash, pbExpc)) + throw new SecurityException("HMAC-SHA-256-" + strID); + + h.Append(CryptographicBuffer.CreateFromByteArray(pbMsg)); + pbHash = h.GetValueAndReset().ToArray(); + if (!MemUtil.ArraysEqual(pbHash, pbExpc)) + throw new SecurityException("HMAC-SHA-256-" + strID + "-R"); + + // BouncyCastle + /*var h = new HMac(new Sha256Digest()); + h.BlockUpdate(pbMsg, 0, pbMsg.Length); + byte[] pbHash = MemUtil.EmptyByteArray; + h.DoFinal(pbHash, 0); + if (!MemUtil.ArraysEqual(pbHash, pbExpc)) + throw new SecurityException("HMAC-SHA-256-" + strID); + + h.Reset(); + h.BlockUpdate(pbMsg, 0, pbMsg.Length); + h.DoFinal(pbHash, 0); + if (!MemUtil.ArraysEqual(pbHash, pbExpc)) + throw new SecurityException("HMAC-SHA-256-" + strID + "-R");*/ +#else + // Original + using(HMACSHA256 h = new HMACSHA256(pbKey)) + { + h.TransformBlock(pbMsg, 0, pbMsg.Length, pbMsg, 0); + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + byte[] pbHash = h.Hash; + if(!MemUtil.ArraysEqual(pbHash, pbExpc)) + throw new SecurityException("HMAC-SHA-256-" + strID); + + // Reuse the object + h.Initialize(); + h.TransformBlock(pbMsg, 0, pbMsg.Length, pbMsg, 0); + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + pbHash = h.Hash; + if(!MemUtil.ArraysEqual(pbHash, pbExpc)) + throw new SecurityException("HMAC-SHA-256-" + strID + "-R"); + } +#endif + } +#endif + + private static void TestKeyTransform(Random r) + { +#if DEBUG + // Up to KeePass 2.34, the OtpKeyProv plugin used the public + // CompositeKey.TransformKeyManaged method (and a finalizing + // SHA-256 computation), which became an internal method of + // the AesKdf class in KeePass 2.35, thus OtpKeyProv now + // uses the AesKdf class; here we ensure that the results + // are the same + + byte[] pbKey = new byte[32]; + r.NextBytes(pbKey); + byte[] pbSeed = new byte[32]; + r.NextBytes(pbSeed); + ulong uRounds = (ulong)r.Next(1, 0x7FFF); + + byte[] pbMan = new byte[pbKey.Length]; + Array.Copy(pbKey, pbMan, pbKey.Length); + if(!AesKdf.TransformKeyManaged(pbMan, pbSeed, uRounds)) + throw new SecurityException("AES-KDF-1"); + pbMan = CryptoUtil.HashSha256(pbMan); + + AesKdf kdf = new AesKdf(); + var p = kdf.GetDefaultParameters(); + p.SetUInt64(AesKdf.ParamRounds, uRounds); + p.SetByteArray(AesKdf.ParamSeed, pbSeed); + byte[] pbKdf = kdf.Transform(pbKey, p); + + if(!MemUtil.ArraysEqual(pbMan, pbKdf)) + throw new SecurityException("AES-KDF-2"); +#endif + } + #if !ModernKeePassLib private static void TestNativeKeyTransform() { #if DEBUG byte[] pbOrgKey = CryptoRandom.Instance.GetRandomBytes(32); byte[] pbSeed = CryptoRandom.Instance.GetRandomBytes(32); - ulong uRounds = (ulong)((new Random()).Next(1, 0x3FFF)); + ulong uRounds = (ulong)r.Next(1, 0x3FFF); byte[] pbManaged = new byte[32]; Array.Copy(pbOrgKey, pbManaged, 32); - if(CompositeKey.TransformKeyManaged(pbManaged, pbSeed, uRounds) == false) - throw new SecurityException("Managed transform."); + if(!AesKdf.TransformKeyManaged(pbManaged, pbSeed, uRounds)) + throw new SecurityException("AES-KDF-1"); byte[] pbNative = new byte[32]; Array.Copy(pbOrgKey, pbNative, 32); - if(NativeLib.TransformKey256(pbNative, pbSeed, uRounds) == false) + if(!NativeLib.TransformKey256(pbNative, pbSeed, uRounds)) return; // Native library not available ("success") if(!MemUtil.ArraysEqual(pbManaged, pbNative)) - throw new SecurityException("Native transform."); + throw new SecurityException("AES-KDF-2"); #endif } #endif - private static void TestMemUtil() + private static void TestMemUtil(Random r) { #if DEBUG - Random r = new Random(); byte[] pb = CryptoRandom.Instance.GetRandomBytes((uint)r.Next( 0, 0x2FFFF)); @@ -297,12 +855,21 @@ namespace ModernKeePassLib.Cryptography pbRes = MemUtil.ParseBase32("JNSXSIDQOJXXM2LEMVZCAYTBONSWIIDPNYQG63TFFV2GS3LFEBYGC43TO5XXEZDTFY======"); pbExp = Encoding.UTF8.GetBytes("Key provider based on one-time passwords."); if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-7"); + + int i = 0 - 0x10203040; + pbRes = MemUtil.Int32ToBytes(i); + if(MemUtil.ByteArrayToHexString(pbRes) != "C0CFDFEF") + throw new Exception("MemUtil-8"); // Must be little-endian + if(MemUtil.BytesToUInt32(pbRes) != (uint)i) + throw new Exception("MemUtil-9"); + if(MemUtil.BytesToInt32(pbRes) != i) + throw new Exception("MemUtil-10"); #endif } private static void TestHmacOtp() { -#if (DEBUG && !KeePassLibSD && !KeePassRT) +#if (DEBUG && !KeePassLibSD) byte[] pbSecret = StrUtil.Utf8.GetBytes("12345678901234567890"); string[] vExp = new string[]{ "755224", "287082", "359152", "969429", "338314", "254676", "287922", "162583", "399871", @@ -316,7 +883,7 @@ namespace ModernKeePassLib.Cryptography #endif } - private static void TestProtectedObjects() + private static void TestProtectedObjects(Random r) { #if DEBUG Encoding enc = StrUtil.Utf8; @@ -371,7 +938,6 @@ namespace ModernKeePassLib.Cryptography if(!ps.IsProtected) throw new SecurityException("ProtectedString-9"); if(!ps2.IsProtected) throw new SecurityException("ProtectedString-10"); - Random r = new Random(); string str = string.Empty; ps = new ProtectedString(); for(int i = 0; i < 100; ++i) @@ -497,16 +1063,26 @@ namespace ModernKeePassLib.Cryptography if(short.MinValue.ToString(NumberFormatInfo.InvariantInfo) != "-32768") throw new InvalidOperationException("StrUtil-Inv4"); + + if(!string.Equals("abcd", "aBcd", StrUtil.CaseIgnoreCmp)) + throw new InvalidOperationException("StrUtil-Case1"); + if(string.Equals(@"ab", StrUtil.CaseIgnoreCmp)) + throw new InvalidOperationException("StrUtil-Case2"); #endif } private static void TestUrlUtil() { #if DEBUG +#if !ModernKeePassLib + Debug.Assert(Uri.UriSchemeHttp.Equals("http", StrUtil.CaseIgnoreCmp)); + Debug.Assert(Uri.UriSchemeHttps.Equals("https", StrUtil.CaseIgnoreCmp)); +#endif + if(UrlUtil.GetHost(@"scheme://domain:port/path?query_string#fragment_id") != "domain") throw new InvalidOperationException("UrlUtil-H1"); - if(UrlUtil.GetHost(@"http://example.org:80") != "example.org") + if(UrlUtil.GetHost(@"https://example.org:443") != "example.org") throw new InvalidOperationException("UrlUtil-H2"); if(UrlUtil.GetHost(@"mailto:bob@example.com") != "example.com") throw new InvalidOperationException("UrlUtil-H3"); diff --git a/ModernKeePassLib/Delegates/Handlers.cs b/ModernKeePassLib/Delegates/Handlers.cs index 8a8b6f4..abfaf8f 100644 --- a/ModernKeePassLib/Delegates/Handlers.cs +++ b/ModernKeePassLib/Delegates/Handlers.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/Interfaces/IDeepCloneable.cs b/ModernKeePassLib/Interfaces/IDeepCloneable.cs index 5a67a9c..835e487 100644 --- a/ModernKeePassLib/Interfaces/IDeepCloneable.cs +++ b/ModernKeePassLib/Interfaces/IDeepCloneable.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/Interfaces/IStatusLogger.cs b/ModernKeePassLib/Interfaces/IStatusLogger.cs index 8fab8ee..22c07ef 100644 --- a/ModernKeePassLib/Interfaces/IStatusLogger.cs +++ b/ModernKeePassLib/Interfaces/IStatusLogger.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/Interfaces/IStructureItem.cs b/ModernKeePassLib/Interfaces/IStructureItem.cs index a733064..b36af1c 100644 --- a/ModernKeePassLib/Interfaces/IStructureItem.cs +++ b/ModernKeePassLib/Interfaces/IStructureItem.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/Interfaces/ITimeLogger.cs b/ModernKeePassLib/Interfaces/ITimeLogger.cs index 4742699..7c68f95 100644 --- a/ModernKeePassLib/Interfaces/ITimeLogger.cs +++ b/ModernKeePassLib/Interfaces/ITimeLogger.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/Interfaces/IUIOperations.cs b/ModernKeePassLib/Interfaces/IUIOperations.cs index 2593676..3f3029b 100644 --- a/ModernKeePassLib/Interfaces/IUIOperations.cs +++ b/ModernKeePassLib/Interfaces/IUIOperations.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/Interfaces/IXmlSerializerEx.cs b/ModernKeePassLib/Interfaces/IXmlSerializerEx.cs index 0be854f..fd8323f 100644 --- a/ModernKeePassLib/Interfaces/IXmlSerializerEx.cs +++ b/ModernKeePassLib/Interfaces/IXmlSerializerEx.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/Keys/CompositeKey.cs b/ModernKeePassLib/Keys/CompositeKey.cs index 5d8806a..a28920e 100644 --- a/ModernKeePassLib/Keys/CompositeKey.cs +++ b/ModernKeePassLib/Keys/CompositeKey.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -40,8 +40,10 @@ using ModernKeePassLib.Security; using ModernKeePassLib.Utility; using Windows.Security.Cryptography.Core; using Windows.Storage.Streams; +using ModernKeePassLib.Cryptography; +using ModernKeePassLib.Cryptography.KeyDerivation; using Org.BouncyCastle.Crypto.Engines; -using Org.BouncyCastle.Crypto.Parameters; +using KdfParameters = Org.BouncyCastle.Crypto.Parameters.KdfParameters; namespace ModernKeePassLib.Keys { @@ -133,8 +135,15 @@ namespace ModernKeePassLib.Keys foreach(IUserKey pKey in m_vUserKeys) { + if(pKey == null) { Debug.Assert(false); continue; } + +#if KeePassUAP + if(pKey.GetType() == tUserKeyType) + return true; +#else if(tUserKeyType.IsInstanceOfType(pKey)) return true; +#endif } return false; @@ -153,8 +162,15 @@ namespace ModernKeePassLib.Keys foreach(IUserKey pKey in m_vUserKeys) { + if(pKey == null) { Debug.Assert(false); continue; } + +#if KeePassUAP + if(pKey.GetType() == tUserKeyType) + return pKey; +#else if(tUserKeyType.IsInstanceOfType(pKey)) return pKey; +#endif } return null; @@ -170,30 +186,31 @@ namespace ModernKeePassLib.Keys ValidateUserKeys(); // Concatenate user key data - MemoryStream ms = new MemoryStream(); + List lData = new List(); + int cbData = 0; foreach(IUserKey pKey in m_vUserKeys) { ProtectedBinary b = pKey.KeyData; if(b != null) { byte[] pbKeyData = b.ReadData(); - ms.Write(pbKeyData, 0, pbKeyData.Length); - MemUtil.ZeroByteArray(pbKeyData); + lData.Add(pbKeyData); + cbData += pbKeyData.Length; } } -#if ModernKeePassLib - /*var sha256 = WinRTCrypto.HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha256); - var pbHash = sha256.HashData(ms.ToArray());*/ - var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256); - var buffer = sha256.HashData(CryptographicBuffer.CreateFromByteArray(ms.ToArray())); - byte[] pbHash; - CryptographicBuffer.CopyToByteArray(buffer, out pbHash); -#else - SHA256Managed sha256 = new SHA256Managed(); - byte[] pbHash = sha256.ComputeHash(ms.ToArray()); -#endif - ms.Dispose(); + byte[] pbAllData = new byte[cbData]; + int p = 0; + foreach(byte[] pbData in lData) + { + Array.Copy(pbData, 0, pbAllData, p, pbData.Length); + p += pbData.Length; + MemUtil.ZeroByteArray(pbData); + } + Debug.Assert(p == cbData); + + byte[] pbHash = CryptoUtil.HashSha256(pbAllData); + MemUtil.ZeroByteArray(pbAllData); return pbHash; } @@ -204,21 +221,13 @@ namespace ModernKeePassLib.Keys byte[] pbThis = CreateRawCompositeKey32(); byte[] pbOther = ckOther.CreateRawCompositeKey32(); bool bResult = MemUtil.ArraysEqual(pbThis, pbOther); - Array.Clear(pbOther, 0, pbOther.Length); - Array.Clear(pbThis, 0, pbThis.Length); + MemUtil.ZeroByteArray(pbOther); + MemUtil.ZeroByteArray(pbThis); return bResult; } - /// - /// Generate a 32-bit wide key out of the composite key. - /// - /// Seed used in the key transformation - /// rounds. Must be a byte array containing exactly 32 bytes; must - /// not be null. - /// Number of key transformation rounds. - /// Returns a protected binary object that contains the - /// resulting 32-bit wide key. + [Obsolete] public ProtectedBinary GenerateKey32(byte[] pbKeySeed32, ulong uNumRounds) { Debug.Assert(pbKeySeed32 != null); @@ -226,24 +235,48 @@ namespace ModernKeePassLib.Keys Debug.Assert(pbKeySeed32.Length == 32); if(pbKeySeed32.Length != 32) throw new ArgumentException("pbKeySeed32"); + AesKdf kdf = new AesKdf(); + var p = kdf.GetDefaultParameters(); + p.SetUInt64(AesKdf.ParamRounds, uNumRounds); + p.SetByteArray(AesKdf.ParamSeed, pbKeySeed32); + + return GenerateKey32(p); + } + + /// + /// Generate a 32-byte (256-bit) key from the composite key. + /// + public ProtectedBinary GenerateKey32(Cryptography.KeyDerivation.KdfParameters p) + { + if(p == null) { Debug.Assert(false); throw new ArgumentNullException("p"); } + byte[] pbRaw32 = CreateRawCompositeKey32(); if((pbRaw32 == null) || (pbRaw32.Length != 32)) { Debug.Assert(false); return null; } - byte[] pbTrf32 = TransformKey(pbRaw32, pbKeySeed32, uNumRounds); - if((pbTrf32 == null) || (pbTrf32.Length != 32)) - { Debug.Assert(false); return null; } + KdfEngine kdf = KdfPool.Get(p.KdfUuid); + if(kdf == null) // CryptographicExceptions are translated to "file corrupted" + throw new Exception(KLRes.UnknownKdf + Environment.NewLine + + KLRes.FileNewVerOrPlgReq + Environment.NewLine + + "UUID: " + p.KdfUuid.ToHexString() + "."); + + byte[] pbTrf32 = kdf.Transform(pbRaw32, p); + if(pbTrf32 == null) { Debug.Assert(false); return null; } + + if(pbTrf32.Length != 32) + { + Debug.Assert(false); + pbTrf32 = CryptoUtil.HashSha256(pbTrf32); + } ProtectedBinary pbRet = new ProtectedBinary(true, pbTrf32); MemUtil.ZeroByteArray(pbTrf32); MemUtil.ZeroByteArray(pbRaw32); - return pbRet; } private void ValidateUserKeys() { -#if !ModernKeePassLib int nAccounts = 0; foreach(IUserKey uKey in m_vUserKeys) @@ -257,223 +290,7 @@ namespace ModernKeePassLib.Keys Debug.Assert(false); throw new InvalidOperationException(); } -#endif } - - /// - /// Transform the current key uNumRounds times. - /// - /// The original key which will be transformed. - /// This parameter won't be modified. - /// Seed used for key transformations. Must not - /// be null. This parameter won't be modified. - /// Transformation count. - /// 256-bit transformed key. - private static byte[] TransformKey(byte[] pbOriginalKey32, byte[] pbKeySeed32, - ulong uNumRounds) - { - Debug.Assert((pbOriginalKey32 != null) && (pbOriginalKey32.Length == 32)); - if(pbOriginalKey32 == null) throw new ArgumentNullException("pbOriginalKey32"); - if(pbOriginalKey32.Length != 32) throw new ArgumentException(); - - Debug.Assert((pbKeySeed32 != null) && (pbKeySeed32.Length == 32)); - if(pbKeySeed32 == null) throw new ArgumentNullException("pbKeySeed32"); - if(pbKeySeed32.Length != 32) throw new ArgumentException(); - - byte[] pbNewKey = new byte[32]; - Array.Copy(pbOriginalKey32, pbNewKey, pbNewKey.Length); - -#if !ModernKeePassLib - // Try to use the native library first - if(NativeLib.TransformKey256(pbNewKey, pbKeySeed32, uNumRounds)) - return (new SHA256Managed()).ComputeHash(pbNewKey); -#endif - - if(TransformKeyManaged(ref pbNewKey, pbKeySeed32, uNumRounds) == false) - return null; - -#if ModernKeePassLib - /*var sha256 = WinRTCrypto.HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha256); - return sha256.HashData(pbNewKey);*/ - var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256); - byte[] result; - var buffer = sha256.HashData(CryptographicBuffer.CreateFromByteArray(pbNewKey)); - CryptographicBuffer.CopyToByteArray(buffer, out result); - return result; -#else - SHA256Managed sha256 = new SHA256Managed(); - return sha256.ComputeHash(pbNewKey); -#endif - } - - public static bool TransformKeyManaged(ref byte[] pbNewKey32, byte[] pbKeySeed32, - ulong uNumRounds) - { -#if KeePassRT - KeyParameter kp = new KeyParameter(pbKeySeed32); - AesEngine aes = new AesEngine(); - aes.Init(true, kp); - - for(ulong i = 0; i < uNumRounds; ++i) - { - aes.ProcessBlock(pbNewKey32, 0, pbNewKey32, 0); - aes.ProcessBlock(pbNewKey32, 16, pbNewKey32, 16); - } -#else -#if ModernKeePassLib - /*var aes = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesEcb); - var key = aes.CreateSymmetricKey(pbKeySeed32); - var iCrypt = WinRTCrypto.CryptographicEngine.CreateEncryptor(key);*/ - /*var aes = SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithmNames.AesEcb); - var key = aes.CreateSymmetricKey(CryptographicBuffer.CreateFromByteArray(pbKeySeed32)); - var parameters = KeyDerivationParameters.BuildForPbkdf2(CryptographicBuffer.CreateFromByteArray(pbKeySeed32), (uint)uNumRounds); - var result = CryptographicEngine.DeriveKeyMaterial(key, parameters, 32); - CryptographicBuffer.CopyToByteArray(result, out pbNewKey32);*/ - KeyParameter kp = new KeyParameter(pbKeySeed32); - AesEngine aes = new AesEngine(); - aes.Init(true, kp); - - for (ulong i = 0; i < uNumRounds; ++i) - { - aes.ProcessBlock(pbNewKey32, 0, pbNewKey32, 0); - aes.ProcessBlock(pbNewKey32, 16, pbNewKey32, 16); - } - -#else - byte[] pbIV = new byte[16]; - Array.Clear(pbIV, 0, pbIV.Length); - - RijndaelManaged r = new RijndaelManaged(); - if(r.BlockSize != 128) // AES block size - { - Debug.Assert(false); - r.BlockSize = 128; - } - - r.IV = pbIV; - r.Mode = CipherMode.ECB; - r.KeySize = 256; - r.Key = pbKeySeed32; - ICryptoTransform iCrypt = r.CreateEncryptor(); -#endif - - // !iCrypt.CanReuseTransform -- doesn't work with Mono - /*if ((iCrypt == null) || (iCrypt.InputBlockSize != 16) || - (iCrypt.OutputBlockSize != 16)) - { - Debug.Assert(false, "Invalid ICryptoTransform."); - Debug.Assert((iCrypt.InputBlockSize == 16), "Invalid input block size!"); - Debug.Assert((iCrypt.OutputBlockSize == 16), "Invalid output block size!"); - return false; - } - - for(ulong i = 0; i < uNumRounds; ++i) - { - iCrypt.TransformBlock(pbNewKey32, 0, 16, pbNewKey32, 0); - iCrypt.TransformBlock(pbNewKey32, 16, 16, pbNewKey32, 16); - }*/ -#endif - - return true; - } - - /// - /// Benchmark the TransformKey method. Within - /// ms, random keys will be transformed - /// and the number of performed transformations are returned. - /// - /// Test duration in ms. - /// Stepping. - /// should be a prime number. For fast processors - /// (PCs) a value of 3001 is recommended, for slower processors (PocketPC) - /// a value of 401 is recommended. - /// Number of transformations performed in the specified - /// amount of time. Maximum value is uint.MaxValue. - /*public static ulong TransformKeyBenchmark(uint uMilliseconds, ulong uStep) - { - ulong uRounds; - -#if !ModernKeePassLib - // Try native method - if(NativeLib.TransformKeyBenchmark256(uMilliseconds, out uRounds)) - return uRounds; -#endif - - byte[] pbKey = new byte[32]; - byte[] pbNewKey = new byte[32]; - for(int i = 0; i < pbKey.Length; ++i) - { - pbKey[i] = (byte)i; - pbNewKey[i] = (byte)i; - } - -#if KeePassRT - KeyParameter kp = new KeyParameter(pbKey); - AesEngine aes = new AesEngine(); - aes.Init(true, kp); -#else -#if ModernKeePassLib - var aes = WinRTCrypto.SymmetricKeyAlgorithmProvider.OpenAlgorithm(SymmetricAlgorithm.AesEcb); - var key = aes.CreateSymmetricKey(pbKey); - var iCrypt = WinRTCrypto.CryptographicEngine.CreateEncryptor(key); - -#else - byte[] pbIV = new byte[16]; - Array.Clear(pbIV, 0, pbIV.Length); - - RijndaelManaged r = new RijndaelManaged(); - if(r.BlockSize != 128) // AES block size - { - Debug.Assert(false); - r.BlockSize = 128; - } - - r.IV = pbIV; - r.Mode = CipherMode.ECB; - r.KeySize = 256; - r.Key = pbKey; - ICryptoTransform iCrypt = r.CreateEncryptor(); -#endif - - // !iCrypt.CanReuseTransform -- doesn't work with Mono - if ((iCrypt == null) || (iCrypt.InputBlockSize != 16) || - (iCrypt.OutputBlockSize != 16)) - { - Debug.Assert(false, "Invalid ICryptoTransform."); - Debug.Assert(iCrypt.InputBlockSize == 16, "Invalid input block size!"); - Debug.Assert(iCrypt.OutputBlockSize == 16, "Invalid output block size!"); - return PwDefs.DefaultKeyEncryptionRounds; - } -#endif - - uRounds = 0; - int tStart = Environment.TickCount; - while(true) - { - for(ulong j = 0; j < uStep; ++j) - { -#if KeePassRT - aes.ProcessBlock(pbNewKey, 0, pbNewKey, 0); - aes.ProcessBlock(pbNewKey, 16, pbNewKey, 16); -#else - iCrypt.TransformBlock(pbNewKey, 0, 16, pbNewKey, 0); - iCrypt.TransformBlock(pbNewKey, 16, 16, pbNewKey, 16); -#endif - } - - uRounds += uStep; - if(uRounds < uStep) // Overflow check - { - uRounds = ulong.MaxValue; - break; - } - - uint tElapsed = (uint)(Environment.TickCount - tStart); - if(tElapsed > uMilliseconds) break; - } - - return uRounds; - }*/ } public sealed class InvalidCompositeKeyException : Exception @@ -482,8 +299,8 @@ namespace ModernKeePassLib.Keys { get { - return KLRes.InvalidCompositeKey + Environment.NewLine - + Environment.NewLine + KLRes.InvalidCompositeKeyHint; + return KLRes.InvalidCompositeKey + Environment.NewLine + + KLRes.InvalidCompositeKeyHint; } } diff --git a/ModernKeePassLib/Keys/IUserKey.cs b/ModernKeePassLib/Keys/IUserKey.cs index 1b733d9..5062395 100644 --- a/ModernKeePassLib/Keys/IUserKey.cs +++ b/ModernKeePassLib/Keys/IUserKey.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/Keys/KcpCustomKey.cs b/ModernKeePassLib/Keys/KcpCustomKey.cs index 5b8d244..b018208 100644 --- a/ModernKeePassLib/Keys/KcpCustomKey.cs +++ b/ModernKeePassLib/Keys/KcpCustomKey.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -30,6 +30,7 @@ using System.Security.Cryptography; using ModernKeePassLib.Security; using ModernKeePassLib.Utility; using Windows.Security.Cryptography.Core; +using ModernKeePassLib.Cryptography; namespace ModernKeePassLib.Keys { @@ -60,18 +61,8 @@ namespace ModernKeePassLib.Keys if(bPerformHash) { -#if ModernKeePassLib - /*var sha256 = WinRTCrypto.HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha256); - var pbRaw = sha256.HashData(pbKeyData);*/ - var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256); - var buffer = sha256.HashData(CryptographicBuffer.CreateFromByteArray(pbKeyData)); - byte[] pbRaw; - CryptographicBuffer.CopyToByteArray(buffer, out pbRaw); -#else - SHA256Managed sha256 = new SHA256Managed(); - byte[] pbRaw = sha256.ComputeHash(pbKeyData); -#endif - m_pbKey = new ProtectedBinary(true, pbRaw); + byte[] pbRaw = CryptoUtil.HashSha256(pbKeyData); + m_pbKey = new ProtectedBinary(true, pbRaw); } else m_pbKey = new ProtectedBinary(true, pbKeyData); } diff --git a/ModernKeePassLib/Keys/KcpKeyFile.cs b/ModernKeePassLib/Keys/KcpKeyFile.cs index 9115fae..b09eff2 100644 --- a/ModernKeePassLib/Keys/KcpKeyFile.cs +++ b/ModernKeePassLib/Keys/KcpKeyFile.cs @@ -138,18 +138,7 @@ namespace ModernKeePassLib.Keys else if(iLength == 64) pbKey = LoadHexKey32(pbFileData); if(pbKey == null) - { -#if ModernKeePassLib - /*var sha256 = WinRTCrypto.HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha256); - pbKey = sha256.HashData(pbFileData);*/ - var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256); - var buffer = sha256.HashData(CryptographicBuffer.CreateFromByteArray(pbFileData)); - CryptographicBuffer.CopyToByteArray(buffer, out pbKey); -#else - SHA256Managed sha256 = new SHA256Managed(); - pbKey = sha256.ComputeHash(pbFileData); -#endif - } + pbKey = CryptoUtil.HashSha256(pbFileData); return pbKey; } @@ -169,12 +158,15 @@ namespace ModernKeePassLib.Keys try { - string strHex = StrUtil.Utf8.GetString(pbFileData, 0, 64); - if(!StrUtil.IsHexString(strHex, true)) return null; + if(!StrUtil.IsHexString(pbFileData, true)) return null; + string strHex = StrUtil.Utf8.GetString(pbFileData, 0, pbFileData.Length); byte[] pbKey = MemUtil.HexStringToByteArray(strHex); if((pbKey == null) || (pbKey.Length != 32)) + { + Debug.Assert(false); return null; + } return pbKey; } @@ -202,21 +194,13 @@ namespace ModernKeePassLib.Keys pbFinalKey32 = pbKey32; else { - MemoryStream ms = new MemoryStream(); - ms.Write(pbAdditionalEntropy, 0, pbAdditionalEntropy.Length); - ms.Write(pbKey32, 0, 32); + using(MemoryStream ms = new MemoryStream()) + { + MemUtil.Write(ms, pbAdditionalEntropy); + MemUtil.Write(ms, pbKey32); -#if ModernKeePassLib - /*var sha256 = WinRTCrypto.HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha256); - pbFinalKey32 = sha256.HashData(ms.ToArray());*/ - var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256); - var buffer = sha256.HashData(CryptographicBuffer.CreateFromByteArray(ms.ToArray())); - CryptographicBuffer.CopyToByteArray(buffer, out pbFinalKey32); -#else - SHA256Managed sha256 = new SHA256Managed(); - pbFinalKey32 = sha256.ComputeHash(ms.ToArray()); -#endif - ms.Dispose(); + pbFinalKey32 = CryptoUtil.HashSha256(ms.ToArray()); + } } CreateXmlKeyFile(strFilePath, pbFinalKey32); @@ -318,11 +302,14 @@ namespace ModernKeePassLib.Keys if(pbKeyData == null) throw new ArgumentNullException("pbKeyData"); IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFile); - var sOut = IOConnection.OpenWrite(ioc); + Stream sOut = IOConnection.OpenWrite(ioc); #if ModernKeePassLib - var settings = new XmlWriterSettings() { Encoding = StrUtil.Utf8 }; - var xtw = XmlWriter.Create(sOut, settings); + XmlWriterSettings xws = new XmlWriterSettings(); + xws.Encoding = StrUtil.Utf8; + xws.Indent = false; + + XmlWriter xtw = XmlWriter.Create(sOut, xws); #else XmlTextWriter xtw = new XmlTextWriter(sOut, StrUtil.Utf8); #endif diff --git a/ModernKeePassLib/Keys/KcpPassword.cs b/ModernKeePassLib/Keys/KcpPassword.cs index a460251..9ac094d 100644 --- a/ModernKeePassLib/Keys/KcpPassword.cs +++ b/ModernKeePassLib/Keys/KcpPassword.cs @@ -29,6 +29,7 @@ using System.Security.Cryptography; using ModernKeePassLib.Security; using ModernKeePassLib.Utility; using Windows.Security.Cryptography.Core; +using ModernKeePassLib.Cryptography; namespace ModernKeePassLib.Keys { @@ -73,19 +74,13 @@ namespace ModernKeePassLib.Keys Debug.Assert(pbPasswordUtf8 != null); if(pbPasswordUtf8 == null) throw new ArgumentNullException("pbPasswordUtf8"); -#if ModernKeePassLib - /*var sha256 = WinRTCrypto.HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha256); - var pbRaw = sha256.HashData(pbPasswordUtf8);*/ - var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256); - var buffer = sha256.HashData(CryptographicBuffer.CreateFromByteArray(pbPasswordUtf8)); - byte[] pbRaw; - CryptographicBuffer.CopyToByteArray(buffer, out pbRaw); -#else - SHA256Managed sha256 = new SHA256Managed(); - byte[] pbRaw = sha256.ComputeHash(pbPasswordUtf8); +#if (DEBUG && !KeePassLibSD) + Debug.Assert(ValidatePassword(pbPasswordUtf8)); #endif - m_psPassword = new ProtectedString(true, pbPasswordUtf8); + byte[] pbRaw = CryptoUtil.HashSha256(pbPasswordUtf8); + + m_psPassword = new ProtectedString(true, pbPasswordUtf8); m_pbKeyData = new ProtectedBinary(true, pbRaw); } @@ -94,5 +89,24 @@ namespace ModernKeePassLib.Keys // m_psPassword = null; // m_pbKeyData = null; // } - } + +#if (DEBUG && !KeePassLibSD) + private static bool ValidatePassword(byte[] pb) + { + try + { + string str = StrUtil.Utf8.GetString(pb, 0, pb.Length); +#if ModernKeePassLib + // TODO: find a way to implement this + return true; +#else + return str.IsNormalized(NormalizationForm.FormC); +#endif + } + catch(Exception) { Debug.Assert(false); } + + return false; + } +#endif + } } diff --git a/ModernKeePassLib/Keys/KcpUserAccount.cs b/ModernKeePassLib/Keys/KcpUserAccount.cs index a4b0386..a4c8162 100644 --- a/ModernKeePassLib/Keys/KcpUserAccount.cs +++ b/ModernKeePassLib/Keys/KcpUserAccount.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,11 +18,16 @@ */ using System; -using System.Security; -using System.Security.Cryptography; +using System.Diagnostics; using System.IO; +using System.Security; + +#if !KeePassUAP +using Windows.Security.Cryptography; +#endif using ModernKeePassLib.Cryptography; +using ModernKeePassLib.Native; using ModernKeePassLib.Resources; using ModernKeePassLib.Security; using ModernKeePassLib.Utility; @@ -37,7 +42,7 @@ namespace ModernKeePassLib.Keys private ProtectedBinary m_pbKeyData = null; // Constant initialization vector (unique for KeePass) - private static readonly byte[] m_pbEntropy = new byte[]{ + private static readonly byte[] m_pbEntropy = new byte[] { 0xDE, 0x13, 0x5B, 0x5F, 0x18, 0xA3, 0x46, 0x70, 0xB2, 0x57, 0x24, 0x29, 0x69, 0x88, 0x98, 0xE6 }; @@ -67,10 +72,14 @@ namespace ModernKeePassLib.Keys byte[] pbKey = LoadUserKey(false); if(pbKey == null) pbKey = CreateUserKey(); - if(pbKey == null) throw new SecurityException(KLRes.UserAccountKeyError); + if(pbKey == null) // Should never happen + { + Debug.Assert(false); + throw new SecurityException(KLRes.UserAccountKeyError); + } m_pbKeyData = new ProtectedBinary(true, pbKey); - Array.Clear(pbKey, 0, pbKey.Length); + MemUtil.ZeroByteArray(pbKey); } // public void Clear() @@ -80,7 +89,7 @@ namespace ModernKeePassLib.Keys private static string GetUserKeyFilePath(bool bCreate) { -#if KeePassRT +#if ModernKeePassLib string strUserDir = Windows.Storage.ApplicationData.Current.RoamingFolder.Path; #else string strUserDir = Environment.GetFolderPath( @@ -94,10 +103,10 @@ namespace ModernKeePassLib.Keys Directory.CreateDirectory(strUserDir); strUserDir = UrlUtil.EnsureTerminatingSeparator(strUserDir, false); - return strUserDir + UserKeyFileName; + return (strUserDir + UserKeyFileName); } - private static byte[] LoadUserKey(bool bShowWarning) + private static byte[] LoadUserKey(bool bThrow) { byte[] pbKey = null; @@ -109,13 +118,10 @@ namespace ModernKeePassLib.Keys pbKey = ProtectedData.Unprotect(pbProtectedKey, m_pbEntropy, DataProtectionScope.CurrentUser); - - Array.Clear(pbProtectedKey, 0, pbProtectedKey.Length); } - catch(Exception exLoad) + catch(Exception) { - if(bShowWarning) MessageService.ShowWarning(exLoad); - + if(bThrow) throw; pbKey = null; } #endif @@ -125,28 +131,23 @@ namespace ModernKeePassLib.Keys private static byte[] CreateUserKey() { - byte[] pbKey = null; +#if KeePassLibSD + return null; +#else + string strFilePath = GetUserKeyFilePath(true); -#if !KeePassLibSD - try - { - string strFilePath = GetUserKeyFilePath(true); + byte[] pbRandomKey = CryptoRandom.Instance.GetRandomBytes(64); + byte[] pbProtectedKey = ProtectedData.Protect(pbRandomKey, + m_pbEntropy, DataProtectionScope.CurrentUser); - byte[] pbRandomKey = CryptoRandom.Instance.GetRandomBytes(64); - byte[] pbProtectedKey = ProtectedData.Protect(pbRandomKey, - m_pbEntropy, DataProtectionScope.CurrentUser); + File.WriteAllBytes(strFilePath, pbProtectedKey); - File.WriteAllBytes(strFilePath, pbProtectedKey); - - Array.Clear(pbProtectedKey, 0, pbProtectedKey.Length); - Array.Clear(pbRandomKey, 0, pbRandomKey.Length); - - pbKey = LoadUserKey(true); - } - catch(Exception) { pbKey = null; } -#endif + byte[] pbKey = LoadUserKey(true); + Debug.Assert(MemUtil.ArraysEqual(pbKey, pbRandomKey)); + MemUtil.ZeroByteArray(pbRandomKey); return pbKey; +#endif } } } diff --git a/ModernKeePassLib/Keys/KeyProvider.cs b/ModernKeePassLib/Keys/KeyProvider.cs index dd6f652..b7facf0 100644 --- a/ModernKeePassLib/Keys/KeyProvider.cs +++ b/ModernKeePassLib/Keys/KeyProvider.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/Keys/KeyProviderPool.cs b/ModernKeePassLib/Keys/KeyProviderPool.cs index b10d1ef..0f10ca7 100644 --- a/ModernKeePassLib/Keys/KeyProviderPool.cs +++ b/ModernKeePassLib/Keys/KeyProviderPool.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/Keys/KeyValidator.cs b/ModernKeePassLib/Keys/KeyValidator.cs index 793a75c..d2c6c01 100644 --- a/ModernKeePassLib/Keys/KeyValidator.cs +++ b/ModernKeePassLib/Keys/KeyValidator.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/Keys/KeyValidatorPool.cs b/ModernKeePassLib/Keys/KeyValidatorPool.cs index 40c483b..df107b8 100644 --- a/ModernKeePassLib/Keys/KeyValidatorPool.cs +++ b/ModernKeePassLib/Keys/KeyValidatorPool.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/Keys/UserKeyType.cs b/ModernKeePassLib/Keys/UserKeyType.cs index 9d25637..d3ebfc3 100644 --- a/ModernKeePassLib/Keys/UserKeyType.cs +++ b/ModernKeePassLib/Keys/UserKeyType.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/ModernKeePassLib.csproj b/ModernKeePassLib/ModernKeePassLib.csproj index 290fb4f..b1fa0d2 100644 --- a/ModernKeePassLib/ModernKeePassLib.csproj +++ b/ModernKeePassLib/ModernKeePassLib.csproj @@ -42,18 +42,32 @@ + + + + + + + + + + + + + + @@ -97,7 +111,11 @@ + + + + @@ -126,7 +144,7 @@ - + @@ -154,4 +172,5 @@ Libs\Windows.winmd + \ No newline at end of file diff --git a/ModernKeePassLib/ModernKeePassLib.nuspec b/ModernKeePassLib/ModernKeePassLib.nuspec index 99477ef..ba47f09 100644 --- a/ModernKeePassLib/ModernKeePassLib.nuspec +++ b/ModernKeePassLib/ModernKeePassLib.nuspec @@ -2,7 +2,7 @@ ModernKeePassLib - 2.28.4000 + 2.28.5000 ModernKeePassLib Geoffroy Bonneville Geoffroy Bonneville @@ -10,9 +10,9 @@ https://github.com/wismna/ModernKeePass false Portable KeePass Password Management Library that targets .Net Standard and WinRT - Bump to 2.28, write mode activated with BouncyCastle + Load image in GfxUtil made async to avoid a hang Copyright © 2017 Geoffroy Bonneville - KeePass KeePassLib Portable PCL + KeePass KeePassLib Portable PCL NetStandard diff --git a/ModernKeePassLib/Properties/AssemblyInfo.cs b/ModernKeePassLib/Properties/AssemblyInfo.cs index 5c05e1c..0b99c20 100644 --- a/ModernKeePassLib/Properties/AssemblyInfo.cs +++ b/ModernKeePassLib/Properties/AssemblyInfo.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/PwCustomIcon.cs b/ModernKeePassLib/PwCustomIcon.cs index 6dd7412..a667cfc 100644 --- a/ModernKeePassLib/PwCustomIcon.cs +++ b/ModernKeePassLib/PwCustomIcon.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,13 +18,13 @@ */ using System; +using System.Collections.Generic; using System.Diagnostics; #if ModernKeePassLib using Image = Splat.IBitmap; #else using System.Drawing; #endif -using System.IO; using ModernKeePassLib.Utility; @@ -35,9 +35,15 @@ namespace ModernKeePassLib /// public sealed class PwCustomIcon { - private PwUuid m_pwUuid; - private byte[] m_pbImageDataPng; - private Image m_pCachedImage; + private readonly PwUuid m_pwUuid; + private readonly byte[] m_pbImageDataPng; + + private readonly Image m_imgOrg; + private Dictionary m_dImageCache = new Dictionary(); + + // Recommended maximum sizes, not obligatory + internal const int MaxWidth = 128; + internal const int MaxHeight = 128; public PwUuid Uuid { @@ -49,9 +55,14 @@ namespace ModernKeePassLib get { return m_pbImageDataPng; } } + [Obsolete("Use GetImage instead.")] public Image Image { - get { return m_pCachedImage; } +#if (!KeePassLibSD && !KeePassUAP) + get { return GetImage(16, 16); } // Backward compatibility +#else + get { return GetImage(); } // Backward compatibility +#endif } public PwCustomIcon(PwUuid pwUuid, byte[] pbImageDataPng) @@ -59,22 +70,65 @@ namespace ModernKeePassLib Debug.Assert(pwUuid != null); if(pwUuid == null) throw new ArgumentNullException("pwUuid"); Debug.Assert(!pwUuid.Equals(PwUuid.Zero)); - if(pwUuid.Equals(PwUuid.Zero)) throw new ArgumentException("pwUuid == 0"); - + if(pwUuid.Equals(PwUuid.Zero)) throw new ArgumentException("pwUuid == 0."); Debug.Assert(pbImageDataPng != null); if(pbImageDataPng == null) throw new ArgumentNullException("pbImageDataPng"); m_pwUuid = pwUuid; m_pbImageDataPng = pbImageDataPng; -#if !KeePassLibSD // MemoryStream ms = new MemoryStream(m_pbImageDataPng, false); - // m_pCachedImage = Image.FromStream(ms); + // m_imgOrg = Image.FromStream(ms); // ms.Close(); - m_pCachedImage = GfxUtil.LoadImage(m_pbImageDataPng); +#if ModernKeePassLib + try { m_imgOrg = GfxUtil.LoadImage(m_pbImageDataPng).GetAwaiter().GetResult(); } #else - m_pCachedImage = null; + try { m_imgOrg = GfxUtil.LoadImage(m_pbImageDataPng); } #endif + catch(Exception) { Debug.Assert(false); m_imgOrg = null; } + + if(m_imgOrg != null) + m_dImageCache[GetID((int)m_imgOrg.Width, (int)m_imgOrg.Height)] = + m_imgOrg; } - } + + private static long GetID(int w, int h) + { + return (((long)w << 32) ^ (long)h); + } + + /// + /// Get the icon as an Image (original size). + /// + public Image GetImage() + { + return m_imgOrg; + } + +#if (!KeePassLibSD && !KeePassUAP) + /// + /// Get the icon as an Image (with the specified size). + /// + /// Width of the returned image. + /// Height of the returned image. + public Image GetImage(int w, int h) + { + if(w < 0) { Debug.Assert(false); return m_imgOrg; } + if(h < 0) { Debug.Assert(false); return m_imgOrg; } + if(m_imgOrg == null) return null; + + long lID = GetID(w, h); + + Image img; + if(m_dImageCache.TryGetValue(lID, out img)) return img; +#if ModernKeePassLib + img = GfxUtil.ScaleImage(m_pbImageDataPng, w, h).GetAwaiter().GetResult(); +#else + img = GfxUtil.ScaleImage(m_imgOrg, w, h, ScaleTransformFlags.UIIcon); +#endif + m_dImageCache[lID] = img; + return img; + } +#endif + } } diff --git a/ModernKeePassLib/PwDatabase.cs b/ModernKeePassLib/PwDatabase.cs index 1cfe598..8fc523f 100644 --- a/ModernKeePassLib/PwDatabase.cs +++ b/ModernKeePassLib/PwDatabase.cs @@ -30,6 +30,7 @@ using Image = Splat.IBitmap; using ModernKeePassLib.Collections; using ModernKeePassLib.Cryptography; using ModernKeePassLib.Cryptography.Cipher; +using ModernKeePassLib.Cryptography.KeyDerivation; using ModernKeePassLib.Delegates; using ModernKeePassLib.Interfaces; using ModernKeePassLib.Keys; @@ -57,7 +58,8 @@ namespace ModernKeePassLib private PwUuid m_uuidDataCipher = StandardAesEngine.AesUuid; private PwCompressionAlgorithm m_caCompression = PwCompressionAlgorithm.GZip; - private ulong m_uKeyEncryptionRounds = PwDefs.DefaultKeyEncryptionRounds; + // private ulong m_uKeyEncryptionRounds = PwDefs.DefaultKeyEncryptionRounds; + private KdfParameters m_kdfParams = KdfPool.GetDefaultParameters(); private CompositeKey m_pwUserKey = null; private MemoryProtectionConfig m_memProtConfig = new MemoryProtectionConfig(); @@ -65,6 +67,7 @@ namespace ModernKeePassLib private List m_vCustomIcons = new List(); private bool m_bUINeedsIconUpdate = true; + private DateTime m_dtSettingsChanged = PwDefs.DtDefaultNow; private string m_strName = string.Empty; private DateTime m_dtNameChanged = PwDefs.DtDefaultNow; private string m_strDesc = string.Empty; @@ -77,6 +80,7 @@ namespace ModernKeePassLib private DateTime m_dtKeyLastChanged = PwDefs.DtDefaultNow; private long m_lKeyChangeRecDays = -1; private long m_lKeyChangeForceDays = -1; + private bool m_bKeyChangeForceOnce = false; private IOConnectionInfo m_ioSource = new IOConnectionInfo(); private bool m_bDatabaseOpened = false; @@ -94,7 +98,8 @@ namespace ModernKeePassLib private int m_nHistoryMaxItems = DefaultHistoryMaxItems; private long m_lHistoryMaxSize = DefaultHistoryMaxSize; // In bytes - private StringDictionaryEx m_vCustomData = new StringDictionaryEx(); + private StringDictionaryEx m_dCustomData = new StringDictionaryEx(); + private VariantDictionary m_dPublicCustomData = new VariantDictionary(); private byte[] m_pbHashOfFileOnDisk = null; private byte[] m_pbHashOfLastIO = null; @@ -169,6 +174,12 @@ namespace ModernKeePassLib } } + public DateTime SettingsChanged + { + get { return m_dtSettingsChanged; } + set { m_dtSettingsChanged = value; } + } + /// /// Name of the database. /// @@ -260,6 +271,12 @@ namespace ModernKeePassLib set { m_lKeyChangeForceDays = value; } } + public bool MasterKeyChangeForceOnce + { + get { return m_bKeyChangeForceOnce; } + set { m_bKeyChangeForceOnce = value; } + } + /// /// The encryption algorithm used to encrypt the data part of the database. /// @@ -282,14 +299,23 @@ namespace ModernKeePassLib set { m_caCompression = value; } } - /// - /// Number of key transformation rounds (in order to make dictionary - /// attacks harder). - /// - public ulong KeyEncryptionRounds + // /// + // /// Number of key transformation rounds (KDF parameter). + // /// + // public ulong KeyEncryptionRounds + // { + // get { return m_uKeyEncryptionRounds; } + // set { m_uKeyEncryptionRounds = value; } + // } + + public KdfParameters KdfParameters { - get { return m_uKeyEncryptionRounds; } - set { m_uKeyEncryptionRounds = value; } + get { return m_kdfParams; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_kdfParams = value; + } } /// @@ -409,14 +435,37 @@ namespace ModernKeePassLib /// /// Custom data container that can be used by plugins to store /// own data in KeePass databases. + /// The data is stored in the encrypted part of encrypted + /// database files. + /// Use unique names for your items, e.g. "PluginName_ItemName". /// public StringDictionaryEx CustomData { - get { return m_vCustomData; } - set + get { return m_dCustomData; } + internal set { - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - m_vCustomData = value; + if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_dCustomData = value; + } + } + + /// + /// Custom data container that can be used by plugins to store + /// own data in KeePass databases. + /// The data is stored in the *unencrypted* part of database files, + /// and it is not supported by all file formats (e.g. supported by KDBX, + /// unsupported by XML). + /// It is highly recommended to use CustomData instead, + /// if possible. + /// Use unique names for your items, e.g. "PluginName_ItemName". + /// + public VariantDictionary PublicCustomData + { + get { return m_dPublicCustomData; } + internal set + { + if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_dPublicCustomData = value; } } @@ -485,7 +534,8 @@ namespace ModernKeePassLib m_uuidDataCipher = StandardAesEngine.AesUuid; m_caCompression = PwCompressionAlgorithm.GZip; - m_uKeyEncryptionRounds = PwDefs.DefaultKeyEncryptionRounds; + // m_uKeyEncryptionRounds = PwDefs.DefaultKeyEncryptionRounds; + m_kdfParams = KdfPool.GetDefaultParameters(); m_pwUserKey = null; m_memProtConfig = new MemoryProtectionConfig(); @@ -493,8 +543,9 @@ namespace ModernKeePassLib m_vCustomIcons = new List(); m_bUINeedsIconUpdate = true; - DateTime dtNow = DateTime.Now; + DateTime dtNow = DateTime.UtcNow; + m_dtSettingsChanged = dtNow; m_strName = string.Empty; m_dtNameChanged = dtNow; m_strDesc = string.Empty; @@ -507,6 +558,7 @@ namespace ModernKeePassLib m_dtKeyLastChanged = dtNow; m_lKeyChangeRecDays = -1; m_lKeyChangeForceDays = -1; + m_bKeyChangeForceOnce = false; m_ioSource = new IOConnectionInfo(); m_bDatabaseOpened = false; @@ -524,7 +576,8 @@ namespace ModernKeePassLib m_nHistoryMaxItems = DefaultHistoryMaxItems; m_lHistoryMaxSize = DefaultHistoryMaxSize; - m_vCustomData = new StringDictionaryEx(); + m_dCustomData = new StringDictionaryEx(); + m_dPublicCustomData = new VariantDictionary(); m_pbHashOfFileOnDisk = null; m_pbHashOfLastIO = null; @@ -589,7 +642,7 @@ namespace ModernKeePassLib KdbxFile kdbx = new KdbxFile(this); kdbx.DetachBinaries = m_strDetachBins; - var s = IOConnection.OpenRead(ioSource); + Stream s = IOConnection.OpenRead(ioSource); kdbx.Load(s, KdbxFormat.Default, slLogger); s.Dispose(); @@ -614,7 +667,7 @@ namespace ModernKeePassLib /// Logger that recieves status information. public void Save(IStatusLogger slLogger) { - Debug.Assert(ValidateUuidUniqueness()); + Debug.Assert(!HasDuplicateUuids()); FileLock fl = null; if(m_bUseFileLocks) fl = new FileLock(m_ioSource); @@ -622,7 +675,7 @@ namespace ModernKeePassLib { FileTransactionEx ft = new FileTransactionEx(m_ioSource, m_bUseFileTransactions); - var s = ft.OpenWrite(); + Stream s = ft.OpenWrite(); KdbxFile kdb = new KdbxFile(this); kdb.Save(s, null, KdbxFormat.Default, slLogger); @@ -693,36 +746,35 @@ namespace ModernKeePassLib MergeIn(pdSource, mm, null); } - /// - /// Synchronize the current database with another one. - /// - /// Input database to synchronize with. This input - /// database is used to update the current one, but is not modified! You - /// must copy the current object if you want a second instance of the - /// synchronized database. The input database must not be seen as valid - /// database any more after calling Synchronize. - /// Merge method. - /// Logger to report status messages to. - /// May be null. public void MergeIn(PwDatabase pdSource, PwMergeMethod mm, IStatusLogger slStatus) { if(pdSource == null) throw new ArgumentNullException("pdSource"); - PwGroup pgOrgStructure = m_pgRootGroup.CloneStructure(); - PwGroup pgSrcStructure = pdSource.m_pgRootGroup.CloneStructure(); - if(mm == PwMergeMethod.CreateNewUuids) { - pdSource.RootGroup.CreateNewItemUuids(true, true, true); pdSource.RootGroup.Uuid = new PwUuid(true); + pdSource.RootGroup.CreateNewItemUuids(true, true, true); } - GroupHandler gh = delegate(PwGroup pg) + // PwGroup pgOrgStructure = m_pgRootGroup.CloneStructure(); + // PwGroup pgSrcStructure = pdSource.RootGroup.CloneStructure(); + // Later in case 'if(mm == PwMergeMethod.Synchronize)': + // PwObjectPoolEx ppOrg = PwObjectPoolEx.FromGroup(pgOrgStructure); + // PwObjectPoolEx ppSrc = PwObjectPoolEx.FromGroup(pgSrcStructure); + + PwObjectPoolEx ppOrg = PwObjectPoolEx.FromGroup(m_pgRootGroup); + PwObjectPoolEx ppSrc = PwObjectPoolEx.FromGroup(pdSource.RootGroup); + + GroupHandler ghSrc = delegate(PwGroup pg) { // if(pg == pdSource.m_pgRootGroup) return true; + // Do not use ppOrg for finding the group, because new groups + // might have been added (which are not in the pool, and the + // pool should not be modified) PwGroup pgLocal = m_pgRootGroup.FindGroup(pg.Uuid, true); + if(pgLocal == null) { PwGroup pgSourceParent = pg.ParentGroup; @@ -745,7 +797,10 @@ namespace ModernKeePassLib PwGroup pgNew = new PwGroup(false, false); pgNew.Uuid = pg.Uuid; pgNew.AssignProperties(pg, false, true); - pgLocalContainer.AddGroup(pgNew, true); + + // pgLocalContainer.AddGroup(pgNew, true); + InsertObjectAtBestPos(pgLocalContainer.Groups, pgNew, ppSrc); + pgNew.ParentGroup = pgLocalContainer; } else // pgLocal != null { @@ -764,9 +819,13 @@ namespace ModernKeePassLib return ((slStatus != null) ? slStatus.ContinueWork() : true); }; - EntryHandler eh = delegate(PwEntry pe) + EntryHandler ehSrc = delegate(PwEntry pe) { - PwEntry peLocal = m_pgRootGroup.FindEntry(pe.Uuid, true); + // PwEntry peLocal = m_pgRootGroup.FindEntry(pe.Uuid, true); + PwEntry peLocal = (ppOrg.GetItemByUuid(pe.Uuid) as PwEntry); + Debug.Assert(object.ReferenceEquals(peLocal, + m_pgRootGroup.FindEntry(pe.Uuid, true))); + if(peLocal == null) { PwGroup pgSourceParent = pe.ParentGroup; @@ -781,7 +840,10 @@ namespace ModernKeePassLib PwEntry peNew = new PwEntry(false, false); peNew.Uuid = pe.Uuid; peNew.AssignProperties(pe, false, true, true); - pgLocalContainer.AddEntry(peNew, true); + + // pgLocalContainer.AddEntry(peNew, true); + InsertObjectAtBestPos(pgLocalContainer.Entries, peNew, ppSrc); + peNew.ParentGroup = pgLocalContainer; } else // peLocal != null { @@ -818,8 +880,8 @@ namespace ModernKeePassLib return ((slStatus != null) ? slStatus.ContinueWork() : true); }; - gh(pdSource.RootGroup); - if(!pdSource.RootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh)) + ghSrc(pdSource.RootGroup); + if(!pdSource.RootGroup.TraverseTree(TraversalMethod.PreOrder, ghSrc, ehSrc)) throw new InvalidOperationException(); IStatusLogger slPrevStatus = m_slStatus; @@ -827,23 +889,25 @@ namespace ModernKeePassLib if(mm == PwMergeMethod.Synchronize) { - ApplyDeletions(pdSource.m_vDeletedObjects, true); - ApplyDeletions(m_vDeletedObjects, false); + RelocateGroups(ppOrg, ppSrc); + RelocateEntries(ppOrg, ppSrc); + ReorderObjects(m_pgRootGroup, ppOrg, ppSrc); - PwObjectPool ppOrgGroups = PwObjectPool.FromGroupRecursive( - pgOrgStructure, false); - PwObjectPool ppSrcGroups = PwObjectPool.FromGroupRecursive( - pgSrcStructure, false); - PwObjectPool ppOrgEntries = PwObjectPool.FromGroupRecursive( - pgOrgStructure, true); - PwObjectPool ppSrcEntries = PwObjectPool.FromGroupRecursive( - pgSrcStructure, true); + // After all relocations and reorderings + MergeInLocationChanged(m_pgRootGroup, ppOrg, ppSrc); + ppOrg = null; // Pools are now invalid, because the location + ppSrc = null; // changed times have been merged in - RelocateGroups(ppOrgGroups, ppSrcGroups); - ReorderGroups(ppOrgGroups, ppSrcGroups); - RelocateEntries(ppOrgEntries, ppSrcEntries); - ReorderEntries(ppOrgEntries, ppSrcEntries); - Debug.Assert(ValidateUuidUniqueness()); + // Delete *after* relocating, because relocating might + // empty some groups that are marked for deletion (and + // objects that weren't relocated yet might prevent the + // deletion) + Dictionary dOrgDel = CreateDeletedObjectsPool(); + MergeInDeletionInfo(pdSource.m_vDeletedObjects, dOrgDel); + ApplyDeletions(m_pgRootGroup, dOrgDel); + + // The list and the dictionary should be kept in sync + Debug.Assert(m_vDeletedObjects.UCount == (uint)dOrgDel.Count); } // Must be called *after* merging groups, because group UUIDs @@ -854,6 +918,7 @@ namespace ModernKeePassLib MaintainBackups(); + Debug.Assert(!HasDuplicateUuids()); m_slStatus = slPrevStatus; } @@ -868,79 +933,120 @@ namespace ModernKeePassLib } } - private void ApplyDeletions(PwObjectList listDelObjects, - bool bCopyDeletionInfoToLocal) + private Dictionary CreateDeletedObjectsPool() { - Debug.Assert(listDelObjects != null); if(listDelObjects == null) throw new ArgumentNullException("listDelObjects"); + Dictionary d = + new Dictionary(); - LinkedList listGroupsToDelete = new LinkedList(); - LinkedList listEntriesToDelete = new LinkedList(); - - GroupHandler gh = delegate(PwGroup pg) + int n = (int)m_vDeletedObjects.UCount; + for(int i = n - 1; i >= 0; --i) { - if(pg == m_pgRootGroup) return true; + PwDeletedObject pdo = m_vDeletedObjects.GetAt((uint)i); - foreach(PwDeletedObject pdo in listDelObjects) + PwDeletedObject pdoEx; + if(d.TryGetValue(pdo.Uuid, out pdoEx)) { - if(pg.Uuid.Equals(pdo.Uuid)) - { - if(TimeUtil.Compare(pg.LastModificationTime, - pdo.DeletionTime, true) < 0) - listGroupsToDelete.AddLast(pg); - } + Debug.Assert(false); // Found duplicate, which should not happen + + if(pdo.DeletionTime > pdoEx.DeletionTime) + pdoEx.DeletionTime = pdo.DeletionTime; + + m_vDeletedObjects.RemoveAt((uint)i); } + else d[pdo.Uuid] = pdo; + } - return ((m_slStatus != null) ? m_slStatus.ContinueWork() : true); - }; + return d; + } - EntryHandler eh = delegate(PwEntry pe) + private void MergeInDeletionInfo(PwObjectList lSrc, + Dictionary dOrgDel) + { + foreach(PwDeletedObject pdoSrc in lSrc) { - foreach(PwDeletedObject pdo in listDelObjects) + PwDeletedObject pdoOrg; + if(dOrgDel.TryGetValue(pdoSrc.Uuid, out pdoOrg)) // Update { - if(pe.Uuid.Equals(pdo.Uuid)) - { - if(TimeUtil.Compare(pe.LastModificationTime, - pdo.DeletionTime, true) < 0) - listEntriesToDelete.AddLast(pe); - } + Debug.Assert(pdoOrg.Uuid.Equals(pdoSrc.Uuid)); + + if(pdoSrc.DeletionTime > pdoOrg.DeletionTime) + pdoOrg.DeletionTime = pdoSrc.DeletionTime; } - - return ((m_slStatus != null) ? m_slStatus.ContinueWork() : true); - }; - - m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh); - - foreach(PwGroup pg in listGroupsToDelete) - pg.ParentGroup.Groups.Remove(pg); - foreach(PwEntry pe in listEntriesToDelete) - pe.ParentGroup.Entries.Remove(pe); - - if(bCopyDeletionInfoToLocal) - { - foreach(PwDeletedObject pdoNew in listDelObjects) + else // Add { - bool bCopy = true; - - foreach(PwDeletedObject pdoLocal in m_vDeletedObjects) - { - if(pdoNew.Uuid.Equals(pdoLocal.Uuid)) - { - bCopy = false; - - if(pdoNew.DeletionTime > pdoLocal.DeletionTime) - pdoLocal.DeletionTime = pdoNew.DeletionTime; - - break; - } - } - - if(bCopy) m_vDeletedObjects.Add(pdoNew); + m_vDeletedObjects.Add(pdoSrc); + dOrgDel[pdoSrc.Uuid] = pdoSrc; } } } - private void RelocateGroups(PwObjectPool ppOrgStructure, - PwObjectPool ppSrcStructure) + private void ApplyDeletions(PwObjectList l, Predicate fCanDelete, + Dictionary dOrgDel) + where T : class, ITimeLogger, IStructureItem, IDeepCloneable + { + int n = (int)l.UCount; + for(int i = n - 1; i >= 0; --i) + { + if((m_slStatus != null) && !m_slStatus.ContinueWork()) break; + + T t = l.GetAt((uint)i); + + PwDeletedObject pdo; + if(dOrgDel.TryGetValue(t.Uuid, out pdo)) + { + Debug.Assert(t.Uuid.Equals(pdo.Uuid)); + + bool bDel = (TimeUtil.Compare(t.LastModificationTime, + pdo.DeletionTime, true) < 0); + bDel &= fCanDelete(t); + + if(bDel) l.RemoveAt((uint)i); + else + { + // Prevent future deletion attempts; this also prevents + // delayed deletions (emptying a group could cause a + // group to be deleted, if the deletion was prevented + // before due to the group not being empty) + if(!m_vDeletedObjects.Remove(pdo)) { Debug.Assert(false); } + if(!dOrgDel.Remove(pdo.Uuid)) { Debug.Assert(false); } + } + } + } + } + + private static bool SafeCanDeleteGroup(PwGroup pg) + { + if(pg == null) { Debug.Assert(false); return false; } + + if(pg.Groups.UCount > 0) return false; + if(pg.Entries.UCount > 0) return false; + return true; + } + + private static bool SafeCanDeleteEntry(PwEntry pe) + { + if(pe == null) { Debug.Assert(false); return false; } + + return true; + } + + // Apply deletions on all objects in the specified container + // (but not the container itself), using post-order traversal + // to avoid implicit deletions; + // https://sourceforge.net/p/keepass/bugs/1499/ + private void ApplyDeletions(PwGroup pgContainer, + Dictionary dOrgDel) + { + foreach(PwGroup pg in pgContainer.Groups) // Post-order traversal + { + ApplyDeletions(pg, dOrgDel); + } + + ApplyDeletions(pgContainer.Groups, PwDatabase.SafeCanDeleteGroup, dOrgDel); + ApplyDeletions(pgContainer.Entries, PwDatabase.SafeCanDeleteEntry, dOrgDel); + } + + private void RelocateGroups(PwObjectPoolEx ppOrg, PwObjectPoolEx ppSrc) { PwObjectList vGroups = m_pgRootGroup.GetGroups(true); @@ -949,10 +1055,10 @@ namespace ModernKeePassLib if((m_slStatus != null) && !m_slStatus.ContinueWork()) break; // PwGroup pgOrg = pgOrgStructure.FindGroup(pg.Uuid, true); - IStructureItem ptOrg = ppOrgStructure.Get(pg.Uuid); + IStructureItem ptOrg = ppOrg.GetItemByUuid(pg.Uuid); if(ptOrg == null) continue; // PwGroup pgSrc = pgSrcStructure.FindGroup(pg.Uuid, true); - IStructureItem ptSrc = ppSrcStructure.Get(pg.Uuid); + IStructureItem ptSrc = ppSrc.GetItemByUuid(pg.Uuid); if(ptSrc == null) continue; PwGroup pgOrgParent = ptOrg.ParentGroup; @@ -966,8 +1072,8 @@ namespace ModernKeePassLib if(pgOrgParent.Uuid.Equals(pgSrcParent.Uuid)) { - pg.LocationChanged = ((ptSrc.LocationChanged > ptOrg.LocationChanged) ? - ptSrc.LocationChanged : ptOrg.LocationChanged); + // pg.LocationChanged = ((ptSrc.LocationChanged > ptOrg.LocationChanged) ? + // ptSrc.LocationChanged : ptOrg.LocationChanged); continue; } @@ -979,8 +1085,12 @@ namespace ModernKeePassLib if(pgLocal.IsContainedIn(pg)) continue; pg.ParentGroup.Groups.Remove(pg); - pgLocal.AddGroup(pg, true); - pg.LocationChanged = ptSrc.LocationChanged; + + // pgLocal.AddGroup(pg, true); + InsertObjectAtBestPos(pgLocal.Groups, pg, ppSrc); + pg.ParentGroup = pgLocal; + + // pg.LocationChanged = ptSrc.LocationChanged; } else { @@ -992,8 +1102,7 @@ namespace ModernKeePassLib Debug.Assert(m_pgRootGroup.GetGroups(true).UCount == vGroups.UCount); } - private void RelocateEntries(PwObjectPool ppOrgStructure, - PwObjectPool ppSrcStructure) + private void RelocateEntries(PwObjectPoolEx ppOrg, PwObjectPoolEx ppSrc) { PwObjectList vEntries = m_pgRootGroup.GetEntries(true); @@ -1002,18 +1111,18 @@ namespace ModernKeePassLib if((m_slStatus != null) && !m_slStatus.ContinueWork()) break; // PwEntry peOrg = pgOrgStructure.FindEntry(pe.Uuid, true); - IStructureItem ptOrg = ppOrgStructure.Get(pe.Uuid); + IStructureItem ptOrg = ppOrg.GetItemByUuid(pe.Uuid); if(ptOrg == null) continue; // PwEntry peSrc = pgSrcStructure.FindEntry(pe.Uuid, true); - IStructureItem ptSrc = ppSrcStructure.Get(pe.Uuid); + IStructureItem ptSrc = ppSrc.GetItemByUuid(pe.Uuid); if(ptSrc == null) continue; PwGroup pgOrg = ptOrg.ParentGroup; PwGroup pgSrc = ptSrc.ParentGroup; if(pgOrg.Uuid.Equals(pgSrc.Uuid)) { - pe.LocationChanged = ((ptSrc.LocationChanged > ptOrg.LocationChanged) ? - ptSrc.LocationChanged : ptOrg.LocationChanged); + // pe.LocationChanged = ((ptSrc.LocationChanged > ptOrg.LocationChanged) ? + // ptSrc.LocationChanged : ptOrg.LocationChanged); continue; } @@ -1023,8 +1132,12 @@ namespace ModernKeePassLib if(pgLocal == null) { Debug.Assert(false); continue; } pe.ParentGroup.Entries.Remove(pe); - pgLocal.AddEntry(pe, true); - pe.LocationChanged = ptSrc.LocationChanged; + + // pgLocal.AddEntry(pe, true); + InsertObjectAtBestPos(pgLocal.Entries, pe, ppSrc); + pe.ParentGroup = pgLocal; + + // pe.LocationChanged = ptSrc.LocationChanged; } else { @@ -1036,255 +1149,295 @@ namespace ModernKeePassLib Debug.Assert(m_pgRootGroup.GetEntries(true).UCount == vEntries.UCount); } - private void ReorderGroups(PwObjectPool ppOrgStructure, - PwObjectPool ppSrcStructure) + private void ReorderObjects(PwGroup pg, PwObjectPoolEx ppOrg, + PwObjectPoolEx ppSrc) { - GroupHandler gh = delegate(PwGroup pg) - { - ReorderObjectList(pg.Groups, ppOrgStructure, - ppSrcStructure, false); - return true; - }; + ReorderObjectList(pg.Groups, ppOrg, ppSrc); + ReorderObjectList(pg.Entries, ppOrg, ppSrc); - ReorderObjectList(m_pgRootGroup.Groups, ppOrgStructure, - ppSrcStructure, false); - m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, null); + foreach(PwGroup pgSub in pg.Groups) + { + ReorderObjects(pgSub, ppOrg, ppSrc); + } } - private void ReorderEntries(PwObjectPool ppOrgStructure, - PwObjectPool ppSrcStructure) + private void ReorderObjectList(PwObjectList lItems, + PwObjectPoolEx ppOrg, PwObjectPoolEx ppSrc) + where T : class, ITimeLogger, IStructureItem, IDeepCloneable { - GroupHandler gh = delegate(PwGroup pg) - { - ReorderObjectList(pg.Entries, ppOrgStructure, - ppSrcStructure, true); - return true; - }; - - ReorderObjectList(m_pgRootGroup.Entries, ppOrgStructure, - ppSrcStructure, true); - m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, null); - } - - private void ReorderObjectList(PwObjectList vItems, - PwObjectPool ppOrgStructure, PwObjectPool ppSrcStructure, bool bEntries) - where T : class, IStructureItem, IDeepCloneable - { - if(!ObjectListRequiresReorder(vItems, ppOrgStructure, ppSrcStructure, - bEntries)) return; + List> lBlocks = PartitionConsec(lItems, ppOrg, ppSrc); + if(lBlocks.Count <= 1) return; #if DEBUG - PwObjectList vOrgListItems = vItems.CloneShallow(); + PwObjectList lOrgItems = lItems.CloneShallow(); #endif - Queue> qToDo = new Queue>(); - qToDo.Enqueue(new KeyValuePair(0, vItems.UCount - 1)); + Queue> qToDo = new Queue>(); + qToDo.Enqueue(new KeyValuePair(0, lBlocks.Count - 1)); while(qToDo.Count > 0) { if((m_slStatus != null) && !m_slStatus.ContinueWork()) break; - KeyValuePair kvp = qToDo.Dequeue(); - if(kvp.Value <= kvp.Key) { Debug.Assert(false); continue; } + KeyValuePair kvp = qToDo.Dequeue(); + if(kvp.Key >= kvp.Value) { Debug.Assert(false); continue; } - Queue qRelBefore = new Queue(); - Queue qRelAfter = new Queue(); - uint uPivot = FindLocationChangedPivot(vItems, kvp, ppOrgStructure, - ppSrcStructure, qRelBefore, qRelAfter, bEntries); - T ptPivot = vItems.GetAt(uPivot); + PwObjectPoolEx pPool; + int iPivot = FindLocationChangedPivot(lBlocks, kvp, out pPool); + PwObjectBlock bPivot = lBlocks[iPivot]; - List vToSort = vItems.GetRange(kvp.Key, kvp.Value); - Queue qBefore = new Queue(); - Queue qAfter = new Queue(); + T tPivotPrimary = bPivot.PrimaryItem; + if(tPivotPrimary == null) { Debug.Assert(false); continue; } + ulong idPivot = pPool.GetIdByUuid(tPivotPrimary.Uuid); + if(idPivot == 0) { Debug.Assert(false); continue; } + + Queue> qBefore = new Queue>(); + Queue> qAfter = new Queue>(); bool bBefore = true; - foreach(T pt in vToSort) + for(int i = kvp.Key; i <= kvp.Value; ++i) { - if(pt == ptPivot) { bBefore = false; continue; } + if(i == iPivot) { bBefore = false; continue; } - bool bAdded = false; - foreach(PwUuid puBefore in qRelBefore) + PwObjectBlock b = lBlocks[i]; + Debug.Assert(b.LocationChanged <= bPivot.LocationChanged); + + T t = b.PrimaryItem; + if(t != null) { - if(puBefore.Equals(pt.Uuid)) + ulong idBPri = pPool.GetIdByUuid(t.Uuid); + if(idBPri > 0) { - qBefore.Enqueue(pt); - bAdded = true; - break; + if(idBPri < idPivot) qBefore.Enqueue(b); + else qAfter.Enqueue(b); + + continue; } } - if(bAdded) continue; + else { Debug.Assert(false); } - foreach(PwUuid puAfter in qRelAfter) - { - if(puAfter.Equals(pt.Uuid)) - { - qAfter.Enqueue(pt); - bAdded = true; - break; - } - } - if(bAdded) continue; - - if(bBefore) qBefore.Enqueue(pt); - else qAfter.Enqueue(pt); - } - Debug.Assert(bBefore == false); - - uint uPos = kvp.Key; - while(qBefore.Count > 0) vItems.SetAt(uPos++, qBefore.Dequeue()); - vItems.SetAt(uPos++, ptPivot); - while(qAfter.Count > 0) vItems.SetAt(uPos++, qAfter.Dequeue()); - Debug.Assert(uPos == (kvp.Value + 1)); - - int iNewPivot = vItems.IndexOf(ptPivot); - if((iNewPivot < (int)kvp.Key) || (iNewPivot > (int)kvp.Value)) - { - Debug.Assert(false); - continue; + if(bBefore) qBefore.Enqueue(b); + else qAfter.Enqueue(b); } - if((iNewPivot - 1) > (int)kvp.Key) - qToDo.Enqueue(new KeyValuePair(kvp.Key, - (uint)(iNewPivot - 1))); + int j = kvp.Key; + while(qBefore.Count > 0) { lBlocks[j] = qBefore.Dequeue(); ++j; } + int iNewPivot = j; + lBlocks[j] = bPivot; + ++j; + while(qAfter.Count > 0) { lBlocks[j] = qAfter.Dequeue(); ++j; } + Debug.Assert(j == (kvp.Value + 1)); - if((iNewPivot + 1) < (int)kvp.Value) - qToDo.Enqueue(new KeyValuePair((uint)(iNewPivot + 1), - kvp.Value)); + if((iNewPivot - 1) > kvp.Key) + qToDo.Enqueue(new KeyValuePair(kvp.Key, iNewPivot - 1)); + if((iNewPivot + 1) < kvp.Value) + qToDo.Enqueue(new KeyValuePair(iNewPivot + 1, kvp.Value)); } -#if DEBUG - foreach(T ptItem in vOrgListItems) + uint u = 0; + foreach(PwObjectBlock b in lBlocks) { - Debug.Assert(vItems.IndexOf(ptItem) >= 0); + foreach(T t in b) + { + lItems.SetAt(u, t); + ++u; + } + } + Debug.Assert(u == lItems.UCount); + +#if DEBUG + Debug.Assert(u == lOrgItems.UCount); + foreach(T ptItem in lOrgItems) + { + Debug.Assert(lItems.IndexOf(ptItem) >= 0); } #endif } - private static uint FindLocationChangedPivot(PwObjectList vItems, - KeyValuePair kvpRange, PwObjectPool ppOrgStructure, - PwObjectPool ppSrcStructure, Queue qBefore, Queue qAfter, - bool bEntries) - where T : class, IStructureItem, IDeepCloneable + private static List> PartitionConsec(PwObjectList lItems, + PwObjectPoolEx ppOrg, PwObjectPoolEx ppSrc) + where T : class, ITimeLogger, IStructureItem, IDeepCloneable { - uint uPosMax = kvpRange.Key; - DateTime dtMax = DateTime.MinValue; - List vNeighborSrc = null; + List> lBlocks = new List>(); - for(uint u = kvpRange.Key; u <= kvpRange.Value; ++u) + Dictionary dItemUuids = new Dictionary(); + foreach(T t in lItems) { dItemUuids[t.Uuid] = true; } + + uint n = lItems.UCount; + for(uint u = 0; u < n; ++u) { - T pt = vItems.GetAt(u); + T t = lItems.GetAt(u); - // IStructureItem ptOrg = pgOrgStructure.FindObject(pt.Uuid, true, bEntries); - IStructureItem ptOrg = ppOrgStructure.Get(pt.Uuid); - if((ptOrg != null) && (ptOrg.LocationChanged > dtMax)) + PwObjectBlock b = new PwObjectBlock(); + + DateTime dtLoc; + PwObjectPoolEx pPool = GetBestPool(t, ppOrg, ppSrc, out dtLoc); + b.Add(t, dtLoc, pPool); + + lBlocks.Add(b); + + ulong idOrg = ppOrg.GetIdByUuid(t.Uuid); + ulong idSrc = ppSrc.GetIdByUuid(t.Uuid); + if((idOrg == 0) || (idSrc == 0)) continue; + + for(uint x = u + 1; x < n; ++x) { - uPosMax = u; - dtMax = ptOrg.LocationChanged; // No 'continue' + T tNext = lItems.GetAt(x); - PwGroup pgParent = ptOrg.ParentGroup; - if(pgParent != null) - vNeighborSrc = pgParent.GetObjects(false, bEntries); - else + ulong idOrgNext = idOrg + 1; + while(true) { - Debug.Assert(false); // Org root should be excluded - vNeighborSrc = new List(); - vNeighborSrc.Add(ptOrg); + IStructureItem ptOrg = ppOrg.GetItemById(idOrgNext); + if(ptOrg == null) { idOrgNext = 0; break; } + if(ptOrg.Uuid.Equals(tNext.Uuid)) break; // Found it + if(dItemUuids.ContainsKey(ptOrg.Uuid)) { idOrgNext = 0; break; } + ++idOrgNext; } - } + if(idOrgNext == 0) break; - // IStructureItem ptSrc = pgSrcStructure.FindObject(pt.Uuid, true, bEntries); - IStructureItem ptSrc = ppSrcStructure.Get(pt.Uuid); - if((ptSrc != null) && (ptSrc.LocationChanged > dtMax)) - { - uPosMax = u; - dtMax = ptSrc.LocationChanged; // No 'continue' - - PwGroup pgParent = ptSrc.ParentGroup; - if(pgParent != null) - vNeighborSrc = pgParent.GetObjects(false, bEntries); - else + ulong idSrcNext = idSrc + 1; + while(true) { - // pgParent may be null (for the source root group) - vNeighborSrc = new List(); - vNeighborSrc.Add(ptSrc); + IStructureItem ptSrc = ppSrc.GetItemById(idSrcNext); + if(ptSrc == null) { idSrcNext = 0; break; } + if(ptSrc.Uuid.Equals(tNext.Uuid)) break; // Found it + if(dItemUuids.ContainsKey(ptSrc.Uuid)) { idSrcNext = 0; break; } + ++idSrcNext; } + if(idSrcNext == 0) break; + + pPool = GetBestPool(tNext, ppOrg, ppSrc, out dtLoc); + b.Add(tNext, dtLoc, pPool); + + ++u; + idOrg = idOrgNext; + idSrc = idSrcNext; } } - GetNeighborItems(vNeighborSrc, vItems.GetAt(uPosMax).Uuid, qBefore, qAfter); - return uPosMax; + return lBlocks; } - private static void GetNeighborItems(List vItems, - PwUuid pwPivot, Queue qBefore, Queue qAfter) + private static PwObjectPoolEx GetBestPool(T t, PwObjectPoolEx ppOrg, + PwObjectPoolEx ppSrc, out DateTime dtLoc) + where T : class, ITimeLogger, IStructureItem, IDeepCloneable { - qBefore.Clear(); - qAfter.Clear(); + PwObjectPoolEx p = null; + dtLoc = TimeUtil.SafeMinValueUtc; - // Checks after clearing the queues - if(vItems == null) { Debug.Assert(false); return; } // No throw - - bool bBefore = true; - for(int i = 0; i < vItems.Count; ++i) + IStructureItem ptOrg = ppOrg.GetItemByUuid(t.Uuid); + if(ptOrg != null) { - PwUuid pw = vItems[i].Uuid; - - if(pw.Equals(pwPivot)) bBefore = false; - else if(bBefore) qBefore.Enqueue(pw); - else qAfter.Enqueue(pw); + dtLoc = ptOrg.LocationChanged; + p = ppOrg; } - Debug.Assert(bBefore == false); + + IStructureItem ptSrc = ppSrc.GetItemByUuid(t.Uuid); + if((ptSrc != null) && (ptSrc.LocationChanged > dtLoc)) + { + dtLoc = ptSrc.LocationChanged; + p = ppSrc; + } + + Debug.Assert(p != null); + return p; } - /// - /// Method to check whether a reordering is required. This fast test - /// allows to skip the reordering routine, resulting in a large - /// performance increase. - /// - private bool ObjectListRequiresReorder(PwObjectList vItems, - PwObjectPool ppOrgStructure, PwObjectPool ppSrcStructure, bool bEntries) - where T : class, IStructureItem, IDeepCloneable + private static int FindLocationChangedPivot(List> lBlocks, + KeyValuePair kvpRange, out PwObjectPoolEx pPool) + where T : class, ITimeLogger, IStructureItem, IDeepCloneable { - Debug.Assert(ppOrgStructure.ContainsOnlyType(bEntries ? typeof(PwEntry) : typeof(PwGroup))); - Debug.Assert(ppSrcStructure.ContainsOnlyType(bEntries ? typeof(PwEntry) : typeof(PwGroup))); - if(vItems.UCount <= 1) return false; + pPool = null; - if((m_slStatus != null) && !m_slStatus.ContinueWork()) return false; + int iPosMax = kvpRange.Key; + DateTime dtMax = TimeUtil.SafeMinValueUtc; - T ptFirst = vItems.GetAt(0); - // IStructureItem ptOrg = pgOrgStructure.FindObject(ptFirst.Uuid, true, bEntries); - IStructureItem ptOrg = ppOrgStructure.Get(ptFirst.Uuid); - if(ptOrg == null) return true; - // IStructureItem ptSrc = pgSrcStructure.FindObject(ptFirst.Uuid, true, bEntries); - IStructureItem ptSrc = ppSrcStructure.Get(ptFirst.Uuid); - if(ptSrc == null) return true; - - if(ptFirst.ParentGroup == null) { Debug.Assert(false); return true; } - PwGroup pgOrgParent = ptOrg.ParentGroup; - if(pgOrgParent == null) return true; // Root might be in tree - PwGroup pgSrcParent = ptSrc.ParentGroup; - if(pgSrcParent == null) return true; // Root might be in tree - - if(!ptFirst.ParentGroup.Uuid.Equals(pgOrgParent.Uuid)) return true; - if(!pgOrgParent.Uuid.Equals(pgSrcParent.Uuid)) return true; - - List lOrg = pgOrgParent.GetObjects(false, bEntries); - List lSrc = pgSrcParent.GetObjects(false, bEntries); - if(vItems.UCount != (uint)lOrg.Count) return true; - if(lOrg.Count != lSrc.Count) return true; - - for(uint u = 0; u < vItems.UCount; ++u) + for(int i = kvpRange.Key; i <= kvpRange.Value; ++i) { - IStructureItem pt = vItems.GetAt(u); - Debug.Assert(pt.ParentGroup == ptFirst.ParentGroup); - - if(!pt.Uuid.Equals(lOrg[(int)u].Uuid)) return true; - if(!pt.Uuid.Equals(lSrc[(int)u].Uuid)) return true; - if(pt.LocationChanged != lOrg[(int)u].LocationChanged) return true; - if(pt.LocationChanged != lSrc[(int)u].LocationChanged) return true; + PwObjectBlock b = lBlocks[i]; + if(b.LocationChanged > dtMax) + { + iPosMax = i; + dtMax = b.LocationChanged; + pPool = b.PoolAssoc; + } } - return false; + return iPosMax; + } + + private static void MergeInLocationChanged(PwGroup pg, + PwObjectPoolEx ppOrg, PwObjectPoolEx ppSrc) + { + GroupHandler gh = delegate(PwGroup pgSub) + { + DateTime dt; + if(GetBestPool(pgSub, ppOrg, ppSrc, out dt) != null) + pgSub.LocationChanged = dt; + else { Debug.Assert(false); } + return true; + }; + + EntryHandler eh = delegate(PwEntry pe) + { + DateTime dt; + if(GetBestPool(pe, ppOrg, ppSrc, out dt) != null) + pe.LocationChanged = dt; + else { Debug.Assert(false); } + return true; + }; + + gh(pg); + pg.TraverseTree(TraversalMethod.PreOrder, gh, eh); + } + + private static void InsertObjectAtBestPos(PwObjectList lItems, + T tNew, PwObjectPoolEx ppSrc) + where T : class, ITimeLogger, IStructureItem, IDeepCloneable + { + if(tNew == null) { Debug.Assert(false); return; } + + ulong idSrc = ppSrc.GetIdByUuid(tNew.Uuid); + if(idSrc == 0) { Debug.Assert(false); lItems.Add(tNew); return; } + + const uint uIdOffset = 2; + Dictionary dOrg = new Dictionary(); + for(uint u = 0; u < lItems.UCount; ++u) + dOrg[lItems.GetAt(u).Uuid] = uIdOffset + u; + + ulong idSrcNext = idSrc + 1; + uint idOrgNext = 0; + while(true) + { + IStructureItem pNext = ppSrc.GetItemById(idSrcNext); + if(pNext == null) break; + if(dOrg.TryGetValue(pNext.Uuid, out idOrgNext)) break; + ++idSrcNext; + } + + if(idOrgNext != 0) + { + lItems.Insert(idOrgNext - uIdOffset, tNew); + return; + } + + ulong idSrcPrev = idSrc - 1; + uint idOrgPrev = 0; + while(true) + { + IStructureItem pPrev = ppSrc.GetItemById(idSrcPrev); + if(pPrev == null) break; + if(dOrg.TryGetValue(pPrev.Uuid, out idOrgPrev)) break; + --idSrcPrev; + } + + if(idOrgPrev != 0) + { + lItems.Insert(idOrgPrev + 1 - uIdOffset, tNew); + return; + } + + lItems.Add(tNew); } private void MergeInDbProperties(PwDatabase pdSource, PwMergeMethod mm) @@ -1294,6 +1447,14 @@ namespace ModernKeePassLib return; bool bForce = (mm == PwMergeMethod.OverwriteExisting); + bool bSourceNewer = (pdSource.m_dtSettingsChanged > m_dtSettingsChanged); + + if(bForce || bSourceNewer) + { + m_dtSettingsChanged = pdSource.m_dtSettingsChanged; + + m_clr = pdSource.m_clr; + } if(bForce || (pdSource.m_dtNameChanged > m_dtNameChanged)) { @@ -1313,8 +1474,6 @@ namespace ModernKeePassLib m_dtDefaultUserChanged = pdSource.m_dtDefaultUserChanged; } - if(bForce) m_clr = pdSource.m_clr; - PwUuid pwPrefBin = m_pwRecycleBin, pwAltBin = pdSource.m_pwRecycleBin; if(bForce || (pdSource.m_dtRecycleBinChanged > m_dtRecycleBinChanged)) { @@ -1341,6 +1500,16 @@ namespace ModernKeePassLib else if(m_pgRootGroup.FindGroup(pwAltTmp, true) != null) m_pwEntryTemplatesGroup = pwAltTmp; else m_pwEntryTemplatesGroup = PwUuid.Zero; // Debug.Assert(false); + + foreach(KeyValuePair kvp in pdSource.m_dCustomData) + { + if(bSourceNewer || !m_dCustomData.Exists(kvp.Key)) + m_dCustomData.Set(kvp.Key, kvp.Value); + } + + VariantDictionary vdLocal = m_dPublicCustomData; // Backup + m_dPublicCustomData = (VariantDictionary)pdSource.m_dPublicCustomData.Clone(); + if(!bSourceNewer) vdLocal.CopyTo(m_dPublicCustomData); // Merge } private void MergeEntryHistory(PwEntry pe, PwEntry peSource, @@ -1461,19 +1630,46 @@ namespace ModernKeePassLib return -1; } - /// - /// Get a custom icon. This function can return null, if - /// no cached image of the icon is available. - /// - /// ID of the icon. - /// Image data. +#if KeePassUAP public Image GetCustomIcon(PwUuid pwIconId) { int nIndex = GetCustomIconIndex(pwIconId); + if(nIndex >= 0) + return m_vCustomIcons[nIndex].GetImage(); + else { Debug.Assert(false); } - if(nIndex >= 0) return m_vCustomIcons[nIndex].Image; - else { Debug.Assert(false); return null; } + return null; } +#elif !KeePassLibSD + [Obsolete("Additionally specify the size.")] + public Image GetCustomIcon(PwUuid pwIconId) + { + return GetCustomIcon(pwIconId, 16, 16); // Backward compatibility + } + + /// + /// Get a custom icon. This method can return null, + /// e.g. if no cached image of the icon is available. + /// + /// ID of the icon. + /// Width of the returned image. If this is + /// negative, the image is returned in its original size. + /// Height of the returned image. If this is + /// negative, the image is returned in its original size. + public Image GetCustomIcon(PwUuid pwIconId, int w, int h) + { + int nIndex = GetCustomIconIndex(pwIconId); + if(nIndex >= 0) + { + if((w >= 0) && (h >= 0)) + return m_vCustomIcons[nIndex].GetImage(w, h); + else return m_vCustomIcons[nIndex].GetImage(); // No assert + } + else { Debug.Assert(false); } + + return null; + } +#endif public bool DeleteCustomIcons(List vUuidsToDelete) { @@ -1538,33 +1734,97 @@ namespace ModernKeePassLib RemoveCustomIconUuid(peHistory, vToDelete); } - private bool ValidateUuidUniqueness() + private int GetTotalObjectUuidCount() { -#if DEBUG - List l = new List(); - bool bAllUnique = true; + uint uGroups, uEntries; + m_pgRootGroup.GetCounts(true, out uGroups, out uEntries); + + uint uTotal = uGroups + uEntries + 1; // 1 for root group + if(uTotal > 0x7FFFFFFFU) { Debug.Assert(false); return 0x7FFFFFFF; } + return (int)uTotal; + } + + internal bool HasDuplicateUuids() + { + int nTotal = GetTotalObjectUuidCount(); + Dictionary d = new Dictionary(nTotal); + bool bDupFound = false; GroupHandler gh = delegate(PwGroup pg) { - foreach(PwUuid u in l) - bAllUnique &= !pg.Uuid.Equals(u); - l.Add(pg.Uuid); - return bAllUnique; + PwUuid pu = pg.Uuid; + if(d.ContainsKey(pu)) + { + bDupFound = true; + return false; + } + + d.Add(pu, null); + Debug.Assert(d.ContainsKey(pu)); + return true; }; EntryHandler eh = delegate(PwEntry pe) { - foreach(PwUuid u in l) - bAllUnique &= !pe.Uuid.Equals(u); - l.Add(pe.Uuid); - return bAllUnique; + PwUuid pu = pe.Uuid; + if(d.ContainsKey(pu)) + { + bDupFound = true; + return false; + } + + d.Add(pu, null); + Debug.Assert(d.ContainsKey(pu)); + return true; }; + gh(m_pgRootGroup); m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh); - return bAllUnique; -#else - return true; -#endif + + Debug.Assert(bDupFound || (d.Count == nTotal)); + return bDupFound; + } + + internal void FixDuplicateUuids() + { + int nTotal = GetTotalObjectUuidCount(); + Dictionary d = new Dictionary(nTotal); + + GroupHandler gh = delegate(PwGroup pg) + { + PwUuid pu = pg.Uuid; + if(d.ContainsKey(pu)) + { + pu = new PwUuid(true); + while(d.ContainsKey(pu)) { Debug.Assert(false); pu = new PwUuid(true); } + + pg.Uuid = pu; + } + + d.Add(pu, null); + return true; + }; + + EntryHandler eh = delegate(PwEntry pe) + { + PwUuid pu = pe.Uuid; + if(d.ContainsKey(pu)) + { + pu = new PwUuid(true); + while(d.ContainsKey(pu)) { Debug.Assert(false); pu = new PwUuid(true); } + + pe.SetUuid(pu, true); + } + + d.Add(pu, null); + return true; + }; + + gh(m_pgRootGroup); + m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh); + + Debug.Assert(d.Count == nTotal); + Debug.Assert(!HasDuplicateUuids()); } /* public void CreateBackupFile(IStatusLogger sl) @@ -1628,7 +1888,7 @@ namespace ModernKeePassLib if(m_bUseRecycleBin) pgRecycleBin = m_pgRootGroup.FindGroup(m_pwRecycleBin, true); - DateTime dtNow = DateTime.Now; + DateTime dtNow = DateTime.UtcNow; PwObjectList l = m_pgRootGroup.GetEntries(true); int i = 0; while(true) @@ -1753,7 +2013,7 @@ namespace ModernKeePassLib if((pg.Groups.UCount > 0) || (pg.Entries.UCount > 0)) continue; pg.ParentGroup.Groups.Remove(pg); - m_vDeletedObjects.Add(new PwDeletedObject(pg.Uuid, DateTime.Now)); + m_vDeletedObjects.Add(new PwDeletedObject(pg.Uuid, DateTime.UtcNow)); ++uDeleted; } diff --git a/ModernKeePassLib/PwDefs.cs b/ModernKeePassLib/PwDefs.cs index c07cabd..b6832e9 100644 --- a/ModernKeePassLib/PwDefs.cs +++ b/ModernKeePassLib/PwDefs.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,9 +19,9 @@ using System; using System.Collections.Generic; -using System.Xml.Serialization; using System.ComponentModel; using System.Diagnostics; +using System.Xml.Serialization; using ModernKeePassLib.Delegates; using ModernKeePassLib.Interfaces; @@ -55,63 +55,64 @@ namespace ModernKeePassLib /// e.g. 2.19 = 0x02130000. /// It is highly recommended to use FileVersion64 instead. /// - public const uint Version32 = 0x021C0000; + public const uint Version32 = 0x02250000; /// /// Version, encoded as 64-bit unsigned integer /// (component-wise, 16 bits per component). /// - public const ulong FileVersion64 = 0x0002001C00000000UL; + public const ulong FileVersion64 = 0x0002002500000000UL; /// /// Version, encoded as string. /// - public const string VersionString = "2.28"; + public const string VersionString = "2.37"; - public const string Copyright = @"Copyright © 2003-2014 Dominik Reichl"; + public const string Copyright = @"Copyright © 2003-2017 Dominik Reichl"; /// /// Product website URL. Terminated by a forward slash. /// - public const string HomepageUrl = "http://keepass.info/"; - - /// - /// Product donations URL. - /// - public const string DonationsUrl = "http://keepass.info/donate.html"; - - /// - /// URL to the online plugins page. - /// - public const string PluginsUrl = "http://keepass.info/plugins.html"; + public const string HomepageUrl = "https://keepass.info/"; /// /// URL to the online translations page. /// - public const string TranslationsUrl = "http://keepass.info/translations.html"; + public const string TranslationsUrl = "https://keepass.info/translations.html"; /// - /// URL to a TXT file (eventually compressed) that contains information - /// about the latest KeePass version available on the website. + /// URL to the online plugins page. /// - public const string VersionUrl = "http://keepass.info/update/version2x.txt.gz"; + public const string PluginsUrl = "https://keepass.info/plugins.html"; + + /// + /// Product donations URL. + /// + public const string DonationsUrl = "https://keepass.info/donate.html"; /// /// URL to the root path of the online KeePass help. Terminated by /// a forward slash. /// - public const string HelpUrl = "http://keepass.info/help/"; + public const string HelpUrl = "https://keepass.info/help/"; + + /// + /// URL to a TXT file (eventually compressed) that contains information + /// about the latest KeePass version available on the website. + /// + public const string VersionUrl = "https://www.dominik-reichl.de/update/version2x.txt.gz"; /// /// A DateTime object that represents the time when the assembly /// was loaded. /// - public static readonly DateTime DtDefaultNow = DateTime.Now; + public static readonly DateTime DtDefaultNow = DateTime.UtcNow; /// - /// Default number of master key encryption/transformation rounds (making dictionary attacks harder). + /// Default number of master key encryption/transformation rounds + /// (making dictionary attacks harder). /// - public const ulong DefaultKeyEncryptionRounds = 6000; + public const ulong DefaultKeyEncryptionRounds = 60000; /// /// Default identifier string for the title field. Should not contain @@ -294,6 +295,22 @@ namespace ModernKeePassLib set { m_bSearchInOther = value; } } + private bool m_bSearchInStringNames = false; + [DefaultValue(false)] + public bool SearchInStringNames + { + get { return m_bSearchInStringNames; } + set { m_bSearchInStringNames = value; } + } + + private bool m_bSearchInTags = true; + [DefaultValue(true)] + public bool SearchInTags + { + get { return m_bSearchInTags; } + set { m_bSearchInTags = value; } + } + private bool m_bSearchInUuids = false; [DefaultValue(false)] public bool SearchInUuids @@ -310,15 +327,7 @@ namespace ModernKeePassLib set { m_bSearchInGroupNames = value; } } - private bool m_bSearchInTags = true; - [DefaultValue(true)] - public bool SearchInTags - { - get { return m_bSearchInTags; } - set { m_bSearchInTags = value; } - } - -#if ModernKeePassLib || KeePassRT +#if ModernKeePassLib || KeePassUAP private StringComparison m_scType = StringComparison.OrdinalIgnoreCase; #else private StringComparison m_scType = StringComparison.InvariantCultureIgnoreCase; @@ -379,20 +388,21 @@ namespace ModernKeePassLib { SearchParameters sp = new SearchParameters(); - // sp.m_strText = string.Empty; - // sp.m_bRegex = false; + Debug.Assert(sp.m_strText.Length == 0); + Debug.Assert(!sp.m_bRegex); sp.m_bSearchInTitles = false; sp.m_bSearchInUserNames = false; - // sp.m_bSearchInPasswords = false; + Debug.Assert(!sp.m_bSearchInPasswords); sp.m_bSearchInUrls = false; sp.m_bSearchInNotes = false; sp.m_bSearchInOther = false; - // sp.m_bSearchInUuids = false; - // sp.SearchInGroupNames = false; + Debug.Assert(!sp.m_bSearchInStringNames); sp.m_bSearchInTags = false; - // sp.m_scType = StringComparison.InvariantCultureIgnoreCase; - // sp.m_bExcludeExpired = false; - // m_bRespectEntrySearchingDisabled = true; + Debug.Assert(!sp.m_bSearchInUuids); + Debug.Assert(!sp.m_bSearchInGroupNames); + // Debug.Assert(sp.m_scType == StringComparison.InvariantCultureIgnoreCase); + Debug.Assert(!sp.m_bExcludeExpired); + Debug.Assert(sp.m_bRespectEntrySearchingDisabled); return sp; } diff --git a/ModernKeePassLib/PwDeletedObject.cs b/ModernKeePassLib/PwDeletedObject.cs index d430e6a..69ec27a 100644 --- a/ModernKeePassLib/PwDeletedObject.cs +++ b/ModernKeePassLib/PwDeletedObject.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/PwEntry.cs b/ModernKeePassLib/PwEntry.cs index 2cf38e2..70290fa 100644 --- a/ModernKeePassLib/PwEntry.cs +++ b/ModernKeePassLib/PwEntry.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -63,6 +63,8 @@ namespace ModernKeePassLib private List m_vTags = new List(); + private StringDictionaryEx m_dCustomData = new StringDictionaryEx(); + /// /// UUID of this entry. /// @@ -272,6 +274,23 @@ namespace ModernKeePassLib } } + /// + /// Custom data container that can be used by plugins to store + /// own data in KeePass entries. + /// The data is stored in the encrypted part of encrypted + /// database files. + /// Use unique names for your items, e.g. "PluginName_ItemName". + /// + public StringDictionaryEx CustomData + { + get { return m_dCustomData; } + internal set + { + if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_dCustomData = value; + } + } + public static EventHandler EntryTouched; public EventHandler Touched; @@ -290,8 +309,11 @@ namespace ModernKeePassLib if(bSetTimes) { - m_tCreation = m_tLastMod = m_tLastAccess = - m_tParentGroupLastMod = DateTime.Now; + DateTime dtNow = DateTime.UtcNow; + m_tCreation = dtNow; + m_tLastMod = dtNow; + m_tLastAccess = dtNow; + m_tParentGroupLastMod = dtNow; } } @@ -315,11 +337,22 @@ namespace ModernKeePassLib if(bSetTimes) { - m_tCreation = m_tLastMod = m_tLastAccess = - m_tParentGroupLastMod = DateTime.Now; + DateTime dtNow = DateTime.UtcNow; + m_tCreation = dtNow; + m_tLastMod = dtNow; + m_tLastAccess = dtNow; + m_tParentGroupLastMod = dtNow; } } +#if DEBUG + // For display in debugger + public override string ToString() + { + return (@"PwEntry '" + m_listStrings.ReadSafe(PwDefs.TitleField) + @"'"); + } +#endif + /// /// Clone the current entry. The returned entry is an exact value copy /// of the current entry (including UUID and parent group reference). @@ -356,6 +389,8 @@ namespace ModernKeePassLib peNew.m_vTags = new List(m_vTags); + peNew.m_dCustomData = m_dCustomData.CloneDeep(); + return peNew; } @@ -476,6 +511,8 @@ namespace ModernKeePassLib if(m_vTags[iTag] != pe.m_vTags[iTag]) return false; } + if(!m_dCustomData.Equals(pe.m_dCustomData)) return false; + return true; } @@ -492,10 +529,10 @@ namespace ModernKeePassLib public void AssignProperties(PwEntry peTemplate, bool bOnlyIfNewer, bool bIncludeHistory, bool bAssignLocationChanged) { - Debug.Assert(peTemplate != null); if(peTemplate == null) throw new ArgumentNullException("peTemplate"); + if(peTemplate == null) { Debug.Assert(false); throw new ArgumentNullException("peTemplate"); } - if(bOnlyIfNewer && (TimeUtil.Compare(peTemplate.m_tLastMod, m_tLastMod, - true) < 0)) + if(bOnlyIfNewer && (TimeUtil.Compare(peTemplate.m_tLastMod, + m_tLastMod, true) < 0)) return; // Template UUID should be the same as the current one @@ -505,10 +542,11 @@ namespace ModernKeePassLib if(bAssignLocationChanged) m_tParentGroupLastMod = peTemplate.m_tParentGroupLastMod; - m_listStrings = peTemplate.m_listStrings; - m_listBinaries = peTemplate.m_listBinaries; - m_listAutoType = peTemplate.m_listAutoType; - if(bIncludeHistory) m_listHistory = peTemplate.m_listHistory; + m_listStrings = peTemplate.m_listStrings.CloneDeep(); + m_listBinaries = peTemplate.m_listBinaries.CloneDeep(); + m_listAutoType = peTemplate.m_listAutoType.CloneDeep(); + if(bIncludeHistory) + m_listHistory = peTemplate.m_listHistory.CloneDeep(); m_pwIcon = peTemplate.m_pwIcon; m_pwCustomIconID = peTemplate.m_pwCustomIconID; // Immutable @@ -526,6 +564,8 @@ namespace ModernKeePassLib m_strOverrideUrl = peTemplate.m_strOverrideUrl; m_vTags = new List(peTemplate.m_vTags); + + m_dCustomData = peTemplate.m_dCustomData.CloneDeep(); } /// @@ -549,7 +589,7 @@ namespace ModernKeePassLib /// get touched, too. public void Touch(bool bModified, bool bTouchParents) { - m_tLastAccess = DateTime.Now; + m_tLastAccess = DateTime.UtcNow; ++m_uUsageCount; if(bModified) m_tLastMod = m_tLastAccess; @@ -688,7 +728,7 @@ namespace ModernKeePassLib private void RemoveOldestBackup() { - DateTime dtMin = DateTime.MaxValue; + DateTime dtMin = TimeUtil.SafeMaxValueUtc; uint idxRemove = uint.MaxValue; for(uint u = 0; u < m_listHistory.UCount; ++u) @@ -777,6 +817,9 @@ namespace ModernKeePassLib foreach(string strTag in m_vTags) uSize += (ulong)strTag.Length; + foreach(KeyValuePair kvp in m_dCustomData) + uSize += (ulong)kvp.Key.Length + (ulong)kvp.Value.Length; + return uSize; } @@ -849,7 +892,7 @@ namespace ModernKeePassLib public void SetCreatedNow() { - DateTime dt = DateTime.Now; + DateTime dt = DateTime.UtcNow; m_tCreation = dt; m_tLastAccess = dt; diff --git a/ModernKeePassLib/PwEnums.cs b/ModernKeePassLib/PwEnums.cs index e4fdc81..18a8058 100644 --- a/ModernKeePassLib/PwEnums.cs +++ b/ModernKeePassLib/PwEnums.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -163,6 +163,26 @@ namespace ModernKeePassLib Manual = 2 } + public enum ProxyAuthType + { + None = 0, + + /// + /// Use default user credentials (provided by the system). + /// + Default = 1, + + Manual = 2, + + /// + /// Default or Manual, depending on whether + /// manual credentials are available. + /// This type exists for supporting upgrading from KeePass + /// 2.28 to 2.29; the user cannot select this type. + /// + Auto = 3 + } + /// /// Comparison modes for in-memory protected objects. /// @@ -257,14 +277,43 @@ namespace ModernKeePassLib GetStdOutput = 1, WaitForExit = 2, - // This flag prevents any handles being garbage-collected - // before the started process has terminated, without - // blocking the current thread; // https://sourceforge.net/p/keepass/patches/84/ + /// + /// This flag prevents any handles being garbage-collected + /// before the started process has terminated, without + /// blocking the current thread. + /// GCKeepAlive = 4, // https://sourceforge.net/p/keepass/patches/85/ DoEvents = 8, DisableForms = 16 } + + [Flags] + public enum ScaleTransformFlags + { + None = 0, + + /// + /// UIIcon indicates that the returned image is going + /// to be displayed as icon in the UI and that it is not + /// subject to future changes in size. + /// + UIIcon = 1 + } + + public enum DesktopType + { + None = 0, + Windows, + Gnome, + Kde, + Unity, + Lxde, + Xfce, + Mate, + Cinnamon, + Pantheon + } } diff --git a/ModernKeePassLib/PwGroup.cs b/ModernKeePassLib/PwGroup.cs index b787304..7600b23 100644 --- a/ModernKeePassLib/PwGroup.cs +++ b/ModernKeePassLib/PwGroup.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -67,6 +67,8 @@ namespace ModernKeePassLib private PwUuid m_pwLastTopVisibleEntry = PwUuid.Zero; + private StringDictionaryEx m_dCustomData = new StringDictionaryEx(); + /// /// UUID of this group. /// @@ -281,6 +283,23 @@ namespace ModernKeePassLib } } + /// + /// Custom data container that can be used by plugins to store + /// own data in KeePass groups. + /// The data is stored in the encrypted part of encrypted + /// database files. + /// Use unique names for your items, e.g. "PluginName_ItemName". + /// + public StringDictionaryEx CustomData + { + get { return m_dCustomData; } + internal set + { + if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_dCustomData = value; + } + } + public static EventHandler GroupTouched; public EventHandler Touched; @@ -302,8 +321,11 @@ namespace ModernKeePassLib if(bSetTimes) { - m_tCreation = m_tLastMod = m_tLastAccess = - m_tParentGroupLastMod = DateTime.Now; + DateTime dtNow = DateTime.UtcNow; + m_tCreation = dtNow; + m_tLastMod = dtNow; + m_tLastAccess = dtNow; + m_tParentGroupLastMod = dtNow; } } @@ -320,8 +342,11 @@ namespace ModernKeePassLib if(bSetTimes) { - m_tCreation = m_tLastMod = m_tLastAccess = - m_tParentGroupLastMod = DateTime.Now; + DateTime dtNow = DateTime.UtcNow; + m_tCreation = dtNow; + m_tLastMod = dtNow; + m_tLastAccess = dtNow; + m_tParentGroupLastMod = dtNow; } if(strName != null) m_strName = strName; @@ -329,6 +354,14 @@ namespace ModernKeePassLib m_pwIcon = pwIcon; } +#if DEBUG + // For display in debugger + public override string ToString() + { + return (@"PwGroup '" + m_strName + @"'"); + } +#endif + /// /// Deeply clone the current group. The returned group will be an exact /// value copy of the current object (including UUID, etc.). @@ -368,6 +401,8 @@ namespace ModernKeePassLib pg.m_pwLastTopVisibleEntry = m_pwLastTopVisibleEntry; + pg.m_dCustomData = m_dCustomData.CloneDeep(); + return pg; } @@ -436,6 +471,8 @@ namespace ModernKeePassLib if(!m_pwLastTopVisibleEntry.Equals(pg.m_pwLastTopVisibleEntry)) return false; + if(!m_dCustomData.Equals(pg.m_dCustomData)) return false; + if((pwOpt & PwCompareOptions.PropertiesOnly) == PwCompareOptions.None) { if(m_listEntries.UCount != pg.m_listEntries.UCount) return false; @@ -501,6 +538,8 @@ namespace ModernKeePassLib m_bEnableSearching = pgTemplate.m_bEnableSearching; m_pwLastTopVisibleEntry = pgTemplate.m_pwLastTopVisibleEntry; + + m_dCustomData = pgTemplate.m_dCustomData.CloneDeep(); } /// @@ -524,7 +563,7 @@ namespace ModernKeePassLib /// get touched, too. public void Touch(bool bModified, bool bTouchParents) { - m_tLastAccess = DateTime.Now; + m_tLastAccess = DateTime.UtcNow; ++m_uUsageCount; if(bModified) m_tLastMod = m_tLastAccess; @@ -629,21 +668,15 @@ namespace ModernKeePassLib } } - if(groupHandler != null) + foreach(PwGroup pg in m_listGroups) { - foreach(PwGroup pg in m_listGroups) + if(groupHandler != null) { if(!groupHandler(pg)) return false; + } - pg.PreOrderTraverseTree(groupHandler, entryHandler); - } - } - else // groupHandler == null - { - foreach(PwGroup pg in m_listGroups) - { - pg.PreOrderTraverseTree(null, entryHandler); - } + if(!pg.PreOrderTraverseTree(groupHandler, entryHandler)) + return false; } return true; @@ -733,96 +766,112 @@ namespace ModernKeePassLib /// /// Search this group and all subgroups for entries. /// - /// Specifies the search method. - /// Entry list in which the search results will - /// be stored. - public void SearchEntries(SearchParameters sp, PwObjectList listStorage) + /// Specifies the search parameters. + /// Entry list in which the search results + /// will be stored. + public void SearchEntries(SearchParameters sp, PwObjectList lResults) { - SearchEntries(sp, listStorage, null); + SearchEntries(sp, lResults, null); } /// /// Search this group and all subgroups for entries. /// - /// Specifies the search method. - /// Entry list in which the search results will - /// be stored. + /// Specifies the search parameters. + /// Entry list in which the search results + /// will be stored. /// Optional status reporting object. - public void SearchEntries(SearchParameters sp, PwObjectList listStorage, + public void SearchEntries(SearchParameters sp, PwObjectList lResults, IStatusLogger slStatus) { if(sp == null) { Debug.Assert(false); return; } - if(listStorage == null) { Debug.Assert(false); return; } + if(lResults == null) { Debug.Assert(false); return; } - ulong uCurEntries = 0, uTotalEntries = 0; + PwObjectList lCand = GetEntries(true); + DateTime dtNow = DateTime.UtcNow; - List lTerms = StrUtil.SplitSearchTerms(sp.SearchString); - if((lTerms.Count <= 1) || sp.RegularExpression) + PwObjectList l = new PwObjectList(); + foreach(PwEntry pe in lCand) { - if(slStatus != null) uTotalEntries = GetEntriesCount(true); - SearchEntriesSingle(sp, listStorage, slStatus, ref uCurEntries, - uTotalEntries); - return; + if(sp.RespectEntrySearchingDisabled && !pe.GetSearchingEnabled()) + continue; + if(sp.ExcludeExpired && pe.Expires && (pe.ExpiryTime <= dtNow)) + continue; + + l.Add(pe); } + lCand = l; + + List lTerms; + if(sp.RegularExpression) + { + lTerms = new List(); + lTerms.Add((sp.SearchString ?? string.Empty).Trim()); + } + else lTerms = StrUtil.SplitSearchTerms(sp.SearchString); // Search longer strings first (for improved performance) lTerms.Sort(StrUtil.CompareLengthGt); - string strFullSearch = sp.SearchString; // Backup + ulong uPrcEntries = 0, uTotalEntries = lCand.UCount; + SearchParameters spSub = sp.Clone(); - PwGroup pg = this; for(int iTerm = 0; iTerm < lTerms.Count; ++iTerm) { // Update counters for a better state guess if(slStatus != null) { ulong uRemRounds = (ulong)(lTerms.Count - iTerm); - uTotalEntries = uCurEntries + (uRemRounds * - pg.GetEntriesCount(true)); + uTotalEntries = uPrcEntries + (uRemRounds * + lCand.UCount); } - PwGroup pgNew = new PwGroup(); - - sp.SearchString = lTerms[iTerm]; + spSub.SearchString = lTerms[iTerm]; // No trim + // spSub.RespectEntrySearchingDisabled = false; // Ignored by sub + // spSub.ExcludeExpired = false; // Ignored by sub bool bNegate = false; - if(sp.SearchString.StartsWith("-")) + if(spSub.SearchString.StartsWith(@"-") && + (spSub.SearchString.Length >= 2)) { - sp.SearchString = sp.SearchString.Substring(1); - bNegate = (sp.SearchString.Length > 0); + spSub.SearchString = spSub.SearchString.Substring(1); + bNegate = true; } - if(!pg.SearchEntriesSingle(sp, pgNew.Entries, slStatus, - ref uCurEntries, uTotalEntries)) + l = new PwObjectList(); + if(!SearchEntriesSingle(lCand, spSub, l, slStatus, + ref uPrcEntries, uTotalEntries)) { - pg = null; + lCand.Clear(); break; } if(bNegate) { - PwObjectList lCand = pg.GetEntries(true); - - pg = new PwGroup(); - foreach(PwEntry peCand in lCand) + PwObjectList lRem = new PwObjectList(); + foreach(PwEntry pe in lCand) { - if(pgNew.Entries.IndexOf(peCand) < 0) pg.Entries.Add(peCand); + if(l.IndexOf(pe) < 0) lRem.Add(pe); } + + lCand = lRem; } - else pg = pgNew; + else lCand = l; } - if(pg != null) listStorage.Add(pg.Entries); - sp.SearchString = strFullSearch; // Restore + Debug.Assert(lResults.UCount == 0); + lResults.Clear(); + lResults.Add(lCand); } - private bool SearchEntriesSingle(SearchParameters spIn, - PwObjectList listStorage, IStatusLogger slStatus, - ref ulong uCurEntries, ulong uTotalEntries) + private static bool SearchEntriesSingle(PwObjectList lSource, + SearchParameters sp, PwObjectList lResults, + IStatusLogger slStatus, ref ulong uPrcEntries, ulong uTotalEntries) { - SearchParameters sp = spIn.Clone(); - if(sp.SearchString == null) { Debug.Assert(false); return true; } - sp.SearchString = sp.SearchString.Trim(); + if(lSource == null) { Debug.Assert(false); return true; } + if(sp == null) { Debug.Assert(false); return true; } + if(lResults == null) { Debug.Assert(false); return true; } + Debug.Assert(lResults.UCount == 0); bool bTitle = sp.SearchInTitles; bool bUserName = sp.SearchInUserNames; @@ -830,22 +879,17 @@ namespace ModernKeePassLib bool bUrl = sp.SearchInUrls; bool bNotes = sp.SearchInNotes; bool bOther = sp.SearchInOther; + bool bStringName = sp.SearchInStringNames; + bool bTags = sp.SearchInTags; bool bUuids = sp.SearchInUuids; bool bGroupName = sp.SearchInGroupNames; - bool bTags = sp.SearchInTags; - bool bExcludeExpired = sp.ExcludeExpired; - bool bRespectEntrySearchingDisabled = sp.RespectEntrySearchingDisabled; - - DateTime dtNow = DateTime.Now; + // bool bExcludeExpired = sp.ExcludeExpired; + // bool bRespectEntrySearchingDisabled = sp.RespectEntrySearchingDisabled; Regex rx = null; if(sp.RegularExpression) { -#if ModernKeePassLib || KeePassRT - RegexOptions ro = RegexOptions.None; -#else - RegexOptions ro = RegexOptions.Compiled; -#endif + RegexOptions ro = RegexOptions.None; // RegexOptions.Compiled if((sp.ComparisonMode == StringComparison.CurrentCultureIgnoreCase) || #if !ModernKeePassLib && !KeePassRT (sp.ComparisonMode == StringComparison.InvariantCultureIgnoreCase) || @@ -858,46 +902,26 @@ namespace ModernKeePassLib rx = new Regex(sp.SearchString, ro); } - ulong uLocalCurEntries = uCurEntries; + ulong uLocalPrcEntries = uPrcEntries; - EntryHandler eh = null; - if(sp.SearchString.Length <= 0) // Report all - { - eh = delegate(PwEntry pe) - { - if(slStatus != null) - { - if(!slStatus.SetProgress((uint)((uLocalCurEntries * - 100UL) / uTotalEntries))) return false; - ++uLocalCurEntries; - } - - if(bRespectEntrySearchingDisabled && !pe.GetSearchingEnabled()) - return true; // Skip - if(bExcludeExpired && pe.Expires && (dtNow > pe.ExpiryTime)) - return true; // Skip - - listStorage.Add(pe); - return true; - }; - } + if(sp.SearchString.Length == 0) lResults.Add(lSource); else { - eh = delegate(PwEntry pe) + foreach(PwEntry pe in lSource) { if(slStatus != null) { - if(!slStatus.SetProgress((uint)((uLocalCurEntries * + if(!slStatus.SetProgress((uint)((uLocalPrcEntries * 100UL) / uTotalEntries))) return false; - ++uLocalCurEntries; + ++uLocalPrcEntries; } - if(bRespectEntrySearchingDisabled && !pe.GetSearchingEnabled()) - return true; // Skip - if(bExcludeExpired && pe.Expires && (dtNow > pe.ExpiryTime)) - return true; // Skip + // if(bRespectEntrySearchingDisabled && !pe.GetSearchingEnabled()) + // continue; + // if(bExcludeExpired && pe.Expires && (pe.ExpiryTime <= dtNow)) + // continue; - uint uInitialResults = listStorage.UCount; + uint uInitialResults = lResults.UCount; foreach(KeyValuePair kvp in pe.Strings) { @@ -906,76 +930,87 @@ namespace ModernKeePassLib if(strKey == PwDefs.TitleField) { if(bTitle) SearchEvalAdd(sp, kvp.Value.ReadString(), - rx, pe, listStorage); + rx, pe, lResults); } else if(strKey == PwDefs.UserNameField) { if(bUserName) SearchEvalAdd(sp, kvp.Value.ReadString(), - rx, pe, listStorage); + rx, pe, lResults); } else if(strKey == PwDefs.PasswordField) { if(bPassword) SearchEvalAdd(sp, kvp.Value.ReadString(), - rx, pe, listStorage); + rx, pe, lResults); } else if(strKey == PwDefs.UrlField) { if(bUrl) SearchEvalAdd(sp, kvp.Value.ReadString(), - rx, pe, listStorage); + rx, pe, lResults); } else if(strKey == PwDefs.NotesField) { if(bNotes) SearchEvalAdd(sp, kvp.Value.ReadString(), - rx, pe, listStorage); + rx, pe, lResults); } else if(bOther) SearchEvalAdd(sp, kvp.Value.ReadString(), - rx, pe, listStorage); + rx, pe, lResults); // An entry can match only once => break if we have added it - if(listStorage.UCount > uInitialResults) break; + if(lResults.UCount != uInitialResults) break; } - if(bUuids && (listStorage.UCount == uInitialResults)) - SearchEvalAdd(sp, pe.Uuid.ToHexString(), rx, pe, listStorage); + if(bStringName) + { + foreach(KeyValuePair kvp in pe.Strings) + { + if(lResults.UCount != uInitialResults) break; - if(bGroupName && (listStorage.UCount == uInitialResults) && - (pe.ParentGroup != null)) - SearchEvalAdd(sp, pe.ParentGroup.Name, rx, pe, listStorage); + SearchEvalAdd(sp, kvp.Key, rx, pe, lResults); + } + } if(bTags) { foreach(string strTag in pe.Tags) { - if(listStorage.UCount != uInitialResults) break; // Match + if(lResults.UCount != uInitialResults) break; - SearchEvalAdd(sp, strTag, rx, pe, listStorage); + SearchEvalAdd(sp, strTag, rx, pe, lResults); } } - return true; - }; + if(bUuids && (lResults.UCount == uInitialResults)) + SearchEvalAdd(sp, pe.Uuid.ToHexString(), rx, pe, lResults); + + if(bGroupName && (lResults.UCount == uInitialResults) && + (pe.ParentGroup != null)) + SearchEvalAdd(sp, pe.ParentGroup.Name, rx, pe, lResults); + } } - if(!PreOrderTraverseTree(null, eh)) return false; - uCurEntries = uLocalCurEntries; + uPrcEntries = uLocalPrcEntries; return true; } - private static void SearchEvalAdd(SearchParameters sp, string strDataField, + private static void SearchEvalAdd(SearchParameters sp, string strData, Regex rx, PwEntry pe, PwObjectList lResults) { - bool bMatch = false; + if(sp == null) { Debug.Assert(false); return; } + if(strData == null) { Debug.Assert(false); return; } + if(pe == null) { Debug.Assert(false); return; } + if(lResults == null) { Debug.Assert(false); return; } + bool bMatch; if(rx == null) - bMatch = (strDataField.IndexOf(sp.SearchString, + bMatch = (strData.IndexOf(sp.SearchString, sp.ComparisonMode) >= 0); - else bMatch = rx.IsMatch(strDataField); + else bMatch = rx.IsMatch(strData); if(!bMatch && (sp.DataTransformationFn != null)) { - string strCmp = sp.DataTransformationFn(strDataField, pe); - if(!object.ReferenceEquals(strCmp, strDataField)) + string strCmp = sp.DataTransformationFn(strData, pe); + if(!object.ReferenceEquals(strCmp, strData)) { if(rx == null) bMatch = (strCmp.IndexOf(sp.SearchString, @@ -1395,15 +1430,20 @@ namespace ModernKeePassLib public PwObjectList GetEntries(bool bIncludeSubGroupEntries) { - if(bIncludeSubGroupEntries == false) return m_listEntries; + PwObjectList l = new PwObjectList(); - PwObjectList list = m_listEntries.CloneShallow(); - foreach(PwGroup pgSub in m_listGroups) + GroupHandler gh = delegate(PwGroup pg) { - list.Add(pgSub.GetEntries(true)); - } + l.Add(pg.Entries); + return true; + }; - return list; + gh(this); + if(bIncludeSubGroupEntries) + PreOrderTraverseTree(gh, null); + + Debug.Assert(l.UCount == GetEntriesCount(bIncludeSubGroupEntries)); + return l; } /// @@ -1476,7 +1516,7 @@ namespace ModernKeePassLib if(bTakeOwnership) subGroup.m_pParentGroup = this; - if(bUpdateLocationChangedOfSub) subGroup.LocationChanged = DateTime.Now; + if(bUpdateLocationChangedOfSub) subGroup.LocationChanged = DateTime.UtcNow; } /// @@ -1511,7 +1551,7 @@ namespace ModernKeePassLib // only assign it to the new one if(bTakeOwnership) pe.ParentGroup = this; - if(bUpdateLocationChangedOfEntry) pe.LocationChanged = DateTime.Now; + if(bUpdateLocationChangedOfEntry) pe.LocationChanged = DateTime.UtcNow; } public void SortSubGroups(bool bRecursive) @@ -1527,7 +1567,7 @@ namespace ModernKeePassLib public void DeleteAllObjects(PwDatabase pdContext) { - DateTime dtNow = DateTime.Now; + DateTime dtNow = DateTime.UtcNow; foreach(PwEntry pe in m_listEntries) { @@ -1571,7 +1611,7 @@ namespace ModernKeePassLib public void SetCreatedNow(bool bRecursive) { - DateTime dt = DateTime.Now; + DateTime dt = DateTime.UtcNow; m_tCreation = dt; m_tLastAccess = dt; diff --git a/ModernKeePassLib/PwUuid.cs b/ModernKeePassLib/PwUuid.cs index 8884444..aa563fd 100644 --- a/ModernKeePassLib/PwUuid.cs +++ b/ModernKeePassLib/PwUuid.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/Resources/KLRes.Generated.cs b/ModernKeePassLib/Resources/KLRes.Generated.cs index 74f4274..007d875 100644 --- a/ModernKeePassLib/Resources/KLRes.Generated.cs +++ b/ModernKeePassLib/Resources/KLRes.Generated.cs @@ -27,14 +27,18 @@ namespace ModernKeePassLib.Resources if(dictNew == null) throw new ArgumentNullException("dictNew"); m_strCryptoStreamFailed = TryGetEx(dictNew, "CryptoStreamFailed", m_strCryptoStreamFailed); - m_strEncAlgorithmAes = TryGetEx(dictNew, "EncAlgorithmAes", m_strEncAlgorithmAes); + m_strEncDataTooLarge = TryGetEx(dictNew, "EncDataTooLarge", m_strEncDataTooLarge); m_strErrorInClipboard = TryGetEx(dictNew, "ErrorInClipboard", m_strErrorInClipboard); + m_strExpect100Continue = TryGetEx(dictNew, "Expect100Continue", m_strExpect100Continue); m_strFatalError = TryGetEx(dictNew, "FatalError", m_strFatalError); m_strFatalErrorText = TryGetEx(dictNew, "FatalErrorText", m_strFatalErrorText); m_strFileCorrupted = TryGetEx(dictNew, "FileCorrupted", m_strFileCorrupted); - m_strFileHeaderEndEarly = TryGetEx(dictNew, "FileHeaderEndEarly", m_strFileHeaderEndEarly); + m_strFileHeaderCorrupted = TryGetEx(dictNew, "FileHeaderCorrupted", m_strFileHeaderCorrupted); + m_strFileIncomplete = TryGetEx(dictNew, "FileIncomplete", m_strFileIncomplete); + m_strFileIncompleteExpc = TryGetEx(dictNew, "FileIncompleteExpc", m_strFileIncompleteExpc); m_strFileLoadFailed = TryGetEx(dictNew, "FileLoadFailed", m_strFileLoadFailed); m_strFileLockedWrite = TryGetEx(dictNew, "FileLockedWrite", m_strFileLockedWrite); + m_strFileNewVerOrPlgReq = TryGetEx(dictNew, "FileNewVerOrPlgReq", m_strFileNewVerOrPlgReq); m_strFileNewVerReq = TryGetEx(dictNew, "FileNewVerReq", m_strFileNewVerReq); m_strFileSaveCorruptionWarning = TryGetEx(dictNew, "FileSaveCorruptionWarning", m_strFileSaveCorruptionWarning); m_strFileSaveFailed = TryGetEx(dictNew, "FileSaveFailed", m_strFileSaveFailed); @@ -44,28 +48,39 @@ namespace ModernKeePassLib.Resources m_strFileVersionUnsupported = TryGetEx(dictNew, "FileVersionUnsupported", m_strFileVersionUnsupported); m_strFinalKeyCreationFailed = TryGetEx(dictNew, "FinalKeyCreationFailed", m_strFinalKeyCreationFailed); m_strFrameworkNotImplExcp = TryGetEx(dictNew, "FrameworkNotImplExcp", m_strFrameworkNotImplExcp); + m_strGeneral = TryGetEx(dictNew, "General", m_strGeneral); m_strInvalidCompositeKey = TryGetEx(dictNew, "InvalidCompositeKey", m_strInvalidCompositeKey); m_strInvalidCompositeKeyHint = TryGetEx(dictNew, "InvalidCompositeKeyHint", m_strInvalidCompositeKeyHint); m_strInvalidDataWhileDecoding = TryGetEx(dictNew, "InvalidDataWhileDecoding", m_strInvalidDataWhileDecoding); m_strKeePass1xHint = TryGetEx(dictNew, "KeePass1xHint", m_strKeePass1xHint); + m_strKeyBits = TryGetEx(dictNew, "KeyBits", m_strKeyBits); m_strKeyFileDbSel = TryGetEx(dictNew, "KeyFileDbSel", m_strKeyFileDbSel); m_strMasterSeedLengthInvalid = TryGetEx(dictNew, "MasterSeedLengthInvalid", m_strMasterSeedLengthInvalid); m_strOldFormat = TryGetEx(dictNew, "OldFormat", m_strOldFormat); + m_strPassive = TryGetEx(dictNew, "Passive", m_strPassive); + m_strPreAuth = TryGetEx(dictNew, "PreAuth", m_strPreAuth); + m_strTimeout = TryGetEx(dictNew, "Timeout", m_strTimeout); m_strTryAgainSecs = TryGetEx(dictNew, "TryAgainSecs", m_strTryAgainSecs); m_strUnknownHeaderId = TryGetEx(dictNew, "UnknownHeaderId", m_strUnknownHeaderId); + m_strUnknownKdf = TryGetEx(dictNew, "UnknownKdf", m_strUnknownKdf); m_strUserAccountKeyError = TryGetEx(dictNew, "UserAccountKeyError", m_strUserAccountKeyError); + m_strUserAgent = TryGetEx(dictNew, "UserAgent", m_strUserAgent); } private static readonly string[] m_vKeyNames = { "CryptoStreamFailed", - "EncAlgorithmAes", + "EncDataTooLarge", "ErrorInClipboard", + "Expect100Continue", "FatalError", "FatalErrorText", "FileCorrupted", - "FileHeaderEndEarly", + "FileHeaderCorrupted", + "FileIncomplete", + "FileIncompleteExpc", "FileLoadFailed", "FileLockedWrite", + "FileNewVerOrPlgReq", "FileNewVerReq", "FileSaveCorruptionWarning", "FileSaveFailed", @@ -75,16 +90,23 @@ namespace ModernKeePassLib.Resources "FileVersionUnsupported", "FinalKeyCreationFailed", "FrameworkNotImplExcp", + "General", "InvalidCompositeKey", "InvalidCompositeKeyHint", "InvalidDataWhileDecoding", "KeePass1xHint", + "KeyBits", "KeyFileDbSel", "MasterSeedLengthInvalid", "OldFormat", + "Passive", + "PreAuth", + "Timeout", "TryAgainSecs", "UnknownHeaderId", - "UserAccountKeyError" + "UnknownKdf", + "UserAccountKeyError", + "UserAgent" }; public static string[] GetKeyNames() @@ -103,15 +125,15 @@ namespace ModernKeePassLib.Resources get { return m_strCryptoStreamFailed; } } - private static string m_strEncAlgorithmAes = - @"AES/Rijndael (256-Bit Key)"; + private static string m_strEncDataTooLarge = + @"The data is too large to be encrypted/decrypted securely using {PARAM}."; /// /// Look up a localized string similar to - /// 'AES/Rijndael (256-Bit Key)'. + /// 'The data is too large to be encrypted/decrypted securely using {PARAM}.'. /// - public static string EncAlgorithmAes + public static string EncDataTooLarge { - get { return m_strEncAlgorithmAes; } + get { return m_strEncDataTooLarge; } } private static string m_strErrorInClipboard = @@ -125,6 +147,17 @@ namespace ModernKeePassLib.Resources get { return m_strErrorInClipboard; } } + private static string m_strExpect100Continue = + @"Expect 100-Continue responses"; + /// + /// Look up a localized string similar to + /// 'Expect 100-Continue responses'. + /// + public static string Expect100Continue + { + get { return m_strExpect100Continue; } + } + private static string m_strFatalError = @"Fatal Error"; /// @@ -158,15 +191,37 @@ namespace ModernKeePassLib.Resources get { return m_strFileCorrupted; } } - private static string m_strFileHeaderEndEarly = - @"The file header is corrupted! Some header data was declared but is not present."; + private static string m_strFileHeaderCorrupted = + @"The file header is corrupted."; /// /// Look up a localized string similar to - /// 'The file header is corrupted! Some header data was declared but is not present.'. + /// 'The file header is corrupted.'. /// - public static string FileHeaderEndEarly + public static string FileHeaderCorrupted { - get { return m_strFileHeaderEndEarly; } + get { return m_strFileHeaderCorrupted; } + } + + private static string m_strFileIncomplete = + @"Data is missing at the end of the file, i.e. the file is incomplete."; + /// + /// Look up a localized string similar to + /// 'Data is missing at the end of the file, i.e. the file is incomplete.'. + /// + public static string FileIncomplete + { + get { return m_strFileIncomplete; } + } + + private static string m_strFileIncompleteExpc = + @"Less data than expected could be read from the file."; + /// + /// Look up a localized string similar to + /// 'Less data than expected could be read from the file.'. + /// + public static string FileIncompleteExpc + { + get { return m_strFileIncompleteExpc; } } private static string m_strFileLoadFailed = @@ -191,6 +246,17 @@ namespace ModernKeePassLib.Resources get { return m_strFileLockedWrite; } } + private static string m_strFileNewVerOrPlgReq = + @"A newer KeePass version or a plugin is required to open this file."; + /// + /// Look up a localized string similar to + /// 'A newer KeePass version or a plugin is required to open this file.'. + /// + public static string FileNewVerOrPlgReq + { + get { return m_strFileNewVerOrPlgReq; } + } + private static string m_strFileNewVerReq = @"A newer KeePass version is required to open this file."; /// @@ -290,6 +356,17 @@ namespace ModernKeePassLib.Resources get { return m_strFrameworkNotImplExcp; } } + private static string m_strGeneral = + @"General"; + /// + /// Look up a localized string similar to + /// 'General'. + /// + public static string General + { + get { return m_strGeneral; } + } + private static string m_strInvalidCompositeKey = @"The composite key is invalid!"; /// @@ -334,6 +411,17 @@ namespace ModernKeePassLib.Resources get { return m_strKeePass1xHint; } } + private static string m_strKeyBits = + @"{PARAM}-bit key"; + /// + /// Look up a localized string similar to + /// '{PARAM}-bit key'. + /// + public static string KeyBits + { + get { return m_strKeyBits; } + } + private static string m_strKeyFileDbSel = @"Database files cannot be used as key files."; /// @@ -367,6 +455,39 @@ namespace ModernKeePassLib.Resources get { return m_strOldFormat; } } + private static string m_strPassive = + @"Passive"; + /// + /// Look up a localized string similar to + /// 'Passive'. + /// + public static string Passive + { + get { return m_strPassive; } + } + + private static string m_strPreAuth = + @"Pre-authenticate"; + /// + /// Look up a localized string similar to + /// 'Pre-authenticate'. + /// + public static string PreAuth + { + get { return m_strPreAuth; } + } + + private static string m_strTimeout = + @"Timeout"; + /// + /// Look up a localized string similar to + /// 'Timeout'. + /// + public static string Timeout + { + get { return m_strTimeout; } + } + private static string m_strTryAgainSecs = @"Please try it again in a few seconds."; /// @@ -389,6 +510,17 @@ namespace ModernKeePassLib.Resources get { return m_strUnknownHeaderId; } } + private static string m_strUnknownKdf = + @"Unknown key derivation function!"; + /// + /// Look up a localized string similar to + /// 'Unknown key derivation function!'. + /// + public static string UnknownKdf + { + get { return m_strUnknownKdf; } + } + private static string m_strUserAccountKeyError = @"The operating system did not grant KeePass read/write access to the user profile folder, where the protected user key is stored."; /// @@ -399,5 +531,16 @@ namespace ModernKeePassLib.Resources { get { return m_strUserAccountKeyError; } } + + private static string m_strUserAgent = + @"User agent"; + /// + /// Look up a localized string similar to + /// 'User agent'. + /// + public static string UserAgent + { + get { return m_strUserAgent; } + } } } diff --git a/ModernKeePassLib/Security/ProtectedBinary.cs b/ModernKeePassLib/Security/ProtectedBinary.cs index e514e47..835bc4d 100644 --- a/ModernKeePassLib/Security/ProtectedBinary.cs +++ b/ModernKeePassLib/Security/ProtectedBinary.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,8 +18,8 @@ */ using System; -using System.Threading; using System.Diagnostics; +using System.Threading; using ModernKeePassLib.Cryptography; using ModernKeePassLib.Cryptography.Cipher; @@ -72,13 +72,13 @@ namespace ModernKeePassLib.Security { None = 0, ProtectedMemory, - Salsa20, + ChaCha20, ExtCrypt } // ProtectedMemory is supported only on Windows 2000 SP3 and higher #if !KeePassLibSD - private static bool? g_bProtectedMemorySupported = null; + private static bool? g_obProtectedMemorySupported = null; #endif private static bool ProtectedMemorySupported { @@ -87,14 +87,14 @@ namespace ModernKeePassLib.Security #if KeePassLibSD return false; #else - bool? ob = g_bProtectedMemorySupported; + bool? ob = g_obProtectedMemorySupported; if(ob.HasValue) return ob.Value; // Mono does not implement any encryption for ProtectedMemory; // https://sourceforge.net/p/keepass/feature-requests/1907/ if(NativeLib.IsUnix()) { - g_bProtectedMemorySupported = false; + g_obProtectedMemorySupported = false; return false; } @@ -115,7 +115,7 @@ namespace ModernKeePassLib.Security } catch(Exception) { } // Windows 98 / ME - g_bProtectedMemorySupported = ob; + g_obProtectedMemorySupported = ob; return ob.Value; #endif } @@ -135,7 +135,7 @@ namespace ModernKeePassLib.Security private PbMemProt m_mp = PbMemProt.None; // Actual protection - private object m_objSync = new object(); + private readonly object m_objSync = new object(); private static byte[] g_pbKey32 = null; @@ -162,7 +162,7 @@ namespace ModernKeePassLib.Security /// public ProtectedBinary() { - Init(false, new byte[0]); + Init(false, MemUtil.EmptyByteArray, 0, 0); } /// @@ -177,7 +177,27 @@ namespace ModernKeePassLib.Security /// i.e. the caller is responsible for clearing it. public ProtectedBinary(bool bEnableProtection, byte[] pbData) { - Init(bEnableProtection, pbData); + if(pbData == null) throw new ArgumentNullException("pbData"); + + Init(bEnableProtection, pbData, 0, pbData.Length); + } + + /// + /// Construct a new protected binary data object. + /// + /// If this paremeter is true, + /// the data will be encrypted in memory. If it is false, the + /// data is stored in plain-text in the process memory. + /// Value of the protected object. + /// The input parameter is not modified and + /// ProtectedBinary doesn't take ownership of the data, + /// i.e. the caller is responsible for clearing it. + /// Offset for . + /// Size for . + public ProtectedBinary(bool bEnableProtection, byte[] pbData, + int iOffset, int cbSize) + { + Init(bEnableProtection, pbData, iOffset, cbSize); } /// @@ -193,13 +213,19 @@ namespace ModernKeePassLib.Security if(xbProtected == null) throw new ArgumentNullException("xbProtected"); byte[] pb = xbProtected.ReadPlainText(); - Init(bEnableProtection, pb); - MemUtil.ZeroByteArray(pb); + Init(bEnableProtection, pb, 0, pb.Length); + + if(bEnableProtection) MemUtil.ZeroByteArray(pb); } - private void Init(bool bEnableProtection, byte[] pbData) + private void Init(bool bEnableProtection, byte[] pbData, int iOffset, + int cbSize) { if(pbData == null) throw new ArgumentNullException("pbData"); + if(iOffset < 0) throw new ArgumentOutOfRangeException("iOffset"); + if(cbSize < 0) throw new ArgumentOutOfRangeException("cbSize"); + if(iOffset > (pbData.Length - cbSize)) + throw new ArgumentOutOfRangeException("cbSize"); #if KeePassLibSD m_lID = ++g_lCurID; @@ -208,15 +234,15 @@ namespace ModernKeePassLib.Security #endif m_bProtected = bEnableProtection; - m_uDataLen = (uint)pbData.Length; + m_uDataLen = (uint)cbSize; const int bs = ProtectedBinary.BlockSize; - int nBlocks = (int)m_uDataLen / bs; - if((nBlocks * bs) < (int)m_uDataLen) ++nBlocks; - Debug.Assert((nBlocks * bs) >= (int)m_uDataLen); + int nBlocks = cbSize / bs; + if((nBlocks * bs) < cbSize) ++nBlocks; + Debug.Assert((nBlocks * bs) >= cbSize); m_pbData = new byte[nBlocks * bs]; - Array.Copy(pbData, m_pbData, (int)m_uDataLen); + Array.Copy(pbData, iOffset, m_pbData, 0, cbSize); Encrypt(); } @@ -258,11 +284,13 @@ namespace ModernKeePassLib.Security if(pbUpd != null) pbKey32 = pbUpd; } - Salsa20Cipher s = new Salsa20Cipher(pbKey32, - BitConverter.GetBytes(m_lID)); - s.Encrypt(m_pbData, m_pbData.Length, true); - s.Dispose(); - m_mp = PbMemProt.Salsa20; + byte[] pbIV = new byte[12]; + MemUtil.UInt64ToBytesEx((ulong)m_lID, pbIV, 4); + using(ChaCha20Cipher c = new ChaCha20Cipher(pbKey32, pbIV, true)) + { + c.Encrypt(m_pbData, 0, m_pbData.Length); + } + m_mp = PbMemProt.ChaCha20; } private void Decrypt() @@ -271,12 +299,14 @@ namespace ModernKeePassLib.Security if(m_mp == PbMemProt.ProtectedMemory) ProtectedMemory.Unprotect(m_pbData, MemoryProtectionScope.SameProcess); - else if(m_mp == PbMemProt.Salsa20) + else if(m_mp == PbMemProt.ChaCha20) { - Salsa20Cipher s = new Salsa20Cipher(g_pbKey32, - BitConverter.GetBytes(m_lID)); - s.Encrypt(m_pbData, m_pbData.Length, true); - s.Dispose(); + byte[] pbIV = new byte[12]; + MemUtil.UInt64ToBytesEx((ulong)m_lID, pbIV, 4); + using(ChaCha20Cipher c = new ChaCha20Cipher(g_pbKey32, pbIV, true)) + { + c.Decrypt(m_pbData, 0, m_pbData.Length); + } } else if(m_mp == PbMemProt.ExtCrypt) m_fExtCrypt(m_pbData, PbCryptFlags.Decrypt, m_lID); @@ -295,7 +325,7 @@ namespace ModernKeePassLib.Security /// protected data and can therefore be cleared safely. public byte[] ReadData() { - if(m_uDataLen == 0) return new byte[0]; + if(m_uDataLen == 0) return MemUtil.EmptyByteArray; byte[] pbReturn = new byte[m_uDataLen]; diff --git a/ModernKeePassLib/Security/ProtectedString.cs b/ModernKeePassLib/Security/ProtectedString.cs index 9b8f715..a1c6f28 100644 --- a/ModernKeePassLib/Security/ProtectedString.cs +++ b/ModernKeePassLib/Security/ProtectedString.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,8 +18,8 @@ */ using System; -using System.Text; using System.Diagnostics; +using System.Text; using ModernKeePassLib.Cryptography; using ModernKeePassLib.Utility; @@ -150,7 +150,8 @@ namespace ModernKeePassLib.Security byte[] pb = xbProtected.ReadPlainText(); Init(bEnableProtection, pb); - MemUtil.ZeroByteArray(pb); + + if(bEnableProtection) MemUtil.ZeroByteArray(pb); } private void Init(bool bEnableProtection, string str) @@ -242,7 +243,8 @@ namespace ModernKeePassLib.Security byte[] pb = ReadUtf8(); ProtectedString ps = new ProtectedString(bProtect, pb); - MemUtil.ZeroByteArray(pb); + + if(bProtect) MemUtil.ZeroByteArray(pb); return ps; } @@ -280,7 +282,7 @@ namespace ModernKeePassLib.Security } finally { - Array.Clear(v, 0, v.Length); + MemUtil.ZeroArray(v); MemUtil.ZeroByteArray(pb); } @@ -290,7 +292,7 @@ namespace ModernKeePassLib.Security Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) == ReadString().Insert(iStart, strInsert)); - Array.Clear(vNew, 0, vNew.Length); + MemUtil.ZeroArray(vNew); MemUtil.ZeroByteArray(pbNew); return ps; } @@ -326,7 +328,7 @@ namespace ModernKeePassLib.Security } finally { - Array.Clear(v, 0, v.Length); + MemUtil.ZeroArray(v); MemUtil.ZeroByteArray(pb); } @@ -336,7 +338,7 @@ namespace ModernKeePassLib.Security Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) == ReadString().Remove(iStart, nCount)); - Array.Clear(vNew, 0, vNew.Length); + MemUtil.ZeroArray(vNew); MemUtil.ZeroByteArray(pbNew); return ps; } diff --git a/ModernKeePassLib/Security/XorredBuffer.cs b/ModernKeePassLib/Security/XorredBuffer.cs index 625e83c..ea66908 100644 --- a/ModernKeePassLib/Security/XorredBuffer.cs +++ b/ModernKeePassLib/Security/XorredBuffer.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/Serialization/BinaryReaderEx.cs b/ModernKeePassLib/Serialization/BinaryReaderEx.cs index 1891861..deef447 100644 --- a/ModernKeePassLib/Serialization/BinaryReaderEx.cs +++ b/ModernKeePassLib/Serialization/BinaryReaderEx.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,8 +19,8 @@ using System; using System.Collections.Generic; -using System.Text; using System.IO; +using System.Text; using ModernKeePassLib.Utility; @@ -31,7 +31,7 @@ namespace ModernKeePassLib.Serialization private Stream m_s; // private Encoding m_enc; // See constructor - private string m_strReadExcp; + private string m_strReadExcp; // May be null public string ReadExceptionText { get { return m_strReadExcp; } @@ -53,8 +53,7 @@ namespace ModernKeePassLib.Serialization public BinaryReaderEx(Stream input, Encoding encoding, string strReadExceptionText) { - if(input == null) - throw new ArgumentNullException("input"); + if(input == null) throw new ArgumentNullException("input"); m_s = input; // m_enc = encoding; // Not used yet @@ -68,20 +67,18 @@ namespace ModernKeePassLib.Serialization byte[] pb = MemUtil.Read(m_s, nCount); if((pb == null) || (pb.Length != nCount)) { - if(m_strReadExcp != null) - throw new IOException(m_strReadExcp); - else - throw new EndOfStreamException(); + if(!string.IsNullOrEmpty(m_strReadExcp)) + throw new EndOfStreamException(m_strReadExcp); + else throw new EndOfStreamException(); } - if(m_sCopyTo != null) - m_sCopyTo.Write(pb, 0, pb.Length); + if(m_sCopyTo != null) m_sCopyTo.Write(pb, 0, pb.Length); return pb; } catch(Exception) { - if(m_strReadExcp != null) - throw new IOException(m_strReadExcp); + if(!string.IsNullOrEmpty(m_strReadExcp)) + throw new IOException(m_strReadExcp); else throw; } } diff --git a/ModernKeePassLib/Serialization/FileLock.cs b/ModernKeePassLib/Serialization/FileLock.cs index d8b6885..224609e 100644 --- a/ModernKeePassLib/Serialization/FileLock.cs +++ b/ModernKeePassLib/Serialization/FileLock.cs @@ -125,34 +125,26 @@ namespace ModernKeePassLib.Serialization public static LockFileInfo Load(IOConnectionInfo iocLockFile) { - using (var s = IOConnection.OpenRead(iocLockFile)) + Stream s = null; try { + s = IOConnection.OpenRead(iocLockFile); if(s == null) return null; - using (var sr = new StreamReader(s, StrUtil.Utf8)) - { - string str = sr.ReadToEnd(); - if (str == null) - { - Debug.Assert(false); - } + StreamReader sr = new StreamReader(s, StrUtil.Utf8); + string str = sr.ReadToEnd(); + sr.Dispose(); + if(str == null) { Debug.Assert(false); return null; } - str = StrUtil.NormalizeNewLines(str, false); - string[] v = str.Split('\n'); - if ((v == null) || (v.Length < 6)) - { - Debug.Assert(false); - } + str = StrUtil.NormalizeNewLines(str, false); + string[] v = str.Split('\n'); + if((v == null) || (v.Length < 6)) { Debug.Assert(false); return null; } - if (!v[0].StartsWith(LockFileHeader)) - { - Debug.Assert(false); - } - return new LockFileInfo(v[1], v[2], v[3], v[4], v[5]); - } + if(!v[0].StartsWith(LockFileHeader)) { Debug.Assert(false); return null; } + return new LockFileInfo(v[1], v[2], v[3], v[4], v[5]); } catch(FileNotFoundException) { } catch(Exception) { Debug.Assert(false); } + finally { if(s != null) s.Dispose(); } return null; } @@ -160,27 +152,48 @@ namespace ModernKeePassLib.Serialization // Throws on error public static LockFileInfo Create(IOConnectionInfo iocLockFile) { - byte[] pbID = CryptoRandom.Instance.GetRandomBytes(16); - string strTime = TimeUtil.SerializeUtc(DateTime.Now); - - var lfi = new LockFileInfo(Convert.ToBase64String(pbID), strTime, - string.Empty, string.Empty, string.Empty); - - StringBuilder sb = new StringBuilder(); - - sb.AppendLine(LockFileHeader); - sb.AppendLine(lfi.ID); - sb.AppendLine(strTime); - sb.AppendLine(lfi.UserName); - sb.AppendLine(lfi.Machine); - sb.AppendLine(lfi.Domain); - - using (var s = IOConnection.OpenWrite(iocLockFile)) + LockFileInfo lfi; + Stream s = null; + try { + byte[] pbID = CryptoRandom.Instance.GetRandomBytes(16); + string strTime = TimeUtil.SerializeUtc(DateTime.UtcNow); + + lfi = new LockFileInfo(Convert.ToBase64String(pbID), strTime, +#if KeePassUAP + EnvironmentExt.UserName, EnvironmentExt.MachineName, + EnvironmentExt.UserDomainName); +#elif ModernKeePassLib|| KeePassLibSD + string.Empty, string.Empty, string.Empty); +#else + Environment.UserName, Environment.MachineName, + Environment.UserDomainName); +#endif + + StringBuilder sb = new StringBuilder(); +#if !KeePassLibSD + sb.AppendLine(LockFileHeader); + sb.AppendLine(lfi.ID); + sb.AppendLine(strTime); + sb.AppendLine(lfi.UserName); + sb.AppendLine(lfi.Machine); + sb.AppendLine(lfi.Domain); +#else + sb.Append(LockFileHeader + Environment.NewLine); + sb.Append(lfi.ID + Environment.NewLine); + sb.Append(strTime + Environment.NewLine); + sb.Append(lfi.UserName + Environment.NewLine); + sb.Append(lfi.Machine + Environment.NewLine); + sb.Append(lfi.Domain + Environment.NewLine); +#endif + byte[] pbFile = StrUtil.Utf8.GetBytes(sb.ToString()); - if (s == null) throw new IOException(iocLockFile.GetDisplayName()); - s.WriteAsync(pbFile, 0, pbFile.Length).GetAwaiter().GetResult(); + + s = IOConnection.OpenWrite(iocLockFile); + if(s == null) throw new IOException(iocLockFile.GetDisplayName()); + s.Write(pbFile, 0, pbFile.Length); } + finally { if(s != null) s.Dispose(); } return lfi; } @@ -241,8 +254,8 @@ namespace ModernKeePassLib.Serialization #endif } - if(bDisposing && !bFileDeleted) - IOConnection.DeleteFile(m_iocLockFile); // Possibly with exception + // if(bDisposing && !bFileDeleted) + // IOConnection.DeleteFile(m_iocLockFile); // Possibly with exception m_iocLockFile = null; } diff --git a/ModernKeePassLib/Serialization/FileTransactionEx.cs b/ModernKeePassLib/Serialization/FileTransactionEx.cs index 59cb555..bdb1440 100644 --- a/ModernKeePassLib/Serialization/FileTransactionEx.cs +++ b/ModernKeePassLib/Serialization/FileTransactionEx.cs @@ -30,7 +30,9 @@ using System.Security.AccessControl; using ModernKeePassLib.Native; using ModernKeePassLib.Utility; using System.Threading.Tasks; +using Windows.Storage; using Windows.Storage.Streams; +using ModernKeePassLib.Resources; namespace ModernKeePassLib.Serialization { @@ -44,6 +46,16 @@ namespace ModernKeePassLib.Serialization private const string StrTempSuffix = ".tmp"; + private static Dictionary g_dEnabled = + new Dictionary(StrUtil.CaseIgnoreComparer); + + private static bool g_bExtraSafe = false; + internal static bool ExtraSafe + { + get { return g_bExtraSafe; } + set { g_bExtraSafe = value; } + } + public FileTransactionEx(IOConnectionInfo iocBaseFile) { Initialize(iocBaseFile, true); @@ -61,16 +73,47 @@ namespace ModernKeePassLib.Serialization m_bTransacted = bTransacted; m_iocBase = iocBaseFile.CloneDeep(); -// ModernKeePassLib is currently targeting .NET 4.5 + string strPath = m_iocBase.Path; + #if !ModernKeePassLib + if(m_iocBase.IsLocalFile()) + { + try + { + if(File.Exists(strPath)) + { + // Symbolic links are realized via reparse points; + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365503.aspx + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365680.aspx + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365006.aspx + // Performing a file transaction on a symbolic link + // would delete/replace the symbolic link instead of + // writing to its target + FileAttributes fa = File.GetAttributes(strPath); + if((long)(fa & FileAttributes.ReparsePoint) != 0) + m_bTransacted = false; + } + } + catch(Exception) { Debug.Assert(false); } + } + // Prevent transactions for FTP URLs under .NET 4.0 in order to // avoid/workaround .NET bug 621450: // https://connect.microsoft.com/VisualStudio/feedback/details/621450/problem-renaming-file-on-ftp-server-using-ftpwebrequest-in-net-framework-4-0-vs2010-only - if(m_iocBase.Path.StartsWith("ftp:", StrUtil.CaseIgnoreCmp) && + if(strPath.StartsWith("ftp:", StrUtil.CaseIgnoreCmp) && (Environment.Version.Major >= 4) && !NativeLib.IsUnix()) m_bTransacted = false; #endif + foreach(KeyValuePair kvp in g_dEnabled) + { + if(strPath.StartsWith(kvp.Key, StrUtil.CaseIgnoreCmp)) + { + m_bTransacted = kvp.Value; + break; + } + } + if(m_bTransacted) { m_iocTemp = m_iocBase.CloneDeep(); @@ -109,6 +152,13 @@ namespace ModernKeePassLib.Serialization bool bEfsEncrypted = false; #endif + if(g_bExtraSafe) + { + if(!IOConnection.FileExists(m_iocTemp)) + throw new FileNotFoundException(m_iocTemp.Path + + Environment.NewLine + KLRes.FileSaveFailed); + } + if(IOConnection.FileExists(m_iocBase)) { #if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT) @@ -119,10 +169,10 @@ namespace ModernKeePassLib.Serialization FileAttributes faBase = File.GetAttributes(m_iocBase.Path); bEfsEncrypted = ((long)(faBase & FileAttributes.Encrypted) != 0); - DateTime tCreation = File.GetCreationTime(m_iocBase.Path); + DateTime tCreation = File.GetCreationTimeUtc(m_iocBase.Path); bkSecurity = File.GetAccessControl(m_iocBase.Path); - File.SetCreationTime(m_iocTemp.Path, tCreation); + File.SetCreationTimeUtc(m_iocTemp.Path, tCreation); } catch(Exception) { Debug.Assert(false); } } @@ -153,5 +203,15 @@ namespace ModernKeePassLib.Serialization if(bMadeUnhidden) UrlUtil.HideFile(m_iocBase.Path, true); // Hide again } + + // For plugins + public static void Configure(string strPrefix, bool? obTransacted) + { + if(string.IsNullOrEmpty(strPrefix)) { Debug.Assert(false); return; } + + if(obTransacted.HasValue) + g_dEnabled[strPrefix] = obTransacted.Value; + else g_dEnabled.Remove(strPrefix); + } } } diff --git a/ModernKeePassLib/Serialization/HashedBlockStream.cs b/ModernKeePassLib/Serialization/HashedBlockStream.cs index 5749603..70e568a 100644 --- a/ModernKeePassLib/Serialization/HashedBlockStream.cs +++ b/ModernKeePassLib/Serialization/HashedBlockStream.cs @@ -31,6 +31,7 @@ using System.Text; using ModernKeePassLib.Native; using ModernKeePassLib.Utility; using Windows.Security.Cryptography.Core; +using ModernKeePassLib.Cryptography; #if KeePassLibSD using KeePassLibSD; @@ -40,7 +41,7 @@ namespace ModernKeePassLib.Serialization { public sealed class HashedBlockStream : Stream { - private const int m_nDefaultBufferSize = 1024 * 1024; // 1 MB + private const int NbDefaultBufferSize = 1024 * 1024; // 1 MB private Stream m_sBaseStream; private bool m_bWriting; @@ -53,7 +54,7 @@ namespace ModernKeePassLib.Serialization private byte[] m_pbBuffer; private int m_nBufferPos = 0; - private uint m_uBufferIndex = 0; + private uint m_uBlockIndex = 0; public override bool CanRead { @@ -72,13 +73,13 @@ namespace ModernKeePassLib.Serialization public override long Length { - get { throw new NotSupportedException(); } + get { Debug.Assert(false); throw new NotSupportedException(); } } public override long Position { - get { throw new NotSupportedException(); } - set { throw new NotSupportedException(); } + get { Debug.Assert(false); throw new NotSupportedException(); } + set { Debug.Assert(false); throw new NotSupportedException(); } } public HashedBlockStream(Stream sBaseStream, bool bWriting) @@ -100,29 +101,28 @@ namespace ModernKeePassLib.Serialization private void Initialize(Stream sBaseStream, bool bWriting, int nBufferSize, bool bVerify) { - if (sBaseStream != null) m_sBaseStream = sBaseStream; - else throw new ArgumentNullException(nameof(sBaseStream)); - if (nBufferSize < 0) - throw new ArgumentOutOfRangeException(nameof(nBufferSize)); + if(sBaseStream == null) throw new ArgumentNullException("sBaseStream"); + if(nBufferSize < 0) throw new ArgumentOutOfRangeException("nBufferSize"); - if(nBufferSize == 0) - nBufferSize = m_nDefaultBufferSize; + if(nBufferSize == 0) nBufferSize = NbDefaultBufferSize; + + m_sBaseStream = sBaseStream; m_bWriting = bWriting; m_bVerify = bVerify; UTF8Encoding utf8 = StrUtil.Utf8; - if(m_bWriting == false) // Reading mode + if(!m_bWriting) // Reading mode { - if(m_sBaseStream.CanRead == false) + if(!m_sBaseStream.CanRead) throw new InvalidOperationException(); m_brInput = new BinaryReader(sBaseStream, utf8); - m_pbBuffer = new byte[0]; + m_pbBuffer = MemUtil.EmptyByteArray; } else // Writing mode { - if(m_sBaseStream.CanWrite == false) + if(!m_sBaseStream.CanWrite) throw new InvalidOperationException(); m_bwOutput = new BinaryWriter(sBaseStream, utf8); @@ -131,25 +131,13 @@ namespace ModernKeePassLib.Serialization } } - public override void Flush() - { - if(m_bWriting) m_bwOutput.Flush(); - } - -#if ModernKeePassLib || KeePassRT protected override void Dispose(bool disposing) { - if(!disposing) return; -#else - public override void Close() - { -#endif - if(m_sBaseStream != null) + if(disposing && (m_sBaseStream != null)) { - if(m_bWriting == false) // Reading mode + if(!m_bWriting) // Reading mode { - try { m_brInput.Dispose(); } catch { } - + m_brInput.Dispose(); m_brInput = null; } else // Writing mode @@ -167,9 +155,16 @@ namespace ModernKeePassLib.Serialization m_bwOutput = null; } - try { m_sBaseStream.Dispose(); } catch { } + m_sBaseStream.Dispose(); m_sBaseStream = null; } + + base.Dispose(disposing); + } + + public override void Flush() + { + if(m_bWriting) m_bwOutput.Flush(); } public override long Seek(long lOffset, SeekOrigin soOrigin) @@ -192,7 +187,7 @@ namespace ModernKeePassLib.Serialization if(m_nBufferPos == m_pbBuffer.Length) { if(ReadHashedBlock() == false) - return nCount - nRemaining; // Bytes actually read + return (nCount - nRemaining); // Bytes actually read } int nCopy = Math.Min(m_pbBuffer.Length - m_nBufferPos, nRemaining); @@ -214,9 +209,9 @@ namespace ModernKeePassLib.Serialization m_nBufferPos = 0; - if(m_brInput.ReadUInt32() != m_uBufferIndex) + if(m_brInput.ReadUInt32() != m_uBlockIndex) throw new InvalidDataException(); - ++m_uBufferIndex; + ++m_uBlockIndex; byte[] pbStoredHash = m_brInput.ReadBytes(32); if((pbStoredHash == null) || (pbStoredHash.Length != 32)) @@ -241,7 +236,7 @@ namespace ModernKeePassLib.Serialization } m_bEos = true; - m_pbBuffer = new byte[0]; + m_pbBuffer = MemUtil.EmptyByteArray; return false; } @@ -251,25 +246,12 @@ namespace ModernKeePassLib.Serialization if(m_bVerify) { -#if ModernKeePassLib - /*var sha256 = WinRTCrypto.HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha256); - var pbComputedHash = sha256.HashData(m_pbBuffer);*/ - var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256); - var buffer = sha256.HashData(CryptographicBuffer.CreateFromByteArray(m_pbBuffer)); - byte[] pbComputedHash; - CryptographicBuffer.CopyToByteArray(buffer, out pbComputedHash); -#else - SHA256Managed sha256 = new SHA256Managed(); - byte[] pbComputedHash = sha256.ComputeHash(m_pbBuffer); -#endif - if ((pbComputedHash == null) || (pbComputedHash.Length != 32)) + byte[] pbComputedHash = CryptoUtil.HashSha256(m_pbBuffer); + if((pbComputedHash == null) || (pbComputedHash.Length != 32)) throw new InvalidOperationException(); - for(int iHashPos = 0; iHashPos < 32; ++iHashPos) - { - if(pbStoredHash[iHashPos] != pbComputedHash[iHashPos]) - throw new InvalidDataException(); - } + if(!MemUtil.ArraysEqual(pbStoredHash, pbComputedHash)) + throw new InvalidDataException(); } return true; @@ -297,39 +279,26 @@ namespace ModernKeePassLib.Serialization private void WriteHashedBlock() { - m_bwOutput.Write(m_uBufferIndex); - ++m_uBufferIndex; + m_bwOutput.Write(m_uBlockIndex); + ++m_uBlockIndex; if(m_nBufferPos > 0) { -#if ModernKeePassLib - /*var sha256 = WinRTCrypto.HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha256); - var pbHash = sha256.HashData(m_pbBuffer.Where((x, i) => i < m_nBufferPos).ToArray());*/ - var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256); - var buffer = sha256.HashData(CryptographicBuffer.CreateFromByteArray(m_pbBuffer.Where((x, i) => i < m_nBufferPos).ToArray())); - byte[] pbHash; - CryptographicBuffer.CopyToByteArray(buffer, out pbHash); -#else + byte[] pbHash = CryptoUtil.HashSha256(m_pbBuffer, 0, m_nBufferPos); - SHA256Managed sha256 = new SHA256Managed(); + // For KeePassLibSD: + // SHA256Managed sha256 = new SHA256Managed(); + // byte[] pbHash; + // if(m_nBufferPos == m_pbBuffer.Length) + // pbHash = sha256.ComputeHash(m_pbBuffer); + // else + // { + // byte[] pbData = new byte[m_nBufferPos]; + // Array.Copy(m_pbBuffer, 0, pbData, 0, m_nBufferPos); + // pbHash = sha256.ComputeHash(pbData); + // } -#if !KeePassLibSD - byte[] pbHash = sha256.ComputeHash(m_pbBuffer, 0, m_nBufferPos); -#else - byte[] pbHash; - if(m_nBufferPos == m_pbBuffer.Length) - pbHash = sha256.ComputeHash(m_pbBuffer); - else - { - byte[] pbData = new byte[m_nBufferPos]; - Array.Copy(m_pbBuffer, 0, pbData, 0, m_nBufferPos); - pbHash = sha256.ComputeHash(pbData); - } -#endif - -#endif - - m_bwOutput.Write(pbHash); + m_bwOutput.Write(pbHash); } else { diff --git a/ModernKeePassLib/Serialization/HmacBlockStream.cs b/ModernKeePassLib/Serialization/HmacBlockStream.cs new file mode 100644 index 0000000..154933a --- /dev/null +++ b/ModernKeePassLib/Serialization/HmacBlockStream.cs @@ -0,0 +1,356 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; +using Windows.Security.Cryptography; +using Windows.Security.Cryptography.Core; +using ModernKeePassLib.Resources; +using ModernKeePassLib.Utility; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Macs; + +namespace ModernKeePassLib.Serialization +{ + public sealed class HmacBlockStream : Stream + { + private const int NbDefaultBufferSize = 1024 * 1024; // 1 MB + + private Stream m_sBase; + private readonly bool m_bWriting; + private readonly bool m_bVerify; + private byte[] m_pbKey; + + private bool m_bEos = false; + private byte[] m_pbBuffer; + private int m_iBufferPos = 0; + + private ulong m_uBlockIndex = 0; + + public override bool CanRead + { + get { return !m_bWriting; } + } + + public override bool CanSeek + { + get { return false; } + } + + public override bool CanWrite + { + get { return m_bWriting; } + } + + public override long Length + { + get { Debug.Assert(false); throw new NotSupportedException(); } + } + + public override long Position + { + get { Debug.Assert(false); throw new NotSupportedException(); } + set { Debug.Assert(false); throw new NotSupportedException(); } + } + + public HmacBlockStream(Stream sBase, bool bWriting, bool bVerify, + byte[] pbKey) + { + if(sBase == null) throw new ArgumentNullException("sBase"); + if(pbKey == null) throw new ArgumentNullException("pbKey"); + + m_sBase = sBase; + m_bWriting = bWriting; + m_bVerify = bVerify; + m_pbKey = pbKey; + + if(!m_bWriting) // Reading mode + { + if(!m_sBase.CanRead) throw new InvalidOperationException(); + + m_pbBuffer = MemUtil.EmptyByteArray; + } + else // Writing mode + { + if(!m_sBase.CanWrite) throw new InvalidOperationException(); + + m_pbBuffer = new byte[NbDefaultBufferSize]; + } + } + + protected override void Dispose(bool disposing) + { + if(disposing && (m_sBase != null)) + { + if(m_bWriting) + { + if(m_iBufferPos == 0) // No data left in buffer + WriteSafeBlock(); // Write terminating block + else + { + WriteSafeBlock(); // Write remaining buffered data + WriteSafeBlock(); // Write terminating block + } + + Flush(); + } + + //m_sBase.Close(); + m_sBase = null; + } + + base.Dispose(disposing); + } + + public override void Flush() + { + Debug.Assert(m_sBase != null); // Object should not be disposed + if(m_bWriting && (m_sBase != null)) m_sBase.Flush(); + } + + public override long Seek(long lOffset, SeekOrigin soOrigin) + { + Debug.Assert(false); + throw new NotSupportedException(); + } + + public override void SetLength(long lValue) + { + Debug.Assert(false); + throw new NotSupportedException(); + } + + internal static byte[] GetHmacKey64(byte[] pbKey, ulong uBlockIndex) + { + if(pbKey == null) throw new ArgumentNullException("pbKey"); + Debug.Assert(pbKey.Length == 64); + + // We are computing the HMAC using SHA-256, whose internal + // block size is 512 bits; thus create a key that is 512 + // bits long (using SHA-512) + + byte[] pbBlockKey = MemUtil.EmptyByteArray; + byte[] pbIndex = MemUtil.UInt64ToBytes(uBlockIndex); + var h = new Sha512Digest(); + h.BlockUpdate(pbIndex, 0, pbIndex.Length); + h.BlockUpdate(pbKey, 0, pbKey.Length); + h.DoFinal(pbBlockKey, 0); + h.Reset(); + + /*byte[] pbBlockKey; + using(SHA512Managed h = new SHA512Managed()) + { + byte[] pbIndex = MemUtil.UInt64ToBytes(uBlockIndex); + + h.TransformBlock(pbIndex, 0, pbIndex.Length, pbIndex, 0); + h.TransformBlock(pbKey, 0, pbKey.Length, pbKey, 0); + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + pbBlockKey = h.Hash; + } + */ + +#if DEBUG + byte[] pbZero = new byte[64]; + Debug.Assert((pbBlockKey.Length == 64) && !MemUtil.ArraysEqual( + pbBlockKey, pbZero)); // Ensure we own pbBlockKey +#endif + return pbBlockKey; + } + + public override int Read(byte[] pbBuffer, int iOffset, int nCount) + { + if(m_bWriting) throw new InvalidOperationException(); + + int nRemaining = nCount; + while(nRemaining > 0) + { + if(m_iBufferPos == m_pbBuffer.Length) + { + if(!ReadSafeBlock()) + return (nCount - nRemaining); // Bytes actually read + } + + int nCopy = Math.Min(m_pbBuffer.Length - m_iBufferPos, nRemaining); + Debug.Assert(nCopy > 0); + + Array.Copy(m_pbBuffer, m_iBufferPos, pbBuffer, iOffset, nCopy); + + iOffset += nCopy; + m_iBufferPos += nCopy; + + nRemaining -= nCopy; + } + + return nCount; + } + + private bool ReadSafeBlock() + { + if(m_bEos) return false; // End of stream reached already + + byte[] pbStoredHmac = MemUtil.Read(m_sBase, 32); + if((pbStoredHmac == null) || (pbStoredHmac.Length != 32)) + throw new EndOfStreamException(KLRes.FileCorrupted + " " + + KLRes.FileIncomplete); + + // Block index is implicit: it's used in the HMAC computation, + // but does not need to be stored + // byte[] pbBlockIndex = MemUtil.Read(m_sBase, 8); + // if((pbBlockIndex == null) || (pbBlockIndex.Length != 8)) + // throw new EndOfStreamException(); + // ulong uBlockIndex = MemUtil.BytesToUInt64(pbBlockIndex); + // if((uBlockIndex != m_uBlockIndex) && m_bVerify) + // throw new InvalidDataException(); + byte[] pbBlockIndex = MemUtil.UInt64ToBytes(m_uBlockIndex); + + byte[] pbBlockSize = MemUtil.Read(m_sBase, 4); + if((pbBlockSize == null) || (pbBlockSize.Length != 4)) + throw new EndOfStreamException(KLRes.FileCorrupted + " " + + KLRes.FileIncomplete); + int nBlockSize = MemUtil.BytesToInt32(pbBlockSize); + if(nBlockSize < 0) + throw new InvalidDataException(KLRes.FileCorrupted); + + m_iBufferPos = 0; + + m_pbBuffer = MemUtil.Read(m_sBase, nBlockSize); + if((m_pbBuffer == null) || ((m_pbBuffer.Length != nBlockSize) && m_bVerify)) + throw new EndOfStreamException(KLRes.FileCorrupted + " " + + KLRes.FileIncompleteExpc); + + if(m_bVerify) + { + byte[] pbCmpHmac = MemUtil.EmptyByteArray; + byte[] pbBlockKey = GetHmacKey64(m_pbKey, m_uBlockIndex); + +#if ModernKeePassLib + var h = new HMac(new Sha256Digest()); + h.BlockUpdate(pbBlockIndex, 0, pbBlockIndex.Length); + h.BlockUpdate(pbBlockSize, 0, pbBlockSize.Length); + if (m_pbBuffer.Length > 0) + h.BlockUpdate(m_pbBuffer, 0, m_pbBuffer.Length); + + h.DoFinal(pbCmpHmac, 0); + h.Reset(); +#else + using(HMACSHA256 h = new HMACSHA256(pbBlockKey)) + { + h.TransformBlock(pbBlockIndex, 0, pbBlockIndex.Length, + pbBlockIndex, 0); + h.TransformBlock(pbBlockSize, 0, pbBlockSize.Length, + pbBlockSize, 0); + + if(m_pbBuffer.Length > 0) + h.TransformBlock(m_pbBuffer, 0, m_pbBuffer.Length, + m_pbBuffer, 0); + + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + pbCmpHmac = h.Hash; + } +#endif + MemUtil.ZeroByteArray(pbBlockKey); + + if(!MemUtil.ArraysEqual(pbCmpHmac, pbStoredHmac)) + throw new InvalidDataException(KLRes.FileCorrupted); + } + + ++m_uBlockIndex; + + if(nBlockSize == 0) + { + m_bEos = true; + return false; // No further data available + } + return true; + } + + public override void Write(byte[] pbBuffer, int iOffset, int nCount) + { + if(!m_bWriting) throw new InvalidOperationException(); + + while(nCount > 0) + { + if(m_iBufferPos == m_pbBuffer.Length) + WriteSafeBlock(); + + int nCopy = Math.Min(m_pbBuffer.Length - m_iBufferPos, nCount); + Debug.Assert(nCopy > 0); + + Array.Copy(pbBuffer, iOffset, m_pbBuffer, m_iBufferPos, nCopy); + + iOffset += nCopy; + m_iBufferPos += nCopy; + + nCount -= nCopy; + } + } + + private void WriteSafeBlock() + { + byte[] pbBlockIndex = MemUtil.UInt64ToBytes(m_uBlockIndex); + + int cbBlockSize = m_iBufferPos; + byte[] pbBlockSize = MemUtil.Int32ToBytes(cbBlockSize); + + byte[] pbBlockHmac = MemUtil.EmptyByteArray; + byte[] pbBlockKey = GetHmacKey64(m_pbKey, m_uBlockIndex); + +#if ModernKeePassLib + var h = new HMac(new Sha256Digest()); + h.BlockUpdate(pbBlockIndex, 0, pbBlockIndex.Length); + h.BlockUpdate(pbBlockSize, 0, pbBlockSize.Length); + if (m_pbBuffer.Length > 0) + h.BlockUpdate(m_pbBuffer, 0, m_pbBuffer.Length); + + h.DoFinal(pbBlockHmac, 0); + h.Reset(); +#else + using(HMACSHA256 h = new HMACSHA256(pbBlockKey)) + { + h.TransformBlock(pbBlockIndex, 0, pbBlockIndex.Length, + pbBlockIndex, 0); + h.TransformBlock(pbBlockSize, 0, pbBlockSize.Length, + pbBlockSize, 0); + + if(cbBlockSize > 0) + h.TransformBlock(m_pbBuffer, 0, cbBlockSize, m_pbBuffer, 0); + + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + pbBlockHmac = h.Hash; + } +#endif + MemUtil.ZeroByteArray(pbBlockKey); + + MemUtil.Write(m_sBase, pbBlockHmac); + // MemUtil.Write(m_sBase, pbBlockIndex); // Implicit + MemUtil.Write(m_sBase, pbBlockSize); + if(cbBlockSize > 0) + m_sBase.Write(m_pbBuffer, 0, cbBlockSize); + + ++m_uBlockIndex; + m_iBufferPos = 0; + } + } +} diff --git a/ModernKeePassLib/Serialization/IOConnection.cs b/ModernKeePassLib/Serialization/IOConnection.cs index 62dc88f..560f808 100644 --- a/ModernKeePassLib/Serialization/IOConnection.cs +++ b/ModernKeePassLib/Serialization/IOConnection.cs @@ -24,6 +24,7 @@ using System.Net; using System.Diagnostics; using Windows.Storage.Streams; using System.Threading.Tasks; +using ModernKeePassLib.Native; #if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT) using System.Net.Cache; using System.Net.Security; @@ -35,7 +36,6 @@ using System.Security.Cryptography.X509Certificates; #if ModernKeePassLib using Windows.Storage; -//using PCLStorage; #endif using ModernKeePassLib.Utility; @@ -112,6 +112,7 @@ namespace ModernKeePassLib.Serialization m_s = sBase; } +#if !KeePassUAP public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { @@ -123,12 +124,16 @@ namespace ModernKeePassLib.Serialization { return BeginWrite(buffer, offset, count, callback, state); } +#endif - public override void Close() + protected override void Dispose(bool disposing) { - m_s.Close(); + if(disposing) m_s.Dispose(); + + base.Dispose(disposing); } +#if !KeePassUAP public override int EndRead(IAsyncResult asyncResult) { return m_s.EndRead(asyncResult); @@ -138,6 +143,7 @@ namespace ModernKeePassLib.Serialization { m_s.EndWrite(asyncResult); } +#endif public override void Flush() { @@ -178,17 +184,19 @@ namespace ModernKeePassLib.Serialization internal sealed class IocStream : WrapperStream { private readonly bool m_bWrite; // Initially opened for writing + private bool m_bDisposed = false; public IocStream(Stream sBase) : base(sBase) { m_bWrite = sBase.CanWrite; } - public override void Close() + protected override void Dispose(bool disposing) { - base.Close(); + base.Dispose(disposing); - if(MonoWorkarounds.IsRequired(10163) && m_bWrite) + if(disposing && MonoWorkarounds.IsRequired(10163) && m_bWrite && + !m_bDisposed) { try { @@ -210,6 +218,8 @@ namespace ModernKeePassLib.Serialization } catch(Exception) { Debug.Assert(false); } } + + m_bDisposed = true; } public static Stream WrapIfRequired(Stream s) @@ -230,15 +240,20 @@ namespace ModernKeePassLib.Serialization private static ProxyServerType m_pstProxyType = ProxyServerType.System; private static string m_strProxyAddr = string.Empty; private static string m_strProxyPort = string.Empty; + private static ProxyAuthType m_patProxyAuthType = ProxyAuthType.Auto; private static string m_strProxyUserName = string.Empty; private static string m_strProxyPassword = string.Empty; +#if !KeePassUAP + private static bool? m_obDefaultExpect100Continue = null; + private static bool m_bSslCertsAcceptInvalid = false; internal static bool SslCertsAcceptInvalid { // get { return m_bSslCertsAcceptInvalid; } set { m_bSslCertsAcceptInvalid = value; } } +#endif #endif // Web request methods @@ -260,31 +275,67 @@ namespace ModernKeePassLib.Serialization } internal static void SetProxy(ProxyServerType pst, string strAddr, - string strPort, string strUserName, string strPassword) + string strPort, ProxyAuthType pat, string strUserName, + string strPassword) { m_pstProxyType = pst; m_strProxyAddr = (strAddr ?? string.Empty); m_strProxyPort = (strPort ?? string.Empty); + m_patProxyAuthType = pat; m_strProxyUserName = (strUserName ?? string.Empty); m_strProxyPassword = (strPassword ?? string.Empty); } - internal static void ConfigureWebRequest(WebRequest request) + internal static void ConfigureWebRequest(WebRequest request, + IOConnectionInfo ioc) { if(request == null) { Debug.Assert(false); return; } // No throw - // WebDAV support - if(request is HttpWebRequest) - { - request.PreAuthenticate = true; // Also auth GET - if(request.Method == WebRequestMethods.Http.Post) - request.Method = WebRequestMethods.Http.Put; - } - // else if(request is FtpWebRequest) - // { - // Debug.Assert(((FtpWebRequest)request).UsePassive); - // } + IocProperties p = ((ioc != null) ? ioc.Properties : null); + if(p == null) { Debug.Assert(false); p = new IocProperties(); } + IHasIocProperties ihpReq = (request as IHasIocProperties); + if(ihpReq != null) + { + IocProperties pEx = ihpReq.IOConnectionProperties; + if(pEx != null) p.CopyTo(pEx); + else ihpReq.IOConnectionProperties = p.CloneDeep(); + } + + if(IsHttpWebRequest(request)) + { + // WebDAV support +#if !KeePassUAP + request.PreAuthenticate = true; // Also auth GET +#endif + if(string.Equals(request.Method, WebRequestMethods.Http.Post, + StrUtil.CaseIgnoreCmp)) + request.Method = WebRequestMethods.Http.Put; + +#if !KeePassUAP + HttpWebRequest hwr = (request as HttpWebRequest); + if(hwr != null) + { + string strUA = p.Get(IocKnownProperties.UserAgent); + if(!string.IsNullOrEmpty(strUA)) hwr.UserAgent = strUA; + } + else { Debug.Assert(false); } +#endif + } +#if !KeePassUAP + else if(IsFtpWebRequest(request)) + { + FtpWebRequest fwr = (request as FtpWebRequest); + if(fwr != null) + { + bool? obPassive = p.GetBool(IocKnownProperties.Passive); + if(obPassive.HasValue) fwr.UsePassive = obPassive.Value; + } + else { Debug.Assert(false); } + } +#endif + +#if !KeePassUAP // Not implemented and ignored in Mono < 2.10 try { @@ -292,6 +343,7 @@ namespace ModernKeePassLib.Serialization } catch(NotImplementedException) { } catch(Exception) { Debug.Assert(false); } +#endif try { @@ -299,10 +351,20 @@ namespace ModernKeePassLib.Serialization if(GetWebProxy(out prx)) request.Proxy = prx; } catch(Exception) { Debug.Assert(false); } + +#if !KeePassUAP + long? olTimeout = p.GetLong(IocKnownProperties.Timeout); + if(olTimeout.HasValue && (olTimeout.Value >= 0)) + request.Timeout = (int)Math.Min(olTimeout.Value, (long)int.MaxValue); + + bool? ob = p.GetBool(IocKnownProperties.PreAuth); + if(ob.HasValue) request.PreAuthenticate = ob.Value; +#endif } internal static void ConfigureWebClient(WebClient wc) { +#if !KeePassUAP // Not implemented and ignored in Mono < 2.10 try { @@ -310,6 +372,7 @@ namespace ModernKeePassLib.Serialization } catch(NotImplementedException) { } catch(Exception) { Debug.Assert(false); } +#endif try { @@ -320,67 +383,163 @@ namespace ModernKeePassLib.Serialization } private static bool GetWebProxy(out IWebProxy prx) + { + bool b = GetWebProxyServer(out prx); + if(b) AssignCredentials(prx); + return b; + } + + private static bool GetWebProxyServer(out IWebProxy prx) { prx = null; if(m_pstProxyType == ProxyServerType.None) return true; // Use null proxy + if(m_pstProxyType == ProxyServerType.Manual) { try { - if(m_strProxyPort.Length > 0) + if(m_strProxyAddr.Length == 0) + { + // First try default (from config), then system + prx = WebRequest.DefaultWebProxy; +#if !KeePassUAP + if(prx == null) prx = WebRequest.GetSystemWebProxy(); +#endif + } + else if(m_strProxyPort.Length > 0) prx = new WebProxy(m_strProxyAddr, int.Parse(m_strProxyPort)); else prx = new WebProxy(m_strProxyAddr); - if((m_strProxyUserName.Length > 0) || (m_strProxyPassword.Length > 0)) - prx.Credentials = new NetworkCredential(m_strProxyUserName, - m_strProxyPassword); - - return true; // Use manual proxy + return (prx != null); } - catch(Exception exProxy) +#if KeePassUAP + catch(Exception) { Debug.Assert(false); } +#else + catch(Exception ex) { string strInfo = m_strProxyAddr; - if(m_strProxyPort.Length > 0) strInfo += ":" + m_strProxyPort; - MessageService.ShowWarning(strInfo, exProxy.Message); + if(m_strProxyPort.Length > 0) + strInfo += ":" + m_strProxyPort; + MessageService.ShowWarning(strInfo, ex.Message); } +#endif return false; // Use default } - if((m_strProxyUserName.Length == 0) && (m_strProxyPassword.Length == 0)) - return false; // Use default proxy, no auth - + Debug.Assert(m_pstProxyType == ProxyServerType.System); try { - prx = WebRequest.DefaultWebProxy; - if(prx == null) prx = WebRequest.GetSystemWebProxy(); - if(prx == null) throw new InvalidOperationException(); + // First try system, then default (from config) +#if !KeePassUAP + prx = WebRequest.GetSystemWebProxy(); +#endif + if(prx == null) prx = WebRequest.DefaultWebProxy; - prx.Credentials = new NetworkCredential(m_strProxyUserName, - m_strProxyPassword); - return true; + return (prx != null); } catch(Exception) { Debug.Assert(false); } return false; } - private static void PrepareWebAccess() + private static void AssignCredentials(IWebProxy prx) { - if(m_bSslCertsAcceptInvalid) - ServicePointManager.ServerCertificateValidationCallback = - IOConnection.AcceptCertificate; - else - ServicePointManager.ServerCertificateValidationCallback = null; + if(prx == null) return; // No assert + + string strUserName = m_strProxyUserName; + string strPassword = m_strProxyPassword; + + ProxyAuthType pat = m_patProxyAuthType; + if(pat == ProxyAuthType.Auto) + { + if((strUserName.Length > 0) || (strPassword.Length > 0)) + pat = ProxyAuthType.Manual; + else pat = ProxyAuthType.Default; + } + + try + { + if(pat == ProxyAuthType.None) + prx.Credentials = null; + else if(pat == ProxyAuthType.Default) + prx.Credentials = CredentialCache.DefaultCredentials; + else if(pat == ProxyAuthType.Manual) + { + if((strUserName.Length > 0) || (strPassword.Length > 0)) + prx.Credentials = new NetworkCredential( + strUserName, strPassword); + } + else { Debug.Assert(false); } + } + catch(Exception) { Debug.Assert(false); } + } + + private static void PrepareWebAccess(IOConnectionInfo ioc) + { +#if !KeePassUAP + IocProperties p = ((ioc != null) ? ioc.Properties : null); + if(p == null) { Debug.Assert(false); p = new IocProperties(); } + + try + { + if(m_bSslCertsAcceptInvalid) + ServicePointManager.ServerCertificateValidationCallback = + IOConnection.AcceptCertificate; + else + ServicePointManager.ServerCertificateValidationCallback = null; + } + catch(Exception) { Debug.Assert(false); } + + try + { + SecurityProtocolType spt = (SecurityProtocolType.Ssl3 | + SecurityProtocolType.Tls); + + // The flags Tls11 and Tls12 in SecurityProtocolType have been + // introduced in .NET 4.5 and must not be set when running under + // older .NET versions (otherwise an exception is thrown) + Type tSpt = typeof(SecurityProtocolType); + string[] vSpt = Enum.GetNames(tSpt); + foreach(string strSpt in vSpt) + { + if(strSpt.Equals("Tls11", StrUtil.CaseIgnoreCmp)) + spt |= (SecurityProtocolType)Enum.Parse(tSpt, "Tls11", true); + else if(strSpt.Equals("Tls12", StrUtil.CaseIgnoreCmp)) + spt |= (SecurityProtocolType)Enum.Parse(tSpt, "Tls12", true); + } + + ServicePointManager.SecurityProtocol = spt; + } + catch(Exception) { Debug.Assert(false); } + + try + { + bool bCurCont = ServicePointManager.Expect100Continue; + if(!m_obDefaultExpect100Continue.HasValue) + { + Debug.Assert(bCurCont); // Default should be true + m_obDefaultExpect100Continue = bCurCont; + } + + bool bNewCont = m_obDefaultExpect100Continue.Value; + bool? ob = p.GetBool(IocKnownProperties.Expect100Continue); + if(ob.HasValue) bNewCont = ob.Value; + + if(bNewCont != bCurCont) + ServicePointManager.Expect100Continue = bNewCont; + } + catch(Exception) { Debug.Assert(false); } +#endif } private static IOWebClient CreateWebClient(IOConnectionInfo ioc) { - PrepareWebAccess(); + PrepareWebAccess(ioc); - IOWebClient wc = new IOWebClient(); + IOWebClient wc = new IOWebClient(ioc); ConfigureWebClient(wc); if((ioc.UserName.Length > 0) || (ioc.Password.Length > 0)) @@ -393,10 +552,10 @@ namespace ModernKeePassLib.Serialization private static WebRequest CreateWebRequest(IOConnectionInfo ioc) { - PrepareWebAccess(); + PrepareWebAccess(ioc); WebRequest req = WebRequest.Create(ioc.Path); - ConfigureWebRequest(req); + ConfigureWebRequest(req, ioc); if((ioc.UserName.Length > 0) || (ioc.Password.Length > 0)) req.Credentials = new NetworkCredential(ioc.UserName, ioc.Password); @@ -422,7 +581,7 @@ namespace ModernKeePassLib.Serialization new Uri(ioc.Path))); } #else - public static Stream OpenRead(IOConnectionInfo ioc) + public static Stream OpenRead(IOConnectionInfo ioc) { RaiseIOAccessPreEvent(ioc, IOAccessType.Read); @@ -449,9 +608,7 @@ namespace ModernKeePassLib.Serialization // Mono does not set HttpWebRequest.Method to POST for writes, // so one needs to set the method to PUT explicitly - if(NativeLib.IsUnix() && (uri.Scheme.Equals(Uri.UriSchemeHttp, - StrUtil.CaseIgnoreCmp) || uri.Scheme.Equals(Uri.UriSchemeHttps, - StrUtil.CaseIgnoreCmp))) + if(NativeLib.IsUnix() && IsHttpWebRequest(uri)) s = CreateWebClient(ioc).OpenWrite(uri, WebRequestMethods.Http.Put); else s = CreateWebClient(ioc).OpenWrite(uri); @@ -478,8 +635,7 @@ namespace ModernKeePassLib.Serialization public static bool FileExists(IOConnectionInfo ioc, bool bThrowErrors) { - if(ioc == null) { Debug.Assert(false); - } + if(ioc == null) { Debug.Assert(false); return false; } RaiseIOAccessPreEvent(ioc, IOAccessType.Exists); @@ -526,7 +682,7 @@ namespace ModernKeePassLib.Serialization } #endif #if !ModernKeePassLib - internal static void DisposeResponse(WebResponse wr, bool bGetStream) + internal static void DisposeResponse(WebResponse wr, bool bGetStream) { if(wr == null) return; @@ -535,26 +691,25 @@ namespace ModernKeePassLib.Serialization if(bGetStream) { Stream s = wr.GetResponseStream(); - if(s != null) s.Dispose(); + if(s != null) s.Close(); } } catch(Exception) { Debug.Assert(false); } - try { wr.Dispose(); } + try { wr.Close(); } catch(Exception) { Debug.Assert(false); } } #endif public static byte[] ReadFile(IOConnectionInfo ioc) { - Stream sIn = null; + Stream sIn = null; MemoryStream ms = null; try { - sIn = OpenRead(ioc); + sIn = IOConnection.OpenRead(ioc); if(sIn == null) return null; ms = new MemoryStream(); - MemUtil.CopyStream(sIn, ms); return ms.ToArray(); @@ -587,5 +742,49 @@ namespace ModernKeePassLib.Serialization IOConnection.IOAccessPre(null, e); } } +#if !ModernKeePassLib + private static bool IsHttpWebRequest(Uri uri) + { + if(uri == null) { Debug.Assert(false); return false; } + + string sch = uri.Scheme; + if(sch == null) { Debug.Assert(false); return false; } + return (sch.Equals("http", StrUtil.CaseIgnoreCmp) || // Uri.UriSchemeHttp + sch.Equals("https", StrUtil.CaseIgnoreCmp)); // Uri.UriSchemeHttps + } + + internal static bool IsHttpWebRequest(WebRequest wr) + { + if(wr == null) { Debug.Assert(false); return false; } + +#if KeePassUAP + return IsHttpWebRequest(wr.RequestUri); +#else + return (wr is HttpWebRequest); +#endif + } + + internal static bool IsFtpWebRequest(WebRequest wr) + { + if(wr == null) { Debug.Assert(false); return false; } + +#if KeePassUAP + return string.Equals(wr.RequestUri.Scheme, "ftp", StrUtil.CaseIgnoreCmp); +#else + return (wr is FtpWebRequest); +#endif + } + + private static bool IsFileWebRequest(WebRequest wr) + { + if(wr == null) { Debug.Assert(false); return false; } + +#if KeePassUAP + return string.Equals(wr.RequestUri.Scheme, "file", StrUtil.CaseIgnoreCmp); +#else + return (wr is FileWebRequest); +#endif + } +#endif // ModernKeePass } } diff --git a/ModernKeePassLib/Serialization/IOConnectionInfo.cs b/ModernKeePassLib/Serialization/IOConnectionInfo.cs index cffd85a..0bf6de3 100644 --- a/ModernKeePassLib/Serialization/IOConnectionInfo.cs +++ b/ModernKeePassLib/Serialization/IOConnectionInfo.cs @@ -139,9 +139,40 @@ namespace ModernKeePassLib.Serialization set { m_ioHint = value; } } */ + private IocProperties m_props = new IocProperties(); + [XmlIgnore] + public IocProperties Properties + { + get { return m_props; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_props = value; + } + } + + /// + /// For serialization only; use Properties in code. + /// + [DefaultValue("")] + public string PropertiesEx + { + get { return m_props.Serialize(); } + set + { + if(value == null) throw new ArgumentNullException("value"); + + IocProperties p = IocProperties.Deserialize(value); + Debug.Assert(p != null); + m_props = (p ?? new IocProperties()); + } + } + public IOConnectionInfo CloneDeep() { - return (IOConnectionInfo)this.MemberwiseClone(); + IOConnectionInfo ioc = (IOConnectionInfo)this.MemberwiseClone(); + ioc.m_props = m_props.CloneDeep(); + return ioc; } #if DEBUG // For debugger display only @@ -274,14 +305,14 @@ namespace ModernKeePassLib.Serialization string str = m_strUrl; if(m_strUser.Length > 0) - str += " (" + m_strUser + ")"; + str += (" (" + m_strUser + ")"); return str; } public bool IsEmpty() { - return (m_strUrl.Length > 0); + return (m_strUrl.Length == 0); } public static IOConnectionInfo FromPath(string strPath) @@ -320,13 +351,13 @@ namespace ModernKeePassLib.Serialization if(IsLocalFile()) return File.Exists(m_strUrl); #endif - return true; + return true; } public bool IsLocalFile() { // Not just ":/", see e.g. AppConfigEx.ChangePathRelAbs - return (m_strUrl.IndexOf(@"://") < 0); + return (m_strUrl.IndexOf("://") < 0); } public void ClearCredentials(bool bDependingOnRememberMode) diff --git a/ModernKeePassLib/Serialization/IocProperties.cs b/ModernKeePassLib/Serialization/IocProperties.cs new file mode 100644 index 0000000..bb355c1 --- /dev/null +++ b/ModernKeePassLib/Serialization/IocProperties.cs @@ -0,0 +1,192 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Text; +using System.Xml; + +using ModernKeePassLib.Interfaces; +using ModernKeePassLib.Utility; + +using StrDict = System.Collections.Generic.Dictionary; + +namespace ModernKeePassLib.Serialization +{ + public interface IHasIocProperties + { + IocProperties IOConnectionProperties { get; set; } + } + + public sealed class IocProperties : IDeepCloneable + { + private StrDict m_dict = new StrDict(); + + public IocProperties() + { + } + + public IocProperties CloneDeep() + { + IocProperties p = new IocProperties(); + p.m_dict = new StrDict(m_dict); + return p; + } + + public string Get(string strKey) + { + if(string.IsNullOrEmpty(strKey)) return null; + + foreach(KeyValuePair kvp in m_dict) + { + if(kvp.Key.Equals(strKey, StrUtil.CaseIgnoreCmp)) + return kvp.Value; + } + + return null; + } + + public void Set(string strKey, string strValue) + { + if(string.IsNullOrEmpty(strKey)) { Debug.Assert(false); return; } + + foreach(KeyValuePair kvp in m_dict) + { + if(kvp.Key.Equals(strKey, StrUtil.CaseIgnoreCmp)) + { + if(string.IsNullOrEmpty(strValue)) m_dict.Remove(kvp.Key); + else m_dict[kvp.Key] = strValue; + return; + } + } + + if(!string.IsNullOrEmpty(strValue)) m_dict[strKey] = strValue; + } + + public bool? GetBool(string strKey) + { + string str = Get(strKey); + if(string.IsNullOrEmpty(str)) return null; + + return StrUtil.StringToBool(str); + } + + public void SetBool(string strKey, bool? ob) + { + if(ob.HasValue) Set(strKey, (ob.Value ? "1" : "0")); + else Set(strKey, null); + } + + public long? GetLong(string strKey) + { + string str = Get(strKey); + if(string.IsNullOrEmpty(str)) return null; + + long l; + if(StrUtil.TryParseLongInvariant(str, out l)) return l; + Debug.Assert(false); + return null; + } + + public void SetLong(string strKey, long? ol) + { + if(ol.HasValue) + Set(strKey, ol.Value.ToString(NumberFormatInfo.InvariantInfo)); + else Set(strKey, null); + } + + public string Serialize() + { + if(m_dict.Count == 0) return string.Empty; + + StringBuilder sbAll = new StringBuilder(); + foreach(KeyValuePair kvp in m_dict) + { + sbAll.Append(kvp.Key); + sbAll.Append(kvp.Value); + } + + string strAll = sbAll.ToString(); + char chSepOuter = ';'; + if(strAll.IndexOf(chSepOuter) >= 0) + chSepOuter = StrUtil.GetUnusedChar(strAll); + + strAll += chSepOuter; + char chSepInner = '='; + if(strAll.IndexOf(chSepInner) >= 0) + chSepInner = StrUtil.GetUnusedChar(strAll); + + StringBuilder sb = new StringBuilder(); + sb.Append(chSepOuter); + sb.Append(chSepInner); + + foreach(KeyValuePair kvp in m_dict) + { + sb.Append(chSepOuter); + sb.Append(kvp.Key); + sb.Append(chSepInner); + sb.Append(kvp.Value); + } + + return sb.ToString(); + } + + public static IocProperties Deserialize(string strSerialized) + { + IocProperties p = new IocProperties(); + if(string.IsNullOrEmpty(strSerialized)) return p; // No assert + + char chSepOuter = strSerialized[0]; + string[] v = strSerialized.Substring(1).Split(new char[] { chSepOuter }); + if((v == null) || (v.Length < 2)) { Debug.Assert(false); return p; } + + string strMeta = v[0]; + if(string.IsNullOrEmpty(strMeta)) { Debug.Assert(false); return p; } + + char chSepInner = strMeta[0]; + char[] vSepInner = new char[] { chSepInner }; + + for(int i = 1; i < v.Length; ++i) + { + string strProp = v[i]; + if(string.IsNullOrEmpty(strProp)) { Debug.Assert(false); continue; } + + string[] vProp = strProp.Split(vSepInner); + if((vProp == null) || (vProp.Length < 2)) { Debug.Assert(false); continue; } + Debug.Assert(vProp.Length == 2); + + p.Set(vProp[0], vProp[1]); + } + + return p; + } + + public void CopyTo(IocProperties p) + { + if(p == null) { Debug.Assert(false); return; } + + foreach(KeyValuePair kvp in m_dict) + { + p.m_dict[kvp.Key] = kvp.Value; + } + } + } +} diff --git a/ModernKeePassLib/Serialization/IocPropertyInfo.cs b/ModernKeePassLib/Serialization/IocPropertyInfo.cs new file mode 100644 index 0000000..a11ed83 --- /dev/null +++ b/ModernKeePassLib/Serialization/IocPropertyInfo.cs @@ -0,0 +1,99 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +using ModernKeePassLib.Utility; + +namespace ModernKeePassLib.Serialization +{ + public sealed class IocPropertyInfo + { + private readonly string m_strName; + public string Name + { + get { return m_strName; } + } + + private readonly Type m_t; + public Type Type + { + get { return m_t; } + } + + private string m_strDisplayName; + public string DisplayName + { + get { return m_strDisplayName; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strDisplayName = value; + } + } + + private List m_lProtocols = new List(); + public IEnumerable Protocols + { + get { return m_lProtocols; } + } + + public IocPropertyInfo(string strName, Type t, string strDisplayName, + string[] vProtocols) + { + if(strName == null) throw new ArgumentNullException("strName"); + if(t == null) throw new ArgumentNullException("t"); + if(strDisplayName == null) throw new ArgumentNullException("strDisplayName"); + + m_strName = strName; + m_t = t; + m_strDisplayName = strDisplayName; + + AddProtocols(vProtocols); + } + + public void AddProtocols(string[] v) + { + if(v == null) { Debug.Assert(false); return; } + + foreach(string strProtocol in v) + { + if(strProtocol == null) continue; + + string str = strProtocol.Trim(); + if(str.Length == 0) continue; + + bool bFound = false; + foreach(string strEx in m_lProtocols) + { + if(strEx.Equals(str, StrUtil.CaseIgnoreCmp)) + { + bFound = true; + break; + } + } + + if(!bFound) m_lProtocols.Add(str); + } + } + } +} diff --git a/ModernKeePassLib/Serialization/IocPropertyInfoPool.cs b/ModernKeePassLib/Serialization/IocPropertyInfoPool.cs new file mode 100644 index 0000000..5e70cb8 --- /dev/null +++ b/ModernKeePassLib/Serialization/IocPropertyInfoPool.cs @@ -0,0 +1,123 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2017 Dominik Reichl + + This program 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. + + This program 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 this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +using ModernKeePassLib.Resources; +using ModernKeePassLib.Utility; + +namespace ModernKeePassLib.Serialization +{ + public static class IocKnownProtocols + { + public const string Http = "HTTP"; + public const string Https = "HTTPS"; + public const string WebDav = "WebDAV"; + public const string Ftp = "FTP"; + } + + public static class IocKnownProperties + { + public const string Timeout = "Timeout"; + public const string PreAuth = "PreAuth"; + + public const string UserAgent = "UserAgent"; + public const string Expect100Continue = "Expect100Continue"; + + public const string Passive = "Passive"; + } + + public static class IocPropertyInfoPool + { + private static List m_l = null; + public static IEnumerable PropertyInfos + { + get { EnsureInitialized(); return m_l; } + } + + private static void EnsureInitialized() + { + if(m_l != null) return; + + string strGen = KLRes.General; + string strHttp = IocKnownProtocols.Http; + string strHttps = IocKnownProtocols.Https; + string strWebDav = IocKnownProtocols.WebDav; + string strFtp = IocKnownProtocols.Ftp; + + string[] vGen = new string[] { strGen }; + string[] vHttp = new string[] { strHttp, strHttps, strWebDav }; + string[] vFtp = new string[] { strFtp }; + + List l = new List(); + + l.Add(new IocPropertyInfo(IocKnownProperties.Timeout, + typeof(long), KLRes.Timeout + " [ms]", vGen)); + l.Add(new IocPropertyInfo(IocKnownProperties.PreAuth, + typeof(bool), KLRes.PreAuth, vGen)); + + l.Add(new IocPropertyInfo(IocKnownProperties.UserAgent, + typeof(string), KLRes.UserAgent, vHttp)); + l.Add(new IocPropertyInfo(IocKnownProperties.Expect100Continue, + typeof(bool), KLRes.Expect100Continue, vHttp)); + + l.Add(new IocPropertyInfo(IocKnownProperties.Passive, + typeof(bool), KLRes.Passive, vFtp)); + + // l.Add(new IocPropertyInfo("Test", typeof(bool), + // "Long long long long long long long long long long long long long long long long long long long long", + // new string[] { "Proto 1/9", "Proto 2/9", "Proto 3/9", "Proto 4/9", "Proto 5/9", + // "Proto 6/9", "Proto 7/9", "Proto 8/9", "Proto 9/9" })); + + m_l = l; + } + + public static IocPropertyInfo Get(string strName) + { + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return null; } + + EnsureInitialized(); + foreach(IocPropertyInfo pi in m_l) + { + if(pi.Name.Equals(strName, StrUtil.CaseIgnoreCmp)) + return pi; + } + + return null; + } + + public static bool Add(IocPropertyInfo pi) + { + if(pi == null) { Debug.Assert(false); return false; } + + // Name must be non-empty + string strName = pi.Name; + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return false; } + + IocPropertyInfo piEx = Get(strName); // Ensures initialized + if(piEx != null) { Debug.Assert(false); return false; } // Exists already + + m_l.Add(pi); + return true; + } + } +} diff --git a/ModernKeePassLib/Serialization/KdbxFile.Read.Streamed.cs b/ModernKeePassLib/Serialization/KdbxFile.Read.Streamed.cs index 0105e32..0e48d17 100644 --- a/ModernKeePassLib/Serialization/KdbxFile.Read.Streamed.cs +++ b/ModernKeePassLib/Serialization/KdbxFile.Read.Streamed.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,6 +19,8 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.IO; using System.Text; using System.Security; using System.Drawing; @@ -58,13 +60,17 @@ namespace ModernKeePassLib.Serialization DeletedObject, Group, GroupTimes, + GroupCustomData, + GroupCustomDataItem, Entry, EntryTimes, EntryString, EntryBinary, EntryAutoType, EntryAutoTypeItem, - EntryHistory + EntryHistory, + EntryCustomData, + EntryCustomDataItem } private bool m_bReadNextNode = true; @@ -84,10 +90,14 @@ namespace ModernKeePassLib.Serialization private byte[] m_pbCustomIconData = null; private string m_strCustomDataKey = null; private string m_strCustomDataValue = null; + private string m_strGroupCustomDataKey = null; + private string m_strGroupCustomDataValue = null; + private string m_strEntryCustomDataKey = null; + private string m_strEntryCustomDataValue = null; - private void ReadXmlStreamed(Stream readerStream, Stream sParentStream) + private void ReadXmlStreamed(Stream sXml, Stream sParent) { - ReadDocumentStreamed(CreateXmlReader(readerStream), sParentStream); + ReadDocumentStreamed(CreateXmlReader(sXml), sParent); } internal static XmlReaderSettings CreateStdXmlReaderSettings() @@ -125,7 +135,6 @@ namespace ModernKeePassLib.Serialization if(xr == null) throw new ArgumentNullException("xr"); m_ctxGroups.Clear(); - m_dictBinPool = new Dictionary(); KdbContext ctx = KdbContext.Null; @@ -216,15 +225,25 @@ namespace ModernKeePassLib.Serialization ReadString(xr); // Ignore else if(xr.Name == ElemHeaderHash) { + // The header hash is typically only stored in + // KDBX <= 3.1 files, not in KDBX >= 4 files + // (here, the header is verified via a HMAC), + // but we also support it for KDBX >= 4 files + // (i.e. if it's present, we check it) + string strHash = ReadString(xr); if(!string.IsNullOrEmpty(strHash) && (m_pbHashOfHeader != null) && !m_bRepairMode) { + Debug.Assert(m_uFileVersion < FileVersion32_4); + byte[] pbHash = Convert.FromBase64String(strHash); if(!MemUtil.ArraysEqual(pbHash, m_pbHashOfHeader)) - throw new IOException(KLRes.FileCorrupted); + throw new InvalidDataException(KLRes.FileCorrupted); } } + else if(xr.Name == ElemSettingsChanged) + m_pwDatabase.SettingsChanged = ReadTime(xr); else if(xr.Name == ElemDbName) m_pwDatabase.Name = ReadString(xr); else if(xr.Name == ElemDbNameChanged) @@ -251,6 +270,8 @@ namespace ModernKeePassLib.Serialization m_pwDatabase.MasterKeyChangeRec = ReadLong(xr, -1); else if(xr.Name == ElemDbKeyChangeForce) m_pwDatabase.MasterKeyChangeForce = ReadLong(xr, -1); + else if(xr.Name == ElemDbKeyChangeForceOnce) + m_pwDatabase.MasterKeyChangeForceOnce = ReadBool(xr, false); else if(xr.Name == ElemMemoryProt) return SwitchContext(ctx, KdbContext.MemoryProtection, xr); else if(xr.Name == ElemCustomIcons) @@ -323,7 +344,14 @@ namespace ModernKeePassLib.Serialization string strKey = xr.Value; ProtectedBinary pbData = ReadProtectedBinary(xr); - m_dictBinPool[strKey ?? string.Empty] = pbData; + int iKey; + if(!StrUtil.TryParseIntInvariant(strKey, out iKey)) + throw new FormatException(); + if(iKey < 0) throw new FormatException(); + + Debug.Assert(m_pbsBinaries.Get(iKey) == null); + Debug.Assert(m_pbsBinaries.Find(pbData) < 0); + m_pbsBinaries.Set(iKey, pbData); } else ReadUnknown(xr); } @@ -369,7 +397,7 @@ namespace ModernKeePassLib.Serialization else if(xr.Name == ElemNotes) m_ctxGroup.Notes = ReadString(xr); else if(xr.Name == ElemIcon) - m_ctxGroup.IconId = (PwIcon)ReadInt(xr, (int)PwIcon.Folder); + m_ctxGroup.IconId = ReadIconId(xr, PwIcon.Folder); else if(xr.Name == ElemCustomIconID) m_ctxGroup.CustomIconUuid = ReadUuid(xr); else if(xr.Name == ElemTimes) @@ -384,6 +412,8 @@ namespace ModernKeePassLib.Serialization m_ctxGroup.EnableSearching = StrUtil.StringToBoolEx(ReadString(xr)); else if(xr.Name == ElemLastTopVisibleEntry) m_ctxGroup.LastTopVisibleEntry = ReadUuid(xr); + else if(xr.Name == ElemCustomData) + return SwitchContext(ctx, KdbContext.GroupCustomData, xr); else if(xr.Name == ElemGroup) { m_ctxGroup = new PwGroup(false, false); @@ -404,11 +434,25 @@ namespace ModernKeePassLib.Serialization else ReadUnknown(xr); break; + case KdbContext.GroupCustomData: + if(xr.Name == ElemStringDictExItem) + return SwitchContext(ctx, KdbContext.GroupCustomDataItem, xr); + else ReadUnknown(xr); + break; + + case KdbContext.GroupCustomDataItem: + if(xr.Name == ElemKey) + m_strGroupCustomDataKey = ReadString(xr); + else if(xr.Name == ElemValue) + m_strGroupCustomDataValue = ReadString(xr); + else ReadUnknown(xr); + break; + case KdbContext.Entry: if(xr.Name == ElemUuid) m_ctxEntry.Uuid = ReadUuid(xr); else if(xr.Name == ElemIcon) - m_ctxEntry.IconId = (PwIcon)ReadInt(xr, (int)PwIcon.Key); + m_ctxEntry.IconId = ReadIconId(xr, PwIcon.Key); else if(xr.Name == ElemCustomIconID) m_ctxEntry.CustomIconUuid = ReadUuid(xr); else if(xr.Name == ElemFgColor) @@ -435,6 +479,8 @@ namespace ModernKeePassLib.Serialization return SwitchContext(ctx, KdbContext.EntryBinary, xr); else if(xr.Name == ElemAutoType) return SwitchContext(ctx, KdbContext.EntryAutoType, xr); + else if(xr.Name == ElemCustomData) + return SwitchContext(ctx, KdbContext.EntryCustomData, xr); else if(xr.Name == ElemHistory) { Debug.Assert(m_bEntryInHistory == false); @@ -509,6 +555,20 @@ namespace ModernKeePassLib.Serialization else ReadUnknown(xr); break; + case KdbContext.EntryCustomData: + if(xr.Name == ElemStringDictExItem) + return SwitchContext(ctx, KdbContext.EntryCustomDataItem, xr); + else ReadUnknown(xr); + break; + + case KdbContext.EntryCustomDataItem: + if(xr.Name == ElemKey) + m_strEntryCustomDataKey = ReadString(xr); + else if(xr.Name == ElemValue) + m_strEntryCustomDataValue = ReadString(xr); + else ReadUnknown(xr); + break; + case KdbContext.EntryHistory: if(xr.Name == ElemEntry) { @@ -610,6 +670,19 @@ namespace ModernKeePassLib.Serialization } else if((ctx == KdbContext.GroupTimes) && (xr.Name == ElemTimes)) return KdbContext.Group; + else if((ctx == KdbContext.GroupCustomData) && (xr.Name == ElemCustomData)) + return KdbContext.Group; + else if((ctx == KdbContext.GroupCustomDataItem) && (xr.Name == ElemStringDictExItem)) + { + if((m_strGroupCustomDataKey != null) && (m_strGroupCustomDataValue != null)) + m_ctxGroup.CustomData.Set(m_strGroupCustomDataKey, m_strGroupCustomDataValue); + else { Debug.Assert(false); } + + m_strGroupCustomDataKey = null; + m_strGroupCustomDataValue = null; + + return KdbContext.GroupCustomData; + } else if((ctx == KdbContext.Entry) && (xr.Name == ElemEntry)) { // Create new UUID if absent @@ -660,6 +733,19 @@ namespace ModernKeePassLib.Serialization m_ctxATSeq = null; return KdbContext.EntryAutoType; } + else if((ctx == KdbContext.EntryCustomData) && (xr.Name == ElemCustomData)) + return KdbContext.Entry; + else if((ctx == KdbContext.EntryCustomDataItem) && (xr.Name == ElemStringDictExItem)) + { + if((m_strEntryCustomDataKey != null) && (m_strEntryCustomDataValue != null)) + m_ctxEntry.CustomData.Set(m_strEntryCustomDataKey, m_strEntryCustomDataValue); + else { Debug.Assert(false); } + + m_strEntryCustomDataKey = null; + m_strEntryCustomDataValue = null; + + return KdbContext.EntryCustomData; + } else if((ctx == KdbContext.EntryHistory) && (xr.Name == ElemHistory)) { m_bEntryInHistory = false; @@ -707,6 +793,55 @@ namespace ModernKeePassLib.Serialization #endif } + private byte[] ReadBase64(XmlReader xr, bool bRaw) + { + // if(bRaw) return ReadBase64RawInChunks(xr); + + string str = (bRaw ? ReadStringRaw(xr) : ReadString(xr)); + if(string.IsNullOrEmpty(str)) return MemUtil.EmptyByteArray; + + return Convert.FromBase64String(str); + } + + /* private byte[] m_pbBase64ReadBuf = new byte[1024 * 1024 * 3]; + private byte[] ReadBase64RawInChunks(XmlReader xr) + { + xr.MoveToContent(); + + List lParts = new List(); + byte[] pbBuf = m_pbBase64ReadBuf; + while(true) + { + int cb = xr.ReadElementContentAsBase64(pbBuf, 0, pbBuf.Length); + if(cb == 0) break; + + byte[] pb = new byte[cb]; + Array.Copy(pbBuf, 0, pb, 0, cb); + lParts.Add(pb); + + // No break when cb < pbBuf.Length, because ReadElementContentAsBase64 + // moves to the next XML node only when returning 0 + } + m_bReadNextNode = false; + + if(lParts.Count == 0) return MemUtil.EmptyByteArray; + if(lParts.Count == 1) return lParts[0]; + + long cbRes = 0; + for(int i = 0; i < lParts.Count; ++i) + cbRes += lParts[i].Length; + + byte[] pbRes = new byte[cbRes]; + int cbCur = 0; + for(int i = 0; i < lParts.Count; ++i) + { + Array.Copy(lParts[i], 0, pbRes, cbCur, lParts[i].Length); + cbCur += lParts[i].Length; + } + + return pbRes; + } */ + private bool ReadBool(XmlReader xr, bool bDefault) { string str = ReadString(xr); @@ -719,9 +854,9 @@ namespace ModernKeePassLib.Serialization private PwUuid ReadUuid(XmlReader xr) { - string str = ReadString(xr); - if(string.IsNullOrEmpty(str)) return PwUuid.Zero; - return new PwUuid(Convert.FromBase64String(str)); + byte[] pb = ReadBase64(xr, false); + if(pb.Length == 0) return PwUuid.Zero; + return new PwUuid(pb); } private int ReadInt(XmlReader xr, int nDefault) @@ -782,15 +917,44 @@ namespace ModernKeePassLib.Serialization private DateTime ReadTime(XmlReader xr) { - string str = ReadString(xr); + // Cf. WriteObject(string, DateTime) + if((m_format == KdbxFormat.Default) && (m_uFileVersion >= FileVersion32_4)) + { + // long l = ReadLong(xr, -1); + // if(l != -1) return DateTime.FromBinary(l); - DateTime dt; - if(TimeUtil.TryDeserializeUtc(str, out dt)) return dt; + byte[] pb = ReadBase64(xr, false); + if(pb.Length != 8) + { + Debug.Assert(false); + byte[] pb8 = new byte[8]; + Array.Copy(pb, pb8, Math.Min(pb.Length, 8)); // Little-endian + pb = pb8; + } + long lSec = MemUtil.BytesToInt64(pb); + return new DateTime(lSec * TimeSpan.TicksPerSecond, DateTimeKind.Utc); + } + else + { + string str = ReadString(xr); + + DateTime dt; + if(TimeUtil.TryDeserializeUtc(str, out dt)) return dt; + } Debug.Assert(false); return m_dtNow; } + private PwIcon ReadIconId(XmlReader xr, PwIcon icDefault) + { + int i = ReadInt(xr, (int)icDefault); + if((i >= 0) && (i < (int)PwIcon.Count)) return (PwIcon)i; + + Debug.Assert(false); + return icDefault; + } + private ProtectedString ReadProtectedString(XmlReader xr) { XorredBuffer xb = ProcessNode(xr); @@ -815,10 +979,27 @@ namespace ModernKeePassLib.Serialization if(xr.MoveToAttribute(AttrRef)) { string strRef = xr.Value; - if(strRef != null) + if(!string.IsNullOrEmpty(strRef)) { - ProtectedBinary pb = BinPoolGet(strRef); - if(pb != null) return pb; + int iRef; + if(StrUtil.TryParseIntInvariant(strRef, out iRef)) + { + ProtectedBinary pb = m_pbsBinaries.Get(iRef); + if(pb != null) + { + // https://sourceforge.net/p/keepass/feature-requests/2023/ + xr.MoveToElement(); +#if DEBUG + string strInner = ReadStringRaw(xr); + Debug.Assert(string.IsNullOrEmpty(strInner)); +#else + ReadStringRaw(xr); +#endif + + return pb; + } + else { Debug.Assert(false); } + } else { Debug.Assert(false); } } else { Debug.Assert(false); } @@ -835,10 +1016,9 @@ namespace ModernKeePassLib.Serialization return new ProtectedBinary(true, xb); } - string strValue = ReadString(xr); - if(strValue.Length == 0) return new ProtectedBinary(); + byte[] pbData = ReadBase64(xr, true); + if(pbData.Length == 0) return new ProtectedBinary(); - byte[] pbData = Convert.FromBase64String(strValue); if(bCompressed) pbData = MemUtil.Decompress(pbData); return new ProtectedBinary(false, pbData); } @@ -875,13 +1055,8 @@ namespace ModernKeePassLib.Serialization if(xr.Value == ValTrue) { xr.MoveToElement(); - string strEncrypted = ReadStringRaw(xr); - - byte[] pbEncrypted; - if(strEncrypted.Length > 0) - pbEncrypted = Convert.FromBase64String(strEncrypted); - else pbEncrypted = new byte[0]; + byte[] pbEncrypted = ReadBase64(xr, true); byte[] pbPad = m_randomStream.GetRandomBytes((uint)pbEncrypted.Length); xb = new XorredBuffer(pbEncrypted, pbPad); diff --git a/ModernKeePassLib/Serialization/KdbxFile.Read.cs b/ModernKeePassLib/Serialization/KdbxFile.Read.cs index f27ab3d..3900cab 100644 --- a/ModernKeePassLib/Serialization/KdbxFile.Read.cs +++ b/ModernKeePassLib/Serialization/KdbxFile.Read.cs @@ -44,6 +44,9 @@ using ModernKeePassLib.Resources; using ModernKeePassLib.Utility; using Windows.Security.Cryptography.Core; using Windows.Storage.Streams; +using ModernKeePassLib.Collections; +using ModernKeePassLib.Cryptography.KeyDerivation; +using ModernKeePassLib.Security; namespace ModernKeePassLib.Serialization { @@ -53,85 +56,141 @@ namespace ModernKeePassLib.Serialization public sealed partial class KdbxFile { /// - /// Load a KDB file from a file. + /// Load a KDBX file. /// /// File to load. - /// Format specifier. + /// Format. /// Status logger (optional). - public void Load(string strFilePath, KdbxFormat kdbFormat, IStatusLogger slLogger) + public void Load(string strFilePath, KdbxFormat fmt, IStatusLogger slLogger) { IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFilePath); - Load(IOConnection.OpenRead(ioc), kdbFormat, slLogger); + Load(IOConnection.OpenRead(ioc), fmt, slLogger); } /// - /// Load a KDB file from a stream. + /// Load a KDBX file from a stream. /// /// Stream to read the data from. Must contain /// a KDBX stream. - /// Format specifier. + /// Format. /// Status logger (optional). - public void Load(Stream sSource, KdbxFormat kdbFormat, IStatusLogger slLogger) + public void Load(Stream sSource, KdbxFormat fmt, IStatusLogger slLogger) { Debug.Assert(sSource != null); if(sSource == null) throw new ArgumentNullException("sSource"); - m_format = kdbFormat; + if(m_bUsedOnce) + throw new InvalidOperationException("Do not reuse KdbxFile objects!"); + m_bUsedOnce = true; + +#if KDBX_BENCHMARK + Stopwatch swTime = Stopwatch.StartNew(); +#endif + + m_format = fmt; m_slLogger = slLogger; - HashingStreamEx hashedStream = new HashingStreamEx(sSource, false, null); + m_pbsBinaries.Clear(); UTF8Encoding encNoBom = StrUtil.Utf8; + byte[] pbCipherKey = null; + byte[] pbHmacKey64 = null; + + List lStreams = new List(); + lStreams.Add(sSource); + + HashingStreamEx sHashing = new HashingStreamEx(sSource, false, null); + lStreams.Add(sHashing); + try { - BinaryReaderEx br = null; - BinaryReaderEx brDecrypted = null; - Stream readerStream = null; - - if(kdbFormat == KdbxFormat.Default) + Stream sXml; + if(fmt == KdbxFormat.Default) { - br = new BinaryReaderEx(hashedStream, encNoBom, KLRes.FileCorrupted); - ReadHeader(br); + BinaryReaderEx br = new BinaryReaderEx(sHashing, + encNoBom, KLRes.FileCorrupted); + byte[] pbHeader = LoadHeader(br); + m_pbHashOfHeader = CryptoUtil.HashSha256(pbHeader); - Stream sDecrypted = AttachStreamDecryptor(hashedStream); - if((sDecrypted == null) || (sDecrypted == hashedStream)) - throw new SecurityException(KLRes.CryptoStreamFailed); + int cbEncKey, cbEncIV; + ICipherEngine iCipher = GetCipher(out cbEncKey, out cbEncIV); - brDecrypted = new BinaryReaderEx(sDecrypted, encNoBom, KLRes.FileCorrupted); - byte[] pbStoredStartBytes = brDecrypted.ReadBytes(32); + ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64); - if((m_pbStreamStartBytes == null) || (m_pbStreamStartBytes.Length != 32)) - throw new InvalidDataException(); + string strIncomplete = KLRes.FileHeaderCorrupted + " " + + KLRes.FileIncomplete; - for(int iStart = 0; iStart < 32; ++iStart) + Stream sPlain; + if(m_uFileVersion < FileVersion32_4) { - if(pbStoredStartBytes[iStart] != m_pbStreamStartBytes[iStart]) - throw new InvalidCompositeKeyException(); - } + Stream sDecrypted = EncryptStream(sHashing, iCipher, + pbCipherKey, cbEncIV, false); + if((sDecrypted == null) || (sDecrypted == sHashing)) + throw new SecurityException(KLRes.CryptoStreamFailed); + lStreams.Add(sDecrypted); - Stream sHashed = new HashedBlockStream(sDecrypted, false, 0, - !m_bRepairMode); + BinaryReaderEx brDecrypted = new BinaryReaderEx(sDecrypted, + encNoBom, strIncomplete); + byte[] pbStoredStartBytes = brDecrypted.ReadBytes(32); + + if((m_pbStreamStartBytes == null) || (m_pbStreamStartBytes.Length != 32)) + throw new EndOfStreamException(strIncomplete); + if(!MemUtil.ArraysEqual(pbStoredStartBytes, m_pbStreamStartBytes)) + throw new InvalidCompositeKeyException(); + + sPlain = new HashedBlockStream(sDecrypted, false, 0, !m_bRepairMode); + } + else // KDBX >= 4 + { + byte[] pbStoredHash = MemUtil.Read(sHashing, 32); + if((pbStoredHash == null) || (pbStoredHash.Length != 32)) + throw new EndOfStreamException(strIncomplete); + if(!MemUtil.ArraysEqual(m_pbHashOfHeader, pbStoredHash)) + throw new InvalidDataException(KLRes.FileHeaderCorrupted); + + byte[] pbHeaderHmac = ComputeHeaderHmac(pbHeader, pbHmacKey64); + byte[] pbStoredHmac = MemUtil.Read(sHashing, 32); + if((pbStoredHmac == null) || (pbStoredHmac.Length != 32)) + throw new EndOfStreamException(strIncomplete); + if(!MemUtil.ArraysEqual(pbHeaderHmac, pbStoredHmac)) + throw new InvalidCompositeKeyException(); + + HmacBlockStream sBlocks = new HmacBlockStream(sHashing, + false, !m_bRepairMode, pbHmacKey64); + lStreams.Add(sBlocks); + + sPlain = EncryptStream(sBlocks, iCipher, pbCipherKey, + cbEncIV, false); + if((sPlain == null) || (sPlain == sBlocks)) + throw new SecurityException(KLRes.CryptoStreamFailed); + } + lStreams.Add(sPlain); if(m_pwDatabase.Compression == PwCompressionAlgorithm.GZip) - readerStream = new GZipStream(sHashed, CompressionMode.Decompress); - else readerStream = sHashed; - } - else if(kdbFormat == KdbxFormat.PlainXml) - readerStream = hashedStream; - else { Debug.Assert(false); throw new FormatException("KdbFormat"); } + { + sXml = new GZipStream(sPlain, CompressionMode.Decompress); + lStreams.Add(sXml); + } + else sXml = sPlain; - if(kdbFormat != KdbxFormat.PlainXml) // Is an encrypted format + if(m_uFileVersion >= FileVersion32_4) + LoadInnerHeader(sXml); // Binary header before XML + } + else if(fmt == KdbxFormat.PlainXml) + sXml = sHashing; + else { Debug.Assert(false); throw new ArgumentOutOfRangeException("fmt"); } + + if(fmt == KdbxFormat.Default) { - if(m_pbProtectedStreamKey == null) + if(m_pbInnerRandomStreamKey == null) { Debug.Assert(false); - throw new SecurityException("Invalid protected stream key!"); + throw new SecurityException("Invalid inner random stream key!"); } m_randomStream = new CryptoRandomStream(m_craInnerRandomStream, - m_pbProtectedStreamKey); + m_pbInnerRandomStreamKey); } - else m_randomStream = null; // No random stream for plain-text files #if KeePassDebug_WriteXml // FileStream fsOut = new FileStream("Raw.xml", FileMode.Create, @@ -140,7 +199,7 @@ namespace ModernKeePassLib.Serialization // { // while(true) // { - // int b = readerStream.ReadByte(); + // int b = sXml.ReadByte(); // if(b == -1) break; // fsOut.WriteByte((byte)b); // } @@ -149,12 +208,8 @@ namespace ModernKeePassLib.Serialization // fsOut.Close(); #endif - ReadXmlStreamed(readerStream, hashedStream); - // ReadXmlDom(readerStream); - - readerStream.Dispose(); - // GC.KeepAlive(br); - // GC.KeepAlive(brDecrypted); + ReadXmlStreamed(sXml, sHashing); + // ReadXmlDom(sXml); } #if !ModernKeePassLib catch(CryptographicException) // Thrown on invalid padding @@ -162,15 +217,30 @@ namespace ModernKeePassLib.Serialization throw new CryptographicException(KLRes.FileCorrupted); } #endif - finally { CommonCleanUpRead(sSource, hashedStream); } + finally + { + if(pbCipherKey != null) MemUtil.ZeroByteArray(pbCipherKey); + if(pbHmacKey64 != null) MemUtil.ZeroByteArray(pbHmacKey64); + + CommonCleanUpRead(lStreams, sHashing); + } + +#if KDBX_BENCHMARK + swTime.Stop(); + MessageService.ShowInfo("Loading KDBX took " + + swTime.ElapsedMilliseconds.ToString() + " ms."); +#endif } - private void CommonCleanUpRead(Stream sSource, HashingStreamEx hashedStream) + private void CommonCleanUpRead(List lStreams, HashingStreamEx sHashing) { - hashedStream.Dispose(); - m_pbHashOfFileOnDisk = hashedStream.Hash; + CloseStreams(lStreams); - sSource.Dispose(); + Debug.Assert(lStreams.Contains(sHashing)); // sHashing must be closed + m_pbHashOfFileOnDisk = sHashing.Hash; + Debug.Assert(m_pbHashOfFileOnDisk != null); + + CleanUpInnerRandomStream(); // Reset memory protection settings (to always use reasonable // defaults) @@ -183,11 +253,21 @@ namespace ModernKeePassLib.Serialization // the history maintenance settings) m_pwDatabase.MaintainBackups(); // Don't mark database as modified + // Expand the root group, such that in case the user accidently + // collapses the root group he can simply reopen the database + PwGroup pgRoot = m_pwDatabase.RootGroup; + if(pgRoot != null) pgRoot.IsExpanded = true; + else { Debug.Assert(false); } + m_pbHashOfHeader = null; } - private void ReadHeader(BinaryReaderEx br) + private byte[] LoadHeader(BinaryReaderEx br) { + string strPrevExcpText = br.ReadExceptionText; + br.ReadExceptionText = KLRes.FileHeaderCorrupted + " " + + KLRes.FileIncompleteExpc; + MemoryStream msHeader = new MemoryStream(); Debug.Assert(br.CopyDataTo == null); br.CopyDataTo = msHeader; @@ -210,29 +290,21 @@ namespace ModernKeePassLib.Serialization uint uVersion = MemUtil.BytesToUInt32(pb); if((uVersion & FileVersionCriticalMask) > (FileVersion32 & FileVersionCriticalMask)) throw new FormatException(KLRes.FileVersionUnsupported + - Environment.NewLine + Environment.NewLine + KLRes.FileNewVerReq); + Environment.NewLine + KLRes.FileNewVerReq); + m_uFileVersion = uVersion; while(true) { - if(ReadHeaderField(br) == false) - break; + if(!ReadHeaderField(br)) break; } br.CopyDataTo = null; byte[] pbHeader = msHeader.ToArray(); msHeader.Dispose(); -#if ModernKeePassLib - /*var sha256 = WinRTCrypto.HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha256); - m_pbHashOfHeader = sha256.HashData(pbHeader);*/ - var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256); - var buffer = sha256.HashData(CryptographicBuffer.CreateFromByteArray(pbHeader)); - CryptographicBuffer.CopyToByteArray(buffer, out m_pbHashOfHeader); -#else - SHA256Managed sha256 = new SHA256Managed(); - m_pbHashOfHeader = sha256.ComputeHash(pbHeader); -#endif - } + br.ReadExceptionText = strPrevExcpText; + return pbHeader; + } private bool ReadHeaderField(BinaryReaderEx brSource) { @@ -240,18 +312,16 @@ namespace ModernKeePassLib.Serialization if(brSource == null) throw new ArgumentNullException("brSource"); byte btFieldID = brSource.ReadByte(); - ushort uSize = MemUtil.BytesToUInt16(brSource.ReadBytes(2)); - byte[] pbData = null; - if(uSize > 0) - { - string strPrevExcpText = brSource.ReadExceptionText; - brSource.ReadExceptionText = KLRes.FileHeaderEndEarly; + int cbSize; + Debug.Assert(m_uFileVersion > 0); + if(m_uFileVersion < FileVersion32_4) + cbSize = (int)MemUtil.BytesToUInt16(brSource.ReadBytes(2)); + else cbSize = MemUtil.BytesToInt32(brSource.ReadBytes(4)); + if(cbSize < 0) throw new FormatException(KLRes.FileCorrupted); - pbData = brSource.ReadBytes(uSize); - - brSource.ReadExceptionText = strPrevExcpText; - } + byte[] pbData = MemUtil.EmptyByteArray; + if(cbSize > 0) pbData = brSource.ReadBytes(cbSize); bool bResult = true; KdbxHeaderFieldID kdbID = (KdbxHeaderFieldID)btFieldID; @@ -274,32 +344,63 @@ namespace ModernKeePassLib.Serialization CryptoRandom.Instance.AddEntropy(pbData); break; + // Obsolete; for backward compatibility only case KdbxHeaderFieldID.TransformSeed: - m_pbTransformSeed = pbData; + Debug.Assert(m_uFileVersion < FileVersion32_4); + + AesKdf kdfS = new AesKdf(); + if(!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfS.Uuid)) + m_pwDatabase.KdfParameters = kdfS.GetDefaultParameters(); + + // m_pbTransformSeed = pbData; + m_pwDatabase.KdfParameters.SetByteArray(AesKdf.ParamSeed, pbData); + CryptoRandom.Instance.AddEntropy(pbData); break; + // Obsolete; for backward compatibility only case KdbxHeaderFieldID.TransformRounds: - m_pwDatabase.KeyEncryptionRounds = MemUtil.BytesToUInt64(pbData); + Debug.Assert(m_uFileVersion < FileVersion32_4); + + AesKdf kdfR = new AesKdf(); + if(!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfR.Uuid)) + m_pwDatabase.KdfParameters = kdfR.GetDefaultParameters(); + + // m_pwDatabase.KeyEncryptionRounds = MemUtil.BytesToUInt64(pbData); + m_pwDatabase.KdfParameters.SetUInt64(AesKdf.ParamRounds, + MemUtil.BytesToUInt64(pbData)); break; case KdbxHeaderFieldID.EncryptionIV: m_pbEncryptionIV = pbData; break; - case KdbxHeaderFieldID.ProtectedStreamKey: - m_pbProtectedStreamKey = pbData; + case KdbxHeaderFieldID.InnerRandomStreamKey: + Debug.Assert(m_uFileVersion < FileVersion32_4); + Debug.Assert(m_pbInnerRandomStreamKey == null); + m_pbInnerRandomStreamKey = pbData; CryptoRandom.Instance.AddEntropy(pbData); break; case KdbxHeaderFieldID.StreamStartBytes: + Debug.Assert(m_uFileVersion < FileVersion32_4); m_pbStreamStartBytes = pbData; break; case KdbxHeaderFieldID.InnerRandomStreamID: + Debug.Assert(m_uFileVersion < FileVersion32_4); SetInnerRandomStreamID(pbData); break; + case KdbxHeaderFieldID.KdfParameters: + m_pwDatabase.KdfParameters = KdfParameters.DeserializeExt(pbData); + break; + + case KdbxHeaderFieldID.PublicCustomData: + Debug.Assert(m_pwDatabase.PublicCustomData.Count == 0); + m_pwDatabase.PublicCustomData = VariantDictionary.Deserialize(pbData); + break; + default: Debug.Assert(false); if(m_slLogger != null) @@ -311,9 +412,71 @@ namespace ModernKeePassLib.Serialization return bResult; } + private void LoadInnerHeader(Stream s) + { + BinaryReaderEx br = new BinaryReaderEx(s, StrUtil.Utf8, + KLRes.FileCorrupted + " " + KLRes.FileIncompleteExpc); + + while(true) + { + if(!ReadInnerHeaderField(br)) break; + } + } + + private bool ReadInnerHeaderField(BinaryReaderEx br) + { + Debug.Assert(br != null); + if(br == null) throw new ArgumentNullException("br"); + + byte btFieldID = br.ReadByte(); + + int cbSize = MemUtil.BytesToInt32(br.ReadBytes(4)); + if(cbSize < 0) throw new FormatException(KLRes.FileCorrupted); + + byte[] pbData = MemUtil.EmptyByteArray; + if(cbSize > 0) pbData = br.ReadBytes(cbSize); + + bool bResult = true; + KdbxInnerHeaderFieldID kdbID = (KdbxInnerHeaderFieldID)btFieldID; + switch(kdbID) + { + case KdbxInnerHeaderFieldID.EndOfHeader: + bResult = false; // Returning false indicates end of header + break; + + case KdbxInnerHeaderFieldID.InnerRandomStreamID: + SetInnerRandomStreamID(pbData); + break; + + case KdbxInnerHeaderFieldID.InnerRandomStreamKey: + Debug.Assert(m_pbInnerRandomStreamKey == null); + m_pbInnerRandomStreamKey = pbData; + CryptoRandom.Instance.AddEntropy(pbData); + break; + + case KdbxInnerHeaderFieldID.Binary: + if(pbData.Length < 1) throw new FormatException(); + KdbxBinaryFlags f = (KdbxBinaryFlags)pbData[0]; + bool bProt = ((f & KdbxBinaryFlags.Protected) != KdbxBinaryFlags.None); + + ProtectedBinary pb = new ProtectedBinary(bProt, pbData, + 1, pbData.Length - 1); + m_pbsBinaries.Add(pb); + + if(bProt) MemUtil.ZeroByteArray(pbData); + break; + + default: + Debug.Assert(false); + break; + } + + return bResult; + } + private void SetCipher(byte[] pbID) { - if((pbID == null) || (pbID.Length != 16)) + if((pbID == null) || (pbID.Length != (int)PwUuid.UuidSize)) throw new FormatException(KLRes.FileUnknownCipher); m_pwDatabase.DataCipherUuid = new PwUuid(pbID); @@ -337,57 +500,34 @@ namespace ModernKeePassLib.Serialization m_craInnerRandomStream = (CrsAlgorithm)uID; } - private Stream AttachStreamDecryptor(Stream s) + [Obsolete] + public static List ReadEntries(Stream msData) { - MemoryStream ms = new MemoryStream(); - - Debug.Assert(m_pbMasterSeed.Length == 32); - if(m_pbMasterSeed.Length != 32) - throw new FormatException(KLRes.MasterSeedLengthInvalid); - ms.Write(m_pbMasterSeed, 0, 32); - - byte[] pKey32 = m_pwDatabase.MasterKey.GenerateKey32(m_pbTransformSeed, - m_pwDatabase.KeyEncryptionRounds).ReadData(); - if((pKey32 == null) || (pKey32.Length != 32)) - throw new SecurityException(KLRes.InvalidCompositeKey); - ms.Write(pKey32, 0, 32); - -#if ModernKeePassLib - /*var sha256 = WinRTCrypto.HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha256); - var aesKey = sha256.HashData(ms.ToArray());*/ - var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256); - var buffer = sha256.HashData(CryptographicBuffer.CreateFromByteArray(ms.ToArray())); - byte[] aesKey; - CryptographicBuffer.CopyToByteArray(buffer, out aesKey); -#else - SHA256Managed sha256 = new SHA256Managed(); - byte[] aesKey = sha256.ComputeHash(ms.ToArray()); -#endif - - ms.Dispose(); - Array.Clear(pKey32, 0, 32); - - if((aesKey == null) || (aesKey.Length != 32)) - throw new SecurityException(KLRes.FinalKeyCreationFailed); - - ICipherEngine iEngine = CipherPool.GlobalPool.GetCipher(m_pwDatabase.DataCipherUuid); - if(iEngine == null) throw new SecurityException(KLRes.FileUnknownCipher); - return iEngine.DecryptStream(s, aesKey, m_pbEncryptionIV); + return ReadEntries(msData, null, false); } [Obsolete] - public static List ReadEntries(PwDatabase pwDatabase, Stream msData) + public static List ReadEntries(PwDatabase pdContext, Stream msData) { - return ReadEntries(msData); + return ReadEntries(msData, pdContext, true); } /// /// Read entries from a stream. /// /// Input stream to read the entries from. - /// Extracted entries. - public static List ReadEntries(Stream msData) + /// Context database (e.g. for storing icons). + /// If true, custom icons required by + /// the loaded entries are copied to the context database. + /// Loaded entries. + public static List ReadEntries(Stream msData, PwDatabase pdContext, + bool bCopyIcons) { + List lEntries = new List(); + + if(msData == null) { Debug.Assert(false); return lEntries; } + // pdContext may be null + /* KdbxFile f = new KdbxFile(pwDatabase); f.m_format = KdbxFormat.PlainXml; @@ -417,17 +557,37 @@ namespace ModernKeePassLib.Serialization return vEntries; */ PwDatabase pd = new PwDatabase(); + pd.New(new IOConnectionInfo(), new CompositeKey()); + KdbxFile f = new KdbxFile(pd); f.Load(msData, KdbxFormat.PlainXml, null); - List vEntries = new List(); foreach(PwEntry pe in pd.RootGroup.Entries) { pe.SetUuid(new PwUuid(true), true); - vEntries.Add(pe); + lEntries.Add(pe); + + if(bCopyIcons && (pdContext != null)) + { + PwUuid pu = pe.CustomIconUuid; + if(!pu.Equals(PwUuid.Zero)) + { + int iSrc = pd.GetCustomIconIndex(pu); + int iDst = pdContext.GetCustomIconIndex(pu); + + if(iSrc < 0) { Debug.Assert(false); } + else if(iDst < 0) + { + pdContext.CustomIcons.Add(pd.CustomIcons[iSrc]); + + pdContext.Modified = true; + pdContext.UINeedsIconUpdate = true; + } + } + } } - return vEntries; + return lEntries; } } } diff --git a/ModernKeePassLib/Serialization/KdbxFile.Write.cs b/ModernKeePassLib/Serialization/KdbxFile.Write.cs index afc496b..5cf0bd8 100644 --- a/ModernKeePassLib/Serialization/KdbxFile.Write.cs +++ b/ModernKeePassLib/Serialization/KdbxFile.Write.cs @@ -49,6 +49,7 @@ using ModernKeePassLib.Security; using ModernKeePassLib.Utility; using Windows.Security.Cryptography.Core; using Windows.Storage.Streams; +using ModernKeePassLib.Cryptography.KeyDerivation; namespace ModernKeePassLib.Serialization { @@ -57,7 +58,7 @@ namespace ModernKeePassLib.Serialization /// public sealed partial class KdbxFile { - // public void Save(string strFile, PwGroup pgDataSource, KdbxFormat format, + // public void Save(string strFile, PwGroup pgDataSource, KdbxFormat fmt, // IStatusLogger slLogger) // { // bool bMadeUnhidden = UrlUtil.UnhideFile(strFile); @@ -75,212 +76,324 @@ namespace ModernKeePassLib.Serialization /// Group containing all groups and /// entries to write. If null, the complete database will /// be written. - /// Format of the file to create. + /// Format of the file to create. /// Logger that recieves status information. - public void Save(Stream sSaveTo, PwGroup pgDataSource, KdbxFormat format, + public void Save(Stream sSaveTo, PwGroup pgDataSource, KdbxFormat fmt, IStatusLogger slLogger) { Debug.Assert(sSaveTo != null); if(sSaveTo == null) throw new ArgumentNullException("sSaveTo"); - m_format = format; + if(m_bUsedOnce) + throw new InvalidOperationException("Do not reuse KdbxFile objects!"); + m_bUsedOnce = true; + + m_format = fmt; m_slLogger = slLogger; - HashingStreamEx hashedStream = new HashingStreamEx(sSaveTo, true, null); - + PwGroup pgRoot = (pgDataSource ?? m_pwDatabase.RootGroup); UTF8Encoding encNoBom = StrUtil.Utf8; CryptoRandom cr = CryptoRandom.Instance; + byte[] pbCipherKey = null; + byte[] pbHmacKey64 = null; + + m_pbsBinaries.Clear(); + m_pbsBinaries.AddFrom(pgRoot); + + List lStreams = new List(); + lStreams.Add(sSaveTo); + + HashingStreamEx sHashing = new HashingStreamEx(sSaveTo, true, null); + lStreams.Add(sHashing); try { + m_uFileVersion = GetMinKdbxVersion(); + + int cbEncKey, cbEncIV; + ICipherEngine iCipher = GetCipher(out cbEncKey, out cbEncIV); + m_pbMasterSeed = cr.GetRandomBytes(32); - m_pbTransformSeed = cr.GetRandomBytes(32); - m_pbEncryptionIV = cr.GetRandomBytes(16); + m_pbEncryptionIV = cr.GetRandomBytes((uint)cbEncIV); - m_pbProtectedStreamKey = cr.GetRandomBytes(32); - m_craInnerRandomStream = CrsAlgorithm.Salsa20; - m_randomStream = new CryptoRandomStream(m_craInnerRandomStream, - m_pbProtectedStreamKey); + // m_pbTransformSeed = cr.GetRandomBytes(32); + PwUuid puKdf = m_pwDatabase.KdfParameters.KdfUuid; + KdfEngine kdf = KdfPool.Get(puKdf); + if(kdf == null) + throw new Exception(KLRes.UnknownKdf + Environment.NewLine + + // KLRes.FileNewVerOrPlgReq + Environment.NewLine + + "UUID: " + puKdf.ToHexString() + "."); + kdf.Randomize(m_pwDatabase.KdfParameters); - m_pbStreamStartBytes = cr.GetRandomBytes(32); - - Stream writerStream; if(m_format == KdbxFormat.Default) { - WriteHeader(hashedStream); // Also flushes the stream + if(m_uFileVersion < FileVersion32_4) + { + m_craInnerRandomStream = CrsAlgorithm.Salsa20; + m_pbInnerRandomStreamKey = cr.GetRandomBytes(32); + } + else // KDBX >= 4 + { + m_craInnerRandomStream = CrsAlgorithm.ChaCha20; + m_pbInnerRandomStreamKey = cr.GetRandomBytes(64); + } - Stream sEncrypted = AttachStreamEncryptor(hashedStream); - if((sEncrypted == null) || (sEncrypted == hashedStream)) - throw new SecurityException(KLRes.CryptoStreamFailed); + m_randomStream = new CryptoRandomStream(m_craInnerRandomStream, + m_pbInnerRandomStreamKey); + } - sEncrypted.Write(m_pbStreamStartBytes, 0, m_pbStreamStartBytes.Length); + if(m_uFileVersion < FileVersion32_4) + m_pbStreamStartBytes = cr.GetRandomBytes(32); - Stream sHashed = new HashedBlockStream(sEncrypted, true); + Stream sXml; + if(m_format == KdbxFormat.Default) + { + byte[] pbHeader = GenerateHeader(); + m_pbHashOfHeader = CryptoUtil.HashSha256(pbHeader); + + MemUtil.Write(sHashing, pbHeader); + sHashing.Flush(); + + ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64); + + Stream sPlain; + if(m_uFileVersion < FileVersion32_4) + { + Stream sEncrypted = EncryptStream(sHashing, iCipher, + pbCipherKey, cbEncIV, true); + if((sEncrypted == null) || (sEncrypted == sHashing)) + throw new SecurityException(KLRes.CryptoStreamFailed); + lStreams.Add(sEncrypted); + + MemUtil.Write(sEncrypted, m_pbStreamStartBytes); + + sPlain = new HashedBlockStream(sEncrypted, true); + } + else // KDBX >= 4 + { + // For integrity checking (without knowing the master key) + MemUtil.Write(sHashing, m_pbHashOfHeader); + + byte[] pbHeaderHmac = ComputeHeaderHmac(pbHeader, pbHmacKey64); + MemUtil.Write(sHashing, pbHeaderHmac); + + Stream sBlocks = new HmacBlockStream(sHashing, true, + true, pbHmacKey64); + lStreams.Add(sBlocks); + + sPlain = EncryptStream(sBlocks, iCipher, pbCipherKey, + cbEncIV, true); + if((sPlain == null) || (sPlain == sBlocks)) + throw new SecurityException(KLRes.CryptoStreamFailed); + } + lStreams.Add(sPlain); if(m_pwDatabase.Compression == PwCompressionAlgorithm.GZip) - writerStream = new GZipStream(sHashed, CompressionMode.Compress); - else - writerStream = sHashed; + { + sXml = new GZipStream(sPlain, CompressionMode.Compress); + lStreams.Add(sXml); + } + else sXml = sPlain; + + if(m_uFileVersion >= FileVersion32_4) + WriteInnerHeader(sXml); // Binary header before XML } else if(m_format == KdbxFormat.PlainXml) - writerStream = hashedStream; - else { Debug.Assert(false); throw new FormatException("KdbFormat"); } + sXml = sHashing; + else + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("fmt"); + } -#if ModernKeePassLib - var settings = new XmlWriterSettings() { - Encoding = encNoBom, - Indent = true, - IndentChars = "\t", - NewLineChars = "\r\n", - }; - m_xmlWriter = XmlWriter.Create(writerStream, settings); +#if ModernKeePassLib || KeePassUAP + XmlWriterSettings xws = new XmlWriterSettings(); + xws.Encoding = encNoBom; + xws.Indent = true; + xws.IndentChars = "\t"; + xws.NewLineOnAttributes = false; + + XmlWriter xw = XmlWriter.Create(sXml, xws); #else - m_xmlWriter = new XmlTextWriter(writerStream, encNoBom); + XmlTextWriter xw = new XmlTextWriter(sXml, encNoBom); + + xw.Formatting = Formatting.Indented; + xw.IndentChar = '\t'; + xw.Indentation = 1; #endif - WriteDocument(pgDataSource); + m_xmlWriter = xw; + + WriteDocument(pgRoot); m_xmlWriter.Flush(); m_xmlWriter.Dispose(); - writerStream.Dispose(); } - finally { CommonCleanUpWrite(sSaveTo, hashedStream); } + finally + { + if(pbCipherKey != null) MemUtil.ZeroByteArray(pbCipherKey); + if(pbHmacKey64 != null) MemUtil.ZeroByteArray(pbHmacKey64); + + CommonCleanUpWrite(lStreams, sHashing); + } } - private void CommonCleanUpWrite(Stream sSaveTo, HashingStreamEx hashedStream) + private void CommonCleanUpWrite(List lStreams, HashingStreamEx sHashing) { - hashedStream.Dispose(); - m_pbHashOfFileOnDisk = hashedStream.Hash; + CloseStreams(lStreams); - sSaveTo.Dispose(); + Debug.Assert(lStreams.Contains(sHashing)); // sHashing must be closed + m_pbHashOfFileOnDisk = sHashing.Hash; + Debug.Assert(m_pbHashOfFileOnDisk != null); + + CleanUpInnerRandomStream(); m_xmlWriter = null; m_pbHashOfHeader = null; } - private void WriteHeader(Stream s) + private byte[] GenerateHeader() { - using (var ms = new MemoryStream()) - { + byte[] pbHeader; + using(MemoryStream ms = new MemoryStream()) + { + MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature1)); + MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature2)); + MemUtil.Write(ms, MemUtil.UInt32ToBytes(m_uFileVersion)); - MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature1)); - MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature2)); - MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileVersion32)); + WriteHeaderField(ms, KdbxHeaderFieldID.CipherID, + m_pwDatabase.DataCipherUuid.UuidBytes); - WriteHeaderField(ms, KdbxHeaderFieldID.CipherID, - m_pwDatabase.DataCipherUuid.UuidBytes); + int nCprID = (int)m_pwDatabase.Compression; + WriteHeaderField(ms, KdbxHeaderFieldID.CompressionFlags, + MemUtil.UInt32ToBytes((uint)nCprID)); - int nCprID = (int) m_pwDatabase.Compression; - WriteHeaderField(ms, KdbxHeaderFieldID.CompressionFlags, - MemUtil.UInt32ToBytes((uint) nCprID)); + WriteHeaderField(ms, KdbxHeaderFieldID.MasterSeed, m_pbMasterSeed); - WriteHeaderField(ms, KdbxHeaderFieldID.MasterSeed, m_pbMasterSeed); - WriteHeaderField(ms, KdbxHeaderFieldID.TransformSeed, m_pbTransformSeed); - WriteHeaderField(ms, KdbxHeaderFieldID.TransformRounds, - MemUtil.UInt64ToBytes(m_pwDatabase.KeyEncryptionRounds)); - WriteHeaderField(ms, KdbxHeaderFieldID.EncryptionIV, m_pbEncryptionIV); - WriteHeaderField(ms, KdbxHeaderFieldID.ProtectedStreamKey, m_pbProtectedStreamKey); - WriteHeaderField(ms, KdbxHeaderFieldID.StreamStartBytes, m_pbStreamStartBytes); + if(m_uFileVersion < FileVersion32_4) + { + Debug.Assert(m_pwDatabase.KdfParameters.KdfUuid.Equals( + (new AesKdf()).Uuid)); + WriteHeaderField(ms, KdbxHeaderFieldID.TransformSeed, + m_pwDatabase.KdfParameters.GetByteArray(AesKdf.ParamSeed)); + WriteHeaderField(ms, KdbxHeaderFieldID.TransformRounds, + MemUtil.UInt64ToBytes(m_pwDatabase.KdfParameters.GetUInt64( + AesKdf.ParamRounds, PwDefs.DefaultKeyEncryptionRounds))); + } + else + WriteHeaderField(ms, KdbxHeaderFieldID.KdfParameters, + KdfParameters.SerializeExt(m_pwDatabase.KdfParameters)); - int nIrsID = (int) m_craInnerRandomStream; - WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamID, - MemUtil.UInt32ToBytes((uint) nIrsID)); + if(m_pbEncryptionIV.Length > 0) + WriteHeaderField(ms, KdbxHeaderFieldID.EncryptionIV, m_pbEncryptionIV); - WriteHeaderField(ms, KdbxHeaderFieldID.EndOfHeader, new byte[] - { - (byte) '\r', (byte) '\n', (byte) '\r', (byte) '\n' - }); + if(m_uFileVersion < FileVersion32_4) + { + WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamKey, + m_pbInnerRandomStreamKey); - byte[] pbHeader = ms.ToArray(); + WriteHeaderField(ms, KdbxHeaderFieldID.StreamStartBytes, + m_pbStreamStartBytes); -#if ModernKeePassLib - /*var sha256 = WinRTCrypto.HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha256); - m_pbHashOfHeader = sha256.HashData(pbHeader);*/ - var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256); - var buffer = sha256.HashData(CryptographicBuffer.CreateFromByteArray(pbHeader)); - CryptographicBuffer.CopyToByteArray(buffer, out m_pbHashOfHeader); -#else - SHA256Managed sha256 = new SHA256Managed(); - m_pbHashOfHeader = sha256.ComputeHash(pbHeader); -#endif + int nIrsID = (int)m_craInnerRandomStream; + WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamID, + MemUtil.Int32ToBytes(nIrsID)); + } - s.Write(pbHeader, 0, pbHeader.Length); - s.Flush(); - } + // Write public custom data only when there is at least one item, + // because KDBX 3.1 didn't support this field yet + if(m_pwDatabase.PublicCustomData.Count > 0) + WriteHeaderField(ms, KdbxHeaderFieldID.PublicCustomData, + VariantDictionary.Serialize(m_pwDatabase.PublicCustomData)); + + WriteHeaderField(ms, KdbxHeaderFieldID.EndOfHeader, new byte[] { + (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' }); + + pbHeader = ms.ToArray(); + } + + return pbHeader; } - private static void WriteHeaderField(Stream s, KdbxHeaderFieldID kdbID, + private void WriteHeaderField(Stream s, KdbxHeaderFieldID kdbID, byte[] pbData) { s.WriteByte((byte)kdbID); - if(pbData != null) + byte[] pb = (pbData ?? MemUtil.EmptyByteArray); + int cb = pb.Length; + if(cb < 0) { Debug.Assert(false); throw new OutOfMemoryException(); } + + Debug.Assert(m_uFileVersion > 0); + if(m_uFileVersion < FileVersion32_4) { - ushort uLength = (ushort)pbData.Length; - MemUtil.Write(s, MemUtil.UInt16ToBytes(uLength)); + if(cb > (int)ushort.MaxValue) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("pbData"); + } - if(uLength > 0) s.Write(pbData, 0, pbData.Length); + MemUtil.Write(s, MemUtil.UInt16ToBytes((ushort)cb)); } - else MemUtil.Write(s, MemUtil.UInt16ToBytes((ushort)0)); + else MemUtil.Write(s, MemUtil.Int32ToBytes(cb)); + + MemUtil.Write(s, pb); } - private Stream AttachStreamEncryptor(Stream s) + private void WriteInnerHeader(Stream s) { - using (var ms = new MemoryStream()) - { - Debug.Assert(m_pbMasterSeed != null); - Debug.Assert(m_pbMasterSeed.Length == 32); - ms.Write(m_pbMasterSeed, 0, 32); + int nIrsID = (int)m_craInnerRandomStream; + WriteInnerHeaderField(s, KdbxInnerHeaderFieldID.InnerRandomStreamID, + MemUtil.Int32ToBytes(nIrsID), null); - Debug.Assert(m_pwDatabase != null); - Debug.Assert(m_pwDatabase.MasterKey != null); - ProtectedBinary pbinKey = m_pwDatabase.MasterKey.GenerateKey32( - m_pbTransformSeed, m_pwDatabase.KeyEncryptionRounds); - Debug.Assert(pbinKey != null); - if (pbinKey == null) - throw new SecurityException(KLRes.InvalidCompositeKey); - byte[] pKey32 = pbinKey.ReadData(); - if ((pKey32 == null) || (pKey32.Length != 32)) - throw new SecurityException(KLRes.InvalidCompositeKey); - ms.Write(pKey32, 0, 32); + WriteInnerHeaderField(s, KdbxInnerHeaderFieldID.InnerRandomStreamKey, + m_pbInnerRandomStreamKey, null); -#if ModernKeePassLib - /*var sha256 = WinRTCrypto.HashAlgorithmProvider.OpenAlgorithm(HashAlgorithm.Sha256); - var aesKey = sha256.HashData(ms.ToArray());*/ - var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256); - var buffer = sha256.HashData(CryptographicBuffer.CreateFromByteArray(ms.ToArray())); - byte[] aesKey; - CryptographicBuffer.CopyToByteArray(buffer, out aesKey); -#else - SHA256Managed sha256 = new SHA256Managed(); - byte[] aesKey = sha256.ComputeHash(ms.ToArray()); -#endif - Array.Clear(pKey32, 0, 32); + ProtectedBinary[] vBin = m_pbsBinaries.ToArray(); + for(int i = 0; i < vBin.Length; ++i) + { + ProtectedBinary pb = vBin[i]; + if(pb == null) throw new InvalidOperationException(); - Debug.Assert(CipherPool.GlobalPool != null); - ICipherEngine iEngine = CipherPool.GlobalPool.GetCipher(m_pwDatabase.DataCipherUuid); - if (iEngine == null) throw new SecurityException(KLRes.FileUnknownCipher); - return iEngine.EncryptStream(s, aesKey, m_pbEncryptionIV); - } + KdbxBinaryFlags f = KdbxBinaryFlags.None; + if(pb.IsProtected) f |= KdbxBinaryFlags.Protected; + + byte[] pbFlags = new byte[1] { (byte)f }; + byte[] pbData = pb.ReadData(); + + WriteInnerHeaderField(s, KdbxInnerHeaderFieldID.Binary, + pbFlags, pbData); + + if(pb.IsProtected) MemUtil.ZeroByteArray(pbData); + } + + WriteInnerHeaderField(s, KdbxInnerHeaderFieldID.EndOfHeader, + null, null); } - private void WriteDocument(PwGroup pgDataSource) + private void WriteInnerHeaderField(Stream s, KdbxInnerHeaderFieldID kdbID, + byte[] pbData1, byte[] pbData2) + { + s.WriteByte((byte)kdbID); + + byte[] pb1 = (pbData1 ?? MemUtil.EmptyByteArray); + byte[] pb2 = (pbData2 ?? MemUtil.EmptyByteArray); + + int cb = pb1.Length + pb2.Length; + if(cb < 0) { Debug.Assert(false); throw new OutOfMemoryException(); } + + MemUtil.Write(s, MemUtil.Int32ToBytes(cb)); + MemUtil.Write(s, pb1); + MemUtil.Write(s, pb2); + } + + private void WriteDocument(PwGroup pgRoot) { Debug.Assert(m_xmlWriter != null); if(m_xmlWriter == null) throw new InvalidOperationException(); - PwGroup pgRoot = (pgDataSource ?? m_pwDatabase.RootGroup); - uint uNumGroups, uNumEntries, uCurEntry = 0; pgRoot.GetCounts(true, out uNumGroups, out uNumEntries); - BinPoolBuild(pgRoot); - -#if !ModernKeePassLib - m_xmlWriter.Formatting = Formatting.Indented; - m_xmlWriter.IndentChar = '\t'; - m_xmlWriter.Indentation = 1; -#endif - m_xmlWriter.WriteStartDocument(true); m_xmlWriter.WriteStartElement(ElemDocNode); @@ -352,12 +465,15 @@ namespace ModernKeePassLib.Serialization { m_xmlWriter.WriteStartElement(ElemMeta); - WriteObject(ElemGenerator, PwDatabase.LocalizedAppName, false); // Generator name + WriteObject(ElemGenerator, PwDatabase.LocalizedAppName, false); - if(m_pbHashOfHeader != null) + if((m_pbHashOfHeader != null) && (m_uFileVersion < FileVersion32_4)) WriteObject(ElemHeaderHash, Convert.ToBase64String( m_pbHashOfHeader), false); + if(m_uFileVersion >= FileVersion32_4) + WriteObject(ElemSettingsChanged, m_pwDatabase.SettingsChanged); + WriteObject(ElemDbName, m_pwDatabase.Name, true); WriteObject(ElemDbNameChanged, m_pwDatabase.NameChanged); WriteObject(ElemDbDesc, m_pwDatabase.Description, true); @@ -369,6 +485,8 @@ namespace ModernKeePassLib.Serialization WriteObject(ElemDbKeyChanged, m_pwDatabase.MasterKeyChanged); WriteObject(ElemDbKeyChangeRec, m_pwDatabase.MasterKeyChangeRec); WriteObject(ElemDbKeyChangeForce, m_pwDatabase.MasterKeyChangeForce); + if(m_pwDatabase.MasterKeyChangeForceOnce) + WriteObject(ElemDbKeyChangeForceOnce, true); WriteList(ElemMemoryProt, m_pwDatabase.MemoryProtection); @@ -385,7 +503,9 @@ namespace ModernKeePassLib.Serialization WriteObject(ElemLastSelectedGroup, m_pwDatabase.LastSelectedGroup); WriteObject(ElemLastTopVisibleGroup, m_pwDatabase.LastTopVisibleGroup); - WriteBinPool(); + if((m_format != KdbxFormat.Default) || (m_uFileVersion < FileVersion32_4)) + WriteBinPool(); + WriteList(ElemCustomData, m_pwDatabase.CustomData); m_xmlWriter.WriteEndElement(); @@ -408,6 +528,9 @@ namespace ModernKeePassLib.Serialization WriteObject(ElemEnableAutoType, StrUtil.BoolToStringEx(pg.EnableAutoType), false); WriteObject(ElemEnableSearching, StrUtil.BoolToStringEx(pg.EnableSearching), false); WriteObject(ElemLastTopVisibleEntry, pg.LastTopVisibleEntry); + + if(pg.CustomData.Count > 0) + WriteList(ElemCustomData, pg.CustomData); } private void EndGroup() @@ -423,7 +546,7 @@ namespace ModernKeePassLib.Serialization WriteObject(ElemUuid, pe.Uuid); WriteObject(ElemIcon, (int)pe.IconId); - + if(!pe.CustomIconUuid.Equals(PwUuid.Zero)) WriteObject(ElemCustomIconID, pe.CustomIconUuid); @@ -438,6 +561,9 @@ namespace ModernKeePassLib.Serialization WriteList(pe.Binaries); WriteList(ElemAutoType, pe.AutoType); + if(pe.CustomData.Count > 0) + WriteList(ElemCustomData, pe.CustomData); + if(!bIsHistory) WriteList(ElemHistory, pe.History, true); else { Debug.Assert(pe.History.UCount == 0); } @@ -647,8 +773,28 @@ namespace ModernKeePassLib.Serialization private void WriteObject(string name, DateTime value) { Debug.Assert(name != null); + Debug.Assert(value.Kind == DateTimeKind.Utc); - WriteObject(name, TimeUtil.SerializeUtc(value), false); + // Cf. ReadTime + if((m_format == KdbxFormat.Default) && (m_uFileVersion >= FileVersion32_4)) + { + DateTime dt = TimeUtil.ToUtc(value, false); + + // DateTime dtBase = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc); + // dt -= new TimeSpan(dtBase.Ticks); + + // WriteObject(name, dt.ToBinary()); + + // dt = TimeUtil.RoundToMultOf2PowLess1s(dt); + // long lBin = dt.ToBinary(); + + long lSec = dt.Ticks / TimeSpan.TicksPerSecond; + // WriteObject(name, lSec); + + byte[] pb = MemUtil.Int64ToBytes(lSec); + WriteObject(name, Convert.ToBase64String(pb), false); + } + else WriteObject(name, TimeUtil.SerializeUtc(value), false); } private void WriteObject(string name, string strKeyName, @@ -695,7 +841,7 @@ namespace ModernKeePassLib.Serialization bProtected = m_pwDatabase.MemoryProtection.ProtectNotes; } - if(bProtected && (m_format != KdbxFormat.PlainXml)) + if(bProtected && (m_format == KdbxFormat.Default)) { m_xmlWriter.WriteAttributeString(AttrProtected, ValTrue); @@ -770,11 +916,15 @@ namespace ModernKeePassLib.Serialization m_xmlWriter.WriteEndElement(); m_xmlWriter.WriteStartElement(ElemValue); - string strRef = (bAllowRef ? BinPoolFind(value) : null); - if(strRef != null) + string strRef = null; + if(bAllowRef) { - m_xmlWriter.WriteAttributeString(AttrRef, strRef); + int iRef = m_pbsBinaries.Find(value); + if(iRef >= 0) strRef = iRef.ToString(NumberFormatInfo.InvariantInfo); + else { Debug.Assert(false); } } + if(strRef != null) + m_xmlWriter.WriteAttributeString(AttrRef, strRef); else SubWriteValue(value); m_xmlWriter.WriteEndElement(); // ElemValue @@ -783,7 +933,7 @@ namespace ModernKeePassLib.Serialization private void SubWriteValue(ProtectedBinary value) { - if(value.IsProtected && (m_format != KdbxFormat.PlainXml)) + if(value.IsProtected && (m_format == KdbxFormat.Default)) { m_xmlWriter.WriteAttributeString(AttrProtected, ValTrue); @@ -793,18 +943,26 @@ namespace ModernKeePassLib.Serialization } else { - if(m_pwDatabase.Compression == PwCompressionAlgorithm.GZip) + if(m_pwDatabase.Compression != PwCompressionAlgorithm.None) { m_xmlWriter.WriteAttributeString(AttrCompressed, ValTrue); byte[] pbRaw = value.ReadData(); byte[] pbCmp = MemUtil.Compress(pbRaw); m_xmlWriter.WriteBase64(pbCmp, 0, pbCmp.Length); + + if(value.IsProtected) + { + MemUtil.ZeroByteArray(pbRaw); + MemUtil.ZeroByteArray(pbCmp); + } } else { byte[] pbRaw = value.ReadData(); m_xmlWriter.WriteBase64(pbRaw, 0, pbRaw.Length); + + if(value.IsProtected) MemUtil.ZeroByteArray(pbRaw); } } } @@ -824,11 +982,13 @@ namespace ModernKeePassLib.Serialization { m_xmlWriter.WriteStartElement(ElemBinaries); - foreach(KeyValuePair kvp in m_dictBinPool) + ProtectedBinary[] v = m_pbsBinaries.ToArray(); + for(int i = 0; i < v.Length; ++i) { m_xmlWriter.WriteStartElement(ElemBinary); - m_xmlWriter.WriteAttributeString(AttrId, kvp.Key); - SubWriteValue(kvp.Value); + m_xmlWriter.WriteAttributeString(AttrId, + i.ToString(NumberFormatInfo.InvariantInfo)); + SubWriteValue(v[i]); m_xmlWriter.WriteEndElement(); } @@ -836,21 +996,18 @@ namespace ModernKeePassLib.Serialization } [Obsolete] - public static bool WriteEntries(Stream msOutput, PwDatabase pwDatabase, - PwEntry[] vEntries) - { - return WriteEntries(msOutput, vEntries); - } - - /// - /// Write entries to a stream. - /// - /// Output stream to which the entries will be written. - /// Entries to serialize. - /// Returns true, if the entries were written successfully - /// to the stream. public static bool WriteEntries(Stream msOutput, PwEntry[] vEntries) { + return WriteEntries(msOutput, null, vEntries); + } + + public static bool WriteEntries(Stream msOutput, PwDatabase pdContext, + PwEntry[] vEntries) + { + if(msOutput == null) { Debug.Assert(false); return false; } + // pdContext may be null + if(vEntries == null) { Debug.Assert(false); return false; } + /* KdbxFile f = new KdbxFile(pwDatabase); f.m_format = KdbxFormat.PlainXml; @@ -881,8 +1038,27 @@ namespace ModernKeePassLib.Serialization PwDatabase pd = new PwDatabase(); pd.New(new IOConnectionInfo(), new CompositeKey()); - foreach(PwEntry peCopy in vEntries) - pd.RootGroup.AddEntry(peCopy.CloneDeep(), true); + PwGroup pg = pd.RootGroup; + if(pg == null) { Debug.Assert(false); return false; } + + foreach(PwEntry pe in vEntries) + { + PwUuid pu = pe.CustomIconUuid; + if(!pu.Equals(PwUuid.Zero) && (pd.GetCustomIconIndex(pu) < 0)) + { + int i = -1; + if(pdContext != null) i = pdContext.GetCustomIconIndex(pu); + if(i >= 0) + { + PwCustomIcon ci = pdContext.CustomIcons[i]; + pd.CustomIcons.Add(ci); + } + else { Debug.Assert(pdContext == null); } + } + + PwEntry peCopy = pe.CloneDeep(); + pg.AddEntry(peCopy, true); + } KdbxFile f = new KdbxFile(pd); f.Save(msOutput, null, KdbxFormat.PlainXml, null); diff --git a/ModernKeePassLib/Serialization/KdbxFile.cs b/ModernKeePassLib/Serialization/KdbxFile.cs index e5c9c25..ca20d8e 100644 --- a/ModernKeePassLib/Serialization/KdbxFile.cs +++ b/ModernKeePassLib/Serialization/KdbxFile.cs @@ -24,7 +24,9 @@ using System.Text; using System.Globalization; using System.IO; using System.Diagnostics; - +using System.Security; +using Windows.Security.Cryptography; +using Windows.Security.Cryptography.Core; #if !KeePassLibSD using System.IO.Compression; #endif @@ -36,8 +38,11 @@ using Windows.Storage; using ModernKeePassLib.Collections; using ModernKeePassLib.Cryptography; +using ModernKeePassLib.Cryptography.Cipher; +using ModernKeePassLib.Cryptography.KeyDerivation; using ModernKeePassLib.Delegates; using ModernKeePassLib.Interfaces; +using ModernKeePassLib.Resources; using ModernKeePassLib.Security; using ModernKeePassLib.Utility; @@ -82,7 +87,10 @@ namespace ModernKeePassLib.Serialization /// The first 2 bytes are critical (i.e. loading will fail, if the /// file version is too high), the last 2 bytes are informational. /// - private const uint FileVersion32 = 0x00030001; + private const uint FileVersion32 = 0x00040000; + + internal const uint FileVersion32_4 = 0x00040000; // First of 4.x series + internal const uint FileVersion32_3 = 0x00030001; // Old format 3.1 private const uint FileVersionCriticalMask = 0xFFFF0000; @@ -101,6 +109,7 @@ namespace ModernKeePassLib.Serialization private const string ElemGenerator = "Generator"; private const string ElemHeaderHash = "HeaderHash"; + private const string ElemSettingsChanged = "SettingsChanged"; private const string ElemDbName = "DatabaseName"; private const string ElemDbNameChanged = "DatabaseNameChanged"; private const string ElemDbDesc = "DatabaseDescription"; @@ -112,6 +121,7 @@ namespace ModernKeePassLib.Serialization private const string ElemDbKeyChanged = "MasterKeyChanged"; private const string ElemDbKeyChangeRec = "MasterKeyChangeRec"; private const string ElemDbKeyChangeForce = "MasterKeyChangeForce"; + private const string ElemDbKeyChangeForceOnce = "MasterKeyChangeForceOnce"; private const string ElemRecycleBinEnabled = "RecycleBinEnabled"; private const string ElemRecycleBinUuid = "RecycleBinUUID"; private const string ElemRecycleBinChanged = "RecycleBinChanged"; @@ -195,6 +205,7 @@ namespace ModernKeePassLib.Serialization private const string ElemStringDictExItem = "Item"; private PwDatabase m_pwDatabase; // Not null, see constructor + private bool m_bUsedOnce = false; #if ModernKeePassLib private XmlWriter m_xmlWriter = null; @@ -205,23 +216,23 @@ namespace ModernKeePassLib.Serialization private KdbxFormat m_format = KdbxFormat.Default; private IStatusLogger m_slLogger = null; + private uint m_uFileVersion = 0; private byte[] m_pbMasterSeed = null; - private byte[] m_pbTransformSeed = null; + // private byte[] m_pbTransformSeed = null; private byte[] m_pbEncryptionIV = null; - private byte[] m_pbProtectedStreamKey = null; private byte[] m_pbStreamStartBytes = null; - // ArcFourVariant only for compatibility; KeePass will default to a - // different (more secure) algorithm when *writing* databases + // ArcFourVariant only for backward compatibility; KeePass defaults + // to a more secure algorithm when *writing* databases private CrsAlgorithm m_craInnerRandomStream = CrsAlgorithm.ArcFourVariant; + private byte[] m_pbInnerRandomStreamKey = null; - private Dictionary m_dictBinPool = - new Dictionary(); + private ProtectedBinarySet m_pbsBinaries = new ProtectedBinarySet(); private byte[] m_pbHashOfHeader = null; private byte[] m_pbHashOfFileOnDisk = null; - private readonly DateTime m_dtNow = DateTime.Now; // Cache current time + private readonly DateTime m_dtNow = DateTime.UtcNow; // Cache current time private const uint NeutralLanguageOffset = 0x100000; // 2^20, see 32-bit Unicode specs private const uint NeutralLanguageIDSec = 0x7DC5C; // See 32-bit Unicode specs @@ -235,12 +246,30 @@ namespace ModernKeePassLib.Serialization CipherID = 2, CompressionFlags = 3, MasterSeed = 4, - TransformSeed = 5, - TransformRounds = 6, + TransformSeed = 5, // KDBX 3.1, for backward compatibility only + TransformRounds = 6, // KDBX 3.1, for backward compatibility only EncryptionIV = 7, - ProtectedStreamKey = 8, - StreamStartBytes = 9, - InnerRandomStreamID = 10 + InnerRandomStreamKey = 8, // KDBX 3.1, for backward compatibility only + StreamStartBytes = 9, // KDBX 3.1, for backward compatibility only + InnerRandomStreamID = 10, // KDBX 3.1, for backward compatibility only + KdfParameters = 11, // KDBX 4, superseding Transform* + PublicCustomData = 12 // KDBX 4 + } + + // Inner header in KDBX >= 4 files + private enum KdbxInnerHeaderFieldID : byte + { + EndOfHeader = 0, + InnerRandomStreamID = 1, // Supersedes KdbxHeaderFieldID.InnerRandomStreamID + InnerRandomStreamKey = 2, // Supersedes KdbxHeaderFieldID.InnerRandomStreamKey + Binary = 3 + } + + [Flags] + private enum KdbxBinaryFlags : byte + { + None = 0, + Protected = 1 } public byte[] HashOfFileOnDisk @@ -255,6 +284,13 @@ namespace ModernKeePassLib.Serialization set { m_bRepairMode = value; } } + private uint m_uForceVersion = 0; + internal uint ForceVersion + { + get { return m_uForceVersion; } + set { m_uForceVersion = value; } + } + private string m_strDetachBins = null; /// /// Detach binaries when opening a file. If this isn't null, @@ -299,64 +335,173 @@ namespace ModernKeePassLib.Serialization } } - private void BinPoolBuild(PwGroup pgDataSource) + private uint GetMinKdbxVersion() { - m_dictBinPool = new Dictionary(); + if(m_uForceVersion != 0) return m_uForceVersion; - if(pgDataSource == null) { Debug.Assert(false); return; } + // See also KeePassKdb2x3.Export (KDBX 3.1 export module) - EntryHandler eh = delegate(PwEntry pe) + AesKdf kdfAes = new AesKdf(); + if(!kdfAes.Uuid.Equals(m_pwDatabase.KdfParameters.KdfUuid)) + return FileVersion32; + + if(m_pwDatabase.PublicCustomData.Count > 0) + return FileVersion32; + + bool bCustomData = false; + GroupHandler gh = delegate(PwGroup pg) { - foreach(PwEntry peHistory in pe.History) - { - BinPoolAdd(peHistory.Binaries); - } - - BinPoolAdd(pe.Binaries); + if(pg == null) { Debug.Assert(false); return true; } + if(pg.CustomData.Count > 0) { bCustomData = true; return false; } return true; }; + EntryHandler eh = delegate(PwEntry pe) + { + if(pe == null) { Debug.Assert(false); return true; } + if(pe.CustomData.Count > 0) { bCustomData = true; return false; } + return true; + }; + gh(m_pwDatabase.RootGroup); + m_pwDatabase.RootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh); + if(bCustomData) return FileVersion32; - pgDataSource.TraverseTree(TraversalMethod.PreOrder, null, eh); + return FileVersion32_3; // KDBX 3.1 is sufficient } - private void BinPoolAdd(ProtectedBinaryDictionary dict) + private void ComputeKeys(out byte[] pbCipherKey, int cbCipherKey, + out byte[] pbHmacKey64) { - foreach(KeyValuePair kvp in dict) + byte[] pbCmp = new byte[32 + 32 + 1]; + try { - BinPoolAdd(kvp.Value); + Debug.Assert(m_pbMasterSeed != null); + if(m_pbMasterSeed == null) + throw new ArgumentNullException("m_pbMasterSeed"); + Debug.Assert(m_pbMasterSeed.Length == 32); + if(m_pbMasterSeed.Length != 32) + throw new FormatException(KLRes.MasterSeedLengthInvalid); + Array.Copy(m_pbMasterSeed, 0, pbCmp, 0, 32); + + Debug.Assert(m_pwDatabase != null); + Debug.Assert(m_pwDatabase.MasterKey != null); + ProtectedBinary pbinUser = m_pwDatabase.MasterKey.GenerateKey32( + m_pwDatabase.KdfParameters); + Debug.Assert(pbinUser != null); + if(pbinUser == null) + throw new SecurityException(KLRes.InvalidCompositeKey); + byte[] pUserKey32 = pbinUser.ReadData(); + if((pUserKey32 == null) || (pUserKey32.Length != 32)) + throw new SecurityException(KLRes.InvalidCompositeKey); + Array.Copy(pUserKey32, 0, pbCmp, 32, 32); + MemUtil.ZeroByteArray(pUserKey32); + + pbCipherKey = CryptoUtil.ResizeKey(pbCmp, 0, 64, cbCipherKey); + + pbCmp[64] = 1; +#if ModernKeePassLib + var sha256 = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256); + var buffer = sha256.HashData(CryptographicBuffer.CreateFromByteArray(pbCmp)); + CryptographicBuffer.CopyToByteArray(buffer, out pbHmacKey64); +#else + using(SHA512Managed h = new SHA512Managed()) + { + pbHmacKey64 = h.ComputeHash(pbCmp); + } +#endif + } + finally { MemUtil.ZeroByteArray(pbCmp); } + } + + private ICipherEngine GetCipher(out int cbEncKey, out int cbEncIV) + { + PwUuid pu = m_pwDatabase.DataCipherUuid; + ICipherEngine iCipher = CipherPool.GlobalPool.GetCipher(pu); + if(iCipher == null) // CryptographicExceptions are translated to "file corrupted" + throw new Exception(KLRes.FileUnknownCipher + + Environment.NewLine + KLRes.FileNewVerOrPlgReq + + Environment.NewLine + "UUID: " + pu.ToHexString() + "."); + + ICipherEngine2 iCipher2 = (iCipher as ICipherEngine2); + if(iCipher2 != null) + { + cbEncKey = iCipher2.KeyLength; + if(cbEncKey < 0) throw new InvalidOperationException("EncKey.Length"); + + cbEncIV = iCipher2.IVLength; + if(cbEncIV < 0) throw new InvalidOperationException("EncIV.Length"); } - } - - private void BinPoolAdd(ProtectedBinary pb) - { - if(pb == null) { Debug.Assert(false); return; } - - if(BinPoolFind(pb) != null) return; // Exists already - - m_dictBinPool.Add(m_dictBinPool.Count.ToString( - NumberFormatInfo.InvariantInfo), pb); - } - - private string BinPoolFind(ProtectedBinary pb) - { - if(pb == null) { Debug.Assert(false); return null; } - - foreach(KeyValuePair kvp in m_dictBinPool) + else { - if(pb.Equals(kvp.Value)) return kvp.Key; + cbEncKey = 32; + cbEncIV = 16; } - return null; + return iCipher; } - private ProtectedBinary BinPoolGet(string strKey) + private Stream EncryptStream(Stream s, ICipherEngine iCipher, + byte[] pbKey, int cbIV, bool bEncrypt) { - if(strKey == null) { Debug.Assert(false); return null; } + byte[] pbIV = (m_pbEncryptionIV ?? MemUtil.EmptyByteArray); + if(pbIV.Length != cbIV) + { + Debug.Assert(false); + throw new Exception(KLRes.FileCorrupted); + } - ProtectedBinary pb; - if(m_dictBinPool.TryGetValue(strKey, out pb)) return pb; + if(bEncrypt) + return iCipher.EncryptStream(s, pbKey, pbIV); + return iCipher.DecryptStream(s, pbKey, pbIV); + } - return null; + private byte[] ComputeHeaderHmac(byte[] pbHeader, byte[] pbKey) + { + byte[] pbHeaderHmac; + byte[] pbBlockKey = HmacBlockStream.GetHmacKey64( + pbKey, ulong.MaxValue); +#if ModernKeePassLib + var h = MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha256).CreateHash(CryptographicBuffer.CreateFromByteArray(pbHeader)); + CryptographicBuffer.CopyToByteArray(h.GetValueAndReset(), out pbHeaderHmac); +#else + using (HMACSHA256 h = new HMACSHA256(pbBlockKey)) + { + pbHeaderHmac = h.ComputeHash(pbHeader); + } +#endif + MemUtil.ZeroByteArray(pbBlockKey); + + return pbHeaderHmac; + } + + private void CloseStreams(List lStreams) + { + if(lStreams == null) { Debug.Assert(false); return; } + + // Typically, closing a stream also closes its base + // stream; however, there may be streams that do not + // do this (e.g. some cipher plugin), thus for safety + // we close all streams manually, from the innermost + // to the outermost + + for(int i = lStreams.Count - 1; i >= 0; --i) + { + // Check for duplicates + Debug.Assert((lStreams.IndexOf(lStreams[i]) == i) && + (lStreams.LastIndexOf(lStreams[i]) == i)); + + try { lStreams[i].Dispose(); } + catch(Exception) { Debug.Assert(false); } + } + + // Do not clear the list + } + + private void CleanUpInnerRandomStream() + { + if(m_randomStream != null) m_randomStream.Dispose(); + + if(m_pbInnerRandomStreamKey != null) + MemUtil.ZeroByteArray(m_pbInnerRandomStreamKey); } private static void SaveBinary(string strName, ProtectedBinary pb, @@ -368,22 +513,22 @@ namespace ModernKeePassLib.Serialization string strPath; int iTry = 1; - do - { - strPath = UrlUtil.EnsureTerminatingSeparator(strSaveDir, false); + do + { + strPath = UrlUtil.EnsureTerminatingSeparator(strSaveDir, false); - string strExt = UrlUtil.GetExtension(strName); - string strDesc = UrlUtil.StripExtension(strName); + string strExt = UrlUtil.GetExtension(strName); + string strDesc = UrlUtil.StripExtension(strName); - strPath += strDesc; - if (iTry > 1) - strPath += " (" + iTry.ToString(NumberFormatInfo.InvariantInfo) + - ")"; + strPath += strDesc; + if(iTry > 1) + strPath += " (" + iTry.ToString(NumberFormatInfo.InvariantInfo) + + ")"; - if (!string.IsNullOrEmpty(strExt)) strPath += "." + strExt; + if(!string.IsNullOrEmpty(strExt)) strPath += "." + strExt; - ++iTry; - } + ++iTry; + } #if ModernKeePassLib //while(FileSystem.Current.GetFileFromPathAsync(strPath).Result != null); while (StorageFile.GetFileFromPathAsync(strPath).GetResults() != null); diff --git a/ModernKeePassLib/Serialization/OldFormatException.cs b/ModernKeePassLib/Serialization/OldFormatException.cs index 46a8aa6..d9d590e 100644 --- a/ModernKeePassLib/Serialization/OldFormatException.cs +++ b/ModernKeePassLib/Serialization/OldFormatException.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -45,7 +45,7 @@ namespace ModernKeePassLib.Serialization (@" (" + m_strFormat + @")") : string.Empty) + "."; if(m_type == OldFormatType.KeePass1x) - str += Environment.NewLine + Environment.NewLine + KLRes.KeePass1xHint; + str += Environment.NewLine + KLRes.KeePass1xHint; return str; } diff --git a/ModernKeePassLib/Translation/KPControlCustomization.cs b/ModernKeePassLib/Translation/KPControlCustomization.cs index b66bb49..e2483a7 100644 --- a/ModernKeePassLib/Translation/KPControlCustomization.cs +++ b/ModernKeePassLib/Translation/KPControlCustomization.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -331,11 +331,9 @@ namespace ModernKeePassLib.Translation WriteControlDependentParams(sb, c); byte[] pb = StrUtil.Utf8.GetBytes(sb.ToString()); + byte[] pbSha = CryptoUtil.HashSha256(pb); - SHA256Managed sha256 = new SHA256Managed(); - byte[] pbSha = sha256.ComputeHash(pb); - - // Also see MatchHash + // See also MatchHash return "v1:" + Convert.ToBase64String(pbSha, 0, 3, Base64FormattingOptions.None); } diff --git a/ModernKeePassLib/Translation/KPFormCustomization.cs b/ModernKeePassLib/Translation/KPFormCustomization.cs index 4d0f7dc..4696fdf 100644 --- a/ModernKeePassLib/Translation/KPFormCustomization.cs +++ b/ModernKeePassLib/Translation/KPFormCustomization.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,6 +19,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Text; using System.Windows.Forms; using System.Xml.Serialization; diff --git a/ModernKeePassLib/Translation/KPStringTable.cs b/ModernKeePassLib/Translation/KPStringTable.cs index ea55bac..39850c8 100644 --- a/ModernKeePassLib/Translation/KPStringTable.cs +++ b/ModernKeePassLib/Translation/KPStringTable.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/Translation/KPStringTableItem.cs b/ModernKeePassLib/Translation/KPStringTableItem.cs index 4bdb234..dd98783 100644 --- a/ModernKeePassLib/Translation/KPStringTableItem.cs +++ b/ModernKeePassLib/Translation/KPStringTableItem.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/Translation/KPTranslation.cs b/ModernKeePassLib/Translation/KPTranslation.cs index 6e16d28..bb53e07 100644 --- a/ModernKeePassLib/Translation/KPTranslation.cs +++ b/ModernKeePassLib/Translation/KPTranslation.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -92,18 +92,25 @@ namespace ModernKeePassLib.Translation } } - public static void SaveToFile(KPTranslation kpTrl, string strFileName, + public static void Save(KPTranslation kpTrl, string strFileName, + IXmlSerializerEx xs) + { + using(FileStream fs = new FileStream(strFileName, FileMode.Create, + FileAccess.Write, FileShare.None)) + { + Save(kpTrl, fs, xs); + } + } + + public static void Save(KPTranslation kpTrl, Stream sOut, IXmlSerializerEx xs) { if(xs == null) throw new ArgumentNullException("xs"); - FileStream fs = new FileStream(strFileName, FileMode.Create, - FileAccess.Write, FileShare.None); - #if !KeePassLibSD - GZipStream gz = new GZipStream(fs, CompressionMode.Compress); + GZipStream gz = new GZipStream(sOut, CompressionMode.Compress); #else - GZipOutputStream gz = new GZipOutputStream(fs); + GZipOutputStream gz = new GZipOutputStream(sOut); #endif XmlWriterSettings xws = new XmlWriterSettings(); @@ -118,27 +125,36 @@ namespace ModernKeePassLib.Translation xw.Close(); gz.Close(); - fs.Close(); + sOut.Close(); } - public static KPTranslation LoadFromFile(string strFile, - IXmlSerializerEx xs) + public static KPTranslation Load(string strFile, IXmlSerializerEx xs) + { + KPTranslation kpTrl = null; + + using(FileStream fs = new FileStream(strFile, FileMode.Open, + FileAccess.Read, FileShare.Read)) + { + kpTrl = Load(fs, xs); + } + + return kpTrl; + } + + public static KPTranslation Load(Stream s, IXmlSerializerEx xs) { if(xs == null) throw new ArgumentNullException("xs"); - FileStream fs = new FileStream(strFile, FileMode.Open, - FileAccess.Read, FileShare.Read); - #if !KeePassLibSD - GZipStream gz = new GZipStream(fs, CompressionMode.Decompress); + GZipStream gz = new GZipStream(s, CompressionMode.Decompress); #else - GZipInputStream gz = new GZipInputStream(fs); + GZipInputStream gz = new GZipInputStream(s); #endif KPTranslation kpTrl = (xs.Deserialize(gz) as KPTranslation); gz.Close(); - fs.Close(); + s.Close(); return kpTrl; } @@ -205,31 +221,76 @@ namespace ModernKeePassLib.Translation ((TrackBar)c).RightToLeftLayout = true; else if(c is TreeView) ((TreeView)c).RightToLeftLayout = true; - else if(c is ToolStrip) - RtlApplyToToolStripItems(((ToolStrip)c).Items); + // else if(c is ToolStrip) + // RtlApplyToToolStripItems(((ToolStrip)c).Items); + /* else if(c is Button) // Also see Label + { + Button btn = (c as Button); + Image img = btn.Image; + if(img != null) + { + Image imgNew = (Image)img.Clone(); + imgNew.RotateFlip(RotateFlipType.RotateNoneFlipX); + btn.Image = imgNew; + } + } + else if(c is Label) // Also see Button + { + Label lbl = (c as Label); + Image img = lbl.Image; + if(img != null) + { + Image imgNew = (Image)img.Clone(); + imgNew.RotateFlip(RotateFlipType.RotateNoneFlipX); + lbl.Image = imgNew; + } + } */ - if((c is GroupBox) || (c is Panel)) RtlMoveChildControls(c); + if(IsRtlMoveChildsRequired(c)) RtlMoveChildControls(c); } } + internal static bool IsRtlMoveChildsRequired(Control c) + { + if(c == null) { Debug.Assert(false); return false; } + + return ((c is GroupBox) || (c is Panel)); + } + private static void RtlMoveChildControls(Control cParent) { int nParentWidth = cParent.Size.Width; foreach(Control c in cParent.Controls) { - Point ptCur = c.Location; - c.Location = new Point(nParentWidth - c.Size.Width - ptCur.X, ptCur.Y); + DockStyle ds = c.Dock; + if(ds == DockStyle.Left) + c.Dock = DockStyle.Right; + else if(ds == DockStyle.Right) + c.Dock = DockStyle.Left; + else + { + Point ptCur = c.Location; + c.Location = new Point(nParentWidth - c.Size.Width - ptCur.X, ptCur.Y); + } } } + /* private static readonly string[] g_vRtlMirrorItemNames = new string[] { }; private static void RtlApplyToToolStripItems(ToolStripItemCollection tsic) { foreach(ToolStripItem tsi in tsic) { - tsi.RightToLeftAutoMirrorImage = true; + if(tsi == null) { Debug.Assert(false); continue; } + + if(Array.IndexOf(g_vRtlMirrorItemNames, tsi.Name) >= 0) + tsi.RightToLeftAutoMirrorImage = true; + + ToolStripDropDownItem tsdd = (tsi as ToolStripDropDownItem); + if(tsdd != null) + RtlApplyToToolStripItems(tsdd.DropDownItems); } - } + } */ public void ApplyTo(string strTableName, ToolStripItemCollection tsic) { diff --git a/ModernKeePassLib/Translation/KPTranslationProperties.cs b/ModernKeePassLib/Translation/KPTranslationProperties.cs index 0d0e721..8a5abbf 100644 --- a/ModernKeePassLib/Translation/KPTranslationProperties.cs +++ b/ModernKeePassLib/Translation/KPTranslationProperties.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/ModernKeePassLib/Utility/AppLogEx.cs b/ModernKeePassLib/Utility/AppLogEx.cs index 0e2dc56..55ae023 100644 --- a/ModernKeePassLib/Utility/AppLogEx.cs +++ b/ModernKeePassLib/Utility/AppLogEx.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,9 +19,9 @@ using System; using System.Collections.Generic; -using System.Text; -using System.IO; using System.Diagnostics; +using System.IO; +using System.Text; #if !KeePassLibSD using System.IO.Compression; @@ -38,8 +38,7 @@ namespace ModernKeePassLib.Utility public static void Open(string strPrefix) { - return; // Logging is not enabled in normal builds of KeePass! - + // Logging is not enabled in normal builds of KeePass! /* AppLogEx.Close(); @@ -58,7 +57,7 @@ namespace ModernKeePassLib.Utility string strPath = strTemp + strPrefix + "-"; Debug.Assert(strPath.IndexOf('/') < 0); - DateTime dtNow = DateTime.Now; + DateTime dtNow = DateTime.UtcNow; string strTime = dtNow.ToString("s"); strTime = strTime.Replace('T', '-'); strTime = strTime.Replace(':', '-'); diff --git a/ModernKeePassLib/Utility/GfxUtil.cs b/ModernKeePassLib/Utility/GfxUtil.cs index 7e17462..976e790 100644 --- a/ModernKeePassLib/Utility/GfxUtil.cs +++ b/ModernKeePassLib/Utility/GfxUtil.cs @@ -28,38 +28,79 @@ using System.Drawing; using System.Drawing.Imaging; #endif using System.Diagnostics; +using System.Threading.Tasks; namespace ModernKeePassLib.Utility { public static class GfxUtil { -#if KeePassRT - public static Image LoadImage(byte[] pb) +#if (!KeePassLibSD && !KeePassUAP) + private sealed class GfxImage { - MemoryStream ms = new MemoryStream(pb, false); - try { return Image.FromStream(ms); } - finally { ms.Close(); } - } -#elif ModernKeePassLib - public static IBitmap LoadImage(byte[] pb) - { - using (var ms = new MemoryStream(pb, false)) { - return BitmapLoader.Current.Load(ms, null, null).Result; + public byte[] Data; + + public int Width; + public int Height; + + public GfxImage(byte[] pbData, int w, int h) + { + this.Data = pbData; + this.Width = w; + this.Height = h; } + +#if DEBUG + // For debugger display + public override string ToString() + { + return (this.Width.ToString() + "x" + this.Height.ToString()); + } +#endif } -#else +#endif + +#if KeePassUAP public static Image LoadImage(byte[] pb) { if(pb == null) throw new ArgumentNullException("pb"); MemoryStream ms = new MemoryStream(pb, false); - try { return LoadImagePriv(ms); } - catch(Exception) + try { return Image.FromStream(ms); } + finally { ms.Close(); } + } +#elif ModernKeePassLib + public static async Task LoadImage(byte[] pb) + { + return await ScaleImage(pb, null, null); + } + + public static async Task ScaleImage(byte[] pb, int? w, int? h) + { + using (var ms = new MemoryStream(pb, false)) + { + return await BitmapLoader.Current.Load(ms, w, h); + } + } +#else + public static Image LoadImage(byte[] pb) + { + if(pb == null) throw new ArgumentNullException("pb"); + +#if !KeePassLibSD + // First try to load the data as ICO and afterwards as + // normal image, because trying to load an ICO using + // the normal image loading methods can result in a + // low resolution image + try { - Image imgIco = TryLoadIco(pb); + Image imgIco = ExtractBestImageFromIco(pb); if(imgIco != null) return imgIco; - throw; } + catch(Exception) { Debug.Assert(false); } +#endif + + MemoryStream ms = new MemoryStream(pb, false); + try { return LoadImagePriv(ms); } finally { ms.Close(); } } @@ -91,7 +132,12 @@ namespace ModernKeePassLib.Utility using(Graphics g = Graphics.FromImage(bmp)) { g.Clear(Color.Transparent); + +#if !KeePassLibSD + g.DrawImageUnscaled(imgSrc, 0, 0); +#else g.DrawImage(imgSrc, 0, 0); +#endif } return bmp; @@ -99,17 +145,313 @@ namespace ModernKeePassLib.Utility finally { if(imgSrc != null) imgSrc.Dispose(); } } - private static Image TryLoadIco(byte[] pb) - { #if !KeePassLibSD - MemoryStream ms = new MemoryStream(pb, false); - try { return (new Icon(ms)).ToBitmap(); } - catch(Exception) { } - finally { ms.Close(); } -#endif + private static Image ExtractBestImageFromIco(byte[] pb) + { + List l = UnpackIco(pb); + if((l == null) || (l.Count == 0)) return null; - return null; + long qMax = 0; + foreach(GfxImage gi in l) + { + if(gi.Width == 0) gi.Width = 256; + if(gi.Height == 0) gi.Height = 256; + + qMax = Math.Max(qMax, (long)gi.Width * (long)gi.Height); + } + + byte[] pbHdrPng = new byte[] { + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A + }; + byte[] pbHdrJpeg = new byte[] { 0xFF, 0xD8, 0xFF }; + + Image imgBest = null; + int bppBest = -1; + + foreach(GfxImage gi in l) + { + if(((long)gi.Width * (long)gi.Height) < qMax) continue; + + byte[] pbImg = gi.Data; + Image img = null; + try + { + if((pbImg.Length > pbHdrPng.Length) && + MemUtil.ArraysEqual(pbHdrPng, + MemUtil.Mid(pbImg, 0, pbHdrPng.Length))) + img = GfxUtil.LoadImage(pbImg); + else if((pbImg.Length > pbHdrJpeg.Length) && + MemUtil.ArraysEqual(pbHdrJpeg, + MemUtil.Mid(pbImg, 0, pbHdrJpeg.Length))) + img = GfxUtil.LoadImage(pbImg); + else + { + using(MemoryStream ms = new MemoryStream(pb, false)) + { + using(Icon ico = new Icon(ms, gi.Width, gi.Height)) + { + img = ico.ToBitmap(); + } + } + } + } + catch(Exception) { Debug.Assert(false); } + + if(img == null) continue; + + if((img.Width < gi.Width) || (img.Height < gi.Height)) + { + Debug.Assert(false); + img.Dispose(); + continue; + } + + int bpp = GetBitsPerPixel(img.PixelFormat); + if(bpp > bppBest) + { + if(imgBest != null) imgBest.Dispose(); + + imgBest = img; + bppBest = bpp; + } + else img.Dispose(); + } + + return imgBest; + } + + private static List UnpackIco(byte[] pb) + { + if(pb == null) { Debug.Assert(false); return null; } + + const int SizeICONDIR = 6; + const int SizeICONDIRENTRY = 16; + + if(pb.Length < SizeICONDIR) return null; + if(MemUtil.BytesToUInt16(pb, 0) != 0) return null; // Reserved, 0 + if(MemUtil.BytesToUInt16(pb, 2) != 1) return null; // ICO type, 1 + + int n = MemUtil.BytesToUInt16(pb, 4); + if(n < 0) { Debug.Assert(false); return null; } + + int cbDir = SizeICONDIR + (n * SizeICONDIRENTRY); + if(pb.Length < cbDir) return null; + + List l = new List(); + int iOffset = SizeICONDIR; + for(int i = 0; i < n; ++i) + { + int w = pb[iOffset]; + int h = pb[iOffset + 1]; + if((w < 0) || (h < 0)) { Debug.Assert(false); return null; } + + int cb = MemUtil.BytesToInt32(pb, iOffset + 8); + if(cb <= 0) return null; // Data must have header (even BMP) + + int p = MemUtil.BytesToInt32(pb, iOffset + 12); + if(p < cbDir) return null; + if((p + cb) > pb.Length) return null; + + try + { + byte[] pbImage = MemUtil.Mid(pb, p, cb); + GfxImage img = new GfxImage(pbImage, w, h); + l.Add(img); + } + catch(Exception) { Debug.Assert(false); return null; } + + iOffset += SizeICONDIRENTRY; + } + + return l; + } + + private static int GetBitsPerPixel(PixelFormat f) + { + int bpp = 0; + switch(f) + { + case PixelFormat.Format1bppIndexed: + bpp = 1; + break; + + case PixelFormat.Format4bppIndexed: + bpp = 4; + break; + + case PixelFormat.Format8bppIndexed: + bpp = 8; + break; + + case PixelFormat.Format16bppArgb1555: + case PixelFormat.Format16bppGrayScale: + case PixelFormat.Format16bppRgb555: + case PixelFormat.Format16bppRgb565: + bpp = 16; + break; + + case PixelFormat.Format24bppRgb: + bpp = 24; + break; + + case PixelFormat.Format32bppArgb: + case PixelFormat.Format32bppPArgb: + case PixelFormat.Format32bppRgb: + bpp = 32; + break; + + case PixelFormat.Format48bppRgb: + bpp = 48; + break; + + case PixelFormat.Format64bppArgb: + case PixelFormat.Format64bppPArgb: + bpp = 64; + break; + + default: + Debug.Assert(false); + break; + } + + return bpp; + } + + public static Image ScaleImage(Image img, int w, int h) + { + return ScaleImage(img, w, h, ScaleTransformFlags.None); + } + + /// + /// Resize an image. + /// + /// Image to resize. + /// Width of the returned image. + /// Height of the returned image. + /// Flags to customize scaling behavior. + /// Resized image. This object is always different + /// from (i.e. they can be + /// disposed separately). + public static Image ScaleImage(Image img, int w, int h, + ScaleTransformFlags f) + { + if(img == null) throw new ArgumentNullException("img"); + if(w < 0) throw new ArgumentOutOfRangeException("w"); + if(h < 0) throw new ArgumentOutOfRangeException("h"); + + bool bUIIcon = ((f & ScaleTransformFlags.UIIcon) != + ScaleTransformFlags.None); + + // We must return a Bitmap object for UIUtil.CreateScaledImage + Bitmap bmp = new Bitmap(w, h, PixelFormat.Format32bppArgb); + using(Graphics g = Graphics.FromImage(bmp)) + { + g.Clear(Color.Transparent); + + g.SmoothingMode = SmoothingMode.HighQuality; + g.CompositingQuality = CompositingQuality.HighQuality; + + int wSrc = img.Width; + int hSrc = img.Height; + + InterpolationMode im = InterpolationMode.HighQualityBicubic; + if((wSrc > 0) && (hSrc > 0)) + { + if(bUIIcon && ((w % wSrc) == 0) && ((h % hSrc) == 0)) + im = InterpolationMode.NearestNeighbor; + // else if((w < wSrc) && (h < hSrc)) + // im = InterpolationMode.HighQualityBilinear; + } + else { Debug.Assert(false); } + g.InterpolationMode = im; + + RectangleF rSource = new RectangleF(0.0f, 0.0f, wSrc, hSrc); + RectangleF rDest = new RectangleF(0.0f, 0.0f, w, h); + AdjustScaleRects(ref rSource, ref rDest); + + g.DrawImage(img, rDest, rSource, GraphicsUnit.Pixel); + } + + return bmp; + } + + internal static void AdjustScaleRects(ref RectangleF rSource, + ref RectangleF rDest) + { + // When enlarging images, apply a -0.5 offset to avoid + // the scaled image being cropped on the top/left side; + // when shrinking images, do not apply a -0.5 offset, + // otherwise the image is cropped on the bottom/right + // side; this applies to all interpolation modes + if(rDest.Width > rSource.Width) + rSource.X = rSource.X - 0.5f; + if(rDest.Height > rSource.Height) + rSource.Y = rSource.Y - 0.5f; + + // When shrinking, apply a +0.5 offset, such that the + // scaled image is less cropped on the bottom/right side + if(rDest.Width < rSource.Width) + rSource.X = rSource.X + 0.5f; + if(rDest.Height < rSource.Height) + rSource.Y = rSource.Y + 0.5f; + } + +#if DEBUG + public static Image ScaleTest(Image[] vIcons) + { + Bitmap bmp = new Bitmap(1024, vIcons.Length * (256 + 12), + PixelFormat.Format32bppArgb); + + using(Graphics g = Graphics.FromImage(bmp)) + { + g.Clear(Color.White); + + int[] v = new int[] { 16, 24, 32, 48, 64, 128, 256 }; + + int x; + int y = 8; + + foreach(Image imgIcon in vIcons) + { + if(imgIcon == null) { Debug.Assert(false); continue; } + + x = 128; + + foreach(int q in v) + { + using(Image img = ScaleImage(imgIcon, q, q, + ScaleTransformFlags.UIIcon)) + { + g.DrawImageUnscaled(img, x, y); + } + + x += q + 8; + } + + y += v[v.Length - 1] + 8; + } + } + + return bmp; + } +#endif // DEBUG +#endif // !KeePassLibSD +#endif // KeePassUAP +#if ModernKeePassLib +#else + internal static string ImageToDataUri(Image img) + { + if(img == null) { Debug.Assert(false); return string.Empty; } + + byte[] pb = null; + using(MemoryStream ms = new MemoryStream()) + { + img.Save(ms, ImageFormat.Png); + pb = ms.ToArray(); + } + + return StrUtil.DataToDataUri(pb, "image/png"); } #endif - } + } } diff --git a/ModernKeePassLib/Utility/MemUtil.cs b/ModernKeePassLib/Utility/MemUtil.cs index 0ff5093..28a9ec1 100644 --- a/ModernKeePassLib/Utility/MemUtil.cs +++ b/ModernKeePassLib/Utility/MemUtil.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,14 +19,15 @@ using System; using System.Collections.Generic; -using System.Text; using System.Diagnostics; using System.IO; +using System.Runtime.CompilerServices; +using System.Text; -#if !KeePassLibSD -using System.IO.Compression; -#else +#if KeePassLibSD using KeePassLibSD; +#else +using System.IO.Compression; #endif namespace ModernKeePassLib.Utility @@ -36,6 +37,8 @@ namespace ModernKeePassLib.Utility /// public static class MemUtil { + internal static readonly byte[] EmptyByteArray = new byte[0]; + private static readonly uint[] m_vSBox = new uint[256] { 0xCD2FACB3, 0xE78A7F5C, 0x6F0803FC, 0xBCF6E230, 0x3A321712, 0x06403DB1, 0xD2F84B95, 0xDF22A6E4, @@ -253,66 +256,139 @@ namespace ModernKeePassLib.Utility Debug.Assert(pbArray != null); if(pbArray == null) throw new ArgumentNullException("pbArray"); - // for(int i = 0; i < pbArray.Length; ++i) - // pbArray[i] = 0; - Array.Clear(pbArray, 0, pbArray.Length); } /// - /// Convert 2 bytes to a 16-bit unsigned integer using Little-Endian - /// encoding. + /// Set all elements of an array to the default value. + /// + /// Input array. +#if KeePassLibSD + [MethodImpl(MethodImplOptions.NoInlining)] +#else + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] +#endif + public static void ZeroArray(T[] v) + { + if(v == null) { Debug.Assert(false); throw new ArgumentNullException("v"); } + + Array.Clear(v, 0, v.Length); + } + + /// + /// Convert 2 bytes to a 16-bit unsigned integer (little-endian). /// - /// Input bytes. Array must contain at least 2 bytes. - /// 16-bit unsigned integer. public static ushort BytesToUInt16(byte[] pb) { Debug.Assert((pb != null) && (pb.Length == 2)); if(pb == null) throw new ArgumentNullException("pb"); - if(pb.Length != 2) throw new ArgumentException(); + if(pb.Length != 2) throw new ArgumentOutOfRangeException("pb"); return (ushort)((ushort)pb[0] | ((ushort)pb[1] << 8)); } /// - /// Convert 4 bytes to a 32-bit unsigned integer using Little-Endian - /// encoding. + /// Convert 2 bytes to a 16-bit unsigned integer (little-endian). + /// + public static ushort BytesToUInt16(byte[] pb, int iOffset) + { + if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } + if((iOffset < 0) || ((iOffset + 1) >= pb.Length)) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("iOffset"); + } + + return (ushort)((ushort)pb[iOffset] | ((ushort)pb[iOffset + 1] << 8)); + } + + /// + /// Convert 4 bytes to a 32-bit unsigned integer (little-endian). /// - /// Input bytes. - /// 32-bit unsigned integer. public static uint BytesToUInt32(byte[] pb) { Debug.Assert((pb != null) && (pb.Length == 4)); if(pb == null) throw new ArgumentNullException("pb"); - if(pb.Length != 4) throw new ArgumentException("Input array must contain 4 bytes!"); + if(pb.Length != 4) throw new ArgumentOutOfRangeException("pb"); - return (uint)pb[0] | ((uint)pb[1] << 8) | ((uint)pb[2] << 16) | - ((uint)pb[3] << 24); + return ((uint)pb[0] | ((uint)pb[1] << 8) | ((uint)pb[2] << 16) | + ((uint)pb[3] << 24)); } /// - /// Convert 8 bytes to a 64-bit unsigned integer using Little-Endian - /// encoding. + /// Convert 4 bytes to a 32-bit unsigned integer (little-endian). + /// + public static uint BytesToUInt32(byte[] pb, int iOffset) + { + if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } + if((iOffset < 0) || ((iOffset + 3) >= pb.Length)) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("iOffset"); + } + + return ((uint)pb[iOffset] | ((uint)pb[iOffset + 1] << 8) | + ((uint)pb[iOffset + 2] << 16) | ((uint)pb[iOffset + 3] << 24)); + } + + /// + /// Convert 8 bytes to a 64-bit unsigned integer (little-endian). /// - /// Input bytes. - /// 64-bit unsigned integer. public static ulong BytesToUInt64(byte[] pb) { Debug.Assert((pb != null) && (pb.Length == 8)); if(pb == null) throw new ArgumentNullException("pb"); - if(pb.Length != 8) throw new ArgumentException(); + if(pb.Length != 8) throw new ArgumentOutOfRangeException("pb"); - return (ulong)pb[0] | ((ulong)pb[1] << 8) | ((ulong)pb[2] << 16) | + return ((ulong)pb[0] | ((ulong)pb[1] << 8) | ((ulong)pb[2] << 16) | ((ulong)pb[3] << 24) | ((ulong)pb[4] << 32) | ((ulong)pb[5] << 40) | - ((ulong)pb[6] << 48) | ((ulong)pb[7] << 56); + ((ulong)pb[6] << 48) | ((ulong)pb[7] << 56)); } /// - /// Convert a 16-bit unsigned integer to 2 bytes using Little-Endian - /// encoding. + /// Convert 8 bytes to a 64-bit unsigned integer (little-endian). + /// + public static ulong BytesToUInt64(byte[] pb, int iOffset) + { + if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } + if((iOffset < 0) || ((iOffset + 7) >= pb.Length)) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("iOffset"); + } + + // if(BitConverter.IsLittleEndian) + // return BitConverter.ToUInt64(pb, iOffset); + + return ((ulong)pb[iOffset] | ((ulong)pb[iOffset + 1] << 8) | + ((ulong)pb[iOffset + 2] << 16) | ((ulong)pb[iOffset + 3] << 24) | + ((ulong)pb[iOffset + 4] << 32) | ((ulong)pb[iOffset + 5] << 40) | + ((ulong)pb[iOffset + 6] << 48) | ((ulong)pb[iOffset + 7] << 56)); + } + + public static int BytesToInt32(byte[] pb) + { + return (int)BytesToUInt32(pb); + } + + public static int BytesToInt32(byte[] pb, int iOffset) + { + return (int)BytesToUInt32(pb, iOffset); + } + + public static long BytesToInt64(byte[] pb) + { + return (long)BytesToUInt64(pb); + } + + public static long BytesToInt64(byte[] pb, int iOffset) + { + return (long)BytesToUInt64(pb, iOffset); + } + + /// + /// Convert a 16-bit unsigned integer to 2 bytes (little-endian). /// - /// 16-bit input word. - /// Two bytes representing the 16-bit value. public static byte[] UInt16ToBytes(ushort uValue) { byte[] pb = new byte[2]; @@ -327,11 +403,8 @@ namespace ModernKeePassLib.Utility } /// - /// Convert a 32-bit unsigned integer to 4 bytes using Little-Endian - /// encoding. + /// Convert a 32-bit unsigned integer to 4 bytes (little-endian). /// - /// 32-bit input word. - /// Four bytes representing the 32-bit value. public static byte[] UInt32ToBytes(uint uValue) { byte[] pb = new byte[4]; @@ -348,11 +421,29 @@ namespace ModernKeePassLib.Utility } /// - /// Convert a 64-bit unsigned integer to 8 bytes using Little-Endian - /// encoding. + /// Convert a 32-bit unsigned integer to 4 bytes (little-endian). + /// + public static void UInt32ToBytesEx(uint uValue, byte[] pb, int iOffset) + { + if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } + if((iOffset < 0) || ((iOffset + 3) >= pb.Length)) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("iOffset"); + } + + unchecked + { + pb[iOffset] = (byte)uValue; + pb[iOffset + 1] = (byte)(uValue >> 8); + pb[iOffset + 2] = (byte)(uValue >> 16); + pb[iOffset + 3] = (byte)(uValue >> 24); + } + } + + /// + /// Convert a 64-bit unsigned integer to 8 bytes (little-endian). /// - /// 64-bit input word. - /// Eight bytes representing the 64-bit value. public static byte[] UInt64ToBytes(ulong uValue) { byte[] pb = new byte[8]; @@ -372,6 +463,61 @@ namespace ModernKeePassLib.Utility return pb; } + /// + /// Convert a 64-bit unsigned integer to 8 bytes (little-endian). + /// + public static void UInt64ToBytesEx(ulong uValue, byte[] pb, int iOffset) + { + if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } + if((iOffset < 0) || ((iOffset + 7) >= pb.Length)) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("iOffset"); + } + + unchecked + { + pb[iOffset] = (byte)uValue; + pb[iOffset + 1] = (byte)(uValue >> 8); + pb[iOffset + 2] = (byte)(uValue >> 16); + pb[iOffset + 3] = (byte)(uValue >> 24); + pb[iOffset + 4] = (byte)(uValue >> 32); + pb[iOffset + 5] = (byte)(uValue >> 40); + pb[iOffset + 6] = (byte)(uValue >> 48); + pb[iOffset + 7] = (byte)(uValue >> 56); + } + } + + public static byte[] Int32ToBytes(int iValue) + { + return UInt32ToBytes((uint)iValue); + } + + public static byte[] Int64ToBytes(long lValue) + { + return UInt64ToBytes((ulong)lValue); + } + + public static uint RotateLeft32(uint u, int nBits) + { + return ((u << nBits) | (u >> (32 - nBits))); + } + + public static uint RotateRight32(uint u, int nBits) + { + return ((u >> nBits) | (u << (32 - nBits))); + } + + public static ulong RotateLeft64(ulong u, int nBits) + { + return ((u << nBits) | (u >> (64 - nBits))); + } + + public static ulong RotateRight64(ulong u, int nBits) + { + return ((u >> nBits) | (u << (64 - nBits))); + } + public static bool ArraysEqual(byte[] x, byte[] y) { // Return false if one of them is null (not comparable)! @@ -387,19 +533,21 @@ namespace ModernKeePassLib.Utility return true; } - public static void XorArray(byte[] pbSource, int nSourceOffset, - byte[] pbBuffer, int nBufferOffset, int nLength) + public static void XorArray(byte[] pbSource, int iSourceOffset, + byte[] pbBuffer, int iBufferOffset, int cb) { if(pbSource == null) throw new ArgumentNullException("pbSource"); - if(nSourceOffset < 0) throw new ArgumentException(); + if(iSourceOffset < 0) throw new ArgumentOutOfRangeException("iSourceOffset"); if(pbBuffer == null) throw new ArgumentNullException("pbBuffer"); - if(nBufferOffset < 0) throw new ArgumentException(); - if(nLength < 0) throw new ArgumentException(); - if((nSourceOffset + nLength) > pbSource.Length) throw new ArgumentException(); - if((nBufferOffset + nLength) > pbBuffer.Length) throw new ArgumentException(); + if(iBufferOffset < 0) throw new ArgumentOutOfRangeException("iBufferOffset"); + if(cb < 0) throw new ArgumentOutOfRangeException("cb"); + if(iSourceOffset > (pbSource.Length - cb)) + throw new ArgumentOutOfRangeException("cb"); + if(iBufferOffset > (pbBuffer.Length - cb)) + throw new ArgumentOutOfRangeException("cb"); - for(int i = 0; i < nLength; ++i) - pbBuffer[nBufferOffset + i] ^= pbSource[nSourceOffset + i]; + for(int i = 0; i < cb; ++i) + pbBuffer[iBufferOffset + i] ^= pbSource[iSourceOffset + i]; } /// @@ -478,7 +626,8 @@ namespace ModernKeePassLib.Utility if(s == null) { Debug.Assert(false); return; } if(pbData == null) { Debug.Assert(false); return; } - s.Write(pbData, 0, pbData.Length); + Debug.Assert(pbData.Length >= 0); + if(pbData.Length > 0) s.Write(pbData, 0, pbData.Length); } public static byte[] Compress(byte[] pbData) @@ -486,15 +635,21 @@ namespace ModernKeePassLib.Utility if(pbData == null) throw new ArgumentNullException("pbData"); if(pbData.Length == 0) return pbData; - MemoryStream msCompressed = new MemoryStream(); - GZipStream gz = new GZipStream(msCompressed, CompressionMode.Compress); - MemoryStream msSource = new MemoryStream(pbData, false); - MemUtil.CopyStream(msSource, gz); - gz.Dispose(); - msSource.Dispose(); + byte[] pbCompressed; + using(MemoryStream msSource = new MemoryStream(pbData, false)) + { + using(MemoryStream msCompressed = new MemoryStream()) + { + using(GZipStream gz = new GZipStream(msCompressed, + CompressionMode.Compress)) + { + MemUtil.CopyStream(msSource, gz); + } + + pbCompressed = msCompressed.ToArray(); + } + } - byte[] pbCompressed = msCompressed.ToArray(); - msCompressed.Dispose(); return pbCompressed; } @@ -503,15 +658,21 @@ namespace ModernKeePassLib.Utility if(pbCompressed == null) throw new ArgumentNullException("pbCompressed"); if(pbCompressed.Length == 0) return pbCompressed; - MemoryStream msCompressed = new MemoryStream(pbCompressed, false); - GZipStream gz = new GZipStream(msCompressed, CompressionMode.Decompress); - MemoryStream msData = new MemoryStream(); - MemUtil.CopyStream(gz, msData); - gz.Dispose(); - msCompressed.Dispose(); + byte[] pbData; + using(MemoryStream msData = new MemoryStream()) + { + using(MemoryStream msCompressed = new MemoryStream(pbCompressed, false)) + { + using(GZipStream gz = new GZipStream(msCompressed, + CompressionMode.Decompress)) + { + MemUtil.CopyStream(gz, msData); + } + } + + pbData = msData.ToArray(); + } - byte[] pbData = msData.ToArray(); - msData.Dispose(); return pbData; } diff --git a/ModernKeePassLib/Utility/MessageService.cs b/ModernKeePassLib/Utility/MessageService.cs index 77e087b..becddf3 100644 --- a/ModernKeePassLib/Utility/MessageService.cs +++ b/ModernKeePassLib/Utility/MessageService.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,9 +20,12 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; -using System.Text; -using System.Windows.Forms; using System.Diagnostics; +using System.Text; + +#if !KeePassUAP +using System.Windows.Forms; +#endif using ModernKeePassLib.Resources; using ModernKeePassLib.Serialization; @@ -94,7 +97,9 @@ namespace ModernKeePassLib.Utility get { return m_uCurrentMessageCount; } } +#if !KeePassUAP public static event EventHandler MessageShowing; +#endif private static string ObjectsToMessage(object[] vLines) { @@ -105,7 +110,7 @@ namespace ModernKeePassLib.Utility { if(vLines == null) return string.Empty; - string strNewPara = MessageService.NewParagraph; + string strNewPara = Environment.NewLine; StringBuilder sbText = new StringBuilder(); bool bSeparator = false; @@ -168,6 +173,7 @@ namespace ModernKeePassLib.Utility } #endif +#if !KeePassUAP internal static DialogResult SafeShowMessageBox(string strText, string strTitle, MessageBoxButtons mb, MessageBoxIcon mi, MessageBoxDefaultButton mdb) { @@ -275,19 +281,21 @@ namespace ModernKeePassLib.Utility ++m_uCurrentMessageCount; string strTitle = PwDefs.ShortProductName + @" - " + KLRes.FatalError; - string strText = KLRes.FatalErrorText + MessageService.NewParagraph + - KLRes.ErrorInClipboard + MessageService.NewParagraph + + string strText = KLRes.FatalErrorText + Environment.NewLine + + KLRes.ErrorInClipboard + Environment.NewLine + // Please send it to the KeePass developers. - // KLRes.ErrorFeedbackRequest + MessageService.NewParagraph + + // KLRes.ErrorFeedbackRequest + Environment.NewLine + ObjectsToMessage(vLines); try { -#if !KeePassLibSD - Clipboard.Clear(); - Clipboard.SetText(ObjectsToMessage(vLines, true)); + string strDetails = ObjectsToMessage(vLines, true); + +#if KeePassLibSD + Clipboard.SetDataObject(strDetails); #else - Clipboard.SetDataObject(ObjectsToMessage(vLines, true)); + Clipboard.Clear(); + Clipboard.SetText(strDetails); #endif } catch(Exception) { Debug.Assert(false); } @@ -364,21 +372,7 @@ namespace ModernKeePassLib.Utility public static void ShowLoadWarning(string strFilePath, Exception ex, bool bFullException) { - string str = string.Empty; - - if((strFilePath != null) && (strFilePath.Length > 0)) - str += strFilePath + MessageService.NewParagraph; - - str += KLRes.FileLoadFailed; - - if((ex != null) && (ex.Message != null) && (ex.Message.Length > 0)) - { - str += MessageService.NewParagraph; - if(!bFullException) str += ex.Message; - else str += ObjectsToMessage(new object[] { ex }, true); - } - - ShowWarning(str); + ShowWarning(GetLoadWarningMessage(strFilePath, ex, bFullException)); } public static void ShowLoadWarning(IOConnectionInfo ioConnection, Exception ex) @@ -398,18 +392,7 @@ namespace ModernKeePassLib.Utility return; } - string str = string.Empty; - if((strFilePath != null) && (strFilePath.Length > 0)) - str += strFilePath + MessageService.NewParagraph; - - str += KLRes.FileSaveFailed; - - if((ex != null) && (ex.Message != null) && (ex.Message.Length > 0)) - str += MessageService.NewParagraph + ex.Message; - - if(bCorruptionWarning) - str += MessageService.NewParagraph + KLRes.FileSaveCorruptionWarning; - + string str = GetSaveWarningMessage(strFilePath, ex, bCorruptionWarning); ShowWarning(str); } @@ -420,6 +403,45 @@ namespace ModernKeePassLib.Utility ShowSaveWarning(ioConnection.GetDisplayName(), ex, bCorruptionWarning); else ShowWarning(ex); } +#endif // !KeePassUAP + + internal static string GetLoadWarningMessage(string strFilePath, + Exception ex, bool bFullException) + { + string str = string.Empty; + + if(!string.IsNullOrEmpty(strFilePath)) + str += strFilePath + Environment.NewLine; + + str += KLRes.FileLoadFailed; + + if((ex != null) && !string.IsNullOrEmpty(ex.Message)) + { + str += Environment.NewLine; + if(!bFullException) str += ex.Message; + else str += ObjectsToMessage(new object[] { ex }, true); + } + + return str; + } + + internal static string GetSaveWarningMessage(string strFilePath, + Exception ex, bool bCorruptionWarning) + { + string str = string.Empty; + if(!string.IsNullOrEmpty(strFilePath)) + str += strFilePath + Environment.NewLine; + + str += KLRes.FileSaveFailed; + + if((ex != null) && !string.IsNullOrEmpty(ex.Message)) + str += Environment.NewLine + ex.Message; + + if(bCorruptionWarning) + str += Environment.NewLine + KLRes.FileSaveCorruptionWarning; + + return str; + } public static void ExternalIncrementMessageCount() { diff --git a/ModernKeePassLib/Utility/MonoWorkarounds.cs b/ModernKeePassLib/Utility/MonoWorkarounds.cs index 53f362c..13849ea 100644 --- a/ModernKeePassLib/Utility/MonoWorkarounds.cs +++ b/ModernKeePassLib/Utility/MonoWorkarounds.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2014 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,13 +17,23 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#if DEBUG +// #define DEBUG_BREAKONFAIL +#endif + using System; using System.Collections.Generic; -using System.Text; -using System.Windows.Forms; using System.ComponentModel; -using System.Reflection; using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Xml; + +#if !KeePassUAP +using System.Windows.Forms; +#endif using ModernKeePassLib.Native; @@ -31,19 +41,72 @@ namespace ModernKeePassLib.Utility { public static class MonoWorkarounds { - private static bool? m_bReq = null; + private const string AppXDoTool = "xdotool"; + + private static Dictionary g_dForceReq = new Dictionary(); + private static Thread g_thFixClip = null; + // private static Predicate g_fOwnWindow = null; + +#if DEBUG_BREAKONFAIL + private static DebugBreakTraceListener g_tlBreak = null; +#endif + + private static bool? g_bReq = null; public static bool IsRequired() { - if(!m_bReq.HasValue) m_bReq = NativeLib.IsUnix(); - return m_bReq.Value; + if(!g_bReq.HasValue) g_bReq = NativeLib.IsUnix(); + return g_bReq.Value; } + // 1219: + // Mono prepends byte order mark (BOM) to StdIn. + // https://sourceforge.net/p/keepass/bugs/1219/ // 1245: // Key events not raised while Alt is down, and nav keys out of order. // https://sourceforge.net/p/keepass/bugs/1245/ // 1254: // NumericUpDown bug: text is drawn below up/down buttons. // https://sourceforge.net/p/keepass/bugs/1254/ + // 1354: + // Finalizer of NotifyIcon throws on Unity. + // See also 1574. + // https://sourceforge.net/p/keepass/bugs/1354/ + // 1358: + // FileDialog crashes when ~/.recently-used is invalid. + // https://sourceforge.net/p/keepass/bugs/1358/ + // 1366: + // Drawing bug when scrolling a RichTextBox. + // https://sourceforge.net/p/keepass/bugs/1366/ + // 1378: + // Mono doesn't implement Microsoft.Win32.SystemEvents events. + // https://sourceforge.net/p/keepass/bugs/1378/ + // https://github.com/mono/mono/blob/master/mcs/class/System/Microsoft.Win32/SystemEvents.cs + // 1418: + // Minimizing a form while loading it doesn't work. + // https://sourceforge.net/p/keepass/bugs/1418/ + // 1468: + // Use LibGCrypt for AES-KDF, because Mono's implementations + // of RijndaelManaged and AesCryptoServiceProvider are slow. + // https://sourceforge.net/p/keepass/bugs/1468/ + // 1527: + // Timer causes 100% CPU load. + // https://sourceforge.net/p/keepass/bugs/1527/ + // 1530: + // Mono's clipboard functions don't work properly. + // https://sourceforge.net/p/keepass/bugs/1530/ + // 1574: + // Finalizer of NotifyIcon throws on Mac OS X. + // See also 1354. + // https://sourceforge.net/p/keepass/bugs/1574/ + // 1632: + // RichTextBox rendering bug for bold/italic text. + // https://sourceforge.net/p/keepass/bugs/1632/ + // 2139: + // Shortcut keys are ignored. + // https://sourceforge.net/p/keepass/feature-requests/2139/ + // 2140: + // Explicit control focusing is ignored. + // https://sourceforge.net/p/keepass/feature-requests/2140/ // 5795: // Text in input field is incomplete. // https://bugzilla.xamarin.com/show_bug.cgi?id=5795 @@ -60,6 +123,12 @@ namespace ModernKeePassLib.Utility // PictureBox not rendered when bitmap height >= control height. // https://bugzilla.xamarin.com/show_bug.cgi?id=12525 // https://sourceforge.net/p/keepass/discussion/329220/thread/54f61e9a/ + // 100001: + // Control locations/sizes are invalid/unexpected. + // [NoRef] + // 373134: + // Control.InvokeRequired doesn't always return the correct value. + // https://bugzilla.novell.com/show_bug.cgi?id=373134 // 586901: // RichTextBox doesn't handle Unicode string correctly. // https://bugzilla.novell.com/show_bug.cgi?id=586901 @@ -71,17 +140,23 @@ namespace ModernKeePassLib.Utility // https://bugzilla.novell.com/show_bug.cgi?id=649266 // 686017: // Minimum sizes must be enforced. - // http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=686017 + // https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=686017 // 801414: // Mono recreates the main window incorrectly. // https://bugs.launchpad.net/ubuntu/+source/keepass2/+bug/801414 // 891029: - // Increase tab control height, otherwise Mono throws exceptions. + // Increase tab control height and don't use images on tabs. // https://sourceforge.net/projects/keepass/forums/forum/329221/topic/4519750 // https://bugs.launchpad.net/ubuntu/+source/keepass2/+bug/891029 + // https://sourceforge.net/p/keepass/bugs/1256/ + // https://sourceforge.net/p/keepass/bugs/1566/ + // https://sourceforge.net/p/keepass/bugs/1634/ // 836428016: // ListView group header selection unsupported. // https://sourceforge.net/p/keepass/discussion/329221/thread/31dae0f0/ + // 2449941153: + // RichTextBox doesn't properly escape '}' when generating RTF data. + // https://sourceforge.net/p/keepass/discussion/329221/thread/920722a1/ // 3574233558: // Problems with minimizing windows, no content rendered. // https://sourceforge.net/p/keepass/discussion/329220/thread/d50a79d6/ @@ -89,6 +164,9 @@ namespace ModernKeePassLib.Utility { if(!MonoWorkarounds.IsRequired()) return false; + bool bForce; + if(g_dForceReq.TryGetValue(uBugID, out bForce)) return bForce; + ulong v = NativeLib.MonoVersion; if(v != 0) { @@ -99,6 +177,140 @@ namespace ModernKeePassLib.Utility return true; } + internal static void SetEnabled(string strIDs, bool bEnabled) + { + if(string.IsNullOrEmpty(strIDs)) return; + + string[] vIDs = strIDs.Split(new char[] { ',' }); + foreach(string strID in vIDs) + { + if(string.IsNullOrEmpty(strID)) continue; + + uint uID; + if(StrUtil.TryParseUInt(strID.Trim(), out uID)) + g_dForceReq[uID] = bEnabled; + } + } + + internal static void Initialize() + { + Terminate(); + + // g_fOwnWindow = fOwnWindow; + + if(IsRequired(1530)) + { + try + { + ThreadStart ts = new ThreadStart(MonoWorkarounds.FixClipThread); + g_thFixClip = new Thread(ts); + g_thFixClip.Start(); + } + catch(Exception) { Debug.Assert(false); } + } + +#if DEBUG_BREAKONFAIL + if(IsRequired() && (g_tlBreak == null)) + { + g_tlBreak = new DebugBreakTraceListener(); + Debug.Listeners.Add(g_tlBreak); + } +#endif + } + + internal static void Terminate() + { + if(g_thFixClip != null) + { + try { g_thFixClip.Abort(); } + catch(Exception) { Debug.Assert(false); } + + g_thFixClip = null; + } + } + + private static void FixClipThread() + { + try + { +#if !KeePassUAP + const int msDelay = 250; + + string strTest = ClipboardU.GetText(); + if(strTest == null) return; // No clipboard support + + // Without XDoTool, the workaround would be applied to + // all applications, which may corrupt the clipboard + // when it doesn't contain simple text only; + // https://sourceforge.net/p/keepass/bugs/1603/#a113 + strTest = (NativeLib.RunConsoleApp(AppXDoTool, + "help") ?? string.Empty).Trim(); + if(strTest.Length == 0) return; + + Thread.Sleep(msDelay); + + string strLast = null; + while(true) + { + string str = ClipboardU.GetText(); + if(str == null) { Debug.Assert(false); } + else if(str != strLast) + { + if(NeedClipboardWorkaround()) + ClipboardU.SetText(str, true); + + strLast = str; + } + + Thread.Sleep(msDelay); + } +#endif + } + catch(ThreadAbortException) + { + try { Thread.ResetAbort(); } + catch(Exception) { Debug.Assert(false); } + } + catch(Exception) { Debug.Assert(false); } + finally { g_thFixClip = null; } + } + +#if !KeePassUAP + private static bool NeedClipboardWorkaround() + { + try + { + string strHandle = (NativeLib.RunConsoleApp(AppXDoTool, + "getactivewindow") ?? string.Empty).Trim(); + if(strHandle.Length == 0) { Debug.Assert(false); return false; } + + // IntPtr h = new IntPtr(long.Parse(strHandle)); + long.Parse(strHandle); // Validate + + // Detection of own windows based on Form.Handle + // comparisons doesn't work reliably (Mono's handles + // are usually off by 1) + // Predicate fOwnWindow = g_fOwnWindow; + // if(fOwnWindow != null) + // { + // if(fOwnWindow(h)) return true; + // } + // else { Debug.Assert(false); } + + string strWmClass = (NativeLib.RunConsoleApp("xprop", + "-id " + strHandle + " WM_CLASS") ?? string.Empty); + + if(strWmClass.IndexOf("\"" + PwDefs.ResClass + "\"", + StrUtil.CaseIgnoreCmp) >= 0) return true; + if(strWmClass.IndexOf("\"Remmina\"", + StrUtil.CaseIgnoreCmp) >= 0) return true; + } + catch(ThreadAbortException) { throw; } + catch(Exception) { Debug.Assert(false); } + + return false; + } + public static void ApplyTo(Form f) { if(!MonoWorkarounds.IsRequired()) return; @@ -320,5 +532,57 @@ namespace ModernKeePassLib.Utility return true; } #endif + + /// + /// Ensure that the file ~/.recently-used is valid (in order to + /// prevent Mono's FileDialog from crashing). + /// + internal static void EnsureRecentlyUsedValid() + { + if(!MonoWorkarounds.IsRequired(1358)) return; + + try + { + string strFile = Environment.GetFolderPath( + Environment.SpecialFolder.Personal); + strFile = UrlUtil.EnsureTerminatingSeparator(strFile, false); + strFile += ".recently-used"; + + if(File.Exists(strFile)) + { + try + { + // Mono's WriteRecentlyUsedFiles method also loads the + // XML file using XmlDocument + XmlDocument xd = new XmlDocument(); + xd.Load(strFile); + } + catch(Exception) // The XML file is invalid + { + File.Delete(strFile); + } + } + } + catch(Exception) { Debug.Assert(false); } + } +#endif // !KeePassUAP + +#if DEBUG_BREAKONFAIL + private sealed class DebugBreakTraceListener : TraceListener + { + public override void Fail(string message) + { + Debugger.Break(); + } + + public override void Fail(string message, string detailMessage) + { + Debugger.Break(); + } + + public override void Write(string message) { } + public override void WriteLine(string message) { } + } +#endif } } diff --git a/ModernKeePassLib/Utility/StrUtil.cs b/ModernKeePassLib/Utility/StrUtil.cs index 5a0a6bc..a29cf4a 100644 --- a/ModernKeePassLib/Utility/StrUtil.cs +++ b/ModernKeePassLib/Utility/StrUtil.cs @@ -360,9 +360,9 @@ namespace ModernKeePassLib.Utility } /// - /// Split up a command-line into application and argument. + /// Split up a command line into application and argument. /// - /// Command-line to split. + /// Command line to split. /// Application path. /// Arguments. public static void SplitCommandLine(string strCmdLine, out string strApp, out string strArgs) @@ -501,7 +501,7 @@ namespace ModernKeePassLib.Utility #if !KeePassLibSD #if !ModernKeePassLib && !KeePassRT if(excp.TargetSite != null) - strText += excp.TargetSite.ToString() + MessageService.NewLine; + strText += excp.TargetSite.ToString() + Environment.NewLine; #endif if(excp.Data != null) @@ -671,7 +671,8 @@ namespace ModernKeePassLib.Utility return DateTime.TryParse(str, out dt); #else try { dt = DateTime.Parse(str); return true; } - catch(Exception) { dt = DateTime.MinValue; return false; } + catch(Exception) { dt = DateTime.UtcNow; } + return false; #endif } @@ -720,7 +721,7 @@ namespace ModernKeePassLib.Utility /// /// Removes all characters that are not valid XML characters, - /// according to http://www.w3.org/TR/xml/#charsets . + /// according to https://www.w3.org/TR/xml/#charsets . /// /// Source text. /// Text containing only valid XML characters. @@ -762,7 +763,7 @@ namespace ModernKeePassLib.Utility return sb.ToString(); } - private static Regex m_rxNaturalSplit = null; + /* private static Regex g_rxNaturalSplit = null; public static int CompareNaturally(string strX, string strY) { Debug.Assert(strX != null); @@ -773,39 +774,31 @@ namespace ModernKeePassLib.Utility if(NativeMethods.SupportsStrCmpNaturally) return NativeMethods.StrCmpNaturally(strX, strY); - strX = strX.ToLower(); // Case-insensitive comparison - strY = strY.ToLower(); + if(g_rxNaturalSplit == null) + g_rxNaturalSplit = new Regex(@"([0-9]+)", RegexOptions.Compiled); - if(m_rxNaturalSplit == null) - m_rxNaturalSplit = new Regex(@"([0-9]+)", -#if ModernKeePassLib || KeePassRT - RegexOptions.None); -#else - RegexOptions.Compiled); -#endif + string[] vPartsX = g_rxNaturalSplit.Split(strX); + string[] vPartsY = g_rxNaturalSplit.Split(strY); - string[] vPartsX = m_rxNaturalSplit.Split(strX); - string[] vPartsY = m_rxNaturalSplit.Split(strY); - - for(int i = 0; i < Math.Min(vPartsX.Length, vPartsY.Length); ++i) + int n = Math.Min(vPartsX.Length, vPartsY.Length); + for(int i = 0; i < n; ++i) { string strPartX = vPartsX[i], strPartY = vPartsY[i]; int iPartCompare; #if KeePassLibSD - ulong uX = 0, uY = 0; try { - uX = ulong.Parse(strPartX); - uY = ulong.Parse(strPartY); + ulong uX = ulong.Parse(strPartX); + ulong uY = ulong.Parse(strPartY); iPartCompare = uX.CompareTo(uY); } - catch(Exception) { iPartCompare = strPartX.CompareTo(strPartY); } + catch(Exception) { iPartCompare = string.Compare(strPartX, strPartY, true); } #else ulong uX, uY; if(ulong.TryParse(strPartX, out uX) && ulong.TryParse(strPartY, out uY)) iPartCompare = uX.CompareTo(uY); - else iPartCompare = strPartX.CompareTo(strPartY); + else iPartCompare = string.Compare(strPartX, strPartY, true); #endif if(iPartCompare != 0) return iPartCompare; @@ -814,6 +807,114 @@ namespace ModernKeePassLib.Utility if(vPartsX.Length == vPartsY.Length) return 0; if(vPartsX.Length < vPartsY.Length) return -1; return 1; + } */ + + public static int CompareNaturally(string strX, string strY) + { + Debug.Assert(strX != null); + if(strX == null) throw new ArgumentNullException("strX"); + Debug.Assert(strY != null); + if(strY == null) throw new ArgumentNullException("strY"); + + if(NativeMethods.SupportsStrCmpNaturally) + return NativeMethods.StrCmpNaturally(strX, strY); + + int cX = strX.Length; + int cY = strY.Length; + if(cX == 0) return ((cY == 0) ? 0 : -1); + if(cY == 0) return 1; + + char chFirstX = strX[0]; + char chFirstY = strY[0]; + bool bExpNum = ((chFirstX >= '0') && (chFirstX <= '9')); + bool bExpNumY = ((chFirstY >= '0') && (chFirstY <= '9')); +#if ModernKeePassLib + if (bExpNum != bExpNumY) return StringComparer.OrdinalIgnoreCase.Compare(strX, strY); +#else + if(bExpNum != bExpNumY) return string.Compare(strX, strY, true); +#endif + + int pX = 0; + int pY = 0; + while((pX < cX) && (pY < cY)) + { + Debug.Assert(((strX[pX] >= '0') && (strX[pX] <= '9')) == bExpNum); + Debug.Assert(((strY[pY] >= '0') && (strY[pY] <= '9')) == bExpNum); + + int pExclX = pX + 1; + while(pExclX < cX) + { + char ch = strX[pExclX]; + bool bChNum = ((ch >= '0') && (ch <= '9')); + if(bChNum != bExpNum) break; + ++pExclX; + } + + int pExclY = pY + 1; + while(pExclY < cY) + { + char ch = strY[pExclY]; + bool bChNum = ((ch >= '0') && (ch <= '9')); + if(bChNum != bExpNum) break; + ++pExclY; + } + + string strPartX = strX.Substring(pX, pExclX - pX); + string strPartY = strY.Substring(pY, pExclY - pY); + + bool bStrCmp = true; + if(bExpNum) + { + // 2^64 - 1 = 18446744073709551615 has length 20 + if((strPartX.Length <= 19) && (strPartY.Length <= 19)) + { + ulong uX, uY; + if(ulong.TryParse(strPartX, out uX) && ulong.TryParse(strPartY, out uY)) + { + if(uX < uY) return -1; + if(uX > uY) return 1; + + bStrCmp = false; + } + else { Debug.Assert(false); } + } + else + { + double dX, dY; + if(double.TryParse(strPartX, out dX) && double.TryParse(strPartY, out dY)) + { + if(dX < dY) return -1; + if(dX > dY) return 1; + + bStrCmp = false; + } + else { Debug.Assert(false); } + } + } + if(bStrCmp) + { +#if ModernKeePassLib + int c = StringComparer.OrdinalIgnoreCase.Compare(strPartX, strPartY); +#else + int c = string.Compare(strPartX, strPartY, true); +#endif + if(c != 0) return c; + } + + bExpNum = !bExpNum; + pX = pExclX; + pY = pExclY; + } + + if(pX >= cX) + { + Debug.Assert(pX == cX); + if(pY >= cY) { Debug.Assert(pY == cY); return 0; } + return -1; + } + + Debug.Assert(pY == cY); + return 1; } public static string RemoveAccelerator(string strMenuText) @@ -888,13 +989,12 @@ namespace ModernKeePassLib.Utility public static bool IsHexString(string str, bool bStrict) { if(str == null) throw new ArgumentNullException("str"); - if(str.Length == 0) return true; foreach(char ch in str) { if((ch >= '0') && (ch <= '9')) continue; - if((ch >= 'a') && (ch <= 'z')) continue; - if((ch >= 'A') && (ch <= 'Z')) continue; + if((ch >= 'a') && (ch <= 'f')) continue; + if((ch >= 'A') && (ch <= 'F')) continue; if(bStrict) return false; @@ -907,8 +1007,31 @@ namespace ModernKeePassLib.Utility return true; } + public static bool IsHexString(byte[] pbUtf8, bool bStrict) + { + if(pbUtf8 == null) throw new ArgumentNullException("pbUtf8"); + + for(int i = 0; i < pbUtf8.Length; ++i) + { + byte bt = pbUtf8[i]; + if((bt >= (byte)'0') && (bt <= (byte)'9')) continue; + if((bt >= (byte)'a') && (bt <= (byte)'f')) continue; + if((bt >= (byte)'A') && (bt <= (byte)'F')) continue; + + if(bStrict) return false; + + if((bt == (byte)' ') || (bt == (byte)'\t') || + (bt == (byte)'\r') || (bt == (byte)'\n')) + continue; + + return false; + } + + return true; + } + #if !KeePassLibSD - private static readonly char[] m_vPatternPartsSep = new char[]{ '*' }; + private static readonly char[] m_vPatternPartsSep = new char[] { '*' }; public static bool SimplePatternMatch(string strPattern, string strText, StringComparison sc) { @@ -1252,7 +1375,7 @@ namespace ModernKeePassLib.Utility return v; } - private static readonly char[] m_vTagSep = new char[]{ ',', ';', ':' }; + private static readonly char[] m_vTagSep = new char[] { ',', ';', ':' }; public static string TagsToString(List vTags, bool bForDisplay) { if(vTags == null) throw new ArgumentNullException("vTags"); @@ -1625,5 +1748,16 @@ namespace ModernKeePassLib.Utility return iCount; } + + internal static string ReplaceNulls(string str) + { + if(str == null) { Debug.Assert(false); return null; } + + if(str.IndexOf('\0') < 0) return str; + + // Replacing null characters by spaces is the + // behavior of Notepad (on Windows 10) + return str.Replace('\0', ' '); + } } } diff --git a/ModernKeePassLib/Utility/TimeUtil.cs b/ModernKeePassLib/Utility/TimeUtil.cs index 9963dc3..7688986 100644 --- a/ModernKeePassLib/Utility/TimeUtil.cs +++ b/ModernKeePassLib/Utility/TimeUtil.cs @@ -38,20 +38,43 @@ namespace ModernKeePassLib.Utility /// public const int PwTimeLength = 7; + public static readonly DateTime SafeMinValueUtc = new DateTime( + DateTime.MinValue.Ticks + TimeSpan.TicksPerDay, DateTimeKind.Utc); + public static readonly DateTime SafeMaxValueUtc = new DateTime( + DateTime.MaxValue.Ticks - TimeSpan.TicksPerDay, DateTimeKind.Utc); + #if !KeePassLibSD private static string m_strDtfStd = null; private static string m_strDtfDate = null; #endif + // private static long m_lTicks2PowLess1s = 0; + + private static DateTime? m_odtUnixRoot = null; + public static DateTime UnixRoot + { + get + { + if(m_odtUnixRoot.HasValue) return m_odtUnixRoot.Value; + + DateTime dtRoot = new DateTime(1970, 1, 1, 0, 0, 0, 0, + DateTimeKind.Utc); + + m_odtUnixRoot = dtRoot; + return dtRoot; + } + } + /// /// Pack a DateTime object into 5 bytes. Layout: 2 zero bits, /// year 12 bits, month 4 bits, day 5 bits, hour 5 bits, minute 6 /// bits, second 6 bits. /// - /// - /// + [Obsolete] public static byte[] PackTime(DateTime dt) { + dt = ToLocal(dt, true); + byte[] pb = new byte[5]; // Pack time to 5 byte structure: @@ -73,6 +96,7 @@ namespace ModernKeePassLib.Utility /// /// Packed time, 5 bytes. /// Unpacked DateTime object. + [Obsolete] public static DateTime UnpackTime(byte[] pb) { Debug.Assert((pb != null) && (pb.Length == 5)); @@ -89,7 +113,8 @@ namespace ModernKeePassLib.Utility int nMinute = ((n4 & 0x0000000F) << 2) | (n5 >> 6); int nSecond = n5 & 0x0000003F; - return new DateTime(nYear, nMonth, nDay, nHour, nMinute, nSecond); + return (new DateTime(nYear, nMonth, nDay, nHour, nMinute, + nSecond, DateTimeKind.Local)).ToUniversalTime(); } /// @@ -97,10 +122,13 @@ namespace ModernKeePassLib.Utility /// /// Object to be encoded. /// Packed time, 7 bytes (PW_TIME). + [Obsolete] public static byte[] PackPwTime(DateTime dt) { Debug.Assert(PwTimeLength == 7); + dt = ToLocal(dt, true); + byte[] pb = new byte[7]; pb[0] = (byte)(dt.Year & 0xFF); pb[1] = (byte)(dt.Year >> 8); @@ -118,6 +146,7 @@ namespace ModernKeePassLib.Utility /// /// Packed time, 7 bytes. /// Unpacked DateTime object. + [Obsolete] public static DateTime UnpackPwTime(byte[] pb) { Debug.Assert(PwTimeLength == 7); @@ -125,8 +154,8 @@ namespace ModernKeePassLib.Utility Debug.Assert(pb != null); if(pb == null) throw new ArgumentNullException("pb"); Debug.Assert(pb.Length == 7); if(pb.Length != 7) throw new ArgumentException(); - return new DateTime(((int)pb[1] << 8) | (int)pb[0], (int)pb[2], (int)pb[3], - (int)pb[4], (int)pb[5], (int)pb[6]); + return (new DateTime(((int)pb[1] << 8) | (int)pb[0], (int)pb[2], (int)pb[3], + (int)pb[4], (int)pb[5], (int)pb[6], DateTimeKind.Local)).ToUniversalTime(); } /// @@ -136,23 +165,32 @@ namespace ModernKeePassLib.Utility /// String representing the specified DateTime object. public static string ToDisplayString(DateTime dt) { - return dt.ToString(); + return ToLocal(dt, true).ToString(); } public static string ToDisplayStringDateOnly(DateTime dt) { - return dt.ToString("d"); + return ToLocal(dt, true).ToString("d"); } public static DateTime FromDisplayString(string strDisplay) { DateTime dt; + if(FromDisplayStringEx(strDisplay, out dt)) return dt; + return DateTime.Now; + } + public static bool FromDisplayStringEx(string strDisplay, out DateTime dt) + { #if KeePassLibSD - try { dt = DateTime.Parse(strDisplay); return dt; } + try { dt = ToLocal(DateTime.Parse(strDisplay), true); return true; } catch(Exception) { } #else - if(DateTime.TryParse(strDisplay, out dt)) return dt; + if(DateTime.TryParse(strDisplay, out dt)) + { + dt = ToLocal(dt, true); + return true; + } // For some custom formats specified using the Control Panel, // DateTime.ToString returns the correct string, but @@ -160,19 +198,25 @@ namespace ModernKeePassLib.Utility // https://sourceforge.net/p/keepass/discussion/329221/thread/3a225b29/?limit=25&page=1#c6ae if((m_strDtfStd == null) || (m_strDtfDate == null)) { - DateTime dtUni = new DateTime(2111, 3, 4, 5, 6, 7); + DateTime dtUni = new DateTime(2111, 3, 4, 5, 6, 7, DateTimeKind.Local); m_strDtfStd = DeriveCustomFormat(ToDisplayString(dtUni), dtUni); m_strDtfDate = DeriveCustomFormat(ToDisplayStringDateOnly(dtUni), dtUni); } const DateTimeStyles dts = DateTimeStyles.AllowWhiteSpaces; if(DateTime.TryParseExact(strDisplay, m_strDtfStd, null, dts, out dt)) - return dt; + { + dt = ToLocal(dt, true); + return true; + } if(DateTime.TryParseExact(strDisplay, m_strDtfDate, null, dts, out dt)) - return dt; + { + dt = ToLocal(dt, true); + return true; + } #endif Debug.Assert(false); - return DateTime.Now; + return false; } #if !KeePassLibSD @@ -262,8 +306,10 @@ namespace ModernKeePassLib.Utility public static string SerializeUtc(DateTime dt) { - string str = dt.ToUniversalTime().ToString("s"); - if(str.EndsWith("Z") == false) str += "Z"; + Debug.Assert(dt.Kind != DateTimeKind.Unspecified); + + string str = ToUtc(dt, false).ToString("s"); + if(!str.EndsWith("Z")) str += "Z"; return str; } @@ -274,37 +320,40 @@ namespace ModernKeePassLib.Utility if(str.EndsWith("Z")) str = str.Substring(0, str.Length - 1); bool bResult = StrUtil.TryParseDateTime(str, out dt); - if(bResult) dt = dt.ToLocalTime(); + if(bResult) dt = ToUtc(dt, true); return bResult; } - private static DateTime? m_dtUnixRoot = null; + public static double SerializeUnix(DateTime dt) + { + return (ToUtc(dt, false) - TimeUtil.UnixRoot).TotalSeconds; + } + public static DateTime ConvertUnixTime(double dtUnix) { - try - { - if(!m_dtUnixRoot.HasValue) - m_dtUnixRoot = (new DateTime(1970, 1, 1, 0, 0, 0, 0, - DateTimeKind.Utc)).ToLocalTime(); - - return m_dtUnixRoot.Value.AddSeconds(dtUnix); - } + try { return TimeUtil.UnixRoot.AddSeconds(dtUnix); } catch(Exception) { Debug.Assert(false); } - return DateTime.Now; + return DateTime.UtcNow; } #if !KeePassLibSD + [Obsolete] + public static DateTime? ParseUSTextDate(string strDate) + { + return ParseUSTextDate(strDate, DateTimeKind.Unspecified); + } + private static string[] m_vUSMonths = null; /// /// Parse a US textual date string, like e.g. "January 02, 2012". /// - public static DateTime? ParseUSTextDate(string strDate) + public static DateTime? ParseUSTextDate(string strDate, DateTimeKind k) { if(strDate == null) { Debug.Assert(false); return null; } if(m_vUSMonths == null) - m_vUSMonths = new string[]{ "January", "February", "March", + m_vUSMonths = new string[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; @@ -314,14 +363,14 @@ namespace ModernKeePassLib.Utility if(str.StartsWith(m_vUSMonths[i], StrUtil.CaseIgnoreCmp)) { str = str.Substring(m_vUSMonths[i].Length); - string[] v = str.Split(new char[]{ ',', ';' }); + string[] v = str.Split(new char[] { ',', ';' }); if((v == null) || (v.Length != 2)) return null; string strDay = v[0].Trim().TrimStart('0'); int iDay, iYear; if(int.TryParse(strDay, out iDay) && int.TryParse(v[1].Trim(), out iYear)) - return new DateTime(iYear, i + 1, iDay); + return new DateTime(iYear, i + 1, iDay, 0, 0, 0, k); else { Debug.Assert(false); return null; } } } @@ -331,11 +380,13 @@ namespace ModernKeePassLib.Utility #endif private static readonly DateTime m_dtInvMin = - new DateTime(2999, 12, 27, 23, 59, 59); + new DateTime(2999, 12, 27, 23, 59, 59, DateTimeKind.Utc); private static readonly DateTime m_dtInvMax = - new DateTime(2999, 12, 29, 23, 59, 59); + new DateTime(2999, 12, 29, 23, 59, 59, DateTimeKind.Utc); public static int Compare(DateTime dtA, DateTime dtB, bool bUnkIsPast) { + Debug.Assert(dtA.Kind == dtB.Kind); + if(bUnkIsPast) { // 2999-12-28 23:59:59 in KeePass 1.x means 'unknown'; @@ -368,5 +419,64 @@ namespace ModernKeePassLib.Utility return Compare(tlA.LastModificationTime, tlB.LastModificationTime, bUnkIsPast); } + + public static DateTime ToUtc(DateTime dt, bool bUnspecifiedIsUtc) + { + DateTimeKind k = dt.Kind; + if(k == DateTimeKind.Utc) return dt; + if(k == DateTimeKind.Local) return dt.ToUniversalTime(); + + Debug.Assert(k == DateTimeKind.Unspecified); + if(bUnspecifiedIsUtc) + return new DateTime(dt.Ticks, DateTimeKind.Utc); + return dt.ToUniversalTime(); // Unspecified = local + } + + public static DateTime ToLocal(DateTime dt, bool bUnspecifiedIsLocal) + { + DateTimeKind k = dt.Kind; + if(k == DateTimeKind.Local) return dt; + if(k == DateTimeKind.Utc) return dt.ToLocalTime(); + + Debug.Assert(k == DateTimeKind.Unspecified); + if(bUnspecifiedIsLocal) + return new DateTime(dt.Ticks, DateTimeKind.Local); + return dt.ToLocalTime(); // Unspecified = UTC + } + + /* internal static DateTime RoundToMultOf2PowLess1s(DateTime dt) + { + long l2Pow = m_lTicks2PowLess1s; + if(l2Pow == 0) + { + l2Pow = 1; + while(true) + { + l2Pow <<= 1; + if(l2Pow >= TimeSpan.TicksPerSecond) break; + } + l2Pow >>= 1; + m_lTicks2PowLess1s = l2Pow; + + Debug.Assert(TimeSpan.TicksPerSecond == 10000000L); // .NET + Debug.Assert(l2Pow == (1L << 23)); // .NET + } + + long l = dt.Ticks; + if((l % l2Pow) == 0L) return dt; + + // Round down to full second + l /= TimeSpan.TicksPerSecond; + l *= TimeSpan.TicksPerSecond; + + // Round up to multiple of l2Pow + long l2PowM1 = l2Pow - 1L; + l = (l + l2PowM1) & ~l2PowM1; + DateTime dtRnd = new DateTime(l, dt.Kind); + + Debug.Assert((dtRnd.Ticks % l2Pow) == 0L); + Debug.Assert(dtRnd.ToString("u") == dt.ToString("u")); + return dtRnd; + } */ } } diff --git a/ModernKeePassLib/Utility/UrlUtil.cs b/ModernKeePassLib/Utility/UrlUtil.cs index 5ac733a..2ca6f4c 100644 --- a/ModernKeePassLib/Utility/UrlUtil.cs +++ b/ModernKeePassLib/Utility/UrlUtil.cs @@ -23,7 +23,7 @@ using System.IO; using System.Runtime.InteropServices; using System.Text; using System.Diagnostics; - +using System.Globalization; #if ModernKeePassLib //using PCLStorage; using Windows.Storage; @@ -686,10 +686,8 @@ namespace ModernKeePassLib.Utility if(strPath.Length == 0) { Debug.Assert(false); continue; } Debug.Assert(strPath == strPathRaw); - if(!strPath.EndsWith(strExt, StrUtil.CaseIgnoreCmp)) - continue; - - l.Add(strPathRaw); + if(strPath.EndsWith(strExt, StrUtil.CaseIgnoreCmp)) + l.Add(strPathRaw); } } else l.AddRange(v); @@ -725,10 +723,8 @@ namespace ModernKeePassLib.Utility if(strPath.Length == 0) { Debug.Assert(false); continue; } Debug.Assert(strPath == strPathRaw); - if(!strPath.EndsWith(strExt, StrUtil.CaseIgnoreCmp)) - continue; - - l.Add(fi); + if(strPath.EndsWith(strExt, StrUtil.CaseIgnoreCmp)) + l.Add(fi); } } else l.AddRange(v); @@ -736,5 +732,54 @@ namespace ModernKeePassLib.Utility return l; } #endif + + /// + /// Expand shell variables in a string. + /// [0] is the value of %1, etc. + /// + public static string ExpandShellVariables(string strText, string[] vParams) + { + if(strText == null) { Debug.Assert(false); return string.Empty; } + if(vParams == null) { Debug.Assert(false); vParams = new string[0]; } + + string str = strText; + NumberFormatInfo nfi = NumberFormatInfo.InvariantInfo; + + for(int i = 0; i <= 9; ++i) + { + string strPlh = "%" + i.ToString(nfi); + + string strValue = string.Empty; + if((i > 0) && ((i - 1) < vParams.Length)) + strValue = (vParams[i - 1] ?? string.Empty); + + str = str.Replace(strPlh, strValue); + + if(i == 1) + { + // %L is replaced by the long version of %1; e.g. + // HKEY_CLASSES_ROOT\\IE.AssocFile.URL\\Shell\\Open\\Command + str = str.Replace("%L", strValue); + str = str.Replace("%l", strValue); + } + } + + if(str.IndexOf("%*") >= 0) + { + StringBuilder sb = new StringBuilder(); + foreach(string strValue in vParams) + { + if(!string.IsNullOrEmpty(strValue)) + { + if(sb.Length > 0) sb.Append(' '); + sb.Append(strValue); + } + } + + str = str.Replace("%*", sb.ToString()); + } + + return str; + } } }