diff --git a/ModernKeePass.Shared/ModernKeePass.Shared.csproj b/ModernKeePass.Shared/ModernKeePass.Shared.csproj new file mode 100644 index 0000000..7c488ee --- /dev/null +++ b/ModernKeePass.Shared/ModernKeePass.Shared.csproj @@ -0,0 +1,52 @@ + + + + + 14.0 + Debug + AnyCPU + {A3354969-5AAC-4075-8CBF-EA4805B59EFA} + Library + Properties + ModernKeePass.Shared + ModernKeePass.Shared + en-US + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + v5.0 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + \ No newline at end of file diff --git a/ModernKeePass.Shared/Properties/AssemblyInfo.cs b/ModernKeePass.Shared/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..0685491 --- /dev/null +++ b/ModernKeePass.Shared/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Resources; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ModernKeePass.Shared")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ModernKeePass.Shared")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ModernKeePass.Shared/project.json b/ModernKeePass.Shared/project.json new file mode 100644 index 0000000..b737941 --- /dev/null +++ b/ModernKeePass.Shared/project.json @@ -0,0 +1,10 @@ +{ + "supports": {}, + "dependencies": { + "Microsoft.NETCore.Portable.Compatibility": "1.0.1", + "NETStandard.Library": "1.6.0" + }, + "frameworks": { + "netstandard1.2": {} + } +} \ No newline at end of file diff --git a/ModernKeePass.sln b/ModernKeePass.sln index 2624c80..9c214f3 100644 --- a/ModernKeePass.sln +++ b/ModernKeePass.sln @@ -9,8 +9,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModernKeePass.Lib", "Modern EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModernKeePass.LibTest", "ModernKeePassLib.Test\ModernKeePass.LibTest.csproj", "{0A4279CF-2A67-4868-9906-052E50C25F3B}" EndProject -Project("{F5034706-568F-408A-B7B3-4D38C6DB8A32}") = "Scripts", "Scripts\Scripts.pssproj", "{6CAFC0C6-A428-4D30-A9F9-700E829FEA51}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModernKeePass.AppTest", "ModernKeePassApp.Test\ModernKeePass.AppTest.csproj", "{7E80F5E7-724A-4668-9333-B10F5D75C6D0}" EndProject Global @@ -89,22 +87,6 @@ Global {0A4279CF-2A67-4868-9906-052E50C25F3B}.Release|x86.ActiveCfg = Release|x86 {0A4279CF-2A67-4868-9906-052E50C25F3B}.Release|x86.Build.0 = Release|x86 {0A4279CF-2A67-4868-9906-052E50C25F3B}.Release|x86.Deploy.0 = Release|x86 - {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|ARM.ActiveCfg = Debug|Any CPU - {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|ARM.Build.0 = Debug|Any CPU - {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|x64.ActiveCfg = Debug|Any CPU - {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|x64.Build.0 = Debug|Any CPU - {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|x86.ActiveCfg = Debug|Any CPU - {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|x86.Build.0 = Debug|Any CPU - {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|Any CPU.Build.0 = Release|Any CPU - {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|ARM.ActiveCfg = Release|Any CPU - {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|ARM.Build.0 = Release|Any CPU - {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|x64.ActiveCfg = Release|Any CPU - {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|x64.Build.0 = Release|Any CPU - {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|x86.ActiveCfg = Release|Any CPU - {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|x86.Build.0 = Release|Any CPU {7E80F5E7-724A-4668-9333-B10F5D75C6D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7E80F5E7-724A-4668-9333-B10F5D75C6D0}.Debug|Any CPU.Build.0 = Debug|Any CPU {7E80F5E7-724A-4668-9333-B10F5D75C6D0}.Debug|Any CPU.Deploy.0 = Debug|Any CPU diff --git a/ModernKeePass/Aop/DatabaseChanged.cs b/ModernKeePass/Aop/DatabaseChanged.cs deleted file mode 100644 index dbc8e6b..0000000 --- a/ModernKeePass/Aop/DatabaseChanged.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using ModernKeePass.Services; - -namespace ModernKeePass.Attributes -{ - [AttributeUsage(AttributeTargets.All)] - public class DatabaseChangedAttribute: Attribute - { - public DatabaseChangedAttribute() - { - DatabaseService.Instance.HasChanged = true; - } - } -} diff --git a/ModernKeePass/Aop/DatabaseChangedProxy.cs b/ModernKeePass/Aop/DatabaseChangedProxy.cs deleted file mode 100644 index e47604a..0000000 --- a/ModernKeePass/Aop/DatabaseChangedProxy.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Reflection; -using ModernKeePass.Interfaces; - -namespace ModernKeePass.Aop -{ - public class DatabaseChangedProxy: IProxyInvocationHandler - { - private readonly T _decorated; - private readonly IDatabaseService _databaseService; - - public DatabaseChangedProxy(T decorated, IDatabaseService databaseService) - { - _decorated = decorated; - _databaseService = databaseService; - } - - public object Invoke(object proxy, MethodInfo method, object[] parameters) - { - object retVal = null; - retVal = method.Invoke(proxy, parameters); - _databaseService.HasChanged = true; - - return retVal; - } - } -} diff --git a/ModernKeePass/ModernKeePass.App.csproj b/ModernKeePass/ModernKeePass.App.csproj index 2241608..d9b3652 100644 --- a/ModernKeePass/ModernKeePass.App.csproj +++ b/ModernKeePass/ModernKeePass.App.csproj @@ -111,11 +111,9 @@ - App.xaml - diff --git a/ModernKeePassLib/Collections/ProtectedStringDictionary.cs b/ModernKeePassLib/Collections/ProtectedStringDictionary.cs index a0a5dbc..b60b8aa 100644 --- a/ModernKeePassLib/Collections/ProtectedStringDictionary.cs +++ b/ModernKeePassLib/Collections/ProtectedStringDictionary.cs @@ -136,7 +136,7 @@ namespace ModernKeePassLib.Collections return false; } - if(ps.ReadString() != kvp.Value.ReadString()) return false; + if(!ps.Equals(kvp.Value, false)) return false; } if(bNeEqStd) @@ -292,12 +292,7 @@ namespace ModernKeePassLib.Collections if(ps == null) return; // Nothing to do, no assert if(ps.IsProtected != bProtect) - { - byte[] pbData = ps.ReadUtf8(); - Set(strField, new ProtectedString(bProtect, pbData)); - - if(bProtect) MemUtil.ZeroByteArray(pbData); - } + Set(strField, ps.WithProtection(bProtect)); } } } diff --git a/ModernKeePassLib/Collections/PwObjectList.cs b/ModernKeePassLib/Collections/PwObjectList.cs index 0857728..9d97763 100644 --- a/ModernKeePassLib/Collections/PwObjectList.cs +++ b/ModernKeePassLib/Collections/PwObjectList.cs @@ -352,6 +352,13 @@ namespace ModernKeePassLib.Collections m_vObjects.Sort(tComparer); } + public void Sort(Comparison tComparison) + { + if(tComparison == null) throw new ArgumentNullException("tComparison"); + + m_vObjects.Sort(tComparison); + } + public static PwObjectList FromArray(T[] tArray) { if(tArray == null) throw new ArgumentNullException("tArray"); diff --git a/ModernKeePassLib/Cryptography/CryptoRandom.cs b/ModernKeePassLib/Cryptography/CryptoRandom.cs index e6796d2..1a83aab 100644 --- a/ModernKeePassLib/Cryptography/CryptoRandom.cs +++ b/ModernKeePassLib/Cryptography/CryptoRandom.cs @@ -223,48 +223,43 @@ namespace ModernKeePassLib.Cryptography pb = DiagnosticsExt.GetProcessEntropy(); MemUtil.Write(ms, pb); #elif !KeePassLibSD - Process p = null; try { - p = Process.GetCurrentProcess(); + using(Process p = Process.GetCurrentProcess()) + { + pb = MemUtil.Int64ToBytes(p.Handle.ToInt64()); + MemUtil.Write(ms, pb); + pb = MemUtil.Int32ToBytes(p.HandleCount); + MemUtil.Write(ms, pb); + pb = MemUtil.Int32ToBytes(p.Id); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.NonpagedSystemMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PagedMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PagedSystemMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PeakPagedMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PeakVirtualMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PeakWorkingSet64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PrivateMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.StartTime.ToBinary()); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.VirtualMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.WorkingSet64); + MemUtil.Write(ms, pb); - pb = MemUtil.Int64ToBytes(p.Handle.ToInt64()); - MemUtil.Write(ms, pb); - pb = MemUtil.Int32ToBytes(p.HandleCount); - MemUtil.Write(ms, pb); - pb = MemUtil.Int32ToBytes(p.Id); - MemUtil.Write(ms, pb); - pb = MemUtil.Int64ToBytes(p.NonpagedSystemMemorySize64); - MemUtil.Write(ms, pb); - pb = MemUtil.Int64ToBytes(p.PagedMemorySize64); - MemUtil.Write(ms, pb); - pb = MemUtil.Int64ToBytes(p.PagedSystemMemorySize64); - MemUtil.Write(ms, pb); - pb = MemUtil.Int64ToBytes(p.PeakPagedMemorySize64); - MemUtil.Write(ms, pb); - pb = MemUtil.Int64ToBytes(p.PeakVirtualMemorySize64); - MemUtil.Write(ms, pb); - pb = MemUtil.Int64ToBytes(p.PeakWorkingSet64); - MemUtil.Write(ms, pb); - pb = MemUtil.Int64ToBytes(p.PrivateMemorySize64); - MemUtil.Write(ms, pb); - pb = MemUtil.Int64ToBytes(p.StartTime.ToBinary()); - MemUtil.Write(ms, pb); - pb = MemUtil.Int64ToBytes(p.VirtualMemorySize64); - MemUtil.Write(ms, pb); - pb = MemUtil.Int64ToBytes(p.WorkingSet64); - MemUtil.Write(ms, pb); - - // Not supported in Mono 1.2.6: - // pb = MemUtil.UInt32ToBytes((uint)p.SessionId); - // MemUtil.Write(ms, pb); + // Not supported in Mono 1.2.6: + // pb = MemUtil.UInt32ToBytes((uint)p.SessionId); + // MemUtil.Write(ms, pb); + } } catch(Exception) { Debug.Assert(NativeLib.IsUnix()); } - finally - { - try { if(p != null) p.Dispose(); } - catch(Exception) { Debug.Assert(false); } - } #endif #endif diff --git a/ModernKeePassLib/Cryptography/CryptoUtil.cs b/ModernKeePassLib/Cryptography/CryptoUtil.cs index 8a0b4d3..2d6b4a4 100644 --- a/ModernKeePassLib/Cryptography/CryptoUtil.cs +++ b/ModernKeePassLib/Cryptography/CryptoUtil.cs @@ -35,6 +35,42 @@ namespace ModernKeePassLib.Cryptography { public static class CryptoUtil { + private static bool? g_obProtData = null; + public static bool IsProtectedDataSupported + { + get + { + if(g_obProtData.HasValue) return g_obProtData.Value; + + bool b = false; + try + { + Random r = CryptoRandom.NewWeakRandom(); + + byte[] pbData = new byte[137]; + r.NextBytes(pbData); + + byte[] pbEnt = new byte[41]; + r.NextBytes(pbEnt); + + byte[] pbEnc = ProtectedData.Protect(pbData, pbEnt, + DataProtectionScope.CurrentUser); + if((pbEnc != null) && !MemUtil.ArraysEqual(pbEnc, pbData)) + { + byte[] pbDec = ProtectedData.Unprotect(pbEnc, pbEnt, + DataProtectionScope.CurrentUser); + if((pbDec != null) && MemUtil.ArraysEqual(pbDec, pbData)) + b = true; + } + } + catch(Exception) { Debug.Assert(false); } + + Debug.Assert(b); // Should be supported on all systems + g_obProtData = b; + return b; + } + } + public static byte[] HashSha256(byte[] pbData) { if(pbData == null) throw new ArgumentNullException("pbData"); @@ -165,5 +201,37 @@ namespace ModernKeePassLib.Cryptography return null; } #endif + public static byte[] ProtectData(byte[] pb, byte[] pbOptEntropy, + DataProtectionScope s) + { + return ProtectDataPriv(pb, true, pbOptEntropy, s); + } + + public static byte[] UnprotectData(byte[] pb, byte[] pbOptEntropy, + DataProtectionScope s) + { + return ProtectDataPriv(pb, false, pbOptEntropy, s); + } + + private static byte[] ProtectDataPriv(byte[] pb, bool bProtect, + byte[] pbOptEntropy, DataProtectionScope s) + { + if(pb == null) throw new ArgumentNullException("pb"); + + if((pbOptEntropy != null) && (pbOptEntropy.Length == 0)) + pbOptEntropy = null; + + if(CryptoUtil.IsProtectedDataSupported) + { + if(bProtect) + return ProtectedData.Protect(pb, pbOptEntropy, s); + return ProtectedData.Unprotect(pb, pbOptEntropy, s); + } + + Debug.Assert(false); + byte[] pbCopy = new byte[pb.Length]; + Array.Copy(pb, pbCopy, pb.Length); + return pbCopy; + } } } diff --git a/ModernKeePassLib/Cryptography/KeyDerivation/AesKdf.GCrypt.cs b/ModernKeePassLib/Cryptography/KeyDerivation/AesKdf.GCrypt.cs index 510ef93..51b100d 100644 --- a/ModernKeePassLib/Cryptography/KeyDerivation/AesKdf.GCrypt.cs +++ b/ModernKeePassLib/Cryptography/KeyDerivation/AesKdf.GCrypt.cs @@ -71,9 +71,6 @@ namespace ModernKeePassLib.Cryptography.KeyDerivation private static bool GCryptInitLib() { - Debug.Assert(Marshal.SizeOf(typeof(int)) == 4); // Also on 64-bit systems - Debug.Assert(Marshal.SizeOf(typeof(uint)) == 4); - if(!NativeLib.IsUnix()) return false; // Independent of workaround state if(!MonoWorkarounds.IsRequired(1468)) return false; // Can be turned off diff --git a/ModernKeePassLib/Cryptography/PasswordGenerator/PwProfile.cs b/ModernKeePassLib/Cryptography/PasswordGenerator/PwProfile.cs index 174c4f2..aa361e3 100644 --- a/ModernKeePassLib/Cryptography/PasswordGenerator/PwProfile.cs +++ b/ModernKeePassLib/Cryptography/PasswordGenerator/PwProfile.cs @@ -239,8 +239,7 @@ namespace ModernKeePassLib.Cryptography.PasswordGenerator PwProfile pp = new PwProfile(); Debug.Assert(psPassword != null); if(psPassword == null) return pp; - byte[] pbUtf8 = psPassword.ReadUtf8(); - char[] vChars = StrUtil.Utf8.GetChars(pbUtf8); + char[] vChars = psPassword.ReadChars(); pp.GeneratorType = PasswordGeneratorType.CharSet; pp.Length = (uint)vChars.Length; @@ -266,7 +265,6 @@ namespace ModernKeePassLib.Cryptography.PasswordGenerator } MemUtil.ZeroArray(vChars); - MemUtil.ZeroByteArray(pbUtf8); return pp; } diff --git a/ModernKeePassLib/Cryptography/PopularPasswords.cs b/ModernKeePassLib/Cryptography/PopularPasswords.cs index 1bb5ec4..905ae7c 100644 --- a/ModernKeePassLib/Cryptography/PopularPasswords.cs +++ b/ModernKeePassLib/Cryptography/PopularPasswords.cs @@ -75,7 +75,7 @@ namespace ModernKeePassLib.Cryptography #endif #endif - try { return IsPopularPasswordPriv(vPassword, out uDictSize); } + try { return IsPopularPasswordPriv(vPassword, out uDictSize); } catch(Exception) { Debug.Assert(false); } uDictSize = 0; diff --git a/ModernKeePassLib/Cryptography/QualityEstimation.cs b/ModernKeePassLib/Cryptography/QualityEstimation.cs index 4c1c7ee..a477a17 100644 --- a/ModernKeePassLib/Cryptography/QualityEstimation.cs +++ b/ModernKeePassLib/Cryptography/QualityEstimation.cs @@ -420,11 +420,12 @@ namespace ModernKeePassLib.Cryptography { if(pbUnprotectedUtf8 == null) { Debug.Assert(false); return 0; } - char[] vChars = StrUtil.Utf8.GetChars(pbUnprotectedUtf8); - uint uResult = EstimatePasswordBits(vChars); - MemUtil.ZeroArray(vChars); + char[] v = StrUtil.Utf8.GetChars(pbUnprotectedUtf8); + uint r; + try { r = EstimatePasswordBits(v); } + finally { MemUtil.ZeroArray(v); } - return uResult; + return r; } private static QeCharType GetCharType(char ch) diff --git a/ModernKeePassLib/Cryptography/SelfTest.cs b/ModernKeePassLib/Cryptography/SelfTest.cs index e5dd277..d8ecc26 100644 --- a/ModernKeePassLib/Cryptography/SelfTest.cs +++ b/ModernKeePassLib/Cryptography/SelfTest.cs @@ -22,6 +22,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; +using System.Runtime.InteropServices; using System.Security; using System.Text; @@ -55,6 +56,25 @@ namespace ModernKeePassLib.Cryptography /// public static void Perform() { +#if KeePassUAP + Debug.Assert(Marshal.SizeOf() == 4); + Debug.Assert(Marshal.SizeOf() == 4); + Debug.Assert(Marshal.SizeOf() == 8); + Debug.Assert(Marshal.SizeOf() == 8); + Debug.Assert(Marshal.SizeOf() == IntPtr.Size); +#else + Debug.Assert(Marshal.SizeOf(typeof(int)) == 4); + Debug.Assert(Marshal.SizeOf(typeof(uint)) == 4); + Debug.Assert(Marshal.SizeOf(typeof(long)) == 8); + Debug.Assert(Marshal.SizeOf(typeof(ulong)) == 8); + Debug.Assert(Marshal.SizeOf(typeof(IntPtr)) == IntPtr.Size); +#endif + Debug.Assert((IntPtr.Size == 4) || (IntPtr.Size == 8)); + + Debug.Assert((int)PwIcon.World == 1); + Debug.Assert((int)PwIcon.Warning == 2); + Debug.Assert((int)PwIcon.BlackBerry == 68); + Random r = CryptoRandom.NewWeakRandom(); TestFipsComplianceProblems(); // Must be the first test @@ -76,10 +96,6 @@ namespace ModernKeePassLib.Cryptography TestStrUtil(); TestUrlUtil(); - Debug.Assert((int)PwIcon.World == 1); - Debug.Assert((int)PwIcon.Warning == 2); - Debug.Assert((int)PwIcon.BlackBerry == 68); - #if KeePassUAP SelfTestEx.Perform(); #endif @@ -944,6 +960,14 @@ namespace ModernKeePassLib.Cryptography if(ps.ReadString() != str) throw new SecurityException("ProtectedString-14"); } + + ps = new ProtectedString(false, "ABCD"); + ps2 = new ProtectedString(true, "EFG"); + ps += (ps2 + "HI"); + if(!ps.Equals(new ProtectedString(true, "ABCDEFGHI"), true)) + throw new SecurityException("ProtectedString-15"); + if(!ps.Equals(new ProtectedString(false, "ABCDEFGHI"), false)) + throw new SecurityException("ProtectedString-16"); #endif } @@ -1041,6 +1065,30 @@ namespace ModernKeePassLib.Cryptography throw new InvalidOperationException("StrUtil-Case1"); if(string.Equals(@"ab", StrUtil.CaseIgnoreCmp)) throw new InvalidOperationException("StrUtil-Case2"); + + const string strNL = "\n01\r23\n45\r\n67\r"; + string strW = StrUtil.NormalizeNewLines(strNL, true); + string strL = StrUtil.NormalizeNewLines(strNL, false); + if(strW != "\r\n01\r\n23\r\n45\r\n67\r\n") + throw new InvalidOperationException("StrUtil-NewLine1"); + if(strL != "\n01\n23\n45\n67\n") + throw new InvalidOperationException("StrUtil-NewLine2"); + if(StrUtil.IsNewLineNormalized(strNL.ToCharArray(), true)) + throw new InvalidOperationException("StrUtil-NewLine3"); + if(StrUtil.IsNewLineNormalized(strNL.ToCharArray(), false)) + throw new InvalidOperationException("StrUtil-NewLine4"); + if(!StrUtil.IsNewLineNormalized(strW.ToCharArray(), true)) + throw new InvalidOperationException("StrUtil-NewLine5"); + if(StrUtil.IsNewLineNormalized(strW.ToCharArray(), false)) + throw new InvalidOperationException("StrUtil-NewLine6"); + if(StrUtil.IsNewLineNormalized(strL.ToCharArray(), true)) + throw new InvalidOperationException("StrUtil-NewLine7"); + if(!StrUtil.IsNewLineNormalized(strL.ToCharArray(), false)) + throw new InvalidOperationException("StrUtil-NewLine8"); + if(!StrUtil.IsNewLineNormalized(string.Empty.ToCharArray(), true)) + throw new InvalidOperationException("StrUtil-NewLine9"); + if(!StrUtil.IsNewLineNormalized(string.Empty.ToCharArray(), false)) + throw new InvalidOperationException("StrUtil-NewLine10"); #endif } diff --git a/ModernKeePassLib/Keys/KcpKeyFile.cs b/ModernKeePassLib/Keys/KcpKeyFile.cs index 6679d68..fde69b7 100644 --- a/ModernKeePassLib/Keys/KcpKeyFile.cs +++ b/ModernKeePassLib/Keys/KcpKeyFile.cs @@ -275,7 +275,7 @@ namespace ModernKeePassLib.Keys } } #else - XmlDocument doc = new XmlDocument(); + XmlDocument doc = XmlUtilEx.CreateXmlDocument(); doc.Load(ms); XmlElement el = doc.DocumentElement; @@ -320,49 +320,31 @@ namespace ModernKeePassLib.Keys #else IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFile); #endif - Stream sOut = IOConnection.OpenWrite(ioc); + using(Stream s = IOConnection.OpenWrite(ioc)) + { + using(XmlWriter xw = XmlUtilEx.CreateXmlWriter(s)) + { + xw.WriteStartDocument(); + xw.WriteStartElement(RootElementName); // -#if ModernKeePassLib || KeePassUAP - XmlWriterSettings xws = new XmlWriterSettings(); - xws.Encoding = StrUtil.Utf8; - xws.Indent = false; + xw.WriteStartElement(MetaElementName); // + xw.WriteStartElement(VersionElementName); // + xw.WriteString("1.00"); + xw.WriteEndElement(); // + xw.WriteEndElement(); // - XmlWriter xtw = XmlWriter.Create(sOut, xws); -#else - XmlTextWriter xtw = new XmlTextWriter(sOut, StrUtil.Utf8); -#endif + xw.WriteStartElement(KeyElementName); // - xtw.WriteStartDocument(); - xtw.WriteWhitespace("\r\n"); - xtw.WriteStartElement(RootElementName); // KeyFile - xtw.WriteWhitespace("\r\n\t"); + xw.WriteStartElement(KeyDataElementName); // + xw.WriteString(Convert.ToBase64String(pbKeyData)); + xw.WriteEndElement(); // - xtw.WriteStartElement(MetaElementName); // Meta - xtw.WriteWhitespace("\r\n\t\t"); - xtw.WriteStartElement(VersionElementName); // Version - xtw.WriteString("1.00"); - xtw.WriteEndElement(); // End Version - xtw.WriteWhitespace("\r\n\t"); - xtw.WriteEndElement(); // End Meta - xtw.WriteWhitespace("\r\n\t"); + xw.WriteEndElement(); // - xtw.WriteStartElement(KeyElementName); // Key - xtw.WriteWhitespace("\r\n\t\t"); - - xtw.WriteStartElement(KeyDataElementName); // Data - xtw.WriteString(Convert.ToBase64String(pbKeyData)); - xtw.WriteEndElement(); // End Data - xtw.WriteWhitespace("\r\n\t"); - - xtw.WriteEndElement(); // End Key - xtw.WriteWhitespace("\r\n"); - - xtw.WriteEndElement(); // RootElementName - xtw.WriteWhitespace("\r\n"); - xtw.WriteEndDocument(); // End KeyFile - xtw.Dispose(); - - sOut.Dispose(); + xw.WriteEndElement(); // + xw.WriteEndDocument(); + } + } } } } diff --git a/ModernKeePassLib/Keys/KcpUserAccount.cs b/ModernKeePassLib/Keys/KcpUserAccount.cs index a16d1ee..6e71bb0 100644 --- a/ModernKeePassLib/Keys/KcpUserAccount.cs +++ b/ModernKeePassLib/Keys/KcpUserAccount.cs @@ -65,11 +65,8 @@ namespace ModernKeePassLib.Keys /// public KcpUserAccount() { - // 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); + if(!CryptoUtil.IsProtectedDataSupported) + throw new PlatformNotSupportedException(); // Windows 98/ME byte[] pbKey = LoadUserKey(false); if(pbKey == null) pbKey = CreateUserKey(); @@ -88,17 +85,17 @@ namespace ModernKeePassLib.Keys // m_pbKeyData = null; // } - private static string GetUserKeyFilePath(bool bCreate) - { + private static string GetUserKeyFilePath(bool bCreate) + { #if ModernKeePassLib - string strUserDir = Windows.Storage.ApplicationData.Current.RoamingFolder.Path; + string strUserDir = Windows.Storage.ApplicationData.Current.RoamingFolder.Path; #else string strUserDir = Environment.GetFolderPath( Environment.SpecialFolder.ApplicationData); #endif - strUserDir = UrlUtil.EnsureTerminatingSeparator(strUserDir, false); - strUserDir += PwDefs.ShortProductName; + strUserDir = UrlUtil.EnsureTerminatingSeparator(strUserDir, false); + strUserDir += PwDefs.ShortProductName; #if !ModernKeePassLib @@ -115,9 +112,9 @@ namespace ModernKeePassLib.Keys byte[] pbKey = null; #if !KeePassLibSD - try - { - string strFilePath = GetUserKeyFilePath(false); + try + { + string strFilePath = GetUserKeyFilePath(false); #if ModernKeePassLib var fileStream = StorageFile.GetFileFromPathAsync(strFilePath).GetAwaiter().GetResult().OpenStreamForReadAsync().GetAwaiter().GetResult(); @@ -128,7 +125,7 @@ namespace ModernKeePassLib.Keys byte[] pbProtectedKey = File.ReadAllBytes(strFilePath); #endif - pbKey = ProtectedData.Unprotect(pbProtectedKey, m_pbEntropy, + pbKey = CryptoUtil.UnprotectData(pbProtectedKey, m_pbEntropy, DataProtectionScope.CurrentUser); } catch(Exception) @@ -138,7 +135,7 @@ namespace ModernKeePassLib.Keys } #endif - return pbKey; + return pbKey; } private static byte[] CreateUserKey() @@ -148,9 +145,9 @@ namespace ModernKeePassLib.Keys #else string strFilePath = GetUserKeyFilePath(true); - byte[] pbRandomKey = CryptoRandom.Instance.GetRandomBytes(64); - byte[] pbProtectedKey = ProtectedData.Protect(pbRandomKey, - m_pbEntropy, DataProtectionScope.CurrentUser); + byte[] pbRandomKey = CryptoRandom.Instance.GetRandomBytes(64); + byte[] pbProtectedKey = CryptoUtil.ProtectData(pbRandomKey, + m_pbEntropy, DataProtectionScope.CurrentUser); #if ModernKeePassLib var fileStream = StorageFile.GetFileFromPathAsync(strFilePath).GetAwaiter().GetResult().OpenStreamForWriteAsync().GetAwaiter().GetResult(); fileStream.Write(pbProtectedKey, 0, (int)fileStream.Length); diff --git a/ModernKeePassLib/ModernKeePass.Lib.csproj b/ModernKeePassLib/ModernKeePass.Lib.csproj index 8cd02bb..0865971 100644 --- a/ModernKeePassLib/ModernKeePass.Lib.csproj +++ b/ModernKeePassLib/ModernKeePass.Lib.csproj @@ -130,6 +130,8 @@ + + @@ -149,7 +151,7 @@ - + diff --git a/ModernKeePassLib/ModernKeePassLib.nuspec b/ModernKeePassLib/ModernKeePassLib.nuspec index 37fc782..e6e594b 100644 --- a/ModernKeePassLib/ModernKeePassLib.nuspec +++ b/ModernKeePassLib/ModernKeePassLib.nuspec @@ -2,7 +2,7 @@ ModernKeePassLib - 2.38.2 + 2.39.1 ModernKeePassLib Geoffroy Bonneville Geoffroy Bonneville @@ -10,7 +10,7 @@ https://github.com/wismna/ModernKeePass false Portable KeePass Password Management Library that targets .Net Standard and WinRT. Allows reading, editing and writing to KeePass 2.x databases. - Deactivation of buggy custom icon implementation + Implementation of KeePass library version 2.39.1 Copyright © 2018 Geoffroy Bonneville KeePass KeePassLib Portable PCL NetStandard diff --git a/ModernKeePassLib/Native/ClipboardU.cs b/ModernKeePassLib/Native/ClipboardU.cs new file mode 100644 index 0000000..87760b4 --- /dev/null +++ b/ModernKeePassLib/Native/ClipboardU.cs @@ -0,0 +1,190 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2018 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using System.Text; + +namespace ModernKeePassLib.Native +{ + internal static class ClipboardU + { + private const string XSel = "xsel"; + private const string XSelV = "--version"; + private const string XSelR = "--output --clipboard"; + private const string XSelC = "--clear --clipboard"; + private const string XSelW = "--input --clipboard"; + private const string XSelND = " --nodetach"; + private const AppRunFlags XSelWF = AppRunFlags.WaitForExit; + + private static bool? g_obXSel = null; + + public static string GetText() + { + // System.Windows.Forms.Clipboard doesn't work properly, + // see Mono workaround 1530 + + // string str = GtkGetText(); + // if(str != null) return str; + + return XSelGetText(); + } + + public static bool SetText(string strText, bool bMayBlock) + { + string str = (strText ?? string.Empty); + + // System.Windows.Forms.Clipboard doesn't work properly, + // see Mono workaround 1530 + + // if(GtkSetText(str)) return true; + + return XSelSetText(str, bMayBlock); + } + + // ============================================================= + // LibGTK + + // Even though GTK+ 3 appears to be loaded already, performing a + // P/Invoke of LibGTK's gtk_init_check function terminates the + // process (!) with the following error message: + // "Gtk-ERROR **: GTK+ 2.x symbols detected. Using GTK+ 2.x and + // GTK+ 3 in the same process is not supported". + + /* private static bool GtkInit() + { + try + { + // GTK requires GLib; + // the following throws if and only if GLib is unavailable + NativeMethods.g_free(IntPtr.Zero); + + if(NativeMethods.gtk_init_check(IntPtr.Zero, IntPtr.Zero) != + NativeMethods.G_FALSE) + return true; + + Debug.Assert(false); + } + catch(Exception) { Debug.Assert(false); } + + return false; + } + + private static string GtkGetText() + { + IntPtr lpText = IntPtr.Zero; + try + { + if(GtkInit()) + { + IntPtr h = NativeMethods.gtk_clipboard_get( + NativeMethods.GDK_SELECTION_CLIPBOARD); + if(h != IntPtr.Zero) + { + lpText = NativeMethods.gtk_clipboard_wait_for_text(h); + if(lpText != IntPtr.Zero) + return NativeMethods.Utf8ZToString(lpText); + } + } + } + catch(Exception) { Debug.Assert(false); } + finally + { + try { NativeMethods.g_free(lpText); } + catch(Exception) { Debug.Assert(false); } + } + + return null; + } + + private static bool GtkSetText(string str) + { + IntPtr lpText = IntPtr.Zero; + try + { + if(GtkInit()) + { + lpText = NativeMethods.Utf8ZFromString(str ?? string.Empty); + if(lpText == IntPtr.Zero) { Debug.Assert(false); return false; } + + bool b = false; + for(int i = 0; i < 2; ++i) + { + IntPtr h = NativeMethods.gtk_clipboard_get((i == 0) ? + NativeMethods.GDK_SELECTION_PRIMARY : + NativeMethods.GDK_SELECTION_CLIPBOARD); + if(h != IntPtr.Zero) + { + NativeMethods.gtk_clipboard_clear(h); + NativeMethods.gtk_clipboard_set_text(h, lpText, -1); + NativeMethods.gtk_clipboard_store(h); + b = true; + } + } + + return b; + } + } + catch(Exception) { Debug.Assert(false); } + finally { NativeMethods.Utf8ZFree(lpText); } + + return false; + } */ + + // ============================================================= + // XSel + + private static bool XSelInit() + { + if(g_obXSel.HasValue) return g_obXSel.Value; + + string strTest = NativeLib.RunConsoleApp(XSel, XSelV); + + bool b = (strTest != null); + g_obXSel = b; + return b; + } + + private static string XSelGetText() + { + if(!XSelInit()) return null; + + return NativeLib.RunConsoleApp(XSel, XSelR); + } + + private static bool XSelSetText(string str, bool bMayBlock) + { + if(!XSelInit()) return false; + + string strOpt = (bMayBlock ? XSelND : string.Empty); + + // xsel with an empty input can hang, thus use --clear + if(str.Length == 0) + return (NativeLib.RunConsoleApp(XSel, XSelC + strOpt, + null, XSelWF) != null); + + // Use --nodetach to prevent clipboard corruption; + // https://sourceforge.net/p/keepass/bugs/1603/ + return (NativeLib.RunConsoleApp(XSel, XSelW + strOpt, + str, XSelWF) != null); + } + } +} diff --git a/ModernKeePassLib/Native/NativeLib.cs b/ModernKeePassLib/Native/NativeLib.cs index 0706486..bfaee1e 100644 --- a/ModernKeePassLib/Native/NativeLib.cs +++ b/ModernKeePassLib/Native/NativeLib.cs @@ -53,24 +53,6 @@ namespace ModernKeePassLib.Native set { m_bAllowNative = value; } } - private static int? g_oiPointerSize = null; - /// - /// Size of a native pointer (in bytes). - /// - public static int PointerSize - { - get - { - if(!g_oiPointerSize.HasValue) -#if KeePassUAP - g_oiPointerSize = Marshal.SizeOf(); -#else - g_oiPointerSize = Marshal.SizeOf(typeof(IntPtr)); -#endif - return g_oiPointerSize.Value; - } - } - private static ulong? m_ouMonoVersion = null; public static ulong MonoVersion { @@ -195,19 +177,21 @@ namespace ModernKeePassLib.Native t = DesktopType.Xfce; else if(strXdg.Equals("MATE", sc)) t = DesktopType.Mate; - else if(strXdg.Equals("X-Cinnamon", sc)) + else if(strXdg.Equals("X-Cinnamon", sc)) // Mint 18.3 t = DesktopType.Cinnamon; else if(strXdg.Equals("Pantheon", sc)) // Elementary OS t = DesktopType.Pantheon; - else if(strXdg.Equals("KDE", sc) || // Mint 16 + else if(strXdg.Equals("KDE", sc) || // Mint 16, Kubuntu 17.10 strGdm.Equals("kde-plasma", sc)) // Ubuntu 12.04 t = DesktopType.Kde; else if(strXdg.Equals("GNOME", sc)) { if(strGdm.Equals("cinnamon", sc)) // Mint 13 t = DesktopType.Cinnamon; - else t = DesktopType.Gnome; + else t = DesktopType.Gnome; // Fedora 27 } + else if(strXdg.Equals("ubuntu:GNOME", sc)) + t = DesktopType.Gnome; } catch(Exception) { Debug.Assert(false); } } @@ -243,6 +227,7 @@ namespace ModernKeePassLib.Native RunProcessDelegate fnRun = delegate() { + Process pToDispose = null; try { ProcessStartInfo psi = new ProcessStartInfo(); @@ -258,6 +243,7 @@ namespace ModernKeePassLib.Native if(!string.IsNullOrEmpty(strParams)) psi.Arguments = strParams; Process p = Process.Start(psi); + pToDispose = p; if(strStdInput != null) { @@ -274,9 +260,11 @@ namespace ModernKeePassLib.Native p.WaitForExit(); else if((f & AppRunFlags.GCKeepAlive) != AppRunFlags.None) { + pToDispose = null; // Thread disposes it + Thread th = new Thread(delegate() { - try { p.WaitForExit(); } + try { p.WaitForExit(); p.Dispose(); } catch(Exception) { Debug.Assert(false); } }); th.Start(); @@ -289,6 +277,11 @@ namespace ModernKeePassLib.Native #else catch(Exception) { } #endif + finally + { + try { if(pToDispose != null) pToDispose.Dispose(); } + catch(Exception) { Debug.Assert(false); } + } return null; }; diff --git a/ModernKeePassLib/Native/NativeMethods.cs b/ModernKeePassLib/Native/NativeMethods.cs index 900d418..c6fbdeb 100644 --- a/ModernKeePassLib/Native/NativeMethods.cs +++ b/ModernKeePassLib/Native/NativeMethods.cs @@ -31,6 +31,14 @@ namespace ModernKeePassLib.Native { internal const int MAX_PATH = 260; + internal const long INVALID_HANDLE_VALUE = -1; + + internal const uint MOVEFILE_REPLACE_EXISTING = 0x00000001; + internal const uint MOVEFILE_COPY_ALLOWED = 0x00000002; + + internal const uint FILE_SUPPORTS_TRANSACTIONS = 0x00200000; + internal const int MAX_TRANSACTION_DESCRIPTION_LENGTH = 64; + // internal const uint TF_SFT_SHOWNORMAL = 0x00000001; // internal const uint TF_SFT_HIDDEN = 0x00000008; @@ -47,10 +55,9 @@ namespace ModernKeePassLib.Native internal static bool TransformKey(IntPtr pBuf256, IntPtr pKey256, UInt64 uRounds) { - if(Marshal.SizeOf(typeof(IntPtr)) == 8) - return TransformKey64(pBuf256, pKey256, uRounds); - else + if(IntPtr.Size == 4) return TransformKey32(pBuf256, pKey256, uRounds); + return TransformKey64(pBuf256, pKey256, uRounds); } [DllImport("KeePassNtv32.dll", EntryPoint = "TransformKeyTimed")] @@ -66,10 +73,9 @@ namespace ModernKeePassLib.Native internal static bool TransformKeyTimed(IntPtr pBuf256, IntPtr pKey256, ref UInt64 puRounds, UInt32 uSeconds) { - if(Marshal.SizeOf(typeof(IntPtr)) == 8) - return TransformKeyTimed64(pBuf256, pKey256, ref puRounds, uSeconds); - else + if(IntPtr.Size == 4) return TransformKeyTimed32(pBuf256, pKey256, ref puRounds, uSeconds); + return TransformKeyTimed64(pBuf256, pKey256, ref puRounds, uSeconds); } */ #if !KeePassUAP @@ -86,10 +92,9 @@ namespace ModernKeePassLib.Native internal static bool TransformKey(IntPtr pBuf256, IntPtr pKey256, UInt64 uRounds) { - if(NativeLib.PointerSize == 8) - return TransformKey64(pBuf256, pKey256, uRounds); - else + if(IntPtr.Size == 4) return TransformKey32(pBuf256, pKey256, uRounds); + return TransformKey64(pBuf256, pKey256, uRounds); } [DllImport("KeePassLibC32.dll", EntryPoint = "TransformKeyBenchmark256")] @@ -100,9 +105,9 @@ namespace ModernKeePassLib.Native internal static UInt64 TransformKeyBenchmark(UInt32 uTimeMs) { - if(NativeLib.PointerSize == 8) - return TransformKeyBenchmark64(uTimeMs); - return TransformKeyBenchmark32(uTimeMs); + if(IntPtr.Size == 4) + return TransformKeyBenchmark32(uTimeMs); + return TransformKeyBenchmark64(uTimeMs); } #endif @@ -116,11 +121,64 @@ namespace ModernKeePassLib.Native internal static bool TfShowLangBar(uint dwFlags) { - if(Marshal.SizeOf(typeof(IntPtr)) == 8) - return TF_ShowLangBar64(dwFlags); - return TF_ShowLangBar32(dwFlags); + if(IntPtr.Size == 4) return TF_ShowLangBar32(dwFlags); + return TF_ShowLangBar64(dwFlags); } */ + [DllImport("KeePassLibC32.dll", EntryPoint = "ProtectProcessWithDacl")] + private static extern void ProtectProcessWithDacl32(); + + [DllImport("KeePassLibC64.dll", EntryPoint = "ProtectProcessWithDacl")] + private static extern void ProtectProcessWithDacl64(); + + internal static void ProtectProcessWithDacl() + { + try + { + if(NativeLib.IsUnix()) return; + + if(IntPtr.Size == 4) ProtectProcessWithDacl32(); + else ProtectProcessWithDacl64(); + } + catch(Exception) { Debug.Assert(false); } + } + + [DllImport("Kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool CloseHandle(IntPtr hObject); + + [DllImport("Kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = false, + SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool GetVolumeInformation(string lpRootPathName, + StringBuilder lpVolumeNameBuffer, UInt32 nVolumeNameSize, + ref UInt32 lpVolumeSerialNumber, ref UInt32 lpMaximumComponentLength, + ref UInt32 lpFileSystemFlags, StringBuilder lpFileSystemNameBuffer, + UInt32 nFileSystemNameSize); + + [DllImport("Kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = false, + SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool MoveFileEx(string lpExistingFileName, + string lpNewFileName, UInt32 dwFlags); + + [DllImport("KtmW32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, + SetLastError = true)] + internal static extern IntPtr CreateTransaction(IntPtr lpTransactionAttributes, + IntPtr lpUOW, UInt32 dwCreateOptions, UInt32 dwIsolationLevel, + UInt32 dwIsolationFlags, UInt32 dwTimeout, string lpDescription); + + [DllImport("KtmW32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool CommitTransaction(IntPtr hTransaction); + + [DllImport("Kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = false, + SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool MoveFileTransacted(string lpExistingFileName, + string lpNewFileName, IntPtr lpProgressRoutine, IntPtr lpData, + UInt32 dwFlags, IntPtr hTransaction); + #if (!KeePassLibSD && !KeePassUAP) [DllImport("ShlWApi.dll", CharSet = CharSet.Auto)] [return: MarshalAs(UnmanagedType.Bool)] diff --git a/ModernKeePassLib/Properties/AssemblyInfo.cs b/ModernKeePassLib/Properties/AssemblyInfo.cs index adcfd09..121fd60 100644 --- a/ModernKeePassLib/Properties/AssemblyInfo.cs +++ b/ModernKeePassLib/Properties/AssemblyInfo.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2017 Dominik Reichl + Copyright (C) 2003-2018 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("wismna")] [assembly: AssemblyProduct("ModernKeePassLib")] -[assembly: AssemblyCopyright("Copyright © 2017 Geoffroy Bonneville")] +[assembly: AssemblyCopyright("Copyright © 2018 Geoffroy Bonneville")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -40,5 +40,5 @@ using System.Runtime.InteropServices; #endif // Assembly version information -[assembly: AssemblyVersion("2.37.0.2000")] -[assembly: AssemblyFileVersion("2.37.0.2000")] +[assembly: AssemblyVersion("2.39.1.*")] +[assembly: AssemblyFileVersion("2.39.1.0")] diff --git a/ModernKeePassLib/PwDatabase.cs b/ModernKeePassLib/PwDatabase.cs index 7a3942d..f903985 100644 --- a/ModernKeePassLib/PwDatabase.cs +++ b/ModernKeePassLib/PwDatabase.cs @@ -607,9 +607,8 @@ namespace ModernKeePassLib m_bDatabaseOpened = true; m_bModified = true; - m_pgRootGroup = new PwGroup(true, true, - UrlUtil.StripExtension(UrlUtil.GetFileName(ioConnection.Path)), - PwIcon.FolderOpen); + m_pgRootGroup = new PwGroup(true, true, UrlUtil.StripExtension( + UrlUtil.GetFileName(ioConnection.Path)), PwIcon.FolderOpen); m_pgRootGroup.IsExpanded = true; } @@ -636,15 +635,15 @@ namespace ModernKeePassLib m_pgRootGroup.IsExpanded = true; m_pwUserKey = pwKey; - m_bModified = false; KdbxFile kdbx = new KdbxFile(this); kdbx.DetachBinaries = m_strDetachBins; - Stream s = IOConnection.OpenRead(ioSource); - kdbx.Load(s, KdbxFormat.Default, slLogger); - s.Dispose(); + using(Stream s = IOConnection.OpenRead(ioSource)) + { + kdbx.Load(s, KdbxFormat.Default, slLogger); + } m_pbHashOfLastIO = kdbx.HashOfFileOnDisk; m_pbHashOfFileOnDisk = kdbx.HashOfFileOnDisk; @@ -673,17 +672,21 @@ namespace ModernKeePassLib if(m_bUseFileLocks) fl = new FileLock(m_ioSource); try { - FileTransactionEx ft = new FileTransactionEx(m_ioSource, - m_bUseFileTransactions); - Stream s = ft.OpenWrite(); + KdbxFile kdbx = new KdbxFile(this); - KdbxFile kdb = new KdbxFile(this); - kdb.Save(s, null, KdbxFormat.Default, slLogger); + using(FileTransactionEx ft = new FileTransactionEx(m_ioSource, + m_bUseFileTransactions)) + { + using(Stream s = ft.OpenWrite()) + { + kdbx.Save(s, null, KdbxFormat.Default, slLogger); + } - ft.CommitWrite(); + ft.CommitWrite(); + } - m_pbHashOfLastIO = kdb.HashOfFileOnDisk; - m_pbHashOfFileOnDisk = kdb.HashOfFileOnDisk; + m_pbHashOfLastIO = kdbx.HashOfFileOnDisk; + m_pbHashOfFileOnDisk = kdbx.HashOfFileOnDisk; Debug.Assert(m_pbHashOfFileOnDisk != null); } finally { if(fl != null) fl.Dispose(); } @@ -1856,10 +1859,7 @@ namespace ModernKeePassLib using(Stream sOut = IOConnection.OpenWrite(iocBk)) { MemUtil.CopyStream(sIn, sOut); - sOut.Close(); } - - sIn.Close(); } } @@ -1969,7 +1969,7 @@ namespace ModernKeePassLib if(psB == null) return false; // Ignore protection setting, compare values only - if(!kvpA.Value.ReadString().Equals(psB.ReadString())) return false; + if(!psB.Equals(kvpA.Value, false)) return false; } foreach(KeyValuePair kvpB in b.Strings) @@ -1980,22 +1980,18 @@ namespace ModernKeePassLib if(psA == null) return false; // Must be equal by logic - Debug.Assert(kvpB.Value.ReadString().Equals(psA.ReadString())); + Debug.Assert(psA.Equals(kvpB.Value, false)); } if(a.Binaries.UCount != b.Binaries.UCount) return false; foreach(KeyValuePair kvpBin in a.Binaries) { + ProtectedBinary pbA = kvpBin.Value; ProtectedBinary pbB = b.Binaries.Get(kvpBin.Key); if(pbB == null) return false; // Ignore protection setting, compare values only - byte[] pbDataA = kvpBin.Value.ReadData(); - byte[] pbDataB = pbB.ReadData(); - bool bBinEq = MemUtil.ArraysEqual(pbDataA, pbDataB); - MemUtil.ZeroByteArray(pbDataA); - MemUtil.ZeroByteArray(pbDataB); - if(!bBinEq) return false; + if(!pbB.Equals(pbA, false)) return false; } return true; diff --git a/ModernKeePassLib/PwDefs.cs b/ModernKeePassLib/PwDefs.cs index dd8a614..f3aade6 100644 --- a/ModernKeePassLib/PwDefs.cs +++ b/ModernKeePassLib/PwDefs.cs @@ -55,18 +55,18 @@ namespace ModernKeePassLib /// e.g. 2.19 = 0x02130000. /// It is highly recommended to use FileVersion64 instead. /// - public const uint Version32 = 0x02260000; + public const uint Version32 = 0x02270100; /// /// Version, encoded as 64-bit unsigned integer /// (component-wise, 16 bits per component). /// - public const ulong FileVersion64 = 0x0002002600000000UL; + public const ulong FileVersion64 = 0x0002002700010000UL; /// /// Version, encoded as string. /// - public const string VersionString = "2.38"; + public const string VersionString = "2.39.1"; public const string Copyright = @"Copyright © 2003-2018 Dominik Reichl"; @@ -219,6 +219,15 @@ namespace ModernKeePassLib return (pe.Strings.ReadSafe(PwDefs.TitleField) == TanTitle); } + + internal static string GetTranslationDisplayVersion(string strFileVersion) + { + if(strFileVersion == null) { Debug.Assert(false); return string.Empty; } + + if(strFileVersion == "2.39") return "2.39 / 2.39.1"; + + return strFileVersion; + } } // #pragma warning disable 1591 // Missing XML comments warning diff --git a/ModernKeePassLib/PwEntry.cs b/ModernKeePassLib/PwEntry.cs index d17ce94..2f91d92 100644 --- a/ModernKeePassLib/PwEntry.cs +++ b/ModernKeePassLib/PwEntry.cs @@ -782,45 +782,49 @@ namespace ModernKeePassLib } /// - /// Approximate the total size of this entry in bytes (including - /// strings, binaries and history entries). + /// Approximate the total size (in process memory) of this entry + /// in bytes (including strings, binaries and history entries). /// /// Size in bytes. public ulong GetSize() { - ulong uSize = 128; // Approx fixed length data + // This method assumes 64-bit pointers/references and Unicode + // strings (i.e. 2 bytes per character) + ulong cb = 248; // Number of bytes; approx. fixed length data + ulong cc = 0; // Number of characters + + cb += (ulong)m_listStrings.UCount * 40; foreach(KeyValuePair kvpStr in m_listStrings) - { - uSize += (ulong)kvpStr.Key.Length; - uSize += (ulong)kvpStr.Value.Length; - } + cc += (ulong)kvpStr.Key.Length + (ulong)kvpStr.Value.Length; + cb += (ulong)m_listBinaries.UCount * 65; foreach(KeyValuePair kvpBin in m_listBinaries) { - uSize += (ulong)kvpBin.Key.Length; - uSize += kvpBin.Value.Length; + cc += (ulong)kvpBin.Key.Length; + cb += (ulong)kvpBin.Value.Length; } - uSize += (ulong)m_listAutoType.DefaultSequence.Length; + cc += (ulong)m_listAutoType.DefaultSequence.Length; + cb += (ulong)m_listAutoType.AssociationsCount * 24; foreach(AutoTypeAssociation a in m_listAutoType.Associations) - { - uSize += (ulong)a.WindowName.Length; - uSize += (ulong)a.Sequence.Length; - } + cc += (ulong)a.WindowName.Length + (ulong)a.Sequence.Length; + cb += (ulong)m_listHistory.UCount * 8; foreach(PwEntry peHistory in m_listHistory) - uSize += peHistory.GetSize(); + cb += peHistory.GetSize(); - uSize += (ulong)m_strOverrideUrl.Length; + cc += (ulong)m_strOverrideUrl.Length; + cb += (ulong)m_vTags.Count * 8; foreach(string strTag in m_vTags) - uSize += (ulong)strTag.Length; + cc += (ulong)strTag.Length; + cb += (ulong)m_dCustomData.Count * 16; foreach(KeyValuePair kvp in m_dCustomData) - uSize += (ulong)kvp.Key.Length + (ulong)kvp.Value.Length; + cc += (ulong)kvp.Key.Length + (ulong)kvp.Value.Length; - return uSize; + return (cb + (cc << 1)); } public bool HasTag(string strTag) diff --git a/ModernKeePassLib/Security/ProtectedBinary.cs b/ModernKeePassLib/Security/ProtectedBinary.cs index 251bdb0..eff079a 100644 --- a/ModernKeePassLib/Security/ProtectedBinary.cs +++ b/ModernKeePassLib/Security/ProtectedBinary.cs @@ -44,9 +44,8 @@ namespace ModernKeePassLib.Security long lID); /// - /// Represents a protected binary, i.e. a byte array that is encrypted - /// in memory. A ProtectedBinary object is immutable and - /// thread-safe. + /// A protected binary, i.e. a byte array that is encrypted in memory. + /// A ProtectedBinary object is immutable and thread-safe. /// public sealed class ProtectedBinary : IEquatable { @@ -71,7 +70,7 @@ namespace ModernKeePassLib.Security private enum PbMemProt { None = 0, - ProtectedMemory, + ProtectedMemory, // DPAPI on Windows ChaCha20, ExtCrypt } @@ -90,7 +89,8 @@ namespace ModernKeePassLib.Security bool? ob = g_obProtectedMemorySupported; if(ob.HasValue) return ob.Value; - // Mono does not implement any encryption for ProtectedMemory; + // Mono does not implement any encryption for ProtectedMemory + // on Linux (Mono uses DPAPI on Windows); // https://sourceforge.net/p/keepass/feature-requests/1907/ if(NativeLib.IsUnix()) { @@ -177,7 +177,7 @@ namespace ModernKeePassLib.Security /// i.e. the caller is responsible for clearing it. public ProtectedBinary(bool bEnableProtection, byte[] pbData) { - if(pbData == null) throw new ArgumentNullException("pbData"); + if(pbData == null) throw new ArgumentNullException("pbData"); // For .Length Init(bEnableProtection, pbData, 0, pbData.Length); } @@ -213,9 +213,8 @@ namespace ModernKeePassLib.Security if(xbProtected == null) throw new ArgumentNullException("xbProtected"); byte[] pb = xbProtected.ReadPlainText(); - Init(bEnableProtection, pb, 0, pb.Length); - - if(bEnableProtection) MemUtil.ZeroByteArray(pb); + try { Init(bEnableProtection, pb, 0, pb.Length); } + finally { if(bEnableProtection) MemUtil.ZeroByteArray(pb); } } private void Init(bool bEnableProtection, byte[] pbData, int iOffset, @@ -374,7 +373,7 @@ namespace ModernKeePassLib.Security for(int i = 0; i < pb.Length; ++i) h = (h << 3) + h + (int)pb[i]; } - MemUtil.ZeroByteArray(pb); + if(m_bProtected) MemUtil.ZeroByteArray(pb); m_hash = h; return h; @@ -382,25 +381,36 @@ namespace ModernKeePassLib.Security public override bool Equals(object obj) { - return Equals(obj as ProtectedBinary); + return this.Equals(obj as ProtectedBinary, true); } public bool Equals(ProtectedBinary other) { - if(other == null) return false; // No assert + return this.Equals(other, true); + } + + public bool Equals(ProtectedBinary other, bool bCheckProtEqual) + { + if(other == null) return false; // No assert + if(object.ReferenceEquals(this, other)) return true; // Perf. opt. + + if(bCheckProtEqual && (m_bProtected != other.m_bProtected)) + return false; - if(m_bProtected != other.m_bProtected) return false; if(m_uDataLen != other.m_uDataLen) return false; - byte[] pbL = ReadData(); - byte[] pbR = other.ReadData(); - bool bEq = MemUtil.ArraysEqual(pbL, pbR); - MemUtil.ZeroByteArray(pbL); - MemUtil.ZeroByteArray(pbR); - -#if DEBUG - if(bEq) { Debug.Assert(GetHashCode() == other.GetHashCode()); } -#endif + byte[] pbL = ReadData(), pbR = null; + bool bEq; + try + { + pbR = other.ReadData(); + bEq = MemUtil.ArraysEqual(pbL, pbR); + } + finally + { + if(m_bProtected) MemUtil.ZeroByteArray(pbL); + if(other.m_bProtected && (pbR != null)) MemUtil.ZeroByteArray(pbR); + } return bEq; } diff --git a/ModernKeePassLib/Security/ProtectedString.cs b/ModernKeePassLib/Security/ProtectedString.cs index 5d20146..eb62841 100644 --- a/ModernKeePassLib/Security/ProtectedString.cs +++ b/ModernKeePassLib/Security/ProtectedString.cs @@ -33,11 +33,11 @@ using KeePassLibSD; namespace ModernKeePassLib.Security { /// - /// Represents an in-memory encrypted string. + /// A string that is protected in process memory. /// ProtectedString objects are immutable and thread-safe. /// #if (DEBUG && !KeePassLibSD) - [DebuggerDisplay(@"{ReadString()}")] + [DebuggerDisplay("{ReadString()}")] #endif public sealed class ProtectedString { @@ -48,11 +48,24 @@ namespace ModernKeePassLib.Security private bool m_bIsProtected; private static readonly ProtectedString m_psEmpty = new ProtectedString(); + /// + /// Get an empty ProtectedString object, without protection. + /// public static ProtectedString Empty { get { return m_psEmpty; } } + private static readonly ProtectedString m_psEmptyEx = new ProtectedString( + true, new byte[0]); + /// + /// Get an empty ProtectedString object, with protection turned on. + /// + public static ProtectedString EmptyEx + { + get { return m_psEmptyEx; } + } + /// /// A flag specifying whether the ProtectedString object /// has turned on memory protection or not. @@ -66,8 +79,8 @@ namespace ModernKeePassLib.Security { get { - ProtectedBinary pBin = m_pbUtf8; // Local ref for thread-safety - if(pBin != null) return (pBin.Length == 0); + ProtectedBinary p = m_pbUtf8; // Local ref for thread-safety + if(p != null) return (p.Length == 0); Debug.Assert(m_strPlainText != null); return (m_strPlainText.Length == 0); @@ -75,18 +88,21 @@ namespace ModernKeePassLib.Security } private int m_nCachedLength = -1; + /// + /// Length of the protected string, in characters. + /// public int Length { get { if(m_nCachedLength >= 0) return m_nCachedLength; - ProtectedBinary pBin = m_pbUtf8; // Local ref for thread-safety - if(pBin != null) + ProtectedBinary p = m_pbUtf8; // Local ref for thread-safety + if(p != null) { - byte[] pbPlain = pBin.ReadData(); - m_nCachedLength = StrUtil.Utf8.GetCharCount(pbPlain); - MemUtil.ZeroByteArray(pbPlain); + byte[] pbPlain = p.ReadData(); + try { m_nCachedLength = StrUtil.Utf8.GetCharCount(pbPlain); } + finally { MemUtil.ZeroByteArray(pbPlain); } } else { @@ -149,9 +165,8 @@ namespace ModernKeePassLib.Security if(xbProtected == null) throw new ArgumentNullException("xbProtected"); byte[] pb = xbProtected.ReadPlainText(); - Init(bEnableProtection, pb); - - if(bEnableProtection) MemUtil.ZeroByteArray(pb); + try { Init(bEnableProtection, pb); } + finally { if(bEnableProtection) MemUtil.ZeroByteArray(pb); } } private void Init(bool bEnableProtection, string str) @@ -160,7 +175,7 @@ namespace ModernKeePassLib.Security m_bIsProtected = bEnableProtection; - // The string already is in memory and immutable, + // As the string already is in memory and immutable, // protection would be useless m_strPlainText = str; } @@ -178,8 +193,8 @@ namespace ModernKeePassLib.Security } /// - /// Convert the protected string to a normal string object. - /// Be careful with this function, the returned string object + /// Convert the protected string to a standard string object. + /// Be careful with this function, as the returned string object /// isn't protected anymore and stored in plain-text in the /// process memory. /// @@ -194,23 +209,42 @@ namespace ModernKeePassLib.Security // No need to clear pb // As the text is now visible in process memory anyway, - // there's no need to protect it anymore + // there's no need to protect it anymore (strings are + // immutable and thus cannot be overwritten) m_strPlainText = str; m_pbUtf8 = null; // Thread-safe order return str; } + /// + /// Read out the string and return it as a char array. + /// The returned array is not protected and should be cleared by + /// the caller. + /// + /// Plain-text char array. + public char[] ReadChars() + { + if(m_strPlainText != null) return m_strPlainText.ToCharArray(); + + byte[] pb = ReadUtf8(); + char[] v; + try { v = StrUtil.Utf8.GetChars(pb); } + finally { MemUtil.ZeroByteArray(pb); } + return v; + } + /// /// Read out the string and return a byte array that contains the - /// string encoded using UTF-8. The returned string is not protected - /// anymore! + /// string encoded using UTF-8. + /// The returned array is not protected and should be cleared by + /// the caller. /// /// Plain-text UTF-8 byte array. public byte[] ReadUtf8() { - ProtectedBinary pBin = m_pbUtf8; // Local ref for thread-safety - if(pBin != null) return pBin.ReadData(); + ProtectedBinary p = m_pbUtf8; // Local ref for thread-safety + if(p != null) return p.ReadData(); return StrUtil.Utf8.GetBytes(m_strPlainText); } @@ -223,7 +257,7 @@ namespace ModernKeePassLib.Security /// Protected string. public byte[] ReadXorredString(CryptoRandomStream crsRandomSource) { - Debug.Assert(crsRandomSource != null); if(crsRandomSource == null) throw new ArgumentNullException("crsRandomSource"); + if(crsRandomSource == null) { Debug.Assert(false); throw new ArgumentNullException("crsRandomSource"); } byte[] pbData = ReadUtf8(); uint uLen = (uint)pbData.Length; @@ -242,10 +276,34 @@ namespace ModernKeePassLib.Security if(bProtect == m_bIsProtected) return this; byte[] pb = ReadUtf8(); - ProtectedString ps = new ProtectedString(bProtect, pb); - if(bProtect) MemUtil.ZeroByteArray(pb); - return ps; + // No need to clear pb; either the current or the new object is unprotected + return new ProtectedString(bProtect, pb); + } + + public bool Equals(ProtectedString ps, bool bCheckProtEqual) + { + if(ps == null) throw new ArgumentNullException("ps"); + if(object.ReferenceEquals(this, ps)) return true; // Perf. opt. + + bool bPA = m_bIsProtected, bPB = ps.m_bIsProtected; + if(bCheckProtEqual && (bPA != bPB)) return false; + if(!bPA && !bPB) return (ReadString() == ps.ReadString()); + + byte[] pbA = ReadUtf8(), pbB = null; + bool bEq; + try + { + pbB = ps.ReadUtf8(); + bEq = MemUtil.ArraysEqual(pbA, pbB); + } + finally + { + if(bPA) MemUtil.ZeroByteArray(pbA); + if(bPB && (pbB != null)) MemUtil.ZeroByteArray(pbB); + } + + return bEq; } public ProtectedString Insert(int iStart, string strInsert) @@ -254,18 +312,14 @@ namespace ModernKeePassLib.Security if(strInsert == null) throw new ArgumentNullException("strInsert"); if(strInsert.Length == 0) return this; - // Only operate directly with strings when m_bIsProtected is - // false, not in the case of non-null m_strPlainText, because - // the operation creates a new sequence in memory if(!m_bIsProtected) return new ProtectedString(false, ReadString().Insert( iStart, strInsert)); UTF8Encoding utf8 = StrUtil.Utf8; - - byte[] pb = ReadUtf8(); - char[] v = utf8.GetChars(pb); - char[] vNew; + char[] v = ReadChars(), vNew = null; + byte[] pbNew = null; + ProtectedString ps; try { @@ -279,21 +333,20 @@ namespace ModernKeePassLib.Security Array.Copy(vIns, 0, vNew, iStart, vIns.Length); Array.Copy(v, iStart, vNew, iStart + vIns.Length, v.Length - iStart); + + pbNew = utf8.GetBytes(vNew); + ps = new ProtectedString(true, pbNew); + + Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) == + ReadString().Insert(iStart, strInsert)); } finally { MemUtil.ZeroArray(v); - MemUtil.ZeroByteArray(pb); + if(vNew != null) MemUtil.ZeroArray(vNew); + if(pbNew != null) MemUtil.ZeroByteArray(pbNew); } - byte[] pbNew = utf8.GetBytes(vNew); - ProtectedString ps = new ProtectedString(m_bIsProtected, pbNew); - - Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) == - ReadString().Insert(iStart, strInsert)); - - MemUtil.ZeroArray(vNew); - MemUtil.ZeroByteArray(pbNew); return ps; } @@ -303,44 +356,81 @@ namespace ModernKeePassLib.Security if(nCount < 0) throw new ArgumentOutOfRangeException("nCount"); if(nCount == 0) return this; - // Only operate directly with strings when m_bIsProtected is - // false, not in the case of non-null m_strPlainText, because - // the operation creates a new sequence in memory if(!m_bIsProtected) return new ProtectedString(false, ReadString().Remove( iStart, nCount)); UTF8Encoding utf8 = StrUtil.Utf8; - - byte[] pb = ReadUtf8(); - char[] v = utf8.GetChars(pb); - char[] vNew; + char[] v = ReadChars(), vNew = null; + byte[] pbNew = null; + ProtectedString ps; try { if((iStart + nCount) > v.Length) - throw new ArgumentException("iStart + nCount"); + throw new ArgumentException("(iStart + nCount) > v.Length"); vNew = new char[v.Length - nCount]; Array.Copy(v, 0, vNew, 0, iStart); Array.Copy(v, iStart + nCount, vNew, iStart, v.Length - (iStart + nCount)); + + pbNew = utf8.GetBytes(vNew); + ps = new ProtectedString(true, pbNew); + + Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) == + ReadString().Remove(iStart, nCount)); } finally { MemUtil.ZeroArray(v); - MemUtil.ZeroByteArray(pb); + if(vNew != null) MemUtil.ZeroArray(vNew); + if(pbNew != null) MemUtil.ZeroByteArray(pbNew); } - byte[] pbNew = utf8.GetBytes(vNew); - ProtectedString ps = new ProtectedString(m_bIsProtected, pbNew); - - Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) == - ReadString().Remove(iStart, nCount)); - - MemUtil.ZeroArray(vNew); - MemUtil.ZeroByteArray(pbNew); return ps; } + + public static ProtectedString operator +(ProtectedString a, ProtectedString b) + { + if(a == null) throw new ArgumentNullException("a"); + if(b == null) throw new ArgumentNullException("b"); + + if(b.IsEmpty) return a; + if(a.IsEmpty) return b; + if(!a.IsProtected && !b.IsProtected) + return new ProtectedString(false, a.ReadString() + b.ReadString()); + + char[] vA = a.ReadChars(), vB = null, vNew = null; + byte[] pbNew = null; + ProtectedString ps; + + try + { + vB = b.ReadChars(); + + vNew = new char[vA.Length + vB.Length]; + Array.Copy(vA, vNew, vA.Length); + Array.Copy(vB, 0, vNew, vA.Length, vB.Length); + + pbNew = StrUtil.Utf8.GetBytes(vNew); + ps = new ProtectedString(true, pbNew); + } + finally + { + MemUtil.ZeroArray(vA); + if(vB != null) MemUtil.ZeroArray(vB); + if(vNew != null) MemUtil.ZeroArray(vNew); + if(pbNew != null) MemUtil.ZeroByteArray(pbNew); + } + + return ps; + } + + public static ProtectedString operator +(ProtectedString a, string b) + { + ProtectedString psB = new ProtectedString(false, b); + return (a + psB); + } } } diff --git a/ModernKeePassLib/Serialization/FileLock.cs b/ModernKeePassLib/Serialization/FileLock.cs index 8981dc6..e476acb 100644 --- a/ModernKeePassLib/Serialization/FileLock.cs +++ b/ModernKeePassLib/Serialization/FileLock.cs @@ -129,9 +129,12 @@ namespace ModernKeePassLib.Serialization { s = IOConnection.OpenRead(iocLockFile); if(s == null) return null; - StreamReader sr = new StreamReader(s, StrUtil.Utf8); - string str = sr.ReadToEnd(); - sr.Dispose(); + + string str = null; + using(StreamReader sr = new StreamReader(s, StrUtil.Utf8)) + { + str = sr.ReadToEnd(); + } if(str == null) { Debug.Assert(false); return null; } str = StrUtil.NormalizeNewLines(str, false); diff --git a/ModernKeePassLib/Serialization/FileTransactionEx.cs b/ModernKeePassLib/Serialization/FileTransactionEx.cs index 29eac7f..4102ef3 100644 --- a/ModernKeePassLib/Serialization/FileTransactionEx.cs +++ b/ModernKeePassLib/Serialization/FileTransactionEx.cs @@ -19,6 +19,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Text; @@ -32,19 +33,25 @@ using ModernKeePassLib.Utility; using System.Threading.Tasks; using Windows.Storage; using Windows.Storage.Streams; +using ModernKeePassLib.Cryptography; using ModernKeePassLib.Resources; namespace ModernKeePassLib.Serialization { - public sealed class FileTransactionEx + public sealed class FileTransactionEx : IDisposable { private bool m_bTransacted; - private IOConnectionInfo m_iocBase; + private IOConnectionInfo m_iocBase; // Null means disposed private IOConnectionInfo m_iocTemp; + private IOConnectionInfo m_iocTxfMidFallback = null; // Null <=> TxF not used private bool m_bMadeUnhidden = false; + private List m_lToDelete = new List(); + private const string StrTempSuffix = ".tmp"; + private const string StrTxfTempPrefix = PwDefs.ShortProductName + "_TxF_"; + private const string StrTxfTempSuffix = ".tmp"; private static Dictionary g_dEnabled = new Dictionary(StrUtil.CaseIgnoreComparer); @@ -56,26 +63,23 @@ namespace ModernKeePassLib.Serialization set { g_bExtraSafe = value; } } - public FileTransactionEx(IOConnectionInfo iocBaseFile) + public FileTransactionEx(IOConnectionInfo iocBaseFile) : + this(iocBaseFile, true) { - Initialize(iocBaseFile, true); } public FileTransactionEx(IOConnectionInfo iocBaseFile, bool bTransacted) - { - Initialize(iocBaseFile, bTransacted); - } - - private void Initialize(IOConnectionInfo iocBaseFile, bool bTransacted) { if(iocBaseFile == null) throw new ArgumentNullException("iocBaseFile"); m_bTransacted = bTransacted; + m_iocBase = iocBaseFile.CloneDeep(); + if(m_iocBase.IsLocalFile()) + m_iocBase.Path = UrlUtil.GetShortestAbsolutePath(m_iocBase.Path); string strPath = m_iocBase.Path; -#if !ModernKeePassLib if(m_iocBase.IsLocalFile()) { try @@ -93,10 +97,23 @@ namespace ModernKeePassLib.Serialization if((long)(fa & FileAttributes.ReparsePoint) != 0) m_bTransacted = false; } + else + { + // If the base and the temporary file are in different + // folders and the base file doesn't exist (i.e. we can't + // backup the ACL), a transaction would cause the new file + // to have the default ACL of the temporary folder instead + // of the one of the base folder; therefore, we don't use + // a transaction when the base file doesn't exist (this + // also results in other applications monitoring the folder + // to see one file creation only) + m_bTransacted = false; + } } catch(Exception) { Debug.Assert(false); } } +#if !ModernKeePassLib // Prevent transactions for FTP URLs under .NET 4.0 in order to // avoid/workaround .NET bug 621450: // https://connect.microsoft.com/VisualStudio/feedback/details/621450/problem-renaming-file-on-ftp-server-using-ftpwebrequest-in-net-framework-4-0-vs2010-only @@ -118,90 +135,152 @@ namespace ModernKeePassLib.Serialization { m_iocTemp = m_iocBase.CloneDeep(); m_iocTemp.Path += StrTempSuffix; + + TxfPrepare(); // Adjusts m_iocTemp } else m_iocTemp = m_iocBase; } + ~FileTransactionEx() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool bDisposing) + { + m_iocBase = null; + if(!bDisposing) return; + + try + { + foreach(IOConnectionInfo ioc in m_lToDelete) + { + if(IOConnection.FileExists(ioc, false)) + IOConnection.DeleteFile(ioc); + } + + m_lToDelete.Clear(); + } + catch(Exception) { Debug.Assert(false); } + } + public Stream OpenWrite() { - if(!m_bTransacted) m_bMadeUnhidden = UrlUtil.UnhideFile(m_iocTemp.Path); - else // m_bTransacted - { - try { IOConnection.DeleteFile(m_iocTemp); } - catch(Exception) { } - } + if(m_iocBase == null) { Debug.Assert(false); throw new ObjectDisposedException(null); } + + if(!m_bTransacted) m_bMadeUnhidden |= UrlUtil.UnhideFile(m_iocTemp.Path); return IOConnection.OpenWrite(m_iocTemp); } public void CommitWrite() { - if(m_bTransacted) CommitWriteTransaction(); - else // !m_bTransacted + if(m_iocBase == null) { Debug.Assert(false); throw new ObjectDisposedException(null); } + + if(!m_bTransacted) { - if(m_bMadeUnhidden) UrlUtil.HideFile(m_iocTemp.Path, true); // Hide again + if(m_bMadeUnhidden) UrlUtil.HideFile(m_iocTemp.Path, true); } + else CommitWriteTransaction(); + + m_iocBase = null; // Dispose } private void CommitWriteTransaction() { - bool bMadeUnhidden = UrlUtil.UnhideFile(m_iocBase.Path); - -#if (!ModernKeePassLib && !KeePassLibSD) - FileSecurity bkSecurity = null; - bool bEfsEncrypted = false; -#endif - if(g_bExtraSafe) { if(!IOConnection.FileExists(m_iocTemp)) throw new FileNotFoundException(m_iocTemp.Path + - Environment.NewLine + KLRes.FileSaveFailed); + MessageService.NewLine + KLRes.FileSaveFailed); } - if(IOConnection.FileExists(m_iocBase)) - { -#if (!ModernKeePassLib && !KeePassLibSD) - if(m_iocBase.IsLocalFile()) - { - try - { - FileAttributes faBase = File.GetAttributes(m_iocBase.Path); - bEfsEncrypted = ((long)(faBase & FileAttributes.Encrypted) != 0); + bool bMadeUnhidden = UrlUtil.UnhideFile(m_iocBase.Path); - DateTime tCreation = File.GetCreationTimeUtc(m_iocBase.Path); - bkSecurity = File.GetAccessControl(m_iocBase.Path); +#if !ModernKeePassLib + // 'All' includes 'Audit' (SACL), which requires SeSecurityPrivilege, + // which we usually don't have and therefore get an exception; + // trying to set 'Owner' or 'Group' can result in an + // UnauthorizedAccessException; thus we restore 'Access' (DACL) only + const AccessControlSections acs = AccessControlSections.Access; - File.SetCreationTimeUtc(m_iocTemp.Path, tCreation); - } - catch(Exception) { Debug.Assert(false); } - } + bool bEfsEncrypted = false; + byte[] pbSec = null; #endif + DateTime? otCreation = null; - IOConnection.DeleteFile(m_iocBase); - } - - IOConnection.RenameFile(m_iocTemp, m_iocBase); - -#if (!ModernKeePassLib && !KeePassLibSD) - if(m_iocBase.IsLocalFile()) + bool bBaseExists = IOConnection.FileExists(m_iocBase); + if(bBaseExists && m_iocBase.IsLocalFile()) { + // FileAttributes faBase = FileAttributes.Normal; try { - if(bEfsEncrypted) - { - try { File.Encrypt(m_iocBase.Path); } - catch(Exception) { Debug.Assert(false); } - } - - if(bkSecurity != null) - File.SetAccessControl(m_iocBase.Path, bkSecurity); - } - catch(Exception) { Debug.Assert(false); } - } +#if !ModernKeePassLib + FileAttributes faBase = File.GetAttributes(m_iocBase.Path); + bEfsEncrypted = ((long)(faBase & FileAttributes.Encrypted) != 0); + try { if(bEfsEncrypted) File.Decrypt(m_iocBase.Path); } // For TxF + catch(Exception) { Debug.Assert(false); } #endif + otCreation = File.GetCreationTimeUtc(m_iocBase.Path); +#if !ModernKeePassLib + // May throw with Mono + FileSecurity sec = File.GetAccessControl(m_iocBase.Path, acs); + if(sec != null) pbSec = sec.GetSecurityDescriptorBinaryForm(); +#endif + } + catch(Exception) { Debug.Assert(NativeLib.IsUnix()); } - if(bMadeUnhidden) UrlUtil.HideFile(m_iocBase.Path, true); // Hide again + // if((long)(faBase & FileAttributes.ReadOnly) != 0) + // throw new UnauthorizedAccessException(); + } + + if(!TxfMove()) + { + if(bBaseExists) IOConnection.DeleteFile(m_iocBase); + IOConnection.RenameFile(m_iocTemp, m_iocBase); + } + else { Debug.Assert(pbSec != null); } // TxF success => NTFS => has ACL + + try + { + // If File.GetCreationTimeUtc fails, it may return a + // date with year 1601, and Unix times start in 1970, + // so testing for 1971 should ensure validity; + // https://msdn.microsoft.com/en-us/library/system.io.file.getcreationtimeutc.aspx + if(otCreation.HasValue && (otCreation.Value.Year >= 1971)) + File.SetCreationTimeUtc(m_iocBase.Path, otCreation.Value); + +#if !ModernKeePassLib + if(bEfsEncrypted) + { + try { File.Encrypt(m_iocBase.Path); } + catch(Exception) { Debug.Assert(false); } + } + + // File.SetAccessControl(m_iocBase.Path, secPrev); + // Directly calling File.SetAccessControl with the previous + // FileSecurity object does not work; the binary form + // indirection is required; + // https://sourceforge.net/p/keepass/bugs/1738/ + // https://msdn.microsoft.com/en-us/library/system.io.file.setaccesscontrol.aspx + if((pbSec != null) && (pbSec.Length != 0)) + { + FileSecurity sec = new FileSecurity(); + sec.SetSecurityDescriptorBinaryForm(pbSec, acs); + + File.SetAccessControl(m_iocBase.Path, sec); + } +#endif + } + catch(Exception) { Debug.Assert(false); } + + if(bMadeUnhidden) UrlUtil.HideFile(m_iocBase.Path, true); } // For plugins @@ -213,5 +292,155 @@ namespace ModernKeePassLib.Serialization g_dEnabled[strPrefix] = obTransacted.Value; else g_dEnabled.Remove(strPrefix); } + + private static bool TxfIsSupported(char chDriveLetter) + { + if(chDriveLetter == '\0') return false; + +#if !ModernKeePassLib + try + { + string strRoot = (new string(chDriveLetter, 1)) + ":\\"; + + const int cch = NativeMethods.MAX_PATH + 1; + StringBuilder sbName = new StringBuilder(cch + 1); + uint uSerial = 0, cchMaxComp = 0, uFlags = 0; + StringBuilder sbFileSystem = new StringBuilder(cch + 1); + + if(!NativeMethods.GetVolumeInformation(strRoot, sbName, (uint)cch, + ref uSerial, ref cchMaxComp, ref uFlags, sbFileSystem, (uint)cch)) + { + Debug.Assert(false, (new Win32Exception()).Message); + return false; + } + + return ((uFlags & NativeMethods.FILE_SUPPORTS_TRANSACTIONS) != 0); + } + catch(Exception) { Debug.Assert(false); } +#endif + return false; + } + + private void TxfPrepare() + { + try + { + if(NativeLib.IsUnix()) return; + if(!m_iocBase.IsLocalFile()) return; + + string strID = StrUtil.AlphaNumericOnly(Convert.ToBase64String( + CryptoRandom.Instance.GetRandomBytes(16))); + string strTempDir = UrlUtil.GetTempPath(); + // See also ClearOld method + string strTemp = UrlUtil.EnsureTerminatingSeparator(strTempDir, + false) + StrTxfTempPrefix + strID + StrTxfTempSuffix; + + char chB = UrlUtil.GetDriveLetter(m_iocBase.Path); + char chT = UrlUtil.GetDriveLetter(strTemp); + if(!TxfIsSupported(chB)) return; + if((chT != chB) && !TxfIsSupported(chT)) return; + + m_iocTxfMidFallback = m_iocTemp; + m_iocTemp = IOConnectionInfo.FromPath(strTemp); + + m_lToDelete.Add(m_iocTemp); + } + catch(Exception) { Debug.Assert(false); m_iocTxfMidFallback = null; } + } + +#if !ModernKeePassLib + private bool TxfMove() + { + if(m_iocTxfMidFallback == null) return false; + + if(TxfMoveWithTx()) return true; + + // Move the temporary file onto the base file's drive first, + // such that it cannot happen that both the base file and + // the temporary file are deleted/corrupted + const uint f = (NativeMethods.MOVEFILE_COPY_ALLOWED | + NativeMethods.MOVEFILE_REPLACE_EXISTING); + bool b = NativeMethods.MoveFileEx(m_iocTemp.Path, m_iocTxfMidFallback.Path, f); + if(b) b = NativeMethods.MoveFileEx(m_iocTxfMidFallback.Path, m_iocBase.Path, f); + if(!b) throw new Win32Exception(); + + Debug.Assert(!File.Exists(m_iocTemp.Path)); + Debug.Assert(!File.Exists(m_iocTxfMidFallback.Path)); + return true; + } + + private bool TxfMoveWithTx() + { + IntPtr hTx = new IntPtr((int)NativeMethods.INVALID_HANDLE_VALUE); + Debug.Assert(hTx.ToInt64() == NativeMethods.INVALID_HANDLE_VALUE); + try + { + string strTx = PwDefs.ShortProductName + " TxF - " + + StrUtil.AlphaNumericOnly(Convert.ToBase64String( + CryptoRandom.Instance.GetRandomBytes(16))); + const int mchTx = NativeMethods.MAX_TRANSACTION_DESCRIPTION_LENGTH; + if(strTx.Length >= mchTx) strTx = strTx.Substring(0, mchTx - 1); + + hTx = NativeMethods.CreateTransaction(IntPtr.Zero, + IntPtr.Zero, 0, 0, 0, 0, strTx); + if(hTx.ToInt64() == NativeMethods.INVALID_HANDLE_VALUE) + { + Debug.Assert(false, (new Win32Exception()).Message); + return false; + } + + if(!NativeMethods.MoveFileTransacted(m_iocTemp.Path, m_iocBase.Path, + IntPtr.Zero, IntPtr.Zero, (NativeMethods.MOVEFILE_COPY_ALLOWED | + NativeMethods.MOVEFILE_REPLACE_EXISTING), hTx)) + { + Debug.Assert(false, (new Win32Exception()).Message); + return false; + } + + if(!NativeMethods.CommitTransaction(hTx)) + { + Debug.Assert(false, (new Win32Exception()).Message); + return false; + } + + Debug.Assert(!File.Exists(m_iocTemp.Path)); + return true; + } + catch(Exception) { Debug.Assert(false); } + finally + { + if(hTx.ToInt64() != NativeMethods.INVALID_HANDLE_VALUE) + { + try { if(!NativeMethods.CloseHandle(hTx)) { Debug.Assert(false); } } + catch(Exception) { Debug.Assert(false); } + } + } + + return false; + } + + internal static void ClearOld() + { + try + { + // See also TxfPrepare method + DirectoryInfo di = new DirectoryInfo(UrlUtil.GetTempPath()); + List l = UrlUtil.GetFileInfos(di, StrTxfTempPrefix + + "*" + StrTxfTempSuffix, SearchOption.TopDirectoryOnly); + + foreach(FileInfo fi in l) + { + if(fi == null) { Debug.Assert(false); continue; } + if(!fi.Name.StartsWith(StrTxfTempPrefix, StrUtil.CaseIgnoreCmp) || + !fi.Name.EndsWith(StrTxfTempSuffix, StrUtil.CaseIgnoreCmp)) + continue; + + if((DateTime.UtcNow - fi.LastWriteTimeUtc).TotalDays > 1.0) + fi.Delete(); + } + } + catch(Exception) { Debug.Assert(false); } + } +#endif } } diff --git a/ModernKeePassLib/Serialization/IOConnection.cs b/ModernKeePassLib/Serialization/IOConnection.cs index 4341c1c..a3a9161 100644 --- a/ModernKeePassLib/Serialization/IOConnection.cs +++ b/ModernKeePassLib/Serialization/IOConnection.cs @@ -431,7 +431,7 @@ namespace ModernKeePassLib.Serialization string strInfo = m_strProxyAddr; if(m_strProxyPort.Length > 0) strInfo += ":" + m_strProxyPort; - MessageService.ShowWarning(strInfo, ex.Message); + MessageService.ShowWarning(strInfo, ex); } #endif @@ -642,7 +642,7 @@ namespace ModernKeePassLib.Serialization #endif } - public static bool FileExists(IOConnectionInfo ioc) + public static bool FileExists(IOConnectionInfo ioc) { return FileExists(ioc, false); } diff --git a/ModernKeePassLib/Serialization/KdbxFile.Read.cs b/ModernKeePassLib/Serialization/KdbxFile.Read.cs index 6cf20a8..058977a 100644 --- a/ModernKeePassLib/Serialization/KdbxFile.Read.cs +++ b/ModernKeePassLib/Serialization/KdbxFile.Read.cs @@ -530,7 +530,7 @@ namespace ModernKeePassLib.Serialization /* KdbxFile f = new KdbxFile(pwDatabase); f.m_format = KdbxFormat.PlainXml; - XmlDocument doc = new XmlDocument(); + XmlDocument doc = XmlUtilEx.CreateXmlDocument(); doc.Load(msData); XmlElement el = doc.DocumentElement; diff --git a/ModernKeePassLib/Serialization/KdbxFile.Write.cs b/ModernKeePassLib/Serialization/KdbxFile.Write.cs index ab2f787..90ed336 100644 --- a/ModernKeePassLib/Serialization/KdbxFile.Write.cs +++ b/ModernKeePassLib/Serialization/KdbxFile.Write.cs @@ -87,6 +87,7 @@ namespace ModernKeePassLib.Serialization m_format = fmt; m_slLogger = slLogger; + m_xmlWriter = null; PwGroup pgRoot = (pgDataSource ?? m_pwDatabase.RootGroup); UTF8Encoding encNoBom = StrUtil.Utf8; @@ -203,44 +204,25 @@ namespace ModernKeePassLib.Serialization throw new ArgumentOutOfRangeException("fmt"); } -#if ModernKeePassLib || KeePassUAP - XmlWriterSettings xws = new XmlWriterSettings(); - xws.Encoding = encNoBom; - xws.Indent = true; - xws.IndentChars = "\t"; - xws.NewLineOnAttributes = false; -#if ModernKeePassLib - // This is needed for Argon2Kdf write - xws.Async = true; - if (m_uFileVersion >= FileVersion32_4) xws.CloseOutput = true; -#endif - - XmlWriter xw = XmlWriter.Create(sXml, xws); -#else - XmlTextWriter xw = new XmlTextWriter(sXml, encNoBom); - - xw.Formatting = Formatting.Indented; - xw.IndentChar = '\t'; - xw.Indentation = 1; -#endif - m_xmlWriter = xw; + m_xmlWriter = XmlUtilEx.CreateXmlWriter(sXml, m_uFileVersion >= FileVersion32_4); WriteDocument(pgRoot); m_xmlWriter.Flush(); - m_xmlWriter.Dispose(); } finally { + CommonCleanUpWrite(lStreams, sHashing); + if(pbCipherKey != null) MemUtil.ZeroByteArray(pbCipherKey); if(pbHmacKey64 != null) MemUtil.ZeroByteArray(pbHmacKey64); - - CommonCleanUpWrite(lStreams, sHashing); } } private void CommonCleanUpWrite(List lStreams, HashingStreamEx sHashing) { + if(m_xmlWriter != null) { m_xmlWriter.Dispose(); m_xmlWriter = null; } + CloseStreams(lStreams); Debug.Assert(lStreams.Contains(sHashing)); // sHashing must be closed @@ -249,7 +231,6 @@ namespace ModernKeePassLib.Serialization CleanUpInnerRandomStream(); - m_xmlWriter = null; m_pbHashOfHeader = null; } diff --git a/ModernKeePassLib/Serialization/KdbxFile.cs b/ModernKeePassLib/Serialization/KdbxFile.cs index 3be8b9a..82248a0 100644 --- a/ModernKeePassLib/Serialization/KdbxFile.cs +++ b/ModernKeePassLib/Serialization/KdbxFile.cs @@ -403,8 +403,8 @@ namespace ModernKeePassLib.Serialization ICipherEngine iCipher = CipherPool.GlobalPool.GetCipher(pu); if(iCipher == null) // CryptographicExceptions are translated to "file corrupted" throw new Exception(KLRes.FileUnknownCipher + - Environment.NewLine + KLRes.FileNewVerOrPlgReq + - Environment.NewLine + "UUID: " + pu.ToHexString() + "."); + MessageService.NewParagraph + KLRes.FileNewVerOrPlgReq + + MessageService.NewParagraph + "UUID: " + pu.ToHexString() + "."); ICipherEngine2 iCipher2 = (iCipher as ICipherEngine2); if(iCipher2 != null) @@ -469,14 +469,14 @@ namespace ModernKeePassLib.Serialization Debug.Assert((lStreams.IndexOf(lStreams[i]) == i) && (lStreams.LastIndexOf(lStreams[i]) == i)); - try { lStreams[i].Dispose(); } - // Unnecessary exception from CryptoStream with - // RijndaelManagedTransform when a stream hasn't been - // read completely (e.g. incorrect master key) + try { lStreams[i].Dispose(); } + // Unnecessary exception from CryptoStream with + // RijndaelManagedTransform when a stream hasn't been + // read completely (e.g. incorrect master key) #if !ModernKeePassLib - catch(CryptographicException) { } + catch(CryptographicException) { } #endif - catch (Exception) { Debug.Assert(false); } + catch(Exception) { Debug.Assert(false); } } // Do not clear the list @@ -539,8 +539,8 @@ namespace ModernKeePassLib.Serialization FileStream fs = new FileStream(strPath, FileMode.Create, FileAccess.Write, FileShare.None); byte[] pbData = pb.ReadData(); - fs.Write(pbData, 0, pbData.Length); - fs.Close(); + try { File.WriteAllBytes(strPath, pbData); } + finally { if(pb.IsProtected) MemUtil.ZeroByteArray(pbData); } #endif } } diff --git a/ModernKeePassLib/Translation/KPTranslation.cs b/ModernKeePassLib/Translation/KPTranslation.cs index e815305..9678e09 100644 --- a/ModernKeePassLib/Translation/KPTranslation.cs +++ b/ModernKeePassLib/Translation/KPTranslation.cs @@ -111,23 +111,17 @@ namespace ModernKeePassLib.Translation if(xs == null) throw new ArgumentNullException("xs"); #if !KeePassLibSD - GZipStream gz = new GZipStream(sOut, CompressionMode.Compress); + using(GZipStream gz = new GZipStream(sOut, CompressionMode.Compress)) #else - GZipOutputStream gz = new GZipOutputStream(sOut); + using(GZipOutputStream gz = new GZipOutputStream(sOut)) #endif + { + using(XmlWriter xw = XmlUtilEx.CreateXmlWriter(gz)) + { + xs.Serialize(xw, kpTrl); + } + } - XmlWriterSettings xws = new XmlWriterSettings(); - xws.CheckCharacters = true; - xws.Encoding = StrUtil.Utf8; - xws.Indent = true; - xws.IndentChars = "\t"; - - XmlWriter xw = XmlWriter.Create(gz, xws); - - xs.Serialize(xw, kpTrl); - - xw.Close(); - gz.Close(); sOut.Close(); } @@ -148,15 +142,17 @@ namespace ModernKeePassLib.Translation { if(xs == null) throw new ArgumentNullException("xs"); + KPTranslation kpTrl = null; + #if !KeePassLibSD - GZipStream gz = new GZipStream(s, CompressionMode.Decompress); + using(GZipStream gz = new GZipStream(s, CompressionMode.Decompress)) #else - GZipInputStream gz = new GZipInputStream(s); + using(GZipInputStream gz = new GZipInputStream(s)) #endif + { + kpTrl = (xs.Deserialize(gz) as KPTranslation); + } - KPTranslation kpTrl = (xs.Deserialize(gz) as KPTranslation); - - gz.Close(); s.Close(); return kpTrl; } diff --git a/ModernKeePassLib/Utility/MemUtil.cs b/ModernKeePassLib/Utility/MemUtil.cs index 6206f29..fc3261b 100644 --- a/ModernKeePassLib/Utility/MemUtil.cs +++ b/ModernKeePassLib/Utility/MemUtil.cs @@ -496,11 +496,21 @@ namespace ModernKeePassLib.Utility return UInt32ToBytes((uint)iValue); } + public static void Int32ToBytesEx(int iValue, byte[] pb, int iOffset) + { + UInt32ToBytesEx((uint)iValue, pb, iOffset); + } + public static byte[] Int64ToBytes(long lValue) { return UInt64ToBytes((ulong)lValue); } + public static void Int64ToBytesEx(long lValue, byte[] pb, int iOffset) + { + UInt64ToBytesEx((ulong)lValue, pb, iOffset); + } + public static uint RotateLeft32(uint u, int nBits) { return ((u << nBits) | (u >> (32 - nBits))); diff --git a/ModernKeePassLib/Utility/MessageService.cs b/ModernKeePassLib/Utility/MessageService.cs index 0b8af08..a8a0d4c 100644 --- a/ModernKeePassLib/Utility/MessageService.cs +++ b/ModernKeePassLib/Utility/MessageService.cs @@ -23,7 +23,7 @@ using System.Collections.Specialized; using System.Diagnostics; using System.Text; -#if !KeePassUAP +#if !ModernKeePassLib using System.Windows.Forms; #endif @@ -34,7 +34,8 @@ namespace ModernKeePassLib.Utility { public sealed class MessageServiceEventArgs : EventArgs { - private string m_strTitle = string.Empty; +#if !ModernKeePassLib + private string m_strTitle = string.Empty; private string m_strText = string.Empty; private MessageBoxButtons m_msgButtons = MessageBoxButtons.OK; private MessageBoxIcon m_msgIcon = MessageBoxIcon.None; @@ -54,12 +55,14 @@ namespace ModernKeePassLib.Utility m_msgButtons = msgButtons; m_msgIcon = msgIcon; } - } +#endif + } public static class MessageService { - private static volatile uint m_uCurrentMessageCount = 0; + private static volatile uint m_uCurrentMessageCount = 0; +#if !ModernKeePassLib #if !KeePassLibSD private const MessageBoxIcon m_mbiInfo = MessageBoxIcon.Information; private const MessageBoxIcon m_mbiWarning = MessageBoxIcon.Warning; @@ -73,8 +76,8 @@ namespace ModernKeePassLib.Utility private const MessageBoxIcon m_mbiFatal = MessageBoxIcon.Hand; #endif private const MessageBoxIcon m_mbiQuestion = MessageBoxIcon.Question; - - public static string NewLine +#endif + public static string NewLine { #if !KeePassLibSD get { return Environment.NewLine; } @@ -123,7 +126,7 @@ namespace ModernKeePassLib.Utility Exception exObj = (obj as Exception); string strObj = (obj as string); -#if (!KeePassLibSD && !KeePassRT) +#if (!KeePassLibSD && !ModernKeePassLib) StringCollection scObj = (obj as StringCollection); #endif @@ -131,10 +134,10 @@ namespace ModernKeePassLib.Utility { if(bFullExceptions) strAppend = StrUtil.FormatException(exObj); - else if((exObj.Message != null) && (exObj.Message.Length > 0)) + else if(!string.IsNullOrEmpty(exObj.Message)) strAppend = exObj.Message; } -#if (!KeePassLibSD && !KeePassRT) +#if (!KeePassLibSD && !ModernKeePassLib) else if(scObj != null) { StringBuilder sb = new StringBuilder(); @@ -146,7 +149,7 @@ namespace ModernKeePassLib.Utility strAppend = sb.ToString(); } #endif - else if(strObj != null) + else if (strObj != null) strAppend = strObj; else strAppend = obj.ToString(); @@ -163,7 +166,7 @@ namespace ModernKeePassLib.Utility return sbText.ToString(); } -#if (!KeePassLibSD && !KeePassRT) +#if (!KeePassLibSD && !ModernKeePassLib) internal static Form GetTopForm() { FormCollection fc = Application.OpenForms; @@ -173,8 +176,8 @@ namespace ModernKeePassLib.Utility } #endif -#if !KeePassUAP - internal static DialogResult SafeShowMessageBox(string strText, string strTitle, +#if !ModernKeePassLib + internal static DialogResult SafeShowMessageBox(string strText, string strTitle, MessageBoxButtons mb, MessageBoxIcon mi, MessageBoxDefaultButton mdb) { #if (KeePassLibSD || KeePassRT) @@ -405,7 +408,7 @@ namespace ModernKeePassLib.Utility } #endif // !KeePassUAP - internal static string GetLoadWarningMessage(string strFilePath, + internal static string GetLoadWarningMessage(string strFilePath, Exception ex, bool bFullException) { string str = string.Empty; diff --git a/ModernKeePassLib/Utility/MonoWorkarounds.cs b/ModernKeePassLib/Utility/MonoWorkarounds.cs index 2b64552..97b4dfd 100644 --- a/ModernKeePassLib/Utility/MonoWorkarounds.cs +++ b/ModernKeePassLib/Utility/MonoWorkarounds.cs @@ -104,6 +104,9 @@ namespace ModernKeePassLib.Utility // 1690: // Removing items from a list view doesn't work properly. // https://sourceforge.net/p/keepass/bugs/1690/ + // 1716: + // 'Always on Top' doesn't work properly on the Cinnamon desktop. + // https://sourceforge.net/p/keepass/bugs/1716/ // 2139: // Shortcut keys are ignored. // https://sourceforge.net/p/keepass/feature-requests/2139/ @@ -560,7 +563,7 @@ namespace ModernKeePassLib.Utility { // Mono's WriteRecentlyUsedFiles method also loads the // XML file using XmlDocument - XmlDocument xd = new XmlDocument(); + XmlDocument xd = XmlUtilEx.CreateXmlDocument(); xd.Load(strFile); } catch(Exception) // The XML file is invalid diff --git a/ModernKeePassLib/Utility/StrUtil.cs b/ModernKeePassLib/Utility/StrUtil.cs index fb60461..b40dca0 100644 --- a/ModernKeePassLib/Utility/StrUtil.cs +++ b/ModernKeePassLib/Utility/StrUtil.cs @@ -227,7 +227,7 @@ namespace ModernKeePassLib.Utility "Unicode (UTF-8)", StrUtil.Utf8, 1, new byte[] { 0xEF, 0xBB, 0xBF })); #else #if !KeePassLibSD - Encoding.Default.EncodingName, + Encoding.Default.EncodingName, #else Encoding.Default.WebName, #endif @@ -301,6 +301,28 @@ namespace ModernKeePassLib.Utility return ("\\u" + sh.ToString(NumberFormatInfo.InvariantInfo) + "?"); } + public static string RtfFix(string strRtf) + { + if(strRtf == null) { Debug.Assert(false); return string.Empty; } + + string str = strRtf; + + // Workaround for .NET bug: the Rtf property of a RichTextBox + // can return an RTF text starting with "{\\urtf", but + // setting such an RTF text throws an exception (the setter + // checks for the RTF text to start with "{\\rtf"); + // https://sourceforge.net/p/keepass/discussion/329221/thread/7788872f/ + // https://www.microsoft.com/en-us/download/details.aspx?id=10725 + // https://msdn.microsoft.com/en-us/library/windows/desktop/bb774284.aspx + // https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/RichTextBox.cs + const string p = "{\\urtf"; // Typically "{\\urtf1\\ansi\\ansicpg65001" + if(str.StartsWith(p) && (str.Length > p.Length) && + char.IsDigit(str[p.Length])) + str = str.Remove(2, 1); // Remove the 'u' + + return str; + } + /// /// Convert a string to a HTML sequence representing that string. /// @@ -324,7 +346,7 @@ namespace ModernKeePassLib.Utility if(bNbsp) str = str.Replace(" ", @" "); // Before
str = NormalizeNewLines(str, false); - str = str.Replace("\n", @"
" + Environment.NewLine); + str = str.Replace("\n", @"
" + MessageService.NewLine); return str; } @@ -497,40 +519,40 @@ namespace ModernKeePassLib.Utility { string strText = string.Empty; - if(excp.Message != null) - strText += excp.Message + Environment.NewLine; + if(!string.IsNullOrEmpty(excp.Message)) + strText += excp.Message + MessageService.NewLine; #if !KeePassLibSD - if(excp.Source != null) - strText += excp.Source + Environment.NewLine; + if(!string.IsNullOrEmpty(excp.Source)) + strText += excp.Source + MessageService.NewLine; #endif - if(excp.StackTrace != null) - strText += excp.StackTrace + Environment.NewLine; + if(!string.IsNullOrEmpty(excp.StackTrace)) + strText += excp.StackTrace + MessageService.NewLine; #if !KeePassLibSD #if !ModernKeePassLib && !KeePassRT if(excp.TargetSite != null) - strText += excp.TargetSite.ToString() + Environment.NewLine; + strText += excp.TargetSite.ToString() + MessageService.NewLine; #endif if(excp.Data != null) { - strText += Environment.NewLine; + strText += MessageService.NewLine; foreach(DictionaryEntry de in excp.Data) strText += @"'" + de.Key + @"' -> '" + de.Value + @"'" + - Environment.NewLine; + MessageService.NewLine; } #endif if(excp.InnerException != null) { - strText += Environment.NewLine + "Inner:" + Environment.NewLine; - if(excp.InnerException.Message != null) - strText += excp.InnerException.Message + Environment.NewLine; + strText += MessageService.NewLine + "Inner:" + MessageService.NewLine; + if(!string.IsNullOrEmpty(excp.InnerException.Message)) + strText += excp.InnerException.Message + MessageService.NewLine; #if !KeePassLibSD - if(excp.InnerException.Source != null) - strText += excp.InnerException.Source + Environment.NewLine; + if(!string.IsNullOrEmpty(excp.InnerException.Source)) + strText += excp.InnerException.Source + MessageService.NewLine; #endif - if(excp.InnerException.StackTrace != null) - strText += excp.InnerException.StackTrace + Environment.NewLine; + if(!string.IsNullOrEmpty(excp.InnerException.StackTrace)) + strText += excp.InnerException.StackTrace + MessageService.NewLine; #if !KeePassLibSD #if !ModernKeePassLib && !KeePassRT if(excp.InnerException.TargetSite != null) @@ -539,10 +561,10 @@ namespace ModernKeePassLib.Utility if(excp.InnerException.Data != null) { - strText += Environment.NewLine; + strText += MessageService.NewLine; foreach(DictionaryEntry de in excp.InnerException.Data) strText += @"'" + de.Key + @"' -> '" + de.Value + @"'" + - Environment.NewLine; + MessageService.NewLine; } #endif } @@ -1140,32 +1162,58 @@ namespace ModernKeePassLib.Utility return str; } - private static char[] m_vNewLineChars = null; public static void NormalizeNewLines(ProtectedStringDictionary dict, bool bWindows) { if(dict == null) { Debug.Assert(false); return; } - if(m_vNewLineChars == null) - m_vNewLineChars = new char[]{ '\r', '\n' }; - - List vKeys = dict.GetKeys(); - foreach(string strKey in vKeys) + List lKeys = dict.GetKeys(); + foreach(string strKey in lKeys) { ProtectedString ps = dict.Get(strKey); if(ps == null) { Debug.Assert(false); continue; } - string strValue = ps.ReadString(); - if(strValue.IndexOfAny(m_vNewLineChars) < 0) continue; - - dict.Set(strKey, new ProtectedString(ps.IsProtected, - NormalizeNewLines(strValue, bWindows))); + char[] v = ps.ReadChars(); + if(!IsNewLineNormalized(v, bWindows)) + dict.Set(strKey, new ProtectedString(ps.IsProtected, + NormalizeNewLines(ps.ReadString(), bWindows))); + MemUtil.ZeroArray(v); } } + internal static bool IsNewLineNormalized(char[] v, bool bWindows) + { + if(v == null) { Debug.Assert(false); return true; } + + if(bWindows) + { + int iFreeCr = -2; // Must be < -1 (for test "!= (i - 1)") + + for(int i = 0; i < v.Length; ++i) + { + char ch = v[i]; + + if(ch == '\r') + { + if(iFreeCr >= 0) return false; + iFreeCr = i; + } + else if(ch == '\n') + { + if(iFreeCr != (i - 1)) return false; + iFreeCr = -2; // Consume \r + } + } + + return (iFreeCr < 0); // Ensure no \r at end + } + + return (Array.IndexOf(v, '\r') < 0); + } + public static string GetNewLineSeq(string str) { - if(str == null) { Debug.Assert(false); return Environment.NewLine; } + if(str == null) { Debug.Assert(false); return MessageService.NewLine; } int n = str.Length, nLf = 0, nCr = 0, nCrLf = 0; char chLast = char.MinValue; @@ -1187,7 +1235,7 @@ namespace ModernKeePassLib.Utility nLf -= nCrLf; int nMax = Math.Max(nCrLf, Math.Max(nCr, nLf)); - if(nMax == 0) return Environment.NewLine; + if(nMax == 0) return MessageService.NewLine; if(nCrLf == nMax) return "\r\n"; return ((nLf == nMax) ? "\n" : "\r"); @@ -1319,7 +1367,7 @@ namespace ModernKeePassLib.Utility try { byte[] pbPlain = StrUtil.Utf8.GetBytes(strPlainText); - byte[] pbEnc = ProtectedData.Protect(pbPlain, m_pbOptEnt, + byte[] pbEnc = CryptoUtil.ProtectData(pbPlain, m_pbOptEnt, DataProtectionScope.CurrentUser); #if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT) @@ -1340,7 +1388,7 @@ namespace ModernKeePassLib.Utility try { byte[] pbEnc = Convert.FromBase64String(strCipherText); - byte[] pbPlain = ProtectedData.Unprotect(pbEnc, m_pbOptEnt, + byte[] pbPlain = CryptoUtil.UnprotectData(pbEnc, m_pbOptEnt, DataProtectionScope.CurrentUser); return StrUtil.Utf8.GetString(pbPlain, 0, pbPlain.Length); diff --git a/ModernKeePassLib/Utility/TypeOverridePool.cs b/ModernKeePassLib/Utility/TypeOverridePool.cs new file mode 100644 index 0000000..454d5cb --- /dev/null +++ b/ModernKeePassLib/Utility/TypeOverridePool.cs @@ -0,0 +1,65 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2018 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; + +using ModernKeePassLib.Delegates; + +namespace KeePassLib.Utility +{ + public static class TypeOverridePool + { + private static Dictionary> g_d = + new Dictionary>(); + + public static void Register(Type t, GFunc f) + { + if(t == null) throw new ArgumentNullException("t"); + if(f == null) throw new ArgumentNullException("f"); + + g_d[t] = f; + } + + public static void Unregister(Type t) + { + if(t == null) throw new ArgumentNullException("t"); + + g_d.Remove(t); + } + + public static bool IsRegistered(Type t) + { + if(t == null) throw new ArgumentNullException("t"); + + return g_d.ContainsKey(t); + } + + public static T CreateInstance() + where T : new() + { + GFunc f; + if(g_d.TryGetValue(typeof(T), out f)) + return (T)(f()); + + return new T(); + } + } +} diff --git a/ModernKeePassLib/Utility/UrlUtil.cs b/ModernKeePassLib/Utility/UrlUtil.cs index 6d62557..4eeff07 100644 --- a/ModernKeePassLib/Utility/UrlUtil.cs +++ b/ModernKeePassLib/Utility/UrlUtil.cs @@ -299,7 +299,7 @@ namespace ModernKeePassLib.Utility else // Unhide { fa &= ~FileAttributes.Hidden; - if((long)fa == 0) fa |= FileAttributes.Normal; + if((long)fa == 0) fa = FileAttributes.Normal; } File.SetAttributes(strFile, fa); @@ -635,13 +635,12 @@ namespace ModernKeePassLib.Utility return false; } -#if !ModernKeePassLib public static string GetTempPath() { string strDir; if(NativeLib.IsUnix()) strDir = NativeMethods.GetUserRuntimeDir(); -#if KeePassRT +#if ModernKeePassLib else strDir = Windows.Storage.ApplicationData.Current.TemporaryFolder.Path; #else else strDir = Path.GetTempPath(); @@ -655,7 +654,6 @@ namespace ModernKeePassLib.Utility return strDir; } -#endif #if !ModernKeePassLib && !KeePassLibSD // Structurally mostly equivalent to UrlUtil.GetFileInfos @@ -779,5 +777,17 @@ namespace ModernKeePassLib.Utility return str; } + + public static char GetDriveLetter(string strPath) + { + if(strPath == null) throw new ArgumentNullException("strPath"); + + Debug.Assert(default(char) == '\0'); + if(strPath.Length < 3) return '\0'; + if((strPath[1] != ':') || (strPath[2] != '\\')) return '\0'; + + char ch = char.ToUpperInvariant(strPath[0]); + return (((ch >= 'A') && (ch <= 'Z')) ? ch : '\0'); + } } } diff --git a/ModernKeePassLib/Utility/XmlUtilEx.cs b/ModernKeePassLib/Utility/XmlUtilEx.cs new file mode 100644 index 0000000..cafb410 --- /dev/null +++ b/ModernKeePassLib/Utility/XmlUtilEx.cs @@ -0,0 +1,127 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2018 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Xml; +using System.Xml.Serialization; +using Windows.Data.Xml.Dom; + +namespace ModernKeePassLib.Utility +{ + public static class XmlUtilEx + { + public static XmlDocument CreateXmlDocument() + { + XmlDocument d = new XmlDocument(); + + // .NET 4.5.2 and newer do not resolve external XML resources + // by default; for older .NET versions, we explicitly + // prevent resolving +#if !ModernKeePassLib + d.XmlResolver = null; // Default in old .NET: XmlUrlResolver object +#endif + + return d; + } + + public static XmlReaderSettings CreateXmlReaderSettings() + { + XmlReaderSettings xrs = new XmlReaderSettings(); + + xrs.CloseInput = false; + xrs.IgnoreComments = true; + xrs.IgnoreProcessingInstructions = true; + xrs.IgnoreWhitespace = true; + +#if KeePassUAP || ModernKeePassLib + xrs.DtdProcessing = DtdProcessing.Prohibit; +#else + // Also see PrepMonoDev.sh script + xrs.ProhibitDtd = true; // Obsolete in .NET 4, but still there + // xrs.DtdProcessing = DtdProcessing.Prohibit; // .NET 4 only + + xrs.ValidationType = ValidationType.None; + xrs.XmlResolver = null; +#endif + + return xrs; + } + + public static XmlReader CreateXmlReader(Stream s) + { + if(s == null) { Debug.Assert(false); throw new ArgumentNullException("s"); } + + return XmlReader.Create(s, CreateXmlReaderSettings()); + } + + public static XmlWriterSettings CreateXmlWriterSettings(bool isVersionGreaterThan4 = false) + { + XmlWriterSettings xws = new XmlWriterSettings(); + + xws.CloseOutput = isVersionGreaterThan4; + xws.Encoding = StrUtil.Utf8; + xws.Indent = true; + xws.IndentChars = "\t"; + xws.NewLineOnAttributes = false; +#if ModernKeePassLib + // This is needed for Argon2Kdf write + xws.Async = true; +#endif + + return xws; + } + + public static XmlWriter CreateXmlWriter(Stream s, bool isVersionGreaterThan4 = false) + { + if(s == null) { Debug.Assert(false); throw new ArgumentNullException("s"); } + + return XmlWriter.Create(s, CreateXmlWriterSettings(isVersionGreaterThan4)); + } + + public static void Serialize(Stream s, T t) + { + if(s == null) { Debug.Assert(false); throw new ArgumentNullException("s"); } + + XmlSerializer xs = new XmlSerializer(typeof(T)); + using(XmlWriter xw = CreateXmlWriter(s)) + { + xs.Serialize(xw, t); + } + } + + public static T Deserialize(Stream s) + { + if(s == null) { Debug.Assert(false); throw new ArgumentNullException("s"); } + + XmlSerializer xs = new XmlSerializer(typeof(T)); + + T t = default(T); + using(XmlReader xr = CreateXmlReader(s)) + { + t = (T)xs.Deserialize(xr); + } + + return t; + } + } +}