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