diff --git a/src/KeePassLib2Android/Collections/AutoTypeConfig.cs b/src/KeePassLib2Android/Collections/AutoTypeConfig.cs index 8df14ce5..dd2d5a28 100644 --- a/src/KeePassLib2Android/Collections/AutoTypeConfig.cs +++ b/src/KeePassLib2Android/Collections/AutoTypeConfig.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Collections/ProtectedBinaryDictionary.cs b/src/KeePassLib2Android/Collections/ProtectedBinaryDictionary.cs index 3b2e4556..beaf2c52 100644 --- a/src/KeePassLib2Android/Collections/ProtectedBinaryDictionary.cs +++ b/src/KeePassLib2Android/Collections/ProtectedBinaryDictionary.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Collections/ProtectedBinarySet.cs b/src/KeePassLib2Android/Collections/ProtectedBinarySet.cs new file mode 100644 index 00000000..1442cb02 --- /dev/null +++ b/src/KeePassLib2Android/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 KeePassLib.Delegates; +using KeePassLib.Security; + +namespace KeePassLib.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/src/KeePassLib2Android/Collections/ProtectedStringDictionary.cs b/src/KeePassLib2Android/Collections/ProtectedStringDictionary.cs index eb34b016..b93068e0 100644 --- a/src/KeePassLib2Android/Collections/ProtectedStringDictionary.cs +++ b/src/KeePassLib2Android/Collections/ProtectedStringDictionary.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Collections/PwObjectList.cs b/src/KeePassLib2Android/Collections/PwObjectList.cs index f18466cb..bde863b7 100644 --- a/src/KeePassLib2Android/Collections/PwObjectList.cs +++ b/src/KeePassLib2Android/Collections/PwObjectList.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Collections/PwObjectPool.cs b/src/KeePassLib2Android/Collections/PwObjectPool.cs index deb05f9b..55ae6777 100644 --- a/src/KeePassLib2Android/Collections/PwObjectPool.cs +++ b/src/KeePassLib2Android/Collections/PwObjectPool.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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,11 +20,12 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Text; using System.Diagnostics; +using System.Text; using KeePassLib.Delegates; using KeePassLib.Interfaces; +using KeePassLib.Utility; #if KeePassLibSD using KeePassLibSD; @@ -182,7 +183,7 @@ namespace KeePassLib.Collections get { return ((m_l.Count > 0) ? m_l[0] : null); } } - private DateTime m_dtLocationChanged = DateTime.MinValue; + private DateTime m_dtLocationChanged = TimeUtil.SafeMinValueUtc; public DateTime LocationChanged { get { return m_dtLocationChanged; } diff --git a/src/KeePassLib2Android/Collections/StringDictionaryEx.cs b/src/KeePassLib2Android/Collections/StringDictionaryEx.cs index 4a1b07ca..efc64e9e 100644 --- a/src/KeePassLib2Android/Collections/StringDictionaryEx.cs +++ b/src/KeePassLib2Android/Collections/StringDictionaryEx.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Collections/VariantDictionary.cs b/src/KeePassLib2Android/Collections/VariantDictionary.cs index 8aa5a48c..509ec312 100644 --- a/src/KeePassLib2Android/Collections/VariantDictionary.cs +++ b/src/KeePassLib2Android/Collections/VariantDictionary.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Cryptography/Cipher/ChaCha20Cipher.cs b/src/KeePassLib2Android/Cryptography/Cipher/ChaCha20Cipher.cs index ad0c93b4..540b6409 100644 --- a/src/KeePassLib2Android/Cryptography/Cipher/ChaCha20Cipher.cs +++ b/src/KeePassLib2Android/Cryptography/Cipher/ChaCha20Cipher.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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 @@ -107,8 +107,11 @@ namespace KeePassLib.Cryptography.Cipher protected override void Dispose(bool bDisposing) { - MemUtil.ZeroArray(m_s); - MemUtil.ZeroArray(m_x); + if(bDisposing) + { + MemUtil.ZeroArray(m_s); + MemUtil.ZeroArray(m_x); + } base.Dispose(bDisposing); } diff --git a/src/KeePassLib2Android/Cryptography/Cipher/ChaCha20Engine.cs b/src/KeePassLib2Android/Cryptography/Cipher/ChaCha20Engine.cs index 2fa6fe1d..2b6da0f9 100644 --- a/src/KeePassLib2Android/Cryptography/Cipher/ChaCha20Engine.cs +++ b/src/KeePassLib2Android/Cryptography/Cipher/ChaCha20Engine.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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 @@ -115,18 +115,21 @@ namespace KeePassLib.Cryptography.Cipher protected override void Dispose(bool bDisposing) { - if(!bDisposing) return; - - if(m_sBase != null) + if(bDisposing) { - m_c.Dispose(); - m_c = null; + if(m_sBase != null) + { + m_c.Dispose(); + m_c = null; - m_sBase.Close(); - m_sBase = null; + m_sBase.Close(); + m_sBase = null; + } + + m_pbBuffer = null; } - m_pbBuffer = null; + base.Dispose(bDisposing); } public override void Flush() diff --git a/src/KeePassLib2Android/Cryptography/Cipher/CipherPool.cs b/src/KeePassLib2Android/Cryptography/Cipher/CipherPool.cs index 4799abd1..e8e25868 100644 --- a/src/KeePassLib2Android/Cryptography/Cipher/CipherPool.cs +++ b/src/KeePassLib2Android/Cryptography/Cipher/CipherPool.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Cryptography/Cipher/CtrBlockCipher.cs b/src/KeePassLib2Android/Cryptography/Cipher/CtrBlockCipher.cs index 87ec3daa..a143306c 100644 --- a/src/KeePassLib2Android/Cryptography/Cipher/CtrBlockCipher.cs +++ b/src/KeePassLib2Android/Cryptography/Cipher/CtrBlockCipher.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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 @@ -53,8 +53,11 @@ namespace KeePassLib.Cryptography.Cipher protected virtual void Dispose(bool bDisposing) { - MemUtil.ZeroByteArray(m_pBlock); - m_iBlockPos = m_pBlock.Length; + if(bDisposing) + { + MemUtil.ZeroByteArray(m_pBlock); + m_iBlockPos = m_pBlock.Length; + } } protected void InvalidateBlock() diff --git a/src/KeePassLib2Android/Cryptography/Cipher/ICipherEngine.cs b/src/KeePassLib2Android/Cryptography/Cipher/ICipherEngine.cs index f647d3f9..a5cb652d 100644 --- a/src/KeePassLib2Android/Cryptography/Cipher/ICipherEngine.cs +++ b/src/KeePassLib2Android/Cryptography/Cipher/ICipherEngine.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Cryptography/Cipher/Salsa20Cipher.cs b/src/KeePassLib2Android/Cryptography/Cipher/Salsa20Cipher.cs index 63d66366..a0385f20 100644 --- a/src/KeePassLib2Android/Cryptography/Cipher/Salsa20Cipher.cs +++ b/src/KeePassLib2Android/Cryptography/Cipher/Salsa20Cipher.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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 @@ -72,8 +72,11 @@ namespace KeePassLib.Cryptography.Cipher protected override void Dispose(bool bDisposing) { - MemUtil.ZeroArray(m_s); - MemUtil.ZeroArray(m_x); + if(bDisposing) + { + MemUtil.ZeroArray(m_s); + MemUtil.ZeroArray(m_x); + } base.Dispose(bDisposing); } diff --git a/src/KeePassLib2Android/Cryptography/Cipher/StandardAesEngine.cs b/src/KeePassLib2Android/Cryptography/Cipher/StandardAesEngine.cs index eebd1248..8e0c07be 100644 --- a/src/KeePassLib2Android/Cryptography/Cipher/StandardAesEngine.cs +++ b/src/KeePassLib2Android/Cryptography/Cipher/StandardAesEngine.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Cryptography/CryptoRandom.cs b/src/KeePassLib2Android/Cryptography/CryptoRandom.cs index d591077c..00f1b736 100644 --- a/src/KeePassLib2Android/Cryptography/CryptoRandom.cs +++ b/src/KeePassLib2Android/Cryptography/CryptoRandom.cs @@ -20,7 +20,10 @@ */ using System; +using System.Collections; +using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.IO; #if !KeePassUAP @@ -34,8 +37,8 @@ using KeePassLib.Utility; namespace KeePassLib.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 @@ -91,12 +94,13 @@ namespace KeePassLib.Cryptography private CryptoRandom() { - Random rWeak = new Random(); - byte[] pb = new byte[8]; - rWeak.NextBytes(pb); - m_uCounter = MemUtil.BytesToUInt64(pb); - AddEntropy(GetSystemData(rWeak)); + // byte[] pb = new byte[8]; + // rWeak.NextBytes(pb); + // m_uCounter = MemUtil.BytesToUInt64(pb); + m_uCounter = (ulong)DateTime.UtcNow.ToBinary(); + + AddEntropy(GetSystemData()); AddEntropy(GetCspData()); } @@ -148,7 +152,7 @@ namespace KeePassLib.Cryptography } } - private static byte[] GetSystemData(Random rWeak) + private static byte[] GetSystemData() { MemoryStream ms = new MemoryStream(); byte[] pb; @@ -175,32 +179,40 @@ namespace KeePassLib.Cryptography */ #endif - pb = MemUtil.Int32ToBytes(rWeak.Next()); - MemUtil.Write(ms, pb); - pb = MemUtil.UInt32ToBytes((uint)NativeLib.GetPlatformID()); MemUtil.Write(ms, pb); 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 - Version v = EnvironmentExt.OSVersion.Version; -#else - Version v = Environment.OSVersion.Version; -#endif - pb = MemUtil.Int32ToBytes(v.GetHashCode()); - 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); @@ -249,6 +261,18 @@ namespace KeePassLib.Cryptography } #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(); MemUtil.Write(ms, pb); @@ -257,6 +281,16 @@ namespace KeePassLib.Cryptography 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]; @@ -321,7 +355,7 @@ namespace KeePassLib.Cryptography byte[] pbRandom256 = GenerateRandom256(); Debug.Assert(pbRandom256.Length == 32); - int cbCopy = Math.Min(cbRem, 32); + int cbCopy = Math.Min(cbRem, pbRandom256.Length); Array.Copy(pbRandom256, 0, pbRes, iPos, cbCopy); MemUtil.ZeroByteArray(pbRandom256); diff --git a/src/KeePassLib2Android/Cryptography/CryptoRandomStream.cs b/src/KeePassLib2Android/Cryptography/CryptoRandomStream.cs index 37367f0b..b0081c4b 100644 --- a/src/KeePassLib2Android/Cryptography/CryptoRandomStream.cs +++ b/src/KeePassLib2Android/Cryptography/CryptoRandomStream.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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 @@ -64,7 +64,7 @@ namespace KeePassLib.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 readonly CrsAlgorithm m_crsAlgorithm; @@ -149,6 +149,30 @@ namespace KeePassLib.Cryptography } } + 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); } + } + } + /// /// Get random bytes. /// @@ -220,8 +244,10 @@ namespace KeePassLib.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/src/KeePassLib2Android/Cryptography/CryptoUtil.cs b/src/KeePassLib2Android/Cryptography/CryptoUtil.cs index 9cb7bcd6..c042080f 100644 --- a/src/KeePassLib2Android/Cryptography/CryptoUtil.cs +++ b/src/KeePassLib2Android/Cryptography/CryptoUtil.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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.Diagnostics; -using System.Security.Cryptography; using System.Text; +#if !KeePassUAP +using System.Security.Cryptography; +#endif + using KeePassLib.Utility; namespace KeePassLib.Cryptography diff --git a/src/KeePassLib2Android/Cryptography/Hash/Blake2b.cs b/src/KeePassLib2Android/Cryptography/Hash/Blake2b.cs index 1a446aaa..98aaa37e 100644 --- a/src/KeePassLib2Android/Cryptography/Hash/Blake2b.cs +++ b/src/KeePassLib2Android/Cryptography/Hash/Blake2b.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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 @@ -23,9 +23,12 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Security.Cryptography; using System.Text; +#if !KeePassUAP +using System.Security.Cryptography; +#endif + using KeePassLib.Utility; namespace KeePassLib.Cryptography.Hash diff --git a/src/KeePassLib2Android/Cryptography/HashingStreamEx.cs b/src/KeePassLib2Android/Cryptography/HashingStreamEx.cs index b96d4e6c..d9aa4c28 100644 --- a/src/KeePassLib2Android/Cryptography/HashingStreamEx.cs +++ b/src/KeePassLib2Android/Cryptography/HashingStreamEx.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2013 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,7 +30,7 @@ namespace KeePassLib.Cryptography { public sealed class HashingStreamEx : Stream { - private Stream m_sBaseStream; + private readonly Stream m_sBaseStream; private bool m_bWriting; private HashAlgorithm m_hash; @@ -105,11 +105,8 @@ namespace KeePassLib.Cryptography #if KeePassRT protected override void Dispose(bool disposing) { - if(!disposing) return; -#else - public override void Close() + if(disposing) { -#endif if(m_hash != null) { try @@ -126,6 +123,14 @@ namespace KeePassLib.Cryptography m_sBaseStream.Close(); } + base.Dispose(disposing); + } + + public override void Flush() + { + m_sBaseStream.Flush(); + } + public override long Seek(long lOffset, SeekOrigin soOrigin) { throw new NotSupportedException(); diff --git a/src/KeePassLib2Android/Cryptography/HmacOtp.cs b/src/KeePassLib2Android/Cryptography/HmacOtp.cs index 17b4d58e..d401e83f 100644 --- a/src/KeePassLib2Android/Cryptography/HmacOtp.cs +++ b/src/KeePassLib2Android/Cryptography/HmacOtp.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2013 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/src/KeePassLib2Android/Cryptography/KeyDerivation/AesKdf.cs b/src/KeePassLib2Android/Cryptography/KeyDerivation/AesKdf.cs index ee0595b2..c7a66a58 100644 --- a/src/KeePassLib2Android/Cryptography/KeyDerivation/AesKdf.cs +++ b/src/KeePassLib2Android/Cryptography/KeyDerivation/AesKdf.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Cryptography/KeyDerivation/Argon2Kdf.Core.cs b/src/KeePassLib2Android/Cryptography/KeyDerivation/Argon2Kdf.Core.cs index bd90c427..5813c7c5 100644 --- a/src/KeePassLib2Android/Cryptography/KeyDerivation/Argon2Kdf.Core.cs +++ b/src/KeePassLib2Android/Cryptography/KeyDerivation/Argon2Kdf.Core.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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 @@ -77,6 +77,16 @@ namespace KeePassLib.Cryptography.KeyDerivation public ulong Lane = 0; public ulong Slice = 0; public ulong Index = 0; + + public void Release() + { + if(this.Finished != null) + { + this.Finished.Close(); + this.Finished = null; + } + else { Debug.Assert(false); } + } } private static byte[] Argon2d(byte[] pbMsg, byte[] pbSalt, uint uParallel, @@ -104,7 +114,12 @@ namespace KeePassLib.Cryptography.KeyDerivation ctx.LaneLength = ctx.SegmentLength * NbSyncPoints; Debug.Assert(NbBlockSize == (NbBlockSizeInQW * - (ulong)Marshal.SizeOf(typeof(ulong)))); +#if KeePassUAP + (ulong)Marshal.SizeOf() +#else + (ulong)Marshal.SizeOf(typeof(ulong)) +#endif + )); ctx.Mem = new ulong[ctx.MemoryBlocks * NbBlockSizeInQW]; Blake2b h = new Blake2b(); @@ -186,8 +201,13 @@ namespace KeePassLib.Cryptography.KeyDerivation // for(int i = 0; i < (int)NbBlockSizeInQW; ++i) // vDst[iDstOffset + i] = vSrc[iSrcOffset + i]; +#if KeePassUAP + Array.Copy(vSrc, (int)uSrcOffset, vDst, (int)uDstOffset, + (int)NbBlockSizeInQW); +#else Array.Copy(vSrc, (long)uSrcOffset, vDst, (long)uDstOffset, (long)NbBlockSizeInQW); +#endif } private static void XorBlock(ulong[] vDst, ulong uDstOffset, ulong[] vSrc, @@ -456,7 +476,10 @@ namespace KeePassLib.Cryptography.KeyDerivation } for(int l = 0; l < np; ++l) + { v[l].Finished.WaitOne(); + v[l].Release(); + } } } } diff --git a/src/KeePassLib2Android/Cryptography/KeyDerivation/Argon2Kdf.cs b/src/KeePassLib2Android/Cryptography/KeyDerivation/Argon2Kdf.cs index 69416ad2..bc858ca8 100644 --- a/src/KeePassLib2Android/Cryptography/KeyDerivation/Argon2Kdf.cs +++ b/src/KeePassLib2Android/Cryptography/KeyDerivation/Argon2Kdf.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfEngine.cs b/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfEngine.cs index 74d0dd25..994e0c7e 100644 --- a/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfEngine.cs +++ b/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfEngine.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfParameters.cs b/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfParameters.cs index 3723b78f..6372d215 100644 --- a/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfParameters.cs +++ b/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfParameters.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfPool.cs b/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfPool.cs index 08979e23..8e4f9da9 100644 --- a/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfPool.cs +++ b/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfPool.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Cryptography/PasswordGenerator/CharSetBasedGenerator.cs b/src/KeePassLib2Android/Cryptography/PasswordGenerator/CharSetBasedGenerator.cs index b0bfa825..f7cec856 100644 --- a/src/KeePassLib2Android/Cryptography/PasswordGenerator/CharSetBasedGenerator.cs +++ b/src/KeePassLib2Android/Cryptography/PasswordGenerator/CharSetBasedGenerator.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2013 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 KeePassLib.Cryptography.PasswordGenerator if(ch == char.MinValue) { - Array.Clear(vGenerated, 0, vGenerated.Length); + MemUtil.ZeroArray(vGenerated); return PwgError.TooFewCharacters; } @@ -57,7 +57,7 @@ namespace KeePassLib.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/src/KeePassLib2Android/Cryptography/PasswordGenerator/CustomPwGenerator.cs b/src/KeePassLib2Android/Cryptography/PasswordGenerator/CustomPwGenerator.cs index 2a441204..943bcf47 100644 --- a/src/KeePassLib2Android/Cryptography/PasswordGenerator/CustomPwGenerator.cs +++ b/src/KeePassLib2Android/Cryptography/PasswordGenerator/CustomPwGenerator.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2013 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/src/KeePassLib2Android/Cryptography/PasswordGenerator/CustomPwGeneratorPool.cs b/src/KeePassLib2Android/Cryptography/PasswordGenerator/CustomPwGeneratorPool.cs index 504e9bc3..b0ae5d14 100644 --- a/src/KeePassLib2Android/Cryptography/PasswordGenerator/CustomPwGeneratorPool.cs +++ b/src/KeePassLib2Android/Cryptography/PasswordGenerator/CustomPwGeneratorPool.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2013 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/src/KeePassLib2Android/Cryptography/PasswordGenerator/PatternBasedGenerator.cs b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PatternBasedGenerator.cs index 81ab2777..bb7e7302 100644 --- a/src/KeePassLib2Android/Cryptography/PasswordGenerator/PatternBasedGenerator.cs +++ b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PatternBasedGenerator.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2013 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 @@ -121,7 +121,7 @@ namespace KeePassLib.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/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwCharSet.cs b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwCharSet.cs index cd0354c4..97f34248 100644 --- a/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwCharSet.cs +++ b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwCharSet.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2013 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 @@ -46,6 +46,8 @@ namespace KeePassLib.Cryptography.PasswordGenerator public const string Invalid = "\t\r\n"; public const string LookAlike = @"O0l1I|"; + internal const string MenuAccels = PwCharSet.LowerCase + PwCharSet.Digits; + private const int CharTabSize = (0x10000 / 8); private List m_vChars = new List(); diff --git a/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwGenerator.cs b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwGenerator.cs index 771f243c..6bedd9a6 100644 --- a/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwGenerator.cs +++ b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwGenerator.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2013 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 @@ -22,6 +22,10 @@ using System.Collections.Generic; using System.Text; using System.Diagnostics; +#if !KeePassUAP +using System.Security.Cryptography; +#endif + using KeePassLib.Security; namespace KeePassLib.Cryptography.PasswordGenerator @@ -46,8 +50,12 @@ namespace KeePassLib.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); @@ -56,13 +64,20 @@ namespace KeePassLib.Cryptography.PasswordGenerator 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(256); // Mix in additional entropy if((pbAdditionalEntropy != null) && (pbAdditionalEntropy.Length > 0)) diff --git a/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwProfile.cs b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwProfile.cs index b9f3c26c..90c99b2b 100644 --- a/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwProfile.cs +++ b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwProfile.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2013 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 @@ -265,7 +265,7 @@ namespace KeePassLib.Cryptography.PasswordGenerator else pcs.Add(ch); } - Array.Clear(vChars, 0, vChars.Length); + MemUtil.ZeroArray(vChars); MemUtil.ZeroByteArray(pbUtf8); return pp; } diff --git a/src/KeePassLib2Android/Cryptography/PopularPasswords.cs b/src/KeePassLib2Android/Cryptography/PopularPasswords.cs index ca80a200..6083520d 100644 --- a/src/KeePassLib2Android/Cryptography/PopularPasswords.cs +++ b/src/KeePassLib2Android/Cryptography/PopularPasswords.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2013 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/src/KeePassLib2Android/Cryptography/QualityEstimation.cs b/src/KeePassLib2Android/Cryptography/QualityEstimation.cs index d90b3fab..cf835825 100644 --- a/src/KeePassLib2Android/Cryptography/QualityEstimation.cs +++ b/src/KeePassLib2Android/Cryptography/QualityEstimation.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2013 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 @@ -422,7 +422,7 @@ namespace KeePassLib.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/src/KeePassLib2Android/Cryptography/SelfTest.cs b/src/KeePassLib2Android/Cryptography/SelfTest.cs index 7817a9f2..43ef45b8 100644 --- a/src/KeePassLib2Android/Cryptography/SelfTest.cs +++ b/src/KeePassLib2Android/Cryptography/SelfTest.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2013 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,34 +19,35 @@ using System; using System.Collections.Generic; -using System.Security; -using System.Security.Cryptography; -using System.Text; -using System.Globalization; using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Security; +using System.Text; + +#if KeePassUAP +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Parameters; +#else +using System.Security.Cryptography; +#endif using KeePassLib.Cryptography.Cipher; +using KeePassLib.Cryptography.Hash; +using KeePassLib.Cryptography.KeyDerivation; using KeePassLib.Keys; using KeePassLib.Native; -using KeePassLib.Utility; using KeePassLib.Resources; using KeePassLib.Security; +using KeePassLib.Utility; + +#if (KeePassUAP && KeePassLibSD) +#error KeePassUAP and KeePassLibSD are mutually exclusive. +#endif namespace KeePassLib.Cryptography { -/* #pragma warning disable 1591 - /// - /// Return values of the SelfTest.Perform method. - /// - public enum SelfTestResult - { - Success = 0, - RijndaelEcbError = 1, - Salsa20Error = 2, - NativeKeyTransformationError = 3 - } -#pragma warning restore 1591 */ - /// /// Class containing self-test methods. /// @@ -61,7 +62,11 @@ namespace KeePassLib.Cryptography TestRijndael(); TestSalsa20(); - + TestChaCha20(); + TestBlake2b(); + TestArgon2(); + TestHmac(); + TestNativeKeyTransform(); TestHmacOtp(); @@ -74,28 +79,31 @@ namespace KeePassLib.Cryptography 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 !KeePassRT - try { new RijndaelManaged(); } +#if !KeePassUAP + try { using(RijndaelManaged r = new RijndaelManaged()) { } } catch(Exception exAes) { throw new SecurityException("AES/Rijndael: " + exAes.Message); } +#endif - try { new SHA256Managed(); } + try { using(SHA256Managed h = new SHA256Managed()) { } } catch(Exception exSha256) { throw new SecurityException("SHA-256: " + exSha256.Message); } -#endif } private static void TestRijndael() { -#if !KeePassRT // Test vector (official ECB test vector #356) byte[] pbIV = new byte[16]; byte[] pbTestKey = new byte[32]; @@ -110,6 +118,13 @@ namespace KeePassLib.Cryptography for(i = 0; i < 16; ++i) pbTestData[i] = 0; pbTestData[0] = 0x04; +#if KeePassUAP + 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 RijndaelManaged r = new RijndaelManaged(); if(r.BlockSize != 128) // AES block size @@ -125,16 +140,17 @@ namespace KeePassLib.Cryptography ICryptoTransform iCrypt = r.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() { +#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, @@ -149,12 +165,11 @@ namespace KeePassLib.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."); + 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 @@ -166,15 +181,27 @@ namespace KeePassLib.Cryptography 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."); + 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."); + throw new SecurityException("Salsa20-3"); + + Dictionary d = new Dictionary(); + const int nRounds = 100; + for(int i = 0; i < nRounds; ++i) + { + byte[] z = new byte[32]; + 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"); #endif } @@ -188,7 +215,7 @@ namespace KeePassLib.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; } @@ -196,6 +223,484 @@ namespace KeePassLib.Cryptography } #endif + private static void TestChaCha20() + { + // ====================================================== + // 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"); + + Random r = new Random(); + 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() + { +#if 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 + }; + + Random r = new Random(); + 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 + + KdfParameters 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) + { + 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 + private static void TestNativeKeyTransform() { #if DEBUG @@ -205,16 +710,16 @@ namespace KeePassLib.Cryptography 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 } @@ -252,12 +757,49 @@ namespace KeePassLib.Cryptography pbN = enc.GetBytes("012b"); if(MemUtil.IndexOf(pb, pbN) >= 0) throw new InvalidOperationException("MemUtil-7"); + + byte[] pbRes = MemUtil.ParseBase32("MY======"); + byte[] pbExp = Encoding.ASCII.GetBytes("f"); + if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-1"); + + pbRes = MemUtil.ParseBase32("MZXQ===="); + pbExp = Encoding.ASCII.GetBytes("fo"); + if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-2"); + + pbRes = MemUtil.ParseBase32("MZXW6==="); + pbExp = Encoding.ASCII.GetBytes("foo"); + if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-3"); + + pbRes = MemUtil.ParseBase32("MZXW6YQ="); + pbExp = Encoding.ASCII.GetBytes("foob"); + if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-4"); + + pbRes = MemUtil.ParseBase32("MZXW6YTB"); + pbExp = Encoding.ASCII.GetBytes("fooba"); + if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-5"); + + pbRes = MemUtil.ParseBase32("MZXW6YTBOI======"); + pbExp = Encoding.ASCII.GetBytes("foobar"); + if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-6"); + + pbRes = MemUtil.ParseBase32("JNSXSIDQOJXXM2LEMVZCAYTBONSWIIDPNYQG63TFFV2GS3LFEBYGC43TO5XXEZDTFY======"); + pbExp = Encoding.ASCII.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", @@ -325,6 +867,41 @@ namespace KeePassLib.Cryptography throw new SecurityException("ProtectedString-8"); 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) + { + bool bProt = ((r.Next() % 4) != 0); + ps = ps.WithProtection(bProt); + + int x = r.Next(str.Length + 1); + int c = r.Next(20); + char ch = (char)r.Next(1, 256); + + string strIns = new string(ch, c); + str = str.Insert(x, strIns); + ps = ps.Insert(x, strIns); + + if(ps.IsProtected != bProt) + throw new SecurityException("ProtectedString-11"); + if(ps.ReadString() != str) + throw new SecurityException("ProtectedString-12"); + + ps = ps.WithProtection(bProt); + + x = r.Next(str.Length); + c = r.Next(str.Length - x + 1); + + str = str.Remove(x, c); + ps = ps.Remove(x, c); + + if(ps.IsProtected != bProt) + throw new SecurityException("ProtectedString-13"); + if(ps.ReadString() != str) + throw new SecurityException("ProtectedString-14"); + } #endif } @@ -365,10 +942,16 @@ namespace KeePassLib.Cryptography throw new InvalidOperationException("StrUtil-V3"); if(StrUtil.VersionToString(0x00FF000000000000UL) != "255") throw new InvalidOperationException("StrUtil-V4"); - if(StrUtil.VersionToString(0x00FF000000000000UL, true) != "255.0") + if(StrUtil.VersionToString(0x00FF000000000000UL, 2) != "255.0") throw new InvalidOperationException("StrUtil-V5"); - if(StrUtil.VersionToString(0x0000000000070000UL, true) != "0.0.7") + if(StrUtil.VersionToString(0x0000000000070000UL) != "0.0.7") throw new InvalidOperationException("StrUtil-V6"); + if(StrUtil.VersionToString(0x0000000000000000UL) != "0") + throw new InvalidOperationException("StrUtil-V7"); + if(StrUtil.VersionToString(0x00000000FFFF0000UL, 4) != "0.0.65535.0") + throw new InvalidOperationException("StrUtil-V8"); + if(StrUtil.VersionToString(0x0000000000000000UL, 4) != "0.0.0.0") + throw new InvalidOperationException("StrUtil-V9"); if(StrUtil.RtfEncodeChar('\u0000') != "\\u0?") throw new InvalidOperationException("StrUtil-Rtf1"); @@ -411,12 +994,22 @@ namespace KeePassLib.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 !KeePassUAP + 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"); diff --git a/src/KeePassLib2Android/Delegates/Handlers.cs b/src/KeePassLib2Android/Delegates/Handlers.cs index c7ab674b..5e8e3646 100644 --- a/src/KeePassLib2Android/Delegates/Handlers.cs +++ b/src/KeePassLib2Android/Delegates/Handlers.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2013 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/src/KeePassLib2Android/Interfaces/IDeepCloneable.cs b/src/KeePassLib2Android/Interfaces/IDeepCloneable.cs index 9d0adb72..55753e77 100644 --- a/src/KeePassLib2Android/Interfaces/IDeepCloneable.cs +++ b/src/KeePassLib2Android/Interfaces/IDeepCloneable.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2013 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/src/KeePassLib2Android/Interfaces/IStatusLogger.cs b/src/KeePassLib2Android/Interfaces/IStatusLogger.cs index 07b71667..ae7258f6 100644 --- a/src/KeePassLib2Android/Interfaces/IStatusLogger.cs +++ b/src/KeePassLib2Android/Interfaces/IStatusLogger.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2013 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/src/KeePassLib2Android/Interfaces/IStructureItem.cs b/src/KeePassLib2Android/Interfaces/IStructureItem.cs index 186b6d40..81de6e1e 100644 --- a/src/KeePassLib2Android/Interfaces/IStructureItem.cs +++ b/src/KeePassLib2Android/Interfaces/IStructureItem.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2013 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/src/KeePassLib2Android/Interfaces/ITimeLogger.cs b/src/KeePassLib2Android/Interfaces/ITimeLogger.cs index 4b5e5e97..ca4b7e18 100644 --- a/src/KeePassLib2Android/Interfaces/ITimeLogger.cs +++ b/src/KeePassLib2Android/Interfaces/ITimeLogger.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2013 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,18 +39,18 @@ namespace KeePassLib.Interfaces } /// - /// The date/time when the object was last accessed. + /// The date/time when the object was last modified. /// - DateTime LastAccessTime + DateTime LastModificationTime { get; set; } /// - /// The date/time when the object was last modified. + /// The date/time when the object was last accessed. /// - DateTime LastModificationTime + DateTime LastAccessTime { get; set; @@ -101,19 +101,5 @@ namespace KeePassLib.Interfaces /// /// Update last modification time. void Touch(bool bModified); - - #region Set times lazily - // Passing xml datetime string to be parsed only on demand - - void SetLazyLastModificationTime(string xmlDateTime); - - void SetLazyCreationTime(string xmlDateTime); - - void SetLazyLastAccessTime(string xmlDateTime); - - void SetLazyExpiryTime(string xmlDateTime); - - void SetLazyLocationChanged(string xmlDateTime); - #endregion } } diff --git a/src/KeePassLib2Android/Interfaces/IUIOperations.cs b/src/KeePassLib2Android/Interfaces/IUIOperations.cs index ab958207..18ec2cec 100644 --- a/src/KeePassLib2Android/Interfaces/IUIOperations.cs +++ b/src/KeePassLib2Android/Interfaces/IUIOperations.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2013 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/src/KeePassLib2Android/Interfaces/IXmlSerializerEx.cs b/src/KeePassLib2Android/Interfaces/IXmlSerializerEx.cs index 79757123..b95f2a5c 100644 --- a/src/KeePassLib2Android/Interfaces/IXmlSerializerEx.cs +++ b/src/KeePassLib2Android/Interfaces/IXmlSerializerEx.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2013 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/src/KeePassLib2Android/Keys/CompositeKey.cs b/src/KeePassLib2Android/Keys/CompositeKey.cs index 673d96dc..180734c0 100644 --- a/src/KeePassLib2Android/Keys/CompositeKey.cs +++ b/src/KeePassLib2Android/Keys/CompositeKey.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Keys/IUserKey.cs b/src/KeePassLib2Android/Keys/IUserKey.cs index ab27e14d..d4739bb2 100644 --- a/src/KeePassLib2Android/Keys/IUserKey.cs +++ b/src/KeePassLib2Android/Keys/IUserKey.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2013 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/src/KeePassLib2Android/Keys/KcpCustomKey.cs b/src/KeePassLib2Android/Keys/KcpCustomKey.cs index 035752ef..9f5deb96 100644 --- a/src/KeePassLib2Android/Keys/KcpCustomKey.cs +++ b/src/KeePassLib2Android/Keys/KcpCustomKey.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2013 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,12 +19,11 @@ using System; using System.Collections.Generic; -using System.Text; using System.Diagnostics; -using System.Security.Cryptography; +using System.Text; +using KeePassLib.Cryptography; using KeePassLib.Security; -using KeePassLib.Utility; namespace KeePassLib.Keys { @@ -55,8 +54,7 @@ namespace KeePassLib.Keys if(bPerformHash) { - SHA256Managed sha256 = new SHA256Managed(); - byte[] pbRaw = sha256.ComputeHash(pbKeyData); + byte[] pbRaw = CryptoUtil.HashSha256(pbKeyData); m_pbKey = new ProtectedBinary(true, pbRaw); } else m_pbKey = new ProtectedBinary(true, pbKeyData); diff --git a/src/KeePassLib2Android/Keys/KcpKeyFile.cs b/src/KeePassLib2Android/Keys/KcpKeyFile.cs index da59571f..c11fb65f 100644 --- a/src/KeePassLib2Android/Keys/KcpKeyFile.cs +++ b/src/KeePassLib2Android/Keys/KcpKeyFile.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Keys/KcpPassword.cs b/src/KeePassLib2Android/Keys/KcpPassword.cs index 4de3ecdb..f1627784 100644 --- a/src/KeePassLib2Android/Keys/KcpPassword.cs +++ b/src/KeePassLib2Android/Keys/KcpPassword.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Keys/KcpUserAccount.cs b/src/KeePassLib2Android/Keys/KcpUserAccount.cs index 3408d68c..2f04b75e 100644 --- a/src/KeePassLib2Android/Keys/KcpUserAccount.cs +++ b/src/KeePassLib2Android/Keys/KcpUserAccount.cs @@ -1,8 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 Dominik Reichl - - Modified to be used with Mono for Android. Changes Copyright (C) 2013 Philipp Crocoll + 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 @@ -43,7 +41,7 @@ namespace KeePassLib.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 }; @@ -65,7 +63,22 @@ namespace KeePassLib.Keys /// public KcpUserAccount() { - throw new NotSupportedException("DataProtection not supported on MonoForAndroid!"); + // Test if ProtectedData is supported -- throws an exception + // when running on an old system (Windows 98 / ME). + byte[] pbDummyData = new byte[128]; + ProtectedData.Protect(pbDummyData, m_pbEntropy, + DataProtectionScope.CurrentUser); + + byte[] pbKey = LoadUserKey(false); + if(pbKey == null) pbKey = CreateUserKey(); + if(pbKey == null) // Should never happen + { + Debug.Assert(false); + throw new SecurityException(KLRes.UserAccountKeyError); + } + + m_pbKeyData = new ProtectedBinary(true, pbKey); + MemUtil.ZeroByteArray(pbKey); } // public void Clear() @@ -75,8 +88,8 @@ namespace KeePassLib.Keys private static string GetUserKeyFilePath(bool bCreate) { -#if KeePassRT - string strUserDir = Windows.Storage.ApplicationData.Current.RoamingFolder.Path; +#if KeePassUAP + string strUserDir = EnvironmentExt.AppDataRoamingFolderPath; #else string strUserDir = Environment.GetFolderPath( Environment.SpecialFolder.ApplicationData); @@ -89,22 +102,25 @@ namespace KeePassLib.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; #if !KeePassLibSD try { - throw new NotSupportedException("DataProtection not supported on MonoForAndroid!"); - } - catch(Exception exLoad) - { - if(bShowWarning) MessageService.ShowWarning(exLoad); + string strFilePath = GetUserKeyFilePath(false); + byte[] pbProtectedKey = File.ReadAllBytes(strFilePath); + pbKey = ProtectedData.Unprotect(pbProtectedKey, m_pbEntropy, + DataProtectionScope.CurrentUser); + } + catch(Exception) + { + if(bThrow) throw; pbKey = null; } #endif @@ -114,17 +130,23 @@ namespace KeePassLib.Keys private static byte[] CreateUserKey() { - byte[] pbKey = null; +#if KeePassLibSD + return null; +#else + string strFilePath = GetUserKeyFilePath(true); -#if !KeePassLibSD - try - { - throw new NotSupportedException("DataProtection not supported on MonoForAndroid!"); - } - catch(Exception) { pbKey = null; } -#endif + byte[] pbRandomKey = CryptoRandom.Instance.GetRandomBytes(64); + byte[] pbProtectedKey = ProtectedData.Protect(pbRandomKey, + m_pbEntropy, DataProtectionScope.CurrentUser); + File.WriteAllBytes(strFilePath, pbProtectedKey); + + byte[] pbKey = LoadUserKey(true); + Debug.Assert(MemUtil.ArraysEqual(pbKey, pbRandomKey)); + + MemUtil.ZeroByteArray(pbRandomKey); return pbKey; +#endif } } } diff --git a/src/KeePassLib2Android/Keys/KeyProvider.cs b/src/KeePassLib2Android/Keys/KeyProvider.cs index 68908446..c331041f 100644 --- a/src/KeePassLib2Android/Keys/KeyProvider.cs +++ b/src/KeePassLib2Android/Keys/KeyProvider.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Keys/KeyProviderPool.cs b/src/KeePassLib2Android/Keys/KeyProviderPool.cs index a90788e5..ba2e1bb2 100644 --- a/src/KeePassLib2Android/Keys/KeyProviderPool.cs +++ b/src/KeePassLib2Android/Keys/KeyProviderPool.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Keys/KeyValidator.cs b/src/KeePassLib2Android/Keys/KeyValidator.cs index bb9eb2ff..37a58942 100644 --- a/src/KeePassLib2Android/Keys/KeyValidator.cs +++ b/src/KeePassLib2Android/Keys/KeyValidator.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Keys/KeyValidatorPool.cs b/src/KeePassLib2Android/Keys/KeyValidatorPool.cs index 17137676..0ce27cb4 100644 --- a/src/KeePassLib2Android/Keys/KeyValidatorPool.cs +++ b/src/KeePassLib2Android/Keys/KeyValidatorPool.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Keys/UserKeyType.cs b/src/KeePassLib2Android/Keys/UserKeyType.cs index ecd307d2..6a58d4d2 100644 --- a/src/KeePassLib2Android/Keys/UserKeyType.cs +++ b/src/KeePassLib2Android/Keys/UserKeyType.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Native/NativeLib.cs b/src/KeePassLib2Android/Native/NativeLib.cs index e4e6cbb5..3c8a1d73 100644 --- a/src/KeePassLib2Android/Native/NativeLib.cs +++ b/src/KeePassLib2Android/Native/NativeLib.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Native/NativeMethods.Unix.cs b/src/KeePassLib2Android/Native/NativeMethods.Unix.cs index a03b3e29..3e0ef0c0 100644 --- a/src/KeePassLib2Android/Native/NativeMethods.Unix.cs +++ b/src/KeePassLib2Android/Native/NativeMethods.Unix.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Native/NativeMethods.cs b/src/KeePassLib2Android/Native/NativeMethods.cs index 07c47ab2..67aaa06d 100644 --- a/src/KeePassLib2Android/Native/NativeMethods.cs +++ b/src/KeePassLib2Android/Native/NativeMethods.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Properties/AssemblyInfo.cs b/src/KeePassLib2Android/Properties/AssemblyInfo.cs index 48474729..6b1bdc1d 100644 --- a/src/KeePassLib2Android/Properties/AssemblyInfo.cs +++ b/src/KeePassLib2Android/Properties/AssemblyInfo.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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 @@ -27,7 +27,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Dominik Reichl")] [assembly: AssemblyProduct("KeePassLib")] -[assembly: AssemblyCopyright("Copyright © 2003-2016 Dominik Reichl")] +[assembly: AssemblyCopyright("Copyright © 2003-2017 Dominik Reichl")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -38,5 +38,5 @@ using System.Runtime.InteropServices; [assembly: Guid("395f6eec-a1e0-4438-aa82-b75099348134")] // Assembly version information -[assembly: AssemblyVersion("2.34.0.*")] -[assembly: AssemblyFileVersion("2.34.0.0")] +[assembly: AssemblyVersion("2.35.0.*")] +[assembly: AssemblyFileVersion("2.35.0.0")] diff --git a/src/KeePassLib2Android/PwDatabase.cs b/src/KeePassLib2Android/PwDatabase.cs index 97eac76d..4b4deb02 100644 --- a/src/KeePassLib2Android/PwDatabase.cs +++ b/src/KeePassLib2Android/PwDatabase.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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 @@ -79,6 +79,7 @@ namespace KeePassLib 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; @@ -269,6 +270,12 @@ namespace KeePassLib 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. /// @@ -537,7 +544,7 @@ namespace KeePassLib m_vCustomIcons = new List(); m_bUINeedsIconUpdate = true; - DateTime dtNow = DateTime.Now; + DateTime dtNow = DateTime.UtcNow; m_dtSettingsChanged = dtNow; m_strName = string.Empty; @@ -552,6 +559,7 @@ namespace KeePassLib m_dtKeyLastChanged = dtNow; m_lKeyChangeRecDays = -1; m_lKeyChangeForceDays = -1; + m_bKeyChangeForceOnce = false; m_ioSource = new IOConnectionInfo(); m_bDatabaseOpened = false; @@ -1347,7 +1355,7 @@ namespace KeePassLib where T : class, ITimeLogger, IStructureItem, IDeepCloneable { PwObjectPoolEx p = null; - dtLoc = DateTime.MinValue; + dtLoc = TimeUtil.SafeMinValueUtc; IStructureItem ptOrg = ppOrg.GetItemByUuid(t.Uuid); if(ptOrg != null) @@ -1374,7 +1382,7 @@ namespace KeePassLib pPool = null; int iPosMax = kvpRange.Key; - DateTime dtMax = DateTime.MinValue; + DateTime dtMax = TimeUtil.SafeMinValueUtc; for(int i = kvpRange.Key; i <= kvpRange.Value; ++i) { @@ -1885,7 +1893,7 @@ namespace KeePassLib 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) @@ -2010,7 +2018,7 @@ namespace KeePassLib 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/src/KeePassLib2Android/PwDefs.cs b/src/KeePassLib2Android/PwDefs.cs index b7ddd6c6..46b4c374 100644 --- a/src/KeePassLib2Android/PwDefs.cs +++ b/src/KeePassLib2Android/PwDefs.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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 @@ -55,20 +55,20 @@ namespace KeePassLib /// e.g. 2.19 = 0x02130000. /// It is highly recommended to use FileVersion64 instead. /// - public const uint Version32 = 0x02220000; + public const uint Version32 = 0x02230000; /// /// Version, encoded as 64-bit unsigned integer /// (component-wise, 16 bits per component). /// - public const ulong FileVersion64 = 0x0002002200000000UL; + public const ulong FileVersion64 = 0x0002002300000000UL; /// /// Version, encoded as string. /// - public const string VersionString = "2.34"; + public const string VersionString = "2.35"; - public const string Copyright = @"Copyright © 2003-2016 Dominik Reichl"; + public const string Copyright = @"Copyright © 2003-2017 Dominik Reichl"; /// /// Product website URL. Terminated by a forward slash. @@ -107,10 +107,11 @@ namespace KeePassLib /// 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 = 500000; diff --git a/src/KeePassLib2Android/PwDeletedObject.cs b/src/KeePassLib2Android/PwDeletedObject.cs index 734fe345..9a648879 100644 --- a/src/KeePassLib2Android/PwDeletedObject.cs +++ b/src/KeePassLib2Android/PwDeletedObject.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/PwEntry.cs b/src/KeePassLib2Android/PwEntry.cs index 22b87468..e44fadea 100644 --- a/src/KeePassLib2Android/PwEntry.cs +++ b/src/KeePassLib2Android/PwEntry.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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 @@ -343,8 +343,11 @@ namespace KeePassLib 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; } } @@ -368,8 +371,11 @@ namespace KeePassLib 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; } } @@ -622,7 +628,7 @@ namespace KeePassLib /// 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; @@ -761,7 +767,7 @@ namespace KeePassLib private void RemoveOldestBackup() { - DateTime dtMin = DateTime.MaxValue; + DateTime dtMin = TimeUtil.SafeMaxValueUtc; uint idxRemove = uint.MaxValue; for(uint u = 0; u < m_listHistory.UCount; ++u) @@ -936,7 +942,7 @@ namespace KeePassLib public void SetCreatedNow() { - DateTime dt = DateTime.Now; + DateTime dt = DateTime.UtcNow; m_tCreation = dt; m_tLastAccess = dt; diff --git a/src/KeePassLib2Android/PwEnums.cs b/src/KeePassLib2Android/PwEnums.cs index 13bed411..ec1037c0 100644 --- a/src/KeePassLib2Android/PwEnums.cs +++ b/src/KeePassLib2Android/PwEnums.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/PwUuid.cs b/src/KeePassLib2Android/PwUuid.cs index 540d6164..06cb77b4 100644 --- a/src/KeePassLib2Android/PwUuid.cs +++ b/src/KeePassLib2Android/PwUuid.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Resources/KLRes.Generated.cs b/src/KeePassLib2Android/Resources/KLRes.Generated.cs index af6f5485..2e23e0b2 100644 --- a/src/KeePassLib2Android/Resources/KLRes.Generated.cs +++ b/src/KeePassLib2Android/Resources/KLRes.Generated.cs @@ -33,7 +33,9 @@ namespace KeePassLib.Resources 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); @@ -73,7 +75,9 @@ namespace KeePassLib.Resources "FatalError", "FatalErrorText", "FileCorrupted", - "FileHeaderEndEarly", + "FileHeaderCorrupted", + "FileIncomplete", + "FileIncompleteExpc", "FileLoadFailed", "FileLockedWrite", "FileNewVerOrPlgReq", @@ -187,15 +191,37 @@ namespace KeePassLib.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 = diff --git a/src/KeePassLib2Android/Security/ProtectedBinary.cs b/src/KeePassLib2Android/Security/ProtectedBinary.cs index 8c92078f..7dfe46c3 100644 --- a/src/KeePassLib2Android/Security/ProtectedBinary.cs +++ b/src/KeePassLib2Android/Security/ProtectedBinary.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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 @@ -165,7 +165,7 @@ namespace KeePassLib.Security /// public ProtectedBinary() { - Init(false, MemUtil.EmptyByteArray); + Init(false, MemUtil.EmptyByteArray, 0, 0); } /// @@ -180,7 +180,27 @@ namespace KeePassLib.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); } /// @@ -196,14 +216,19 @@ namespace KeePassLib.Security if(xbProtected == null) throw new ArgumentNullException("xbProtected"); byte[] pb = xbProtected.ReadPlainText(); - Init(bEnableProtection, 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; @@ -212,15 +237,15 @@ namespace KeePassLib.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(); } diff --git a/src/KeePassLib2Android/Security/ProtectedString.cs b/src/KeePassLib2Android/Security/ProtectedString.cs index adad63b4..95e4b284 100644 --- a/src/KeePassLib2Android/Security/ProtectedString.cs +++ b/src/KeePassLib2Android/Security/ProtectedString.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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 @@ -282,7 +282,7 @@ namespace KeePassLib.Security } finally { - Array.Clear(v, 0, v.Length); + MemUtil.ZeroArray(v); MemUtil.ZeroByteArray(pb); } @@ -292,7 +292,7 @@ namespace KeePassLib.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; } @@ -328,7 +328,7 @@ namespace KeePassLib.Security } finally { - Array.Clear(v, 0, v.Length); + MemUtil.ZeroArray(v); MemUtil.ZeroByteArray(pb); } @@ -338,7 +338,7 @@ namespace KeePassLib.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/src/KeePassLib2Android/Security/XorredBuffer.cs b/src/KeePassLib2Android/Security/XorredBuffer.cs index 8586f295..bf8dd3c7 100644 --- a/src/KeePassLib2Android/Security/XorredBuffer.cs +++ b/src/KeePassLib2Android/Security/XorredBuffer.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Serialization/BinaryReaderEx.cs b/src/KeePassLib2Android/Serialization/BinaryReaderEx.cs index 9d4351fd..3ff99873 100644 --- a/src/KeePassLib2Android/Serialization/BinaryReaderEx.cs +++ b/src/KeePassLib2Android/Serialization/BinaryReaderEx.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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 @@ -31,7 +31,7 @@ namespace KeePassLib.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; } @@ -67,7 +67,8 @@ namespace KeePassLib.Serialization byte[] pb = MemUtil.Read(m_s, nCount); if((pb == null) || (pb.Length != nCount)) { - if(m_strReadExcp != null) throw new IOException(m_strReadExcp); + if(!string.IsNullOrEmpty(m_strReadExcp)) + throw new EndOfStreamException(m_strReadExcp); else throw new EndOfStreamException(); } @@ -76,7 +77,8 @@ namespace KeePassLib.Serialization } 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/src/KeePassLib2Android/Serialization/FileLock.cs b/src/KeePassLib2Android/Serialization/FileLock.cs index 7cc591a0..6a0437f8 100644 --- a/src/KeePassLib2Android/Serialization/FileLock.cs +++ b/src/KeePassLib2Android/Serialization/FileLock.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Serialization/HashedBlockStream.cs b/src/KeePassLib2Android/Serialization/HashedBlockStream.cs index dd2955b9..5ea52799 100644 --- a/src/KeePassLib2Android/Serialization/HashedBlockStream.cs +++ b/src/KeePassLib2Android/Serialization/HashedBlockStream.cs @@ -164,21 +164,10 @@ namespace KeePassLib.Serialization } } - public override void Flush() - { - if(m_bWriting) m_bwOutput.Flush(); - } - -#if KeePassUAP protected override void Dispose(bool disposing) { - if(!disposing) return; -#else - public override void Close() + if(disposing && (m_sBaseStream != null)) { -#endif - if(m_sBaseStream != null) - { if(!m_bWriting) // Reading mode { m_brInput.Close(); @@ -202,6 +191,13 @@ namespace KeePassLib.Serialization m_sBaseStream.Close(); m_sBaseStream = null; } + + base.Dispose(disposing); + } + + public override void Flush() + { + if(m_bWriting) m_bwOutput.Flush(); } public override long Seek(long lOffset, SeekOrigin soOrigin) diff --git a/src/KeePassLib2Android/Serialization/HmacBlockStream.cs b/src/KeePassLib2Android/Serialization/HmacBlockStream.cs index 59295742..b9dd414a 100644 --- a/src/KeePassLib2Android/Serialization/HmacBlockStream.cs +++ b/src/KeePassLib2Android/Serialization/HmacBlockStream.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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 @@ -98,21 +98,9 @@ namespace KeePassLib.Serialization } } - public override void Flush() - { - Debug.Assert(m_sBase != null); // Object should not be disposed - if(m_bWriting && (m_sBase != null)) m_sBase.Flush(); - } - -#if KeePassUAP protected override void Dispose(bool disposing) { - if(!disposing) return; -#else - public override void Close() - { -#endif - if(m_sBase != null) + if(disposing && (m_sBase != null)) { if(m_bWriting) { @@ -130,6 +118,14 @@ namespace KeePassLib.Serialization 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) @@ -206,7 +202,8 @@ namespace KeePassLib.Serialization byte[] pbStoredHmac = MemUtil.Read(m_sBase, 32); if((pbStoredHmac == null) || (pbStoredHmac.Length != 32)) - throw new EndOfStreamException(); + 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 @@ -220,7 +217,8 @@ namespace KeePassLib.Serialization byte[] pbBlockSize = MemUtil.Read(m_sBase, 4); if((pbBlockSize == null) || (pbBlockSize.Length != 4)) - throw new EndOfStreamException(); + throw new EndOfStreamException(KLRes.FileCorrupted + " " + + KLRes.FileIncomplete); int nBlockSize = MemUtil.BytesToInt32(pbBlockSize); if(nBlockSize < 0) throw new InvalidDataException(KLRes.FileCorrupted); @@ -229,7 +227,8 @@ namespace KeePassLib.Serialization m_pbBuffer = MemUtil.Read(m_sBase, nBlockSize); if((m_pbBuffer == null) || ((m_pbBuffer.Length != nBlockSize) && m_bVerify)) - throw new EndOfStreamException(); + throw new EndOfStreamException(KLRes.FileCorrupted + " " + + KLRes.FileIncompleteExpc); if(m_bVerify) { diff --git a/src/KeePassLib2Android/Serialization/IOConnection.cs b/src/KeePassLib2Android/Serialization/IOConnection.cs index 60cfe0b4..c23f8bcb 100644 --- a/src/KeePassLib2Android/Serialization/IOConnection.cs +++ b/src/KeePassLib2Android/Serialization/IOConnection.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2013 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl Modified to be used with Mono for Android. Changes Copyright (C) 2013 Philipp Crocoll @@ -53,6 +53,185 @@ namespace KeePassLib.Serialization } #endif + internal abstract class WrapperStream : Stream + { + private readonly Stream m_s; + protected Stream BaseStream + { + get { return m_s; } + } + + public override bool CanRead + { + get { return m_s.CanRead; } + } + + public override bool CanSeek + { + get { return m_s.CanSeek; } + } + + public override bool CanTimeout + { + get { return m_s.CanTimeout; } + } + + public override bool CanWrite + { + get { return m_s.CanWrite; } + } + + public override long Length + { + get { return m_s.Length; } + } + + public override long Position + { + get { return m_s.Position; } + set { m_s.Position = value; } + } + + public override int ReadTimeout + { + get { return m_s.ReadTimeout; } + set { m_s.ReadTimeout = value; } + } + + public override int WriteTimeout + { + get { return m_s.WriteTimeout; } + set { m_s.WriteTimeout = value; } + } + + public WrapperStream(Stream sBase) : base() + { + if(sBase == null) throw new ArgumentNullException("sBase"); + + m_s = sBase; + } + +#if !KeePassUAP + public override IAsyncResult BeginRead(byte[] buffer, int offset, + int count, AsyncCallback callback, object state) + { + return m_s.BeginRead(buffer, offset, count, callback, state); + } + + public override IAsyncResult BeginWrite(byte[] buffer, int offset, + int count, AsyncCallback callback, object state) + { + return BeginWrite(buffer, offset, count, callback, state); + } +#endif + + protected override void Dispose(bool disposing) + { + if(disposing) m_s.Dispose(); + + base.Dispose(disposing); + } + +#if !KeePassUAP + public override int EndRead(IAsyncResult asyncResult) + { + return m_s.EndRead(asyncResult); + } + + public override void EndWrite(IAsyncResult asyncResult) + { + m_s.EndWrite(asyncResult); + } +#endif + + public override void Flush() + { + m_s.Flush(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + return m_s.Read(buffer, offset, count); + } + + public override int ReadByte() + { + return m_s.ReadByte(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + return m_s.Seek(offset, origin); + } + + public override void SetLength(long value) + { + m_s.SetLength(value); + } + + public override void Write(byte[] buffer, int offset, int count) + { + m_s.Write(buffer, offset, count); + } + + public override void WriteByte(byte value) + { + m_s.WriteByte(value); + } + } + + 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; + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if(disposing && MonoWorkarounds.IsRequired(10163) && m_bWrite && + !m_bDisposed) + { + try + { + Stream s = this.BaseStream; + Type t = s.GetType(); + if(t.Name == "WebConnectionStream") + { + PropertyInfo pi = t.GetProperty("Request", + BindingFlags.Instance | BindingFlags.NonPublic); + if(pi != null) + { + WebRequest wr = (pi.GetValue(s, null) as WebRequest); + if(wr != null) + IOConnection.DisposeResponse(wr.GetResponse(), false); + else { Debug.Assert(false); } + } + else { Debug.Assert(false); } + } + } + catch(Exception) { Debug.Assert(false); } + } + + m_bDisposed = true; + } + + public static Stream WrapIfRequired(Stream s) + { + if(s == null) { Debug.Assert(false); return null; } + + if(MonoWorkarounds.IsRequired(10163) && s.CanWrite) + return new IocStream(s); + + return s; + } + } + public static class IOConnection { #if (!KeePassLibSD && !KeePassRT) diff --git a/src/KeePassLib2Android/Serialization/IOConnectionInfo.cs b/src/KeePassLib2Android/Serialization/IOConnectionInfo.cs index a692d94f..b887d6ab 100644 --- a/src/KeePassLib2Android/Serialization/IOConnectionInfo.cs +++ b/src/KeePassLib2Android/Serialization/IOConnectionInfo.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Serialization/IocProperties.cs b/src/KeePassLib2Android/Serialization/IocProperties.cs index 9fd9acc6..337e662c 100644 --- a/src/KeePassLib2Android/Serialization/IocProperties.cs +++ b/src/KeePassLib2Android/Serialization/IocProperties.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Serialization/IocPropertyInfo.cs b/src/KeePassLib2Android/Serialization/IocPropertyInfo.cs index c63358f4..e84cf0d8 100644 --- a/src/KeePassLib2Android/Serialization/IocPropertyInfo.cs +++ b/src/KeePassLib2Android/Serialization/IocPropertyInfo.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Serialization/IocPropertyInfoPool.cs b/src/KeePassLib2Android/Serialization/IocPropertyInfoPool.cs index 99b661f6..a7c8d871 100644 --- a/src/KeePassLib2Android/Serialization/IocPropertyInfoPool.cs +++ b/src/KeePassLib2Android/Serialization/IocPropertyInfoPool.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Serialization/KdbxFile.Read.Streamed.cs b/src/KeePassLib2Android/Serialization/KdbxFile.Read.Streamed.cs index e0e0effb..9d0ef6e4 100644 --- a/src/KeePassLib2Android/Serialization/KdbxFile.Read.Streamed.cs +++ b/src/KeePassLib2Android/Serialization/KdbxFile.Read.Streamed.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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,6 @@ namespace KeePassLib.Serialization if(xr == null) throw new ArgumentNullException("xr"); m_ctxGroups.Clear(); - m_dictBinPool = new Dictionary(); KdbContext ctx = KdbContext.Null; @@ -233,11 +232,11 @@ namespace KeePassLib.Serialization if(!string.IsNullOrEmpty(strHash) && (m_pbHashOfHeader != null) && !m_bRepairMode) { - Debug.Assert(m_uFileVersion <= FileVersion32_3); + 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) @@ -268,6 +267,8 @@ namespace KeePassLib.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) @@ -340,7 +341,14 @@ namespace KeePassLib.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); } @@ -386,7 +394,7 @@ namespace KeePassLib.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) @@ -441,7 +449,7 @@ namespace KeePassLib.Serialization 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) @@ -849,15 +857,45 @@ namespace KeePassLib.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; + string str = ReadString(xr); + byte[] pb = Convert.FromBase64String(str); + 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); @@ -882,21 +920,26 @@ namespace KeePassLib.Serialization if(xr.MoveToAttribute(AttrRef)) { string strRef = xr.Value; - if(strRef != null) + if(!string.IsNullOrEmpty(strRef)) { - ProtectedBinary pb = BinPoolGet(strRef); - if(pb != null) + int iRef; + if(StrUtil.TryParseIntInvariant(strRef, out iRef)) { - // https://sourceforge.net/p/keepass/feature-requests/2023/ - xr.MoveToElement(); + 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)); + string strInner = ReadStringRaw(xr); + Debug.Assert(string.IsNullOrEmpty(strInner)); #else - ReadStringRaw(xr); + ReadStringRaw(xr); #endif - return pb; + return pb; + } + else { Debug.Assert(false); } } else { Debug.Assert(false); } } diff --git a/src/KeePassLib2Android/Serialization/KdbxFile.Read.cs b/src/KeePassLib2Android/Serialization/KdbxFile.Read.cs index 1e4e17a5..643edf08 100644 --- a/src/KeePassLib2Android/Serialization/KdbxFile.Read.cs +++ b/src/KeePassLib2Android/Serialization/KdbxFile.Read.cs @@ -17,6 +17,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +// #define KDBX_BENCHMARK + using System; using System.Collections.Generic; using System.Diagnostics; @@ -42,6 +44,7 @@ using KeePassLib.Cryptography.KeyDerivation; using KeePassLib.Interfaces; using KeePassLib.Keys; using KeePassLib.Resources; +using KeePassLib.Security; using KeePassLib.Utility; using keepass2android; @@ -77,9 +80,19 @@ namespace KeePassLib.Serialization Debug.Assert(sSource != null); if(sSource == null) throw new ArgumentNullException("sSource"); + 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; + m_pbsBinaries.Clear(); + UTF8Encoding encNoBom = StrUtil.Utf8; byte[] pbCipherKey = null; byte[] pbHmacKey64 = null; @@ -98,6 +111,7 @@ namespace KeePassLib.Serialization BinaryReaderEx br = new BinaryReaderEx(sHashing, encNoBom, KLRes.FileCorrupted); byte[] pbHeader = LoadHeader(br); + m_pbHashOfHeader = CryptoUtil.HashSha256(pbHeader); int cbEncKey, cbEncIV; ICipherEngine iCipher = GetCipher(out cbEncKey, out cbEncIV); @@ -107,8 +121,11 @@ namespace KeePassLib.Serialization ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64); + string strIncomplete = KLRes.FileHeaderCorrupted + " " + + KLRes.FileIncomplete; + Stream sPlain; - if(m_uFileVersion <= FileVersion32_3) + if(m_uFileVersion < FileVersion32_4) { Stream sDecrypted = EncryptStream(sHashing, iCipher, pbCipherKey, cbEncIV, false); @@ -121,11 +138,11 @@ namespace KeePassLib.Serialization lStreams.Add(sDecrypted); BinaryReaderEx brDecrypted = new BinaryReaderEx(sDecrypted, - encNoBom, KLRes.FileCorrupted); + encNoBom, strIncomplete); byte[] pbStoredStartBytes = brDecrypted.ReadBytes(32); if((m_pbStreamStartBytes == null) || (m_pbStreamStartBytes.Length != 32)) - throw new InvalidDataException(); + throw new EndOfStreamException(strIncomplete); if(!MemUtil.ArraysEqual(pbStoredStartBytes, m_pbStreamStartBytes)) throw new InvalidCompositeKeyException(); @@ -137,10 +154,16 @@ namespace KeePassLib.Serialization } 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 InvalidDataException(); + throw new EndOfStreamException(strIncomplete); if(!MemUtil.ArraysEqual(pbHeaderHmac, pbStoredHmac)) throw new InvalidCompositeKeyException(); @@ -161,6 +184,9 @@ namespace KeePassLib.Serialization lStreams.Add(sXml); } else sXml = sPlain; + + if(m_uFileVersion >= FileVersion32_4) + LoadInnerHeader(sXml); // Binary header before XML } else if(fmt == KdbxFormat.PlainXml) sXml = sHashing; @@ -168,16 +194,15 @@ namespace KeePassLib.Serialization 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 (m_slLogger != null) m_slLogger.SetText("KP2AKEY_ParsingDatabase", LogStatusType.AdditionalInfo); @@ -225,6 +250,12 @@ namespace KeePassLib.Serialization CommonCleanUpRead(lStreams, sHashing); } + +#if KDBX_BENCHMARK + swTime.Stop(); + MessageService.ShowInfo("Loading KDBX took " + + swTime.ElapsedMilliseconds.ToString() + " ms."); +#endif } private void CommonCleanUpRead(List lStreams, HashingStreamEx sHashing) @@ -235,8 +266,10 @@ namespace KeePassLib.Serialization m_pbHashOfFileOnDisk = sHashing.Hash; Debug.Assert(m_pbHashOfFileOnDisk != null); + CleanUpInnerRandomStream(); // Reset memory protection settings (to always use reasonable // defaults) + // defaults) m_pwDatabase.MemoryProtection = new MemoryProtectionConfig(); // Remove old backups (this call is required here in order to apply @@ -257,6 +290,10 @@ namespace KeePassLib.Serialization 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; @@ -291,7 +328,7 @@ namespace KeePassLib.Serialization byte[] pbHeader = msHeader.ToArray(); msHeader.Close(); - m_pbHashOfHeader = CryptoUtil.HashSha256(pbHeader); + br.ReadExceptionText = strPrevExcpText; return pbHeader; } @@ -304,21 +341,13 @@ namespace KeePassLib.Serialization int cbSize; Debug.Assert(m_uFileVersion > 0); - if(m_uFileVersion <= FileVersion32_3) + 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); byte[] pbData = MemUtil.EmptyByteArray; - if(cbSize > 0) - { - string strPrevExcpText = brSource.ReadExceptionText; - brSource.ReadExceptionText = KLRes.FileHeaderEndEarly; - - pbData = brSource.ReadBytes(cbSize); - - brSource.ReadExceptionText = strPrevExcpText; - } + if(cbSize > 0) pbData = brSource.ReadBytes(cbSize); bool bResult = true; KdbxHeaderFieldID kdbID = (KdbxHeaderFieldID)btFieldID; @@ -343,6 +372,8 @@ namespace KeePassLib.Serialization // Obsolete; for backward compatibility only case KdbxHeaderFieldID.TransformSeed: + Debug.Assert(m_uFileVersion < FileVersion32_4); + AesKdf kdfS = new AesKdf(); if(!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfS.Uuid)) m_pwDatabase.KdfParameters = kdfS.GetDefaultParameters(); @@ -355,6 +386,8 @@ namespace KeePassLib.Serialization // Obsolete; for backward compatibility only case KdbxHeaderFieldID.TransformRounds: + Debug.Assert(m_uFileVersion < FileVersion32_4); + AesKdf kdfR = new AesKdf(); if(!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfR.Uuid)) m_pwDatabase.KdfParameters = kdfR.GetDefaultParameters(); @@ -368,17 +401,20 @@ namespace KeePassLib.Serialization 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_3); + Debug.Assert(m_uFileVersion < FileVersion32_4); m_pbStreamStartBytes = pbData; break; case KdbxHeaderFieldID.InnerRandomStreamID: + Debug.Assert(m_uFileVersion < FileVersion32_4); SetInnerRandomStreamID(pbData); break; @@ -402,6 +438,68 @@ namespace KeePassLib.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 != (int)PwUuid.UuidSize)) @@ -429,19 +527,31 @@ namespace KeePassLib.Serialization } [Obsolete] - public static List ReadEntries(PwDatabase pwDatabase, Stream msData) + public static List ReadEntries(Stream msData) { - return ReadEntries(msData); + return ReadEntries(msData, null, false); } + [Obsolete] + public static List ReadEntries(PwDatabase pdContext, Stream 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(); /* KdbxFile f = new KdbxFile(pwDatabase); + if(msData == null) { Debug.Assert(false); return lEntries; } f.m_format = KdbxFormat.PlainXml; XmlDocument doc = new XmlDocument(); @@ -467,20 +577,43 @@ namespace KeePassLib.Serialization else { Debug.Assert(false); } } + return vEntries; */ + + 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/src/KeePassLib2Android/Serialization/KdbxFile.Write.cs b/src/KeePassLib2Android/Serialization/KdbxFile.Write.cs index a000a188..8fd70896 100644 --- a/src/KeePassLib2Android/Serialization/KdbxFile.Write.cs +++ b/src/KeePassLib2Android/Serialization/KdbxFile.Write.cs @@ -83,14 +83,22 @@ namespace KeePassLib.Serialization Debug.Assert(sSaveTo != null); if(sSaveTo == null) throw new ArgumentNullException("sSaveTo"); + if(m_bUsedOnce) + throw new InvalidOperationException("Do not reuse KdbxFile objects!"); + m_bUsedOnce = true; + m_format = fmt; m_slLogger = slLogger; + 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); @@ -116,20 +124,24 @@ namespace KeePassLib.Serialization "UUID: " + puKdf.ToHexString() + "."); kdf.Randomize(m_pwDatabase.KdfParameters); - if(m_uFileVersion <= FileVersion32_3) + if(m_format == KdbxFormat.Default) { + if(m_uFileVersion < FileVersion32_4) + { m_craInnerRandomStream = CrsAlgorithm.Salsa20; - m_pbProtectedStreamKey = cr.GetRandomBytes(32); + m_pbInnerRandomStreamKey = cr.GetRandomBytes(32); } else // KDBX >= 4 { m_craInnerRandomStream = CrsAlgorithm.ChaCha20; - m_pbProtectedStreamKey = cr.GetRandomBytes(64); + m_pbInnerRandomStreamKey = cr.GetRandomBytes(64); } - m_randomStream = new CryptoRandomStream(m_craInnerRandomStream, - m_pbProtectedStreamKey); - if(m_uFileVersion <= FileVersion32_3) + m_randomStream = new CryptoRandomStream(m_craInnerRandomStream, + m_pbInnerRandomStreamKey); + } + + if(m_uFileVersion < FileVersion32_4) m_pbStreamStartBytes = cr.GetRandomBytes(32); Stream sXml; @@ -144,7 +156,7 @@ namespace KeePassLib.Serialization ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64); Stream sPlain; - if(m_uFileVersion <= FileVersion32_3) + if(m_uFileVersion < FileVersion32_4) { Stream sEncrypted = EncryptStream(sHashing, iCipher, pbCipherKey, cbEncIV, true); @@ -158,6 +170,9 @@ namespace KeePassLib.Serialization } 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); @@ -178,6 +193,9 @@ namespace KeePassLib.Serialization lStreams.Add(sXml); } else sXml = sPlain; + + if(m_uFileVersion >= FileVersion32_4) + WriteInnerHeader(sXml); // Binary header before XML } else if(m_format == KdbxFormat.PlainXml) sXml = sHashing; @@ -213,7 +231,7 @@ namespace KeePassLib.Serialization #endif m_xmlWriter = xw; - WriteDocument(pgDataSource); + WriteDocument(pgRoot); m_xmlWriter.Flush(); m_xmlWriter.Close(); @@ -239,6 +257,8 @@ namespace KeePassLib.Serialization m_pbHashOfFileOnDisk = sHashing.Hash; Debug.Assert(m_pbHashOfFileOnDisk != null); + CleanUpInnerRandomStream(); + m_xmlWriter = null; m_pbHashOfHeader = null; } @@ -261,7 +281,7 @@ namespace KeePassLib.Serialization WriteHeaderField(ms, KdbxHeaderFieldID.MasterSeed, m_pbMasterSeed); - if(m_uFileVersion <= FileVersion32_3) + if(m_uFileVersion < FileVersion32_4) { Debug.Assert(m_pwDatabase.KdfParameters.KdfUuid.Equals( (new AesKdf()).Uuid)); @@ -278,15 +298,18 @@ namespace KeePassLib.Serialization if(m_pbEncryptionIV.Length > 0) WriteHeaderField(ms, KdbxHeaderFieldID.EncryptionIV, m_pbEncryptionIV); - WriteHeaderField(ms, KdbxHeaderFieldID.ProtectedStreamKey, m_pbProtectedStreamKey); + if(m_uFileVersion < FileVersion32_4) + { + WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamKey, + m_pbInnerRandomStreamKey); - if(m_uFileVersion <= FileVersion32_3) WriteHeaderField(ms, KdbxHeaderFieldID.StreamStartBytes, m_pbStreamStartBytes); int nIrsID = (int)m_craInnerRandomStream; WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamID, MemUtil.Int32ToBytes(nIrsID)); + } // Write public custom data only when there is at least one item, // because KDBX 3.1 didn't support this field yet @@ -313,7 +336,7 @@ namespace KeePassLib.Serialization if(cb < 0) { Debug.Assert(false); throw new OutOfMemoryException(); } Debug.Assert(m_uFileVersion > 0); - if(m_uFileVersion <= FileVersion32_3) + if(m_uFileVersion < FileVersion32_4) { if(cb > (int)ushort.MaxValue) { @@ -325,21 +348,64 @@ namespace KeePassLib.Serialization } else MemUtil.Write(s, MemUtil.Int32ToBytes(cb)); - if(cb > 0) s.Write(pb, 0, cb); + MemUtil.Write(s, pb); } - private void WriteDocument(PwGroup pgDataSource) + private void WriteInnerHeader(Stream s) + { + int nIrsID = (int)m_craInnerRandomStream; + WriteInnerHeaderField(s, KdbxInnerHeaderFieldID.InnerRandomStreamID, + MemUtil.Int32ToBytes(nIrsID), null); + + WriteInnerHeaderField(s, KdbxInnerHeaderFieldID.InnerRandomStreamKey, + m_pbInnerRandomStreamKey, null); + + ProtectedBinary[] vBin = m_pbsBinaries.ToArray(); + for(int i = 0; i < vBin.Length; ++i) + { + ProtectedBinary pb = vBin[i]; + if(pb == null) throw new InvalidOperationException(); + + 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 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); - m_xmlWriter.WriteStartDocument(true); m_xmlWriter.WriteStartElement(ElemDocNode); @@ -413,11 +479,11 @@ namespace KeePassLib.Serialization WriteObject(ElemGenerator, PwDatabase.LocalizedAppName, false); - if((m_pbHashOfHeader != null) && (m_uFileVersion <= FileVersion32_3)) + if((m_pbHashOfHeader != null) && (m_uFileVersion < FileVersion32_4)) WriteObject(ElemHeaderHash, Convert.ToBase64String( m_pbHashOfHeader), false); - if(m_uFileVersion > FileVersion32_3) + if(m_uFileVersion >= FileVersion32_4) WriteObject(ElemSettingsChanged, m_pwDatabase.SettingsChanged); WriteObject(ElemDbName, m_pwDatabase.Name, true); @@ -431,6 +497,8 @@ namespace KeePassLib.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); @@ -447,7 +515,9 @@ namespace KeePassLib.Serialization WriteObject(ElemLastSelectedGroup, m_pwDatabase.LastSelectedGroup); WriteObject(ElemLastTopVisibleGroup, m_pwDatabase.LastTopVisibleGroup); + if((m_format != KdbxFormat.Default) || (m_uFileVersion < FileVersion32_4)) WriteBinPool(); + WriteList(ElemCustomData, m_pwDatabase.CustomData); m_xmlWriter.WriteEndElement(); @@ -715,8 +785,28 @@ namespace KeePassLib.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, @@ -763,7 +853,7 @@ namespace KeePassLib.Serialization bProtected = m_pwDatabase.MemoryProtection.ProtectNotes; } - if(bProtected && (m_format != KdbxFormat.PlainXml)) + if(bProtected && (m_format == KdbxFormat.Default)) { m_xmlWriter.WriteAttributeString(AttrProtected, ValTrue); @@ -838,11 +928,15 @@ namespace KeePassLib.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 @@ -851,7 +945,7 @@ namespace KeePassLib.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); @@ -861,18 +955,26 @@ namespace KeePassLib.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); } } } @@ -892,11 +994,13 @@ namespace KeePassLib.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(); } @@ -904,21 +1008,23 @@ namespace KeePassLib.Serialization } [Obsolete] - public static bool WriteEntries(Stream msOutput, PwDatabase pwDatabase, - PwEntry[] vEntries) + public static bool WriteEntries(Stream msOutput, PwEntry[] vEntries) { - return WriteEntries(msOutput, vEntries); + return WriteEntries(msOutput, null, vEntries); } + public static bool WriteEntries(Stream msOutput, PwDatabase pdContext, + PwEntry[] vEntries) + { + if(msOutput == null) { Debug.Assert(false); return false; } /// + if(vEntries == null) { Debug.Assert(false); return false; } /// 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) - { /* KdbxFile f = new KdbxFile(pwDatabase); f.m_format = KdbxFormat.PlainXml; @@ -941,17 +1047,31 @@ namespace KeePassLib.Serialization xtw.WriteEndElement(); xtw.WriteEndDocument(); - - xtw.Flush(); - xtw.Close(); - return true; */ - 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; } + xtw.Flush(); + 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); } + } + xtw.Close(); + PwEntry peCopy = pe.CloneDeep(); + pg.AddEntry(peCopy, true); + } + return true; */ KdbxFile f = new KdbxFile(pd); f.Save(msOutput, null, KdbxFormat.PlainXml, null); return true; diff --git a/src/KeePassLib2Android/Serialization/KdbxFile.cs b/src/KeePassLib2Android/Serialization/KdbxFile.cs index 2a5a872e..261e4fd7 100644 --- a/src/KeePassLib2Android/Serialization/KdbxFile.cs +++ b/src/KeePassLib2Android/Serialization/KdbxFile.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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 @@ -125,7 +125,9 @@ namespace KeePassLib.Serialization /// file version is too high), the last 2 bytes are informational. /// private const uint FileVersion32 = 0x00040000; - private const uint FileVersion32_3 = 0x00030001; // Old format + + 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; @@ -156,6 +158,7 @@ namespace KeePassLib.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"; @@ -239,6 +242,7 @@ namespace KeePassLib.Serialization private const string ElemStringDictExItem = "Item"; private PwDatabase m_pwDatabase; // Not null, see constructor + private bool m_bUsedOnce = false; private XmlWriter m_xmlWriter = null; private CryptoRandomStream m_randomStream = null; @@ -249,20 +253,19 @@ namespace KeePassLib.Serialization private byte[] m_pbMasterSeed = null; // private byte[] m_pbTransformSeed = null; private byte[] m_pbEncryptionIV = null; - private byte[] m_pbProtectedStreamKey = null; private byte[] m_pbStreamStartBytes = null; // 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 @@ -279,13 +282,29 @@ namespace KeePassLib.Serialization TransformSeed = 5, // KDBX 3.1, for backward compatibility only TransformRounds = 6, // KDBX 3.1, for backward compatibility only EncryptionIV = 7, - ProtectedStreamKey = 8, + InnerRandomStreamKey = 8, // KDBX 3.1, for backward compatibility only StreamStartBytes = 9, // KDBX 3.1, for backward compatibility only - InnerRandomStreamID = 10, - KdfParameters = 11, // KDBX 4 + 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 { get { return m_pbHashOfFileOnDisk; } @@ -298,6 +317,13 @@ namespace KeePassLib.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, @@ -344,6 +370,10 @@ namespace KeePassLib.Serialization private uint GetMinKdbxVersion() { + if(m_uForceVersion != 0) return m_uForceVersion; + + // See also KeePassKdb2x3.Export (KDBX 3.1 export module) + AesKdf kdfAes = new AesKdf(); if(!kdfAes.Uuid.Equals(m_pwDatabase.KdfParameters.KdfUuid)) return FileVersion32; @@ -488,64 +518,12 @@ namespace KeePassLib.Serialization // Do not clear the list } - private void BinPoolBuild(PwGroup pgDataSource) + private void CleanUpInnerRandomStream() { - m_dictBinPool = new Dictionary(); + if(m_randomStream != null) m_randomStream.Dispose(); - if(pgDataSource == null) { Debug.Assert(false); return; } - - EntryHandler eh = delegate(PwEntry pe) - { - foreach(PwEntry peHistory in pe.History) - { - BinPoolAdd(peHistory.Binaries); - } - - BinPoolAdd(pe.Binaries); - return true; - }; - - pgDataSource.TraverseTree(TraversalMethod.PreOrder, null, eh); - } - - private void BinPoolAdd(ProtectedBinaryDictionary dict) - { - foreach(KeyValuePair kvp in dict) - { - BinPoolAdd(kvp.Value); - } - } - - 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) - { - if(pb.Equals(kvp.Value)) return kvp.Key; - } - - return null; - } - - private ProtectedBinary BinPoolGet(string strKey) - { - if(strKey == null) { Debug.Assert(false); return null; } - - ProtectedBinary pb; - if(m_dictBinPool.TryGetValue(strKey, out pb)) return pb; - - return null; + if(m_pbInnerRandomStreamKey != null) + MemUtil.ZeroByteArray(m_pbInnerRandomStreamKey); } private static void SaveBinary(string strName, ProtectedBinary pb, diff --git a/src/KeePassLib2Android/Serialization/OldFormatException.cs b/src/KeePassLib2Android/Serialization/OldFormatException.cs index f05de4a4..2dd6b20c 100644 --- a/src/KeePassLib2Android/Serialization/OldFormatException.cs +++ b/src/KeePassLib2Android/Serialization/OldFormatException.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Translation/KPControlCustomization.cs b/src/KeePassLib2Android/Translation/KPControlCustomization.cs index 65dcda0a..3372fcac 100644 --- a/src/KeePassLib2Android/Translation/KPControlCustomization.cs +++ b/src/KeePassLib2Android/Translation/KPControlCustomization.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2013 Dominik Reichl + Copyright (C) 2003-2017 Dominik Reichl Modified to be used with Mono for Android. Changes Copyright (C) 2013 Philipp Crocoll diff --git a/src/KeePassLib2Android/Translation/KPFormCustomization.cs b/src/KeePassLib2Android/Translation/KPFormCustomization.cs index 63a2842d..f3ca86f7 100644 --- a/src/KeePassLib2Android/Translation/KPFormCustomization.cs +++ b/src/KeePassLib2Android/Translation/KPFormCustomization.cs @@ -1,8 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2013 Dominik Reichl - - Modified to be used with Mono for Android. Changes Copyright (C) 2013 Philipp Crocoll + 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 @@ -21,15 +19,16 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Text; using System.Xml.Serialization; -using System.Diagnostics; -using System.Reflection; + +#if !KeePassUAP +using System.Windows.Forms; +#endif namespace KeePassLib.Translation { - public class Form - {} public sealed class KPFormCustomization { private string m_strFQName = string.Empty; @@ -69,7 +68,7 @@ namespace KeePassLib.Translation } } -#if (!KeePassLibSD && !KeePassRT) +#if (!KeePassLibSD && !KeePassUAP) private Form m_formEnglish = null; [XmlIgnore] public Form FormEnglish @@ -78,7 +77,7 @@ namespace KeePassLib.Translation set { m_formEnglish = value; } } - /*public void ApplyTo(Form form) + public void ApplyTo(Form form) { Debug.Assert(form != null); if(form == null) throw new ArgumentNullException("form"); @@ -103,7 +102,7 @@ namespace KeePassLib.Translation } foreach(Control cSub in c.Controls) ApplyToControl(cSub); - }*/ + } #endif } } diff --git a/src/KeePassLib2Android/Translation/KPStringTable.cs b/src/KeePassLib2Android/Translation/KPStringTable.cs index cafb2b5a..62ea37da 100644 --- a/src/KeePassLib2Android/Translation/KPStringTable.cs +++ b/src/KeePassLib2Android/Translation/KPStringTable.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Translation/KPStringTableItem.cs b/src/KeePassLib2Android/Translation/KPStringTableItem.cs index 3106fa71..5a879a90 100644 --- a/src/KeePassLib2Android/Translation/KPStringTableItem.cs +++ b/src/KeePassLib2Android/Translation/KPStringTableItem.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Translation/KPTranslation.cs b/src/KeePassLib2Android/Translation/KPTranslation.cs index e7a68269..6314f74c 100644 --- a/src/KeePassLib2Android/Translation/KPTranslation.cs +++ b/src/KeePassLib2Android/Translation/KPTranslation.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Translation/KPTranslationProperties.cs b/src/KeePassLib2Android/Translation/KPTranslationProperties.cs index 60b84db0..01e511bf 100644 --- a/src/KeePassLib2Android/Translation/KPTranslationProperties.cs +++ b/src/KeePassLib2Android/Translation/KPTranslationProperties.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Utility/AppLogEx.cs b/src/KeePassLib2Android/Utility/AppLogEx.cs index 53501856..53dcf09f 100644 --- a/src/KeePassLib2Android/Utility/AppLogEx.cs +++ b/src/KeePassLib2Android/Utility/AppLogEx.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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 @@ -57,7 +57,7 @@ namespace KeePassLib.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/src/KeePassLib2Android/Utility/MemUtil.cs b/src/KeePassLib2Android/Utility/MemUtil.cs index 964e25a4..5cfd4373 100644 --- a/src/KeePassLib2Android/Utility/MemUtil.cs +++ b/src/KeePassLib2Android/Utility/MemUtil.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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/src/KeePassLib2Android/Utility/MessageService.cs b/src/KeePassLib2Android/Utility/MessageService.cs index af9c4937..a017a78c 100644 --- a/src/KeePassLib2Android/Utility/MessageService.cs +++ b/src/KeePassLib2Android/Utility/MessageService.cs @@ -1,8 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2013 Dominik Reichl - - Modified to be used with Mono for Android. Changes Copyright (C) 2013 Philipp Crocoll + 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 @@ -22,45 +20,29 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; +using System.Diagnostics; using System.Text; -using System.Diagnostics; +#if !KeePassUAP +using System.Windows.Forms; +#endif using KeePassLib.Resources; using KeePassLib.Serialization; namespace KeePassLib.Utility { - public enum MessageBoxButtons - { - OK, OKCancel, AbortRetryIgnore, YesNoCancel, YesNo, RetryCancel - } - public enum MessageBoxIcon - { - Information, Warning, Error, Question - } - public enum MessageBoxDefaultButton - { - Button1, Button2, Button3 - } - - public enum DialogResult - { - Yes, No, Cancel, Retry, Abort - } - - public sealed class MessageServiceEventArgs : EventArgs { private string m_strTitle = string.Empty; private string m_strText = string.Empty; - //private MessageBoxButtons m_msgButtons = MessageBoxButtons.OK; - //private MessageBoxIcon m_msgIcon = MessageBoxIcon.None; + private MessageBoxButtons m_msgButtons = MessageBoxButtons.OK; + private MessageBoxIcon m_msgIcon = MessageBoxIcon.None; public string Title { get { return m_strTitle; } } public string Text { get { return m_strText; } } - //public MessageBoxButtons Buttons { get { return m_msgButtons; } } - //public MessageBoxIcon Icon { get { return m_msgIcon; } } + public MessageBoxButtons Buttons { get { return m_msgButtons; } } + public MessageBoxIcon Icon { get { return m_msgIcon; } } public MessageServiceEventArgs() { } @@ -69,7 +51,8 @@ namespace KeePassLib.Utility { m_strTitle = (strTitle ?? string.Empty); m_strText = (strText ?? string.Empty); - + m_msgButtons = msgButtons; + m_msgIcon = msgIcon; } } @@ -82,12 +65,14 @@ namespace KeePassLib.Utility private const MessageBoxIcon m_mbiWarning = MessageBoxIcon.Warning; private const MessageBoxIcon m_mbiFatal = MessageBoxIcon.Error; + private const MessageBoxOptions m_mboRtl = (MessageBoxOptions.RtlReading | + MessageBoxOptions.RightAlign); #else private const MessageBoxIcon m_mbiInfo = MessageBoxIcon.Asterisk; private const MessageBoxIcon m_mbiWarning = MessageBoxIcon.Exclamation; private const MessageBoxIcon m_mbiFatal = MessageBoxIcon.Hand; #endif - //private const MessageBoxIcon m_mbiQuestion = MessageBoxIcon.Question; + private const MessageBoxIcon m_mbiQuestion = MessageBoxIcon.Question; public static string NewLine { @@ -112,7 +97,9 @@ namespace KeePassLib.Utility get { return m_uCurrentMessageCount; } } +#if !KeePassUAP public static event EventHandler MessageShowing; +#endif private static string ObjectsToMessage(object[] vLines) { @@ -136,7 +123,7 @@ namespace KeePassLib.Utility Exception exObj = (obj as Exception); string strObj = (obj as string); -#if (!KeePassLibSD && !KeePassRT) +#if !KeePassLibSD StringCollection scObj = (obj as StringCollection); #endif @@ -147,7 +134,7 @@ namespace KeePassLib.Utility else if((exObj.Message != null) && (exObj.Message.Length > 0)) strAppend = exObj.Message; } -#if (!KeePassLibSD && !KeePassRT) +#if !KeePassLibSD else if(scObj != null) { StringBuilder sb = new StringBuilder(); @@ -176,30 +163,24 @@ namespace KeePassLib.Utility return sbText.ToString(); } -#if (!KeePassLibSD && !KeePassRT) - /*internal static Form GetTopForm() +#if (!KeePassLibSD && !KeePassUAP) + internal static Form GetTopForm() { FormCollection fc = Application.OpenForms; if((fc == null) || (fc.Count == 0)) return null; return fc[fc.Count - 1]; - }*/ + } #endif - private static DialogResult SafeShowMessageBox(string strText, string strTitle, +#if !KeePassUAP + internal static DialogResult SafeShowMessageBox(string strText, string strTitle, MessageBoxButtons mb, MessageBoxIcon mi, MessageBoxDefaultButton mdb) { -#if (KeePassLibSD || KeePassRT) +#if KeePassLibSD return MessageBox.Show(strText, strTitle, mb, mi, mdb); #else - - if (mb == MessageBoxButtons.OK) - { - //Android.Widget.Toast toast = .. - } - //this might help: http://www.gregshackles.com/2011/04/using-background-threads-in-mono-for-android-applications/ - throw new NotImplementedException(); - /*IWin32Window wnd = null; + IWin32Window wnd = null; try { Form f = GetTopForm(); @@ -228,12 +209,11 @@ namespace KeePassLib.Utility if(StrUtil.RightToLeft) return MessageBox.Show(strText, strTitle, mb, mi, mdb, m_mboRtl); return MessageBox.Show(strText, strTitle, mb, mi, mdb); - */ #endif } -#if (!KeePassLibSD && !KeePassRT) - /* internal delegate DialogResult SafeShowMessageBoxInternalDelegate(IWin32Window iParent, +#if !KeePassLibSD + internal delegate DialogResult SafeShowMessageBoxInternalDelegate(IWin32Window iParent, string strText, string strTitle, MessageBoxButtons mb, MessageBoxIcon mi, MessageBoxDefaultButton mdb); @@ -244,7 +224,7 @@ namespace KeePassLib.Utility if(StrUtil.RightToLeft) return MessageBox.Show(iParent, strText, strTitle, mb, mi, mdb, m_mboRtl); return MessageBox.Show(iParent, strText, strTitle, mb, mi, mdb); - }*/ + } #endif public static void ShowInfo(params object[] vLines) @@ -309,12 +289,13 @@ namespace KeePassLib.Utility try { -#if !KeePassLibSD - /* nicht benoetigt - hoffentlich :-) -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); } @@ -339,10 +320,10 @@ Clipboard.SetText(ObjectsToMessage(vLines, true));*/ if(MessageService.MessageShowing != null) MessageService.MessageShowing(null, new MessageServiceEventArgs( - strTitleEx, strTextEx, mbb, MessageBoxIcon.Question)); + strTitleEx, strTextEx, mbb, m_mbiQuestion)); DialogResult dr = SafeShowMessageBox(strTextEx, strTitleEx, mbb, - MessageBoxIcon.Question, MessageBoxDefaultButton.Button1); + m_mbiQuestion, MessageBoxDefaultButton.Button1); --m_uCurrentMessageCount; return dr; @@ -358,10 +339,10 @@ Clipboard.SetText(ObjectsToMessage(vLines, true));*/ if(MessageService.MessageShowing != null) MessageService.MessageShowing(null, new MessageServiceEventArgs( - strTitleEx, strTextEx, MessageBoxButtons.YesNo, MessageBoxIcon.Question)); + strTitleEx, strTextEx, MessageBoxButtons.YesNo, mbi)); DialogResult dr = SafeShowMessageBox(strTextEx, strTitleEx, - MessageBoxButtons.YesNo, MessageBoxIcon.Question, bDefaultToYes ? + MessageBoxButtons.YesNo, mbi, bDefaultToYes ? MessageBoxDefaultButton.Button1 : MessageBoxDefaultButton.Button2); --m_uCurrentMessageCount; @@ -370,17 +351,17 @@ Clipboard.SetText(ObjectsToMessage(vLines, true));*/ public static bool AskYesNo(string strText, string strTitle, bool bDefaultToYes) { - return AskYesNo(strText, strTitle, bDefaultToYes, MessageBoxIcon.Question); + return AskYesNo(strText, strTitle, bDefaultToYes, m_mbiQuestion); } public static bool AskYesNo(string strText, string strTitle) { - return AskYesNo(strText, strTitle, true, MessageBoxIcon.Question); + return AskYesNo(strText, strTitle, true, m_mbiQuestion); } public static bool AskYesNo(string strText) { - return AskYesNo(strText, null, true, MessageBoxIcon.Question); + return AskYesNo(strText, null, true, m_mbiQuestion); } public static void ShowLoadWarning(string strFilePath, Exception ex) @@ -391,21 +372,7 @@ Clipboard.SetText(ObjectsToMessage(vLines, true));*/ 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) @@ -425,18 +392,7 @@ Clipboard.SetText(ObjectsToMessage(vLines, true));*/ 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); } @@ -447,6 +403,45 @@ Clipboard.SetText(ObjectsToMessage(vLines, true));*/ 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 + MessageService.NewParagraph; + + str += KLRes.FileLoadFailed; + + if((ex != null) && !string.IsNullOrEmpty(ex.Message)) + { + str += MessageService.NewParagraph; + 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 + MessageService.NewParagraph; + + str += KLRes.FileSaveFailed; + + if((ex != null) && !string.IsNullOrEmpty(ex.Message)) + str += MessageService.NewParagraph + ex.Message; + + if(bCorruptionWarning) + str += MessageService.NewParagraph + KLRes.FileSaveCorruptionWarning; + + return str; + } public static void ExternalIncrementMessageCount() { diff --git a/src/KeePassLib2Android/Utility/MonoWorkarounds.cs b/src/KeePassLib2Android/Utility/MonoWorkarounds.cs index 76410a51..1952a7e1 100644 --- a/src/KeePassLib2Android/Utility/MonoWorkarounds.cs +++ b/src/KeePassLib2Android/Utility/MonoWorkarounds.cs @@ -24,6 +24,7 @@ using System.Diagnostics; using System.IO; using System.Reflection; using System.Text; +using System.Threading; using System.Xml; @@ -34,6 +35,8 @@ namespace KeePassLib.Utility public static class MonoWorkarounds { private static Dictionary m_dForceReq = new Dictionary(); + private static Thread m_thFixClip = null; + private static bool? m_bReq = null; public static bool IsRequired() @@ -114,6 +117,17 @@ namespace KeePassLib.Utility // 3574233558: // Problems with minimizing windows, no content rendered. // https://sourceforge.net/p/keepass/discussion/329220/thread/d50a79d6/ + // https://bugs.launchpad.net/ubuntu/+source/keepass2/+bug/801414 + // 891029: + // Increase tab control height, otherwise Mono throws exceptions. + // https://sourceforge.net/projects/keepass/forums/forum/329221/topic/4519750 + // https://bugs.launchpad.net/ubuntu/+source/keepass2/+bug/891029 + // 836428016: + // ListView group header selection unsupported. + // https://sourceforge.net/p/keepass/discussion/329221/thread/31dae0f0/ + // 3574233558: + // Problems with minimizing windows, no content rendered. + // https://sourceforge.net/p/keepass/discussion/329220/thread/d50a79d6/ public static bool IsRequired(uint uBugID) { if(!MonoWorkarounds.IsRequired()) return false; @@ -146,6 +160,118 @@ namespace KeePassLib.Utility } } + internal static void Initialize() + { + Terminate(); + + // m_fOwnWindow = fOwnWindow; + + if(IsRequired(1530)) + { + try + { + ThreadStart ts = new ThreadStart(MonoWorkarounds.FixClipThread); + m_thFixClip = new Thread(ts); + m_thFixClip.Start(); + } + catch(Exception) { Debug.Assert(false); } + } + } + + internal static void Terminate() + { + if(m_thFixClip != null) + { + try { m_thFixClip.Abort(); } + catch(Exception) { Debug.Assert(false); } + + m_thFixClip = null; + } + } + + private static void FixClipThread() + { + try + { +#if !KeePassUAP + const string strXSel = "xsel"; + const AppRunFlags rfW = AppRunFlags.WaitForExit; + + string strLast = null; + while(true) + { + string str = NativeLib.RunConsoleApp(strXSel, + "--output --clipboard"); + if(str == null) return; // 'xsel' not installed + + if(str != strLast) + { + if(NeedClipboardWorkaround()) + NativeLib.RunConsoleApp(strXSel, + "--input --clipboard", str, rfW); + + strLast = str; + } + + Thread.Sleep(250); + } +#endif + } + catch(ThreadAbortException) + { + try { Thread.ResetAbort(); } + catch(Exception) { Debug.Assert(false); } + } + catch(Exception) { Debug.Assert(false); } + finally { m_thFixClip = null; } + } + + private static bool NeedClipboardWorkaround() + { + const bool bDef = true; + + try + { + string strHandle = (NativeLib.RunConsoleApp("xdotool", + "getactivewindow") ?? string.Empty).Trim(); + if(strHandle.Length == 0) return bDef; + + // 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 = m_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; + + // Workaround for Remmina + if(strWmClass.IndexOf("\"Remmina\"", + StrUtil.CaseIgnoreCmp) >= 0) return true; + + return false; + } + catch(ThreadAbortException) { throw; } + catch(Exception) { Debug.Assert(false); } + + return bDef; + } + +#if !KeePassUAP + public static void ApplyTo(Form f) + { + if(!MonoWorkarounds.IsRequired()) return; + if(f == null) { Debug.Assert(false); return; } /// /// Ensure that the file ~/.recently-used is valid (in order to /// prevent Mono's FileDialog from crashing). diff --git a/src/KeePassLib2Android/Utility/StrUtil.cs b/src/KeePassLib2Android/Utility/StrUtil.cs index 8625eaf4..97690f69 100644 --- a/src/KeePassLib2Android/Utility/StrUtil.cs +++ b/src/KeePassLib2Android/Utility/StrUtil.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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 @@ -669,7 +669,8 @@ namespace KeePassLib.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 } @@ -1722,5 +1723,16 @@ namespace KeePassLib.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/src/KeePassLib2Android/Utility/TimeUtil.cs b/src/KeePassLib2Android/Utility/TimeUtil.cs index 2e67dfff..9d4b410a 100644 --- a/src/KeePassLib2Android/Utility/TimeUtil.cs +++ b/src/KeePassLib2Android/Utility/TimeUtil.cs @@ -38,11 +38,18 @@ namespace KeePassLib.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 { @@ -50,8 +57,8 @@ namespace KeePassLib.Utility { if(m_odtUnixRoot.HasValue) return m_odtUnixRoot.Value; /// Pack a DateTime object into 5 bytes. Layout: 2 zero bits, - DateTime dtRoot = (new DateTime(1970, 1, 1, 0, 0, 0, 0, - DateTimeKind.Utc)).ToLocalTime(); + DateTime dtRoot = new DateTime(1970, 1, 1, 0, 0, 0, 0, + DateTimeKind.Utc); /// year 12 bits, month 4 bits, day 5 bits, hour 5 bits, minute 6 m_odtUnixRoot = dtRoot; return dtRoot; @@ -63,10 +70,11 @@ namespace KeePassLib.Utility /// /// 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: @@ -88,6 +96,7 @@ namespace KeePassLib.Utility /// /// Packed time, 5 bytes. /// Unpacked DateTime object. + [Obsolete] public static DateTime UnpackTime(byte[] pb) { Debug.Assert((pb != null) && (pb.Length == 5)); @@ -104,7 +113,8 @@ namespace KeePassLib.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(); } /// @@ -112,10 +122,13 @@ namespace KeePassLib.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); @@ -133,6 +146,7 @@ namespace KeePassLib.Utility /// /// Packed time, 7 bytes. /// Unpacked DateTime object. + [Obsolete] public static DateTime UnpackPwTime(byte[] pb) { Debug.Assert(PwTimeLength == 7); @@ -140,8 +154,8 @@ namespace KeePassLib.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(); } /// @@ -151,23 +165,32 @@ namespace KeePassLib.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 @@ -175,19 +198,25 @@ namespace KeePassLib.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 @@ -277,8 +306,10 @@ namespace KeePassLib.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; } @@ -289,23 +320,13 @@ namespace KeePassLib.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; } - /// - /// Deserializes a UTC XML DateTime to a local time, or returns defaultValue if it could not be parsed - /// - public static DateTime DeserializeUtcOrDefault(string xmlDateTimeString, DateTime defaultValue) + public static double SerializeUnix(DateTime dt) { - try - { - return System.Xml.XmlConvert.ToDateTime(xmlDateTimeString, System.Xml.XmlDateTimeSerializationMode.Local); - } - catch(FormatException) - { - return defaultValue; - } + return (ToUtc(dt, false) - TimeUtil.UnixRoot).TotalSeconds; } @@ -322,15 +343,21 @@ namespace KeePassLib.Utility } 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; } @@ -352,7 +379,7 @@ namespace KeePassLib.Utility 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; } } } @@ -362,11 +389,13 @@ namespace KeePassLib.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'; @@ -399,5 +428,64 @@ namespace KeePassLib.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/src/KeePassLib2Android/Utility/UrlUtil.cs b/src/KeePassLib2Android/Utility/UrlUtil.cs index ff0c349c..aa34c093 100644 --- a/src/KeePassLib2Android/Utility/UrlUtil.cs +++ b/src/KeePassLib2Android/Utility/UrlUtil.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2016 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 @@ -285,7 +285,7 @@ namespace KeePassLib.Utility else // Unhide { fa &= ~FileAttributes.Hidden; - if((long)fa == 0) fa |= FileAttributes.Normal; + if((long)fa == 0) fa = FileAttributes.Normal; } File.SetAttributes(strFile, fa);