From 3585d4f61f00b1f28e853de2366cb67e2758971d Mon Sep 17 00:00:00 2001 From: Philipp Crocoll Date: Tue, 30 Aug 2016 04:04:23 +0200 Subject: [PATCH 1/3] Keepass original source code version 2.30 --- .../Native/NativeMethods.Unix.cs | 115 +++++ .../Utility/MonoWorkarounds.cs | 403 ++++++++++++++++++ 2 files changed, 518 insertions(+) create mode 100644 src/KeePassLib2Android/Native/NativeMethods.Unix.cs create mode 100644 src/KeePassLib2Android/Utility/MonoWorkarounds.cs diff --git a/src/KeePassLib2Android/Native/NativeMethods.Unix.cs b/src/KeePassLib2Android/Native/NativeMethods.Unix.cs new file mode 100644 index 00000000..a03b3e29 --- /dev/null +++ b/src/KeePassLib2Android/Native/NativeMethods.Unix.cs @@ -0,0 +1,115 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 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.Runtime.InteropServices; +using System.Text; + +#if !KeePassUAP +using System.Windows.Forms; +#endif + +namespace KeePassLib.Native +{ + internal static partial class NativeMethods + { +#if (!KeePassLibSD && !KeePassUAP) + [StructLayout(LayoutKind.Sequential)] + private struct XClassHint + { + public IntPtr res_name; + public IntPtr res_class; + } + + [DllImport("libX11")] + private static extern int XSetClassHint(IntPtr display, IntPtr window, IntPtr class_hints); + + private static Type m_tXplatUIX11 = null; + private static Type GetXplatUIX11Type(bool bThrowOnError) + { + if(m_tXplatUIX11 == null) + { + // CheckState is in System.Windows.Forms + string strTypeCS = typeof(CheckState).AssemblyQualifiedName; + string strTypeX11 = strTypeCS.Replace("CheckState", "XplatUIX11"); + m_tXplatUIX11 = Type.GetType(strTypeX11, bThrowOnError, true); + } + + return m_tXplatUIX11; + } + + private static Type m_tHwnd = null; + private static Type GetHwndType(bool bThrowOnError) + { + if(m_tHwnd == null) + { + // CheckState is in System.Windows.Forms + string strTypeCS = typeof(CheckState).AssemblyQualifiedName; + string strTypeHwnd = strTypeCS.Replace("CheckState", "Hwnd"); + m_tHwnd = Type.GetType(strTypeHwnd, bThrowOnError, true); + } + + return m_tHwnd; + } + + internal static void SetWmClass(Form f, string strName, string strClass) + { + if(f == null) { Debug.Assert(false); return; } + + // The following crashes under Mac OS X (SIGSEGV in native code, + // not just an exception), thus skip it when we're on Mac OS X; + // https://sourceforge.net/projects/keepass/forums/forum/329221/topic/5860588 + if(NativeLib.GetPlatformID() == PlatformID.MacOSX) return; + + try + { + Type tXplatUIX11 = GetXplatUIX11Type(true); + FieldInfo fiDisplayHandle = tXplatUIX11.GetField("DisplayHandle", + BindingFlags.NonPublic | BindingFlags.Static); + IntPtr hDisplay = (IntPtr)fiDisplayHandle.GetValue(null); + + Type tHwnd = GetHwndType(true); + MethodInfo miObjectFromHandle = tHwnd.GetMethod("ObjectFromHandle", + BindingFlags.Public | BindingFlags.Static); + object oHwnd = miObjectFromHandle.Invoke(null, new object[] { f.Handle }); + + FieldInfo fiWholeWindow = tHwnd.GetField("whole_window", + BindingFlags.NonPublic | BindingFlags.Instance); + IntPtr hWindow = (IntPtr)fiWholeWindow.GetValue(oHwnd); + + XClassHint xch = new XClassHint(); + xch.res_name = Marshal.StringToCoTaskMemAnsi(strName ?? string.Empty); + xch.res_class = Marshal.StringToCoTaskMemAnsi(strClass ?? string.Empty); + IntPtr pXch = Marshal.AllocCoTaskMem(Marshal.SizeOf(xch)); + Marshal.StructureToPtr(xch, pXch, false); + + XSetClassHint(hDisplay, hWindow, pXch); + + Marshal.FreeCoTaskMem(pXch); + Marshal.FreeCoTaskMem(xch.res_name); + Marshal.FreeCoTaskMem(xch.res_class); + } + catch(Exception) { Debug.Assert(false); } + } +#endif + } +} diff --git a/src/KeePassLib2Android/Utility/MonoWorkarounds.cs b/src/KeePassLib2Android/Utility/MonoWorkarounds.cs new file mode 100644 index 00000000..064791c9 --- /dev/null +++ b/src/KeePassLib2Android/Utility/MonoWorkarounds.cs @@ -0,0 +1,403 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 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.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Text; +using System.Xml; + +#if !KeePassUAP +using System.Windows.Forms; +#endif + +using KeePassLib.Native; + +namespace KeePassLib.Utility +{ + public static class MonoWorkarounds + { + private static Dictionary m_dForceReq = new Dictionary(); + + private static bool? m_bReq = null; + public static bool IsRequired() + { + if(!m_bReq.HasValue) m_bReq = NativeLib.IsUnix(); + return m_bReq.Value; + } + + // 1219: + // Mono prepends byte order mark (BOM) to StdIn. + // https://sourceforge.net/p/keepass/bugs/1219/ + // 1245: + // Key events not raised while Alt is down, and nav keys out of order. + // https://sourceforge.net/p/keepass/bugs/1245/ + // 1254: + // NumericUpDown bug: text is drawn below up/down buttons. + // https://sourceforge.net/p/keepass/bugs/1254/ + // 1354: + // Finalizer of NotifyIcon throws on Unity. + // https://sourceforge.net/p/keepass/bugs/1354/ + // 1358: + // FileDialog crashes when ~/.recently-used is invalid. + // https://sourceforge.net/p/keepass/bugs/1358/ + // 1366: + // Drawing bug when scrolling a RichTextBox. + // https://sourceforge.net/p/keepass/bugs/1366/ + // 1378: + // Mono doesn't implement Microsoft.Win32.SystemEvents events. + // https://sourceforge.net/p/keepass/bugs/1378/ + // https://github.com/mono/mono/blob/master/mcs/class/System/Microsoft.Win32/SystemEvents.cs + // 1418: + // Minimizing a form while loading it doesn't work. + // https://sourceforge.net/p/keepass/bugs/1418/ + // 5795: + // Text in input field is incomplete. + // https://bugzilla.xamarin.com/show_bug.cgi?id=5795 + // https://sourceforge.net/p/keepass/discussion/329220/thread/d23dc88b/ + // 10163: + // WebRequest GetResponse call missing, breaks WebDAV due to no PUT. + // https://bugzilla.xamarin.com/show_bug.cgi?id=10163 + // https://sourceforge.net/p/keepass/bugs/1117/ + // https://sourceforge.net/p/keepass/discussion/329221/thread/9422258c/ + // https://github.com/mono/mono/commit/8e67b8c2fc7cb66bff7816ebf7c1039fb8cfc43b + // https://bugzilla.xamarin.com/show_bug.cgi?id=1512 + // https://sourceforge.net/p/keepass/patches/89/ + // 12525: + // PictureBox not rendered when bitmap height >= control height. + // https://bugzilla.xamarin.com/show_bug.cgi?id=12525 + // https://sourceforge.net/p/keepass/discussion/329220/thread/54f61e9a/ + // 586901: + // RichTextBox doesn't handle Unicode string correctly. + // https://bugzilla.novell.com/show_bug.cgi?id=586901 + // 620618: + // ListView column headers not drawn. + // https://bugzilla.novell.com/show_bug.cgi?id=620618 + // 649266: + // Calling Control.Hide doesn't remove the application from taskbar. + // https://bugzilla.novell.com/show_bug.cgi?id=649266 + // 686017: + // Minimum sizes must be enforced. + // http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=686017 + // 801414: + // Mono recreates the main window incorrectly. + // https://bugs.launchpad.net/ubuntu/+source/keepass2/+bug/801414 + // 891029: + // Increase tab control height, otherwise Mono throws exceptions. + // https://sourceforge.net/projects/keepass/forums/forum/329221/topic/4519750 + // https://bugs.launchpad.net/ubuntu/+source/keepass2/+bug/891029 + // 836428016: + // ListView group header selection unsupported. + // https://sourceforge.net/p/keepass/discussion/329221/thread/31dae0f0/ + // 3574233558: + // Problems with minimizing windows, no content rendered. + // https://sourceforge.net/p/keepass/discussion/329220/thread/d50a79d6/ + public static bool IsRequired(uint uBugID) + { + if(!MonoWorkarounds.IsRequired()) return false; + + bool bForce; + if(m_dForceReq.TryGetValue(uBugID, out bForce)) return bForce; + + ulong v = NativeLib.MonoVersion; + if(v != 0) + { + if(uBugID == 10163) + return (v >= 0x0002000B00000000UL); // >= 2.11 + } + + return true; + } + + internal static void SetEnabled(string strIDs, bool bEnabled) + { + if(string.IsNullOrEmpty(strIDs)) return; + + string[] vIDs = strIDs.Split(new char[] { ',' }); + foreach(string strID in vIDs) + { + if(string.IsNullOrEmpty(strID)) continue; + + uint uID; + if(StrUtil.TryParseUInt(strID.Trim(), out uID)) + m_dForceReq[uID] = bEnabled; + } + } + +#if !KeePassUAP + public static void ApplyTo(Form f) + { + if(!MonoWorkarounds.IsRequired()) return; + if(f == null) { Debug.Assert(false); return; } + +#if !KeePassLibSD + f.HandleCreated += MonoWorkarounds.OnFormHandleCreated; + SetWmClass(f); + + ApplyToControlsRec(f.Controls, f, MonoWorkarounds.ApplyToControl); +#endif + } + + public static void Release(Form f) + { + if(!MonoWorkarounds.IsRequired()) return; + if(f == null) { Debug.Assert(false); return; } + +#if !KeePassLibSD + f.HandleCreated -= MonoWorkarounds.OnFormHandleCreated; + + ApplyToControlsRec(f.Controls, f, MonoWorkarounds.ReleaseControl); +#endif + } + +#if !KeePassLibSD + private delegate void MwaControlHandler(Control c, Form fContext); + + private static void ApplyToControlsRec(Control.ControlCollection cc, + Form fContext, MwaControlHandler fn) + { + if(cc == null) { Debug.Assert(false); return; } + + foreach(Control c in cc) + { + fn(c, fContext); + ApplyToControlsRec(c.Controls, fContext, fn); + } + } + + private static void ApplyToControl(Control c, Form fContext) + { + Button btn = (c as Button); + if(btn != null) ApplyToButton(btn, fContext); + + NumericUpDown nc = (c as NumericUpDown); + if((nc != null) && MonoWorkarounds.IsRequired(1254)) + { + if(nc.TextAlign == HorizontalAlignment.Right) + nc.TextAlign = HorizontalAlignment.Left; + } + } + + private sealed class MwaHandlerInfo + { + private readonly Delegate m_fnOrg; // May be null + public Delegate FunctionOriginal + { + get { return m_fnOrg; } + } + + private readonly Delegate m_fnOvr; + public Delegate FunctionOverride + { + get { return m_fnOvr; } + } + + private readonly DialogResult m_dr; + public DialogResult Result + { + get { return m_dr; } + } + + private readonly Form m_fContext; + public Form FormContext + { + get { return m_fContext; } + } + + public MwaHandlerInfo(Delegate fnOrg, Delegate fnOvr, DialogResult dr, + Form fContext) + { + m_fnOrg = fnOrg; + m_fnOvr = fnOvr; + m_dr = dr; + m_fContext = fContext; + } + } + + private static EventHandlerList GetEventHandlers(Component c, + out object objClickEvent) + { + FieldInfo fi = typeof(Control).GetField("ClickEvent", // Mono + BindingFlags.Static | BindingFlags.NonPublic); + if(fi == null) + fi = typeof(Control).GetField("EventClick", // .NET + BindingFlags.Static | BindingFlags.NonPublic); + if(fi == null) { Debug.Assert(false); objClickEvent = null; return null; } + + objClickEvent = fi.GetValue(null); + if(objClickEvent == null) { Debug.Assert(false); return null; } + + PropertyInfo pi = typeof(Component).GetProperty("Events", + BindingFlags.Instance | BindingFlags.NonPublic); + return (pi.GetValue(c, null) as EventHandlerList); + } + + private static Dictionary m_dictHandlers = + new Dictionary(); + private static void ApplyToButton(Button btn, Form fContext) + { + DialogResult dr = btn.DialogResult; + if(dr == DialogResult.None) return; // No workaround required + + object objClickEvent; + EventHandlerList ehl = GetEventHandlers(btn, out objClickEvent); + if(ehl == null) { Debug.Assert(false); return; } + Delegate fnClick = ehl[objClickEvent]; // May be null + + EventHandler fnOvr = new EventHandler(MonoWorkarounds.OnButtonClick); + m_dictHandlers[btn] = new MwaHandlerInfo(fnClick, fnOvr, dr, fContext); + + btn.DialogResult = DialogResult.None; + if(fnClick != null) ehl.RemoveHandler(objClickEvent, fnClick); + ehl[objClickEvent] = fnOvr; + } + + private static void ReleaseControl(Control c, Form fContext) + { + Button btn = (c as Button); + if(btn != null) ReleaseButton(btn, fContext); + } + + private static void ReleaseButton(Button btn, Form fContext) + { + MwaHandlerInfo hi; + m_dictHandlers.TryGetValue(btn, out hi); + if(hi == null) return; + + object objClickEvent; + EventHandlerList ehl = GetEventHandlers(btn, out objClickEvent); + if(ehl == null) { Debug.Assert(false); return; } + + ehl.RemoveHandler(objClickEvent, hi.FunctionOverride); + if(hi.FunctionOriginal != null) + ehl[objClickEvent] = hi.FunctionOriginal; + + btn.DialogResult = hi.Result; + m_dictHandlers.Remove(btn); + } + + private static void OnButtonClick(object sender, EventArgs e) + { + Button btn = (sender as Button); + if(btn == null) { Debug.Assert(false); return; } + + MwaHandlerInfo hi; + m_dictHandlers.TryGetValue(btn, out hi); + if(hi == null) { Debug.Assert(false); return; } + + Form f = hi.FormContext; + + // Set current dialog result by setting the form's private + // variable; the DialogResult property can't be used, + // because it raises close events + FieldInfo fiRes = typeof(Form).GetField("dialog_result", + BindingFlags.Instance | BindingFlags.NonPublic); + if(fiRes == null) { Debug.Assert(false); return; } + if(f != null) fiRes.SetValue(f, hi.Result); + + if(hi.FunctionOriginal != null) + hi.FunctionOriginal.Method.Invoke(hi.FunctionOriginal.Target, + new object[] { btn, e }); + + // Raise close events, if the click event handler hasn't + // reset the dialog result + if((f != null) && (f.DialogResult == hi.Result)) + f.DialogResult = hi.Result; // Raises close events + } + + private static void SetWmClass(Form f) + { + NativeMethods.SetWmClass(f, PwDefs.UnixName, PwDefs.ResClass); + } + + private static void OnFormHandleCreated(object sender, EventArgs e) + { + Form f = (sender as Form); + if(f == null) { Debug.Assert(false); return; } + + if(!f.IsHandleCreated) return; // Prevent infinite loop + + SetWmClass(f); + } + + /// + /// Set the value of the private shown_raised member + /// variable of a form. + /// + /// Previous shown_raised value. + internal static bool ExchangeFormShownRaised(Form f, bool bNewValue) + { + if(f == null) { Debug.Assert(false); return true; } + + try + { + FieldInfo fi = typeof(Form).GetField("shown_raised", + BindingFlags.Instance | BindingFlags.NonPublic); + if(fi == null) { Debug.Assert(false); return true; } + + bool bPrevious = (bool)fi.GetValue(f); + + fi.SetValue(f, bNewValue); + + return bPrevious; + } + catch(Exception) { Debug.Assert(false); } + + return true; + } +#endif + + /// + /// Ensure that the file ~/.recently-used is valid (in order to + /// prevent Mono's FileDialog from crashing). + /// + internal static void EnsureRecentlyUsedValid() + { + if(!MonoWorkarounds.IsRequired(1358)) return; + + try + { + string strFile = Environment.GetFolderPath( + Environment.SpecialFolder.Personal); + strFile = UrlUtil.EnsureTerminatingSeparator(strFile, false); + strFile += ".recently-used"; + + if(File.Exists(strFile)) + { + try + { + // Mono's WriteRecentlyUsedFiles method also loads the + // XML file using XmlDocument + XmlDocument xd = new XmlDocument(); + xd.Load(strFile); + } + catch(Exception) // The XML file is invalid + { + File.Delete(strFile); + } + } + } + catch(Exception) { Debug.Assert(false); } + } +#endif // !KeePassUAP + } +} From 6d1e28e5026c9f42a01bb4102a47ac48c0f3ff4d Mon Sep 17 00:00:00 2001 From: Philipp Crocoll Date: Tue, 30 Aug 2016 04:09:53 +0200 Subject: [PATCH 2/3] Keepass original source code version 2.34 --- .../Collections/AutoTypeConfig.cs | 40 +- .../Collections/ProtectedBinaryDictionary.cs | 3 +- .../Collections/ProtectedStringDictionary.cs | 11 +- .../Collections/PwObjectList.cs | 74 +- .../Collections/PwObjectPool.cs | 167 ++- .../Collections/StringDictionaryEx.cs | 2 +- .../Cryptography/Cipher/CipherPool.cs | 10 +- .../Cryptography/Cipher/ICipherEngine.cs | 2 +- .../Cryptography/Cipher/Salsa20Cipher.cs | 17 +- .../Cryptography/Cipher/StandardAesEngine.cs | 32 +- .../Cryptography/CryptoRandom.cs | 97 +- .../Cryptography/CryptoRandomStream.cs | 7 +- .../Cryptography/HashingStreamEx.cs | 24 +- .../Cryptography/HmacOtp.cs | 11 +- .../CharSetBasedGenerator.cs | 2 +- .../PasswordGenerator/CustomPwGenerator.cs | 2 +- .../CustomPwGeneratorPool.cs | 6 +- .../PatternBasedGenerator.cs | 13 +- .../PasswordGenerator/PwCharSet.cs | 187 ++-- .../PasswordGenerator/PwGenerator.cs | 2 +- .../PasswordGenerator/PwProfile.cs | 13 +- .../Cryptography/PopularPasswords.cs | 568 ++-------- .../Cryptography/QualityEstimation.cs | 767 ++++++++++++-- .../Cryptography/SelfTest.cs | 221 +++- src/KeePassLib2Android/Delegates/Handlers.cs | 2 +- .../Interfaces/IDeepCloneable.cs | 2 +- .../Interfaces/IStatusLogger.cs | 2 +- .../Interfaces/IStructureItem.cs | 2 +- .../Interfaces/ITimeLogger.cs | 10 +- .../Interfaces/IUIOperations.cs | 2 +- .../Interfaces/IXmlSerializerEx.cs | 2 +- src/KeePassLib2Android/Keys/CompositeKey.cs | 108 +- src/KeePassLib2Android/Keys/IUserKey.cs | 2 +- src/KeePassLib2Android/Keys/KcpCustomKey.cs | 8 +- src/KeePassLib2Android/Keys/KcpKeyFile.cs | 67 +- src/KeePassLib2Android/Keys/KcpPassword.cs | 25 +- src/KeePassLib2Android/Keys/KcpUserAccount.cs | 64 +- src/KeePassLib2Android/Keys/KeyProvider.cs | 2 +- .../Keys/KeyProviderPool.cs | 2 +- src/KeePassLib2Android/Keys/KeyValidator.cs | 2 +- .../Keys/KeyValidatorPool.cs | 2 +- src/KeePassLib2Android/Keys/UserKeyType.cs | 2 +- src/KeePassLib2Android/Native/NativeLib.cs | 280 ++++- .../Native/NativeMethods.cs | 93 +- .../Properties/AssemblyInfo.cs | 8 +- src/KeePassLib2Android/PwCustomIcon.cs | 76 +- src/KeePassLib2Android/PwDatabase.cs | 994 +++++++++++------- src/KeePassLib2Android/PwDefs.cs | 50 +- src/KeePassLib2Android/PwDeletedObject.cs | 2 +- src/KeePassLib2Android/PwEntry.cs | 65 +- src/KeePassLib2Android/PwEnums.cs | 114 +- src/KeePassLib2Android/PwGroup.cs | 216 +++- src/KeePassLib2Android/PwUuid.cs | 128 ++- .../Resources/KLRes.Generated.cs | 93 +- .../Security/ProtectedBinary.cs | 237 ++++- .../Security/ProtectedString.cs | 123 ++- .../Security/XorredBuffer.cs | 2 +- .../Serialization/BinaryReaderEx.cs | 8 +- .../Serialization/FileLock.cs | 22 +- .../Serialization/FileTransactionEx.cs | 76 +- .../Serialization/HashedBlockStream.cs | 23 +- .../Serialization/IOConnection.cs | 579 ++++++++-- .../Serialization/IOConnectionInfo.cs | 91 +- .../Serialization/IocProperties.cs | 192 ++++ .../Serialization/IocPropertyInfo.cs | 99 ++ .../Serialization/IocPropertyInfoPool.cs | 123 +++ .../Serialization/KdbxFile.Read.Streamed.cs | 64 +- .../Serialization/KdbxFile.Read.cs | 33 +- .../Serialization/KdbxFile.Write.cs | 70 +- .../Serialization/KdbxFile.cs | 33 +- .../Serialization/OldFormatException.cs | 2 +- .../Translation/KPControlCustomization.cs | 26 +- .../Translation/KPFormCustomization.cs | 14 +- .../Translation/KPStringTable.cs | 9 +- .../Translation/KPStringTableItem.cs | 2 +- .../Translation/KPTranslation.cs | 73 +- .../Translation/KPTranslationProperties.cs | 2 +- src/KeePassLib2Android/Utility/AppLogEx.cs | 13 +- src/KeePassLib2Android/Utility/GfxUtil.cs | 368 ++++++- src/KeePassLib2Android/Utility/MemUtil.cs | 333 +++++- .../Utility/MessageService.cs | 110 +- src/KeePassLib2Android/Utility/StrUtil.cs | 572 ++++++++-- src/KeePassLib2Android/Utility/TimeUtil.cs | 188 +++- src/KeePassLib2Android/Utility/UrlUtil.cs | 152 ++- 84 files changed, 6370 insertions(+), 1972 deletions(-) create mode 100644 src/KeePassLib2Android/Serialization/IocProperties.cs create mode 100644 src/KeePassLib2Android/Serialization/IocPropertyInfo.cs create mode 100644 src/KeePassLib2Android/Serialization/IocPropertyInfoPool.cs diff --git a/src/KeePassLib2Android/Collections/AutoTypeConfig.cs b/src/KeePassLib2Android/Collections/AutoTypeConfig.cs index 9a58d48c..8df14ce5 100644 --- a/src/KeePassLib2Android/Collections/AutoTypeConfig.cs +++ b/src/KeePassLib2Android/Collections/AutoTypeConfig.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 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 @@ -194,13 +194,6 @@ namespace KeePassLib.Collections return true; } - public void Add(AutoTypeAssociation a) - { - Debug.Assert(a != null); if(a == null) throw new ArgumentNullException("a"); - - m_lWindowAssocs.Add(a); - } - public AutoTypeAssociation GetAt(int iIndex) { if((iIndex < 0) || (iIndex >= m_lWindowAssocs.Count)) @@ -209,6 +202,22 @@ namespace KeePassLib.Collections return m_lWindowAssocs[iIndex]; } + public void Add(AutoTypeAssociation a) + { + if(a == null) { Debug.Assert(false); throw new ArgumentNullException("a"); } + + m_lWindowAssocs.Add(a); + } + + public void Insert(int iIndex, AutoTypeAssociation a) + { + if((iIndex < 0) || (iIndex > m_lWindowAssocs.Count)) + throw new ArgumentOutOfRangeException("iIndex"); + if(a == null) { Debug.Assert(false); throw new ArgumentNullException("a"); } + + m_lWindowAssocs.Insert(iIndex, a); + } + public void RemoveAt(int iIndex) { if((iIndex < 0) || (iIndex >= m_lWindowAssocs.Count)) @@ -216,5 +225,20 @@ namespace KeePassLib.Collections m_lWindowAssocs.RemoveAt(iIndex); } + + // public void Sort() + // { + // m_lWindowAssocs.Sort(AutoTypeConfig.AssocCompareFn); + // } + + // private static int AssocCompareFn(AutoTypeAssociation x, + // AutoTypeAssociation y) + // { + // if(x == null) { Debug.Assert(false); return ((y == null) ? 0 : -1); } + // if(y == null) { Debug.Assert(false); return 1; } + // int cn = x.WindowName.CompareTo(y.WindowName); + // if(cn != 0) return cn; + // return x.Sequence.CompareTo(y.Sequence); + // } } } diff --git a/src/KeePassLib2Android/Collections/ProtectedBinaryDictionary.cs b/src/KeePassLib2Android/Collections/ProtectedBinaryDictionary.cs index c0df6707..415503ec 100644 --- a/src/KeePassLib2Android/Collections/ProtectedBinaryDictionary.cs +++ b/src/KeePassLib2Android/Collections/ProtectedBinaryDictionary.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 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 @@ -25,7 +25,6 @@ using System.Diagnostics; using KeePassLib.Interfaces; using KeePassLib.Security; -using KeePassLib.Utility; #if KeePassLibSD using KeePassLibSD; diff --git a/src/KeePassLib2Android/Collections/ProtectedStringDictionary.cs b/src/KeePassLib2Android/Collections/ProtectedStringDictionary.cs index ef03ff78..ed6a6e6e 100644 --- a/src/KeePassLib2Android/Collections/ProtectedStringDictionary.cs +++ b/src/KeePassLib2Android/Collections/ProtectedStringDictionary.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -283,11 +283,7 @@ namespace KeePassLib.Collections public List GetKeys() { - List v = new List(); - - foreach(string strKey in m_vStrings.Keys) v.Add(strKey); - - return v; + return new List(m_vStrings.Keys); } public void EnableProtection(string strField, bool bProtect) @@ -299,7 +295,8 @@ namespace KeePassLib.Collections { byte[] pbData = ps.ReadUtf8(); Set(strField, new ProtectedString(bProtect, pbData)); - MemUtil.ZeroByteArray(pbData); + + if(bProtect) MemUtil.ZeroByteArray(pbData); } } } diff --git a/src/KeePassLib2Android/Collections/PwObjectList.cs b/src/KeePassLib2Android/Collections/PwObjectList.cs index afd6fcd8..d247bc0b 100644 --- a/src/KeePassLib2Android/Collections/PwObjectList.cs +++ b/src/KeePassLib2Android/Collections/PwObjectList.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 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 @@ -133,6 +133,14 @@ namespace KeePassLib.Collections } } + public void Insert(uint uIndex, T pwObject) + { + Debug.Assert(pwObject != null); + if(pwObject == null) throw new ArgumentNullException("pwObject"); + + m_vObjects.Insert((int)uIndex, pwObject); + } + /// /// Get an object of the list. /// @@ -225,7 +233,7 @@ namespace KeePassLib.Collections if(nCount <= 1) return; int nIndex = m_vObjects.IndexOf(tObject); - Debug.Assert(nIndex >= 0); + if(nIndex < 0) { Debug.Assert(false); return; } if(bUp && (nIndex > 0)) // No assert for top item { @@ -241,6 +249,68 @@ namespace KeePassLib.Collections } } + public void MoveOne(T[] vObjects, bool bUp) + { + Debug.Assert(vObjects != null); + if(vObjects == null) throw new ArgumentNullException("vObjects"); + + List lIndices = new List(); + foreach(T t in vObjects) + { + if(t == null) { Debug.Assert(false); continue; } + + int p = IndexOf(t); + if(p >= 0) lIndices.Add(p); + else { Debug.Assert(false); } + } + + MoveOne(lIndices.ToArray(), bUp); + } + + public void MoveOne(int[] vIndices, bool bUp) + { + Debug.Assert(vIndices != null); + if(vIndices == null) throw new ArgumentNullException("vIndices"); + + int n = m_vObjects.Count; + if(n <= 1) return; // No moving possible + + int m = vIndices.Length; + if(m == 0) return; // Nothing to move + + int[] v = new int[m]; + Array.Copy(vIndices, v, m); + Array.Sort(v); + + if((bUp && (v[0] <= 0)) || (!bUp && (v[m - 1] >= (n - 1)))) + return; // Moving as a block is not possible + + int iStart = (bUp ? 0 : (m - 1)); + int iExcl = (bUp ? m : -1); + int iStep = (bUp ? 1 : -1); + + for(int i = iStart; i != iExcl; i += iStep) + { + int p = v[i]; + if((p < 0) || (p >= n)) { Debug.Assert(false); continue; } + + T t = m_vObjects[p]; + + if(bUp) + { + Debug.Assert(p > 0); + m_vObjects.RemoveAt(p); + m_vObjects.Insert(p - 1, t); + } + else // Down + { + Debug.Assert(p < (n - 1)); + m_vObjects.RemoveAt(p); + m_vObjects.Insert(p + 1, t); + } + } + } + /// /// Move some of the objects in this list to the top/bottom. /// diff --git a/src/KeePassLib2Android/Collections/PwObjectPool.cs b/src/KeePassLib2Android/Collections/PwObjectPool.cs index 90c980b8..3b390484 100644 --- a/src/KeePassLib2Android/Collections/PwObjectPool.cs +++ b/src/KeePassLib2Android/Collections/PwObjectPool.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,6 +18,7 @@ */ using System; +using System.Collections; using System.Collections.Generic; using System.Text; using System.Diagnostics; @@ -33,8 +34,8 @@ namespace KeePassLib.Collections { public sealed class PwObjectPool { - private SortedDictionary m_dict = - new SortedDictionary(); + private SortedDictionary m_dict = + new SortedDictionary(); public static PwObjectPool FromGroupRecursive(PwGroup pgRoot, bool bEntries) { @@ -42,16 +43,16 @@ namespace KeePassLib.Collections PwObjectPool p = new PwObjectPool(); - if(!bEntries) p.m_dict[new PwUuidComparable(pgRoot.Uuid)] = pgRoot; + if(!bEntries) p.m_dict[pgRoot.Uuid] = pgRoot; GroupHandler gh = delegate(PwGroup pg) { - p.m_dict[new PwUuidComparable(pg.Uuid)] = pg; + p.m_dict[pg.Uuid] = pg; return true; }; EntryHandler eh = delegate(PwEntry pe) { - p.m_dict[new PwUuidComparable(pe.Uuid)] = pe; + p.m_dict[pe.Uuid] = pe; return true; }; @@ -63,13 +64,13 @@ namespace KeePassLib.Collections public IStructureItem Get(PwUuid pwUuid) { IStructureItem pItem; - m_dict.TryGetValue(new PwUuidComparable(pwUuid), out pItem); + m_dict.TryGetValue(pwUuid, out pItem); return pItem; } public bool ContainsOnlyType(Type t) { - foreach(KeyValuePair kvp in m_dict) + foreach(KeyValuePair kvp in m_dict) { if(kvp.Value.GetType() != t) return false; } @@ -77,4 +78,154 @@ namespace KeePassLib.Collections return true; } } + + internal sealed class PwObjectPoolEx + { + private Dictionary m_dUuidToId = + new Dictionary(); + private Dictionary m_dIdToItem = + new Dictionary(); + + private PwObjectPoolEx() + { + } + + public static PwObjectPoolEx FromGroup(PwGroup pg) + { + PwObjectPoolEx p = new PwObjectPoolEx(); + + if(pg == null) { Debug.Assert(false); return p; } + + ulong uFreeId = 2; // 0 = "not found", 1 is a hole + + p.m_dUuidToId[pg.Uuid] = uFreeId; + p.m_dIdToItem[uFreeId] = pg; + uFreeId += 2; // Make hole + + p.AddGroupRec(pg, ref uFreeId); + return p; + } + + private void AddGroupRec(PwGroup pg, ref ulong uFreeId) + { + if(pg == null) { Debug.Assert(false); return; } + + ulong uId = uFreeId; + + // Consecutive entries must have consecutive IDs + foreach(PwEntry pe in pg.Entries) + { + Debug.Assert(!m_dUuidToId.ContainsKey(pe.Uuid)); + Debug.Assert(!m_dIdToItem.ContainsValue(pe)); + + m_dUuidToId[pe.Uuid] = uId; + m_dIdToItem[uId] = pe; + ++uId; + } + ++uId; // Make hole + + // Consecutive groups must have consecutive IDs + foreach(PwGroup pgSub in pg.Groups) + { + Debug.Assert(!m_dUuidToId.ContainsKey(pgSub.Uuid)); + Debug.Assert(!m_dIdToItem.ContainsValue(pgSub)); + + m_dUuidToId[pgSub.Uuid] = uId; + m_dIdToItem[uId] = pgSub; + ++uId; + } + ++uId; // Make hole + + foreach(PwGroup pgSub in pg.Groups) + { + AddGroupRec(pgSub, ref uId); + } + + uFreeId = uId; + } + + public ulong GetIdByUuid(PwUuid pwUuid) + { + if(pwUuid == null) { Debug.Assert(false); return 0; } + + ulong uId; + m_dUuidToId.TryGetValue(pwUuid, out uId); + return uId; + } + + public IStructureItem GetItemByUuid(PwUuid pwUuid) + { + if(pwUuid == null) { Debug.Assert(false); return null; } + + ulong uId; + if(!m_dUuidToId.TryGetValue(pwUuid, out uId)) return null; + Debug.Assert(uId != 0); + + return GetItemById(uId); + } + + public IStructureItem GetItemById(ulong uId) + { + IStructureItem p; + m_dIdToItem.TryGetValue(uId, out p); + return p; + } + } + + internal sealed class PwObjectBlock : IEnumerable + where T : class, ITimeLogger, IStructureItem, IDeepCloneable + { + private List m_l = new List(); + + public T PrimaryItem + { + get { return ((m_l.Count > 0) ? m_l[0] : null); } + } + + private DateTime m_dtLocationChanged = DateTime.MinValue; + public DateTime LocationChanged + { + get { return m_dtLocationChanged; } + } + + private PwObjectPoolEx m_poolAssoc = null; + public PwObjectPoolEx PoolAssoc + { + get { return m_poolAssoc; } + } + + public PwObjectBlock() + { + } + +#if DEBUG + public override string ToString() + { + return ("PwObjectBlock, Count = " + m_l.Count.ToString()); + } +#endif + + IEnumerator IEnumerable.GetEnumerator() + { + return m_l.GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + return m_l.GetEnumerator(); + } + + public void Add(T t, DateTime dtLoc, PwObjectPoolEx pool) + { + if(t == null) { Debug.Assert(false); return; } + + m_l.Add(t); + + if(dtLoc > m_dtLocationChanged) + { + m_dtLocationChanged = dtLoc; + m_poolAssoc = pool; + } + } + } } diff --git a/src/KeePassLib2Android/Collections/StringDictionaryEx.cs b/src/KeePassLib2Android/Collections/StringDictionaryEx.cs index 26a494f4..2c55b306 100644 --- a/src/KeePassLib2Android/Collections/StringDictionaryEx.cs +++ b/src/KeePassLib2Android/Collections/StringDictionaryEx.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/KeePassLib2Android/Cryptography/Cipher/CipherPool.cs b/src/KeePassLib2Android/Cryptography/Cipher/CipherPool.cs index 0367151c..c7668ac7 100644 --- a/src/KeePassLib2Android/Cryptography/Cipher/CipherPool.cs +++ b/src/KeePassLib2Android/Cryptography/Cipher/CipherPool.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,8 +22,6 @@ using System.Collections.Generic; using System.Text; using System.IO; using System.Diagnostics; -using System.Security; -using System.Security.Cryptography; namespace KeePassLib.Cryptography.Cipher { @@ -70,7 +68,7 @@ namespace KeePassLib.Cryptography.Cipher // Return if a cipher with that ID is registered already. for(int i = 0; i < m_vCiphers.Count; ++i) - if(m_vCiphers[i].CipherUuid.EqualsValue(csEngine.CipherUuid)) + if(m_vCiphers[i].CipherUuid.Equals(csEngine.CipherUuid)) return; m_vCiphers.Add(csEngine); @@ -86,7 +84,7 @@ namespace KeePassLib.Cryptography.Cipher { foreach(ICipherEngine iEngine in m_vCiphers) { - if(iEngine.CipherUuid.EqualsValue(uuidCipher)) + if(iEngine.CipherUuid.Equals(uuidCipher)) return iEngine; } @@ -104,7 +102,7 @@ namespace KeePassLib.Cryptography.Cipher { for(int i = 0; i < m_vCiphers.Count; ++i) { - if(m_vCiphers[i].CipherUuid.EqualsValue(uuidCipher)) + if(m_vCiphers[i].CipherUuid.Equals(uuidCipher)) return i; } diff --git a/src/KeePassLib2Android/Cryptography/Cipher/ICipherEngine.cs b/src/KeePassLib2Android/Cryptography/Cipher/ICipherEngine.cs index d9dbad1b..734368a0 100644 --- a/src/KeePassLib2Android/Cryptography/Cipher/ICipherEngine.cs +++ b/src/KeePassLib2Android/Cryptography/Cipher/ICipherEngine.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/KeePassLib2Android/Cryptography/Cipher/Salsa20Cipher.cs b/src/KeePassLib2Android/Cryptography/Cipher/Salsa20Cipher.cs index bcd7c4d8..534adfa2 100644 --- a/src/KeePassLib2Android/Cryptography/Cipher/Salsa20Cipher.cs +++ b/src/KeePassLib2Android/Cryptography/Cipher/Salsa20Cipher.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 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 @@ -26,7 +26,7 @@ using KeePassLib.Utility; namespace KeePassLib.Cryptography.Cipher { - public sealed class Salsa20Cipher + public sealed class Salsa20Cipher : IDisposable { private uint[] m_state = new uint[16]; private uint[] m_x = new uint[16]; // Working buffer @@ -34,7 +34,7 @@ namespace KeePassLib.Cryptography.Cipher private byte[] m_output = new byte[64]; private int m_outputPos = 64; - private static readonly uint[] m_sigma = new uint[4]{ + private static readonly uint[] m_sigma = new uint[4] { 0x61707865, 0x3320646E, 0x79622D32, 0x6B206574 }; @@ -45,6 +45,17 @@ namespace KeePassLib.Cryptography.Cipher } ~Salsa20Cipher() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool bDisposing) { // Clear sensitive data Array.Clear(m_state, 0, m_state.Length); diff --git a/src/KeePassLib2Android/Cryptography/Cipher/StandardAesEngine.cs b/src/KeePassLib2Android/Cryptography/Cipher/StandardAesEngine.cs index 0bf442b7..4b4095e3 100644 --- a/src/KeePassLib2Android/Cryptography/Cipher/StandardAesEngine.cs +++ b/src/KeePassLib2Android/Cryptography/Cipher/StandardAesEngine.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,9 +22,12 @@ using System.Collections.Generic; using System.Text; using System.IO; using System.Security; -using System.Security.Cryptography; using System.Diagnostics; +#if !KeePassUAP +using System.Security.Cryptography; +#endif + using KeePassLib.Resources; namespace KeePassLib.Cryptography.Cipher @@ -34,8 +37,10 @@ namespace KeePassLib.Cryptography.Cipher /// public sealed class StandardAesEngine : ICipherEngine { +#if !KeePassUAP private const CipherMode m_rCipherMode = CipherMode.CBC; private const PaddingMode m_rCipherPadding = PaddingMode.PKCS7; +#endif private static PwUuid m_uuidAes = null; @@ -48,11 +53,9 @@ namespace KeePassLib.Cryptography.Cipher get { if(m_uuidAes == null) - { m_uuidAes = new PwUuid(new byte[]{ 0x31, 0xC1, 0xF2, 0xE6, 0xBF, 0x71, 0x43, 0x50, 0xBE, 0x58, 0x05, 0x21, 0x6A, 0xFC, 0x5A, 0xFF }); - } return m_uuidAes; } @@ -86,12 +89,12 @@ namespace KeePassLib.Cryptography.Cipher if(bEncrypt) { Debug.Assert(stream.CanWrite); - if(stream.CanWrite == false) throw new ArgumentException("Stream must be writable!"); + if(!stream.CanWrite) throw new ArgumentException("Stream must be writable!"); } else // Decrypt { Debug.Assert(stream.CanRead); - if(stream.CanRead == false) throw new ArgumentException("Encrypted stream must be readable!"); + if(!stream.CanRead) throw new ArgumentException("Encrypted stream must be readable!"); } } @@ -99,23 +102,25 @@ namespace KeePassLib.Cryptography.Cipher { StandardAesEngine.ValidateArguments(s, bEncrypt, pbKey, pbIV); - RijndaelManaged r = new RijndaelManaged(); + byte[] pbLocalIV = new byte[16]; + Array.Copy(pbIV, pbLocalIV, 16); + byte[] pbLocalKey = new byte[32]; + Array.Copy(pbKey, pbLocalKey, 32); + +#if KeePassUAP + return StandardAesEngineExt.CreateStream(s, bEncrypt, pbLocalKey, pbLocalIV); +#else + RijndaelManaged r = new RijndaelManaged(); if(r.BlockSize != 128) // AES block size { Debug.Assert(false); r.BlockSize = 128; } - byte[] pbLocalIV = new byte[16]; - Array.Copy(pbIV, pbLocalIV, 16); r.IV = pbLocalIV; - - byte[] pbLocalKey = new byte[32]; - Array.Copy(pbKey, pbLocalKey, 32); r.KeySize = 256; r.Key = pbLocalKey; - r.Mode = m_rCipherMode; r.Padding = m_rCipherPadding; @@ -125,6 +130,7 @@ namespace KeePassLib.Cryptography.Cipher return new CryptoStream(s, iTransform, bEncrypt ? CryptoStreamMode.Write : CryptoStreamMode.Read); +#endif } public Stream EncryptStream(Stream sPlainText, byte[] pbKey, byte[] pbIV) diff --git a/src/KeePassLib2Android/Cryptography/CryptoRandom.cs b/src/KeePassLib2Android/Cryptography/CryptoRandom.cs index 1cd1523e..a8b22618 100644 --- a/src/KeePassLib2Android/Cryptography/CryptoRandom.cs +++ b/src/KeePassLib2Android/Cryptography/CryptoRandom.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,12 +18,14 @@ */ using System; -using System.Security; -using System.Security.Cryptography; -using System.IO; using System.Diagnostics; -using System.Windows.Forms; +using System.IO; + +#if !KeePassUAP using System.Drawing; +using System.Security.Cryptography; +using System.Windows.Forms; +#endif using KeePassLib.Native; using KeePassLib.Utility; @@ -31,8 +33,8 @@ using KeePassLib.Utility; namespace KeePassLib.Cryptography { /// - /// Cryptographically strong random number generator. The returned values - /// are unpredictable and cannot be reproduced. + /// Cryptographically strong random number generator. The returned + /// values are unpredictable and cannot be reproduced. /// CryptoRandom is a singleton class. /// public sealed class CryptoRandom @@ -42,6 +44,7 @@ namespace KeePassLib.Cryptography private RNGCryptoServiceProvider m_rng = new RNGCryptoServiceProvider(); private ulong m_uGeneratedBytesCount = 0; + private static object g_oSyncRoot = new object(); private object m_oSyncRoot = new object(); private static CryptoRandom m_pInstance = null; @@ -49,10 +52,18 @@ namespace KeePassLib.Cryptography { get { - if(m_pInstance != null) return m_pInstance; + CryptoRandom cr; + lock(g_oSyncRoot) + { + cr = m_pInstance; + if(cr == null) + { + cr = new CryptoRandom(); + m_pInstance = cr; + } + } - m_pInstance = new CryptoRandom(); - return m_pInstance; + return cr; } } @@ -100,10 +111,10 @@ namespace KeePassLib.Cryptography byte[] pbNewData = pbEntropy; if(pbEntropy.Length >= 64) { -#if !KeePassLibSD - SHA512Managed shaNew = new SHA512Managed(); -#else +#if KeePassLibSD SHA256Managed shaNew = new SHA256Managed(); +#else + SHA512Managed shaNew = new SHA512Managed(); #endif pbNewData = shaNew.ComputeHash(pbEntropy); } @@ -115,11 +126,11 @@ namespace KeePassLib.Cryptography ms.Write(pbNewData, 0, pbNewData.Length); byte[] pbFinal = ms.ToArray(); -#if !KeePassLibSD +#if KeePassLibSD + SHA256Managed shaPool = new SHA256Managed(); +#else Debug.Assert(pbFinal.Length == (64 + pbNewData.Length)); SHA512Managed shaPool = new SHA512Managed(); -#else - SHA256Managed shaPool = new SHA256Managed(); #endif m_pbEntropyPool = shaPool.ComputeHash(pbFinal); } @@ -138,11 +149,17 @@ namespace KeePassLib.Cryptography ms.Write(pb, 0, pb.Length); #if !KeePassLibSD - Point pt = Cursor.Position; - pb = MemUtil.UInt32ToBytes((uint)pt.X); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt32ToBytes((uint)pt.Y); - ms.Write(pb, 0, pb.Length); + // In try-catch for systems without GUI; + // https://sourceforge.net/p/keepass/discussion/329221/thread/20335b73/ + try + { + Point pt = Cursor.Position; + pb = MemUtil.UInt32ToBytes((uint)pt.X); + ms.Write(pb, 0, pb.Length); + pb = MemUtil.UInt32ToBytes((uint)pt.Y); + ms.Write(pb, 0, pb.Length); + } + catch(Exception) { } #endif pb = MemUtil.UInt32ToBytes((uint)rWeak.Next()); @@ -151,22 +168,35 @@ namespace KeePassLib.Cryptography pb = MemUtil.UInt32ToBytes((uint)NativeLib.GetPlatformID()); ms.Write(pb, 0, pb.Length); -#if !KeePassLibSD try { pb = MemUtil.UInt32ToBytes((uint)Environment.ProcessorCount); ms.Write(pb, 0, pb.Length); + +#if KeePassUAP + Version v = EnvironmentExt.OSVersion.Version; +#else + Version v = Environment.OSVersion.Version; +#endif + pb = MemUtil.UInt32ToBytes((uint)v.GetHashCode()); + ms.Write(pb, 0, pb.Length); + +#if !KeePassUAP pb = MemUtil.UInt64ToBytes((ulong)Environment.WorkingSet); ms.Write(pb, 0, pb.Length); +#endif + } + catch(Exception) { Debug.Assert(false); } - Version v = Environment.OSVersion.Version; - int nv = (v.Major << 28) + (v.MajorRevision << 24) + - (v.Minor << 20) + (v.MinorRevision << 16) + - (v.Revision << 12) + v.Build; - pb = MemUtil.UInt32ToBytes((uint)nv); - ms.Write(pb, 0, pb.Length); +#if KeePassUAP + pb = DiagnosticsExt.GetProcessEntropy(); + ms.Write(pb, 0, pb.Length); +#elif !KeePassLibSD + Process p = null; + try + { + p = Process.GetCurrentProcess(); - Process p = Process.GetCurrentProcess(); pb = MemUtil.UInt64ToBytes((ulong)p.Handle.ToInt64()); ms.Write(pb, 0, pb.Length); pb = MemUtil.UInt32ToBytes((uint)p.HandleCount); @@ -198,7 +228,12 @@ namespace KeePassLib.Cryptography // pb = MemUtil.UInt32ToBytes((uint)p.SessionId); // ms.Write(pb, 0, pb.Length); } - catch(Exception) { } + catch(Exception) { Debug.Assert(NativeLib.IsUnix()); } + finally + { + try { if(p != null) p.Dispose(); } + catch(Exception) { Debug.Assert(false); } + } #endif pb = Guid.NewGuid().ToByteArray(); @@ -266,7 +301,7 @@ namespace KeePassLib.Cryptography long lCopy = (long)((uRequestedBytes < 32) ? uRequestedBytes : 32); -#if !KeePassLibSD +#if (!KeePassLibSD && !KeePassUAP) Array.Copy(pbRandom256, 0, pbRes, lPos, lCopy); #else Array.Copy(pbRandom256, 0, pbRes, (int)lPos, (int)lCopy); diff --git a/src/KeePassLib2Android/Cryptography/CryptoRandomStream.cs b/src/KeePassLib2Android/Cryptography/CryptoRandomStream.cs index 8d4e90de..44d9c63a 100644 --- a/src/KeePassLib2Android/Cryptography/CryptoRandomStream.cs +++ b/src/KeePassLib2Android/Cryptography/CryptoRandomStream.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,7 +19,10 @@ using System; using System.Diagnostics; + +#if !KeePassUAP using System.Security.Cryptography; +#endif using KeePassLib.Cryptography.Cipher; @@ -113,7 +116,7 @@ namespace KeePassLib.Cryptography { SHA256Managed sha256 = new SHA256Managed(); byte[] pbKey32 = sha256.ComputeHash(pbKey); - byte[] pbIV = new byte[]{ 0xE8, 0x30, 0x09, 0x4B, + byte[] pbIV = new byte[8] { 0xE8, 0x30, 0x09, 0x4B, 0x97, 0x20, 0x5D, 0x2A }; // Unique constant m_salsa20 = new Salsa20Cipher(pbKey32, pbIV); diff --git a/src/KeePassLib2Android/Cryptography/HashingStreamEx.cs b/src/KeePassLib2Android/Cryptography/HashingStreamEx.cs index f900b15e..ec7279c1 100644 --- a/src/KeePassLib2Android/Cryptography/HashingStreamEx.cs +++ b/src/KeePassLib2Android/Cryptography/HashingStreamEx.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,10 +19,13 @@ using System; using System.Collections.Generic; -using System.Text; -using System.IO; -using System.Security.Cryptography; using System.Diagnostics; +using System.IO; +using System.Text; + +#if !KeePassUAP +using System.Security.Cryptography; +#endif using KeePassLib.Utility; @@ -87,12 +90,9 @@ namespace KeePassLib.Cryptography if(m_hash == null) { Debug.Assert(false); return; } // Validate hash algorithm - if((!m_hash.CanReuseTransform) || (!m_hash.CanTransformMultipleBlocks) || - (m_hash.InputBlockSize != 1) || (m_hash.OutputBlockSize != 1)) + if(!m_hash.CanReuseTransform || !m_hash.CanTransformMultipleBlocks) { -#if DEBUG - MessageService.ShowWarning("Broken HashAlgorithm object in HashingStreamEx."); -#endif + Debug.Assert(false); m_hash = null; } } @@ -102,8 +102,14 @@ namespace KeePassLib.Cryptography m_sBaseStream.Flush(); } +#if KeePassUAP + protected override void Dispose(bool disposing) + { + if(!disposing) return; +#else public override void Close() { +#endif if(m_hash != null) { try diff --git a/src/KeePassLib2Android/Cryptography/HmacOtp.cs b/src/KeePassLib2Android/Cryptography/HmacOtp.cs index 42409280..df576593 100644 --- a/src/KeePassLib2Android/Cryptography/HmacOtp.cs +++ b/src/KeePassLib2Android/Cryptography/HmacOtp.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,8 +19,12 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Text; + +#if !KeePassUAP using System.Security.Cryptography; +#endif using KeePassLib.Utility; @@ -39,7 +43,7 @@ namespace KeePassLib.Cryptography uint uCodeDigits, bool bAddChecksum, int iTruncationOffset) { byte[] pbText = MemUtil.UInt64ToBytes(uFactor); - Array.Reverse(pbText); // Big-Endian + Array.Reverse(pbText); // To big-endian HMACSHA1 hsha1 = new HMACSHA1(pbSecret); byte[] pbHash = hsha1.ComputeHash(pbText); @@ -58,7 +62,8 @@ namespace KeePassLib.Cryptography uOtp = ((uOtp * 10) + CalculateChecksum(uOtp, uCodeDigits)); uint uDigits = (bAddChecksum ? (uCodeDigits + 1) : uCodeDigits); - return uOtp.ToString().PadLeft((int)uDigits, '0'); + return uOtp.ToString(NumberFormatInfo.InvariantInfo).PadLeft( + (int)uDigits, '0'); } private static readonly uint[] vDoubleDigits = new uint[]{ 0, 2, 4, 6, 8, diff --git a/src/KeePassLib2Android/Cryptography/PasswordGenerator/CharSetBasedGenerator.cs b/src/KeePassLib2Android/Cryptography/PasswordGenerator/CharSetBasedGenerator.cs index 89fb92e0..0e27f4cc 100644 --- a/src/KeePassLib2Android/Cryptography/PasswordGenerator/CharSetBasedGenerator.cs +++ b/src/KeePassLib2Android/Cryptography/PasswordGenerator/CharSetBasedGenerator.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/KeePassLib2Android/Cryptography/PasswordGenerator/CustomPwGenerator.cs b/src/KeePassLib2Android/Cryptography/PasswordGenerator/CustomPwGenerator.cs index bfd5c271..6bc2aaab 100644 --- a/src/KeePassLib2Android/Cryptography/PasswordGenerator/CustomPwGenerator.cs +++ b/src/KeePassLib2Android/Cryptography/PasswordGenerator/CustomPwGenerator.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/KeePassLib2Android/Cryptography/PasswordGenerator/CustomPwGeneratorPool.cs b/src/KeePassLib2Android/Cryptography/PasswordGenerator/CustomPwGeneratorPool.cs index c3cf9c1e..7511463d 100644 --- a/src/KeePassLib2Android/Cryptography/PasswordGenerator/CustomPwGeneratorPool.cs +++ b/src/KeePassLib2Android/Cryptography/PasswordGenerator/CustomPwGeneratorPool.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 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 @@ -66,7 +66,7 @@ namespace KeePassLib.Cryptography.PasswordGenerator foreach(CustomPwGenerator pwg in m_vGens) { - if(uuid.EqualsValue(pwg.Uuid)) return pwg; + if(uuid.Equals(pwg.Uuid)) return pwg; } return null; @@ -90,7 +90,7 @@ namespace KeePassLib.Cryptography.PasswordGenerator for(int i = 0; i < m_vGens.Count; ++i) { - if(uuid.EqualsValue(m_vGens[i].Uuid)) return i; + if(uuid.Equals(m_vGens[i].Uuid)) return i; } return -1; diff --git a/src/KeePassLib2Android/Cryptography/PasswordGenerator/PatternBasedGenerator.cs b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PatternBasedGenerator.cs index 3aa08104..ee397ed6 100644 --- a/src/KeePassLib2Android/Cryptography/PasswordGenerator/PatternBasedGenerator.cs +++ b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PatternBasedGenerator.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -67,6 +67,17 @@ namespace KeePassLib.Cryptography.PasswordGenerator pcsUsed.Add(ch); } } + else if(ch == '^') + { + ch = csStream.ReadChar(); + if(ch == char.MinValue) // ^ at the end + { + vGenerated.AddLast('^'); + break; + } + + if(bInCharSetDef) pcsCustom.Remove(ch); + } else if(ch == '[') { pcsCustom.Clear(); diff --git a/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwCharSet.cs b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwCharSet.cs index 9c5552a6..4cca5d3c 100644 --- a/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwCharSet.cs +++ b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwCharSet.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -46,51 +46,87 @@ namespace KeePassLib.Cryptography.PasswordGenerator public const string Invalid = "\t\r\n"; public const string LookAlike = @"O0l1I|"; + internal const string MenuAccels = PwCharSet.LowerCase + PwCharSet.Digits; + private const int CharTabSize = (0x10000 / 8); private List m_vChars = new List(); private byte[] m_vTab = new byte[CharTabSize]; - private string m_strHighAnsi = string.Empty; - private string m_strSpecial = string.Empty; + private static string m_strHighAnsi = null; + public static string HighAnsiChars + { + get + { + if(m_strHighAnsi == null) { new PwCharSet(); } // Create string + Debug.Assert(m_strHighAnsi != null); + return m_strHighAnsi; + } + } + + private static string m_strSpecial = null; + public static string SpecialChars + { + get + { + if(m_strSpecial == null) { new PwCharSet(); } // Create string + Debug.Assert(m_strSpecial != null); + return m_strSpecial; + } + } /// /// Create a new, empty character set collection object. /// public PwCharSet() { - this.Initialize(true); + Initialize(true); } public PwCharSet(string strCharSet) { - this.Initialize(true); - this.Add(strCharSet); + Initialize(true); + Add(strCharSet); } private PwCharSet(bool bFullInitialize) { - this.Initialize(bFullInitialize); + Initialize(bFullInitialize); } private void Initialize(bool bFullInitialize) { - this.Clear(); + Clear(); - if(bFullInitialize == false) return; + if(!bFullInitialize) return; - StringBuilder sbHighAnsi = new StringBuilder(); - for(char ch = '~'; ch < 255; ++ch) - sbHighAnsi.Append(ch); - m_strHighAnsi = sbHighAnsi.ToString(); + if(m_strHighAnsi == null) + { + StringBuilder sbHighAnsi = new StringBuilder(); + // [U+0080, U+009F] are C1 control characters, + // U+00A0 is non-breaking space + for(char ch = '\u00A1'; ch <= '\u00AC'; ++ch) + sbHighAnsi.Append(ch); + // U+00AD is soft hyphen (format character) + for(char ch = '\u00AE'; ch < '\u00FF'; ++ch) + sbHighAnsi.Append(ch); + sbHighAnsi.Append('\u00FF'); - PwCharSet pcs = new PwCharSet(false); - pcs.AddRange('!', '/'); - pcs.AddRange(':', '@'); - pcs.AddRange('[', '`'); - pcs.Remove(@"-_ "); - pcs.Remove(PwCharSet.Brackets); - m_strSpecial = pcs.ToString(); + m_strHighAnsi = sbHighAnsi.ToString(); + } + + if(m_strSpecial == null) + { + PwCharSet pcs = new PwCharSet(false); + pcs.AddRange('!', '/'); + pcs.AddRange(':', '@'); + pcs.AddRange('[', '`'); + pcs.Add(@"|~"); + pcs.Remove(@"-_ "); + pcs.Remove(PwCharSet.Brackets); + + m_strSpecial = pcs.ToString(); + } } /// @@ -118,9 +154,6 @@ namespace KeePassLib.Cryptography.PasswordGenerator } } - public string SpecialChars { get { return m_strSpecial; } } - public string HighAnsiChars { get { return m_strHighAnsi; } } - /// /// Remove all characters from this set. /// @@ -142,7 +175,7 @@ namespace KeePassLib.Cryptography.PasswordGenerator foreach(char ch in strCharacters) { - if(this.Contains(ch) == false) return false; + if(!Contains(ch)) return false; } return true; @@ -156,7 +189,7 @@ namespace KeePassLib.Cryptography.PasswordGenerator { if(ch == char.MinValue) { Debug.Assert(false); return; } - if(this.Contains(ch) == false) + if(!Contains(ch)) { m_vChars.Add(ch); m_vTab[ch / 8] |= (byte)(1 << (ch % 8)); @@ -175,20 +208,20 @@ namespace KeePassLib.Cryptography.PasswordGenerator m_vChars.Capacity = m_vChars.Count + strCharSet.Length; foreach(char ch in strCharSet) - this.Add(ch); + Add(ch); } public void Add(string strCharSet1, string strCharSet2) { - this.Add(strCharSet1); - this.Add(strCharSet2); + Add(strCharSet1); + Add(strCharSet2); } public void Add(string strCharSet1, string strCharSet2, string strCharSet3) { - this.Add(strCharSet1); - this.Add(strCharSet2); - this.Add(strCharSet3); + Add(strCharSet1); + Add(strCharSet2); + Add(strCharSet3); } public void AddRange(char chMin, char chMax) @@ -196,9 +229,9 @@ namespace KeePassLib.Cryptography.PasswordGenerator m_vChars.Capacity = m_vChars.Count + (chMax - chMin) + 1; for(char ch = chMin; ch < chMax; ++ch) - this.Add(ch); + Add(ch); - this.Add(chMax); + Add(chMax); } public bool AddCharSet(char chCharSetIdentifier) @@ -207,29 +240,29 @@ namespace KeePassLib.Cryptography.PasswordGenerator switch(chCharSetIdentifier) { - case 'a': this.Add(PwCharSet.LowerCase, PwCharSet.Digits); break; - case 'A': this.Add(PwCharSet.LowerCase, PwCharSet.UpperCase, + case 'a': Add(PwCharSet.LowerCase, PwCharSet.Digits); break; + case 'A': Add(PwCharSet.LowerCase, PwCharSet.UpperCase, PwCharSet.Digits); break; - case 'U': this.Add(PwCharSet.UpperCase, PwCharSet.Digits); break; - case 'c': this.Add(PwCharSet.LowerConsonants); break; - case 'C': this.Add(PwCharSet.LowerConsonants, + case 'U': Add(PwCharSet.UpperCase, PwCharSet.Digits); break; + case 'c': Add(PwCharSet.LowerConsonants); break; + case 'C': Add(PwCharSet.LowerConsonants, PwCharSet.UpperConsonants); break; - case 'z': this.Add(PwCharSet.UpperConsonants); break; - case 'd': this.Add(PwCharSet.Digits); break; // Digit - case 'h': this.Add(PwCharSet.LowerHex); break; - case 'H': this.Add(PwCharSet.UpperHex); break; - case 'l': this.Add(PwCharSet.LowerCase); break; - case 'L': this.Add(PwCharSet.LowerCase, PwCharSet.UpperCase); break; - case 'u': this.Add(PwCharSet.UpperCase); break; - case 'p': this.Add(PwCharSet.Punctuation); break; - case 'b': this.Add(PwCharSet.Brackets); break; - case 's': this.Add(PwCharSet.PrintableAsciiSpecial); break; - case 'S': this.Add(PwCharSet.UpperCase, PwCharSet.LowerCase); - this.Add(PwCharSet.Digits, PwCharSet.PrintableAsciiSpecial); break; - case 'v': this.Add(PwCharSet.LowerVowels); break; - case 'V': this.Add(PwCharSet.LowerVowels, PwCharSet.UpperVowels); break; - case 'Z': this.Add(PwCharSet.UpperVowels); break; - case 'x': this.Add(m_strHighAnsi); break; + case 'z': Add(PwCharSet.UpperConsonants); break; + case 'd': Add(PwCharSet.Digits); break; // Digit + case 'h': Add(PwCharSet.LowerHex); break; + case 'H': Add(PwCharSet.UpperHex); break; + case 'l': Add(PwCharSet.LowerCase); break; + case 'L': Add(PwCharSet.LowerCase, PwCharSet.UpperCase); break; + case 'u': Add(PwCharSet.UpperCase); break; + case 'p': Add(PwCharSet.Punctuation); break; + case 'b': Add(PwCharSet.Brackets); break; + case 's': Add(PwCharSet.PrintableAsciiSpecial); break; + case 'S': Add(PwCharSet.UpperCase, PwCharSet.LowerCase); + Add(PwCharSet.Digits, PwCharSet.PrintableAsciiSpecial); break; + case 'v': Add(PwCharSet.LowerVowels); break; + case 'V': Add(PwCharSet.LowerVowels, PwCharSet.UpperVowels); break; + case 'Z': Add(PwCharSet.UpperVowels); break; + case 'x': Add(m_strHighAnsi); break; default: bResult = false; break; } @@ -238,7 +271,7 @@ namespace KeePassLib.Cryptography.PasswordGenerator public bool Remove(char ch) { - m_vTab[ch / 8] &= (byte)~(1 << (ch % 8)); + m_vTab[ch / 8] &= (byte)(~(1 << (ch % 8))); return m_vChars.Remove(ch); } @@ -261,10 +294,10 @@ namespace KeePassLib.Cryptography.PasswordGenerator Debug.Assert(strCharacters != null); if(strCharacters == null) throw new ArgumentNullException("strCharacters"); - if(this.Contains(strCharacters) == false) + if(!Contains(strCharacters)) return false; - return this.Remove(strCharacters); + return Remove(strCharacters); } /// @@ -284,16 +317,16 @@ namespace KeePassLib.Cryptography.PasswordGenerator { StringBuilder sb = new StringBuilder(); - sb.Append(this.RemoveIfAllExist(PwCharSet.UpperCase) ? 'U' : '_'); - sb.Append(this.RemoveIfAllExist(PwCharSet.LowerCase) ? 'L' : '_'); - sb.Append(this.RemoveIfAllExist(PwCharSet.Digits) ? 'D' : '_'); - sb.Append(this.RemoveIfAllExist(m_strSpecial) ? 'S' : '_'); - sb.Append(this.RemoveIfAllExist(PwCharSet.Punctuation) ? 'P' : '_'); - sb.Append(this.RemoveIfAllExist(@"-") ? 'm' : '_'); - sb.Append(this.RemoveIfAllExist(@"_") ? 'u' : '_'); - sb.Append(this.RemoveIfAllExist(@" ") ? 's' : '_'); - sb.Append(this.RemoveIfAllExist(PwCharSet.Brackets) ? 'B' : '_'); - sb.Append(this.RemoveIfAllExist(m_strHighAnsi) ? 'H' : '_'); + sb.Append(RemoveIfAllExist(PwCharSet.UpperCase) ? 'U' : '_'); + sb.Append(RemoveIfAllExist(PwCharSet.LowerCase) ? 'L' : '_'); + sb.Append(RemoveIfAllExist(PwCharSet.Digits) ? 'D' : '_'); + sb.Append(RemoveIfAllExist(m_strSpecial) ? 'S' : '_'); + sb.Append(RemoveIfAllExist(PwCharSet.Punctuation) ? 'P' : '_'); + sb.Append(RemoveIfAllExist(@"-") ? 'm' : '_'); + sb.Append(RemoveIfAllExist(@"_") ? 'u' : '_'); + sb.Append(RemoveIfAllExist(@" ") ? 's' : '_'); + sb.Append(RemoveIfAllExist(PwCharSet.Brackets) ? 'B' : '_'); + sb.Append(RemoveIfAllExist(m_strHighAnsi) ? 'H' : '_'); return sb.ToString(); } @@ -303,16 +336,16 @@ namespace KeePassLib.Cryptography.PasswordGenerator if(strRanges == null) { Debug.Assert(false); return; } if(strRanges.Length < 10) { Debug.Assert(false); return; } - if(strRanges[0] != '_') this.Add(PwCharSet.UpperCase); - if(strRanges[1] != '_') this.Add(PwCharSet.LowerCase); - if(strRanges[2] != '_') this.Add(PwCharSet.Digits); - if(strRanges[3] != '_') this.Add(m_strSpecial); - if(strRanges[4] != '_') this.Add(PwCharSet.Punctuation); - if(strRanges[5] != '_') this.Add('-'); - if(strRanges[6] != '_') this.Add('_'); - if(strRanges[7] != '_') this.Add(' '); - if(strRanges[8] != '_') this.Add(PwCharSet.Brackets); - if(strRanges[9] != '_') this.Add(m_strHighAnsi); + if(strRanges[0] != '_') Add(PwCharSet.UpperCase); + if(strRanges[1] != '_') Add(PwCharSet.LowerCase); + if(strRanges[2] != '_') Add(PwCharSet.Digits); + if(strRanges[3] != '_') Add(m_strSpecial); + if(strRanges[4] != '_') Add(PwCharSet.Punctuation); + if(strRanges[5] != '_') Add('-'); + if(strRanges[6] != '_') Add('_'); + if(strRanges[7] != '_') Add(' '); + if(strRanges[8] != '_') Add(PwCharSet.Brackets); + if(strRanges[9] != '_') Add(m_strHighAnsi); } } } diff --git a/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwGenerator.cs b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwGenerator.cs index 7e0e2ca8..285bfee9 100644 --- a/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwGenerator.cs +++ b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwGenerator.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwProfile.cs b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwProfile.cs index df484aaa..5d52c768 100644 --- a/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwProfile.cs +++ b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwProfile.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 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 @@ -253,14 +253,15 @@ namespace KeePassLib.Cryptography.PasswordGenerator if((ch >= 'A') && (ch <= 'Z')) pcs.Add(PwCharSet.UpperCase); else if((ch >= 'a') && (ch <= 'z')) pcs.Add(PwCharSet.LowerCase); else if((ch >= '0') && (ch <= '9')) pcs.Add(PwCharSet.Digits); - else if((@"!#$%&'*+,./:;=?@^").IndexOf(ch) >= 0) pcs.Add(pcs.SpecialChars); + else if(PwCharSet.SpecialChars.IndexOf(ch) >= 0) + pcs.Add(PwCharSet.SpecialChars); else if(ch == ' ') pcs.Add(' '); else if(ch == '-') pcs.Add('-'); else if(ch == '_') pcs.Add('_'); - else if(ch == '\"') pcs.Add(pcs.SpecialChars); - else if(ch == '\\') pcs.Add(pcs.SpecialChars); - else if((@"()[]{}<>").IndexOf(ch) >= 0) pcs.Add(PwCharSet.Brackets); - else if((ch >= '~') && (ch <= 255)) pcs.Add(pcs.HighAnsiChars); + else if(PwCharSet.Brackets.IndexOf(ch) >= 0) + pcs.Add(PwCharSet.Brackets); + else if(PwCharSet.HighAnsiChars.IndexOf(ch) >= 0) + pcs.Add(PwCharSet.HighAnsiChars); else pcs.Add(ch); } diff --git a/src/KeePassLib2Android/Cryptography/PopularPasswords.cs b/src/KeePassLib2Android/Cryptography/PopularPasswords.cs index 7e4a70f1..e4e8aca3 100644 --- a/src/KeePassLib2Android/Cryptography/PopularPasswords.cs +++ b/src/KeePassLib2Android/Cryptography/PopularPasswords.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,521 +20,115 @@ using System; using System.Collections.Generic; using System.Text; -using System.IO; -using System.Security.Cryptography; -using System.Globalization; using System.Diagnostics; using KeePassLib.Utility; -#if !KeePassLibSD namespace KeePassLib.Cryptography { - /// - /// Bloom filter-based popular password checking. - /// public static class PopularPasswords { - private const int PpcTableSize = 8192 * 8; // Bits, multiple of 64 + private static Dictionary> m_dicts = + new Dictionary>(); - // Bits set: 32433 of 65536 - // Hash functions: 32 - // Phi (bits unset ratio) estimation: 0.505455388896019 - // Exact Phi: 0.505111694335938 - // False positives ratio: 1.67583063859565E-10 - private static readonly ulong[] PpcTable = { - 0x60383D3A85560B9BUL, 0x2578CE9D37C6AEB7UL, 0xF509A5743FD03228UL, - 0x19B7455E8933EE56UL, 0x5EA419ADCFD9C20EUL, 0xEA618EFC0B37A162UL, - 0xE0FD4D1FFF1CE415UL, 0x7A649E0301BB6060UL, 0x80D9CD9F9EEB603DUL, - 0x47D6010D0D6E6CDEUL, 0x2552708C589EB554UL, 0x073F1A3DB3267502UL, - 0x3313FEC2A2FEA475UL, 0x4593665C44934FEBUL, 0x410A301A23660395UL, - 0x6AD06DA533FF5659UL, 0x423DAF86F3E41F4AUL, 0x82F035A971C6FD18UL, - 0xB5E9139F28C93223UL, 0x1D07C3F4160585CAUL, 0x24B01EDB6B23E2C5UL, - 0xD52F25B724F936C9UL, 0x8018392517836928UL, 0x3AA4C0F8E181EDA2UL, - 0x8D93683EF7D52529UL, 0x6164BB6208114460UL, 0x737A04D8FEF3D88FUL, - 0x3400097098D5C2CBUL, 0x3C2B9ABE5C455B2EUL, 0x3A3819973AB32DA2UL, - 0x38ACB428510AF40BUL, 0x83320D5114B74771UL, 0xC25BEC333B90DCD1UL, - 0x0E9F412FBA3813D1UL, 0x047E31E3098EB2B8UL, 0xBB686AC643F1741FUL, - 0x0BE22E9C0EF0E8F2UL, 0x65AA9504E5F40D31UL, 0xE018DF5D64C62AC7UL, - 0x17020E9A7EFA12EDUL, 0xFC12A7C16006DE82UL, 0x8DE4747E3745346DUL, - 0x31D8C051A43CECAFUL, 0xBE9AFBEF127C1B12UL, 0xAEE94B4B808BBEE2UL, - 0x3A0099CA32835B41UL, 0x59EB3173468D8C49UL, 0x6F89DB1E6DAAE9E1UL, - 0x4C1ADAA837E968E4UL, 0x6E3593A56C682769UL, 0x022AD591689B5B82UL, - 0x4AC33861ED978032UL, 0xF6F476E4E6A1318DUL, 0x2DA690A11AA05A23UL, - 0x916FC56378C29D77UL, 0xAB3238BE22294659UL, 0x2D73A29019B28C77UL, - 0xAAF26C12EC9C3C42UL, 0x058A278A24B334F9UL, 0x033BD18FB8D9BACDUL, - 0x8B3833596008B07CUL, 0x280B6D093333E5E5UL, 0x2128DBE126CA3E1EUL, - 0xCCF09769382472D8UL, 0x0CB6E495BD90CED6UL, 0x1303A37577C01C5AUL, - 0xC8BBF4734FC34C53UL, 0x1B38B72B10F86CD5UL, 0x5098E2D6C1892E51UL, - 0x2DD8065B79DB5380UL, 0x5B9A1A6D6A2292B7UL, 0xC70F751604D0497CUL, - 0x911E08D7363B5213UL, 0x9F2E245273308D2EUL, 0x64D354827957F50EUL, - 0x09856750F560342CUL, 0xDE091F26603F0E70UL, 0xDDE6B4E76173E3B1UL, - 0xC1584AE1B26FA08EUL, 0x1EA29887837838D2UL, 0x6D7643FC67B15C54UL, - 0x921E60571ED103EAUL, 0x63EB1EB33E7AFFF1UL, 0x80BA4D1F95BFD615UL, - 0xEC8A1D4FC1A6B8E0UL, 0x2C46861B6DB17D1AUL, 0x01F05D06927E443BUL, - 0x6172EC2EABEAD454UL, 0x21B8726C6F7C4102UL, 0x3C016CD9945C72ECUL, - 0x708F77B2C0E8B665UL, 0xFC35BE2BB88974DAUL, 0x805897A33702BD61UL, - 0x9A93367A6041226CUL, 0xFDAB188B6158F6BEUL, 0x5F21014A065E918CUL, - 0xF4381DD77772D19CUL, 0xC664B6358AA85011UL, 0xF2639D7B3E2307E6UL, - 0x3FA000D4A5A9C37AUL, 0x8F45D116ED8DC70FUL, 0x8CB8758E45C140D0UL, - 0x49832B46D716236DUL, 0xCC8E4961A93065B8UL, 0x8A996533EDACEB0EUL, - 0x15B35155EC56FAC1UL, 0xE7E0C6C05A9F1885UL, 0x05914F9A1D1C79F9UL, - 0x730000A30B6725F0UL, 0xC95E671F8E543780UL, 0x47D68382400AF94EUL, - 0x1A27F2734FE2249AUL, 0x828079C332D9C0ABUL, 0x2E9BC798EA09170EUL, - 0x6B7CDAC829018C91UL, 0x7B89604901736993UL, 0xABE4EB26F47608F0UL, - 0x70D5FDC88A0FF1B1UL, 0x5A1F0BAB9AB8A158UL, 0xDC89AE0A735C51A4UL, - 0x36C1EA01E9C89B84UL, 0x3A9757AF204096DBUL, 0x1D56C8328540F963UL, - 0x910A8694692472FAUL, 0x697192C9DF145604UL, 0xB20F7A4438712AA2UL, - 0xE8C99185243F4896UL, 0xFBC8970EDBC39CA7UL, 0x33485403868C3761UL, - 0xAFA97DDEDB1D6AD8UL, 0x54A1A6F24476A3BBUL, 0xFE4E078B184BDB7FUL, - 0x5ED1543919754CD8UL, 0x86F8C775160FC08CUL, 0x9B4098F57019040DUL, - 0x039518BBE841327BUL, 0x111D0D420A3F5F6AUL, 0x0666067346AF34ACUL, - 0xD43F1D14EB239B9BUL, 0xA6BB91FEB5618F5BUL, 0xA2B5218B202409ADUL, - 0xC004FA688C3AC25EUL, 0xF0E2D9EA2935E1DCUL, 0x380B31CFA2F2AF43UL, - 0x50E050AE426250EAUL, 0x628ED94D1AA8F55BUL, 0xF8EB0654F0166311UL, - 0x1F8858D26DDA5CC5UL, 0x931425D11CB1EFEBUL, 0xF661D461DC1A05D3UL, - 0x7B75ED7EC6936DA8UL, 0x8713C59690985202UL, 0xF61D6F93F07C0E85UL, - 0xFD1771F6711D6F4FUL, 0x5835A67E1B11419FUL, 0x33EF08ABD56A1050UL, - 0x55B5D0043FA2C01CUL, 0x53316ED963B92D9DUL, 0x6A8C93744E521EDBUL, - 0x083E948062EB9543UL, 0x1C15289B3189AFB1UL, 0xA6A0A5053AE2212DUL, - 0x6573AF7F01FAFF3BUL, 0x58B6F034CFCFE843UL, 0xEB2837CA5AEA6AEDUL, - 0x633E7897097AC328UL, 0x7FA91789B6CCEE82UL, 0xBEE2402F4E7D65EEUL, - 0x616A103EC8FB0DBEUL, 0x65991F9FB25E13FCUL, 0x54EA8A3FADEC1F4BUL, - 0x6D497C5ACDEA0E7AUL, 0x5865045E8CA18527UL, 0xA406C09215ABD61FUL, - 0x68F81F5745FC9875UL, 0xE496D850CEFF3FA9UL, 0xD225C88D63212CB1UL, - 0x37676390525116D2UL, 0x415614AB14188A7DUL, 0xABE58EBC1F6DDC63UL, - 0xDE10312B2C25D28CUL, 0x86C86D7A0B847635UL, 0x408B511D584DC3DCUL, - 0x6711FCC14B303FEDUL, 0x1284DF9CC6972023UL, 0xC3CE0B33141BFA8FUL, - 0x0F3F58367D4A1819UL, 0x9313F83058FBE6D0UL, 0x6FCA5EF39B8E2F47UL, - 0xA90F5C95D887756DUL, 0x96C4E2AD85D5AF6EUL, 0x0ED68A81F526F0A0UL, - 0x53E4472DB4255A35UL, 0xAC581015134D58A6UL, 0x12C000D85C644FC7UL, - 0x124D489B2C0FE6E4UL, 0x8FF83531C6F5D61AUL, 0x132BD6488304F73BUL, - 0x110E99BC59604CB9UL, 0xC28186ACBC940C9BUL, 0x2094C07F48141230UL, - 0x65FB9881A5053589UL, 0x940A3E6D72F09D69UL, 0x972A922CB14BA66EUL, - 0x8D07E59C6DDD4327UL, 0xCB67F993F820157CUL, 0x65B7A54E5FB2ED6CUL, - 0xC235828849576653UL, 0xA695F85479467538UL, 0x9E2BA885E63C4243UL, - 0xDE64A6A5EF84C222UL, 0xC2AB9AF302080621UL, 0x88DBA09B87FA0734UL, - 0xDF002765B44D02E1UL, 0xD50D8D90587CD820UL, 0x1B68B70ED179EFE1UL, - 0xD6E77F8EC26AE95CUL, 0xEE57EB7C45051872UL, 0x4D2B445F36A7F9FDUL, - 0x5502ABB8BB14D7F1UL, 0xAF2C0DF0406FA901UL, 0x6522833444BF4A83UL, - 0xD7CB2E3FC691BE8DUL, 0x4F36F70D2E80D19AUL, 0xF6945FE911D4923BUL, - 0xE3C6FE1EA47399C1UL, 0xF09EA1B2F837702CUL, 0x5122537CF97B5CB5UL, - 0x0C8202B70E9BF154UL, 0x68B554AB58EB5E68UL, 0x7BF9B8052C9BEADEUL, - 0x33612BFCD303810DUL, 0x03E38CF67B37DC53UL, 0x2BFDFF8691F37D5CUL, - 0x4AB483D1CB1D07F6UL, 0xF071A58640639A5CUL, 0x9D6B98169B745CE1UL, - 0x5F42D3E870FDCD93UL, 0x4EDF04404F258238UL, 0x2EAB6E10D65C9BB3UL, - 0x5BB71411EF78DAD2UL, 0x0DE8128636A5D689UL, 0x18FDD1F484DC9365UL, - 0x9896B8896941DA5BUL, 0x8BEF8E3BA4448E5FUL, 0x963A1E977CB1D2CAUL, - 0x02BCF5F068D52851UL, 0x0CD783F09BFBE381UL, 0x350DA833D8C5DB47UL, - 0x8D444C914D795C43UL, 0x8A00B4DFC44D9476UL, 0x4B36CBEC089E55FDUL, - 0xD9D2FA1B0AC66211UL, 0x6C7FC30FA31A8B60UL, 0x9EF4504CC985AD6BUL, - 0x8F2E7E5E0C00EE73UL, 0x819131CFEEBEA069UL, 0xB1E406A863E7A1B4UL, - 0x501F072FF1F2AB67UL, 0xDE578BFC5ADBC264UL, 0xCDD66A09C8E13881UL, - 0x4D443460CE52957FUL, 0x3B198C267976ECFAUL, 0x6B98323D8BD26522UL, - 0x80161F6A489E4BF8UL, 0xE03A8AFCC7AE6872UL, 0x2484BD95A305AB27UL, - 0x6ADDAA46BF25DD0EUL, 0xA429D8B00100477CUL, 0x55AEDB88A074BF2CUL, - 0x63D9F9021AB8F5F3UL, 0x37858538A10C265CUL, 0xEF54C2CE9D063149UL, - 0xFA5CE5AF33E2C136UL, 0xE601A559D0C391D7UL, 0x7C4ED29BBF57DC7EUL, - 0x8FD0D4146DE9E900UL, 0xB58ABFA6CE6C0733UL, 0xF8D7F7743B33EAFFUL, - 0x453FA782F454643CUL, 0xD01752C21AF21E66UL, 0xA50BB7913EAF05DFUL, - 0x966D5B140B2F4189UL, 0x956F5638AFF3D148UL, 0x93FAA838420E8AB3UL, - 0x715E26043071EABDUL, 0x01E7B458B5FD3A41UL, 0x5CFA99C4CC0492AAUL, - 0x761FD391C3623044UL, 0xD39E44E9DB96B5BCUL, 0x8806C544F0534A07UL, - 0x9B225CAFE97EAAC1UL, 0xEAE5E18583492767UL, 0x6B4E51E4C297F096UL, - 0xFC512662EF47E41DUL, 0xB6AC60427DB46F8BUL, 0x8F137F3DB4429C9DUL, - 0x04C65FBEAE9FD8D0UL, 0xEB72305958AE5022UL, 0xAA93AA14BCA2961EUL, - 0x6C7547F9456CA37AUL, 0xEE6094871615BA34UL, 0x489BC8EDE0940402UL, - 0x1108AEFAAD892229UL, 0x673B8B1CF6BED23EUL, 0xFDB781A75FD94DEAUL, - 0x11D9E0F5D914A7BEUL, 0x02830D07F018143DUL, 0x9B3163B8188FD2BAUL, - 0x32C1BEC97D06117EUL, 0x697268B761240CFFUL, 0xBD89CE3037C2E7A9UL, - 0xF21C158125B19600UL, 0x632CB1931601B70AUL, 0x7BB3FB131338085CUL, - 0xA9C06689B8138384UL, 0x161CCBF83EBDC2A1UL, 0x2CF83C01A80B7935UL, - 0x9E51FE393B8E2FF0UL, 0xFE96E52B1606C1A7UL, 0x5E20DFB87F81ACCEUL, - 0xF95DB9602CDAE467UL, 0xDEA155CD35555FEBUL, 0xF0669B810F70CDC6UL, - 0xD36C2FBEB6A449ACUL, 0xCE500C6621C0A445UL, 0x41308909E366460AUL, - 0xAC4D8178DA0CEC24UL, 0xC69049179ED09F7DUL, 0x36B608A0BA2FD848UL, - 0xDF511894DD9568B4UL, 0xB3BFDF78EC861A6CUL, 0xCD50F39D19848153UL, - 0xD2C1BC57E78A408CUL, 0x1E6613EFBB11B5EBUL, 0xF58E30D2D90F73D3UL, - 0xCCB5E2F5E168D742UL, 0xEE97259469BDB672UL, 0x6784D35AF65935A8UL, - 0x71032765ADED1FE8UL, 0x4BBF2FE54D9B72E3UL, 0x5A1BB7831E876A05UL, - 0x12A8FC949EE91686UL, 0x8296F8FA83BD112CUL, 0xAAA7E3BFF64D34D5UL, - 0x0301655E1794EE4BUL, 0x1E547C011BBF30E1UL, 0x39D74FEC536F31D6UL, - 0x3C31A7478B1815BAUL, 0x525C774F82D5836EUL, 0xECF7186DC612FD8CUL, - 0x96B7C4EDD1F3536FUL, 0x7E8C21F19C08541CUL, 0xEE92DB0CF91E4B09UL, - 0xF666190D1591AE5DUL, 0x5E9B45102C895361UL, 0x9A95597AAE5C905DUL, - 0x6E1272E5BB93F93FUL, 0x0E39E612402BFCF8UL, 0x576C9E8CA2A3B35EUL, - 0x7E2E629996D0C35FUL, 0xC95DFF54E3524FCCUL, 0x260F9DEBDEB0E5CBUL, - 0x577B6C6640BAF1ABUL, 0xCA76677779CA358EUL, 0x9E2714BEBCFDB144UL, - 0xD660595CE30FD3EEUL, 0x72DE172D55A5706EUL, 0xB4C84D564489D420UL, - 0x160AA2B9399D5A9DUL, 0x2906ECE619DAC4D2UL, 0x12CE8E8E68A4C317UL, - 0x6BE2DFE89901CAA1UL, 0xEE1D68158102EB77UL, 0x64EB75E45BDA1AC5UL, - 0xEFECF9F98720B55DUL, 0x41CDF813931315BFUL, 0x5F1E4F50CF98FFD4UL, - 0xE69E09EED12E173BUL, 0x89A3707F0396FF65UL, 0x81E36B9DF4FFB492UL, - 0x58C32E883D4DE6DDUL, 0x2D4725C2A5F0B469UL, 0x6B2B9C27CC421CACUL, - 0x3C30F2AD966800C7UL, 0xFF74938BB76B8A7CUL, 0x52B5C99114FD93FAUL, - 0x51647EDCA6C104DAUL, 0xEB47684CF796DF4EUL, 0x376D74A5AB14BD71UL, - 0xF0871FEF8E9DAA3EUL, 0x1D65B134B2E045B6UL, 0x9DC8C0D8623BBA48UL, - 0xAD6FC3C59DBDADF4UL, 0x66F6EBA55488B569UL, 0xB00D53E0E2D38F0AUL, - 0x43A4212CEAD34593UL, 0x44724185FF7019FFUL, 0x50F46061432B3635UL, - 0x880AA4C24E6B320BUL, 0xCAFCB3409A0DB43FUL, 0xA7F1A13DEF47514BUL, - 0x3DC8A385A698220CUL, 0xFA17F82E30B85580UL, 0x430E7F0E88655F47UL, - 0x45A1566013837B47UL, 0x84B2306D2292804EUL, 0xE7A3AF21D074E419UL, - 0x09D08E2C5E569D4DUL, 0x84228F8908383FA2UL, 0xC34079610C8D3E82UL, - 0x66C96426C54A5453UL, 0xD41F164117D32C93UL, 0x7829A66BF1FEC186UL, - 0x4BB6846694BDFC18UL, 0x857D1C1C01352C01UL, 0xAB8E68BA85402A45UL, - 0x74B3C4F101FE76C8UL, 0x6CF482CFAFB29FFEUL, 0x28B174A18F4DC3D1UL, - 0x833C3425B2AA3755UL, 0x8AA58A32747F4432UL, 0xFE7B9FB4BCE3CD58UL, - 0xB0836B2C16FA5553UL, 0x1D08EE6861BF3F23UL, 0x0FAE41E914562DF3UL, - 0xB10A2E041937FC57UL, 0xDA60BB363415BF4CUL, 0xEEC67DBAB4CF4F0AUL, - 0x9A6ED59FCC923B5CUL, 0x9A913C01A8EC7A83UL, 0xAD4779F2F9C7721FUL, - 0x2BF0B7D105BE7459UL, 0x189EFA9AD5195EC6UL, 0xB5C9A2DD64B2A903UL, - 0x5BCD642B2C2FD32CUL, 0xFED3FBF78CB0891FUL, 0x1ED958EE3C36DD3FUL, - 0x030F5DE9CA65E97CUL, 0xBB5BCF8C931B85FEUL, 0xFD128759EA1D8061UL, - 0x2C0238AC416BE6BCUL, 0xBB018584EEACFA27UL, 0xCEA7288C1964DE15UL, - 0x7EA5C3840F29AA4DUL, 0x5DA841BA609E4A50UL, 0xE53AF84845985EB1UL, - 0x93264DA9487183E4UL, 0xC3A4E367AF6D8D15UL, 0xDD4EB6450577BAF8UL, - 0x2AA3093EE2C658ACUL, 0x3D036EC45055C580UL, 0xDDEDB34341C5B7DFUL, - 0x524FFBDC4A1FAC90UL, 0x1B9D63DE13D82907UL, 0x69F9BAF0E868B640UL, - 0xFC1A453A9253013CUL, 0x08B900DECAA77377UL, 0xFF24C72324153C59UL, - 0x6182C1285C507A9BUL, 0x4E6680A54A03CCC8UL, 0x7165680200B67F1FUL, - 0xC3290B26A07DCE5BUL, 0x2AD16584AA5BECE9UL, 0x5F10DF677C91B05EUL, - 0x4BE1B0E2334B198AUL, 0xEA2466E4F4E4406DUL, 0x6ECAA92FF91E6F1DUL, - 0x0267738EFA75CADDUL, 0x4282ED10A0EBFCF2UL, 0xD3F84CE8E1685271UL, - 0xB667ED35716CA215UL, 0x97B4623D70DB7FA8UL, 0xB7BA3AA5E6C2E7CBUL, - 0x8942B2F97118255BUL, 0x009050F842FB52ADUL, 0x114F5511999F5BD5UL, - 0x70C1CAAF1E83F00AUL, 0xAC8EE25D462BB1AAUL, 0x63EEF42AD4E1BED9UL, - 0x58DFBB3D22D3D1A5UL, 0x82B0027C0C63D816UL, 0x48D038F08F3D848BUL, - 0xCE262D5F9A12610EUL, 0xA54BF51C21BD0167UL, 0xF3645F6FB948397DUL, - 0x9188AE58532DA501UL, 0xEC90B0E1479DB767UL, 0x37F4886B83724F80UL, - 0x232B8FF20ACD95AFUL, 0x88A228285D6BCDF0UL, 0x321FB91600259AEEUL, - 0xA1F875F161D18E5EUL, 0x5B6087CDA21AEA0CUL, 0x0156923ED1A3D5F1UL, - 0xC2892C8A6133B5D3UL, 0x015CA4DF0EA6354DUL, 0x5E25EB261B69A7D4UL, - 0xAAA8CF0C012EFBA7UL, 0xCF3466248C37868BUL, 0x0D744514BD1D82C0UL, - 0xB00FF1431EDDF490UL, 0xC79B86A0E3A8AB08UL, 0xFC361529BC9F1252UL, - 0x869285653FB82865UL, 0x9F1C7A17546339ABUL, 0xE31AA66DBD5C4760UL, - 0x51B9D2A765E0FC31UL, 0x31F39528C4CD13D8UL, 0x16C6C35B0D3A341DUL, - 0x90296B1B0F28E2CDUL, 0x36338472A8DB5830UL, 0xA648E6D44DF14F87UL, - 0x93E231E65EB1823FUL, 0x95AA7B9D08E2B627UL, 0x7932D149374700C7UL, - 0x09EFE0A8BF245193UL, 0x742AA63BCEAFD6D8UL, 0x82D4BC5FEDF158B7UL, - 0x02CDEA673CFF150DUL, 0xD8D7B5813B602D15UL, 0xA5A7B670EF15A5EDUL, - 0x4C08E580A1D46AF2UL, 0xC3CA9B905D035647UL, 0x6A39ABB02F6F1B83UL, - 0xD2EC2169F4D02436UL, 0x8E6AEA4DF8515AF2UL, 0x7B3DD4A8693CA2DAUL, - 0xC2ABF17A50AEC383UL, 0xD4FB84F8B6D4F709UL, 0x2839A3EAA2E4C8A7UL, - 0x5D5FD278FE10E1E9UL, 0x5610DDF74125D5A7UL, 0xA484B0B83461DCEAUL, - 0xA511920C0A502369UL, 0xC53F30C6A5394CA4UL, 0x528799285D304DD4UL, - 0xF6D7914CB252BB48UL, 0x892129CB52E65D15UL, 0x15A81B70519ACE6FUL, - 0x5CFBFFD7A2A1C630UL, 0x3B900509C82DF46DUL, 0x19C3CE05D66D5FFCUL, - 0x937D521A4A4799A0UL, 0xD0AE34A6EAD7207DUL, 0x3258A69F1D1A1B38UL, - 0xB173E3255723CC02UL, 0xD7E48FEF7F414F1BUL, 0xDCEBA75F5C761ABEUL, - 0x6DA10C618DEA0D17UL, 0x423FA8B05954FBD1UL, 0x7E73C2E7D923F3C9UL, - 0xC22E21C927C684D1UL, 0x756BAA758764685FUL, 0x8C90B4C4E741D880UL, - 0x1B658C9F4B41D846UL, 0x5D80C14094366707UL, 0xB55FED3E03C00F2DUL, - 0x9B69EB7964C69C83UL, 0x356ED81C9494DADDUL, 0x7599AFF0B2A339D6UL, - 0xA5EBFD25C9B5816BUL, 0xA481DC1C8995E1EFUL, 0xE42C63DF0D402397UL, - 0x3B497B4C30873BAAUL, 0xA950B78BA8772C96UL, 0xD46308D4C76F115DUL, - 0x73714A4ACA76A857UL, 0x0DA86B958FF8CB7DUL, 0xEB61F617B90E0A75UL, - 0xD6106C9B39F51F55UL, 0xB179F73A6BD23B59UL, 0xE7F056E50104A460UL, - 0xBC5B5387634A8642UL, 0xE1678D8752996AF4UL, 0xB508F3D394664A4BUL, - 0xC88536DC4A219B0FUL, 0x39964CBB8CE367B1UL, 0xD51E211D5E9E1417UL, - 0x6821B97B496870F2UL, 0xA596257350CA0A99UL, 0x6D051EE2C49D4D1DUL, - 0xCB426AD61AA8D9B5UL, 0x5FFD3A4062B06D22UL, 0xDAD37BF2A4C594EBUL, - 0x6B9CC848E2B0C686UL, 0x19B4232F3BC622AEUL, 0x70C13C7E5950B702UL, - 0x383318CA622101ACUL, 0xD9647C028CD1C4DFUL, 0x006D123BC553B93CUL, - 0x2CA9D7D896EAE722UL, 0xF19872AC8A0BD5A8UL, 0x59838578EB9E8E5CUL, - 0xB948621EE99B27D4UL, 0x2B470E6036E0E387UL, 0xD0A7E8F0C8A32A84UL, - 0xCBF869271A8E0914UL, 0x705F76A5EA4437CFUL, 0xBAD2BF4933215152UL, - 0xE52EDE847038EA23UL, 0xB8A3EFD3D58D7607UL, 0x748472F5AD330239UL, - 0xCC079CFD428690F6UL, 0x3687450CB7534DACUL, 0x0FEF82D5CC8ACE2AUL, - 0x214653D5C552CA9AUL, 0x9FCA4E87BF6A04FDUL, 0x78D4B114D234A0D7UL, - 0x22840422BD6A5BB5UL, 0x5B9ABE0DE1B4410FUL, 0xB3B50007963FDD6BUL, - 0x53A8A46793B68E35UL, 0x8CDD8E8D188B6033UL, 0x5DD22B6E3ED49572UL, - 0xE561F5D27F5302D6UL, 0xDF89CEC3322E56CDUL, 0x87167F503D600F90UL, - 0x1698BB71C8201862UL, 0xF7BF5DFDB023108EUL, 0xA17FB15B66ACFB5FUL, - 0x2DD771987768073BUL, 0x19299D2D86E0EB29UL, 0x8B537B7F206EED29UL, - 0xE536DA153325ABFCUL, 0x30A69976796DB3B9UL, 0x8E65A2C94E2D4981UL, - 0xC301D53553BD6514UL, 0x46DF3639B9E43790UL, 0x3004CD0E5AFD0463UL, - 0x46E460B0F6ACA1A0UL, 0xCBA210E7372D9BD5UL, 0x45064274A74CA582UL, - 0xFDD57EA43CE631AEUL, 0xF2BA08FFA4A683D0UL, 0x8DA658C4DAD42999UL, - 0x7418042943E88040UL, 0x96000F72E9371FEFUL, 0xE9F1212DC8F47302UL, - 0x2AFB565ECC3553EDUL, 0xCD3D55137EFF7FD6UL, 0xD36F11059388E442UL, - 0xC4B47515DB5709DDUL, 0x5C363EFBF0BAAB67UL, 0x28C63B5A31650BBBUL, - 0x6AE54E5068061C81UL, 0xDEE62000F4E0AA21UL, 0xE8238672FE088A8BUL, - 0x9869CB6370F075B9UL, 0xBA376E2FC7DB330FUL, 0xB0F73E208487CDEEUL, - 0x359D5017BE37FE97UL, 0x684D828C7F95E2DCUL, 0x9985ECA20E46AE1FUL, - 0x8030A5137D1A21C4UL, 0xF78CDC00FC37AC39UL, 0x41CDDC8E61D9C644UL, - 0xB6F3CD1D833BAD1DUL, 0x301D0D858A23DE22UL, 0xA51FCA12AD849BC8UL, - 0x9F55E615986AB10EUL, 0x904AAA59854F2215UL, 0x12ECEA4AB40F51A7UL, - 0xB4EDF5807735E23BUL, 0x6190200F1C589478UL, 0xA3CA57F321909A5AUL, - 0x0BFAEE04B7325B63UL, 0x10C393E7FBCF826DUL, 0x4050A2CA53FDA708UL, - 0xF31114A9B462B680UL, 0x6FB9A6F121BA2006UL, 0x04550CF09389D602UL, - 0xB8C7D6D8CA8942F7UL, 0x71BB430C6436E9D1UL, 0xD9070DD5FAA0A10AUL, - 0x8FD6827757D07E5BUL, 0xD04E6C313F8FD974UL, 0x2CFDEA1187909B9AUL, - 0xC7A8E758C115F593UL, 0xA79A17663009ACC2UL, 0x8091A6B5372B141DUL, - 0xEB33B08767F5BA73UL, 0xDAC1F6823B6111C7UL, 0x697DF90C3515611BUL, - 0xCC1005F198761F48UL, 0x5067E4F5303B45A1UL, 0x04816D292A2D9AC2UL, - 0x2949C7A0874DD5E9UL, 0x25DB2547804CEE5EUL, 0x7EDC3A8946D6F229UL, - 0x00B586F67FD0C622UL, 0x3CAE5798E40324E0UL, 0x0A4F1437DE637164UL, - 0x5F59B2B715871981UL, 0x5D68FF31051E48FBUL, 0x0F2C369D73A2AA46UL, - 0xB009C6B53DD23399UL, 0xC366A81277084988UL, 0x5AF0E0CA0711E730UL, - 0x7AD831A9E9E854BAUL, 0x1DD5EDB0CA4E85AEUL, 0x54651209D259E9DDUL, - 0x3EBB1D9DAB237EADUL, 0xDA96989317AC464CUL, 0xBFCB0F8FBC52C74EUL, - 0x9597ACB9E27B692EUL, 0x6F436B1643C95B23UL, 0xB81A1253E1C3CD9DUL, - 0x7B35F37E905EC67EUL, 0x29CE62666EDA76DDUL, 0xFF2490DC1EC4014DUL, - 0x2D4FF9124DD6B5C4UL, 0xB9510FEC23E0E9D1UL, 0x8BCDBC56541ED071UL, - 0x5414E097C1B0CCB2UL, 0x82BEF8213076F5C7UL, 0xE9FC9A71BD512615UL, - 0xCF15ECC39490DF5AUL, 0x49FA9328D8EE97DBUL, 0x5F80FF0153BC2145UL, - 0xF63BA156B55BCB02UL, 0x0E3B9113109FDF36UL, 0x8FCD6528F54EDE69UL, - 0x5D6AE9C000054763UL, 0x326D873633431FBBUL, 0x380E07FFCEF7A978UL, - 0xDCAA09874A1DF230UL, 0x601494F49F6D261EUL, 0x856159486C9B60E3UL, - 0x85C7F822D07089A5UL, 0xAFFB99CF5AB836C2UL, 0x241AD414FBBB956BUL, - 0x0CFC042822831692UL, 0x382B16D049727FF2UL, 0x784F9997633C819AUL, - 0x5C40ED725F6C390AUL, 0x2CE78B7A3331BA9CUL, 0x9C80636639963B41UL, - 0x1B2D41C968355018UL, 0xD189B57691FB60E4UL, 0x3BD599A9DD85CE31UL, - 0x46FC8E2EF0B9A77CUL, 0x1A389E07D0075EA4UL, 0x1622CA52401DF2ACUL, - 0x528F3FF9B7993BFAUL, 0xF16C176CCA292DDBUL, 0x6C154010961EF542UL, - 0x04B78E92BF6C82DFUL, 0x7D9AFEA6ABB46072UL, 0x3BC573291CBFFC2EUL, - 0x277FFF096D567AF3UL, 0x1CBEB86841A6F757UL, 0xD0BCD49E76CA20A7UL, - 0x25B6024756B1FE90UL, 0xE685C04EF84881FBUL, 0xDCAB14B352FC442EUL, - 0x4FF80A521719953DUL, 0xD10425E411DBE94BUL, 0x60998D0507D6E38DUL, - 0x146AA432C981BD5EUL, 0x1729A596282AAA41UL, 0x152BE1A263BAF963UL, - 0x15278DF497D254CAUL, 0xE4B5E9891E88A5DAUL, 0x087FA3472067D0ACUL, - 0xD99C2899A0AD9158UL, 0x5040F234DC531236UL, 0x9D7E1531259EEE90UL, - 0x29AFB8B49391036EUL, 0x84B619599642D68EUL, 0xE838AAE0F249545CUL, - 0x42D524BA8BB96959UL, 0x9A5B3E817A20EE59UL, 0x584F0530EC4C566BUL, - 0xD6544FD14B47F945UL, 0x3613FB3B553A7CDEUL, 0x284E92B56831AA56UL, - 0xCEE89BA10E951A22UL, 0x476806FA1A8A44E0UL, 0xC84CEF151885C1DFUL, - 0x3DB1D5C1B0B73936UL, 0x45D2D90FDF452388UL, 0x038A7DD71BC5DD21UL, - 0x2AC90C7422C56819UL, 0x4742046638ECE0FBUL, 0x553B44360FC8495DUL, - 0xC8DBA1CF3F9A6E97UL, 0xF85919F494CAB021UL, 0x1479455C2FF236AFUL, - 0x29BCAD159F7D018DUL, 0x016DFF51CBA3BCC5UL, 0x234BF8A77F6B1CF5UL, - 0x20564C6F44F9E641UL, 0x063A550C5AA50FA8UL, 0xB063D0AAAA96DFECUL, - 0x3EC659DF42C092F8UL, 0x29D4A76A5A5F7E09UL, 0x65EFF3EE6E691D1EUL, - 0xBD1634F5721CF799UL, 0xE85BD016723B43FFUL, 0x5233E9E7AEA11022UL, - 0x8C68852EA9039B4CUL, 0x2C978ADBE885BC15UL, 0x726615ED9D497550UL, - 0x7C1E145EB8D2BD96UL, 0xC2FEFB25935A5D71UL, 0x9EE9C3E1C3DE416FUL, - 0xFFD568A03E20E0B3UL, 0xF53649AD90156F2AUL, 0x0331B91DCE54E7EDUL, - 0x67CED5A86E99392FUL, 0x16FC0A5815500B05UL, 0x030392E8D24A7C00UL, - 0x232E5E31DF32A7B5UL, 0xCC8BF22B1947DF21UL, 0x4EC2C72D9C1EEABDUL, - 0x0B1B79F45220E668UL, 0xCC3CF0EE9C4A899BUL, 0xFC260A60592EBC80UL, - 0xC1989A0382CB03EDUL, 0x35FE679A6CD800B2UL, 0x8A6B1ADE4FBB162FUL, - 0xB0FD284563625295UL, 0xCDCC1C7B2181D024UL, 0x5B8BA0C895C0BB23UL, - 0xA681FEA9ADCD43DBUL, 0x0FE30FB6876DE718UL, 0x6DDD1E27B769C494UL, - 0x83A1E58460FFE8BBUL, 0x8FAD6FD2DC90FF65UL, 0x41BB28B81201CB24UL, - 0xA148CE79B2597204UL, 0x7CB87DF97BB477A6UL, 0x9F79E6DED87DC688UL, - 0xE044D84A6C758171UL, 0x1A29E750D9EC4097UL, 0x8445FC2B80C4A0F5UL, - 0x5EFD9784AFED4ED2UL, 0x344C252BD90EB0E4UL, 0xEAD18D2E4418E5B5UL, - 0x207EF4FFC260BD24UL, 0xD2E5C3AE534EC538UL, 0x2F5A59BF3D10E7E1UL, - 0x9528E29266C2924CUL, 0x0121B6BDAB45D138UL, 0xADD0256ACBC771DDUL, - 0x7301769600C6C50DUL, 0x3E7404EA8231D497UL, 0xB39B3840B8D03117UL, - 0x56EFCEDDEF5B6634UL, 0xE6BE2C0D73B72098UL, 0x5A2841A21A5E4959UL, - 0xCFEB3586156DF6E0UL, 0xD84F58901E2D65B8UL, 0x79796786CCC59703UL, - 0x13BFA9A94DD07696UL, 0x7B63116A6B5458B6UL, 0x1406628176C932E0UL, - 0xDD7ACC4E97F91B1AUL, 0xC82B8F84A56BDBE8UL, 0x325D87D08ED8B0B0UL, - 0x3F7847B1E82002DDUL, 0x4662900D2ADAF6BFUL, 0x12AE9F58561DB1D7UL, - 0xA896E956A95CC074UL, 0xAA4FA3A2F8BA4084UL, 0x1D577E35F5DCA67FUL, - 0x796FF2D75469DEC2UL, 0xBD3F3F87E4DE894EUL, 0x3666B2262DEBFB6BUL, - 0x1E26D7AEEF976C2EUL, 0x6BC3854F867AC4A0UL, 0x743DBF8C2E95A821UL, - 0xA62A76B9AE2E645AUL, 0xB4D76561F40187C1UL, 0xD3E5F23F9FA5DB25UL, - 0x34B1F6B39E6A87E2UL, 0x7DA5C3DFF7BE72CFUL, 0xFDF46B1BE80AD4F9UL, - 0x0B21121CA9653B8AUL, 0x1199CA9D0A90F21AUL, 0x6021EA302D01E0BAUL, - 0x8101D063C05CF5D4UL, 0xE2652410DFE78F23UL, 0x84095ACF47C21A25UL, - 0xD7E29A4DB2FD3A99UL, 0x7793C0CB57959F93UL, 0x94C605308B9E5AA7UL, - 0x943DB1AC54165B8FUL, 0xC1391A544C07447CUL, 0x3FEF1A61F785D97BUL, - 0x6DFCC3152478BDE4UL, 0x312AFB0E1982933AUL, 0xB8069C2605631ED3UL, - 0x5A6076423430BED2UL, 0x34E379F09E2D4F42UL, 0x9167F5E4019573E3UL, - 0x18F81157828D2A49UL, 0xF4A8723B4955EAB8UL, 0x0BE6C0ABFEA9E8A6UL, - 0xC63ADCF2CEF25556UL, 0xC5EBD3BEAE9F364FUL, 0xA301D60CF5B6F2FCUL, - 0x8C606CA881D27A00UL, 0x826FEE13B554C18AUL, 0x8DF251716F10B776UL, - 0xB2573A33AC7D94FFUL, 0xC0E771248CB7ABB9UL, 0x753DD605DB38F4EAUL, - 0x21901664C3D92114UL, 0xA408FCB7A1892612UL, 0x3084FC64A03D6722UL, - 0xC8C9D9569AD42A34UL, 0x1FBFBAFC1694B383UL, 0x1894280CC3F94ABEUL, - 0xE14C38A7BBB54651UL, 0x23A48CC84A6EB704UL, 0xD034ADC45AABEDBDUL, - 0xC93F2C21C973C766UL, 0x66A8AEC11D743CC6UL, 0xB4F72AA52E37C145UL, - 0xB02834DF0D9266B4UL, 0xDB8E724EA1FF402FUL, 0x531E9C058112E352UL, - 0xC2F692531DB317D2UL, 0xEFC9586498D263A7UL, 0x84F2C524D2F3ADB9UL, - 0xAFAF02C27CF25D08UL, 0x385873595F9CFC09UL, 0x36DDA10D1A152B7AUL, - 0x9F9B997A0DACFB55UL, 0x10AB5EB5C4714882UL, 0x7BA4E8703E22B7EEUL, - 0x0A2BFD558607BCC8UL, 0x201D3706F74F8BA1UL, 0x3DBD573B1358F02EUL, - 0x5B37645FA93BCEBCUL, 0xC0166864BC1A7544UL, 0x45C7AA5559FC65D7UL, - 0xEFEA04AA83349B78UL, 0x607859194F9E9FD8UL, 0xA6B9AE5B53CF7710UL, - 0x73B9142ACBC50821UL, 0x8B7D67495887E65CUL, 0x39B6C4FB2B232E56UL, - 0xD212DB10E31D2A68UL, 0x629AC0A3D263DC6EUL, 0x6BC2E7FF912050BAUL, - 0xE0AD5A8FDB183F62UL, 0xF05648134F0C6F0FUL, 0x31E146F4AF980FDAUL, - 0x7FAF0078D84D62CCUL, 0xE13F044C2830D21EUL, 0x49A047AD204B4C4BUL, - 0xF3AFBE2237351A74UL, 0x32826C9217BB07EDUL, 0xD4C3AEB099319B5CUL, - 0x49CE5BD05B2B0F61UL, 0x75DD36984DCBD0A2UL, 0x84EC5D7C2F0AAC6CUL, - 0x8E59CC9B9942EDDFUL, 0x89FF85DCDF9AE895UL, 0x6F9EE0D8D9E8D414UL, - 0x10E01A59058D3904UL, 0x1DFAF567BFF55D2EUL, 0x8DD6A18C03382CD4UL, - 0xE12FD89A0CF58553UL, 0xE245DA902C0C4F5CUL, 0x8BE7566B9987520DUL, - 0x4CA1C0A4B38A3098UL, 0x81E45015BE618A72UL, 0xA80E0344FF27EFDFUL, - 0xC98DAEC6DC5005BAUL, 0xF56873F3A958AE5EUL, 0xDB88604670C794ACUL, - 0x4F68E448DDF6535FUL, 0x3443DBF1CA6031A8UL, 0x73DFA5DEEF409A41UL, - 0xA7C556941F6643B2UL, 0x424BC40D8C83D962UL, 0x6F292A325B99B726UL, - 0x6EECB1009717D65EUL, 0x899BE4CE7BB2D8EEUL, 0x25285FED3E59781DUL, - 0x14C5AEDD76E092D3UL, 0x9BB5EE10567640AEUL, 0xCD62A1D43558FD06UL, - 0x70A7B09FC5F39447UL, 0xF10064AE92EFFB99UL, 0xD55FA1A918A23082UL, - 0xD03F28AD25C73A78UL, 0x76CFFFEE094D8B0EUL, 0x4FD5A46AD5A4B4CFUL, - 0x8F3A36F9D7BF87E3UL, 0x64224315210625BEUL, 0x749A131B71B64350UL, - 0x9034FF9DAC089F48UL, 0xB58D3017E7321217UL, 0x549D818937D5CE90UL, - 0x903CE1452419E99CUL, 0xFD052F0388DB2E76UL, 0x7390051E3972480EUL, - 0x5E5F6EC3F27B3679UL, 0x3E3637D4D4EE917DUL, 0x4FE04068CA2A4309UL, - 0x98C9C17454AAE42DUL, 0x659AE0BDB113BC21UL, 0x4C0BDECB1511AF4CUL, - 0x17048BFAEAC0006DUL, 0x68F106AADAA64912UL, 0x2286234ECEB7EAF0UL, - 0x350CD42CAF697E51UL, 0x8DCDE6D1FAC19A9FUL, 0xF97E55A245A8A8A2UL, - 0xAAE86B2092DA90A3UL, 0x5123E878AA8AEF76UL, 0x022B88B9694A55F6UL, - 0xC4C1A9B1C0221985UL, 0x20056D91DD5E0FFEUL, 0xF5BF61EC225C9843UL, - 0x1A315A05BDCF4A31UL, 0x5710A21A8DF4F15FUL, 0x99BD1A0AF97AD027UL, - 0x7602C5997AD4E12CUL - }; + internal static int MaxLength + { + get + { + Debug.Assert(m_dicts.Count > 0); // Should be initialized + + int iMaxLen = 0; + foreach(int iLen in m_dicts.Keys) + { + if(iLen > iMaxLen) iMaxLen = iLen; + } + + return iMaxLen; + } + } + + internal static bool ContainsLength(int nLength) + { + Dictionary dDummy; + return m_dicts.TryGetValue(nLength, out dDummy); + } public static bool IsPopularPassword(char[] vPassword) { - Debug.Assert(PpcTable.Length == (PpcTableSize / 64)); + ulong uDummy; + return IsPopularPassword(vPassword, out uDummy); + } + public static bool IsPopularPassword(char[] vPassword, out ulong uDictSize) + { if(vPassword == null) throw new ArgumentNullException("vPassword"); - if(vPassword.Length == 0) return false; + if(vPassword.Length == 0) { uDictSize = 0; return false; } - foreach(char ch in vPassword) + string str = new string(vPassword); + + try { return IsPopularPasswordPriv(str, out uDictSize); } + catch(Exception) { Debug.Assert(false); } + + uDictSize = 0; + return false; + } + + private static bool IsPopularPasswordPriv(string str, out ulong uDictSize) + { + Debug.Assert(m_dicts.Count > 0); // Should be initialized with data + + Dictionary d; + if(!m_dicts.TryGetValue(str.Length, out d)) { - if(!IsPopularChar(ch)) return false; + uDictSize = 0; + return false; } - byte[] pbUtf8 = StrUtil.Utf8.GetBytes(vPassword); + uDictSize = (ulong)d.Count; + return d.ContainsKey(str); + } - int[] vIndices = GetTableIndices(pbUtf8, PpcTableSize); - Array.Clear(pbUtf8, 0, pbUtf8.Length); - - foreach(int iIndex in vIndices) + public static void Add(byte[] pbData, bool bGZipped) + { + try { - if(!GetTableBit(PpcTable, iIndex)) return false; - } + if(bGZipped) + pbData = MemUtil.Decompress(pbData); - return true; - } + string strData = StrUtil.Utf8.GetString(pbData, 0, pbData.Length); + if(string.IsNullOrEmpty(strData)) { Debug.Assert(false); return; } - private static bool IsPopularChar(char ch) - { - return (((ch >= 'A') && (ch <= 'Z')) || ((ch >= 'a') && (ch <= 'z')) || - ((ch >= '0') && (ch <= '9')) || (ch == '_') || (ch == '!')); - } + if(!char.IsWhiteSpace(strData[strData.Length - 1])) + strData += "\n"; - private static int[] GetTableIndices(byte[] pbPasswordUtf8, int nTableSize) - { - Debug.Assert((nTableSize >= 2) && (nTableSize <= 0x10000)); - Debug.Assert((nTableSize % 64) == 0); - - SHA512Managed sha = new SHA512Managed(); - byte[] pbHash = sha.ComputeHash(pbPasswordUtf8); - - int[] vIndices = new int[pbHash.Length / 2]; - for(int i = 0; i < vIndices.Length; ++i) - vIndices[i] = ((((int)pbHash[i * 2] << 8) | - (int)pbHash[i * 2 + 1]) % nTableSize); - - return vIndices; - } - - private static bool GetTableBit(ulong[] vTable, int iBit) - { - return ((vTable[iBit >> 6] & (1UL << (iBit & 0x3F))) != 0UL); - } - -#if (DEBUG && !KeePassLibSD) - private static bool SetTableBit(ulong[] vTable, int iBit) - { - if(GetTableBit(vTable, iBit)) return false; - - vTable[iBit >> 6] = (vTable[iBit >> 6] | (1UL << (iBit & 0x3F))); - return true; - } - - public static void MakeList() - { - string strData = File.ReadAllText("MostPopularPasswords.txt", StrUtil.Utf8); - strData += " "; - CharStream cs = new CharStream(strData); - - List vPasswords = new List(); - StringBuilder sbPassword = new StringBuilder(); - while(true) - { - char ch = cs.ReadChar(); - if(ch == char.MinValue) break; - - if(char.IsWhiteSpace(ch)) + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < strData.Length; ++i) { - string strPassword = sbPassword.ToString(); - strPassword = strPassword.ToLower(); + char ch = strData[i]; - if(strPassword.Length > 3) + if(char.IsWhiteSpace(ch)) { - if(vPasswords.IndexOf(strPassword) < 0) - vPasswords.Add(strPassword); + int cc = sb.Length; + if(cc > 0) + { + string strWord = sb.ToString(); + Debug.Assert(strWord.Length == cc); + + Dictionary d; + if(!m_dicts.TryGetValue(cc, out d)) + { + d = new Dictionary(); + m_dicts[cc] = d; + } + + d[strWord] = true; + sb.Remove(0, cc); + } } - - sbPassword = new StringBuilder(); - } - else - { - Debug.Assert(!char.IsControl(ch) && !char.IsHighSurrogate(ch) && - !char.IsLowSurrogate(ch) && !char.IsSurrogate(ch)); - Debug.Assert(IsPopularChar(ch)); - sbPassword.Append(ch); + else sb.Append(char.ToLower(ch)); } } - - ulong[] vTable = new ulong[PpcTableSize / 64]; - Array.Clear(vTable, 0, vTable.Length); - - long lBitsInTable = 0; - foreach(string strPassword in vPasswords) - { - byte[] pbUtf8 = StrUtil.Utf8.GetBytes(strPassword); - int[] vIndices = GetTableIndices(pbUtf8, PpcTableSize); - - foreach(int i in vIndices) - { - if(SetTableBit(vTable, i)) ++lBitsInTable; - } - } - - StringBuilder sb = new StringBuilder(); - sb.Append("\t\t\t"); - for(int i = 0; i < vTable.Length; ++i) - { - if(i > 0) - { - if((i % 3) == 0) - { - sb.AppendLine(","); - sb.Append("\t\t\t"); - } - else sb.Append(", "); - } - - sb.Append("0x"); - sb.Append(vTable[i].ToString("X16")); - sb.Append("UL"); - } - - sb.AppendLine(); - sb.AppendLine(); - sb.AppendLine("Bits set: " + lBitsInTable.ToString() + " of " + - PpcTableSize.ToString()); - int cHashFn = GetTableIndices(StrUtil.Utf8.GetBytes("Dummy"), PpcTableSize).Length; - sb.AppendLine("Hash functions: " + cHashFn.ToString()); - double dblPhi = Math.Pow(1.0 - ((double)cHashFn / PpcTableSize), - (double)vPasswords.Count); - sb.AppendLine("Phi (bits unset ratio) estimation: " + - dblPhi.ToString(CultureInfo.InvariantCulture)); - dblPhi = ((double)(PpcTableSize - lBitsInTable) / (double)PpcTableSize); - sb.AppendLine("Exact Phi: " + dblPhi.ToString(CultureInfo.InvariantCulture)); - sb.AppendLine("False positives ratio: " + Math.Pow(1.0 - dblPhi, - (double)cHashFn).ToString(CultureInfo.InvariantCulture)); - - File.WriteAllText("Table.txt", sb.ToString()); + catch(Exception) { Debug.Assert(false); } } -#endif } } -#endif diff --git a/src/KeePassLib2Android/Cryptography/QualityEstimation.cs b/src/KeePassLib2Android/Cryptography/QualityEstimation.cs index 0d5e6297..d85ee3f9 100644 --- a/src/KeePassLib2Android/Cryptography/QualityEstimation.cs +++ b/src/KeePassLib2Android/Cryptography/QualityEstimation.cs @@ -1,6 +1,6 @@ -/* +/* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,9 +19,10 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Text; +using System.Diagnostics; +using KeePassLib.Cryptography.PasswordGenerator; using KeePassLib.Utility; namespace KeePassLib.Cryptography @@ -32,13 +33,286 @@ namespace KeePassLib.Cryptography /// public static class QualityEstimation { - private enum CharSpaceBits : uint + private static class PatternID { - Control = 32, - Alpha = 26, - Number = 10, - Special = 33, - High = 112 + public const char LowerAlpha = 'L'; + public const char UpperAlpha = 'U'; + public const char Digit = 'D'; + public const char Special = 'S'; + public const char High = 'H'; + public const char Other = 'X'; + + public const char Dictionary = 'W'; + public const char Repetition = 'R'; + public const char Number = 'N'; + public const char DiffSeq = 'C'; + + public const string All = "LUDSHXWRNC"; + } + + // private static class CharDistrib + // { + // public static readonly ulong[] LowerAlpha = new ulong[26] { + // 884, 211, 262, 249, 722, 98, 172, 234, 556, 124, 201, 447, 321, + // 483, 518, 167, 18, 458, 416, 344, 231, 105, 80, 48, 238, 76 + // }; + // public static readonly ulong[] UpperAlpha = new ulong[26] { + // 605, 188, 209, 200, 460, 81, 130, 163, 357, 122, 144, 332, 260, + // 317, 330, 132, 18, 320, 315, 250, 137, 76, 60, 36, 161, 54 + // }; + // public static readonly ulong[] Digit = new ulong[10] { + // 574, 673, 524, 377, 339, 336, 312, 310, 357, 386 + // }; + // } + + private sealed class QeCharType + { + private readonly char m_chTypeID; + public char TypeID { get { return m_chTypeID; } } + + private readonly string m_strAlph; + public string Alphabet { get { return m_strAlph; } } + + private readonly int m_nChars; + public int CharCount { get { return m_nChars; } } + + private readonly char m_chFirst; + private readonly char m_chLast; + + private readonly double m_dblCharSize; + public double CharSize { get { return m_dblCharSize; } } + + public QeCharType(char chTypeID, string strAlphabet, bool bIsConsecutive) + { + if(strAlphabet == null) throw new ArgumentNullException(); + if(strAlphabet.Length == 0) throw new ArgumentException(); + + m_chTypeID = chTypeID; + m_strAlph = strAlphabet; + m_nChars = m_strAlph.Length; + m_chFirst = (bIsConsecutive ? m_strAlph[0] : char.MinValue); + m_chLast = (bIsConsecutive ? m_strAlph[m_nChars - 1] : char.MinValue); + + m_dblCharSize = Log2(m_nChars); + + Debug.Assert(((int)(m_chLast - m_chFirst) == (m_nChars - 1)) || + !bIsConsecutive); + } + + public QeCharType(char chTypeID, int nChars) // Catch-none set + { + if(nChars <= 0) throw new ArgumentOutOfRangeException(); + + m_chTypeID = chTypeID; + m_strAlph = string.Empty; + m_nChars = nChars; + m_chFirst = char.MinValue; + m_chLast = char.MinValue; + + m_dblCharSize = Log2(m_nChars); + } + + public bool Contains(char ch) + { + if(m_chLast != char.MinValue) + return ((ch >= m_chFirst) && (ch <= m_chLast)); + + Debug.Assert(m_strAlph.Length > 0); // Don't call for catch-none set + return (m_strAlph.IndexOf(ch) >= 0); + } + } + + private sealed class EntropyEncoder + { + private readonly string m_strAlph; + private Dictionary m_dHisto = new Dictionary(); + private readonly ulong m_uBaseWeight; + private readonly ulong m_uCharWeight; + private readonly ulong m_uOccExclThreshold; + + public EntropyEncoder(string strAlphabet, ulong uBaseWeight, + ulong uCharWeight, ulong uOccExclThreshold) + { + if(strAlphabet == null) throw new ArgumentNullException(); + if(strAlphabet.Length == 0) throw new ArgumentException(); + + m_strAlph = strAlphabet; + m_uBaseWeight = uBaseWeight; + m_uCharWeight = uCharWeight; + m_uOccExclThreshold = uOccExclThreshold; + +#if DEBUG + Dictionary d = new Dictionary(); + foreach(char ch in m_strAlph) { d[ch] = true; } + Debug.Assert(d.Count == m_strAlph.Length); // No duplicates +#endif + } + + public void Reset() + { + m_dHisto.Clear(); + } + + public void Write(char ch) + { + Debug.Assert(m_strAlph.IndexOf(ch) >= 0); + + ulong uOcc; + m_dHisto.TryGetValue(ch, out uOcc); + Debug.Assert(m_dHisto.ContainsKey(ch) || (uOcc == 0)); + m_dHisto[ch] = uOcc + 1; + } + + public double GetOutputSize() + { + ulong uTotalWeight = m_uBaseWeight * (ulong)m_strAlph.Length; + foreach(ulong u in m_dHisto.Values) + { + Debug.Assert(u >= 1); + if(u > m_uOccExclThreshold) + uTotalWeight += (u - m_uOccExclThreshold) * m_uCharWeight; + } + + double dSize = 0.0, dTotalWeight = (double)uTotalWeight; + foreach(ulong u in m_dHisto.Values) + { + ulong uWeight = m_uBaseWeight; + if(u > m_uOccExclThreshold) + uWeight += (u - m_uOccExclThreshold) * m_uCharWeight; + + dSize -= (double)u * Log2((double)uWeight / dTotalWeight); + } + + return dSize; + } + } + + private sealed class MultiEntropyEncoder + { + private Dictionary m_dEncs = + new Dictionary(); + + public MultiEntropyEncoder() + { + } + + public void AddEncoder(char chTypeID, EntropyEncoder ec) + { + if(ec == null) { Debug.Assert(false); return; } + + Debug.Assert(!m_dEncs.ContainsKey(chTypeID)); + m_dEncs[chTypeID] = ec; + } + + public void Reset() + { + foreach(EntropyEncoder ec in m_dEncs.Values) { ec.Reset(); } + } + + public bool Write(char chTypeID, char chData) + { + EntropyEncoder ec; + if(!m_dEncs.TryGetValue(chTypeID, out ec)) + return false; + + ec.Write(chData); + return true; + } + + public double GetOutputSize() + { + double d = 0.0; + + foreach(EntropyEncoder ec in m_dEncs.Values) + { + d += ec.GetOutputSize(); + } + + return d; + } + } + + private sealed class QePatternInstance + { + private readonly int m_iPos; + public int Position { get { return m_iPos; } } + + private readonly int m_nLen; + public int Length { get { return m_nLen; } } + + private readonly char m_chPatternID; + public char PatternID { get { return m_chPatternID; } } + + private readonly double m_dblCost; + public double Cost { get { return m_dblCost; } } + + private readonly QeCharType m_ctSingle; + public QeCharType SingleCharType { get { return m_ctSingle; } } + + public QePatternInstance(int iPosition, int nLength, char chPatternID, + double dblCost) + { + m_iPos = iPosition; + m_nLen = nLength; + m_chPatternID = chPatternID; + m_dblCost = dblCost; + m_ctSingle = null; + } + + public QePatternInstance(int iPosition, int nLength, QeCharType ctSingle) + { + m_iPos = iPosition; + m_nLen = nLength; + m_chPatternID = ctSingle.TypeID; + m_dblCost = ctSingle.CharSize; + m_ctSingle = ctSingle; + } + } + + private sealed class QePathState + { + public readonly int Position; + public readonly List Path; + + public QePathState(int iPosition, List lPath) + { + this.Position = iPosition; + this.Path = lPath; + } + } + + private static object m_objSyncInit = new object(); + private static List m_lCharTypes = null; + + private static void EnsureInitialized() + { + lock(m_objSyncInit) + { + if(m_lCharTypes == null) + { + string strSpecial = PwCharSet.PrintableAsciiSpecial; + if(strSpecial.IndexOf(' ') >= 0) { Debug.Assert(false); } + else strSpecial = strSpecial + " "; + + int nSp = strSpecial.Length; + int nHi = PwCharSet.HighAnsiChars.Length; + + m_lCharTypes = new List(); + + m_lCharTypes.Add(new QeCharType(PatternID.LowerAlpha, + PwCharSet.LowerCase, true)); + m_lCharTypes.Add(new QeCharType(PatternID.UpperAlpha, + PwCharSet.UpperCase, true)); + m_lCharTypes.Add(new QeCharType(PatternID.Digit, + PwCharSet.Digits, true)); + m_lCharTypes.Add(new QeCharType(PatternID.Special, + strSpecial, false)); + m_lCharTypes.Add(new QeCharType(PatternID.High, + PwCharSet.HighAnsiChars, false)); + m_lCharTypes.Add(new QeCharType(PatternID.Other, + 0x10000 - (2 * 26) - 10 - nSp - nHi)); + } + } } /// @@ -46,84 +320,95 @@ namespace KeePassLib.Cryptography /// /// Password to check. /// Estimated bit-strength of the password. - /// Thrown if the input - /// parameter is null. public static uint EstimatePasswordBits(char[] vPasswordChars) { - Debug.Assert(vPasswordChars != null); - if(vPasswordChars == null) throw new ArgumentNullException("vPasswordChars"); + if(vPasswordChars == null) { Debug.Assert(false); return 0; } + if(vPasswordChars.Length == 0) return 0; - bool bChLower = false, bChUpper = false, bChNumber = false; - bool bChSpecial = false, bChHigh = false, bChControl = false; - Dictionary vCharCounts = new Dictionary(); - Dictionary vDifferences = new Dictionary(); - double dblEffectiveLength = 0.0; + EnsureInitialized(); - for(int i = 0; i < vPasswordChars.Length; ++i) // Get character types + int n = vPasswordChars.Length; + List[] vPatterns = new List[n]; + for(int i = 0; i < n; ++i) { - char tch = vPasswordChars[i]; + vPatterns[i] = new List(); - if(tch < ' ') bChControl = true; - else if((tch >= 'A') && (tch <= 'Z')) bChUpper = true; - else if((tch >= 'a') && (tch <= 'z')) bChLower = true; - else if((tch >= '0') && (tch <= '9')) bChNumber = true; - else if((tch >= ' ') && (tch <= '/')) bChSpecial = true; - else if((tch >= ':') && (tch <= '@')) bChSpecial = true; - else if((tch >= '[') && (tch <= '`')) bChSpecial = true; - else if((tch >= '{') && (tch <= '~')) bChSpecial = true; - else if(tch > '~') bChHigh = true; + QePatternInstance piChar = new QePatternInstance(i, 1, + GetCharType(vPasswordChars[i])); + vPatterns[i].Add(piChar); + } - double dblDiffFactor = 1.0; - if(i >= 1) + FindRepetitions(vPasswordChars, vPatterns); + FindNumbers(vPasswordChars, vPatterns); + FindDiffSeqs(vPasswordChars, vPatterns); + FindPopularPasswords(vPasswordChars, vPatterns); + + // Encoders must not be static, because the entropy estimation + // may run concurrently in multiple threads and the encoders are + // not read-only + EntropyEncoder ecPattern = new EntropyEncoder(PatternID.All, 0, 1, 0); + MultiEntropyEncoder mcData = new MultiEntropyEncoder(); + for(int i = 0; i < (m_lCharTypes.Count - 1); ++i) + { + // Let m be the alphabet size. In order to ensure that two same + // characters cost at least as much as a single character, for + // the probability p and weight w of the character it must hold: + // -log(1/m) >= -2*log(p) + // <=> log(1/m) <= log(p^2) <=> 1/m <= p^2 <=> p >= sqrt(1/m); + // sqrt(1/m) = (1+w)/(m+w) + // <=> m+w = (1+w)*sqrt(m) <=> m+w = sqrt(m) + w*sqrt(m) + // <=> w*(1-sqrt(m)) = sqrt(m) - m <=> w = (sqrt(m)-m)/(1-sqrt(m)) + // <=> w = (sqrt(m)-m)*(1+sqrt(m))/(1-m) + // <=> w = (sqrt(m)-m+m-m*sqrt(m))/(1-m) <=> w = sqrt(m) + ulong uw = (ulong)Math.Sqrt((double)m_lCharTypes[i].CharCount); + + mcData.AddEncoder(m_lCharTypes[i].TypeID, new EntropyEncoder( + m_lCharTypes[i].Alphabet, 1, uw, 1)); + } + + double dblMinCost = (double)int.MaxValue; + int tStart = Environment.TickCount; + + Stack sRec = new Stack(); + sRec.Push(new QePathState(0, new List())); + while(sRec.Count > 0) + { + int tDiff = Environment.TickCount - tStart; + if(tDiff > 500) break; + + QePathState s = sRec.Pop(); + + if(s.Position >= n) { - int iDiff = (int)tch - (int)vPasswordChars[i - 1]; + Debug.Assert(s.Position == n); - uint uDiffCount; - if(vDifferences.TryGetValue(iDiff, out uDiffCount)) - { - ++uDiffCount; - vDifferences[iDiff] = uDiffCount; - dblDiffFactor /= (double)uDiffCount; - } - else vDifferences.Add(iDiff, 1); - } - - uint uCharCount; - if(vCharCounts.TryGetValue(tch, out uCharCount)) - { - ++uCharCount; - vCharCounts[tch] = uCharCount; - dblEffectiveLength += dblDiffFactor * (1.0 / (double)uCharCount); + double dblCost = ComputePathCost(s.Path, vPasswordChars, + ecPattern, mcData); + if(dblCost < dblMinCost) dblMinCost = dblCost; } else { - vCharCounts.Add(tch, 1); - dblEffectiveLength += dblDiffFactor; + List lSubs = vPatterns[s.Position]; + for(int i = lSubs.Count - 1; i >= 0; --i) + { + QePatternInstance pi = lSubs[i]; + Debug.Assert(pi.Position == s.Position); + Debug.Assert(pi.Length >= 1); + + List lNewPath = + new List(s.Path.Count + 1); + lNewPath.AddRange(s.Path); + lNewPath.Add(pi); + Debug.Assert(lNewPath.Capacity == (s.Path.Count + 1)); + + QePathState sNew = new QePathState(s.Position + + pi.Length, lNewPath); + sRec.Push(sNew); + } } } - uint uCharSpace = 0; - if(bChControl) uCharSpace += (uint)CharSpaceBits.Control; - if(bChUpper) uCharSpace += (uint)CharSpaceBits.Alpha; - if(bChLower) uCharSpace += (uint)CharSpaceBits.Alpha; - if(bChNumber) uCharSpace += (uint)CharSpaceBits.Number; - if(bChSpecial) uCharSpace += (uint)CharSpaceBits.Special; - if(bChHigh) uCharSpace += (uint)CharSpaceBits.High; - - if(uCharSpace == 0) return 0; - - double dblBitsPerChar = Math.Log((double)uCharSpace) / Math.Log(2.0); - double dblRating = dblBitsPerChar * dblEffectiveLength; - -#if !KeePassLibSD - char[] vLowerCopy = new char[vPasswordChars.Length]; - for(int ilc = 0; ilc < vLowerCopy.Length; ++ilc) - vLowerCopy[ilc] = char.ToLower(vPasswordChars[ilc]); - if(PopularPasswords.IsPopularPassword(vLowerCopy)) dblRating /= 8.0; - Array.Clear(vLowerCopy, 0, vLowerCopy.Length); -#endif - - return (uint)Math.Ceiling(dblRating); + return (uint)Math.Ceiling(dblMinCost); } /// @@ -141,5 +426,343 @@ namespace KeePassLib.Cryptography return uResult; } + + private static QeCharType GetCharType(char ch) + { + int nTypes = m_lCharTypes.Count; + Debug.Assert((nTypes > 0) && (m_lCharTypes[nTypes - 1].CharCount > 256)); + + for(int i = 0; i < (nTypes - 1); ++i) + { + if(m_lCharTypes[i].Contains(ch)) + return m_lCharTypes[i]; + } + + return m_lCharTypes[nTypes - 1]; + } + + private static double ComputePathCost(List l, + char[] vPassword, EntropyEncoder ecPattern, MultiEntropyEncoder mcData) + { + ecPattern.Reset(); + for(int i = 0; i < l.Count; ++i) + ecPattern.Write(l[i].PatternID); + double dblPatternCost = ecPattern.GetOutputSize(); + + mcData.Reset(); + double dblDataCost = 0.0; + foreach(QePatternInstance pi in l) + { + QeCharType tChar = pi.SingleCharType; + if(tChar != null) + { + char ch = vPassword[pi.Position]; + if(!mcData.Write(tChar.TypeID, ch)) + dblDataCost += pi.Cost; + } + else dblDataCost += pi.Cost; + } + dblDataCost += mcData.GetOutputSize(); + + return (dblPatternCost + dblDataCost); + } + + private static void FindPopularPasswords(char[] vPassword, + List[] vPatterns) + { + int n = vPassword.Length; + + char[] vLower = new char[n]; + char[] vLeet = new char[n]; + for(int i = 0; i < n; ++i) + { + char ch = vPassword[i]; + + vLower[i] = char.ToLower(ch); + vLeet[i] = char.ToLower(DecodeLeetChar(ch)); + } + + char chErased = default(char); + Debug.Assert(chErased == char.MinValue); + + int nMaxLen = Math.Min(n, PopularPasswords.MaxLength); + for(int nSubLen = nMaxLen; nSubLen >= 3; --nSubLen) + { + if(!PopularPasswords.ContainsLength(nSubLen)) continue; + + char[] vSub = new char[nSubLen]; + + for(int i = 0; i <= (n - nSubLen); ++i) + { + if(Array.IndexOf(vLower, chErased, i, nSubLen) >= 0) + continue; + + Array.Copy(vLower, i, vSub, 0, nSubLen); + if(!EvalAddPopularPasswordPattern(vPatterns, vPassword, + i, vSub, 0.0)) + { + Array.Copy(vLeet, i, vSub, 0, nSubLen); + if(EvalAddPopularPasswordPattern(vPatterns, vPassword, + i, vSub, 1.5)) + { + Array.Clear(vLower, i, nSubLen); // Not vLeet + Debug.Assert(vLower[i] == chErased); + } + } + else + { + Array.Clear(vLower, i, nSubLen); + Debug.Assert(vLower[i] == chErased); + } + } + } + } + + private static bool EvalAddPopularPasswordPattern(List[] vPatterns, + char[] vPassword, int i, char[] vSub, double dblCostPerMod) + { + ulong uDictSize; + if(!PopularPasswords.IsPopularPassword(vSub, out uDictSize)) + return false; + + int n = vSub.Length; + int d = HammingDist(vSub, 0, vPassword, i, n); + + double dblCost = Log2((double)uDictSize); + + // dblCost += log2(n binom d) + int k = Math.Min(d, n - d); + for(int j = n; j > (n - k); --j) + dblCost += Log2(j); + for(int j = k; j >= 2; --j) + dblCost -= Log2(j); + + dblCost += dblCostPerMod * (double)d; + + vPatterns[i].Add(new QePatternInstance(i, n, PatternID.Dictionary, + dblCost)); + return true; + } + + private static char DecodeLeetChar(char chLeet) + { + if((chLeet >= '\u00C0') && (chLeet <= '\u00C6')) return 'a'; + if((chLeet >= '\u00C8') && (chLeet <= '\u00CB')) return 'e'; + if((chLeet >= '\u00CC') && (chLeet <= '\u00CF')) return 'i'; + if((chLeet >= '\u00D2') && (chLeet <= '\u00D6')) return 'o'; + if((chLeet >= '\u00D9') && (chLeet <= '\u00DC')) return 'u'; + if((chLeet >= '\u00E0') && (chLeet <= '\u00E6')) return 'a'; + if((chLeet >= '\u00E8') && (chLeet <= '\u00EB')) return 'e'; + if((chLeet >= '\u00EC') && (chLeet <= '\u00EF')) return 'i'; + if((chLeet >= '\u00F2') && (chLeet <= '\u00F6')) return 'o'; + if((chLeet >= '\u00F9') && (chLeet <= '\u00FC')) return 'u'; + + char ch; + switch(chLeet) + { + case '4': + case '@': + case '?': + case '^': + case '\u00AA': ch = 'a'; break; + case '8': + case '\u00DF': ch = 'b'; break; + case '(': + case '{': + case '[': + case '<': + case '\u00A2': + case '\u00A9': + case '\u00C7': + case '\u00E7': ch = 'c'; break; + case '\u00D0': + case '\u00F0': ch = 'd'; break; + case '3': + case '\u20AC': + case '&': + case '\u00A3': ch = 'e'; break; + case '6': + case '9': ch = 'g'; break; + case '#': ch = 'h'; break; + case '1': + case '!': + case '|': + case '\u00A1': + case '\u00A6': ch = 'i'; break; + case '\u00D1': + case '\u00F1': ch = 'n'; break; + case '0': + case '*': + case '\u00A4': // Currency + case '\u00B0': // Degree + case '\u00D8': + case '\u00F8': ch = 'o'; break; + case '\u00AE': ch = 'r'; break; + case '$': + case '5': + case '\u00A7': ch = 's'; break; + case '+': + case '7': ch = 't'; break; + case '\u00B5': ch = 'u'; break; + case '%': + case '\u00D7': ch = 'x'; break; + case '\u00A5': + case '\u00DD': + case '\u00FD': + case '\u00FF': ch = 'y'; break; + case '2': ch = 'z'; break; + default: ch = chLeet; break; + } + + return ch; + } + + private static int HammingDist(char[] v1, int iOffset1, + char[] v2, int iOffset2, int nLength) + { + int nDist = 0; + for(int i = 0; i < nLength; ++i) + { + if(v1[iOffset1 + i] != v2[iOffset2 + i]) ++nDist; + } + + return nDist; + } + + private static void FindRepetitions(char[] vPassword, + List[] vPatterns) + { + int n = vPassword.Length; + char[] v = new char[n]; + Array.Copy(vPassword, v, n); + + char chErased = char.MaxValue; + for(int m = (n / 2); m >= 3; --m) + { + for(int x1 = 0; x1 <= (n - (2 * m)); ++x1) + { + bool bFoundRep = false; + + for(int x2 = (x1 + m); x2 <= (n - m); ++x2) + { + if(PartsEqual(v, x1, x2, m)) + { + double dblCost = Log2(x1 + 1) + Log2(m); + vPatterns[x2].Add(new QePatternInstance(x2, m, + PatternID.Repetition, dblCost)); + + ErasePart(v, x2, m, ref chErased); + bFoundRep = true; + } + } + + if(bFoundRep) ErasePart(v, x1, m, ref chErased); + } + } + } + + private static bool PartsEqual(char[] v, int x1, int x2, int nLength) + { + for(int i = 0; i < nLength; ++i) + { + if(v[x1 + i] != v[x2 + i]) return false; + } + + return true; + } + + private static void ErasePart(char[] v, int i, int n, ref char chErased) + { + for(int j = 0; j < n; ++j) + { + v[i + j] = chErased; + --chErased; + } + } + + private static void FindNumbers(char[] vPassword, + List[] vPatterns) + { + int n = vPassword.Length; + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < n; ++i) + { + char ch = vPassword[i]; + if((ch >= '0') && (ch <= '9')) sb.Append(ch); + else + { + AddNumberPattern(vPatterns, sb.ToString(), i - sb.Length); + sb.Remove(0, sb.Length); + } + } + AddNumberPattern(vPatterns, sb.ToString(), n - sb.Length); + } + + private static void AddNumberPattern(List[] vPatterns, + string strNumber, int i) + { + if(strNumber.Length <= 2) return; + + int nZeros = 0; + for(int j = 0; j < strNumber.Length; ++j) + { + if(strNumber[j] != '0') break; + ++nZeros; + } + + double dblCost = Log2(nZeros + 1); + if(nZeros < strNumber.Length) + { + string strNonZero = strNumber.Substring(nZeros); + +#if KeePassLibSD + try { dblCost += Log2(double.Parse(strNonZero)); } + catch(Exception) { Debug.Assert(false); return; } +#else + double d; + if(double.TryParse(strNonZero, out d)) + dblCost += Log2(d); + else { Debug.Assert(false); return; } +#endif + } + + vPatterns[i].Add(new QePatternInstance(i, strNumber.Length, + PatternID.Number, dblCost)); + } + + private static void FindDiffSeqs(char[] vPassword, + List[] vPatterns) + { + int d = int.MinValue, p = 0; + string str = new string(vPassword) + new string(char.MaxValue, 1); + + for(int i = 1; i < str.Length; ++i) + { + int dCur = (int)str[i] - (int)str[i - 1]; + if(dCur != d) + { + if((i - p) >= 3) // At least 3 chars involved + { + QeCharType ct = GetCharType(str[p]); + double dblCost = ct.CharSize + Log2(i - p - 1); + + vPatterns[p].Add(new QePatternInstance(p, + i - p, PatternID.DiffSeq, dblCost)); + } + + d = dCur; + p = i - 1; + } + } + } + + private static double Log2(double dblValue) + { +#if KeePassLibSD + return (Math.Log(dblValue) / Math.Log(2.0)); +#else + return Math.Log(dblValue, 2.0); +#endif + } } } diff --git a/src/KeePassLib2Android/Cryptography/SelfTest.cs b/src/KeePassLib2Android/Cryptography/SelfTest.cs index 6ebf07b9..069d3c34 100644 --- a/src/KeePassLib2Android/Cryptography/SelfTest.cs +++ b/src/KeePassLib2Android/Cryptography/SelfTest.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,21 +20,32 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.Security; -using System.Security.Cryptography; using System.Text; +#if KeePassUAP +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Parameters; +#else +using System.Security.Cryptography; +#endif + using KeePassLib.Cryptography.Cipher; using KeePassLib.Keys; using KeePassLib.Native; -using KeePassLib.Utility; using KeePassLib.Resources; using KeePassLib.Security; +using KeePassLib.Utility; + +#if (KeePassUAP && KeePassLibSD) +#error KeePassUAP and KeePassLibSD are mutually exclusive. +#endif namespace KeePassLib.Cryptography { -/* #pragma warning disable 1591 - /// + /* /// /// Return values of the SelfTest.Perform method. /// public enum SelfTestResult @@ -43,8 +54,7 @@ namespace KeePassLib.Cryptography RijndaelEcbError = 1, Salsa20Error = 2, NativeKeyTransformationError = 3 - } -#pragma warning restore 1591 */ + } */ /// /// Class containing self-test methods. @@ -73,15 +83,21 @@ namespace KeePassLib.Cryptography Debug.Assert((int)PwIcon.World == 1); Debug.Assert((int)PwIcon.Warning == 2); Debug.Assert((int)PwIcon.BlackBerry == 68); + +#if KeePassUAP + SelfTestEx.Perform(); +#endif } internal static void TestFipsComplianceProblems() { +#if !KeePassUAP try { new RijndaelManaged(); } catch(Exception exAes) { throw new SecurityException("AES/Rijndael: " + exAes.Message); } +#endif try { new SHA256Managed(); } catch(Exception exSha256) @@ -106,6 +122,13 @@ namespace KeePassLib.Cryptography for(i = 0; i < 16; ++i) pbTestData[i] = 0; pbTestData[0] = 0x04; +#if KeePassUAP + AesEngine r = new AesEngine(); + r.Init(true, new KeyParameter(pbTestKey)); + if(r.GetBlockSize() != pbTestData.Length) + throw new SecurityException(KLRes.EncAlgorithmAes + " (BS)."); + r.ProcessBlock(pbTestData, 0, pbTestData, 0); +#else RijndaelManaged r = new RijndaelManaged(); if(r.BlockSize != 128) // AES block size @@ -121,6 +144,7 @@ namespace KeePassLib.Cryptography ICryptoTransform iCrypt = r.CreateEncryptor(); iCrypt.TransformBlock(pbTestData, 0, 16, pbTestData, 0); +#endif if(!MemUtil.ArraysEqual(pbTestData, pbReferenceCT)) throw new SecurityException(KLRes.EncAlgorithmAes + "."); @@ -146,7 +170,7 @@ namespace KeePassLib.Cryptography Salsa20Cipher c = new Salsa20Cipher(pbKey, pbIV); c.Encrypt(pb, pb.Length, false); if(!MemUtil.ArraysEqual(pb, pbExpected)) - throw new SecurityException("Salsa20."); + throw new SecurityException("Salsa20-1"); #if DEBUG // Extended test in debug mode @@ -163,13 +187,24 @@ namespace KeePassLib.Cryptography int nPos = Salsa20ToPos(c, r, pb.Length, 65536); c.Encrypt(pb, pb.Length, false); if(!MemUtil.ArraysEqual(pb, pbExpected2)) - throw new SecurityException("Salsa20-2."); + throw new SecurityException("Salsa20-2"); nPos = Salsa20ToPos(c, r, nPos + pb.Length, 131008); Array.Clear(pb, 0, pb.Length); c.Encrypt(pb, pb.Length, true); if(!MemUtil.ArraysEqual(pb, pbExpected3)) - throw new SecurityException("Salsa20-3."); + throw new SecurityException("Salsa20-3"); + + Dictionary d = new Dictionary(); + const int nRounds = 100; + for(int i = 0; i < nRounds; ++i) + { + byte[] z = new byte[32]; + c = new Salsa20Cipher(z, BitConverter.GetBytes((long)i)); + c.Encrypt(z, z.Length, true); + d[MemUtil.ByteArrayToHexString(z)] = true; + } + if(d.Count != nRounds) throw new SecurityException("Salsa20-4"); #endif } @@ -200,12 +235,12 @@ namespace KeePassLib.Cryptography byte[] pbManaged = new byte[32]; Array.Copy(pbOrgKey, pbManaged, 32); - if(CompositeKey.TransformKeyManaged(pbManaged, pbSeed, uRounds) == false) + if(!CompositeKey.TransformKeyManaged(pbManaged, pbSeed, uRounds)) throw new SecurityException("Managed transform."); byte[] pbNative = new byte[32]; Array.Copy(pbOrgKey, pbNative, 32); - if(NativeLib.TransformKey256(pbNative, pbSeed, uRounds) == false) + if(!NativeLib.TransformKey256(pbNative, pbSeed, uRounds)) return; // Native library not available ("success") if(!MemUtil.ArraysEqual(pbManaged, pbNative)) @@ -224,35 +259,64 @@ namespace KeePassLib.Cryptography if(!MemUtil.ArraysEqual(MemUtil.Decompress(pbCompressed), pb)) throw new InvalidOperationException("GZip"); - pb = Encoding.ASCII.GetBytes("012345678901234567890a"); - byte[] pbN = Encoding.ASCII.GetBytes("9012"); + Encoding enc = StrUtil.Utf8; + pb = enc.GetBytes("012345678901234567890a"); + byte[] pbN = enc.GetBytes("9012"); if(MemUtil.IndexOf(pb, pbN) != 9) throw new InvalidOperationException("MemUtil-1"); - pbN = Encoding.ASCII.GetBytes("01234567890123"); + pbN = enc.GetBytes("01234567890123"); if(MemUtil.IndexOf(pb, pbN) != 0) throw new InvalidOperationException("MemUtil-2"); - pbN = Encoding.ASCII.GetBytes("a"); + pbN = enc.GetBytes("a"); if(MemUtil.IndexOf(pb, pbN) != 21) throw new InvalidOperationException("MemUtil-3"); - pbN = Encoding.ASCII.GetBytes("0a"); + pbN = enc.GetBytes("0a"); if(MemUtil.IndexOf(pb, pbN) != 20) throw new InvalidOperationException("MemUtil-4"); - pbN = Encoding.ASCII.GetBytes("1"); + pbN = enc.GetBytes("1"); if(MemUtil.IndexOf(pb, pbN) != 1) throw new InvalidOperationException("MemUtil-5"); - pbN = Encoding.ASCII.GetBytes("b"); + pbN = enc.GetBytes("b"); if(MemUtil.IndexOf(pb, pbN) >= 0) throw new InvalidOperationException("MemUtil-6"); - pbN = Encoding.ASCII.GetBytes("012b"); + pbN = enc.GetBytes("012b"); if(MemUtil.IndexOf(pb, pbN) >= 0) throw new InvalidOperationException("MemUtil-7"); + + byte[] pbRes = MemUtil.ParseBase32("MY======"); + byte[] pbExp = Encoding.ASCII.GetBytes("f"); + if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-1"); + + pbRes = MemUtil.ParseBase32("MZXQ===="); + pbExp = Encoding.ASCII.GetBytes("fo"); + if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-2"); + + pbRes = MemUtil.ParseBase32("MZXW6==="); + pbExp = Encoding.ASCII.GetBytes("foo"); + if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-3"); + + pbRes = MemUtil.ParseBase32("MZXW6YQ="); + pbExp = Encoding.ASCII.GetBytes("foob"); + if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-4"); + + pbRes = MemUtil.ParseBase32("MZXW6YTB"); + pbExp = Encoding.ASCII.GetBytes("fooba"); + if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-5"); + + pbRes = MemUtil.ParseBase32("MZXW6YTBOI======"); + pbExp = Encoding.ASCII.GetBytes("foobar"); + if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-6"); + + pbRes = MemUtil.ParseBase32("JNSXSIDQOJXXM2LEMVZCAYTBONSWIIDPNYQG63TFFV2GS3LFEBYGC43TO5XXEZDTFY======"); + pbExp = Encoding.ASCII.GetBytes("Key provider based on one-time passwords."); + if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-7"); #endif } private static void TestHmacOtp() { #if (DEBUG && !KeePassLibSD) - byte[] pbSecret = Encoding.ASCII.GetBytes("12345678901234567890"); + byte[] pbSecret = StrUtil.Utf8.GetBytes("12345678901234567890"); string[] vExp = new string[]{ "755224", "287082", "359152", "969429", "338314", "254676", "287922", "162583", "399871", "520489" }; @@ -268,7 +332,9 @@ namespace KeePassLib.Cryptography private static void TestProtectedObjects() { #if DEBUG - byte[] pbData = Encoding.ASCII.GetBytes("Test Test Test Test"); + Encoding enc = StrUtil.Utf8; + + byte[] pbData = enc.GetBytes("Test Test Test Test"); ProtectedBinary pb = new ProtectedBinary(true, pbData); if(!pb.IsProtected) throw new SecurityException("ProtectedBinary-1"); @@ -277,8 +343,8 @@ namespace KeePassLib.Cryptography throw new SecurityException("ProtectedBinary-2"); if(!pb.IsProtected) throw new SecurityException("ProtectedBinary-3"); - byte[] pbData2 = Encoding.ASCII.GetBytes("Test Test Test Test"); - byte[] pbData3 = Encoding.ASCII.GetBytes("Test Test Test Test Test"); + byte[] pbData2 = enc.GetBytes("Test Test Test Test"); + byte[] pbData3 = enc.GetBytes("Test Test Test Test Test"); ProtectedBinary pb2 = new ProtectedBinary(true, pbData2); ProtectedBinary pb3 = new ProtectedBinary(true, pbData3); if(!pb.Equals(pb2)) throw new SecurityException("ProtectedBinary-4"); @@ -301,8 +367,7 @@ namespace KeePassLib.Cryptography throw new SecurityException("ProtectedString-3"); ps = new ProtectedString(true, "Test"); - ProtectedString ps2 = new ProtectedString(true, - StrUtil.Utf8.GetBytes("Test")); + ProtectedString ps2 = new ProtectedString(true, enc.GetBytes("Test")); if(ps.IsEmpty) throw new SecurityException("ProtectedString-4"); pbData = ps.ReadUtf8(); pbData2 = ps2.ReadUtf8(); @@ -318,6 +383,41 @@ namespace KeePassLib.Cryptography throw new SecurityException("ProtectedString-8"); if(!ps.IsProtected) throw new SecurityException("ProtectedString-9"); if(!ps2.IsProtected) throw new SecurityException("ProtectedString-10"); + + Random r = new Random(); + string str = string.Empty; + ps = new ProtectedString(); + for(int i = 0; i < 100; ++i) + { + bool bProt = ((r.Next() % 4) != 0); + ps = ps.WithProtection(bProt); + + int x = r.Next(str.Length + 1); + int c = r.Next(20); + char ch = (char)r.Next(1, 256); + + string strIns = new string(ch, c); + str = str.Insert(x, strIns); + ps = ps.Insert(x, strIns); + + if(ps.IsProtected != bProt) + throw new SecurityException("ProtectedString-11"); + if(ps.ReadString() != str) + throw new SecurityException("ProtectedString-12"); + + ps = ps.WithProtection(bProt); + + x = r.Next(str.Length); + c = r.Next(str.Length - x + 1); + + str = str.Remove(x, c); + ps = ps.Remove(x, c); + + if(ps.IsProtected != bProt) + throw new SecurityException("ProtectedString-13"); + if(ps.ReadString() != str) + throw new SecurityException("ProtectedString-14"); + } #endif } @@ -358,16 +458,74 @@ namespace KeePassLib.Cryptography throw new InvalidOperationException("StrUtil-V3"); if(StrUtil.VersionToString(0x00FF000000000000UL) != "255") throw new InvalidOperationException("StrUtil-V4"); - if(StrUtil.VersionToString(0x00FF000000000000UL, true) != "255.0") + if(StrUtil.VersionToString(0x00FF000000000000UL, 2) != "255.0") throw new InvalidOperationException("StrUtil-V5"); - if(StrUtil.VersionToString(0x0000000000070000UL, true) != "0.0.7") + if(StrUtil.VersionToString(0x0000000000070000UL) != "0.0.7") throw new InvalidOperationException("StrUtil-V6"); + if(StrUtil.VersionToString(0x0000000000000000UL) != "0") + throw new InvalidOperationException("StrUtil-V7"); + if(StrUtil.VersionToString(0x00000000FFFF0000UL, 4) != "0.0.65535.0") + throw new InvalidOperationException("StrUtil-V8"); + if(StrUtil.VersionToString(0x0000000000000000UL, 4) != "0.0.0.0") + throw new InvalidOperationException("StrUtil-V9"); + + if(StrUtil.RtfEncodeChar('\u0000') != "\\u0?") + throw new InvalidOperationException("StrUtil-Rtf1"); + if(StrUtil.RtfEncodeChar('\u7FFF') != "\\u32767?") + throw new InvalidOperationException("StrUtil-Rtf2"); + if(StrUtil.RtfEncodeChar('\u8000') != "\\u-32768?") + throw new InvalidOperationException("StrUtil-Rtf3"); + if(StrUtil.RtfEncodeChar('\uFFFF') != "\\u-1?") + throw new InvalidOperationException("StrUtil-Rtf4"); + + if(!StrUtil.StringToBool(Boolean.TrueString)) + throw new InvalidOperationException("StrUtil-Bool1"); + if(StrUtil.StringToBool(Boolean.FalseString)) + throw new InvalidOperationException("StrUtil-Bool2"); + + if(StrUtil.Count("Abracadabra", "a") != 4) + throw new InvalidOperationException("StrUtil-Count1"); + if(StrUtil.Count("Bla", "U") != 0) + throw new InvalidOperationException("StrUtil-Count2"); + if(StrUtil.Count("AAAAA", "AA") != 4) + throw new InvalidOperationException("StrUtil-Count3"); + + const string sU = "data:mytype;base64,"; + if(!StrUtil.IsDataUri(sU)) + throw new InvalidOperationException("StrUtil-DataUri1"); + if(!StrUtil.IsDataUri(sU, "mytype")) + throw new InvalidOperationException("StrUtil-DataUri2"); + if(StrUtil.IsDataUri(sU, "notmytype")) + throw new InvalidOperationException("StrUtil-DataUri3"); + + uint u = 0x7FFFFFFFU; + if(u.ToString(NumberFormatInfo.InvariantInfo) != "2147483647") + throw new InvalidOperationException("StrUtil-Inv1"); + if(uint.MaxValue.ToString(NumberFormatInfo.InvariantInfo) != + "4294967295") + throw new InvalidOperationException("StrUtil-Inv2"); + if(long.MinValue.ToString(NumberFormatInfo.InvariantInfo) != + "-9223372036854775808") + throw new InvalidOperationException("StrUtil-Inv3"); + if(short.MinValue.ToString(NumberFormatInfo.InvariantInfo) != + "-32768") + throw new InvalidOperationException("StrUtil-Inv4"); + + if(!string.Equals("abcd", "aBcd", StrUtil.CaseIgnoreCmp)) + throw new InvalidOperationException("StrUtil-Case1"); + if(string.Equals(@"ab", StrUtil.CaseIgnoreCmp)) + throw new InvalidOperationException("StrUtil-Case2"); #endif } private static void TestUrlUtil() { #if DEBUG +#if !KeePassUAP + Debug.Assert(Uri.UriSchemeHttp.Equals("http", StrUtil.CaseIgnoreCmp)); + Debug.Assert(Uri.UriSchemeHttps.Equals("https", StrUtil.CaseIgnoreCmp)); +#endif + if(UrlUtil.GetHost(@"scheme://domain:port/path?query_string#fragment_id") != "domain") throw new InvalidOperationException("UrlUtil-H1"); @@ -396,6 +554,13 @@ namespace KeePassLib.Cryptography str = UrlUtil.MakeAbsolutePath(strBase, strRel); if(!str.Equals(strDoc)) throw new InvalidOperationException("UrlUtil-R2"); + + str = UrlUtil.GetQuotedAppPath(" \"Test\" \"%1\" "); + if(str != "Test") throw new InvalidOperationException("UrlUtil-Q1"); + str = UrlUtil.GetQuotedAppPath("C:\\Program Files\\Test.exe"); + if(str != "C:\\Program Files\\Test.exe") throw new InvalidOperationException("UrlUtil-Q2"); + str = UrlUtil.GetQuotedAppPath("Reg.exe \"Test\" \"Test 2\""); + if(str != "Reg.exe \"Test\" \"Test 2\"") throw new InvalidOperationException("UrlUtil-Q3"); #endif } } diff --git a/src/KeePassLib2Android/Delegates/Handlers.cs b/src/KeePassLib2Android/Delegates/Handlers.cs index 770e32ed..09e536fe 100644 --- a/src/KeePassLib2Android/Delegates/Handlers.cs +++ b/src/KeePassLib2Android/Delegates/Handlers.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/KeePassLib2Android/Interfaces/IDeepCloneable.cs b/src/KeePassLib2Android/Interfaces/IDeepCloneable.cs index 5f80f82d..82d8dc8f 100644 --- a/src/KeePassLib2Android/Interfaces/IDeepCloneable.cs +++ b/src/KeePassLib2Android/Interfaces/IDeepCloneable.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/KeePassLib2Android/Interfaces/IStatusLogger.cs b/src/KeePassLib2Android/Interfaces/IStatusLogger.cs index 036ec6a7..df1d9d99 100644 --- a/src/KeePassLib2Android/Interfaces/IStatusLogger.cs +++ b/src/KeePassLib2Android/Interfaces/IStatusLogger.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/KeePassLib2Android/Interfaces/IStructureItem.cs b/src/KeePassLib2Android/Interfaces/IStructureItem.cs index fb2f303f..57813fd3 100644 --- a/src/KeePassLib2Android/Interfaces/IStructureItem.cs +++ b/src/KeePassLib2Android/Interfaces/IStructureItem.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/KeePassLib2Android/Interfaces/ITimeLogger.cs b/src/KeePassLib2Android/Interfaces/ITimeLogger.cs index 86a6b85d..4a1b94b5 100644 --- a/src/KeePassLib2Android/Interfaces/ITimeLogger.cs +++ b/src/KeePassLib2Android/Interfaces/ITimeLogger.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -39,18 +39,18 @@ namespace KeePassLib.Interfaces } /// - /// The date/time when the object was last accessed. + /// The date/time when the object was last modified. /// - DateTime LastAccessTime + DateTime LastModificationTime { get; set; } /// - /// The date/time when the object was last modified. + /// The date/time when the object was last accessed. /// - DateTime LastModificationTime + DateTime LastAccessTime { get; set; diff --git a/src/KeePassLib2Android/Interfaces/IUIOperations.cs b/src/KeePassLib2Android/Interfaces/IUIOperations.cs index 1fc054f3..92d863cc 100644 --- a/src/KeePassLib2Android/Interfaces/IUIOperations.cs +++ b/src/KeePassLib2Android/Interfaces/IUIOperations.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/KeePassLib2Android/Interfaces/IXmlSerializerEx.cs b/src/KeePassLib2Android/Interfaces/IXmlSerializerEx.cs index afd84ab2..fea48aaa 100644 --- a/src/KeePassLib2Android/Interfaces/IXmlSerializerEx.cs +++ b/src/KeePassLib2Android/Interfaces/IXmlSerializerEx.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/KeePassLib2Android/Keys/CompositeKey.cs b/src/KeePassLib2Android/Keys/CompositeKey.cs index 614408c1..363c7797 100644 --- a/src/KeePassLib2Android/Keys/CompositeKey.cs +++ b/src/KeePassLib2Android/Keys/CompositeKey.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,11 +18,17 @@ */ using System; -using System.Text; using System.Collections.Generic; using System.Diagnostics; -using System.IO; +using System.Text; + +#if KeePassUAP +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Parameters; +#else using System.Security.Cryptography; +#endif using KeePassLib.Native; using KeePassLib.Resources; @@ -118,8 +124,15 @@ namespace KeePassLib.Keys foreach(IUserKey pKey in m_vUserKeys) { + if(pKey == null) { Debug.Assert(false); continue; } + +#if KeePassUAP + if(pKey.GetType() == tUserKeyType) + return true; +#else if(tUserKeyType.IsInstanceOfType(pKey)) return true; +#endif } return false; @@ -138,8 +151,15 @@ namespace KeePassLib.Keys foreach(IUserKey pKey in m_vUserKeys) { + if(pKey == null) { Debug.Assert(false); continue; } + +#if KeePassUAP + if(pKey.GetType() == tUserKeyType) + return pKey; +#else if(tUserKeyType.IsInstanceOfType(pKey)) return pKey; +#endif } return null; @@ -154,21 +174,32 @@ namespace KeePassLib.Keys ValidateUserKeys(); // Concatenate user key data - MemoryStream ms = new MemoryStream(); + List lData = new List(); + int cbData = 0; foreach(IUserKey pKey in m_vUserKeys) { ProtectedBinary b = pKey.KeyData; if(b != null) { byte[] pbKeyData = b.ReadData(); - ms.Write(pbKeyData, 0, pbKeyData.Length); - MemUtil.ZeroByteArray(pbKeyData); + lData.Add(pbKeyData); + cbData += pbKeyData.Length; } } + byte[] pbAllData = new byte[cbData]; + int p = 0; + foreach(byte[] pbData in lData) + { + Array.Copy(pbData, 0, pbAllData, p, pbData.Length); + p += pbData.Length; + MemUtil.ZeroByteArray(pbData); + } + Debug.Assert(p == cbData); + SHA256Managed sha256 = new SHA256Managed(); - byte[] pbHash = sha256.ComputeHash(ms.ToArray()); - ms.Close(); + byte[] pbHash = sha256.ComputeHash(pbAllData); + MemUtil.ZeroByteArray(pbAllData); return pbHash; } @@ -179,8 +210,8 @@ namespace KeePassLib.Keys byte[] pbThis = CreateRawCompositeKey32(); byte[] pbOther = ckOther.CreateRawCompositeKey32(); bool bResult = MemUtil.ArraysEqual(pbThis, pbOther); - Array.Clear(pbOther, 0, pbOther.Length); - Array.Clear(pbThis, 0, pbThis.Length); + MemUtil.ZeroByteArray(pbOther); + MemUtil.ZeroByteArray(pbThis); return bResult; } @@ -256,20 +287,34 @@ namespace KeePassLib.Keys byte[] pbNewKey = new byte[32]; Array.Copy(pbOriginalKey32, pbNewKey, pbNewKey.Length); - // Try to use the native library first - if(NativeLib.TransformKey256(pbNewKey, pbKeySeed32, uNumRounds)) + try + { + // Try to use the native library first + if(NativeLib.TransformKey256(pbNewKey, pbKeySeed32, uNumRounds)) + return (new SHA256Managed()).ComputeHash(pbNewKey); + + if(!TransformKeyManaged(pbNewKey, pbKeySeed32, uNumRounds)) + return null; + return (new SHA256Managed()).ComputeHash(pbNewKey); - - if(TransformKeyManaged(pbNewKey, pbKeySeed32, uNumRounds) == false) - return null; - - SHA256Managed sha256 = new SHA256Managed(); - return sha256.ComputeHash(pbNewKey); + } + finally { MemUtil.ZeroByteArray(pbNewKey); } } public static bool TransformKeyManaged(byte[] pbNewKey32, byte[] pbKeySeed32, ulong uNumRounds) { +#if KeePassUAP + KeyParameter kp = new KeyParameter(pbKeySeed32); + AesEngine aes = new AesEngine(); + aes.Init(true, kp); + + for(ulong i = 0; i < uNumRounds; ++i) + { + aes.ProcessBlock(pbNewKey32, 0, pbNewKey32, 0); + aes.ProcessBlock(pbNewKey32, 16, pbNewKey32, 16); + } +#else byte[] pbIV = new byte[16]; Array.Clear(pbIV, 0, pbIV.Length); @@ -301,6 +346,7 @@ namespace KeePassLib.Keys iCrypt.TransformBlock(pbNewKey32, 0, 16, pbNewKey32, 0); iCrypt.TransformBlock(pbNewKey32, 16, 16, pbNewKey32, 16); } +#endif return true; } @@ -325,9 +371,6 @@ namespace KeePassLib.Keys if(NativeLib.TransformKeyBenchmark256(uMilliseconds, out uRounds)) return uRounds; - byte[] pbIV = new byte[16]; - Array.Clear(pbIV, 0, pbIV.Length); - byte[] pbKey = new byte[32]; byte[] pbNewKey = new byte[32]; for(int i = 0; i < pbKey.Length; ++i) @@ -336,6 +379,14 @@ namespace KeePassLib.Keys pbNewKey[i] = (byte)i; } +#if KeePassUAP + KeyParameter kp = new KeyParameter(pbKey); + AesEngine aes = new AesEngine(); + aes.Init(true, kp); +#else + byte[] pbIV = new byte[16]; + Array.Clear(pbIV, 0, pbIV.Length); + RijndaelManaged r = new RijndaelManaged(); if(r.BlockSize != 128) // AES block size { @@ -358,18 +409,21 @@ namespace KeePassLib.Keys Debug.Assert(iCrypt.OutputBlockSize == 16, "Invalid output block size!"); return PwDefs.DefaultKeyEncryptionRounds; } - - DateTime dtStart = DateTime.Now; - TimeSpan ts; - double dblReqMillis = uMilliseconds; +#endif uRounds = 0; + int tStart = Environment.TickCount; while(true) { for(ulong j = 0; j < uStep; ++j) { +#if KeePassUAP + aes.ProcessBlock(pbNewKey, 0, pbNewKey, 0); + aes.ProcessBlock(pbNewKey, 16, pbNewKey, 16); +#else iCrypt.TransformBlock(pbNewKey, 0, 16, pbNewKey, 0); iCrypt.TransformBlock(pbNewKey, 16, 16, pbNewKey, 16); +#endif } uRounds += uStep; @@ -379,8 +433,8 @@ namespace KeePassLib.Keys break; } - ts = DateTime.Now - dtStart; - if(ts.TotalMilliseconds > dblReqMillis) break; + uint tElapsed = (uint)(Environment.TickCount - tStart); + if(tElapsed > uMilliseconds) break; } return uRounds; diff --git a/src/KeePassLib2Android/Keys/IUserKey.cs b/src/KeePassLib2Android/Keys/IUserKey.cs index f5bc556e..45b31a55 100644 --- a/src/KeePassLib2Android/Keys/IUserKey.cs +++ b/src/KeePassLib2Android/Keys/IUserKey.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/KeePassLib2Android/Keys/KcpCustomKey.cs b/src/KeePassLib2Android/Keys/KcpCustomKey.cs index 71a3a486..0f0c369b 100644 --- a/src/KeePassLib2Android/Keys/KcpCustomKey.cs +++ b/src/KeePassLib2Android/Keys/KcpCustomKey.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,12 +19,14 @@ using System; using System.Collections.Generic; -using System.Text; using System.Diagnostics; +using System.Text; + +#if !KeePassUAP using System.Security.Cryptography; +#endif using KeePassLib.Security; -using KeePassLib.Utility; namespace KeePassLib.Keys { diff --git a/src/KeePassLib2Android/Keys/KcpKeyFile.cs b/src/KeePassLib2Android/Keys/KcpKeyFile.cs index 3a0a71a0..07726bcc 100644 --- a/src/KeePassLib2Android/Keys/KcpKeyFile.cs +++ b/src/KeePassLib2Android/Keys/KcpKeyFile.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,14 +18,18 @@ */ using System; -using System.Text; -using System.IO; -using System.Xml; -using System.Security; -using System.Security.Cryptography; using System.Diagnostics; +using System.IO; +using System.Security; +using System.Text; +using System.Xml; + +#if !KeePassUAP +using System.Security.Cryptography; +#endif using KeePassLib.Cryptography; +using KeePassLib.Resources; using KeePassLib.Security; using KeePassLib.Serialization; using KeePassLib.Utility; @@ -60,19 +64,47 @@ namespace KeePassLib.Keys public KcpKeyFile(string strKeyFile) { - Construct(IOConnectionInfo.FromPath(strKeyFile)); + Construct(IOConnectionInfo.FromPath(strKeyFile), false); + } + + public KcpKeyFile(string strKeyFile, bool bThrowIfDbFile) + { + Construct(IOConnectionInfo.FromPath(strKeyFile), bThrowIfDbFile); } public KcpKeyFile(IOConnectionInfo iocKeyFile) { - Construct(iocKeyFile); + Construct(iocKeyFile, false); } - private void Construct(IOConnectionInfo iocFile) + public KcpKeyFile(IOConnectionInfo iocKeyFile, bool bThrowIfDbFile) + { + Construct(iocKeyFile, bThrowIfDbFile); + } + + private void Construct(IOConnectionInfo iocFile, bool bThrowIfDbFile) { byte[] pbFileData = IOConnection.ReadFile(iocFile); if(pbFileData == null) throw new FileNotFoundException(); + if(bThrowIfDbFile && (pbFileData.Length >= 8)) + { + uint uSig1 = MemUtil.BytesToUInt32(MemUtil.Mid(pbFileData, 0, 4)); + uint uSig2 = MemUtil.BytesToUInt32(MemUtil.Mid(pbFileData, 4, 4)); + + if(((uSig1 == KdbxFile.FileSignature1) && + (uSig2 == KdbxFile.FileSignature2)) || + ((uSig1 == KdbxFile.FileSignaturePreRelease1) && + (uSig2 == KdbxFile.FileSignaturePreRelease2)) || + ((uSig1 == KdbxFile.FileSignatureOld1) && + (uSig2 == KdbxFile.FileSignatureOld2))) +#if KeePassLibSD + throw new Exception(KLRes.KeyFileDbSel); +#else + throw new InvalidDataException(KLRes.KeyFileDbSel); +#endif + } + byte[] pbKey = LoadXmlKeyFile(pbFileData); if(pbKey == null) pbKey = LoadKeyFile(pbFileData); @@ -124,7 +156,7 @@ namespace KeePassLib.Keys try { - string strHex = Encoding.ASCII.GetString(pbFileData, 0, 64); + string strHex = StrUtil.Utf8.GetString(pbFileData, 0, 64); if(!StrUtil.IsHexString(strHex, true)) return null; byte[] pbKey = MemUtil.HexStringToByteArray(strHex); @@ -235,7 +267,18 @@ namespace KeePassLib.Keys Debug.Assert(pbKeyData != null); if(pbKeyData == null) throw new ArgumentNullException("pbKeyData"); - XmlTextWriter xtw = new XmlTextWriter(strFile, StrUtil.Utf8); + IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFile); + Stream sOut = IOConnection.OpenWrite(ioc); + +#if KeePassUAP + XmlWriterSettings xws = new XmlWriterSettings(); + xws.Encoding = StrUtil.Utf8; + xws.Indent = false; + + XmlWriter xtw = XmlWriter.Create(sOut, xws); +#else + XmlTextWriter xtw = new XmlTextWriter(sOut, StrUtil.Utf8); +#endif xtw.WriteStartDocument(); xtw.WriteWhitespace("\r\n"); @@ -266,6 +309,8 @@ namespace KeePassLib.Keys xtw.WriteWhitespace("\r\n"); xtw.WriteEndDocument(); // End KeyFile xtw.Close(); + + sOut.Close(); } } } diff --git a/src/KeePassLib2Android/Keys/KcpPassword.cs b/src/KeePassLib2Android/Keys/KcpPassword.cs index 30df57e8..b5526665 100644 --- a/src/KeePassLib2Android/Keys/KcpPassword.cs +++ b/src/KeePassLib2Android/Keys/KcpPassword.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,9 +18,12 @@ */ using System; -using System.Text; using System.Diagnostics; +using System.Text; + +#if !KeePassUAP using System.Security.Cryptography; +#endif using KeePassLib.Security; using KeePassLib.Utility; @@ -68,6 +71,10 @@ namespace KeePassLib.Keys Debug.Assert(pbPasswordUtf8 != null); if(pbPasswordUtf8 == null) throw new ArgumentNullException("pbPasswordUtf8"); +#if (DEBUG && !KeePassLibSD) + Debug.Assert(ValidatePassword(pbPasswordUtf8)); +#endif + SHA256Managed sha256 = new SHA256Managed(); byte[] pbRaw = sha256.ComputeHash(pbPasswordUtf8); @@ -80,5 +87,19 @@ namespace KeePassLib.Keys // m_psPassword = null; // m_pbKeyData = null; // } + +#if (DEBUG && !KeePassLibSD) + private static bool ValidatePassword(byte[] pb) + { + try + { + string str = StrUtil.Utf8.GetString(pb); + return str.IsNormalized(NormalizationForm.FormC); + } + catch(Exception) { Debug.Assert(false); } + + return false; + } +#endif } } diff --git a/src/KeePassLib2Android/Keys/KcpUserAccount.cs b/src/KeePassLib2Android/Keys/KcpUserAccount.cs index aefb02c9..b0d66c0a 100644 --- a/src/KeePassLib2Android/Keys/KcpUserAccount.cs +++ b/src/KeePassLib2Android/Keys/KcpUserAccount.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,9 +18,13 @@ */ using System; -using System.Security; -using System.Security.Cryptography; +using System.Diagnostics; using System.IO; +using System.Security; + +#if !KeePassUAP +using System.Security.Cryptography; +#endif using KeePassLib.Cryptography; using KeePassLib.Resources; @@ -37,7 +41,7 @@ namespace KeePassLib.Keys private ProtectedBinary m_pbKeyData = null; // Constant initialization vector (unique for KeePass) - private static readonly byte[] m_pbEntropy = new byte[]{ + private static readonly byte[] m_pbEntropy = new byte[] { 0xDE, 0x13, 0x5B, 0x5F, 0x18, 0xA3, 0x46, 0x70, 0xB2, 0x57, 0x24, 0x29, 0x69, 0x88, 0x98, 0xE6 }; @@ -67,10 +71,14 @@ namespace KeePassLib.Keys byte[] pbKey = LoadUserKey(false); if(pbKey == null) pbKey = CreateUserKey(); - if(pbKey == null) throw new SecurityException(KLRes.UserAccountKeyError); + if(pbKey == null) // Should never happen + { + Debug.Assert(false); + throw new SecurityException(KLRes.UserAccountKeyError); + } m_pbKeyData = new ProtectedBinary(true, pbKey); - Array.Clear(pbKey, 0, pbKey.Length); + MemUtil.ZeroByteArray(pbKey); } // public void Clear() @@ -80,8 +88,12 @@ namespace KeePassLib.Keys private static string GetUserKeyFilePath(bool bCreate) { +#if KeePassUAP + string strUserDir = EnvironmentExt.AppDataRoamingFolderPath; +#else string strUserDir = Environment.GetFolderPath( Environment.SpecialFolder.ApplicationData); +#endif strUserDir = UrlUtil.EnsureTerminatingSeparator(strUserDir, false); strUserDir += PwDefs.ShortProductName; @@ -90,10 +102,10 @@ namespace KeePassLib.Keys Directory.CreateDirectory(strUserDir); strUserDir = UrlUtil.EnsureTerminatingSeparator(strUserDir, false); - return strUserDir + UserKeyFileName; + return (strUserDir + UserKeyFileName); } - private static byte[] LoadUserKey(bool bShowWarning) + private static byte[] LoadUserKey(bool bThrow) { byte[] pbKey = null; @@ -105,13 +117,10 @@ namespace KeePassLib.Keys pbKey = ProtectedData.Unprotect(pbProtectedKey, m_pbEntropy, DataProtectionScope.CurrentUser); - - Array.Clear(pbProtectedKey, 0, pbProtectedKey.Length); } - catch(Exception exLoad) + catch(Exception) { - if(bShowWarning) MessageService.ShowWarning(exLoad); - + if(bThrow) throw; pbKey = null; } #endif @@ -121,28 +130,23 @@ namespace KeePassLib.Keys private static byte[] CreateUserKey() { - byte[] pbKey = null; +#if KeePassLibSD + return null; +#else + string strFilePath = GetUserKeyFilePath(true); -#if !KeePassLibSD - try - { - string strFilePath = GetUserKeyFilePath(true); + byte[] pbRandomKey = CryptoRandom.Instance.GetRandomBytes(64); + byte[] pbProtectedKey = ProtectedData.Protect(pbRandomKey, + m_pbEntropy, DataProtectionScope.CurrentUser); - byte[] pbRandomKey = CryptoRandom.Instance.GetRandomBytes(64); - byte[] pbProtectedKey = ProtectedData.Protect(pbRandomKey, - m_pbEntropy, DataProtectionScope.CurrentUser); + File.WriteAllBytes(strFilePath, pbProtectedKey); - File.WriteAllBytes(strFilePath, pbProtectedKey); - - Array.Clear(pbProtectedKey, 0, pbProtectedKey.Length); - Array.Clear(pbRandomKey, 0, pbRandomKey.Length); - - pbKey = LoadUserKey(true); - } - catch(Exception) { pbKey = null; } -#endif + byte[] pbKey = LoadUserKey(true); + Debug.Assert(MemUtil.ArraysEqual(pbKey, pbRandomKey)); + MemUtil.ZeroByteArray(pbRandomKey); return pbKey; +#endif } } } diff --git a/src/KeePassLib2Android/Keys/KeyProvider.cs b/src/KeePassLib2Android/Keys/KeyProvider.cs index 2efc57f7..68908446 100644 --- a/src/KeePassLib2Android/Keys/KeyProvider.cs +++ b/src/KeePassLib2Android/Keys/KeyProvider.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/KeePassLib2Android/Keys/KeyProviderPool.cs b/src/KeePassLib2Android/Keys/KeyProviderPool.cs index 48c547f8..a90788e5 100644 --- a/src/KeePassLib2Android/Keys/KeyProviderPool.cs +++ b/src/KeePassLib2Android/Keys/KeyProviderPool.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/KeePassLib2Android/Keys/KeyValidator.cs b/src/KeePassLib2Android/Keys/KeyValidator.cs index 61c819b9..bb9eb2ff 100644 --- a/src/KeePassLib2Android/Keys/KeyValidator.cs +++ b/src/KeePassLib2Android/Keys/KeyValidator.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/KeePassLib2Android/Keys/KeyValidatorPool.cs b/src/KeePassLib2Android/Keys/KeyValidatorPool.cs index 65e24c72..17137676 100644 --- a/src/KeePassLib2Android/Keys/KeyValidatorPool.cs +++ b/src/KeePassLib2Android/Keys/KeyValidatorPool.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/KeePassLib2Android/Keys/UserKeyType.cs b/src/KeePassLib2Android/Keys/UserKeyType.cs index ce8bf6e8..ecd307d2 100644 --- a/src/KeePassLib2Android/Keys/UserKeyType.cs +++ b/src/KeePassLib2Android/Keys/UserKeyType.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/KeePassLib2Android/Native/NativeLib.cs b/src/KeePassLib2Android/Native/NativeLib.cs index 26bf031b..b71bd5b5 100644 --- a/src/KeePassLib2Android/Native/NativeLib.cs +++ b/src/KeePassLib2Android/Native/NativeLib.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,8 +19,17 @@ using System; using System.Collections.Generic; -using System.Runtime.InteropServices; using System.Diagnostics; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.RegularExpressions; + +#if !KeePassUAP +using System.IO; +using System.Threading; +using System.Windows.Forms; +#endif using KeePassLib.Utility; @@ -44,6 +53,61 @@ namespace KeePassLib.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 + { + get + { + if(m_ouMonoVersion.HasValue) return m_ouMonoVersion.Value; + + ulong uVersion = 0; + try + { + Type t = Type.GetType("Mono.Runtime"); + if(t != null) + { + MethodInfo mi = t.GetMethod("GetDisplayName", + BindingFlags.NonPublic | BindingFlags.Static); + if(mi != null) + { + string strName = (mi.Invoke(null, null) as string); + if(!string.IsNullOrEmpty(strName)) + { + Match m = Regex.Match(strName, "\\d+(\\.\\d+)+"); + if(m.Success) + uVersion = StrUtil.ParseVersion(m.Value); + else { Debug.Assert(false); } + } + else { Debug.Assert(false); } + } + else { Debug.Assert(false); } + } + } + catch(Exception) { Debug.Assert(false); } + + m_ouMonoVersion = uVersion; + return uVersion; + } + } + /// /// Determine if the native library is installed. /// @@ -87,9 +151,13 @@ namespace KeePassLib.Native { if(m_platID.HasValue) return m_platID.Value; +#if KeePassUAP + m_platID = EnvironmentExt.OSVersion.Platform; +#else m_platID = Environment.OSVersion.Platform; +#endif -#if !KeePassLibSD +#if (!KeePassLibSD && !KeePassUAP) // Mono returns PlatformID.Unix on Mac OS X, workaround this if(m_platID.Value == PlatformID.Unix) { @@ -102,7 +170,55 @@ namespace KeePassLib.Native return m_platID.Value; } -#if !KeePassLibSD + private static DesktopType? m_tDesktop = null; + public static DesktopType GetDesktopType() + { + if(!m_tDesktop.HasValue) + { + DesktopType t = DesktopType.None; + if(!IsUnix()) t = DesktopType.Windows; + else + { + try + { + string strXdg = (Environment.GetEnvironmentVariable( + "XDG_CURRENT_DESKTOP") ?? string.Empty).Trim(); + string strGdm = (Environment.GetEnvironmentVariable( + "GDMSESSION") ?? string.Empty).Trim(); + StringComparison sc = StrUtil.CaseIgnoreCmp; + + if(strXdg.Equals("Unity", sc)) + t = DesktopType.Unity; + else if(strXdg.Equals("LXDE", sc)) + t = DesktopType.Lxde; + else if(strXdg.Equals("XFCE", sc)) + t = DesktopType.Xfce; + else if(strXdg.Equals("MATE", sc)) + t = DesktopType.Mate; + else if(strXdg.Equals("X-Cinnamon", sc)) + t = DesktopType.Cinnamon; + else if(strXdg.Equals("Pantheon", sc)) // Elementary OS + t = DesktopType.Pantheon; + else if(strXdg.Equals("KDE", sc) || // Mint 16 + 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; + } + } + catch(Exception) { Debug.Assert(false); } + } + + m_tDesktop = t; + } + + return m_tDesktop.Value; + } + +#if (!KeePassLibSD && !KeePassUAP) public static string RunConsoleApp(string strAppPath, string strParams) { return RunConsoleApp(strAppPath, strParams, null); @@ -110,40 +226,134 @@ namespace KeePassLib.Native public static string RunConsoleApp(string strAppPath, string strParams, string strStdInput) + { + return RunConsoleApp(strAppPath, strParams, strStdInput, + (AppRunFlags.GetStdOutput | AppRunFlags.WaitForExit)); + } + + private delegate string RunProcessDelegate(); + + public static string RunConsoleApp(string strAppPath, string strParams, + string strStdInput, AppRunFlags f) { if(strAppPath == null) throw new ArgumentNullException("strAppPath"); if(strAppPath.Length == 0) throw new ArgumentException("strAppPath"); - try + bool bStdOut = ((f & AppRunFlags.GetStdOutput) != AppRunFlags.None); + + RunProcessDelegate fnRun = delegate() { - ProcessStartInfo psi = new ProcessStartInfo(); - - psi.CreateNoWindow = true; - psi.FileName = strAppPath; - psi.WindowStyle = ProcessWindowStyle.Hidden; - psi.UseShellExecute = false; - psi.RedirectStandardOutput = true; - - if(strStdInput != null) psi.RedirectStandardInput = true; - - if(!string.IsNullOrEmpty(strParams)) psi.Arguments = strParams; - - Process p = Process.Start(psi); - - if(strStdInput != null) + try { - p.StandardInput.Write(strStdInput); - p.StandardInput.Close(); + ProcessStartInfo psi = new ProcessStartInfo(); + + psi.CreateNoWindow = true; + psi.FileName = strAppPath; + psi.WindowStyle = ProcessWindowStyle.Hidden; + psi.UseShellExecute = false; + psi.RedirectStandardOutput = bStdOut; + + if(strStdInput != null) psi.RedirectStandardInput = true; + + if(!string.IsNullOrEmpty(strParams)) psi.Arguments = strParams; + + Process p = Process.Start(psi); + + if(strStdInput != null) + { + EnsureNoBom(p.StandardInput); + + p.StandardInput.Write(strStdInput); + p.StandardInput.Close(); + } + + string strOutput = string.Empty; + if(bStdOut) strOutput = p.StandardOutput.ReadToEnd(); + + if((f & AppRunFlags.WaitForExit) != AppRunFlags.None) + p.WaitForExit(); + else if((f & AppRunFlags.GCKeepAlive) != AppRunFlags.None) + { + Thread th = new Thread(delegate() + { + try { p.WaitForExit(); } + catch(Exception) { Debug.Assert(false); } + }); + th.Start(); + } + + return strOutput; + } + catch(Exception) { Debug.Assert(false); } + + return null; + }; + + if((f & AppRunFlags.DoEvents) != AppRunFlags.None) + { + List
lDisabledForms = new List(); + if((f & AppRunFlags.DisableForms) != AppRunFlags.None) + { + foreach(Form form in Application.OpenForms) + { + if(!form.Enabled) continue; + + lDisabledForms.Add(form); + form.Enabled = false; + } } - string strOutput = p.StandardOutput.ReadToEnd(); - p.WaitForExit(); + IAsyncResult ar = fnRun.BeginInvoke(null, null); - return strOutput; + while(!ar.AsyncWaitHandle.WaitOne(0)) + { + Application.DoEvents(); + Thread.Sleep(2); + } + + string strRet = fnRun.EndInvoke(ar); + + for(int i = lDisabledForms.Count - 1; i >= 0; --i) + lDisabledForms[i].Enabled = true; + + return strRet; + } + + return fnRun(); + } + + private static void EnsureNoBom(StreamWriter sw) + { + if(sw == null) { Debug.Assert(false); return; } + if(!MonoWorkarounds.IsRequired(1219)) return; + + try + { + Encoding enc = sw.Encoding; + if(enc == null) { Debug.Assert(false); return; } + byte[] pbBom = enc.GetPreamble(); + if((pbBom == null) || (pbBom.Length == 0)) return; + + // For Mono >= 4.0 (using Microsoft's reference source) + try + { + FieldInfo fi = typeof(StreamWriter).GetField("haveWrittenPreamble", + BindingFlags.Instance | BindingFlags.NonPublic); + if(fi != null) + { + fi.SetValue(sw, true); + return; + } + } + catch(Exception) { Debug.Assert(false); } + + // For Mono < 4.0 + FieldInfo fiPD = typeof(StreamWriter).GetField("preamble_done", + BindingFlags.Instance | BindingFlags.NonPublic); + if(fiPD != null) fiPD.SetValue(sw, true); + else { Debug.Assert(false); } } catch(Exception) { Debug.Assert(false); } - - return null; } #endif @@ -157,7 +367,10 @@ namespace KeePassLib.Native public static bool TransformKey256(byte[] pBuf256, byte[] pKey256, ulong uRounds) { - if(m_bAllowNative == false) return false; +#if KeePassUAP + return false; +#else + if(!m_bAllowNative) return false; KeyValuePair kvp = PrepareArrays256(pBuf256, pKey256); bool bResult = false; @@ -170,26 +383,31 @@ namespace KeePassLib.Native if(bResult) GetBuffers256(kvp, pBuf256, pKey256); - NativeLib.FreeArrays(kvp); + FreeArrays(kvp); return bResult; +#endif } /// /// Benchmark key transformation. /// - /// Number of seconds to perform the benchmark. + /// Number of milliseconds to perform the benchmark. /// Number of transformations done. /// Returns true, if the benchmark was successful. public static bool TransformKeyBenchmark256(uint uTimeMs, out ulong puRounds) { puRounds = 0; - if(m_bAllowNative == false) return false; +#if KeePassUAP + return false; +#else + if(!m_bAllowNative) return false; try { puRounds = NativeMethods.TransformKeyBenchmark(uTimeMs); } catch(Exception) { return false; } return true; +#endif } private static KeyValuePair PrepareArrays256(byte[] pBuf256, diff --git a/src/KeePassLib2Android/Native/NativeMethods.cs b/src/KeePassLib2Android/Native/NativeMethods.cs index 42036622..07c47ab2 100644 --- a/src/KeePassLib2Android/Native/NativeMethods.cs +++ b/src/KeePassLib2Android/Native/NativeMethods.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,20 +18,22 @@ */ using System; -using System.Text; -using System.Security; -using System.Runtime.InteropServices; -using System.IO; using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; using KeePassLib.Utility; namespace KeePassLib.Native { - internal static class NativeMethods + internal static partial class NativeMethods { internal const int MAX_PATH = 260; + // internal const uint TF_SFT_SHOWNORMAL = 0x00000001; + // internal const uint TF_SFT_HIDDEN = 0x00000008; + /* [DllImport("KeePassNtv32.dll", EntryPoint = "TransformKey")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool TransformKey32(IntPtr pBuf256, @@ -70,6 +72,7 @@ namespace KeePassLib.Native return TransformKeyTimed32(pBuf256, pKey256, ref puRounds, uSeconds); } */ +#if !KeePassUAP [DllImport("KeePassLibC32.dll", EntryPoint = "TransformKey256")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool TransformKey32(IntPtr pBuf256, @@ -83,7 +86,7 @@ namespace KeePassLib.Native internal static bool TransformKey(IntPtr pBuf256, IntPtr pKey256, UInt64 uRounds) { - if(Marshal.SizeOf(typeof(IntPtr)) == 8) + if(NativeLib.PointerSize == 8) return TransformKey64(pBuf256, pKey256, uRounds); else return TransformKey32(pBuf256, pKey256, uRounds); @@ -97,67 +100,88 @@ namespace KeePassLib.Native internal static UInt64 TransformKeyBenchmark(UInt32 uTimeMs) { - if(Marshal.SizeOf(typeof(IntPtr)) == 8) + if(NativeLib.PointerSize == 8) return TransformKeyBenchmark64(uTimeMs); - else - return TransformKeyBenchmark32(uTimeMs); + return TransformKeyBenchmark32(uTimeMs); } +#endif -#if !KeePassLibSD - [DllImport("ShlWApi.dll", CharSet = CharSet.Unicode, ExactSpelling = true)] - internal static extern int StrCmpLogicalW(string x, string y); + /* [DllImport("KeePassLibC32.dll", EntryPoint = "TF_ShowLangBar")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool TF_ShowLangBar32(UInt32 dwFlags); + [DllImport("KeePassLibC64.dll", EntryPoint = "TF_ShowLangBar")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool TF_ShowLangBar64(UInt32 dwFlags); + + internal static bool TfShowLangBar(uint dwFlags) + { + if(Marshal.SizeOf(typeof(IntPtr)) == 8) + return TF_ShowLangBar64(dwFlags); + return TF_ShowLangBar32(dwFlags); + } */ + +#if (!KeePassLibSD && !KeePassUAP) [DllImport("ShlWApi.dll", CharSet = CharSet.Auto)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool PathRelativePathTo([Out] StringBuilder pszPath, - [In] string pszFrom, [In] uint dwAttrFrom, [In] string pszTo, - [In] uint dwAttrTo); -#endif + [In] string pszFrom, uint dwAttrFrom, [In] string pszTo, uint dwAttrTo); - private static bool? m_bSupportsLogicalCmp = null; + [DllImport("ShlWApi.dll", CharSet = CharSet.Unicode, ExactSpelling = true)] + private static extern int StrCmpLogicalW(string x, string y); + + private static bool? m_obSupportsLogicalCmp = null; private static void TestNaturalComparisonsSupport() { -#if KeePassLibSD -#warning No native natural comparisons supported. - m_bSupportsLogicalCmp = false; -#else try { StrCmpLogicalW("0", "0"); // Throws exception if unsupported - m_bSupportsLogicalCmp = true; + m_obSupportsLogicalCmp = true; } - catch(Exception) { m_bSupportsLogicalCmp = false; } -#endif + catch(Exception) { m_obSupportsLogicalCmp = false; } } +#endif internal static bool SupportsStrCmpNaturally { get { - if(m_bSupportsLogicalCmp.HasValue == false) +#if (!KeePassLibSD && !KeePassUAP) + if(!m_obSupportsLogicalCmp.HasValue) TestNaturalComparisonsSupport(); - return m_bSupportsLogicalCmp.Value; + return m_obSupportsLogicalCmp.Value; +#else + return false; +#endif } } internal static int StrCmpNaturally(string x, string y) { - if(m_bSupportsLogicalCmp.HasValue == false) TestNaturalComparisonsSupport(); - if(m_bSupportsLogicalCmp.Value == false) return 0; +#if (!KeePassLibSD && !KeePassUAP) + if(!NativeMethods.SupportsStrCmpNaturally) + { + Debug.Assert(false); + return string.Compare(x, y, true); + } -#if KeePassLibSD -#warning No native natural comparisons supported. - return x.CompareTo(y); -#else return StrCmpLogicalW(x, y); +#else + Debug.Assert(false); + return string.Compare(x, y, true); #endif } internal static string GetUserRuntimeDir() { -#if !KeePassLibSD +#if KeePassLibSD + return Path.GetTempPath(); +#else +#if KeePassUAP + string strRtDir = EnvironmentExt.AppDataLocalFolderPath; +#else string strRtDir = Environment.GetEnvironmentVariable("XDG_RUNTIME_DIR"); if(string.IsNullOrEmpty(strRtDir)) strRtDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); @@ -166,13 +190,12 @@ namespace KeePassLib.Native Debug.Assert(false); return Path.GetTempPath(); // Not UrlUtil (otherwise cyclic) } +#endif strRtDir = UrlUtil.EnsureTerminatingSeparator(strRtDir, false); strRtDir += PwDefs.ShortProductName; return strRtDir; -#else - return Path.GetTempPath(); #endif } } diff --git a/src/KeePassLib2Android/Properties/AssemblyInfo.cs b/src/KeePassLib2Android/Properties/AssemblyInfo.cs index e55beedf..48474729 100644 --- a/src/KeePassLib2Android/Properties/AssemblyInfo.cs +++ b/src/KeePassLib2Android/Properties/AssemblyInfo.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -27,7 +27,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Dominik Reichl")] [assembly: AssemblyProduct("KeePassLib")] -[assembly: AssemblyCopyright("Copyright © 2003-2012 Dominik Reichl")] +[assembly: AssemblyCopyright("Copyright © 2003-2016 Dominik Reichl")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -38,5 +38,5 @@ using System.Runtime.InteropServices; [assembly: Guid("395f6eec-a1e0-4438-aa82-b75099348134")] // Assembly version information -[assembly: AssemblyVersion("2.20.1.*")] -[assembly: AssemblyFileVersion("2.20.1.0")] +[assembly: AssemblyVersion("2.34.0.*")] +[assembly: AssemblyFileVersion("2.34.0.0")] diff --git a/src/KeePassLib2Android/PwCustomIcon.cs b/src/KeePassLib2Android/PwCustomIcon.cs index f32bc1a8..10195804 100644 --- a/src/KeePassLib2Android/PwCustomIcon.cs +++ b/src/KeePassLib2Android/PwCustomIcon.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,9 +18,12 @@ */ using System; +using System.Collections.Generic; using System.Diagnostics; + +#if !KeePassUAP using System.Drawing; -using System.IO; +#endif using KeePassLib.Utility; @@ -33,7 +36,13 @@ namespace KeePassLib { private PwUuid m_pwUuid; private byte[] m_pbImageDataPng; - private Image m_pCachedImage; + + private Image m_imgOrg = null; + private Dictionary m_dImageCache = new Dictionary(); + + // Recommended maximum sizes, not obligatory + internal const int MaxWidth = 128; + internal const int MaxHeight = 128; public PwUuid Uuid { @@ -45,32 +54,73 @@ namespace KeePassLib get { return m_pbImageDataPng; } } + [Obsolete("Use GetImage instead.")] public Image Image { - get { return m_pCachedImage; } +#if (!KeePassLibSD && !KeePassUAP) + get { return GetImage(16, 16); } // Backward compatibility +#else + get { return GetImage(); } // Backward compatibility +#endif } public PwCustomIcon(PwUuid pwUuid, byte[] pbImageDataPng) { Debug.Assert(pwUuid != null); if(pwUuid == null) throw new ArgumentNullException("pwUuid"); - Debug.Assert(pwUuid != PwUuid.Zero); - if(pwUuid == PwUuid.Zero) throw new ArgumentException("pwUuid == 0"); - + Debug.Assert(!pwUuid.Equals(PwUuid.Zero)); + if(pwUuid.Equals(PwUuid.Zero)) throw new ArgumentException("pwUuid == 0."); Debug.Assert(pbImageDataPng != null); if(pbImageDataPng == null) throw new ArgumentNullException("pbImageDataPng"); m_pwUuid = pwUuid; m_pbImageDataPng = pbImageDataPng; -#if !KeePassLibSD // MemoryStream ms = new MemoryStream(m_pbImageDataPng, false); - // m_pCachedImage = Image.FromStream(ms); + // m_imgOrg = Image.FromStream(ms); // ms.Close(); - m_pCachedImage = GfxUtil.LoadImage(m_pbImageDataPng); -#else - m_pCachedImage = null; -#endif + try { m_imgOrg = GfxUtil.LoadImage(m_pbImageDataPng); } + catch(Exception) { Debug.Assert(false); } + + if(m_imgOrg != null) + m_dImageCache[GetID(m_imgOrg.Width, m_imgOrg.Height)] = + m_imgOrg; } + + private static long GetID(int w, int h) + { + return (((long)w << 32) ^ (long)h); + } + + /// + /// Get the icon as an Image (original size). + /// + public Image GetImage() + { + return m_imgOrg; + } + +#if (!KeePassLibSD && !KeePassUAP) + /// + /// Get the icon as an Image (with the specified size). + /// + /// Width of the returned image. + /// Height of the returned image. + public Image GetImage(int w, int h) + { + if(w < 0) { Debug.Assert(false); return m_imgOrg; } + if(h < 0) { Debug.Assert(false); return m_imgOrg; } + if(m_imgOrg == null) return null; + + long lID = GetID(w, h); + + Image img; + if(m_dImageCache.TryGetValue(lID, out img)) return img; + + img = GfxUtil.ScaleImage(m_imgOrg, w, h, ScaleTransformFlags.UIIcon); + m_dImageCache[lID] = img; + return img; + } +#endif } } diff --git a/src/KeePassLib2Android/PwDatabase.cs b/src/KeePassLib2Android/PwDatabase.cs index d3ef6570..5f3f7129 100644 --- a/src/KeePassLib2Android/PwDatabase.cs +++ b/src/KeePassLib2Android/PwDatabase.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,7 +21,10 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; + +#if !KeePassUAP using System.Drawing; +#endif using KeePassLib.Collections; using KeePassLib.Cryptography; @@ -610,7 +613,7 @@ namespace KeePassLib /// Logger that recieves status information. public void Save(IStatusLogger slLogger) { - Debug.Assert(ValidateUuidUniqueness()); + Debug.Assert(!HasDuplicateUuids()); FileLock fl = null; if(m_bUseFileLocks) fl = new FileLock(m_ioSource); @@ -684,43 +687,53 @@ namespace KeePassLib Clear(); } - public void MergeIn(PwDatabase pwSource, PwMergeMethod mm) + public void MergeIn(PwDatabase pdSource, PwMergeMethod mm) { - MergeIn(pwSource, mm, null); + MergeIn(pdSource, mm, null); } - /// - /// Synchronize the current database with another one. - /// - /// Input database to synchronize with. This input - /// database is used to update the current one, but is not modified! You - /// must copy the current object if you want a second instance of the - /// synchronized database. The input database must not be seen as valid - /// database any more after calling Synchronize. - /// Merge method. - /// Logger to report status messages to. - /// May be null. - public void MergeIn(PwDatabase pwSource, PwMergeMethod mm, + public void MergeIn(PwDatabase pdSource, PwMergeMethod mm, IStatusLogger slStatus) { - if(pwSource == null) throw new ArgumentNullException("pwSource"); - - PwGroup pgOrgStructure = m_pgRootGroup.CloneStructure(); - PwGroup pgSrcStructure = pwSource.m_pgRootGroup.CloneStructure(); + if(pdSource == null) throw new ArgumentNullException("pdSource"); if(mm == PwMergeMethod.CreateNewUuids) - pwSource.RootGroup.CreateNewItemUuids(true, true, true); - - GroupHandler gh = delegate(PwGroup pg) { - if(pg == pwSource.m_pgRootGroup) return true; + pdSource.RootGroup.Uuid = new PwUuid(true); + pdSource.RootGroup.CreateNewItemUuids(true, true, true); + } + // PwGroup pgOrgStructure = m_pgRootGroup.CloneStructure(); + // PwGroup pgSrcStructure = pdSource.RootGroup.CloneStructure(); + // Later in case 'if(mm == PwMergeMethod.Synchronize)': + // PwObjectPoolEx ppOrg = PwObjectPoolEx.FromGroup(pgOrgStructure); + // PwObjectPoolEx ppSrc = PwObjectPoolEx.FromGroup(pgSrcStructure); + + PwObjectPoolEx ppOrg = PwObjectPoolEx.FromGroup(m_pgRootGroup); + PwObjectPoolEx ppSrc = PwObjectPoolEx.FromGroup(pdSource.RootGroup); + + GroupHandler ghSrc = delegate(PwGroup pg) + { + // if(pg == pdSource.m_pgRootGroup) return true; + + // Do not use ppOrg for finding the group, because new groups + // might have been added (which are not in the pool, and the + // pool should not be modified) PwGroup pgLocal = m_pgRootGroup.FindGroup(pg.Uuid, true); + if(pgLocal == null) { PwGroup pgSourceParent = pg.ParentGroup; PwGroup pgLocalContainer; - if(pgSourceParent == pwSource.m_pgRootGroup) + if(pgSourceParent == null) + { + // pg is the root group of pdSource, and no corresponding + // local group was found; create the group within the + // local root group + Debug.Assert(pg == pdSource.m_pgRootGroup); + pgLocalContainer = m_pgRootGroup; + } + else if(pgSourceParent == pdSource.m_pgRootGroup) pgLocalContainer = m_pgRootGroup; else pgLocalContainer = m_pgRootGroup.FindGroup(pgSourceParent.Uuid, true); @@ -730,7 +743,10 @@ namespace KeePassLib PwGroup pgNew = new PwGroup(false, false); pgNew.Uuid = pg.Uuid; pgNew.AssignProperties(pg, false, true); - pgLocalContainer.AddGroup(pgNew, true); + + // pgLocalContainer.AddGroup(pgNew, true); + InsertObjectAtBestPos(pgLocalContainer.Groups, pgNew, ppSrc); + pgNew.ParentGroup = pgLocalContainer; } else // pgLocal != null { @@ -749,14 +765,18 @@ namespace KeePassLib return ((slStatus != null) ? slStatus.ContinueWork() : true); }; - EntryHandler eh = delegate(PwEntry pe) + EntryHandler ehSrc = delegate(PwEntry pe) { - PwEntry peLocal = m_pgRootGroup.FindEntry(pe.Uuid, true); + // PwEntry peLocal = m_pgRootGroup.FindEntry(pe.Uuid, true); + PwEntry peLocal = (ppOrg.GetItemByUuid(pe.Uuid) as PwEntry); + Debug.Assert(object.ReferenceEquals(peLocal, + m_pgRootGroup.FindEntry(pe.Uuid, true))); + if(peLocal == null) { PwGroup pgSourceParent = pe.ParentGroup; PwGroup pgLocalContainer; - if(pgSourceParent == pwSource.m_pgRootGroup) + if(pgSourceParent == pdSource.m_pgRootGroup) pgLocalContainer = m_pgRootGroup; else pgLocalContainer = m_pgRootGroup.FindGroup(pgSourceParent.Uuid, true); @@ -766,7 +786,10 @@ namespace KeePassLib PwEntry peNew = new PwEntry(false, false); peNew.Uuid = pe.Uuid; peNew.AssignProperties(pe, false, true, true); - pgLocalContainer.AddEntry(peNew, true); + + // pgLocalContainer.AddEntry(peNew, true); + InsertObjectAtBestPos(pgLocalContainer.Entries, peNew, ppSrc); + peNew.ParentGroup = pgLocalContainer; } else // peLocal != null { @@ -779,12 +802,12 @@ namespace KeePassLib bool bOrgBackup = !bEquals; if(mm != PwMergeMethod.OverwriteExisting) - bOrgBackup &= (pe.LastModificationTime > peLocal.LastModificationTime); + bOrgBackup &= (TimeUtil.CompareLastMod(pe, peLocal, true) > 0); bOrgBackup &= !pe.HasBackupOfData(peLocal, false, true); if(bOrgBackup) peLocal.CreateBackup(null); // Maintain at end bool bSrcBackup = !bEquals && (mm != PwMergeMethod.OverwriteExisting); - bSrcBackup &= (peLocal.LastModificationTime > pe.LastModificationTime); + bSrcBackup &= (TimeUtil.CompareLastMod(peLocal, pe, true) > 0); bSrcBackup &= !peLocal.HasBackupOfData(pe, false, true); if(bSrcBackup) pe.CreateBackup(null); // Maintain at end @@ -803,7 +826,8 @@ namespace KeePassLib return ((slStatus != null) ? slStatus.ContinueWork() : true); }; - if(!pwSource.RootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh)) + ghSrc(pdSource.RootGroup); + if(!pdSource.RootGroup.TraverseTree(TraversalMethod.PreOrder, ghSrc, ehSrc)) throw new InvalidOperationException(); IStatusLogger slPrevStatus = m_slStatus; @@ -811,39 +835,42 @@ namespace KeePassLib if(mm == PwMergeMethod.Synchronize) { - ApplyDeletions(pwSource.m_vDeletedObjects, true); - ApplyDeletions(m_vDeletedObjects, false); + RelocateGroups(ppOrg, ppSrc); + RelocateEntries(ppOrg, ppSrc); + ReorderObjects(m_pgRootGroup, ppOrg, ppSrc); - PwObjectPool ppOrgGroups = PwObjectPool.FromGroupRecursive( - pgOrgStructure, false); - PwObjectPool ppSrcGroups = PwObjectPool.FromGroupRecursive( - pgSrcStructure, false); - PwObjectPool ppOrgEntries = PwObjectPool.FromGroupRecursive( - pgOrgStructure, true); - PwObjectPool ppSrcEntries = PwObjectPool.FromGroupRecursive( - pgSrcStructure, true); + // After all relocations and reorderings + MergeInLocationChanged(m_pgRootGroup, ppOrg, ppSrc); + ppOrg = null; // Pools are now invalid, because the location + ppSrc = null; // changed times have been merged in - RelocateGroups(ppOrgGroups, ppSrcGroups); - ReorderGroups(ppOrgGroups, ppSrcGroups); - RelocateEntries(ppOrgEntries, ppSrcEntries); - ReorderEntries(ppOrgEntries, ppSrcEntries); - Debug.Assert(ValidateUuidUniqueness()); + // Delete *after* relocating, because relocating might + // empty some groups that are marked for deletion (and + // objects that weren't relocated yet might prevent the + // deletion) + Dictionary dOrgDel = CreateDeletedObjectsPool(); + MergeInDeletionInfo(pdSource.m_vDeletedObjects, dOrgDel); + ApplyDeletions(m_pgRootGroup, dOrgDel); + + // The list and the dictionary should be kept in sync + Debug.Assert(m_vDeletedObjects.UCount == (uint)dOrgDel.Count); } // Must be called *after* merging groups, because group UUIDs // are required for recycle bin and entry template UUIDs - MergeInDbProperties(pwSource, mm); + MergeInDbProperties(pdSource, mm); - MergeInCustomIcons(pwSource); + MergeInCustomIcons(pdSource); MaintainBackups(); + Debug.Assert(!HasDuplicateUuids()); m_slStatus = slPrevStatus; } - private void MergeInCustomIcons(PwDatabase pwSource) + private void MergeInCustomIcons(PwDatabase pdSource) { - foreach(PwCustomIcon pwci in pwSource.CustomIcons) + foreach(PwCustomIcon pwci in pdSource.CustomIcons) { if(GetCustomIconIndex(pwci.Uuid) >= 0) continue; @@ -852,77 +879,120 @@ namespace KeePassLib } } - /// - /// Apply a list of deleted objects. - /// - /// List of deleted objects. - private void ApplyDeletions(PwObjectList listDelObjects, - bool bCopyDeletionInfoToLocal) + private Dictionary CreateDeletedObjectsPool() { - Debug.Assert(listDelObjects != null); if(listDelObjects == null) throw new ArgumentNullException("listDelObjects"); + Dictionary d = + new Dictionary(); - LinkedList listGroupsToDelete = new LinkedList(); - LinkedList listEntriesToDelete = new LinkedList(); - - GroupHandler gh = delegate(PwGroup pg) + int n = (int)m_vDeletedObjects.UCount; + for(int i = n - 1; i >= 0; --i) { - if(pg == m_pgRootGroup) return true; + PwDeletedObject pdo = m_vDeletedObjects.GetAt((uint)i); - foreach(PwDeletedObject pdo in listDelObjects) + PwDeletedObject pdoEx; + if(d.TryGetValue(pdo.Uuid, out pdoEx)) { - if(pg.Uuid.EqualsValue(pdo.Uuid)) - if(pg.LastModificationTime < pdo.DeletionTime) - listGroupsToDelete.AddLast(pg); + Debug.Assert(false); // Found duplicate, which should not happen + + if(pdo.DeletionTime > pdoEx.DeletionTime) + pdoEx.DeletionTime = pdo.DeletionTime; + + m_vDeletedObjects.RemoveAt((uint)i); } + else d[pdo.Uuid] = pdo; + } - return ((m_slStatus != null) ? m_slStatus.ContinueWork() : true); - }; + return d; + } - EntryHandler eh = delegate(PwEntry pe) + private void MergeInDeletionInfo(PwObjectList lSrc, + Dictionary dOrgDel) + { + foreach(PwDeletedObject pdoSrc in lSrc) { - foreach(PwDeletedObject pdo in listDelObjects) + PwDeletedObject pdoOrg; + if(dOrgDel.TryGetValue(pdoSrc.Uuid, out pdoOrg)) // Update { - if(pe.Uuid.EqualsValue(pdo.Uuid)) - if(pe.LastModificationTime < pdo.DeletionTime) - listEntriesToDelete.AddLast(pe); + Debug.Assert(pdoOrg.Uuid.Equals(pdoSrc.Uuid)); + + if(pdoSrc.DeletionTime > pdoOrg.DeletionTime) + pdoOrg.DeletionTime = pdoSrc.DeletionTime; } - - return ((m_slStatus != null) ? m_slStatus.ContinueWork() : true); - }; - - m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh); - - foreach(PwGroup pg in listGroupsToDelete) - pg.ParentGroup.Groups.Remove(pg); - foreach(PwEntry pe in listEntriesToDelete) - pe.ParentGroup.Entries.Remove(pe); - - if(bCopyDeletionInfoToLocal) - { - foreach(PwDeletedObject pdoNew in listDelObjects) + else // Add { - bool bCopy = true; - - foreach(PwDeletedObject pdoLocal in m_vDeletedObjects) - { - if(pdoNew.Uuid.EqualsValue(pdoLocal.Uuid)) - { - bCopy = false; - - if(pdoNew.DeletionTime > pdoLocal.DeletionTime) - pdoLocal.DeletionTime = pdoNew.DeletionTime; - - break; - } - } - - if(bCopy) m_vDeletedObjects.Add(pdoNew); + m_vDeletedObjects.Add(pdoSrc); + dOrgDel[pdoSrc.Uuid] = pdoSrc; } } } - private void RelocateGroups(PwObjectPool ppOrgStructure, - PwObjectPool ppSrcStructure) + private void ApplyDeletions(PwObjectList l, Predicate fCanDelete, + Dictionary dOrgDel) + where T : class, ITimeLogger, IStructureItem, IDeepCloneable + { + int n = (int)l.UCount; + for(int i = n - 1; i >= 0; --i) + { + if((m_slStatus != null) && !m_slStatus.ContinueWork()) break; + + T t = l.GetAt((uint)i); + + PwDeletedObject pdo; + if(dOrgDel.TryGetValue(t.Uuid, out pdo)) + { + Debug.Assert(t.Uuid.Equals(pdo.Uuid)); + + bool bDel = (TimeUtil.Compare(t.LastModificationTime, + pdo.DeletionTime, true) < 0); + bDel &= fCanDelete(t); + + if(bDel) l.RemoveAt((uint)i); + else + { + // Prevent future deletion attempts; this also prevents + // delayed deletions (emptying a group could cause a + // group to be deleted, if the deletion was prevented + // before due to the group not being empty) + if(!m_vDeletedObjects.Remove(pdo)) { Debug.Assert(false); } + if(!dOrgDel.Remove(pdo.Uuid)) { Debug.Assert(false); } + } + } + } + } + + private static bool SafeCanDeleteGroup(PwGroup pg) + { + if(pg == null) { Debug.Assert(false); return false; } + + if(pg.Groups.UCount > 0) return false; + if(pg.Entries.UCount > 0) return false; + return true; + } + + private static bool SafeCanDeleteEntry(PwEntry pe) + { + if(pe == null) { Debug.Assert(false); return false; } + + return true; + } + + // Apply deletions on all objects in the specified container + // (but not the container itself), using post-order traversal + // to avoid implicit deletions; + // https://sourceforge.net/p/keepass/bugs/1499/ + private void ApplyDeletions(PwGroup pgContainer, + Dictionary dOrgDel) + { + foreach(PwGroup pg in pgContainer.Groups) // Post-order traversal + { + ApplyDeletions(pg, dOrgDel); + } + + ApplyDeletions(pgContainer.Groups, PwDatabase.SafeCanDeleteGroup, dOrgDel); + ApplyDeletions(pgContainer.Entries, PwDatabase.SafeCanDeleteEntry, dOrgDel); + } + + private void RelocateGroups(PwObjectPoolEx ppOrg, PwObjectPoolEx ppSrc) { PwObjectList vGroups = m_pgRootGroup.GetGroups(true); @@ -931,18 +1001,25 @@ namespace KeePassLib if((m_slStatus != null) && !m_slStatus.ContinueWork()) break; // PwGroup pgOrg = pgOrgStructure.FindGroup(pg.Uuid, true); - IStructureItem ptOrg = ppOrgStructure.Get(pg.Uuid); + IStructureItem ptOrg = ppOrg.GetItemByUuid(pg.Uuid); if(ptOrg == null) continue; // PwGroup pgSrc = pgSrcStructure.FindGroup(pg.Uuid, true); - IStructureItem ptSrc = ppSrcStructure.Get(pg.Uuid); + IStructureItem ptSrc = ppSrc.GetItemByUuid(pg.Uuid); if(ptSrc == null) continue; PwGroup pgOrgParent = ptOrg.ParentGroup; + // vGroups does not contain the root group, thus pgOrgParent + // should not be null + if(pgOrgParent == null) { Debug.Assert(false); continue; } + PwGroup pgSrcParent = ptSrc.ParentGroup; - if(pgOrgParent.Uuid.EqualsValue(pgSrcParent.Uuid)) + // pgSrcParent may be null (for the source root group) + if(pgSrcParent == null) continue; + + if(pgOrgParent.Uuid.Equals(pgSrcParent.Uuid)) { - pg.LocationChanged = ((ptSrc.LocationChanged > ptOrg.LocationChanged) ? - ptSrc.LocationChanged : ptOrg.LocationChanged); + // pg.LocationChanged = ((ptSrc.LocationChanged > ptOrg.LocationChanged) ? + // ptSrc.LocationChanged : ptOrg.LocationChanged); continue; } @@ -954,12 +1031,16 @@ namespace KeePassLib if(pgLocal.IsContainedIn(pg)) continue; pg.ParentGroup.Groups.Remove(pg); - pgLocal.AddGroup(pg, true); - pg.LocationChanged = ptSrc.LocationChanged; + + // pgLocal.AddGroup(pg, true); + InsertObjectAtBestPos(pgLocal.Groups, pg, ppSrc); + pg.ParentGroup = pgLocal; + + // pg.LocationChanged = ptSrc.LocationChanged; } else { - Debug.Assert(pg.ParentGroup.Uuid.EqualsValue(pgOrgParent.Uuid)); + Debug.Assert(pg.ParentGroup.Uuid.Equals(pgOrgParent.Uuid)); Debug.Assert(pg.LocationChanged == ptOrg.LocationChanged); } } @@ -967,8 +1048,7 @@ namespace KeePassLib Debug.Assert(m_pgRootGroup.GetGroups(true).UCount == vGroups.UCount); } - private void RelocateEntries(PwObjectPool ppOrgStructure, - PwObjectPool ppSrcStructure) + private void RelocateEntries(PwObjectPoolEx ppOrg, PwObjectPoolEx ppSrc) { PwObjectList vEntries = m_pgRootGroup.GetEntries(true); @@ -977,18 +1057,18 @@ namespace KeePassLib if((m_slStatus != null) && !m_slStatus.ContinueWork()) break; // PwEntry peOrg = pgOrgStructure.FindEntry(pe.Uuid, true); - IStructureItem ptOrg = ppOrgStructure.Get(pe.Uuid); + IStructureItem ptOrg = ppOrg.GetItemByUuid(pe.Uuid); if(ptOrg == null) continue; // PwEntry peSrc = pgSrcStructure.FindEntry(pe.Uuid, true); - IStructureItem ptSrc = ppSrcStructure.Get(pe.Uuid); + IStructureItem ptSrc = ppSrc.GetItemByUuid(pe.Uuid); if(ptSrc == null) continue; PwGroup pgOrg = ptOrg.ParentGroup; PwGroup pgSrc = ptSrc.ParentGroup; - if(pgOrg.Uuid.EqualsValue(pgSrc.Uuid)) + if(pgOrg.Uuid.Equals(pgSrc.Uuid)) { - pe.LocationChanged = ((ptSrc.LocationChanged > ptOrg.LocationChanged) ? - ptSrc.LocationChanged : ptOrg.LocationChanged); + // pe.LocationChanged = ((ptSrc.LocationChanged > ptOrg.LocationChanged) ? + // ptSrc.LocationChanged : ptOrg.LocationChanged); continue; } @@ -998,12 +1078,16 @@ namespace KeePassLib if(pgLocal == null) { Debug.Assert(false); continue; } pe.ParentGroup.Entries.Remove(pe); - pgLocal.AddEntry(pe, true); - pe.LocationChanged = ptSrc.LocationChanged; + + // pgLocal.AddEntry(pe, true); + InsertObjectAtBestPos(pgLocal.Entries, pe, ppSrc); + pe.ParentGroup = pgLocal; + + // pe.LocationChanged = ptSrc.LocationChanged; } else { - Debug.Assert(pe.ParentGroup.Uuid.EqualsValue(pgOrg.Uuid)); + Debug.Assert(pe.ParentGroup.Uuid.Equals(pgOrg.Uuid)); Debug.Assert(pe.LocationChanged == ptOrg.LocationChanged); } } @@ -1011,274 +1095,332 @@ namespace KeePassLib Debug.Assert(m_pgRootGroup.GetEntries(true).UCount == vEntries.UCount); } - private void ReorderGroups(PwObjectPool ppOrgStructure, - PwObjectPool ppSrcStructure) + private void ReorderObjects(PwGroup pg, PwObjectPoolEx ppOrg, + PwObjectPoolEx ppSrc) { - GroupHandler gh = delegate(PwGroup pg) - { - ReorderObjectList(pg.Groups, ppOrgStructure, - ppSrcStructure, false); - return true; - }; + ReorderObjectList(pg.Groups, ppOrg, ppSrc); + ReorderObjectList(pg.Entries, ppOrg, ppSrc); - ReorderObjectList(m_pgRootGroup.Groups, ppOrgStructure, - ppSrcStructure, false); - m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, null); + foreach(PwGroup pgSub in pg.Groups) + { + ReorderObjects(pgSub, ppOrg, ppSrc); + } } - private void ReorderEntries(PwObjectPool ppOrgStructure, - PwObjectPool ppSrcStructure) + private void ReorderObjectList(PwObjectList lItems, + PwObjectPoolEx ppOrg, PwObjectPoolEx ppSrc) + where T : class, ITimeLogger, IStructureItem, IDeepCloneable { - GroupHandler gh = delegate(PwGroup pg) - { - ReorderObjectList(pg.Entries, ppOrgStructure, - ppSrcStructure, true); - return true; - }; - - ReorderObjectList(m_pgRootGroup.Entries, ppOrgStructure, - ppSrcStructure, true); - m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, null); - } - - private void ReorderObjectList(PwObjectList vItems, - PwObjectPool ppOrgStructure, PwObjectPool ppSrcStructure, bool bEntries) - where T : class, IStructureItem, IDeepCloneable - { - if(!ObjectListRequiresReorder(vItems, ppOrgStructure, ppSrcStructure, - bEntries)) return; + List> lBlocks = PartitionConsec(lItems, ppOrg, ppSrc); + if(lBlocks.Count <= 1) return; #if DEBUG - PwObjectList vOrgListItems = vItems.CloneShallow(); + PwObjectList lOrgItems = lItems.CloneShallow(); #endif - Queue> qToDo = new Queue>(); - qToDo.Enqueue(new KeyValuePair(0, vItems.UCount - 1)); + Queue> qToDo = new Queue>(); + qToDo.Enqueue(new KeyValuePair(0, lBlocks.Count - 1)); while(qToDo.Count > 0) { if((m_slStatus != null) && !m_slStatus.ContinueWork()) break; - KeyValuePair kvp = qToDo.Dequeue(); - if(kvp.Value <= kvp.Key) { Debug.Assert(false); continue; } + KeyValuePair kvp = qToDo.Dequeue(); + if(kvp.Key >= kvp.Value) { Debug.Assert(false); continue; } - Queue qRelBefore = new Queue(); - Queue qRelAfter = new Queue(); - uint uPivot = FindLocationChangedPivot(vItems, kvp, ppOrgStructure, - ppSrcStructure, qRelBefore, qRelAfter, bEntries); - T ptPivot = vItems.GetAt(uPivot); + PwObjectPoolEx pPool; + int iPivot = FindLocationChangedPivot(lBlocks, kvp, out pPool); + PwObjectBlock bPivot = lBlocks[iPivot]; - List vToSort = vItems.GetRange(kvp.Key, kvp.Value); - Queue qBefore = new Queue(); - Queue qAfter = new Queue(); + T tPivotPrimary = bPivot.PrimaryItem; + if(tPivotPrimary == null) { Debug.Assert(false); continue; } + ulong idPivot = pPool.GetIdByUuid(tPivotPrimary.Uuid); + if(idPivot == 0) { Debug.Assert(false); continue; } + + Queue> qBefore = new Queue>(); + Queue> qAfter = new Queue>(); bool bBefore = true; - foreach(T pt in vToSort) + for(int i = kvp.Key; i <= kvp.Value; ++i) { - if(pt == ptPivot) { bBefore = false; continue; } + if(i == iPivot) { bBefore = false; continue; } - bool bAdded = false; - foreach(PwUuid puBefore in qRelBefore) + PwObjectBlock b = lBlocks[i]; + Debug.Assert(b.LocationChanged <= bPivot.LocationChanged); + + T t = b.PrimaryItem; + if(t != null) { - if(puBefore.EqualsValue(pt.Uuid)) + ulong idBPri = pPool.GetIdByUuid(t.Uuid); + if(idBPri > 0) { - qBefore.Enqueue(pt); - bAdded = true; - break; + if(idBPri < idPivot) qBefore.Enqueue(b); + else qAfter.Enqueue(b); + + continue; } } - if(bAdded) continue; + else { Debug.Assert(false); } - foreach(PwUuid puAfter in qRelAfter) - { - if(puAfter.EqualsValue(pt.Uuid)) - { - qAfter.Enqueue(pt); - bAdded = true; - break; - } - } - if(bAdded) continue; - - if(bBefore) qBefore.Enqueue(pt); - else qAfter.Enqueue(pt); - } - Debug.Assert(bBefore == false); - - uint uPos = kvp.Key; - while(qBefore.Count > 0) vItems.SetAt(uPos++, qBefore.Dequeue()); - vItems.SetAt(uPos++, ptPivot); - while(qAfter.Count > 0) vItems.SetAt(uPos++, qAfter.Dequeue()); - Debug.Assert(uPos == (kvp.Value + 1)); - - int iNewPivot = vItems.IndexOf(ptPivot); - if((iNewPivot < (int)kvp.Key) || (iNewPivot > (int)kvp.Value)) - { - Debug.Assert(false); - continue; + if(bBefore) qBefore.Enqueue(b); + else qAfter.Enqueue(b); } - if((iNewPivot - 1) > (int)kvp.Key) - qToDo.Enqueue(new KeyValuePair(kvp.Key, - (uint)(iNewPivot - 1))); + int j = kvp.Key; + while(qBefore.Count > 0) { lBlocks[j] = qBefore.Dequeue(); ++j; } + int iNewPivot = j; + lBlocks[j] = bPivot; + ++j; + while(qAfter.Count > 0) { lBlocks[j] = qAfter.Dequeue(); ++j; } + Debug.Assert(j == (kvp.Value + 1)); - if((iNewPivot + 1) < (int)kvp.Value) - qToDo.Enqueue(new KeyValuePair((uint)(iNewPivot + 1), - kvp.Value)); + if((iNewPivot - 1) > kvp.Key) + qToDo.Enqueue(new KeyValuePair(kvp.Key, iNewPivot - 1)); + if((iNewPivot + 1) < kvp.Value) + qToDo.Enqueue(new KeyValuePair(iNewPivot + 1, kvp.Value)); } -#if DEBUG - foreach(T ptItem in vOrgListItems) + uint u = 0; + foreach(PwObjectBlock b in lBlocks) { - Debug.Assert(vItems.IndexOf(ptItem) >= 0); + foreach(T t in b) + { + lItems.SetAt(u, t); + ++u; + } + } + Debug.Assert(u == lItems.UCount); + +#if DEBUG + Debug.Assert(u == lOrgItems.UCount); + foreach(T ptItem in lOrgItems) + { + Debug.Assert(lItems.IndexOf(ptItem) >= 0); } #endif } - private static uint FindLocationChangedPivot(PwObjectList vItems, - KeyValuePair kvpRange, PwObjectPool ppOrgStructure, - PwObjectPool ppSrcStructure, Queue qBefore, Queue qAfter, - bool bEntries) - where T : class, IStructureItem, IDeepCloneable + private static List> PartitionConsec(PwObjectList lItems, + PwObjectPoolEx ppOrg, PwObjectPoolEx ppSrc) + where T : class, ITimeLogger, IStructureItem, IDeepCloneable { - uint uPosMax = kvpRange.Key; + List> lBlocks = new List>(); + + Dictionary dItemUuids = new Dictionary(); + foreach(T t in lItems) { dItemUuids[t.Uuid] = true; } + + uint n = lItems.UCount; + for(uint u = 0; u < n; ++u) + { + T t = lItems.GetAt(u); + + PwObjectBlock b = new PwObjectBlock(); + + DateTime dtLoc; + PwObjectPoolEx pPool = GetBestPool(t, ppOrg, ppSrc, out dtLoc); + b.Add(t, dtLoc, pPool); + + lBlocks.Add(b); + + ulong idOrg = ppOrg.GetIdByUuid(t.Uuid); + ulong idSrc = ppSrc.GetIdByUuid(t.Uuid); + if((idOrg == 0) || (idSrc == 0)) continue; + + for(uint x = u + 1; x < n; ++x) + { + T tNext = lItems.GetAt(x); + + ulong idOrgNext = idOrg + 1; + while(true) + { + IStructureItem ptOrg = ppOrg.GetItemById(idOrgNext); + if(ptOrg == null) { idOrgNext = 0; break; } + if(ptOrg.Uuid.Equals(tNext.Uuid)) break; // Found it + if(dItemUuids.ContainsKey(ptOrg.Uuid)) { idOrgNext = 0; break; } + ++idOrgNext; + } + if(idOrgNext == 0) break; + + ulong idSrcNext = idSrc + 1; + while(true) + { + IStructureItem ptSrc = ppSrc.GetItemById(idSrcNext); + if(ptSrc == null) { idSrcNext = 0; break; } + if(ptSrc.Uuid.Equals(tNext.Uuid)) break; // Found it + if(dItemUuids.ContainsKey(ptSrc.Uuid)) { idSrcNext = 0; break; } + ++idSrcNext; + } + if(idSrcNext == 0) break; + + pPool = GetBestPool(tNext, ppOrg, ppSrc, out dtLoc); + b.Add(tNext, dtLoc, pPool); + + ++u; + idOrg = idOrgNext; + idSrc = idSrcNext; + } + } + + return lBlocks; + } + + private static PwObjectPoolEx GetBestPool(T t, PwObjectPoolEx ppOrg, + PwObjectPoolEx ppSrc, out DateTime dtLoc) + where T : class, ITimeLogger, IStructureItem, IDeepCloneable + { + PwObjectPoolEx p = null; + dtLoc = DateTime.MinValue; + + IStructureItem ptOrg = ppOrg.GetItemByUuid(t.Uuid); + if(ptOrg != null) + { + dtLoc = ptOrg.LocationChanged; + p = ppOrg; + } + + IStructureItem ptSrc = ppSrc.GetItemByUuid(t.Uuid); + if((ptSrc != null) && (ptSrc.LocationChanged > dtLoc)) + { + dtLoc = ptSrc.LocationChanged; + p = ppSrc; + } + + Debug.Assert(p != null); + return p; + } + + private static int FindLocationChangedPivot(List> lBlocks, + KeyValuePair kvpRange, out PwObjectPoolEx pPool) + where T : class, ITimeLogger, IStructureItem, IDeepCloneable + { + pPool = null; + + int iPosMax = kvpRange.Key; DateTime dtMax = DateTime.MinValue; - List vNeighborSrc = null; - for(uint u = kvpRange.Key; u <= kvpRange.Value; ++u) + for(int i = kvpRange.Key; i <= kvpRange.Value; ++i) { - T pt = vItems.GetAt(u); - - // IStructureItem ptOrg = pgOrgStructure.FindObject(pt.Uuid, true, bEntries); - IStructureItem ptOrg = ppOrgStructure.Get(pt.Uuid); - if((ptOrg != null) && (ptOrg.LocationChanged > dtMax)) + PwObjectBlock b = lBlocks[i]; + if(b.LocationChanged > dtMax) { - uPosMax = u; - dtMax = ptOrg.LocationChanged; // No 'continue' - vNeighborSrc = ptOrg.ParentGroup.GetObjects(false, bEntries); - } - - // IStructureItem ptSrc = pgSrcStructure.FindObject(pt.Uuid, true, bEntries); - IStructureItem ptSrc = ppSrcStructure.Get(pt.Uuid); - if((ptSrc != null) && (ptSrc.LocationChanged > dtMax)) - { - uPosMax = u; - dtMax = ptSrc.LocationChanged; // No 'continue' - vNeighborSrc = ptSrc.ParentGroup.GetObjects(false, bEntries); + iPosMax = i; + dtMax = b.LocationChanged; + pPool = b.PoolAssoc; } } - GetNeighborItems(vNeighborSrc, vItems.GetAt(uPosMax).Uuid, qBefore, qAfter); - return uPosMax; + return iPosMax; } - private static void GetNeighborItems(List vItems, - PwUuid pwPivot, Queue qBefore, Queue qAfter) + private static void MergeInLocationChanged(PwGroup pg, + PwObjectPoolEx ppOrg, PwObjectPoolEx ppSrc) { - qBefore.Clear(); - qAfter.Clear(); - - // Checks after clearing the queues - if(vItems == null) { Debug.Assert(false); return; } // No throw - - bool bBefore = true; - for(int i = 0; i < vItems.Count; ++i) + GroupHandler gh = delegate(PwGroup pgSub) { - PwUuid pw = vItems[i].Uuid; + DateTime dt; + if(GetBestPool(pgSub, ppOrg, ppSrc, out dt) != null) + pgSub.LocationChanged = dt; + else { Debug.Assert(false); } + return true; + }; - if(pw.EqualsValue(pwPivot)) bBefore = false; - else if(bBefore) qBefore.Enqueue(pw); - else qAfter.Enqueue(pw); - } - Debug.Assert(bBefore == false); + EntryHandler eh = delegate(PwEntry pe) + { + DateTime dt; + if(GetBestPool(pe, ppOrg, ppSrc, out dt) != null) + pe.LocationChanged = dt; + else { Debug.Assert(false); } + return true; + }; + + gh(pg); + pg.TraverseTree(TraversalMethod.PreOrder, gh, eh); } - /// - /// Method to check whether a reordering is required. This fast test - /// allows to skip the reordering routine, resulting in a large - /// performance increase. - /// - private bool ObjectListRequiresReorder(PwObjectList vItems, - PwObjectPool ppOrgStructure, PwObjectPool ppSrcStructure, bool bEntries) - where T : class, IStructureItem, IDeepCloneable + private static void InsertObjectAtBestPos(PwObjectList lItems, + T tNew, PwObjectPoolEx ppSrc) + where T : class, ITimeLogger, IStructureItem, IDeepCloneable { - Debug.Assert(ppOrgStructure.ContainsOnlyType(bEntries ? typeof(PwEntry) : typeof(PwGroup))); - Debug.Assert(ppSrcStructure.ContainsOnlyType(bEntries ? typeof(PwEntry) : typeof(PwGroup))); - if(vItems.UCount <= 1) return false; + if(tNew == null) { Debug.Assert(false); return; } - if((m_slStatus != null) && !m_slStatus.ContinueWork()) return false; + ulong idSrc = ppSrc.GetIdByUuid(tNew.Uuid); + if(idSrc == 0) { Debug.Assert(false); lItems.Add(tNew); return; } - T ptFirst = vItems.GetAt(0); - // IStructureItem ptOrg = pgOrgStructure.FindObject(ptFirst.Uuid, true, bEntries); - IStructureItem ptOrg = ppOrgStructure.Get(ptFirst.Uuid); - if(ptOrg == null) return true; - // IStructureItem ptSrc = pgSrcStructure.FindObject(ptFirst.Uuid, true, bEntries); - IStructureItem ptSrc = ppSrcStructure.Get(ptFirst.Uuid); - if(ptSrc == null) return true; + const uint uIdOffset = 2; + Dictionary dOrg = new Dictionary(); + for(uint u = 0; u < lItems.UCount; ++u) + dOrg[lItems.GetAt(u).Uuid] = uIdOffset + u; - if(ptFirst.ParentGroup == null) { Debug.Assert(false); return true; } - PwGroup pgOrgParent = ptOrg.ParentGroup; - if(pgOrgParent == null) return true; // Root might be in tree - PwGroup pgSrcParent = ptSrc.ParentGroup; - if(pgSrcParent == null) return true; // Root might be in tree - - if(!ptFirst.ParentGroup.Uuid.EqualsValue(pgOrgParent.Uuid)) return true; - if(!pgOrgParent.Uuid.EqualsValue(pgSrcParent.Uuid)) return true; - - List lOrg = pgOrgParent.GetObjects(false, bEntries); - List lSrc = pgSrcParent.GetObjects(false, bEntries); - if(vItems.UCount != (uint)lOrg.Count) return true; - if(lOrg.Count != lSrc.Count) return true; - - for(uint u = 0; u < vItems.UCount; ++u) + ulong idSrcNext = idSrc + 1; + uint idOrgNext = 0; + while(true) { - IStructureItem pt = vItems.GetAt(u); - Debug.Assert(pt.ParentGroup == ptFirst.ParentGroup); - - if(!pt.Uuid.EqualsValue(lOrg[(int)u].Uuid)) return true; - if(!pt.Uuid.EqualsValue(lSrc[(int)u].Uuid)) return true; - if(pt.LocationChanged != lOrg[(int)u].LocationChanged) return true; - if(pt.LocationChanged != lSrc[(int)u].LocationChanged) return true; + IStructureItem pNext = ppSrc.GetItemById(idSrcNext); + if(pNext == null) break; + if(dOrg.TryGetValue(pNext.Uuid, out idOrgNext)) break; + ++idSrcNext; } - return false; + if(idOrgNext != 0) + { + lItems.Insert(idOrgNext - uIdOffset, tNew); + return; + } + + ulong idSrcPrev = idSrc - 1; + uint idOrgPrev = 0; + while(true) + { + IStructureItem pPrev = ppSrc.GetItemById(idSrcPrev); + if(pPrev == null) break; + if(dOrg.TryGetValue(pPrev.Uuid, out idOrgPrev)) break; + --idSrcPrev; + } + + if(idOrgPrev != 0) + { + lItems.Insert(idOrgPrev + 1 - uIdOffset, tNew); + return; + } + + lItems.Add(tNew); } - private void MergeInDbProperties(PwDatabase pwSource, PwMergeMethod mm) + private void MergeInDbProperties(PwDatabase pdSource, PwMergeMethod mm) { - if(pwSource == null) { Debug.Assert(false); return; } + if(pdSource == null) { Debug.Assert(false); return; } if((mm == PwMergeMethod.KeepExisting) || (mm == PwMergeMethod.None)) return; bool bForce = (mm == PwMergeMethod.OverwriteExisting); - if(bForce || (pwSource.m_dtNameChanged > m_dtNameChanged)) + if(bForce || (pdSource.m_dtNameChanged > m_dtNameChanged)) { - m_strName = pwSource.m_strName; - m_dtNameChanged = pwSource.m_dtNameChanged; + m_strName = pdSource.m_strName; + m_dtNameChanged = pdSource.m_dtNameChanged; } - if(bForce || (pwSource.m_dtDescChanged > m_dtDescChanged)) + if(bForce || (pdSource.m_dtDescChanged > m_dtDescChanged)) { - m_strDesc = pwSource.m_strDesc; - m_dtDescChanged = pwSource.m_dtDescChanged; + m_strDesc = pdSource.m_strDesc; + m_dtDescChanged = pdSource.m_dtDescChanged; } - if(bForce || (pwSource.m_dtDefaultUserChanged > m_dtDefaultUserChanged)) + if(bForce || (pdSource.m_dtDefaultUserChanged > m_dtDefaultUserChanged)) { - m_strDefaultUserName = pwSource.m_strDefaultUserName; - m_dtDefaultUserChanged = pwSource.m_dtDefaultUserChanged; + m_strDefaultUserName = pdSource.m_strDefaultUserName; + m_dtDefaultUserChanged = pdSource.m_dtDefaultUserChanged; } - if(bForce) m_clr = pwSource.m_clr; + if(bForce) m_clr = pdSource.m_clr; - PwUuid pwPrefBin = m_pwRecycleBin, pwAltBin = pwSource.m_pwRecycleBin; - if(bForce || (pwSource.m_dtRecycleBinChanged > m_dtRecycleBinChanged)) + PwUuid pwPrefBin = m_pwRecycleBin, pwAltBin = pdSource.m_pwRecycleBin; + if(bForce || (pdSource.m_dtRecycleBinChanged > m_dtRecycleBinChanged)) { - pwPrefBin = pwSource.m_pwRecycleBin; + pwPrefBin = pdSource.m_pwRecycleBin; pwAltBin = m_pwRecycleBin; - m_bUseRecycleBin = pwSource.m_bUseRecycleBin; - m_dtRecycleBinChanged = pwSource.m_dtRecycleBinChanged; + m_bUseRecycleBin = pdSource.m_bUseRecycleBin; + m_dtRecycleBinChanged = pdSource.m_dtRecycleBinChanged; } if(m_pgRootGroup.FindGroup(pwPrefBin, true) != null) m_pwRecycleBin = pwPrefBin; @@ -1286,12 +1428,12 @@ namespace KeePassLib m_pwRecycleBin = pwAltBin; else m_pwRecycleBin = PwUuid.Zero; // Debug.Assert(false); - PwUuid pwPrefTmp = m_pwEntryTemplatesGroup, pwAltTmp = pwSource.m_pwEntryTemplatesGroup; - if(bForce || (pwSource.m_dtEntryTemplatesChanged > m_dtEntryTemplatesChanged)) + PwUuid pwPrefTmp = m_pwEntryTemplatesGroup, pwAltTmp = pdSource.m_pwEntryTemplatesGroup; + if(bForce || (pdSource.m_dtEntryTemplatesChanged > m_dtEntryTemplatesChanged)) { - pwPrefTmp = pwSource.m_pwEntryTemplatesGroup; + pwPrefTmp = pdSource.m_pwEntryTemplatesGroup; pwAltTmp = m_pwEntryTemplatesGroup; - m_dtEntryTemplatesChanged = pwSource.m_dtEntryTemplatesChanged; + m_dtEntryTemplatesChanged = pdSource.m_dtEntryTemplatesChanged; } if(m_pgRootGroup.FindGroup(pwPrefTmp, true) != null) m_pwEntryTemplatesGroup = pwPrefTmp; @@ -1303,7 +1445,7 @@ namespace KeePassLib private void MergeEntryHistory(PwEntry pe, PwEntry peSource, PwMergeMethod mm) { - if(!pe.Uuid.EqualsValue(peSource.Uuid)) { Debug.Assert(false); return; } + if(!pe.Uuid.Equals(peSource.Uuid)) { Debug.Assert(false); return; } if(pe.History.UCount == peSource.History.UCount) { @@ -1323,27 +1465,32 @@ namespace KeePassLib if((m_slStatus != null) && !m_slStatus.ContinueWork()) return; - SortedList list = new SortedList(); + IDictionary dict = +#if KeePassLibSD + new SortedList(); +#else + new SortedDictionary(); +#endif foreach(PwEntry peOrg in pe.History) { - list[peOrg.LastModificationTime] = peOrg; + dict[peOrg.LastModificationTime] = peOrg; } foreach(PwEntry peSrc in peSource.History) { DateTime dt = peSrc.LastModificationTime; - if(list.ContainsKey(dt)) + if(dict.ContainsKey(dt)) { if(mm == PwMergeMethod.OverwriteExisting) - list[dt] = peSrc.CloneDeep(); + dict[dt] = peSrc.CloneDeep(); } - else list[dt] = peSrc.CloneDeep(); + else dict[dt] = peSrc.CloneDeep(); } pe.History.Clear(); - foreach(KeyValuePair kvpCur in list) + foreach(KeyValuePair kvpCur in dict) { - Debug.Assert(kvpCur.Value.Uuid.EqualsValue(pe.Uuid)); + Debug.Assert(kvpCur.Value.Uuid.Equals(pe.Uuid)); Debug.Assert(kvpCur.Value.History.UCount == 0); pe.History.Add(kvpCur.Value); } @@ -1370,12 +1517,12 @@ namespace KeePassLib /// Source file. public void Synchronize(string strFile) { - PwDatabase pwSource = new PwDatabase(); + PwDatabase pdSource = new PwDatabase(); IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFile); - pwSource.Open(ioc, m_pwUserKey, null); + pdSource.Open(ioc, m_pwUserKey, null); - MergeIn(pwSource, PwMergeMethod.Synchronize); + MergeIn(pdSource, PwMergeMethod.Synchronize); } */ /// @@ -1385,33 +1532,74 @@ namespace KeePassLib /// Index of the icon. public int GetCustomIconIndex(PwUuid pwIconId) { - int nIndex = 0; - - foreach(PwCustomIcon pwci in m_vCustomIcons) + for(int i = 0; i < m_vCustomIcons.Count; ++i) { - if(pwci.Uuid.EqualsValue(pwIconId)) - return nIndex; - - ++nIndex; + PwCustomIcon pwci = m_vCustomIcons[i]; + if(pwci.Uuid.Equals(pwIconId)) + return i; } // Debug.Assert(false); // Do not assert return -1; } - /// - /// Get a custom icon. This function can return null, if - /// no cached image of the icon is available. - /// - /// ID of the icon. - /// Image data. + public int GetCustomIconIndex(byte[] pbPngData) + { + if(pbPngData == null) { Debug.Assert(false); return -1; } + + for(int i = 0; i < m_vCustomIcons.Count; ++i) + { + PwCustomIcon pwci = m_vCustomIcons[i]; + byte[] pbEx = pwci.ImageDataPng; + if(pbEx == null) { Debug.Assert(false); continue; } + + if(MemUtil.ArraysEqual(pbEx, pbPngData)) + return i; + } + + return -1; + } + +#if KeePassUAP public Image GetCustomIcon(PwUuid pwIconId) { int nIndex = GetCustomIconIndex(pwIconId); + if(nIndex >= 0) + return m_vCustomIcons[nIndex].GetImage(); + else { Debug.Assert(false); } - if(nIndex >= 0) return m_vCustomIcons[nIndex].Image; - else { Debug.Assert(false); return null; } + return null; } +#elif !KeePassLibSD + [Obsolete("Additionally specify the size.")] + public Image GetCustomIcon(PwUuid pwIconId) + { + return GetCustomIcon(pwIconId, 16, 16); // Backward compatibility + } + + /// + /// Get a custom icon. This method can return null, + /// e.g. if no cached image of the icon is available. + /// + /// ID of the icon. + /// Width of the returned image. If this is + /// negative, the image is returned in its original size. + /// Height of the returned image. If this is + /// negative, the image is returned in its original size. + public Image GetCustomIcon(PwUuid pwIconId, int w, int h) + { + int nIndex = GetCustomIconIndex(pwIconId); + if(nIndex >= 0) + { + if((w >= 0) && (h >= 0)) + return m_vCustomIcons[nIndex].GetImage(w, h); + else return m_vCustomIcons[nIndex].GetImage(); // No assert + } + else { Debug.Assert(false); } + + return null; + } +#endif public bool DeleteCustomIcons(List vUuidsToDelete) { @@ -1422,11 +1610,11 @@ namespace KeePassLib GroupHandler gh = delegate(PwGroup pg) { PwUuid uuidThis = pg.CustomIconUuid; - if(uuidThis.EqualsValue(PwUuid.Zero)) return true; + if(uuidThis.Equals(PwUuid.Zero)) return true; foreach(PwUuid uuidDelete in vUuidsToDelete) { - if(uuidThis.EqualsValue(uuidDelete)) + if(uuidThis.Equals(uuidDelete)) { pg.CustomIconUuid = PwUuid.Zero; break; @@ -1461,11 +1649,11 @@ namespace KeePassLib private static void RemoveCustomIconUuid(PwEntry pe, List vToDelete) { PwUuid uuidThis = pe.CustomIconUuid; - if(uuidThis.EqualsValue(PwUuid.Zero)) return; + if(uuidThis.Equals(PwUuid.Zero)) return; foreach(PwUuid uuidDelete in vToDelete) { - if(uuidThis.EqualsValue(uuidDelete)) + if(uuidThis.Equals(uuidDelete)) { pe.CustomIconUuid = PwUuid.Zero; break; @@ -1476,33 +1664,97 @@ namespace KeePassLib RemoveCustomIconUuid(peHistory, vToDelete); } - private bool ValidateUuidUniqueness() + private int GetTotalObjectUuidCount() { -#if DEBUG - List l = new List(); - bool bAllUnique = true; + uint uGroups, uEntries; + m_pgRootGroup.GetCounts(true, out uGroups, out uEntries); + + uint uTotal = uGroups + uEntries + 1; // 1 for root group + if(uTotal > 0x7FFFFFFFU) { Debug.Assert(false); return 0x7FFFFFFF; } + return (int)uTotal; + } + + internal bool HasDuplicateUuids() + { + int nTotal = GetTotalObjectUuidCount(); + Dictionary d = new Dictionary(nTotal); + bool bDupFound = false; GroupHandler gh = delegate(PwGroup pg) { - foreach(PwUuid u in l) - bAllUnique &= !pg.Uuid.EqualsValue(u); - l.Add(pg.Uuid); - return bAllUnique; + PwUuid pu = pg.Uuid; + if(d.ContainsKey(pu)) + { + bDupFound = true; + return false; + } + + d.Add(pu, null); + Debug.Assert(d.ContainsKey(pu)); + return true; }; EntryHandler eh = delegate(PwEntry pe) { - foreach(PwUuid u in l) - bAllUnique &= !pe.Uuid.EqualsValue(u); - l.Add(pe.Uuid); - return bAllUnique; + PwUuid pu = pe.Uuid; + if(d.ContainsKey(pu)) + { + bDupFound = true; + return false; + } + + d.Add(pu, null); + Debug.Assert(d.ContainsKey(pu)); + return true; }; + gh(m_pgRootGroup); m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh); - return bAllUnique; -#else - return true; -#endif + + Debug.Assert(bDupFound || (d.Count == nTotal)); + return bDupFound; + } + + internal void FixDuplicateUuids() + { + int nTotal = GetTotalObjectUuidCount(); + Dictionary d = new Dictionary(nTotal); + + GroupHandler gh = delegate(PwGroup pg) + { + PwUuid pu = pg.Uuid; + if(d.ContainsKey(pu)) + { + pu = new PwUuid(true); + while(d.ContainsKey(pu)) { Debug.Assert(false); pu = new PwUuid(true); } + + pg.Uuid = pu; + } + + d.Add(pu, null); + return true; + }; + + EntryHandler eh = delegate(PwEntry pe) + { + PwUuid pu = pe.Uuid; + if(d.ContainsKey(pu)) + { + pu = new PwUuid(true); + while(d.ContainsKey(pu)) { Debug.Assert(false); pu = new PwUuid(true); } + + pe.SetUuid(pu, true); + } + + d.Add(pu, null); + return true; + }; + + gh(m_pgRootGroup); + m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh); + + Debug.Assert(d.Count == nTotal); + Debug.Assert(!HasDuplicateUuids()); } /* public void CreateBackupFile(IStatusLogger sl) @@ -1591,7 +1843,7 @@ namespace KeePassLib PwEntry peB = l.GetAt(j); if(!DupEntriesEqual(peA, peB)) continue; - bool bDeleteA = (peA.LastModificationTime <= peB.LastModificationTime); + bool bDeleteA = (TimeUtil.CompareLastMod(peA, peB, true) <= 0); if(pgRecycleBin != null) { bool bAInBin = peA.IsContainedIn(pgRecycleBin); @@ -1708,11 +1960,11 @@ namespace KeePassLib GroupHandler gh = delegate(PwGroup pg) { PwUuid pwUuid = pg.CustomIconUuid; - if((pwUuid == null) || pwUuid.EqualsValue(PwUuid.Zero)) return true; + if((pwUuid == null) || pwUuid.Equals(PwUuid.Zero)) return true; for(int i = 0; i < lToDelete.Count; ++i) { - if(lToDelete[i].EqualsValue(pwUuid)) + if(lToDelete[i].Equals(pwUuid)) { lToDelete.RemoveAt(i); break; @@ -1725,11 +1977,11 @@ namespace KeePassLib EntryHandler eh = delegate(PwEntry pe) { PwUuid pwUuid = pe.CustomIconUuid; - if((pwUuid == null) || pwUuid.EqualsValue(PwUuid.Zero)) return true; + if((pwUuid == null) || pwUuid.Equals(PwUuid.Zero)) return true; for(int i = 0; i < lToDelete.Count; ++i) { - if(lToDelete[i].EqualsValue(pwUuid)) + if(lToDelete[i].Equals(pwUuid)) { lToDelete.RemoveAt(i); break; diff --git a/src/KeePassLib2Android/PwDefs.cs b/src/KeePassLib2Android/PwDefs.cs index 229ee44c..0b3d0dd7 100644 --- a/src/KeePassLib2Android/PwDefs.cs +++ b/src/KeePassLib2Android/PwDefs.cs @@ -1,6 +1,6 @@ -/* +/* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,12 +19,13 @@ using System; using System.Collections.Generic; -using System.Xml.Serialization; using System.ComponentModel; using System.Diagnostics; +using System.Xml.Serialization; using KeePassLib.Delegates; using KeePassLib.Interfaces; +using KeePassLib.Serialization; namespace KeePassLib { @@ -54,20 +55,20 @@ namespace KeePassLib /// e.g. 2.19 = 0x02130000. /// It is highly recommended to use FileVersion64 instead. /// - public const uint Version32 = 0x02140100; + public const uint Version32 = 0x02220000; /// /// Version, encoded as 64-bit unsigned integer /// (component-wise, 16 bits per component). /// - public const ulong FileVersion64 = 0x0002001400010000UL; + public const ulong FileVersion64 = 0x0002002200000000UL; /// /// Version, encoded as string. /// - public const string VersionString = "2.20.1"; + public const string VersionString = "2.34"; - public const string Copyright = @"Copyright © 2003-2012 Dominik Reichl"; + public const string Copyright = @"Copyright © 2003-2016 Dominik Reichl"; /// /// Product website URL. Terminated by a forward slash. @@ -93,7 +94,8 @@ namespace KeePassLib /// URL to a TXT file (eventually compressed) that contains information /// about the latest KeePass version available on the website. /// - public const string VersionUrl = "http://keepass.info/update/version2x.txt.gz"; + public const string VersionUrl = "https://sslsites.de/keepass.info/update/version2x.txt.gz"; + // public const string VersionUrl = "http://keepass.info/update/version2x.txt.gz"; /// /// URL to the root path of the online KeePass help. Terminated by @@ -219,7 +221,7 @@ namespace KeePassLib } } - #pragma warning disable 1591 // Missing XML comments warning + // #pragma warning disable 1591 // Missing XML comments warning /// /// Search parameters for group and entry searches. /// @@ -317,7 +319,11 @@ namespace KeePassLib set { m_bSearchInTags = value; } } +#if KeePassUAP + private StringComparison m_scType = StringComparison.OrdinalIgnoreCase; +#else private StringComparison m_scType = StringComparison.InvariantCultureIgnoreCase; +#endif /// /// String comparison type. Specifies the condition when the specified /// text matches a group/entry string. @@ -405,9 +411,9 @@ namespace KeePassLib return (SearchParameters)this.MemberwiseClone(); } } - #pragma warning restore 1591 // Missing XML comments warning + // #pragma warning restore 1591 // Missing XML comments warning - #pragma warning disable 1591 // Missing XML comments warning + // #pragma warning disable 1591 // Missing XML comments warning /// /// Memory protection configuration structure (for default fields). /// @@ -437,7 +443,7 @@ namespace KeePassLib return false; } } - #pragma warning restore 1591 // Missing XML comments warning + // #pragma warning restore 1591 // Missing XML comments warning public sealed class ObjectTouchedEventArgs : EventArgs { @@ -458,4 +464,24 @@ namespace KeePassLib m_bParentsTouched = bParentsTouched; } } + + public sealed class IOAccessEventArgs : EventArgs + { + private IOConnectionInfo m_ioc; + public IOConnectionInfo IOConnectionInfo { get { return m_ioc; } } + + private IOConnectionInfo m_ioc2; + public IOConnectionInfo IOConnectionInfo2 { get { return m_ioc2; } } + + private IOAccessType m_t; + public IOAccessType Type { get { return m_t; } } + + public IOAccessEventArgs(IOConnectionInfo ioc, IOConnectionInfo ioc2, + IOAccessType t) + { + m_ioc = ioc; + m_ioc2 = ioc2; + m_t = t; + } + } } diff --git a/src/KeePassLib2Android/PwDeletedObject.cs b/src/KeePassLib2Android/PwDeletedObject.cs index 0b549efa..734fe345 100644 --- a/src/KeePassLib2Android/PwDeletedObject.cs +++ b/src/KeePassLib2Android/PwDeletedObject.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/KeePassLib2Android/PwEntry.cs b/src/KeePassLib2Android/PwEntry.cs index b26bec6f..4d2694c6 100644 --- a/src/KeePassLib2Android/PwEntry.cs +++ b/src/KeePassLib2Android/PwEntry.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,8 +20,10 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Xml; + +#if !KeePassUAP using System.Drawing; +#endif using KeePassLib.Collections; using KeePassLib.Interfaces; @@ -83,7 +85,7 @@ namespace KeePassLib { get { return m_pParentGroup; } - /// Plugins: use PwGroup.AddEntry instead. + // Plugins: use PwGroup.AddEntry instead. internal set { m_pParentGroup = value; } } @@ -199,15 +201,6 @@ namespace KeePassLib set { m_tCreation = value; } } - /// - /// The date/time when this entry was last accessed (read). - /// - public DateTime LastAccessTime - { - get { return m_tLastAccess; } - set { m_tLastAccess = value; } - } - /// /// The date/time when this entry was last modified. /// @@ -217,6 +210,15 @@ namespace KeePassLib set { m_tLastMod = value; } } + /// + /// The date/time when this entry was last accessed (read). + /// + public DateTime LastAccessTime + { + get { return m_tLastAccess; } + set { m_tLastAccess = value; } + } + /// /// The date/time when this entry expires. Use the Expires property /// to specify if the entry does actually expire or not. @@ -320,6 +322,14 @@ namespace KeePassLib } } +#if DEBUG + // For display in debugger + public override string ToString() + { + return (@"PwEntry '" + m_listStrings.ReadSafe(PwDefs.TitleField) + @"'"); + } +#endif + /// /// Clone the current entry. The returned entry is an exact value copy /// of the current entry (including UUID and parent group reference). @@ -413,7 +423,7 @@ namespace KeePassLib bool bIgnoreLastMod = ((pwOpt & PwCompareOptions.IgnoreLastMod) != PwCompareOptions.None); - if(!m_uuid.EqualsValue(pe.m_uuid)) return false; + if(!m_uuid.Equals(pe.m_uuid)) return false; if((pwOpt & PwCompareOptions.IgnoreParentGroup) == PwCompareOptions.None) { if(m_pParentGroup != pe.m_pParentGroup) return false; @@ -456,7 +466,7 @@ namespace KeePassLib } if(m_pwIcon != pe.m_pwIcon) return false; - if(!m_pwCustomIconID.EqualsValue(pe.m_pwCustomIconID)) return false; + if(!m_pwCustomIconID.Equals(pe.m_pwCustomIconID)) return false; if(m_clrForeground != pe.m_clrForeground) return false; if(m_clrBackground != pe.m_clrBackground) return false; @@ -494,10 +504,12 @@ namespace KeePassLib { Debug.Assert(peTemplate != null); if(peTemplate == null) throw new ArgumentNullException("peTemplate"); - if(bOnlyIfNewer && (peTemplate.m_tLastMod < m_tLastMod)) return; + if(bOnlyIfNewer && (TimeUtil.Compare(peTemplate.m_tLastMod, m_tLastMod, + true) < 0)) + return; // Template UUID should be the same as the current one - Debug.Assert(m_uuid.EqualsValue(peTemplate.m_uuid)); + Debug.Assert(m_uuid.Equals(peTemplate.m_uuid)); m_uuid = peTemplate.m_uuid; if(bAssignLocationChanged) @@ -692,7 +704,7 @@ namespace KeePassLib for(uint u = 0; u < m_listHistory.UCount; ++u) { PwEntry pe = m_listHistory.GetAt(u); - if(pe.LastModificationTime < dtMin) + if(TimeUtil.Compare(pe.LastModificationTime, dtMin, true) < 0) { idxRemove = u; dtMin = pe.LastModificationTime; @@ -844,6 +856,24 @@ namespace KeePassLib } } } + + public void SetCreatedNow() + { + DateTime dt = DateTime.Now; + + m_tCreation = dt; + m_tLastAccess = dt; + } + + public PwEntry Duplicate() + { + PwEntry pe = CloneDeep(); + + pe.SetUuid(new PwUuid(true), true); + pe.SetCreatedNow(); + + return pe; + } } public sealed class PwEntryComparer : IComparer @@ -868,6 +898,7 @@ namespace KeePassLib string strB = b.Strings.ReadSafe(m_strFieldName); if(m_bCompareNaturally) return StrUtil.CompareNaturally(strA, strB); + return string.Compare(strA, strB, m_bCaseInsensitive); } } diff --git a/src/KeePassLib2Android/PwEnums.cs b/src/KeePassLib2Android/PwEnums.cs index 27cde682..13bed411 100644 --- a/src/KeePassLib2Android/PwEnums.cs +++ b/src/KeePassLib2Android/PwEnums.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 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 @@ -65,6 +65,8 @@ namespace KeePassLib /// public enum PwMergeMethod { + // Do not change the explicitly assigned values, otherwise + // serialization (e.g. of Ecas triggers) breaks None = 0, OverwriteExisting = 1, KeepExisting = 2, @@ -161,6 +163,26 @@ namespace KeePassLib Manual = 2 } + public enum ProxyAuthType + { + None = 0, + + /// + /// Use default user credentials (provided by the system). + /// + Default = 1, + + Manual = 2, + + /// + /// Default or Manual, depending on whether + /// manual credentials are available. + /// This type exists for supporting upgrading from KeePass + /// 2.28 to 2.29; the user cannot select this type. + /// + Auto = 3 + } + /// /// Comparison modes for in-memory protected objects. /// @@ -202,6 +224,96 @@ namespace KeePassLib IgnoreHistory = 0x10, IgnoreLastBackup = 0x20, + // For groups: + PropertiesOnly = 0x40, + IgnoreTimes = (IgnoreLastAccess | IgnoreLastMod) } + + public enum IOAccessType + { + None = 0, + + /// + /// The IO connection is being opened for reading. + /// + Read = 1, + + /// + /// The IO connection is being opened for writing. + /// + Write = 2, + + /// + /// The IO connection is being opened for testing + /// whether a file/object exists. + /// + Exists = 3, + + /// + /// The IO connection is being opened for deleting a file/object. + /// + Delete = 4, + + /// + /// The IO connection is being opened for renaming/moving a file/object. + /// + Move = 5 + } + + // public enum PwLogicalOp + // { + // None = 0, + // Or = 1, + // And = 2, + // NOr = 3, + // NAnd = 4 + // } + + [Flags] + public enum AppRunFlags + { + None = 0, + GetStdOutput = 1, + WaitForExit = 2, + + // https://sourceforge.net/p/keepass/patches/84/ + /// + /// This flag prevents any handles being garbage-collected + /// before the started process has terminated, without + /// blocking the current thread. + /// + GCKeepAlive = 4, + + // https://sourceforge.net/p/keepass/patches/85/ + DoEvents = 8, + DisableForms = 16 + } + + [Flags] + public enum ScaleTransformFlags + { + None = 0, + + /// + /// UIIcon indicates that the returned image is going + /// to be displayed as icon in the UI and that it is not + /// subject to future changes in size. + /// + UIIcon = 1 + } + + public enum DesktopType + { + None = 0, + Windows, + Gnome, + Kde, + Unity, + Lxde, + Xfce, + Mate, + Cinnamon, + Pantheon + } } diff --git a/src/KeePassLib2Android/PwGroup.cs b/src/KeePassLib2Android/PwGroup.cs index 791e0fbe..142e6902 100644 --- a/src/KeePassLib2Android/PwGroup.cs +++ b/src/KeePassLib2Android/PwGroup.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 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 @@ -137,7 +137,7 @@ namespace KeePassLib { get { return m_pParentGroup; } - /// Plugins: use PwGroup.AddGroup instead. + // Plugins: use PwGroup.AddGroup instead. internal set { Debug.Assert(value != this); m_pParentGroup = value; } } @@ -329,6 +329,14 @@ namespace KeePassLib m_pwIcon = pwIcon; } +#if DEBUG + // For display in debugger + public override string ToString() + { + return (@"PwGroup '" + m_strName + @"'"); + } +#endif + /// /// Deeply clone the current group. The returned group will be an exact /// value copy of the current object (including UUID, etc.). @@ -352,9 +360,9 @@ namespace KeePassLib pg.m_pwCustomIconID = m_pwCustomIconID; pg.m_tCreation = m_tCreation; - pg.m_tExpire = m_tExpire; - pg.m_tLastAccess = m_tLastAccess; pg.m_tLastMod = m_tLastMod; + pg.m_tLastAccess = m_tLastAccess; + pg.m_tExpire = m_tExpire; pg.m_bExpires = m_bExpires; pg.m_uUsageCount = m_uUsageCount; @@ -363,6 +371,9 @@ namespace KeePassLib pg.m_strDefaultAutoTypeSequence = m_strDefaultAutoTypeSequence; + pg.m_bEnableAutoType = m_bEnableAutoType; + pg.m_bEnableSearching = m_bEnableSearching; + pg.m_pwLastTopVisibleEntry = m_pwLastTopVisibleEntry; return pg; @@ -385,6 +396,76 @@ namespace KeePassLib return pg; } + public bool EqualsGroup(PwGroup pg, PwCompareOptions pwOpt, + MemProtCmpMode mpCmpStr) + { + if(pg == null) { Debug.Assert(false); return false; } + + bool bIgnoreLastAccess = ((pwOpt & PwCompareOptions.IgnoreLastAccess) != + PwCompareOptions.None); + bool bIgnoreLastMod = ((pwOpt & PwCompareOptions.IgnoreLastMod) != + PwCompareOptions.None); + + if(!m_uuid.Equals(pg.m_uuid)) return false; + if((pwOpt & PwCompareOptions.IgnoreParentGroup) == PwCompareOptions.None) + { + if(m_pParentGroup != pg.m_pParentGroup) return false; + if(!bIgnoreLastMod && (m_tParentGroupLastMod != pg.m_tParentGroupLastMod)) + return false; + } + + if(m_strName != pg.m_strName) return false; + if(m_strNotes != pg.m_strNotes) return false; + + if(m_pwIcon != pg.m_pwIcon) return false; + if(!m_pwCustomIconID.Equals(pg.m_pwCustomIconID)) return false; + + if(m_tCreation != pg.m_tCreation) return false; + if(!bIgnoreLastMod && (m_tLastMod != pg.m_tLastMod)) return false; + if(!bIgnoreLastAccess && (m_tLastAccess != pg.m_tLastAccess)) return false; + if(m_tExpire != pg.m_tExpire) return false; + if(m_bExpires != pg.m_bExpires) return false; + if(!bIgnoreLastAccess && (m_uUsageCount != pg.m_uUsageCount)) return false; + + // if(m_bIsExpanded != pg.m_bIsExpanded) return false; + + if(m_strDefaultAutoTypeSequence != pg.m_strDefaultAutoTypeSequence) return false; + + if(m_bEnableAutoType.HasValue != pg.m_bEnableAutoType.HasValue) return false; + if(m_bEnableAutoType.HasValue) + { + if(m_bEnableAutoType.Value != pg.m_bEnableAutoType.Value) return false; + } + if(m_bEnableSearching.HasValue != pg.m_bEnableSearching.HasValue) return false; + if(m_bEnableSearching.HasValue) + { + if(m_bEnableSearching.Value != pg.m_bEnableSearching.Value) return false; + } + + if(!m_pwLastTopVisibleEntry.Equals(pg.m_pwLastTopVisibleEntry)) return false; + + if((pwOpt & PwCompareOptions.PropertiesOnly) == PwCompareOptions.None) + { + if(m_listEntries.UCount != pg.m_listEntries.UCount) return false; + for(uint u = 0; u < m_listEntries.UCount; ++u) + { + PwEntry peA = m_listEntries.GetAt(u); + PwEntry peB = pg.m_listEntries.GetAt(u); + if(!peA.EqualsEntry(peB, pwOpt, mpCmpStr)) return false; + } + + if(m_listGroups.UCount != pg.m_listGroups.UCount) return false; + for(uint u = 0; u < m_listGroups.UCount; ++u) + { + PwGroup pgA = m_listGroups.GetAt(u); + PwGroup pgB = pg.m_listGroups.GetAt(u); + if(!pgA.EqualsGroup(pgB, pwOpt, mpCmpStr)) return false; + } + } + + return true; + } + /// /// Assign properties to the current group based on a template group. /// @@ -398,10 +479,12 @@ namespace KeePassLib { Debug.Assert(pgTemplate != null); if(pgTemplate == null) throw new ArgumentNullException("pgTemplate"); - if(bOnlyIfNewer && (pgTemplate.m_tLastMod < m_tLastMod)) return; + if(bOnlyIfNewer && (TimeUtil.Compare(pgTemplate.m_tLastMod, m_tLastMod, + true) < 0)) + return; // Template UUID should be the same as the current one - Debug.Assert(m_uuid.EqualsValue(pgTemplate.m_uuid)); + Debug.Assert(m_uuid.Equals(pgTemplate.m_uuid)); m_uuid = pgTemplate.m_uuid; if(bAssignLocationChanged) @@ -422,6 +505,9 @@ namespace KeePassLib m_strDefaultAutoTypeSequence = pgTemplate.m_strDefaultAutoTypeSequence; + m_bEnableAutoType = pgTemplate.m_bEnableAutoType; + m_bEnableSearching = pgTemplate.m_bEnableSearching; + m_pwLastTopVisibleEntry = pgTemplate.m_pwLastTopVisibleEntry; } @@ -573,8 +659,6 @@ namespace KeePassLib /// /// Pack all groups into one flat linked list of references (recursively). - /// Temporary IDs (TemporaryID field) and levels (TemporaryLevel) - /// are assigned automatically. /// /// Flat list of all groups. public LinkedList GetFlatGroupList() @@ -671,6 +755,7 @@ namespace KeePassLib /// Specifies the search method. /// Entry list in which the search results will /// be stored. + /// Optional status reporting object. public void SearchEntries(SearchParameters sp, PwObjectList listStorage, IStatusLogger slStatus) { @@ -764,9 +849,11 @@ namespace KeePassLib Regex rx = null; if(sp.RegularExpression) { - RegexOptions ro = RegexOptions.Compiled; + RegexOptions ro = RegexOptions.None; // RegexOptions.Compiled if((sp.ComparisonMode == StringComparison.CurrentCultureIgnoreCase) || +#if !KeePassUAP (sp.ComparisonMode == StringComparison.InvariantCultureIgnoreCase) || +#endif (sp.ComparisonMode == StringComparison.OrdinalIgnoreCase)) { ro |= RegexOptions.IgnoreCase; @@ -938,6 +1025,30 @@ namespace KeePassLib return vTags; } +#if !KeePassLibSD + public IDictionary BuildEntryTagsDict(bool bSort) + { + IDictionary d; + if(!bSort) d = new Dictionary(StrUtil.CaseIgnoreComparer); + else d = new SortedDictionary(StrUtil.CaseIgnoreComparer); + + EntryHandler eh = delegate(PwEntry pe) + { + foreach(string strTag in pe.Tags) + { + uint u; + if(d.TryGetValue(strTag, out u)) d[strTag] = u + 1; + else d[strTag] = 1; + } + + return true; + }; + + TraverseTree(TraversalMethod.PreOrder, null, eh); + return d; + } +#endif + public void FindEntriesByTag(string strTag, PwObjectList listStorage, bool bSearchRecursive) { @@ -972,7 +1083,7 @@ namespace KeePassLib public PwGroup FindGroup(PwUuid uuid, bool bSearchRecursive) { // Do not assert on PwUuid.Zero - if(m_uuid.EqualsValue(uuid)) return this; + if(m_uuid.Equals(uuid)) return this; if(bSearchRecursive) { @@ -987,7 +1098,7 @@ namespace KeePassLib { foreach(PwGroup pg in m_listGroups) { - if(pg.m_uuid.EqualsValue(uuid)) + if(pg.m_uuid.Equals(uuid)) return pg; } } @@ -1051,7 +1162,7 @@ namespace KeePassLib { foreach(PwEntry pe in m_listEntries) { - if(pe.Uuid.EqualsValue(uuid)) return pe; + if(pe.Uuid.Equals(uuid)) return pe; } if(bSearchRecursive) @@ -1081,6 +1192,8 @@ namespace KeePassLib /// /// String that separates the group /// names. + /// Specifies whether the returned + /// path starts with the topmost group. /// Full path of the group. public string GetFullPath(string strSeparator, bool bIncludeTopMostGroup) { @@ -1151,6 +1264,7 @@ namespace KeePassLib } } +#if !KeePassLibSD /// /// Find/create a subtree of groups. /// @@ -1164,11 +1278,23 @@ namespace KeePassLib public PwGroup FindCreateSubTree(string strTree, char[] vSeparators, bool bAllowCreate) + { + if(vSeparators == null) { Debug.Assert(false); vSeparators = new char[0]; } + + string[] v = new string[vSeparators.Length]; + for(int i = 0; i < vSeparators.Length; ++i) + v[i] = new string(vSeparators[i], 1); + + return FindCreateSubTree(strTree, v, bAllowCreate); + } + + public PwGroup FindCreateSubTree(string strTree, string[] vSeparators, + bool bAllowCreate) { Debug.Assert(strTree != null); if(strTree == null) return this; if(strTree.Length == 0) return this; - string[] vGroups = strTree.Split(vSeparators); + string[] vGroups = strTree.Split(vSeparators, StringSplitOptions.None); if((vGroups == null) || (vGroups.Length == 0)) return this; PwGroup pgContainer = this; @@ -1199,6 +1325,7 @@ namespace KeePassLib return pgContainer; } +#endif /// /// Get the level of the group (i.e. the number of parent groups). @@ -1422,6 +1549,69 @@ namespace KeePassLib } m_listGroups.Clear(); } + + internal List GetTopSearchSkippedGroups() + { + List l = new List(); + + if(!GetSearchingEnabledInherited()) l.Add(this); + else GetTopSearchSkippedGroupsRec(l); + + return l; + } + + private void GetTopSearchSkippedGroupsRec(List l) + { + if(m_bEnableSearching.HasValue && !m_bEnableSearching.Value) + { + l.Add(this); + return; + } + else { Debug.Assert(GetSearchingEnabledInherited()); } + + foreach(PwGroup pgSub in m_listGroups) + pgSub.GetTopSearchSkippedGroupsRec(l); + } + + public void SetCreatedNow(bool bRecursive) + { + DateTime dt = DateTime.Now; + + m_tCreation = dt; + m_tLastAccess = dt; + + if(!bRecursive) return; + + GroupHandler gh = delegate(PwGroup pg) + { + pg.m_tCreation = dt; + pg.m_tLastAccess = dt; + return true; + }; + + EntryHandler eh = delegate(PwEntry pe) + { + pe.CreationTime = dt; + pe.LastAccessTime = dt; + return true; + }; + + TraverseTree(TraversalMethod.PreOrder, gh, eh); + } + + public PwGroup Duplicate() + { + PwGroup pg = CloneDeep(); + + pg.Uuid = new PwUuid(true); + pg.CreateNewItemUuids(true, true, true); + + pg.SetCreatedNow(true); + + pg.TakeOwnership(true, true, true); + + return pg; + } } public sealed class PwGroupComparer : IComparer diff --git a/src/KeePassLib2Android/PwUuid.cs b/src/KeePassLib2Android/PwUuid.cs index dfb0be52..540d6164 100644 --- a/src/KeePassLib2Android/PwUuid.cs +++ b/src/KeePassLib2Android/PwUuid.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,6 +18,7 @@ */ using System; +using System.Collections.Generic; using System.Xml; using System.Diagnostics; @@ -27,10 +28,10 @@ namespace KeePassLib { // [ImmutableObject(true)] /// - /// Represents an UUID of a password entry or group. Once created, PwUuid - /// objects aren't modifyable anymore (immutable). + /// Represents an UUID of a password entry or group. Once created, + /// PwUuid objects aren't modifyable anymore (immutable). /// - public sealed class PwUuid + public sealed class PwUuid : IComparable, IEquatable { /// /// Standard size in bytes of a UUID. @@ -40,9 +41,9 @@ namespace KeePassLib /// /// Zero UUID (all bytes are zero). /// - public static readonly PwUuid Zero = new PwUuid(); + public static readonly PwUuid Zero = new PwUuid(false); - private byte[] m_pbUuid = new byte[UuidSize]; + private byte[] m_pbUuid = null; // Never null after constructor /// /// Get the 16 UUID bytes. @@ -52,14 +53,6 @@ namespace KeePassLib get { return m_pbUuid; } } - /// - /// Construct a new UUID object. Its value is initialized to zero. - /// - private PwUuid() - { - SetZero(); - } - /// /// Construct a new UUID object. /// @@ -88,38 +81,95 @@ namespace KeePassLib /// otherwise it returns false. private void CreateNew() { + Debug.Assert(m_pbUuid == null); // Only call from constructor while(true) { m_pbUuid = Guid.NewGuid().ToByteArray(); - if((m_pbUuid == null) || (m_pbUuid.Length != UuidSize)) + if((m_pbUuid == null) || (m_pbUuid.Length != (int)UuidSize)) + { + Debug.Assert(false); throw new InvalidOperationException(); + } // Zero is a reserved value -- do not generate Zero - if(this.EqualsValue(PwUuid.Zero) == false) - break; + if(!Equals(PwUuid.Zero)) break; + Debug.Assert(false); } } - /// - /// Compare this UUID with another. - /// - /// Second UUID object. - /// Returns true if both PwUuid object contain the same - /// value, otherwise false is returned. + private void SetValue(byte[] uuidBytes) + { + Debug.Assert((uuidBytes != null) && (uuidBytes.Length == (int)UuidSize)); + if(uuidBytes == null) throw new ArgumentNullException("uuidBytes"); + if(uuidBytes.Length != (int)UuidSize) throw new ArgumentException(); + + Debug.Assert(m_pbUuid == null); // Only call from constructor + m_pbUuid = new byte[UuidSize]; + + Array.Copy(uuidBytes, m_pbUuid, (int)UuidSize); + } + + private void SetZero() + { + Debug.Assert(m_pbUuid == null); // Only call from constructor + m_pbUuid = new byte[UuidSize]; + + // Array.Clear(m_pbUuid, 0, (int)UuidSize); +#if DEBUG + List l = new List(m_pbUuid); + Debug.Assert(l.TrueForAll(bt => (bt == 0))); +#endif + } + + [Obsolete] public bool EqualsValue(PwUuid uuid) { - Debug.Assert(uuid != null); - if(uuid == null) throw new ArgumentNullException("uuid"); + return Equals(uuid); + } - for(int i = 0; i < UuidSize; ++i) + public override bool Equals(object obj) + { + return Equals(obj as PwUuid); + } + + public bool Equals(PwUuid other) + { + if(other == null) { Debug.Assert(false); return false; } + + for(int i = 0; i < (int)UuidSize; ++i) { - if(m_pbUuid[i] != uuid.m_pbUuid[i]) return false; + if(m_pbUuid[i] != other.m_pbUuid[i]) return false; } return true; } + private int m_h = 0; + public override int GetHashCode() + { + if(m_h == 0) + m_h = (int)MemUtil.Hash32(m_pbUuid, 0, m_pbUuid.Length); + return m_h; + } + + public int CompareTo(PwUuid other) + { + if(other == null) + { + Debug.Assert(false); + throw new ArgumentNullException("other"); + } + + for(int i = 0; i < (int)UuidSize; ++i) + { + if(m_pbUuid[i] < other.m_pbUuid[i]) return -1; + if(m_pbUuid[i] > other.m_pbUuid[i]) return 1; + } + + return 0; + } + /// /// Convert the UUID to its string representation. /// @@ -129,29 +179,15 @@ namespace KeePassLib return MemUtil.ByteArrayToHexString(m_pbUuid); } - /// - /// Set the UUID value. The input parameter will not be modified. - /// - /// UUID bytes. The byte array must contain - /// exactly UUIDSize bytes, otherwise the function will fail. - private void SetValue(byte[] uuidBytes) +#if DEBUG + public override string ToString() { - Debug.Assert((uuidBytes != null) && (uuidBytes.Length == UuidSize)); - if(uuidBytes == null) throw new ArgumentNullException("uuidBytes"); - if(uuidBytes.Length != UuidSize) throw new ArgumentException(); - - Array.Copy(uuidBytes, m_pbUuid, (int)UuidSize); - } - - /// - /// Set the UUID value to zero. - /// - private void SetZero() - { - Array.Clear(m_pbUuid, 0, (int)UuidSize); + return ToHexString(); } +#endif } + [Obsolete] public sealed class PwUuidComparable : IComparable { private byte[] m_pbUuid = new byte[PwUuid.UuidSize]; diff --git a/src/KeePassLib2Android/Resources/KLRes.Generated.cs b/src/KeePassLib2Android/Resources/KLRes.Generated.cs index 4aaf889f..84c7f3e3 100644 --- a/src/KeePassLib2Android/Resources/KLRes.Generated.cs +++ b/src/KeePassLib2Android/Resources/KLRes.Generated.cs @@ -29,6 +29,7 @@ namespace KeePassLib.Resources m_strCryptoStreamFailed = TryGetEx(dictNew, "CryptoStreamFailed", m_strCryptoStreamFailed); m_strEncAlgorithmAes = TryGetEx(dictNew, "EncAlgorithmAes", m_strEncAlgorithmAes); m_strErrorInClipboard = TryGetEx(dictNew, "ErrorInClipboard", m_strErrorInClipboard); + m_strExpect100Continue = TryGetEx(dictNew, "Expect100Continue", m_strExpect100Continue); m_strFatalError = TryGetEx(dictNew, "FatalError", m_strFatalError); m_strFatalErrorText = TryGetEx(dictNew, "FatalErrorText", m_strFatalErrorText); m_strFileCorrupted = TryGetEx(dictNew, "FileCorrupted", m_strFileCorrupted); @@ -44,21 +45,28 @@ namespace KeePassLib.Resources m_strFileVersionUnsupported = TryGetEx(dictNew, "FileVersionUnsupported", m_strFileVersionUnsupported); m_strFinalKeyCreationFailed = TryGetEx(dictNew, "FinalKeyCreationFailed", m_strFinalKeyCreationFailed); m_strFrameworkNotImplExcp = TryGetEx(dictNew, "FrameworkNotImplExcp", m_strFrameworkNotImplExcp); + m_strGeneral = TryGetEx(dictNew, "General", m_strGeneral); m_strInvalidCompositeKey = TryGetEx(dictNew, "InvalidCompositeKey", m_strInvalidCompositeKey); m_strInvalidCompositeKeyHint = TryGetEx(dictNew, "InvalidCompositeKeyHint", m_strInvalidCompositeKeyHint); m_strInvalidDataWhileDecoding = TryGetEx(dictNew, "InvalidDataWhileDecoding", m_strInvalidDataWhileDecoding); m_strKeePass1xHint = TryGetEx(dictNew, "KeePass1xHint", m_strKeePass1xHint); + m_strKeyFileDbSel = TryGetEx(dictNew, "KeyFileDbSel", m_strKeyFileDbSel); m_strMasterSeedLengthInvalid = TryGetEx(dictNew, "MasterSeedLengthInvalid", m_strMasterSeedLengthInvalid); m_strOldFormat = TryGetEx(dictNew, "OldFormat", m_strOldFormat); + m_strPassive = TryGetEx(dictNew, "Passive", m_strPassive); + m_strPreAuth = TryGetEx(dictNew, "PreAuth", m_strPreAuth); + m_strTimeout = TryGetEx(dictNew, "Timeout", m_strTimeout); m_strTryAgainSecs = TryGetEx(dictNew, "TryAgainSecs", m_strTryAgainSecs); m_strUnknownHeaderId = TryGetEx(dictNew, "UnknownHeaderId", m_strUnknownHeaderId); m_strUserAccountKeyError = TryGetEx(dictNew, "UserAccountKeyError", m_strUserAccountKeyError); + m_strUserAgent = TryGetEx(dictNew, "UserAgent", m_strUserAgent); } private static readonly string[] m_vKeyNames = { "CryptoStreamFailed", "EncAlgorithmAes", "ErrorInClipboard", + "Expect100Continue", "FatalError", "FatalErrorText", "FileCorrupted", @@ -74,15 +82,21 @@ namespace KeePassLib.Resources "FileVersionUnsupported", "FinalKeyCreationFailed", "FrameworkNotImplExcp", + "General", "InvalidCompositeKey", "InvalidCompositeKeyHint", "InvalidDataWhileDecoding", "KeePass1xHint", + "KeyFileDbSel", "MasterSeedLengthInvalid", "OldFormat", + "Passive", + "PreAuth", + "Timeout", "TryAgainSecs", "UnknownHeaderId", - "UserAccountKeyError" + "UserAccountKeyError", + "UserAgent" }; public static string[] GetKeyNames() @@ -123,6 +137,17 @@ namespace KeePassLib.Resources get { return m_strErrorInClipboard; } } + private static string m_strExpect100Continue = + @"Expect 100-Continue responses"; + /// + /// Look up a localized string similar to + /// 'Expect 100-Continue responses'. + /// + public static string Expect100Continue + { + get { return m_strExpect100Continue; } + } + private static string m_strFatalError = @"Fatal Error"; /// @@ -288,6 +313,17 @@ namespace KeePassLib.Resources get { return m_strFrameworkNotImplExcp; } } + private static string m_strGeneral = + @"General"; + /// + /// Look up a localized string similar to + /// 'General'. + /// + public static string General + { + get { return m_strGeneral; } + } + private static string m_strInvalidCompositeKey = @"The composite key is invalid!"; /// @@ -332,6 +368,17 @@ namespace KeePassLib.Resources get { return m_strKeePass1xHint; } } + private static string m_strKeyFileDbSel = + @"Database files cannot be used as key files."; + /// + /// Look up a localized string similar to + /// 'Database files cannot be used as key files.'. + /// + public static string KeyFileDbSel + { + get { return m_strKeyFileDbSel; } + } + private static string m_strMasterSeedLengthInvalid = @"The length of the master key seed is invalid!"; /// @@ -354,6 +401,39 @@ namespace KeePassLib.Resources get { return m_strOldFormat; } } + private static string m_strPassive = + @"Passive"; + /// + /// Look up a localized string similar to + /// 'Passive'. + /// + public static string Passive + { + get { return m_strPassive; } + } + + private static string m_strPreAuth = + @"Pre-authenticate"; + /// + /// Look up a localized string similar to + /// 'Pre-authenticate'. + /// + public static string PreAuth + { + get { return m_strPreAuth; } + } + + private static string m_strTimeout = + @"Timeout"; + /// + /// Look up a localized string similar to + /// 'Timeout'. + /// + public static string Timeout + { + get { return m_strTimeout; } + } + private static string m_strTryAgainSecs = @"Please try it again in a few seconds."; /// @@ -386,5 +466,16 @@ namespace KeePassLib.Resources { get { return m_strUserAccountKeyError; } } + + private static string m_strUserAgent = + @"User agent"; + /// + /// Look up a localized string similar to + /// 'User agent'. + /// + public static string UserAgent + { + get { return m_strUserAgent; } + } } } diff --git a/src/KeePassLib2Android/Security/ProtectedBinary.cs b/src/KeePassLib2Android/Security/ProtectedBinary.cs index b8ce4923..fd9ca2f2 100644 --- a/src/KeePassLib2Android/Security/ProtectedBinary.cs +++ b/src/KeePassLib2Android/Security/ProtectedBinary.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,10 +18,16 @@ */ using System; -using System.Security.Cryptography; using System.Diagnostics; +using System.Threading; + +#if !KeePassUAP +using System.Security.Cryptography; +#endif using KeePassLib.Cryptography; +using KeePassLib.Cryptography.Cipher; +using KeePassLib.Native; using KeePassLib.Utility; #if KeePassLibSD @@ -30,6 +36,17 @@ using KeePassLibSD; namespace KeePassLib.Security { + [Flags] + public enum PbCryptFlags + { + None = 0, + Encrypt = 1, + Decrypt = 2 + } + + public delegate void PbCryptDelegate(byte[] pbData, PbCryptFlags cf, + long lID); + /// /// Represents a protected binary, i.e. a byte array that is encrypted /// in memory. A ProtectedBinary object is immutable and @@ -37,26 +54,98 @@ namespace KeePassLib.Security /// public sealed class ProtectedBinary : IEquatable { - private const int PmBlockSize = 16; + private const int BlockSize = 16; - // In-memory protection is supported only on Windows 2000 SP3 and - // higher. - private static bool m_bProtectionSupported; + private static PbCryptDelegate g_fExtCrypt = null; + /// + /// A plugin can provide a custom memory protection method + /// by assigning a non-null delegate to this property. + /// + public static PbCryptDelegate ExtCrypt + { + get { return g_fExtCrypt; } + set { g_fExtCrypt = value; } + } + + // Local copy of the delegate that was used for encryption, + // in order to allow correct decryption even when the global + // delegate changes + private PbCryptDelegate m_fExtCrypt = null; + + private enum PbMemProt + { + None = 0, + ProtectedMemory, + Salsa20, + ExtCrypt + } + + // ProtectedMemory is supported only on Windows 2000 SP3 and higher +#if !KeePassLibSD + private static bool? g_obProtectedMemorySupported = null; +#endif + private static bool ProtectedMemorySupported + { + get + { +#if KeePassLibSD + return false; +#else + bool? ob = g_obProtectedMemorySupported; + if(ob.HasValue) return ob.Value; + + // Mono does not implement any encryption for ProtectedMemory; + // https://sourceforge.net/p/keepass/feature-requests/1907/ + if(NativeLib.IsUnix()) + { + g_obProtectedMemorySupported = false; + return false; + } + + ob = false; + try // Test whether ProtectedMemory is supported + { + // BlockSize * 3 in order to test encryption for multiple + // blocks, but not introduce a power of 2 as factor + byte[] pb = new byte[ProtectedBinary.BlockSize * 3]; + for(int i = 0; i < pb.Length; ++i) pb[i] = (byte)i; + + ProtectedMemory.Protect(pb, MemoryProtectionScope.SameProcess); + + for(int i = 0; i < pb.Length; ++i) + { + if(pb[i] != (byte)i) { ob = true; break; } + } + } + catch(Exception) { } // Windows 98 / ME + + g_obProtectedMemorySupported = ob; + return ob.Value; +#endif + } + } + + private static long g_lCurID = 0; + private long m_lID; private byte[] m_pbData; // Never null - // The real length of the data. This value can be different than + // The real length of the data; this value can be different from // m_pbData.Length, as the length of m_pbData always is a multiple - // of PmBlockSize (required for fast in-memory protection). + // of BlockSize (required for ProtectedMemory) private uint m_uDataLen; - private bool m_bProtected; + private bool m_bProtected; // Protection requested by the caller + + private PbMemProt m_mp = PbMemProt.None; // Actual protection private object m_objSync = new object(); + private static byte[] g_pbKey32 = null; + /// /// A flag specifying whether the ProtectedBinary object has - /// turned on in-memory protection or not. + /// turned on memory protection or not. /// public bool IsProtected { @@ -71,23 +160,9 @@ namespace KeePassLib.Security get { return m_uDataLen; } } - static ProtectedBinary() - { - try // Test whether ProtectedMemory is supported - { - byte[] pbDummy = new byte[PmBlockSize * 2]; - ProtectedMemory.Protect(pbDummy, MemoryProtectionScope.SameProcess); - m_bProtectionSupported = true; - } - catch(Exception) // Windows 98 / ME - { - m_bProtectionSupported = false; - } - } - /// - /// Construct a new, empty protected binary data object. Protection - /// is disabled. + /// Construct a new, empty protected binary data object. + /// Protection is disabled. /// public ProtectedBinary() { @@ -116,34 +191,103 @@ namespace KeePassLib.Security /// Enable protection or not. /// XorredBuffer object used to /// initialize the ProtectedBinary object. - /// Thrown if the input - /// parameter is null. public ProtectedBinary(bool bEnableProtection, XorredBuffer xbProtected) { - Debug.Assert(xbProtected != null); if(xbProtected == null) throw new ArgumentNullException("xbProtected"); + Debug.Assert(xbProtected != null); + if(xbProtected == null) throw new ArgumentNullException("xbProtected"); byte[] pb = xbProtected.ReadPlainText(); Init(bEnableProtection, pb); - MemUtil.ZeroByteArray(pb); + + if(bEnableProtection) MemUtil.ZeroByteArray(pb); } private void Init(bool bEnableProtection, byte[] pbData) { if(pbData == null) throw new ArgumentNullException("pbData"); +#if KeePassLibSD + m_lID = ++g_lCurID; +#else + m_lID = Interlocked.Increment(ref g_lCurID); +#endif + m_bProtected = bEnableProtection; m_uDataLen = (uint)pbData.Length; - int nBlocks = (int)m_uDataLen / PmBlockSize; - if((nBlocks * PmBlockSize) < (int)m_uDataLen) ++nBlocks; - Debug.Assert((nBlocks * PmBlockSize) >= (int)m_uDataLen); + const int bs = ProtectedBinary.BlockSize; + int nBlocks = (int)m_uDataLen / bs; + if((nBlocks * bs) < (int)m_uDataLen) ++nBlocks; + Debug.Assert((nBlocks * bs) >= (int)m_uDataLen); - m_pbData = new byte[nBlocks * PmBlockSize]; + m_pbData = new byte[nBlocks * bs]; Array.Copy(pbData, m_pbData, (int)m_uDataLen); - // Data size must be > 0, otherwise 'Protect' throws - if(m_bProtected && m_bProtectionSupported && (m_uDataLen > 0)) + Encrypt(); + } + + private void Encrypt() + { + Debug.Assert(m_mp == PbMemProt.None); + + // Nothing to do if caller didn't request protection + if(!m_bProtected) return; + + // ProtectedMemory.Protect throws for data size == 0 + if(m_pbData.Length == 0) return; + + PbCryptDelegate f = g_fExtCrypt; + if(f != null) + { + f(m_pbData, PbCryptFlags.Encrypt, m_lID); + + m_fExtCrypt = f; + m_mp = PbMemProt.ExtCrypt; + return; + } + + if(ProtectedBinary.ProtectedMemorySupported) + { ProtectedMemory.Protect(m_pbData, MemoryProtectionScope.SameProcess); + + m_mp = PbMemProt.ProtectedMemory; + return; + } + + byte[] pbKey32 = g_pbKey32; + if(pbKey32 == null) + { + pbKey32 = CryptoRandom.Instance.GetRandomBytes(32); + + byte[] pbUpd = Interlocked.Exchange(ref g_pbKey32, pbKey32); + if(pbUpd != null) pbKey32 = pbUpd; + } + + Salsa20Cipher s = new Salsa20Cipher(pbKey32, + BitConverter.GetBytes(m_lID)); + s.Encrypt(m_pbData, m_pbData.Length, true); + s.Dispose(); + m_mp = PbMemProt.Salsa20; + } + + private void Decrypt() + { + if(m_pbData.Length == 0) return; + + if(m_mp == PbMemProt.ProtectedMemory) + ProtectedMemory.Unprotect(m_pbData, MemoryProtectionScope.SameProcess); + else if(m_mp == PbMemProt.Salsa20) + { + Salsa20Cipher s = new Salsa20Cipher(g_pbKey32, + BitConverter.GetBytes(m_lID)); + s.Encrypt(m_pbData, m_pbData.Length, true); + s.Dispose(); + } + else if(m_mp == PbMemProt.ExtCrypt) + m_fExtCrypt(m_pbData, PbCryptFlags.Decrypt, m_lID); + else { Debug.Assert(m_mp == PbMemProt.None); } + + m_mp = PbMemProt.None; } /// @@ -160,16 +304,12 @@ namespace KeePassLib.Security byte[] pbReturn = new byte[m_uDataLen]; - if(m_bProtected && m_bProtectionSupported) + lock(m_objSync) { - lock(m_objSync) - { - ProtectedMemory.Unprotect(m_pbData, MemoryProtectionScope.SameProcess); - Array.Copy(m_pbData, pbReturn, (int)m_uDataLen); - ProtectedMemory.Protect(m_pbData, MemoryProtectionScope.SameProcess); - } + Decrypt(); + Array.Copy(m_pbData, pbReturn, (int)m_uDataLen); + Encrypt(); } - else Array.Copy(m_pbData, pbReturn, (int)m_uDataLen); return pbReturn; } @@ -179,9 +319,6 @@ namespace KeePassLib.Security /// of bytes generated by a random stream. /// /// Random number source. - /// Protected data. - /// Thrown if the input - /// parameter is null. public byte[] ReadXorredData(CryptoRandomStream crsRandomSource) { Debug.Assert(crsRandomSource != null); @@ -191,7 +328,7 @@ namespace KeePassLib.Security uint uLen = (uint)pbData.Length; byte[] randomPad = crsRandomSource.GetRandomBytes(uLen); - Debug.Assert(randomPad.Length == uLen); + Debug.Assert(randomPad.Length == pbData.Length); for(uint i = 0; i < uLen; ++i) pbData[i] ^= randomPad[i]; @@ -199,8 +336,11 @@ namespace KeePassLib.Security return pbData; } + private int? m_hash = null; public override int GetHashCode() { + if(m_hash.HasValue) return m_hash.Value; + int h = (m_bProtected ? 0x7B11D289 : 0); byte[] pb = ReadData(); @@ -211,6 +351,7 @@ namespace KeePassLib.Security } MemUtil.ZeroByteArray(pb); + m_hash = h; return h; } diff --git a/src/KeePassLib2Android/Security/ProtectedString.cs b/src/KeePassLib2Android/Security/ProtectedString.cs index 7f9d2590..adad63b4 100644 --- a/src/KeePassLib2Android/Security/ProtectedString.cs +++ b/src/KeePassLib2Android/Security/ProtectedString.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,8 +18,8 @@ */ using System; -using System.Text; using System.Diagnostics; +using System.Text; using KeePassLib.Cryptography; using KeePassLib.Utility; @@ -47,7 +47,7 @@ namespace KeePassLib.Security private bool m_bIsProtected; - private static ProtectedString m_psEmpty = new ProtectedString(); + private static readonly ProtectedString m_psEmpty = new ProtectedString(); public static ProtectedString Empty { get { return m_psEmpty; } @@ -55,7 +55,7 @@ namespace KeePassLib.Security /// /// A flag specifying whether the ProtectedString object - /// has turned on in-memory protection or not. + /// has turned on memory protection or not. /// public bool IsProtected { @@ -112,10 +112,9 @@ namespace KeePassLib.Security /// to the value supplied in the parameters. /// /// If this parameter is true, - /// the string will be protected in-memory (encrypted). If it + /// the string will be protected in memory (encrypted). If it /// is false, the string will be stored as plain-text. - /// The initial string value. This - /// parameter won't be modified. + /// The initial string value. public ProtectedString(bool bEnableProtection, string strValue) { Init(bEnableProtection, strValue); @@ -126,7 +125,7 @@ namespace KeePassLib.Security /// to the value supplied in the parameters (UTF-8 encoded string). /// /// If this parameter is true, - /// the string will be protected in-memory (encrypted). If it + /// the string will be protected in memory (encrypted). If it /// is false, the string will be stored as plain-text. /// The initial string value, encoded as /// UTF-8 byte array. This parameter won't be modified; the caller @@ -144,15 +143,15 @@ namespace KeePassLib.Security /// XorredBuffer object containing the /// string in UTF-8 representation. The UTF-8 string must not /// be null-terminated. - /// Thrown if the input - /// parameter is null. public ProtectedString(bool bEnableProtection, XorredBuffer xbProtected) { + Debug.Assert(xbProtected != null); if(xbProtected == null) throw new ArgumentNullException("xbProtected"); byte[] pb = xbProtected.ReadPlainText(); Init(bEnableProtection, pb); - MemUtil.ZeroByteArray(pb); + + if(bEnableProtection) MemUtil.ZeroByteArray(pb); } private void Init(bool bEnableProtection, string str) @@ -222,8 +221,6 @@ namespace KeePassLib.Security /// /// Random number source. /// Protected string. - /// Thrown if the input - /// parameter is null. public byte[] ReadXorredString(CryptoRandomStream crsRandomSource) { Debug.Assert(crsRandomSource != null); if(crsRandomSource == null) throw new ArgumentNullException("crsRandomSource"); @@ -232,7 +229,7 @@ namespace KeePassLib.Security uint uLen = (uint)pbData.Length; byte[] randomPad = crsRandomSource.GetRandomBytes(uLen); - Debug.Assert(randomPad.Length == uLen); + Debug.Assert(randomPad.Length == pbData.Length); for(uint i = 0; i < uLen; ++i) pbData[i] ^= randomPad[i]; @@ -246,7 +243,103 @@ namespace KeePassLib.Security byte[] pb = ReadUtf8(); ProtectedString ps = new ProtectedString(bProtect, pb); - MemUtil.ZeroByteArray(pb); + + if(bProtect) MemUtil.ZeroByteArray(pb); + return ps; + } + + public ProtectedString Insert(int iStart, string strInsert) + { + if(iStart < 0) throw new ArgumentOutOfRangeException("iStart"); + 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; + + try + { + if(iStart > v.Length) + throw new ArgumentOutOfRangeException("iStart"); + + char[] vIns = strInsert.ToCharArray(); + + vNew = new char[v.Length + vIns.Length]; + Array.Copy(v, 0, vNew, 0, iStart); + Array.Copy(vIns, 0, vNew, iStart, vIns.Length); + Array.Copy(v, iStart, vNew, iStart + vIns.Length, + v.Length - iStart); + } + finally + { + Array.Clear(v, 0, v.Length); + MemUtil.ZeroByteArray(pb); + } + + byte[] pbNew = utf8.GetBytes(vNew); + ProtectedString ps = new ProtectedString(m_bIsProtected, pbNew); + + Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) == + ReadString().Insert(iStart, strInsert)); + + Array.Clear(vNew, 0, vNew.Length); + MemUtil.ZeroByteArray(pbNew); + return ps; + } + + public ProtectedString Remove(int iStart, int nCount) + { + if(iStart < 0) throw new ArgumentOutOfRangeException("iStart"); + 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; + + try + { + if((iStart + nCount) > v.Length) + throw new ArgumentException("iStart + nCount"); + + vNew = new char[v.Length - nCount]; + Array.Copy(v, 0, vNew, 0, iStart); + Array.Copy(v, iStart + nCount, vNew, iStart, v.Length - + (iStart + nCount)); + } + finally + { + Array.Clear(v, 0, v.Length); + MemUtil.ZeroByteArray(pb); + } + + byte[] pbNew = utf8.GetBytes(vNew); + ProtectedString ps = new ProtectedString(m_bIsProtected, pbNew); + + Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) == + ReadString().Remove(iStart, nCount)); + + Array.Clear(vNew, 0, vNew.Length); + MemUtil.ZeroByteArray(pbNew); return ps; } } diff --git a/src/KeePassLib2Android/Security/XorredBuffer.cs b/src/KeePassLib2Android/Security/XorredBuffer.cs index df330751..8586f295 100644 --- a/src/KeePassLib2Android/Security/XorredBuffer.cs +++ b/src/KeePassLib2Android/Security/XorredBuffer.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/KeePassLib2Android/Serialization/BinaryReaderEx.cs b/src/KeePassLib2Android/Serialization/BinaryReaderEx.cs index 3ce22a51..9d4351fd 100644 --- a/src/KeePassLib2Android/Serialization/BinaryReaderEx.cs +++ b/src/KeePassLib2Android/Serialization/BinaryReaderEx.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,8 +19,8 @@ using System; using System.Collections.Generic; -using System.Text; using System.IO; +using System.Text; using KeePassLib.Utility; @@ -29,7 +29,7 @@ namespace KeePassLib.Serialization public sealed class BinaryReaderEx { private Stream m_s; - private Encoding m_enc; + // private Encoding m_enc; // See constructor private string m_strReadExcp; public string ReadExceptionText @@ -56,7 +56,7 @@ namespace KeePassLib.Serialization if(input == null) throw new ArgumentNullException("input"); m_s = input; - m_enc = encoding; + // m_enc = encoding; // Not used yet m_strReadExcp = strReadExceptionText; } diff --git a/src/KeePassLib2Android/Serialization/FileLock.cs b/src/KeePassLib2Android/Serialization/FileLock.cs index ac20e00d..9fa81777 100644 --- a/src/KeePassLib2Android/Serialization/FileLock.cs +++ b/src/KeePassLib2Android/Serialization/FileLock.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,10 +19,10 @@ using System; using System.Collections.Generic; -using System.Text; -using System.IO; -using System.Threading; using System.Diagnostics; +using System.IO; +using System.Text; +using System.Threading; using KeePassLib.Cryptography; using KeePassLib.Resources; @@ -154,13 +154,15 @@ namespace KeePassLib.Serialization byte[] pbID = CryptoRandom.Instance.GetRandomBytes(16); string strTime = TimeUtil.SerializeUtc(DateTime.Now); -#if !KeePassLibSD lfi = new LockFileInfo(Convert.ToBase64String(pbID), strTime, +#if KeePassUAP + EnvironmentExt.UserName, EnvironmentExt.MachineName, + EnvironmentExt.UserDomainName); +#elif KeePassLibSD + string.Empty, string.Empty, string.Empty); +#else Environment.UserName, Environment.MachineName, Environment.UserDomainName); -#else - lfi = new LockFileInfo(Convert.ToBase64String(pbID), strTime, - string.Empty, string.Empty, string.Empty); #endif StringBuilder sb = new StringBuilder(); @@ -242,8 +244,8 @@ namespace KeePassLib.Serialization if(bDisposing) Thread.Sleep(50); } - if(bDisposing && !bFileDeleted) - IOConnection.DeleteFile(m_iocLockFile); // Possibly with exception + // if(bDisposing && !bFileDeleted) + // IOConnection.DeleteFile(m_iocLockFile); // Possibly with exception m_iocLockFile = null; } diff --git a/src/KeePassLib2Android/Serialization/FileTransactionEx.cs b/src/KeePassLib2Android/Serialization/FileTransactionEx.cs index 7e179963..9cd91057 100644 --- a/src/KeePassLib2Android/Serialization/FileTransactionEx.cs +++ b/src/KeePassLib2Android/Serialization/FileTransactionEx.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,14 +19,16 @@ using System; using System.Collections.Generic; -using System.Text; -using System.IO; using System.Diagnostics; +using System.IO; +using System.Text; -#if !KeePassLibSD +#if (!KeePassLibSD && !KeePassUAP) using System.Security.AccessControl; #endif +using KeePassLib.Native; +using KeePassLib.Resources; using KeePassLib.Utility; namespace KeePassLib.Serialization @@ -41,6 +43,16 @@ namespace KeePassLib.Serialization private const string StrTempSuffix = ".tmp"; + private static Dictionary g_dEnabled = + new Dictionary(StrUtil.CaseIgnoreComparer); + + private static bool g_bExtraSafe = false; + internal static bool ExtraSafe + { + get { return g_bExtraSafe; } + set { g_bExtraSafe = value; } + } + public FileTransactionEx(IOConnectionInfo iocBaseFile) { Initialize(iocBaseFile, true); @@ -58,6 +70,30 @@ namespace KeePassLib.Serialization m_bTransacted = bTransacted; m_iocBase = iocBaseFile.CloneDeep(); + string strPath = m_iocBase.Path; + +#if !KeePassUAP + // Prevent transactions for FTP URLs under .NET 4.0 in order to + // avoid/workaround .NET bug 621450: + // https://connect.microsoft.com/VisualStudio/feedback/details/621450/problem-renaming-file-on-ftp-server-using-ftpwebrequest-in-net-framework-4-0-vs2010-only + if(strPath.StartsWith("ftp:", StrUtil.CaseIgnoreCmp) && + (Environment.Version.Major >= 4) && !NativeLib.IsUnix()) + m_bTransacted = false; + else + { +#endif + foreach(KeyValuePair kvp in g_dEnabled) + { + if(strPath.StartsWith(kvp.Key, StrUtil.CaseIgnoreCmp)) + { + m_bTransacted = kvp.Value; + break; + } + } +#if !KeePassUAP + } +#endif + if(m_bTransacted) { m_iocTemp = m_iocBase.CloneDeep(); @@ -91,11 +127,18 @@ namespace KeePassLib.Serialization { bool bMadeUnhidden = UrlUtil.UnhideFile(m_iocBase.Path); -#if !KeePassLibSD +#if (!KeePassLibSD && !KeePassUAP) FileSecurity bkSecurity = null; bool bEfsEncrypted = false; #endif + if(g_bExtraSafe) + { + if(!IOConnection.FileExists(m_iocTemp)) + throw new FileNotFoundException(m_iocTemp.Path + + MessageService.NewLine + KLRes.FileSaveFailed); + } + if(IOConnection.FileExists(m_iocBase)) { #if !KeePassLibSD @@ -103,15 +146,18 @@ namespace KeePassLib.Serialization { try { +#if !KeePassUAP FileAttributes faBase = File.GetAttributes(m_iocBase.Path); bEfsEncrypted = ((long)(faBase & FileAttributes.Encrypted) != 0); - +#endif DateTime tCreation = File.GetCreationTime(m_iocBase.Path); - bkSecurity = File.GetAccessControl(m_iocBase.Path); - File.SetCreationTime(m_iocTemp.Path, tCreation); +#if !KeePassUAP + // May throw with Mono + bkSecurity = File.GetAccessControl(m_iocBase.Path); +#endif } - catch(Exception) { Debug.Assert(false); } + catch(Exception) { Debug.Assert(NativeLib.IsUnix()); } } #endif @@ -120,7 +166,7 @@ namespace KeePassLib.Serialization IOConnection.RenameFile(m_iocTemp, m_iocBase); -#if !KeePassLibSD +#if (!KeePassLibSD && !KeePassUAP) if(m_iocBase.IsLocalFile()) { try @@ -140,5 +186,15 @@ namespace KeePassLib.Serialization if(bMadeUnhidden) UrlUtil.HideFile(m_iocBase.Path, true); // Hide again } + + // For plugins + public static void Configure(string strPrefix, bool? obTransacted) + { + if(string.IsNullOrEmpty(strPrefix)) { Debug.Assert(false); return; } + + if(obTransacted.HasValue) + g_dEnabled[strPrefix] = obTransacted.Value; + else g_dEnabled.Remove(strPrefix); + } } } diff --git a/src/KeePassLib2Android/Serialization/HashedBlockStream.cs b/src/KeePassLib2Android/Serialization/HashedBlockStream.cs index 403b6dfd..ead4855d 100644 --- a/src/KeePassLib2Android/Serialization/HashedBlockStream.cs +++ b/src/KeePassLib2Android/Serialization/HashedBlockStream.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,11 +18,14 @@ */ using System; -using System.IO; -using System.Security.Cryptography; using System.Diagnostics; +using System.IO; using System.Text; +#if !KeePassUAP +using System.Security.Cryptography; +#endif + using KeePassLib.Native; using KeePassLib.Utility; @@ -104,9 +107,9 @@ namespace KeePassLib.Serialization m_bVerify = bVerify; UTF8Encoding utf8 = StrUtil.Utf8; - if(m_bWriting == false) // Reading mode + if(!m_bWriting) // Reading mode { - if(m_sBaseStream.CanRead == false) + if(!m_sBaseStream.CanRead) throw new InvalidOperationException(); m_brInput = new BinaryReader(sBaseStream, utf8); @@ -115,7 +118,7 @@ namespace KeePassLib.Serialization } else // Writing mode { - if(m_sBaseStream.CanWrite == false) + if(!m_sBaseStream.CanWrite) throw new InvalidOperationException(); m_bwOutput = new BinaryWriter(sBaseStream, utf8); @@ -129,8 +132,14 @@ namespace KeePassLib.Serialization if(m_bWriting) m_bwOutput.Flush(); } +#if KeePassUAP + protected override void Dispose(bool disposing) + { + if(!disposing) return; +#else public override void Close() { +#endif if(m_sBaseStream != null) { if(m_bWriting == false) // Reading mode @@ -178,7 +187,7 @@ namespace KeePassLib.Serialization if(m_nBufferPos == m_pbBuffer.Length) { if(ReadHashedBlock() == false) - return nCount - nRemaining; // Bytes actually read + return (nCount - nRemaining); // Bytes actually read } int nCopy = Math.Min(m_pbBuffer.Length - m_nBufferPos, nRemaining); diff --git a/src/KeePassLib2Android/Serialization/IOConnection.cs b/src/KeePassLib2Android/Serialization/IOConnection.cs index e115ff5b..16e38e52 100644 --- a/src/KeePassLib2Android/Serialization/IOConnection.cs +++ b/src/KeePassLib2Android/Serialization/IOConnection.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,42 +19,250 @@ using System; using System.Collections.Generic; -using System.Text; +using System.Diagnostics; using System.IO; using System.Net; -using System.Security.Cryptography.X509Certificates; -using System.Diagnostics; +using System.Reflection; +using System.Text; -#if !KeePassLibSD +#if (!KeePassLibSD && !KeePassUAP) using System.Net.Cache; using System.Net.Security; #endif +#if !KeePassUAP +using System.Security.Cryptography.X509Certificates; +#endif + using KeePassLib.Native; using KeePassLib.Utility; namespace KeePassLib.Serialization { #if !KeePassLibSD - public sealed class IOWebClient : WebClient + internal sealed class IOWebClient : WebClient { + private IOConnectionInfo m_ioc; + + public IOWebClient(IOConnectionInfo ioc) : base() + { + m_ioc = ioc; + } + protected override WebRequest GetWebRequest(Uri address) { WebRequest request = base.GetWebRequest(address); - IOConnection.ConfigureWebRequest(request); + IOConnection.ConfigureWebRequest(request, m_ioc); return request; } } #endif + internal abstract class WrapperStream : Stream + { + private readonly Stream m_s; + protected Stream BaseStream + { + get { return m_s; } + } + + public override bool CanRead + { + get { return m_s.CanRead; } + } + + public override bool CanSeek + { + get { return m_s.CanSeek; } + } + + public override bool CanTimeout + { + get { return m_s.CanTimeout; } + } + + public override bool CanWrite + { + get { return m_s.CanWrite; } + } + + public override long Length + { + get { return m_s.Length; } + } + + public override long Position + { + get { return m_s.Position; } + set { m_s.Position = value; } + } + + public override int ReadTimeout + { + get { return m_s.ReadTimeout; } + set { m_s.ReadTimeout = value; } + } + + public override int WriteTimeout + { + get { return m_s.WriteTimeout; } + set { m_s.WriteTimeout = value; } + } + + public WrapperStream(Stream sBase) : base() + { + if(sBase == null) throw new ArgumentNullException("sBase"); + + m_s = sBase; + } + +#if !KeePassUAP + public override IAsyncResult BeginRead(byte[] buffer, int offset, + int count, AsyncCallback callback, object state) + { + return m_s.BeginRead(buffer, offset, count, callback, state); + } + + public override IAsyncResult BeginWrite(byte[] buffer, int offset, + int count, AsyncCallback callback, object state) + { + return BeginWrite(buffer, offset, count, callback, state); + } +#endif + +#if KeePassUAP + protected override void Dispose(bool disposing) + { + if(disposing) m_s.Dispose(); + } +#else + public override void Close() + { + m_s.Close(); + } +#endif + +#if !KeePassUAP + public override int EndRead(IAsyncResult asyncResult) + { + return m_s.EndRead(asyncResult); + } + + public override void EndWrite(IAsyncResult asyncResult) + { + m_s.EndWrite(asyncResult); + } +#endif + + public override void Flush() + { + m_s.Flush(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + return m_s.Read(buffer, offset, count); + } + + public override int ReadByte() + { + return m_s.ReadByte(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + return m_s.Seek(offset, origin); + } + + public override void SetLength(long value) + { + m_s.SetLength(value); + } + + public override void Write(byte[] buffer, int offset, int count) + { + m_s.Write(buffer, offset, count); + } + + public override void WriteByte(byte value) + { + m_s.WriteByte(value); + } + } + + internal sealed class IocStream : WrapperStream + { + private readonly bool m_bWrite; // Initially opened for writing + + public IocStream(Stream sBase) : base(sBase) + { + m_bWrite = sBase.CanWrite; + } + +#if KeePassUAP + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); +#else + public override void Close() + { + base.Close(); +#endif + if(MonoWorkarounds.IsRequired(10163) && m_bWrite) + { + try + { + Stream s = this.BaseStream; + Type t = s.GetType(); + if(t.Name == "WebConnectionStream") + { + PropertyInfo pi = t.GetProperty("Request", + BindingFlags.Instance | BindingFlags.NonPublic); + if(pi != null) + { + WebRequest wr = (pi.GetValue(s, null) as WebRequest); + if(wr != null) + IOConnection.DisposeResponse(wr.GetResponse(), false); + else { Debug.Assert(false); } + } + else { Debug.Assert(false); } + } + } + catch(Exception) { Debug.Assert(false); } + } + } + + public static Stream WrapIfRequired(Stream s) + { + if(s == null) { Debug.Assert(false); return null; } + + if(MonoWorkarounds.IsRequired(10163) && s.CanWrite) + return new IocStream(s); + + return s; + } + } + public static class IOConnection { #if !KeePassLibSD private static ProxyServerType m_pstProxyType = ProxyServerType.System; private static string m_strProxyAddr = string.Empty; private static string m_strProxyPort = string.Empty; + private static ProxyAuthType m_patProxyAuthType = ProxyAuthType.Auto; private static string m_strProxyUserName = string.Empty; private static string m_strProxyPassword = string.Empty; + +#if !KeePassUAP + private static bool? m_obDefaultExpect100Continue = null; + + private static bool m_bSslCertsAcceptInvalid = false; + internal static bool SslCertsAcceptInvalid + { + // get { return m_bSslCertsAcceptInvalid; } + set { m_bSslCertsAcceptInvalid = value; } + } +#endif #endif // Web request methods @@ -64,41 +272,81 @@ namespace KeePassLib.Serialization // Web request headers public const string WrhMoveFileTo = "MoveFileTo"; + public static event EventHandler IOAccessPre; + #if !KeePassLibSD +#if !KeePassUAP // Allow self-signed certificates, expired certificates, etc. - private static bool ValidateServerCertificate(object sender, + private static bool AcceptCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { return true; } +#endif - public static void SetProxy(ProxyServerType pst, string strAddr, - string strPort, string strUserName, string strPassword) + internal static void SetProxy(ProxyServerType pst, string strAddr, + string strPort, ProxyAuthType pat, string strUserName, + string strPassword) { m_pstProxyType = pst; m_strProxyAddr = (strAddr ?? string.Empty); m_strProxyPort = (strPort ?? string.Empty); + m_patProxyAuthType = pat; m_strProxyUserName = (strUserName ?? string.Empty); m_strProxyPassword = (strPassword ?? string.Empty); } - internal static void ConfigureWebRequest(WebRequest request) + internal static void ConfigureWebRequest(WebRequest request, + IOConnectionInfo ioc) { if(request == null) { Debug.Assert(false); return; } // No throw - // WebDAV support - if(request is HttpWebRequest) - { - request.PreAuthenticate = true; // Also auth GET - if(request.Method == WebRequestMethods.Http.Post) - request.Method = WebRequestMethods.Http.Put; - } - // else if(request is FtpWebRequest) - // { - // Debug.Assert(((FtpWebRequest)request).UsePassive); - // } + IocProperties p = ((ioc != null) ? ioc.Properties : null); + if(p == null) { Debug.Assert(false); p = new IocProperties(); } + IHasIocProperties ihpReq = (request as IHasIocProperties); + if(ihpReq != null) + { + IocProperties pEx = ihpReq.IOConnectionProperties; + if(pEx != null) p.CopyTo(pEx); + else ihpReq.IOConnectionProperties = p.CloneDeep(); + } + + if(IsHttpWebRequest(request)) + { + // WebDAV support +#if !KeePassUAP + request.PreAuthenticate = true; // Also auth GET +#endif + if(string.Equals(request.Method, WebRequestMethods.Http.Post, + StrUtil.CaseIgnoreCmp)) + request.Method = WebRequestMethods.Http.Put; + +#if !KeePassUAP + HttpWebRequest hwr = (request as HttpWebRequest); + if(hwr != null) + { + string strUA = p.Get(IocKnownProperties.UserAgent); + if(!string.IsNullOrEmpty(strUA)) hwr.UserAgent = strUA; + } + else { Debug.Assert(false); } +#endif + } +#if !KeePassUAP + else if(IsFtpWebRequest(request)) + { + FtpWebRequest fwr = (request as FtpWebRequest); + if(fwr != null) + { + bool? obPassive = p.GetBool(IocKnownProperties.Passive); + if(obPassive.HasValue) fwr.UsePassive = obPassive.Value; + } + else { Debug.Assert(false); } + } +#endif + +#if !KeePassUAP // Not implemented and ignored in Mono < 2.10 try { @@ -106,6 +354,7 @@ namespace KeePassLib.Serialization } catch(NotImplementedException) { } catch(Exception) { Debug.Assert(false); } +#endif try { @@ -113,10 +362,20 @@ namespace KeePassLib.Serialization if(GetWebProxy(out prx)) request.Proxy = prx; } catch(Exception) { Debug.Assert(false); } + +#if !KeePassUAP + long? olTimeout = p.GetLong(IocKnownProperties.Timeout); + if(olTimeout.HasValue && (olTimeout.Value >= 0)) + request.Timeout = (int)Math.Min(olTimeout.Value, (long)int.MaxValue); + + bool? ob = p.GetBool(IocKnownProperties.PreAuth); + if(ob.HasValue) request.PreAuthenticate = ob.Value; +#endif } internal static void ConfigureWebClient(WebClient wc) { +#if !KeePassUAP // Not implemented and ignored in Mono < 2.10 try { @@ -124,6 +383,7 @@ namespace KeePassLib.Serialization } catch(NotImplementedException) { } catch(Exception) { Debug.Assert(false); } +#endif try { @@ -134,64 +394,163 @@ namespace KeePassLib.Serialization } private static bool GetWebProxy(out IWebProxy prx) + { + bool b = GetWebProxyServer(out prx); + if(b) AssignCredentials(prx); + return b; + } + + private static bool GetWebProxyServer(out IWebProxy prx) { prx = null; if(m_pstProxyType == ProxyServerType.None) return true; // Use null proxy + if(m_pstProxyType == ProxyServerType.Manual) { try { - if(m_strProxyPort.Length > 0) + if(m_strProxyAddr.Length == 0) + { + // First try default (from config), then system + prx = WebRequest.DefaultWebProxy; +#if !KeePassUAP + if(prx == null) prx = WebRequest.GetSystemWebProxy(); +#endif + } + else if(m_strProxyPort.Length > 0) prx = new WebProxy(m_strProxyAddr, int.Parse(m_strProxyPort)); else prx = new WebProxy(m_strProxyAddr); - if((m_strProxyUserName.Length > 0) || (m_strProxyPassword.Length > 0)) - prx.Credentials = new NetworkCredential(m_strProxyUserName, - m_strProxyPassword); - - return true; // Use manual proxy + return (prx != null); } - catch(Exception exProxy) +#if KeePassUAP + catch(Exception) { Debug.Assert(false); } +#else + catch(Exception ex) { string strInfo = m_strProxyAddr; - if(m_strProxyPort.Length > 0) strInfo += ":" + m_strProxyPort; - MessageService.ShowWarning(strInfo, exProxy.Message); + if(m_strProxyPort.Length > 0) + strInfo += ":" + m_strProxyPort; + MessageService.ShowWarning(strInfo, ex.Message); } +#endif return false; // Use default } - if((m_strProxyUserName.Length == 0) && (m_strProxyPassword.Length == 0)) - return false; // Use default proxy, no auth - + Debug.Assert(m_pstProxyType == ProxyServerType.System); try { - prx = WebRequest.DefaultWebProxy; - if(prx == null) prx = WebRequest.GetSystemWebProxy(); - if(prx == null) throw new InvalidOperationException(); + // First try system, then default (from config) +#if !KeePassUAP + prx = WebRequest.GetSystemWebProxy(); +#endif + if(prx == null) prx = WebRequest.DefaultWebProxy; - prx.Credentials = new NetworkCredential(m_strProxyUserName, - m_strProxyPassword); - return true; + return (prx != null); } catch(Exception) { Debug.Assert(false); } return false; } - private static void PrepareWebAccess() + private static void AssignCredentials(IWebProxy prx) { - ServicePointManager.ServerCertificateValidationCallback = - ValidateServerCertificate; + if(prx == null) return; // No assert + + string strUserName = m_strProxyUserName; + string strPassword = m_strProxyPassword; + + ProxyAuthType pat = m_patProxyAuthType; + if(pat == ProxyAuthType.Auto) + { + if((strUserName.Length > 0) || (strPassword.Length > 0)) + pat = ProxyAuthType.Manual; + else pat = ProxyAuthType.Default; + } + + try + { + if(pat == ProxyAuthType.None) + prx.Credentials = null; + else if(pat == ProxyAuthType.Default) + prx.Credentials = CredentialCache.DefaultCredentials; + else if(pat == ProxyAuthType.Manual) + { + if((strUserName.Length > 0) || (strPassword.Length > 0)) + prx.Credentials = new NetworkCredential( + strUserName, strPassword); + } + else { Debug.Assert(false); } + } + catch(Exception) { Debug.Assert(false); } + } + + private static void PrepareWebAccess(IOConnectionInfo ioc) + { +#if !KeePassUAP + IocProperties p = ((ioc != null) ? ioc.Properties : null); + if(p == null) { Debug.Assert(false); p = new IocProperties(); } + + try + { + if(m_bSslCertsAcceptInvalid) + ServicePointManager.ServerCertificateValidationCallback = + IOConnection.AcceptCertificate; + else + ServicePointManager.ServerCertificateValidationCallback = null; + } + catch(Exception) { Debug.Assert(false); } + + try + { + SecurityProtocolType spt = (SecurityProtocolType.Ssl3 | + SecurityProtocolType.Tls); + + // The flags Tls11 and Tls12 in SecurityProtocolType have been + // introduced in .NET 4.5 and must not be set when running under + // older .NET versions (otherwise an exception is thrown) + Type tSpt = typeof(SecurityProtocolType); + string[] vSpt = Enum.GetNames(tSpt); + foreach(string strSpt in vSpt) + { + if(strSpt.Equals("Tls11", StrUtil.CaseIgnoreCmp)) + spt |= (SecurityProtocolType)Enum.Parse(tSpt, "Tls11", true); + else if(strSpt.Equals("Tls12", StrUtil.CaseIgnoreCmp)) + spt |= (SecurityProtocolType)Enum.Parse(tSpt, "Tls12", true); + } + + ServicePointManager.SecurityProtocol = spt; + } + catch(Exception) { Debug.Assert(false); } + + try + { + bool bCurCont = ServicePointManager.Expect100Continue; + if(!m_obDefaultExpect100Continue.HasValue) + { + Debug.Assert(bCurCont); // Default should be true + m_obDefaultExpect100Continue = bCurCont; + } + + bool bNewCont = m_obDefaultExpect100Continue.Value; + bool? ob = p.GetBool(IocKnownProperties.Expect100Continue); + if(ob.HasValue) bNewCont = ob.Value; + + if(bNewCont != bCurCont) + ServicePointManager.Expect100Continue = bNewCont; + } + catch(Exception) { Debug.Assert(false); } +#endif } private static IOWebClient CreateWebClient(IOConnectionInfo ioc) { - PrepareWebAccess(); + PrepareWebAccess(ioc); - IOWebClient wc = new IOWebClient(); + IOWebClient wc = new IOWebClient(ioc); ConfigureWebClient(wc); if((ioc.UserName.Length > 0) || (ioc.Password.Length > 0)) @@ -204,10 +563,10 @@ namespace KeePassLib.Serialization private static WebRequest CreateWebRequest(IOConnectionInfo ioc) { - PrepareWebAccess(); + PrepareWebAccess(ioc); WebRequest req = WebRequest.Create(ioc.Path); - ConfigureWebRequest(req); + ConfigureWebRequest(req, ioc); if((ioc.UserName.Length > 0) || (ioc.Password.Length > 0)) req.Credentials = new NetworkCredential(ioc.UserName, ioc.Password); @@ -219,6 +578,8 @@ namespace KeePassLib.Serialization public static Stream OpenRead(IOConnectionInfo ioc) { + RaiseIOAccessPreEvent(ioc, IOAccessType.Read); + if(StrUtil.IsDataUri(ioc.Path)) { byte[] pbData = StrUtil.DataUriToData(ioc.Path); @@ -227,11 +588,14 @@ namespace KeePassLib.Serialization if(ioc.IsLocalFile()) return OpenReadLocal(ioc); - return CreateWebClient(ioc).OpenRead(new Uri(ioc.Path)); + return IocStream.WrapIfRequired(CreateWebClient(ioc).OpenRead( + new Uri(ioc.Path))); } #else public static Stream OpenRead(IOConnectionInfo ioc) { + RaiseIOAccessPreEvent(ioc, IOAccessType.Read); + return OpenReadLocal(ioc); } #endif @@ -247,22 +611,26 @@ namespace KeePassLib.Serialization { if(ioc == null) { Debug.Assert(false); return null; } + RaiseIOAccessPreEvent(ioc, IOAccessType.Write); + if(ioc.IsLocalFile()) return OpenWriteLocal(ioc); Uri uri = new Uri(ioc.Path); + Stream s; // Mono does not set HttpWebRequest.Method to POST for writes, // so one needs to set the method to PUT explicitly - if(NativeLib.IsUnix() && (uri.Scheme.Equals(Uri.UriSchemeHttp, - StrUtil.CaseIgnoreCmp) || uri.Scheme.Equals(Uri.UriSchemeHttps, - StrUtil.CaseIgnoreCmp))) - return CreateWebClient(ioc).OpenWrite(uri, WebRequestMethods.Http.Put); + if(NativeLib.IsUnix() && IsHttpWebRequest(uri)) + s = CreateWebClient(ioc).OpenWrite(uri, WebRequestMethods.Http.Put); + else s = CreateWebClient(ioc).OpenWrite(uri); - return CreateWebClient(ioc).OpenWrite(uri); + return IocStream.WrapIfRequired(s); } #else public static Stream OpenWrite(IOConnectionInfo ioc) { + RaiseIOAccessPreEvent(ioc, IOAccessType.Write); + return OpenWriteLocal(ioc); } #endif @@ -282,6 +650,8 @@ namespace KeePassLib.Serialization { if(ioc == null) { Debug.Assert(false); return false; } + RaiseIOAccessPreEvent(ioc, IOAccessType.Exists); + if(ioc.IsLocalFile()) return File.Exists(ioc.Path); #if !KeePassLibSD @@ -317,15 +687,18 @@ namespace KeePassLib.Serialization public static void DeleteFile(IOConnectionInfo ioc) { + RaiseIOAccessPreEvent(ioc, IOAccessType.Delete); + if(ioc.IsLocalFile()) { File.Delete(ioc.Path); return; } #if !KeePassLibSD WebRequest req = CreateWebRequest(ioc); if(req != null) { - if(req is HttpWebRequest) req.Method = "DELETE"; - else if(req is FtpWebRequest) req.Method = WebRequestMethods.Ftp.DeleteFile; - else if(req is FileWebRequest) + if(IsHttpWebRequest(req)) req.Method = "DELETE"; + else if(IsFtpWebRequest(req)) + req.Method = WebRequestMethods.Ftp.DeleteFile; + else if(IsFileWebRequest(req)) { File.Delete(UrlUtil.FileUrlToPath(ioc.Path)); return; @@ -348,23 +721,39 @@ namespace KeePassLib.Serialization /// Target file path. public static void RenameFile(IOConnectionInfo iocFrom, IOConnectionInfo iocTo) { + RaiseIOAccessPreEvent(iocFrom, iocTo, IOAccessType.Move); + if(iocFrom.IsLocalFile()) { File.Move(iocFrom.Path, iocTo.Path); return; } #if !KeePassLibSD WebRequest req = CreateWebRequest(iocFrom); if(req != null) { - if(req is HttpWebRequest) + if(IsHttpWebRequest(req)) { +#if KeePassUAP + throw new NotSupportedException(); +#else req.Method = "MOVE"; req.Headers.Set("Destination", iocTo.Path); // Full URL supported +#endif } - else if(req is FtpWebRequest) + else if(IsFtpWebRequest(req)) { +#if KeePassUAP + throw new NotSupportedException(); +#else req.Method = WebRequestMethods.Ftp.Rename; - ((FtpWebRequest)req).RenameTo = UrlUtil.GetFileName(iocTo.Path); + string strTo = UrlUtil.GetFileName(iocTo.Path); + + // We're affected by .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 + // Prepending "./", "%2E/" or "Dummy/../" doesn't work. + + ((FtpWebRequest)req).RenameTo = strTo; +#endif } - else if(req is FileWebRequest) + else if(IsFileWebRequest(req)) { File.Move(UrlUtil.FileUrlToPath(iocFrom.Path), UrlUtil.FileUrlToPath(iocTo.Path)); @@ -372,11 +761,17 @@ namespace KeePassLib.Serialization } else { +#if KeePassUAP + throw new NotSupportedException(); +#else req.Method = WrmMoveFile; req.Headers.Set(WrhMoveFileTo, iocTo.Path); +#endif } +#if !KeePassUAP // Unreachable code DisposeResponse(req.GetResponse(), true); +#endif } #endif @@ -408,7 +803,7 @@ namespace KeePassLib.Serialization } #endif - private static void DisposeResponse(WebResponse wr, bool bGetStream) + internal static void DisposeResponse(WebResponse wr, bool bGetStream) { if(wr == null) return; @@ -449,5 +844,67 @@ namespace KeePassLib.Serialization return null; } + + private static void RaiseIOAccessPreEvent(IOConnectionInfo ioc, IOAccessType t) + { + RaiseIOAccessPreEvent(ioc, null, t); + } + + private static void RaiseIOAccessPreEvent(IOConnectionInfo ioc, + IOConnectionInfo ioc2, IOAccessType t) + { + if(ioc == null) { Debug.Assert(false); return; } + // ioc2 may be null + + if(IOConnection.IOAccessPre != null) + { + IOConnectionInfo ioc2Lcl = ((ioc2 != null) ? ioc2.CloneDeep() : null); + IOAccessEventArgs e = new IOAccessEventArgs(ioc.CloneDeep(), ioc2Lcl, t); + IOConnection.IOAccessPre(null, e); + } + } + + private static bool IsHttpWebRequest(Uri uri) + { + if(uri == null) { Debug.Assert(false); return false; } + + string sch = uri.Scheme; + if(sch == null) { Debug.Assert(false); return false; } + return (sch.Equals("http", StrUtil.CaseIgnoreCmp) || // Uri.UriSchemeHttp + sch.Equals("https", StrUtil.CaseIgnoreCmp)); // Uri.UriSchemeHttps + } + + internal static bool IsHttpWebRequest(WebRequest wr) + { + if(wr == null) { Debug.Assert(false); return false; } + +#if KeePassUAP + return IsHttpWebRequest(wr.RequestUri); +#else + return (wr is HttpWebRequest); +#endif + } + + internal static bool IsFtpWebRequest(WebRequest wr) + { + if(wr == null) { Debug.Assert(false); return false; } + +#if KeePassUAP + return string.Equals(wr.RequestUri.Scheme, "ftp", StrUtil.CaseIgnoreCmp); +#else + return (wr is FtpWebRequest); +#endif + } + + private static bool IsFileWebRequest(WebRequest wr) + { + if(wr == null) { Debug.Assert(false); return false; } + +#if KeePassUAP + return string.Equals(wr.RequestUri.Scheme, "file", StrUtil.CaseIgnoreCmp); +#else + return (wr is FileWebRequest); +#endif + } } } diff --git a/src/KeePassLib2Android/Serialization/IOConnectionInfo.cs b/src/KeePassLib2Android/Serialization/IOConnectionInfo.cs index 2adc05ed..297e564a 100644 --- a/src/KeePassLib2Android/Serialization/IOConnectionInfo.cs +++ b/src/KeePassLib2Android/Serialization/IOConnectionInfo.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,11 +19,11 @@ using System; using System.Collections.Generic; -using System.Text; -using System.IO; -using System.Net; using System.ComponentModel; using System.Diagnostics; +using System.IO; +using System.Text; +using System.Xml.Serialization; using KeePassLib.Interfaces; using KeePassLib.Utility; @@ -119,22 +119,67 @@ namespace KeePassLib.Serialization set { m_ioCredSaveMode = value; } } + private bool m_bComplete = false; + [XmlIgnore] + public bool IsComplete // Credentials etc. fully specified + { + get { return m_bComplete; } + set { m_bComplete = value; } + } + /* public IOFileFormatHint FileFormatHint { get { return m_ioHint; } set { m_ioHint = value; } } */ + private IocProperties m_props = new IocProperties(); + [XmlIgnore] + public IocProperties Properties + { + get { return m_props; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_props = value; + } + } + + /// + /// For serialization only; use Properties in code. + /// + [DefaultValue("")] + public string PropertiesEx + { + get { return m_props.Serialize(); } + set + { + if(value == null) throw new ArgumentNullException("value"); + + IocProperties p = IocProperties.Deserialize(value); + Debug.Assert(p != null); + m_props = (p ?? new IocProperties()); + } + } + public IOConnectionInfo CloneDeep() { - return (IOConnectionInfo)this.MemberwiseClone(); + IOConnectionInfo ioc = (IOConnectionInfo)this.MemberwiseClone(); + ioc.m_props = m_props.CloneDeep(); + return ioc; } +#if DEBUG // For debugger display only + public override string ToString() + { + return GetDisplayName(); + } +#endif + /* /// /// Serialize the current connection info to a string. Credentials - /// are only serialized if the SaveCredentials property - /// is true. + /// are serialized based on the CredSaveMode property. /// /// Input object to be serialized. /// Serialized object as string. @@ -146,31 +191,9 @@ namespace KeePassLib.Serialization string strUrl = iocToCompile.Path; string strUser = TransformUnreadable(iocToCompile.UserName, true); string strPassword = TransformUnreadable(iocToCompile.Password, true); - string strAll = strUrl + strUser + strPassword; - char chSep = char.MinValue; - - char[] vPrefSeps = new char[]{ '@', '#', '!', '$', '*' }; - foreach(char ch in vPrefSeps) - { - if(strAll.IndexOf(ch) < 0) - { - chSep = ch; - break; - } - } - - if(chSep == char.MinValue) - { - for(char chEnum = '!'; chEnum < char.MaxValue; ++chEnum) - { - if(strAll.IndexOf(chEnum) < 0) - { - chSep = chEnum; - break; - } - } - } + string strAll = strUrl + strUser + strPassword + "CUN"; + char chSep = StrUtil.GetUnusedChar(strAll); if(chSep == char.MinValue) throw new FormatException(); StringBuilder sb = new StringBuilder(); @@ -276,14 +299,14 @@ namespace KeePassLib.Serialization string str = m_strUrl; if(m_strUser.Length > 0) - str += " (" + m_strUser + ")"; + str += (" (" + m_strUser + ")"); return str; } public bool IsEmpty() { - return (m_strUrl.Length > 0); + return (m_strUrl.Length == 0); } public static IOConnectionInfo FromPath(string strPath) @@ -306,7 +329,7 @@ namespace KeePassLib.Serialization public bool IsLocalFile() { // Not just ":/", see e.g. AppConfigEx.ChangePathRelAbs - return (m_strUrl.IndexOf(@"://") < 0); + return (m_strUrl.IndexOf("://") < 0); } public void ClearCredentials(bool bDependingOnRememberMode) diff --git a/src/KeePassLib2Android/Serialization/IocProperties.cs b/src/KeePassLib2Android/Serialization/IocProperties.cs new file mode 100644 index 00000000..9fd9acc6 --- /dev/null +++ b/src/KeePassLib2Android/Serialization/IocProperties.cs @@ -0,0 +1,192 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Text; +using System.Xml; + +using KeePassLib.Interfaces; +using KeePassLib.Utility; + +using StrDict = System.Collections.Generic.Dictionary; + +namespace KeePassLib.Serialization +{ + public interface IHasIocProperties + { + IocProperties IOConnectionProperties { get; set; } + } + + public sealed class IocProperties : IDeepCloneable + { + private StrDict m_dict = new StrDict(); + + public IocProperties() + { + } + + public IocProperties CloneDeep() + { + IocProperties p = new IocProperties(); + p.m_dict = new StrDict(m_dict); + return p; + } + + public string Get(string strKey) + { + if(string.IsNullOrEmpty(strKey)) return null; + + foreach(KeyValuePair kvp in m_dict) + { + if(kvp.Key.Equals(strKey, StrUtil.CaseIgnoreCmp)) + return kvp.Value; + } + + return null; + } + + public void Set(string strKey, string strValue) + { + if(string.IsNullOrEmpty(strKey)) { Debug.Assert(false); return; } + + foreach(KeyValuePair kvp in m_dict) + { + if(kvp.Key.Equals(strKey, StrUtil.CaseIgnoreCmp)) + { + if(string.IsNullOrEmpty(strValue)) m_dict.Remove(kvp.Key); + else m_dict[kvp.Key] = strValue; + return; + } + } + + if(!string.IsNullOrEmpty(strValue)) m_dict[strKey] = strValue; + } + + public bool? GetBool(string strKey) + { + string str = Get(strKey); + if(string.IsNullOrEmpty(str)) return null; + + return StrUtil.StringToBool(str); + } + + public void SetBool(string strKey, bool? ob) + { + if(ob.HasValue) Set(strKey, (ob.Value ? "1" : "0")); + else Set(strKey, null); + } + + public long? GetLong(string strKey) + { + string str = Get(strKey); + if(string.IsNullOrEmpty(str)) return null; + + long l; + if(StrUtil.TryParseLongInvariant(str, out l)) return l; + Debug.Assert(false); + return null; + } + + public void SetLong(string strKey, long? ol) + { + if(ol.HasValue) + Set(strKey, ol.Value.ToString(NumberFormatInfo.InvariantInfo)); + else Set(strKey, null); + } + + public string Serialize() + { + if(m_dict.Count == 0) return string.Empty; + + StringBuilder sbAll = new StringBuilder(); + foreach(KeyValuePair kvp in m_dict) + { + sbAll.Append(kvp.Key); + sbAll.Append(kvp.Value); + } + + string strAll = sbAll.ToString(); + char chSepOuter = ';'; + if(strAll.IndexOf(chSepOuter) >= 0) + chSepOuter = StrUtil.GetUnusedChar(strAll); + + strAll += chSepOuter; + char chSepInner = '='; + if(strAll.IndexOf(chSepInner) >= 0) + chSepInner = StrUtil.GetUnusedChar(strAll); + + StringBuilder sb = new StringBuilder(); + sb.Append(chSepOuter); + sb.Append(chSepInner); + + foreach(KeyValuePair kvp in m_dict) + { + sb.Append(chSepOuter); + sb.Append(kvp.Key); + sb.Append(chSepInner); + sb.Append(kvp.Value); + } + + return sb.ToString(); + } + + public static IocProperties Deserialize(string strSerialized) + { + IocProperties p = new IocProperties(); + if(string.IsNullOrEmpty(strSerialized)) return p; // No assert + + char chSepOuter = strSerialized[0]; + string[] v = strSerialized.Substring(1).Split(new char[] { chSepOuter }); + if((v == null) || (v.Length < 2)) { Debug.Assert(false); return p; } + + string strMeta = v[0]; + if(string.IsNullOrEmpty(strMeta)) { Debug.Assert(false); return p; } + + char chSepInner = strMeta[0]; + char[] vSepInner = new char[] { chSepInner }; + + for(int i = 1; i < v.Length; ++i) + { + string strProp = v[i]; + if(string.IsNullOrEmpty(strProp)) { Debug.Assert(false); continue; } + + string[] vProp = strProp.Split(vSepInner); + if((vProp == null) || (vProp.Length < 2)) { Debug.Assert(false); continue; } + Debug.Assert(vProp.Length == 2); + + p.Set(vProp[0], vProp[1]); + } + + return p; + } + + public void CopyTo(IocProperties p) + { + if(p == null) { Debug.Assert(false); return; } + + foreach(KeyValuePair kvp in m_dict) + { + p.m_dict[kvp.Key] = kvp.Value; + } + } + } +} diff --git a/src/KeePassLib2Android/Serialization/IocPropertyInfo.cs b/src/KeePassLib2Android/Serialization/IocPropertyInfo.cs new file mode 100644 index 00000000..c63358f4 --- /dev/null +++ b/src/KeePassLib2Android/Serialization/IocPropertyInfo.cs @@ -0,0 +1,99 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +using KeePassLib.Utility; + +namespace KeePassLib.Serialization +{ + public sealed class IocPropertyInfo + { + private readonly string m_strName; + public string Name + { + get { return m_strName; } + } + + private readonly Type m_t; + public Type Type + { + get { return m_t; } + } + + private string m_strDisplayName; + public string DisplayName + { + get { return m_strDisplayName; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strDisplayName = value; + } + } + + private List m_lProtocols = new List(); + public IEnumerable Protocols + { + get { return m_lProtocols; } + } + + public IocPropertyInfo(string strName, Type t, string strDisplayName, + string[] vProtocols) + { + if(strName == null) throw new ArgumentNullException("strName"); + if(t == null) throw new ArgumentNullException("t"); + if(strDisplayName == null) throw new ArgumentNullException("strDisplayName"); + + m_strName = strName; + m_t = t; + m_strDisplayName = strDisplayName; + + AddProtocols(vProtocols); + } + + public void AddProtocols(string[] v) + { + if(v == null) { Debug.Assert(false); return; } + + foreach(string strProtocol in v) + { + if(strProtocol == null) continue; + + string str = strProtocol.Trim(); + if(str.Length == 0) continue; + + bool bFound = false; + foreach(string strEx in m_lProtocols) + { + if(strEx.Equals(str, StrUtil.CaseIgnoreCmp)) + { + bFound = true; + break; + } + } + + if(!bFound) m_lProtocols.Add(str); + } + } + } +} diff --git a/src/KeePassLib2Android/Serialization/IocPropertyInfoPool.cs b/src/KeePassLib2Android/Serialization/IocPropertyInfoPool.cs new file mode 100644 index 00000000..99b661f6 --- /dev/null +++ b/src/KeePassLib2Android/Serialization/IocPropertyInfoPool.cs @@ -0,0 +1,123 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +using KeePassLib.Resources; +using KeePassLib.Utility; + +namespace KeePassLib.Serialization +{ + public static class IocKnownProtocols + { + public const string Http = "HTTP"; + public const string Https = "HTTPS"; + public const string WebDav = "WebDAV"; + public const string Ftp = "FTP"; + } + + public static class IocKnownProperties + { + public const string Timeout = "Timeout"; + public const string PreAuth = "PreAuth"; + + public const string UserAgent = "UserAgent"; + public const string Expect100Continue = "Expect100Continue"; + + public const string Passive = "Passive"; + } + + public static class IocPropertyInfoPool + { + private static List m_l = null; + public static IEnumerable PropertyInfos + { + get { EnsureInitialized(); return m_l; } + } + + private static void EnsureInitialized() + { + if(m_l != null) return; + + string strGen = KLRes.General; + string strHttp = IocKnownProtocols.Http; + string strHttps = IocKnownProtocols.Https; + string strWebDav = IocKnownProtocols.WebDav; + string strFtp = IocKnownProtocols.Ftp; + + string[] vGen = new string[] { strGen }; + string[] vHttp = new string[] { strHttp, strHttps, strWebDav }; + string[] vFtp = new string[] { strFtp }; + + List l = new List(); + + l.Add(new IocPropertyInfo(IocKnownProperties.Timeout, + typeof(long), KLRes.Timeout + " [ms]", vGen)); + l.Add(new IocPropertyInfo(IocKnownProperties.PreAuth, + typeof(bool), KLRes.PreAuth, vGen)); + + l.Add(new IocPropertyInfo(IocKnownProperties.UserAgent, + typeof(string), KLRes.UserAgent, vHttp)); + l.Add(new IocPropertyInfo(IocKnownProperties.Expect100Continue, + typeof(bool), KLRes.Expect100Continue, vHttp)); + + l.Add(new IocPropertyInfo(IocKnownProperties.Passive, + typeof(bool), KLRes.Passive, vFtp)); + + // l.Add(new IocPropertyInfo("Test", typeof(bool), + // "Long long long long long long long long long long long long long long long long long long long long", + // new string[] { "Proto 1/9", "Proto 2/9", "Proto 3/9", "Proto 4/9", "Proto 5/9", + // "Proto 6/9", "Proto 7/9", "Proto 8/9", "Proto 9/9" })); + + m_l = l; + } + + public static IocPropertyInfo Get(string strName) + { + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return null; } + + EnsureInitialized(); + foreach(IocPropertyInfo pi in m_l) + { + if(pi.Name.Equals(strName, StrUtil.CaseIgnoreCmp)) + return pi; + } + + return null; + } + + public static bool Add(IocPropertyInfo pi) + { + if(pi == null) { Debug.Assert(false); return false; } + + // Name must be non-empty + string strName = pi.Name; + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return false; } + + IocPropertyInfo piEx = Get(strName); // Ensures initialized + if(piEx != null) { Debug.Assert(false); return false; } // Exists already + + m_l.Add(pi); + return true; + } + } +} diff --git a/src/KeePassLib2Android/Serialization/KdbxFile.Read.Streamed.cs b/src/KeePassLib2Android/Serialization/KdbxFile.Read.Streamed.cs index 27b85c4e..5f39afda 100644 --- a/src/KeePassLib2Android/Serialization/KdbxFile.Read.Streamed.cs +++ b/src/KeePassLib2Android/Serialization/KdbxFile.Read.Streamed.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,18 +19,17 @@ using System; using System.Collections.Generic; -using System.Text; -using System.Security; -using System.Security.Cryptography; -using System.Drawing; -using System.Xml; -using System.IO; using System.Diagnostics; +using System.IO; +using System.Text; +using System.Xml; + +#if !KeePassUAP +using System.Drawing; +#endif using KeePassLib; using KeePassLib.Collections; -using KeePassLib.Cryptography; -using KeePassLib.Cryptography.Cipher; using KeePassLib.Interfaces; using KeePassLib.Resources; using KeePassLib.Security; @@ -100,11 +99,15 @@ namespace KeePassLib.Serialization xrs.IgnoreProcessingInstructions = true; xrs.IgnoreWhitespace = true; +#if KeePassUAP + xrs.DtdProcessing = DtdProcessing.Prohibit; +#else #if !KeePassLibSD - xrs.ProhibitDtd = true; + xrs.ProhibitDtd = true; // Obsolete in .NET 4, but still there + // xrs.DtdProcessing = DtdProcessing.Prohibit; // .NET 4 only #endif - xrs.ValidationType = ValidationType.None; +#endif return xrs; } @@ -451,10 +454,10 @@ namespace KeePassLib.Serialization (ITimeLogger)m_ctxGroup : (ITimeLogger)m_ctxEntry); Debug.Assert(tl != null); - if(xr.Name == ElemLastModTime) - tl.LastModificationTime = ReadTime(xr); - else if(xr.Name == ElemCreationTime) + if(xr.Name == ElemCreationTime) tl.CreationTime = ReadTime(xr); + else if(xr.Name == ElemLastModTime) + tl.LastModificationTime = ReadTime(xr); else if(xr.Name == ElemLastAccessTime) tl.LastAccessTime = ReadTime(xr); else if(xr.Name == ElemExpiryTime) @@ -560,7 +563,8 @@ namespace KeePassLib.Serialization return KdbContext.Meta; else if((ctx == KdbContext.CustomIcon) && (xr.Name == ElemCustomIconItem)) { - if((m_uuidCustomIconID != PwUuid.Zero) && (m_pbCustomIconData != null)) + if(!m_uuidCustomIconID.Equals(PwUuid.Zero) && + (m_pbCustomIconData != null)) m_pwDatabase.CustomIcons.Add(new PwCustomIcon( m_uuidCustomIconID, m_pbCustomIconData)); else { Debug.Assert(false); } @@ -587,7 +591,7 @@ namespace KeePassLib.Serialization } else if((ctx == KdbContext.Group) && (xr.Name == ElemGroup)) { - if(PwUuid.Zero.EqualsValue(m_ctxGroup.Uuid)) + if(PwUuid.Zero.Equals(m_ctxGroup.Uuid)) m_ctxGroup.Uuid = new PwUuid(true); // No assert (import) m_ctxGroups.Pop(); @@ -608,7 +612,7 @@ namespace KeePassLib.Serialization else if((ctx == KdbContext.Entry) && (xr.Name == ElemEntry)) { // Create new UUID if absent - if(PwUuid.Zero.EqualsValue(m_ctxEntry.Uuid)) + if(PwUuid.Zero.Equals(m_ctxEntry.Uuid)) m_ctxEntry.Uuid = new PwUuid(true); // No assert (import) if(m_bEntryInHistory) @@ -716,6 +720,9 @@ namespace KeePassLib.Serialization string str = ReadString(xr); int n; + if(StrUtil.TryParseIntInvariant(str, out n)) return n; + + // Backward compatibility if(StrUtil.TryParseInt(str, out n)) return n; Debug.Assert(false); @@ -727,6 +734,9 @@ namespace KeePassLib.Serialization string str = ReadString(xr); uint u; + if(StrUtil.TryParseUIntInvariant(str, out u)) return u; + + // Backward compatibility if(StrUtil.TryParseUInt(str, out u)) return u; Debug.Assert(false); @@ -738,6 +748,9 @@ namespace KeePassLib.Serialization string str = ReadString(xr); long l; + if(StrUtil.TryParseLongInvariant(str, out l)) return l; + + // Backward compatibility if(StrUtil.TryParseLong(str, out l)) return l; Debug.Assert(false); @@ -749,6 +762,9 @@ namespace KeePassLib.Serialization string str = ReadString(xr); ulong u; + if(StrUtil.TryParseULongInvariant(str, out u)) return u; + + // Backward compatibility if(StrUtil.TryParseULong(str, out u)) return u; Debug.Assert(false); @@ -793,7 +809,19 @@ namespace KeePassLib.Serialization if(strRef != null) { ProtectedBinary pb = BinPoolGet(strRef); - if(pb != null) return pb; + if(pb != null) + { + // https://sourceforge.net/p/keepass/feature-requests/2023/ + xr.MoveToElement(); +#if DEBUG + string strInner = ReadStringRaw(xr); + Debug.Assert(string.IsNullOrEmpty(strInner)); +#else + ReadStringRaw(xr); +#endif + + return pb; + } else { Debug.Assert(false); } } else { Debug.Assert(false); } diff --git a/src/KeePassLib2Android/Serialization/KdbxFile.Read.cs b/src/KeePassLib2Android/Serialization/KdbxFile.Read.cs index 03456962..8d9b4a00 100644 --- a/src/KeePassLib2Android/Serialization/KdbxFile.Read.cs +++ b/src/KeePassLib2Android/Serialization/KdbxFile.Read.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,13 +19,16 @@ using System; using System.Collections.Generic; -using System.Text; -using System.IO; using System.Diagnostics; +using System.IO; using System.Security; -using System.Security.Cryptography; +using System.Text; using System.Xml; +#if !KeePassUAP +using System.Security.Cryptography; +#endif + #if !KeePassLibSD using System.IO.Compression; #else @@ -127,6 +130,22 @@ namespace KeePassLib.Serialization } else m_randomStream = null; // No random stream for plain-text files +#if KeePassDebug_WriteXml + // FileStream fsOut = new FileStream("Raw.xml", FileMode.Create, + // FileAccess.Write, FileShare.None); + // try + // { + // while(true) + // { + // int b = readerStream.ReadByte(); + // if(b == -1) break; + // fsOut.WriteByte((byte)b); + // } + // } + // catch(Exception) { } + // fsOut.Close(); +#endif + ReadXmlStreamed(readerStream, hashedStream); // ReadXmlDom(readerStream); @@ -159,6 +178,12 @@ namespace KeePassLib.Serialization // the history maintenance settings) m_pwDatabase.MaintainBackups(); // Don't mark database as modified + // Expand the root group, such that in case the user accidently + // collapses the root group he can simply reopen the database + PwGroup pgRoot = m_pwDatabase.RootGroup; + if(pgRoot != null) pgRoot.IsExpanded = true; + else { Debug.Assert(false); } + m_pbHashOfHeader = null; } diff --git a/src/KeePassLib2Android/Serialization/KdbxFile.Write.cs b/src/KeePassLib2Android/Serialization/KdbxFile.Write.cs index ada3a9ed..27a1853e 100644 --- a/src/KeePassLib2Android/Serialization/KdbxFile.Write.cs +++ b/src/KeePassLib2Android/Serialization/KdbxFile.Write.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,20 +19,22 @@ using System; using System.Collections.Generic; -using System.Text; -using System.IO; -using System.Xml; using System.Diagnostics; -using System.Security; -using System.Security.Cryptography; -using System.Drawing; using System.Globalization; -using System.Drawing.Imaging; +using System.IO; +using System.Security; +using System.Text; +using System.Xml; -#if !KeePassLibSD -using System.IO.Compression; -#else +#if !KeePassUAP +using System.Drawing; +using System.Security.Cryptography; +#endif + +#if KeePassLibSD using KeePassLibSD; +#else +using System.IO.Compression; #endif using KeePassLib.Collections; @@ -121,7 +123,24 @@ namespace KeePassLib.Serialization writerStream = hashedStream; else { Debug.Assert(false); throw new FormatException("KdbFormat"); } - m_xmlWriter = new XmlTextWriter(writerStream, encNoBom); +#if KeePassUAP + XmlWriterSettings xws = new XmlWriterSettings(); + xws.Encoding = encNoBom; + xws.Indent = true; + xws.IndentChars = "\t"; + xws.NewLineOnAttributes = false; + + XmlWriter xw = XmlWriter.Create(writerStream, xws); +#else + XmlTextWriter xw = new XmlTextWriter(writerStream, encNoBom); + + xw.Formatting = Formatting.Indented; + xw.IndentChar = '\t'; + xw.Indentation = 1; +#endif + + m_xmlWriter = xw; + WriteDocument(pgDataSource); m_xmlWriter.Flush(); @@ -241,10 +260,6 @@ namespace KeePassLib.Serialization BinPoolBuild(pgRoot); - m_xmlWriter.Formatting = Formatting.Indented; - m_xmlWriter.IndentChar = '\t'; - m_xmlWriter.Indentation = 1; - m_xmlWriter.WriteStartDocument(true); m_xmlWriter.WriteStartElement(ElemDocNode); @@ -363,7 +378,7 @@ namespace KeePassLib.Serialization WriteObject(ElemNotes, pg.Notes, true); WriteObject(ElemIcon, (int)pg.IconId); - if(pg.CustomIconUuid != PwUuid.Zero) + if(!pg.CustomIconUuid.Equals(PwUuid.Zero)) WriteObject(ElemCustomIconID, pg.CustomIconUuid); WriteList(ElemTimes, pg); @@ -388,7 +403,7 @@ namespace KeePassLib.Serialization WriteObject(ElemUuid, pe.Uuid); WriteObject(ElemIcon, (int)pe.IconId); - if(pe.CustomIconUuid != PwUuid.Zero) + if(!pe.CustomIconUuid.Equals(PwUuid.Zero)) WriteObject(ElemCustomIconID, pe.CustomIconUuid); WriteObject(ElemFgColor, StrUtil.ColorToUnnamedHtml(pe.ForegroundColor, true), false); @@ -454,8 +469,8 @@ namespace KeePassLib.Serialization m_xmlWriter.WriteStartElement(name); - WriteObject(ElemLastModTime, times.LastModificationTime); WriteObject(ElemCreationTime, times.CreationTime); + WriteObject(ElemLastModTime, times.LastModificationTime); WriteObject(ElemLastAccessTime, times.LastAccessTime); WriteObject(ElemExpiryTime, times.ExpiryTime); WriteObject(ElemExpires, times.Expires); @@ -577,7 +592,7 @@ namespace KeePassLib.Serialization Debug.Assert(name != null); m_xmlWriter.WriteStartElement(name); - m_xmlWriter.WriteString(value.ToString()); + m_xmlWriter.WriteString(value.ToString(NumberFormatInfo.InvariantInfo)); m_xmlWriter.WriteEndElement(); } @@ -586,7 +601,7 @@ namespace KeePassLib.Serialization Debug.Assert(name != null); m_xmlWriter.WriteStartElement(name); - m_xmlWriter.WriteString(value.ToString()); + m_xmlWriter.WriteString(value.ToString(NumberFormatInfo.InvariantInfo)); m_xmlWriter.WriteEndElement(); } @@ -595,7 +610,7 @@ namespace KeePassLib.Serialization Debug.Assert(name != null); m_xmlWriter.WriteStartElement(name); - m_xmlWriter.WriteString(value.ToString()); + m_xmlWriter.WriteString(value.ToString(NumberFormatInfo.InvariantInfo)); m_xmlWriter.WriteEndElement(); } @@ -604,7 +619,7 @@ namespace KeePassLib.Serialization Debug.Assert(name != null); m_xmlWriter.WriteStartElement(name); - m_xmlWriter.WriteString(value.ToString()); + m_xmlWriter.WriteString(value.ToString(NumberFormatInfo.InvariantInfo)); m_xmlWriter.WriteEndElement(); } @@ -686,7 +701,8 @@ namespace KeePassLib.Serialization // page area if(char.IsSymbol(ch) || char.IsSurrogate(ch)) { - System.Globalization.UnicodeCategory cat = char.GetUnicodeCategory(ch); + System.Globalization.UnicodeCategory cat = + CharUnicodeInfo.GetUnicodeCategory(ch); // Map character to correct position in code page chMapped = (char)((int)cat * 32 + ch); } @@ -698,7 +714,11 @@ namespace KeePassLib.Serialization // in the low ANSI range (up to 255) when calling // ToLower on them with invariant culture (see // http://lists.ximian.com/pipermail/mono-patches/2002-February/086106.html ) - chMapped = char.ToLower(ch, CultureInfo.InvariantCulture); +#if !KeePassLibSD + chMapped = char.ToLowerInvariant(ch); +#else + chMapped = char.ToLower(ch); +#endif } } diff --git a/src/KeePassLib2Android/Serialization/KdbxFile.cs b/src/KeePassLib2Android/Serialization/KdbxFile.cs index b1cd287a..2ca29c95 100644 --- a/src/KeePassLib2Android/Serialization/KdbxFile.cs +++ b/src/KeePassLib2Android/Serialization/KdbxFile.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,15 +19,11 @@ using System; using System.Collections.Generic; -using System.Xml; -using System.Text; +using System.Diagnostics; using System.Globalization; using System.IO; -using System.Diagnostics; - -#if !KeePassLibSD -using System.IO.Compression; -#endif +using System.Text; +using System.Xml; using KeePassLib.Collections; using KeePassLib.Cryptography; @@ -63,12 +59,12 @@ namespace KeePassLib.Serialization /// /// File identifier, first 32-bit value. /// - private const uint FileSignature1 = 0x9AA2D903; + internal const uint FileSignature1 = 0x9AA2D903; /// /// File identifier, second 32-bit value. /// - private const uint FileSignature2 = 0xB54BFB67; + internal const uint FileSignature2 = 0xB54BFB67; /// /// File version of files saved by the current KdbxFile class. @@ -82,11 +78,11 @@ namespace KeePassLib.Serialization private const uint FileVersionCriticalMask = 0xFFFF0000; // KeePass 1.x signature - private const uint FileSignatureOld1 = 0x9AA2D903; - private const uint FileSignatureOld2 = 0xB54BFB65; + internal const uint FileSignatureOld1 = 0x9AA2D903; + internal const uint FileSignatureOld2 = 0xB54BFB65; // KeePass 2.x pre-release (alpha and beta) signature - private const uint FileSignaturePreRelease1 = 0x9AA2D903; - private const uint FileSignaturePreRelease2 = 0xB54BFB66; + internal const uint FileSignaturePreRelease1 = 0x9AA2D903; + internal const uint FileSignaturePreRelease2 = 0xB54BFB66; private const string ElemDocNode = "KeePassFile"; private const string ElemMeta = "Meta"; @@ -191,7 +187,7 @@ namespace KeePassLib.Serialization private PwDatabase m_pwDatabase; // Not null, see constructor - private XmlTextWriter m_xmlWriter = null; + private XmlWriter m_xmlWriter = null; private CryptoRandomStream m_randomStream = null; private KdbxFormat m_format = KdbxFormat.Default; private IStatusLogger m_slLogger = null; @@ -324,7 +320,8 @@ namespace KeePassLib.Serialization if(BinPoolFind(pb) != null) return; // Exists already - m_dictBinPool.Add(m_dictBinPool.Count.ToString(), pb); + m_dictBinPool.Add(m_dictBinPool.Count.ToString( + NumberFormatInfo.InvariantInfo), pb); } private string BinPoolFind(ProtectedBinary pb) @@ -366,7 +363,9 @@ namespace KeePassLib.Serialization string strDesc = UrlUtil.StripExtension(strName); strPath += strDesc; - if(iTry > 1) strPath += " (" + iTry.ToString() + ")"; + if(iTry > 1) + strPath += " (" + iTry.ToString(NumberFormatInfo.InvariantInfo) + + ")"; if(!string.IsNullOrEmpty(strExt)) strPath += "." + strExt; diff --git a/src/KeePassLib2Android/Serialization/OldFormatException.cs b/src/KeePassLib2Android/Serialization/OldFormatException.cs index 06593f2b..f05de4a4 100644 --- a/src/KeePassLib2Android/Serialization/OldFormatException.cs +++ b/src/KeePassLib2Android/Serialization/OldFormatException.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/KeePassLib2Android/Translation/KPControlCustomization.cs b/src/KeePassLib2Android/Translation/KPControlCustomization.cs index 1530f655..f71d3213 100644 --- a/src/KeePassLib2Android/Translation/KPControlCustomization.cs +++ b/src/KeePassLib2Android/Translation/KPControlCustomization.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,15 +19,17 @@ using System; using System.Collections.Generic; -using System.Text; using System.ComponentModel; -using System.Windows.Forms; using System.Diagnostics; -using System.Xml.Serialization; using System.Globalization; -using System.IO; -using System.Security.Cryptography; +using System.Text; +using System.Xml.Serialization; + +#if !KeePassUAP using System.Drawing; +using System.Security.Cryptography; +using System.Windows.Forms; +#endif using KeePassLib.Utility; @@ -42,8 +44,8 @@ namespace KeePassLib.Translation private const string m_strControlRelative = @"%c"; - internal const NumberStyles m_nsParser = NumberStyles.AllowLeadingSign | - NumberStyles.AllowDecimalPoint; + internal const NumberStyles m_nsParser = (NumberStyles.AllowLeadingSign | + NumberStyles.AllowDecimalPoint); internal static readonly CultureInfo m_lclInv = CultureInfo.InvariantCulture; private string m_strPosX = string.Empty; @@ -112,7 +114,7 @@ namespace KeePassLib.Translation else { Debug.Assert(false); } } -#if !KeePassLibSD +#if (!KeePassLibSD && !KeePassUAP) internal void ApplyTo(Control c) { Debug.Assert(c != null); if(c == null) return; @@ -267,7 +269,7 @@ namespace KeePassLib.Translation return m_strMemberName.CompareTo(kpOther.Name); } -#if !KeePassLibSD +#if (!KeePassLibSD && !KeePassUAP) private static readonly Type[] m_vTextControls = new Type[] { typeof(MenuStrip), typeof(PictureBox), typeof(ListView), typeof(TreeView), typeof(ToolStrip), typeof(WebBrowser), @@ -309,8 +311,8 @@ namespace KeePassLib.Translation if(c is Form) { - WriteCpiParam(sb, c.ClientSize.Width.ToString()); - WriteCpiParam(sb, c.ClientSize.Height.ToString()); + WriteCpiParam(sb, c.ClientSize.Width.ToString(KpccLayout.m_lclInv)); + WriteCpiParam(sb, c.ClientSize.Height.ToString(KpccLayout.m_lclInv)); } else // Normal control { diff --git a/src/KeePassLib2Android/Translation/KPFormCustomization.cs b/src/KeePassLib2Android/Translation/KPFormCustomization.cs index 9c6a3442..5635d141 100644 --- a/src/KeePassLib2Android/Translation/KPFormCustomization.cs +++ b/src/KeePassLib2Android/Translation/KPFormCustomization.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,11 +19,13 @@ using System; using System.Collections.Generic; -using System.Text; -using System.Windows.Forms; -using System.Xml.Serialization; using System.Diagnostics; -using System.Reflection; +using System.Text; +using System.Xml.Serialization; + +#if !KeePassUAP +using System.Windows.Forms; +#endif namespace KeePassLib.Translation { @@ -66,6 +68,7 @@ namespace KeePassLib.Translation } } +#if (!KeePassLibSD && !KeePassUAP) private Form m_formEnglish = null; [XmlIgnore] public Form FormEnglish @@ -74,7 +77,6 @@ namespace KeePassLib.Translation set { m_formEnglish = value; } } -#if !KeePassLibSD public void ApplyTo(Form form) { Debug.Assert(form != null); if(form == null) throw new ArgumentNullException("form"); diff --git a/src/KeePassLib2Android/Translation/KPStringTable.cs b/src/KeePassLib2Android/Translation/KPStringTable.cs index b5c676fb..33258f32 100644 --- a/src/KeePassLib2Android/Translation/KPStringTable.cs +++ b/src/KeePassLib2Android/Translation/KPStringTable.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,10 +19,13 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Text; using System.Xml.Serialization; + +#if !KeePassUAP using System.Windows.Forms; -using System.Diagnostics; +#endif namespace KeePassLib.Translation { @@ -66,7 +69,7 @@ namespace KeePassLib.Translation return dict; } -#if !KeePassLibSD +#if (!KeePassLibSD && !KeePassUAP) public void ApplyTo(ToolStripItemCollection tsic) { if(tsic == null) throw new ArgumentNullException("tsic"); diff --git a/src/KeePassLib2Android/Translation/KPStringTableItem.cs b/src/KeePassLib2Android/Translation/KPStringTableItem.cs index 3cc0bddd..3106fa71 100644 --- a/src/KeePassLib2Android/Translation/KPStringTableItem.cs +++ b/src/KeePassLib2Android/Translation/KPStringTableItem.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/KeePassLib2Android/Translation/KPTranslation.cs b/src/KeePassLib2Android/Translation/KPTranslation.cs index 2705325d..e6415ce5 100644 --- a/src/KeePassLib2Android/Translation/KPTranslation.cs +++ b/src/KeePassLib2Android/Translation/KPTranslation.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,24 +19,27 @@ using System; using System.Collections.Generic; -using System.Text; +using System.ComponentModel; +using System.Diagnostics; using System.IO; +using System.Text; using System.Xml; using System.Xml.Serialization; -using System.Windows.Forms; -using System.ComponentModel; + +#if !KeePassUAP using System.Drawing; -using System.Diagnostics; +using System.Windows.Forms; +#endif + +#if KeePassLibSD +using ICSharpCode.SharpZipLib.GZip; +#else +using System.IO.Compression; +#endif using KeePassLib.Interfaces; using KeePassLib.Utility; -#if !KeePassLibSD -using System.IO.Compression; -#else -using ICSharpCode.SharpZipLib.GZip; -#endif - namespace KeePassLib.Translation { [XmlRoot("Translation")] @@ -92,18 +95,25 @@ namespace KeePassLib.Translation } } - public static void SaveToFile(KPTranslation kpTrl, string strFileName, + public static void Save(KPTranslation kpTrl, string strFileName, + IXmlSerializerEx xs) + { + using(FileStream fs = new FileStream(strFileName, FileMode.Create, + FileAccess.Write, FileShare.None)) + { + Save(kpTrl, fs, xs); + } + } + + public static void Save(KPTranslation kpTrl, Stream sOut, IXmlSerializerEx xs) { if(xs == null) throw new ArgumentNullException("xs"); - FileStream fs = new FileStream(strFileName, FileMode.Create, - FileAccess.Write, FileShare.None); - #if !KeePassLibSD - GZipStream gz = new GZipStream(fs, CompressionMode.Compress); + GZipStream gz = new GZipStream(sOut, CompressionMode.Compress); #else - GZipOutputStream gz = new GZipOutputStream(fs); + GZipOutputStream gz = new GZipOutputStream(sOut); #endif XmlWriterSettings xws = new XmlWriterSettings(); @@ -118,27 +128,36 @@ namespace KeePassLib.Translation xw.Close(); gz.Close(); - fs.Close(); + sOut.Close(); } - public static KPTranslation LoadFromFile(string strFile, - IXmlSerializerEx xs) + public static KPTranslation Load(string strFile, IXmlSerializerEx xs) + { + KPTranslation kpTrl = null; + + using(FileStream fs = new FileStream(strFile, FileMode.Open, + FileAccess.Read, FileShare.Read)) + { + kpTrl = Load(fs, xs); + } + + return kpTrl; + } + + public static KPTranslation Load(Stream s, IXmlSerializerEx xs) { if(xs == null) throw new ArgumentNullException("xs"); - FileStream fs = new FileStream(strFile, FileMode.Open, - FileAccess.Read, FileShare.Read); - #if !KeePassLibSD - GZipStream gz = new GZipStream(fs, CompressionMode.Decompress); + GZipStream gz = new GZipStream(s, CompressionMode.Decompress); #else - GZipInputStream gz = new GZipInputStream(fs); + GZipInputStream gz = new GZipInputStream(s); #endif KPTranslation kpTrl = (xs.Deserialize(gz) as KPTranslation); gz.Close(); - fs.Close(); + s.Close(); return kpTrl; } @@ -153,7 +172,7 @@ namespace KeePassLib.Translation return new Dictionary(); } -#if !KeePassLibSD +#if (!KeePassLibSD && !KeePassUAP) public void ApplyTo(Form form) { if(form == null) throw new ArgumentNullException("form"); diff --git a/src/KeePassLib2Android/Translation/KPTranslationProperties.cs b/src/KeePassLib2Android/Translation/KPTranslationProperties.cs index 914268f8..60b84db0 100644 --- a/src/KeePassLib2Android/Translation/KPTranslationProperties.cs +++ b/src/KeePassLib2Android/Translation/KPTranslationProperties.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/KeePassLib2Android/Utility/AppLogEx.cs b/src/KeePassLib2Android/Utility/AppLogEx.cs index 7074b60d..53501856 100644 --- a/src/KeePassLib2Android/Utility/AppLogEx.cs +++ b/src/KeePassLib2Android/Utility/AppLogEx.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,9 +19,9 @@ using System; using System.Collections.Generic; -using System.Text; -using System.IO; using System.Diagnostics; +using System.IO; +using System.Text; #if !KeePassLibSD using System.IO.Compression; @@ -38,8 +38,7 @@ namespace KeePassLib.Utility public static void Open(string strPrefix) { - return; // Logging is not enabled in normal builds of KeePass! - + // Logging is not enabled in normal builds of KeePass! /* AppLogEx.Close(); @@ -49,7 +48,7 @@ namespace KeePassLib.Utility try { string strDirSep = string.Empty; - strDirSep += Path.DirectorySeparatorChar; + strDirSep += UrlUtil.LocalDirSepChar; string strTemp = UrlUtil.GetTempPath(); if(!strTemp.EndsWith(strDirSep)) @@ -64,7 +63,7 @@ namespace KeePassLib.Utility strTime = strTime.Replace(':', '-'); strPath += strTime + "-" + Environment.TickCount.ToString( - CultureInfo.InvariantCulture) + ".log.gz"; + NumberFormatInfo.InvariantInfo) + ".log.gz"; FileStream fsOut = new FileStream(strPath, FileMode.Create, FileAccess.Write, FileShare.None); diff --git a/src/KeePassLib2Android/Utility/GfxUtil.cs b/src/KeePassLib2Android/Utility/GfxUtil.cs index 2b487407..2a61e370 100644 --- a/src/KeePassLib2Android/Utility/GfxUtil.cs +++ b/src/KeePassLib2Android/Utility/GfxUtil.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,28 +19,74 @@ using System; using System.Collections.Generic; -using System.Text; -using System.IO; -using System.Drawing; -using System.Drawing.Imaging; using System.Diagnostics; +using System.IO; +using System.Text; + +#if !KeePassUAP +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +#endif namespace KeePassLib.Utility { public static class GfxUtil { +#if (!KeePassLibSD && !KeePassUAP) + private sealed class GfxImage + { + public byte[] Data; + + public int Width; + public int Height; + + public GfxImage(byte[] pbData, int w, int h) + { + this.Data = pbData; + this.Width = w; + this.Height = h; + } + +#if DEBUG + // For debugger display + public override string ToString() + { + return (this.Width.ToString() + "x" + this.Height.ToString()); + } +#endif + } +#endif + +#if KeePassUAP public static Image LoadImage(byte[] pb) { if(pb == null) throw new ArgumentNullException("pb"); MemoryStream ms = new MemoryStream(pb, false); - try { return LoadImagePriv(ms); } - catch(Exception) + try { return Image.FromStream(ms); } + finally { ms.Close(); } + } +#else + public static Image LoadImage(byte[] pb) + { + if(pb == null) throw new ArgumentNullException("pb"); + +#if !KeePassLibSD + // First try to load the data as ICO and afterwards as + // normal image, because trying to load an ICO using + // the normal image loading methods can result in a + // low resolution image + try { - Image imgIco = TryLoadIco(pb); + Image imgIco = ExtractBestImageFromIco(pb); if(imgIco != null) return imgIco; - throw; } + catch(Exception) { Debug.Assert(false); } +#endif + + MemoryStream ms = new MemoryStream(pb, false); + try { return LoadImagePriv(ms); } finally { ms.Close(); } } @@ -72,7 +118,12 @@ namespace KeePassLib.Utility using(Graphics g = Graphics.FromImage(bmp)) { g.Clear(Color.Transparent); + +#if !KeePassLibSD + g.DrawImageUnscaled(imgSrc, 0, 0); +#else g.DrawImage(imgSrc, 0, 0); +#endif } return bmp; @@ -80,16 +131,299 @@ namespace KeePassLib.Utility finally { if(imgSrc != null) imgSrc.Dispose(); } } - private static Image TryLoadIco(byte[] pb) - { #if !KeePassLibSD - MemoryStream ms = new MemoryStream(pb, false); - try { return (new Icon(ms)).ToBitmap(); } - catch(Exception) { } - finally { ms.Close(); } -#endif + private static Image ExtractBestImageFromIco(byte[] pb) + { + List l = UnpackIco(pb); + if((l == null) || (l.Count == 0)) return null; - return null; + long qMax = 0; + foreach(GfxImage gi in l) + { + if(gi.Width == 0) gi.Width = 256; + if(gi.Height == 0) gi.Height = 256; + + qMax = Math.Max(qMax, (long)gi.Width * (long)gi.Height); + } + + byte[] pbHdrPng = new byte[] { + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A + }; + byte[] pbHdrJpeg = new byte[] { 0xFF, 0xD8, 0xFF }; + + Image imgBest = null; + int bppBest = -1; + + foreach(GfxImage gi in l) + { + if(((long)gi.Width * (long)gi.Height) < qMax) continue; + + byte[] pbImg = gi.Data; + Image img = null; + try + { + if((pbImg.Length > pbHdrPng.Length) && + MemUtil.ArraysEqual(pbHdrPng, + MemUtil.Mid(pbImg, 0, pbHdrPng.Length))) + img = GfxUtil.LoadImage(pbImg); + else if((pbImg.Length > pbHdrJpeg.Length) && + MemUtil.ArraysEqual(pbHdrJpeg, + MemUtil.Mid(pbImg, 0, pbHdrJpeg.Length))) + img = GfxUtil.LoadImage(pbImg); + else + { + MemoryStream ms = new MemoryStream(pb, false); + try + { + Icon ico = new Icon(ms, gi.Width, gi.Height); + img = ico.ToBitmap(); + ico.Dispose(); + } + finally { ms.Close(); } + } + } + catch(Exception) { Debug.Assert(false); } + + if(img == null) continue; + + if((img.Width < gi.Width) || (img.Height < gi.Height)) + { + Debug.Assert(false); + img.Dispose(); + continue; + } + + int bpp = GetBitsPerPixel(img.PixelFormat); + if(bpp > bppBest) + { + if(imgBest != null) imgBest.Dispose(); + + imgBest = img; + bppBest = bpp; + } + else img.Dispose(); + } + + return imgBest; } + + private static List UnpackIco(byte[] pb) + { + if(pb == null) { Debug.Assert(false); return null; } + + const int SizeICONDIR = 6; + const int SizeICONDIRENTRY = 16; + + Debug.Assert(BitConverter.ToInt32(new byte[] { 1, 2, 3, 4 }, + 0) == 0x04030201); // Little-endian + + if(pb.Length < SizeICONDIR) return null; + if(BitConverter.ToUInt16(pb, 0) != 0) return null; // Reserved, 0 + if(BitConverter.ToUInt16(pb, 2) != 1) return null; // ICO type, 1 + + int n = BitConverter.ToUInt16(pb, 4); + if(n < 0) { Debug.Assert(false); return null; } + + int cbDir = SizeICONDIR + (n * SizeICONDIRENTRY); + if(pb.Length < cbDir) return null; + + List l = new List(); + int iOffset = SizeICONDIR; + for(int i = 0; i < n; ++i) + { + int w = pb[iOffset]; + int h = pb[iOffset + 1]; + if((w < 0) || (h < 0)) { Debug.Assert(false); return null; } + + int cb = BitConverter.ToInt32(pb, iOffset + 8); + if(cb <= 0) return null; // Data must have header (even BMP) + + int p = BitConverter.ToInt32(pb, iOffset + 12); + if(p < cbDir) return null; + if((p + cb) > pb.Length) return null; + + try + { + byte[] pbImage = MemUtil.Mid(pb, p, cb); + GfxImage img = new GfxImage(pbImage, w, h); + l.Add(img); + } + catch(Exception) { Debug.Assert(false); return null; } + + iOffset += SizeICONDIRENTRY; + } + + return l; + } + + private static int GetBitsPerPixel(PixelFormat f) + { + int bpp = 0; + switch(f) + { + case PixelFormat.Format1bppIndexed: + bpp = 1; + break; + + case PixelFormat.Format4bppIndexed: + bpp = 4; + break; + + case PixelFormat.Format8bppIndexed: + bpp = 8; + break; + + case PixelFormat.Format16bppArgb1555: + case PixelFormat.Format16bppGrayScale: + case PixelFormat.Format16bppRgb555: + case PixelFormat.Format16bppRgb565: + bpp = 16; + break; + + case PixelFormat.Format24bppRgb: + bpp = 24; + break; + + case PixelFormat.Format32bppArgb: + case PixelFormat.Format32bppPArgb: + case PixelFormat.Format32bppRgb: + bpp = 32; + break; + + case PixelFormat.Format48bppRgb: + bpp = 48; + break; + + case PixelFormat.Format64bppArgb: + case PixelFormat.Format64bppPArgb: + bpp = 64; + break; + + default: + Debug.Assert(false); + break; + } + + return bpp; + } + + public static Image ScaleImage(Image img, int w, int h) + { + return ScaleImage(img, w, h, ScaleTransformFlags.None); + } + + /// + /// Resize an image. + /// + /// Image to resize. + /// Width of the returned image. + /// Height of the returned image. + /// Flags to customize scaling behavior. + /// Resized image. This object is always different + /// from (i.e. they can be + /// disposed separately). + public static Image ScaleImage(Image img, int w, int h, + ScaleTransformFlags f) + { + if(img == null) throw new ArgumentNullException("img"); + if(w < 0) throw new ArgumentOutOfRangeException("w"); + if(h < 0) throw new ArgumentOutOfRangeException("h"); + + bool bUIIcon = ((f & ScaleTransformFlags.UIIcon) != + ScaleTransformFlags.None); + + // We must return a Bitmap object for UIUtil.CreateScaledImage + Bitmap bmp = new Bitmap(w, h, PixelFormat.Format32bppArgb); + using(Graphics g = Graphics.FromImage(bmp)) + { + g.Clear(Color.Transparent); + + g.SmoothingMode = SmoothingMode.HighQuality; + g.CompositingQuality = CompositingQuality.HighQuality; + + int wSrc = img.Width; + int hSrc = img.Height; + + InterpolationMode im = InterpolationMode.HighQualityBicubic; + if((wSrc > 0) && (hSrc > 0)) + { + if(bUIIcon && ((w % wSrc) == 0) && ((h % hSrc) == 0)) + im = InterpolationMode.NearestNeighbor; + // else if((w < wSrc) && (h < hSrc)) + // im = InterpolationMode.HighQualityBilinear; + } + else { Debug.Assert(false); } + g.InterpolationMode = im; + + RectangleF rSource = new RectangleF(0.0f, 0.0f, wSrc, hSrc); + RectangleF rDest = new RectangleF(0.0f, 0.0f, w, h); + AdjustScaleRects(ref rSource, ref rDest); + + g.DrawImage(img, rDest, rSource, GraphicsUnit.Pixel); + } + + return bmp; + } + + internal static void AdjustScaleRects(ref RectangleF rSource, + ref RectangleF rDest) + { + // When enlarging images, apply a -0.5 offset to avoid + // the scaled image being cropped on the top/left side; + // when shrinking images, do not apply a -0.5 offset, + // otherwise the image is cropped on the bottom/right + // side; this applies to all interpolation modes + if(rDest.Width > rSource.Width) + rSource.X = rSource.X - 0.5f; + if(rDest.Height > rSource.Height) + rSource.Y = rSource.Y - 0.5f; + + // When shrinking, apply a +0.5 offset, such that the + // scaled image is less cropped on the bottom/right side + if(rDest.Width < rSource.Width) + rSource.X = rSource.X + 0.5f; + if(rDest.Height < rSource.Height) + rSource.Y = rSource.Y + 0.5f; + } + +#if DEBUG + public static Image ScaleTest(Image[] vIcons) + { + Bitmap bmp = new Bitmap(1024, vIcons.Length * (256 + 12), + PixelFormat.Format32bppArgb); + + using(Graphics g = Graphics.FromImage(bmp)) + { + g.Clear(Color.White); + + int[] v = new int[] { 16, 24, 32, 48, 64, 128, 256 }; + + int x; + int y = 8; + + foreach(Image imgIcon in vIcons) + { + if(imgIcon == null) { Debug.Assert(false); continue; } + + x = 128; + + foreach(int q in v) + { + Image img = ScaleImage(imgIcon, q, q, + ScaleTransformFlags.UIIcon); + g.DrawImageUnscaled(img, x, y); + img.Dispose(); + x += q + 8; + } + + y += v[v.Length - 1] + 8; + } + } + + return bmp; + } +#endif // DEBUG +#endif // !KeePassLibSD +#endif // KeePassUAP } } diff --git a/src/KeePassLib2Android/Utility/MemUtil.cs b/src/KeePassLib2Android/Utility/MemUtil.cs index af7520f2..6b22b720 100644 --- a/src/KeePassLib2Android/Utility/MemUtil.cs +++ b/src/KeePassLib2Android/Utility/MemUtil.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,15 +18,16 @@ */ using System; -using System.Text; -using System.Security.Cryptography; +using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Runtime.CompilerServices; +using System.Text; -#if !KeePassLibSD -using System.IO.Compression; -#else +#if KeePassLibSD using KeePassLibSD; +#else +using System.IO.Compression; #endif namespace KeePassLib.Utility @@ -36,6 +37,73 @@ namespace KeePassLib.Utility /// public static class MemUtil { + private static readonly uint[] m_vSBox = new uint[256] { + 0xCD2FACB3, 0xE78A7F5C, 0x6F0803FC, 0xBCF6E230, + 0x3A321712, 0x06403DB1, 0xD2F84B95, 0xDF22A6E4, + 0x07CE9E5B, 0x31788A0C, 0xF683F6F4, 0xEA061F49, + 0xFA5C2ACA, 0x4B9E494E, 0xB0AB25BA, 0x767731FC, + 0x261893A7, 0x2B09F2CE, 0x046261E4, 0x41367B4B, + 0x18A7F225, 0x8F923C0E, 0x5EF3A325, 0x28D0435E, + 0x84C22919, 0xED66873C, 0x8CEDE444, 0x7FC47C24, + 0xFCFC6BA3, 0x676F928D, 0xB4147187, 0xD8FB126E, + 0x7D798D17, 0xFF82E424, 0x1712FA5B, 0xABB09DD5, + 0x8156BA63, 0x84E4D969, 0xC937FB9A, 0x2F1E5BFC, + 0x178ECA11, 0x0E71CD5F, 0x52AAC6F4, 0x71EEFC8F, + 0x7090D749, 0x21CACA31, 0x92996378, 0x0939A8A8, + 0xE9EE1934, 0xD2718616, 0xF2500543, 0xB911873C, + 0xD3CB3EEC, 0x2BA0DBEB, 0xB42D0A27, 0xECE67C0F, + 0x302925F0, 0x6114F839, 0xD39E6307, 0xE28970D6, + 0xEB982F99, 0x941B4CDF, 0xC540E550, 0x8124FC45, + 0x98B025C7, 0xE2BF90EA, 0x4F57C976, 0xCF546FE4, + 0x59566DC8, 0xE3F4360D, 0xF5F9D231, 0xD6180B22, + 0xB54E088A, 0xB5DFE6A6, 0x3637A36F, 0x056E9284, + 0xAFF8FBC5, 0x19E01648, 0x8611F043, 0xDAE44337, + 0xF61B6A1C, 0x257ACD9E, 0xDD35F507, 0xEF05CAFA, + 0x05EB4A83, 0xFC25CA92, 0x0A4728E6, 0x9CF150EF, + 0xAEEF67DE, 0xA9472337, 0x57C81EFE, 0x3E5E009F, + 0x02CB03BB, 0x2BA85674, 0xF21DC251, 0x78C34A34, + 0xABB1F5BF, 0xB95A2FBD, 0x1FB47777, 0x9A96E8AC, + 0x5D2D2838, 0x55AAC92A, 0x99EE324E, 0x10F6214B, + 0x58ABDFB1, 0x2008794D, 0xBEC880F0, 0xE75E5341, + 0x88015C34, 0x352D8FBF, 0x622B7F6C, 0xF5C59EA2, + 0x1F759D8E, 0xADE56159, 0xCC7B4C25, 0x5B8BC48C, + 0xB6BD15AF, 0x3C5B5110, 0xE74A7C3D, 0xEE613161, + 0x156A1C67, 0x72C06817, 0xEA0A6F69, 0x4CECF993, + 0xCA9D554C, 0x8E20361F, 0x42D396B9, 0x595DE578, + 0x749D7955, 0xFD1BA5FD, 0x81FC160E, 0xDB97E28C, + 0x7CF148F7, 0x0B0B3CF5, 0x534DE605, 0x46421066, + 0xD4B68DD1, 0x9E479CE6, 0xAE667A9D, 0xBC082082, + 0xB06DD6EF, 0x20F0F23F, 0xB99E1551, 0xF47A2E3A, + 0x71DA50C6, 0x67B65779, 0x2A8CB376, 0x1EA71EEE, + 0x29ABCD50, 0xB6EB0C6B, 0x23C10511, 0x6F3F2144, + 0x6AF23012, 0xF696BD9E, 0xB94099D8, 0xAD5A9C81, + 0x7A0794FA, 0x7EDF59D6, 0x1E72E574, 0x8561913C, + 0x4E4D568F, 0xEECB9928, 0x9C124D2E, 0x0848B82C, + 0xF1CA395F, 0x9DAF43DC, 0xF77EC323, 0x394E9B59, + 0x7E200946, 0x8B811D68, 0x16DA3305, 0xAB8DE2C3, + 0xE6C53B64, 0x98C2D321, 0x88A97D81, 0xA7106419, + 0x8E52F7BF, 0x8ED262AF, 0x7CCA974E, 0xF0933241, + 0x040DD437, 0xE143B3D4, 0x3019F56F, 0xB741521D, + 0xF1745362, 0x4C435F9F, 0xB4214D0D, 0x0B0C348B, + 0x5051D189, 0x4C30447E, 0x7393D722, 0x95CEDD0B, + 0xDD994E80, 0xC3D22ED9, 0x739CD900, 0x131EB9C4, + 0xEF1062B2, 0x4F0DE436, 0x52920073, 0x9A7F3D80, + 0x896E7B1B, 0x2C8BBE5A, 0xBD304F8A, 0xA993E22C, + 0x134C41A0, 0xFA989E00, 0x39CE9726, 0xFB89FCCF, + 0xE8FBAC97, 0xD4063FFC, 0x935A2B5A, 0x44C8EE83, + 0xCB2BC7B6, 0x02989E92, 0x75478BEA, 0x144378D0, + 0xD853C087, 0x8897A34E, 0xDD23629D, 0xBDE2A2A2, + 0x581D8ECC, 0x5DA8AEE8, 0xFF8AAFD0, 0xBA2BCF6E, + 0x4BD98DAC, 0xF2EDB9E4, 0xFA2DC868, 0x47E84661, + 0xECEB1C7D, 0x41705CA4, 0x5982E4D4, 0xEB5204A1, + 0xD196CAFB, 0x6414804D, 0x3ABD4B46, 0x8B494C26, + 0xB432D52B, 0x39C5356B, 0x6EC80BF7, 0x71BE5483, + 0xCEC4A509, 0xE9411D61, 0x52F341E5, 0xD2E6197B, + 0x4F02826C, 0xA9E48838, 0xD1F8F247, 0xE4957FB3, + 0x586CCA99, 0x9A8B6A5B, 0x4998FBEA, 0xF762BE4C, + 0x90DFE33C, 0x9731511E, 0x88C6A82F, 0xDD65A4D4 + }; + /// /// Convert a hexadecimal string to a byte array. The input string must be /// even (i.e. its length is a multiple of 2). @@ -119,27 +187,84 @@ namespace KeePassLib.Utility return sb.ToString(); } + /// + /// Decode Base32 strings according to RFC 4648. + /// + public static byte[] ParseBase32(string str) + { + if((str == null) || ((str.Length % 8) != 0)) + { + Debug.Assert(false); + return null; + } + + ulong uMaxBits = (ulong)str.Length * 5UL; + List l = new List((int)(uMaxBits / 8UL) + 1); + Debug.Assert(l.Count == 0); + + for(int i = 0; i < str.Length; i += 8) + { + ulong u = 0; + int nBits = 0; + + for(int j = 0; j < 8; ++j) + { + char ch = str[i + j]; + if(ch == '=') break; + + ulong uValue; + if((ch >= 'A') && (ch <= 'Z')) + uValue = (ulong)(ch - 'A'); + else if((ch >= 'a') && (ch <= 'z')) + uValue = (ulong)(ch - 'a'); + else if((ch >= '2') && (ch <= '7')) + uValue = (ulong)(ch - '2') + 26UL; + else { Debug.Assert(false); return null; } + + u <<= 5; + u += uValue; + nBits += 5; + } + + int nBitsTooMany = (nBits % 8); + u >>= nBitsTooMany; + nBits -= nBitsTooMany; + Debug.Assert((nBits % 8) == 0); + + int idxNewBytes = l.Count; + while(nBits > 0) + { + l.Add((byte)(u & 0xFF)); + u >>= 8; + nBits -= 8; + } + l.Reverse(idxNewBytes, l.Count - idxNewBytes); + } + + return l.ToArray(); + } + /// /// Set all bytes in a byte array to zero. /// - /// Input array. All bytes of this array will be set - /// to zero. + /// Input array. All bytes of this array + /// will be set to zero. +#if KeePassLibSD + [MethodImpl(MethodImplOptions.NoInlining)] +#else + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] +#endif public static void ZeroByteArray(byte[] pbArray) { - Debug.Assert(pbArray != null); if(pbArray == null) throw new ArgumentNullException("pbArray"); - - // for(int i = 0; i < pbArray.Length; ++i) - // pbArray[i] = 0; + Debug.Assert(pbArray != null); + if(pbArray == null) throw new ArgumentNullException("pbArray"); Array.Clear(pbArray, 0, pbArray.Length); } /// - /// Convert 2 bytes to a 16-bit unsigned integer using Little-Endian - /// encoding. + /// Convert 2 bytes to a 16-bit unsigned integer (little-endian). /// - /// Input bytes. Array must contain at least 2 bytes. - /// 16-bit unsigned integer. public static ushort BytesToUInt16(byte[] pb) { Debug.Assert((pb != null) && (pb.Length == 2)); @@ -150,44 +275,35 @@ namespace KeePassLib.Utility } /// - /// Convert 4 bytes to a 32-bit unsigned integer using Little-Endian - /// encoding. + /// Convert 4 bytes to a 32-bit unsigned integer (little-endian). /// - /// Input bytes. - /// 32-bit unsigned integer. public static uint BytesToUInt32(byte[] pb) { Debug.Assert((pb != null) && (pb.Length == 4)); if(pb == null) throw new ArgumentNullException("pb"); - if(pb.Length != 4) throw new ArgumentException("Input array must contain 4 bytes!"); + if(pb.Length != 4) throw new ArgumentException(); - return (uint)pb[0] | ((uint)pb[1] << 8) | ((uint)pb[2] << 16) | - ((uint)pb[3] << 24); + return ((uint)pb[0] | ((uint)pb[1] << 8) | ((uint)pb[2] << 16) | + ((uint)pb[3] << 24)); } /// - /// Convert 8 bytes to a 64-bit unsigned integer using Little-Endian - /// encoding. + /// Convert 8 bytes to a 64-bit unsigned integer (little-endian). /// - /// Input bytes. - /// 64-bit unsigned integer. public static ulong BytesToUInt64(byte[] pb) { Debug.Assert((pb != null) && (pb.Length == 8)); if(pb == null) throw new ArgumentNullException("pb"); if(pb.Length != 8) throw new ArgumentException(); - return (ulong)pb[0] | ((ulong)pb[1] << 8) | ((ulong)pb[2] << 16) | + return ((ulong)pb[0] | ((ulong)pb[1] << 8) | ((ulong)pb[2] << 16) | ((ulong)pb[3] << 24) | ((ulong)pb[4] << 32) | ((ulong)pb[5] << 40) | - ((ulong)pb[6] << 48) | ((ulong)pb[7] << 56); + ((ulong)pb[6] << 48) | ((ulong)pb[7] << 56)); } /// - /// Convert a 16-bit unsigned integer to 2 bytes using Little-Endian - /// encoding. + /// Convert a 16-bit unsigned integer to 2 bytes (little-endian). /// - /// 16-bit input word. - /// Two bytes representing the 16-bit value. public static byte[] UInt16ToBytes(ushort uValue) { byte[] pb = new byte[2]; @@ -202,11 +318,8 @@ namespace KeePassLib.Utility } /// - /// Convert a 32-bit unsigned integer to 4 bytes using Little-Endian - /// encoding. + /// Convert a 32-bit unsigned integer to 4 bytes (little-endian). /// - /// 32-bit input word. - /// Four bytes representing the 32-bit value. public static byte[] UInt32ToBytes(uint uValue) { byte[] pb = new byte[4]; @@ -223,11 +336,8 @@ namespace KeePassLib.Utility } /// - /// Convert a 64-bit unsigned integer to 8 bytes using Little-Endian - /// encoding. + /// Convert a 64-bit unsigned integer to 8 bytes (little-endian). /// - /// 64-bit input word. - /// Eight bytes representing the 64-bit value. public static byte[] UInt64ToBytes(ulong uValue) { byte[] pb = new byte[8]; @@ -277,6 +387,31 @@ namespace KeePassLib.Utility pbBuffer[nBufferOffset + i] ^= pbSource[nSourceOffset + i]; } + /// + /// Fast hash that can be used e.g. for hash tables. + /// The algorithm might change in the future; do not store + /// the hashes for later use. + /// + public static uint Hash32(byte[] v, int iStart, int iLength) + { + uint u = 0x326F637B; + + if(v == null) { Debug.Assert(false); return u; } + if(iStart < 0) { Debug.Assert(false); return u; } + if(iLength < 0) { Debug.Assert(false); return u; } + + int m = iStart + iLength; + if(m > v.Length) { Debug.Assert(false); return u; } + + for(int i = iStart; i < m; ++i) + { + u ^= m_vSBox[v[i]]; + u *= 3; + } + + return u; + } + public static void CopyStream(Stream sSource, Stream sTarget) { Debug.Assert((sSource != null) && (sTarget != null)); @@ -336,15 +471,21 @@ namespace KeePassLib.Utility if(pbData == null) throw new ArgumentNullException("pbData"); if(pbData.Length == 0) return pbData; - MemoryStream msCompressed = new MemoryStream(); - GZipStream gz = new GZipStream(msCompressed, CompressionMode.Compress); - MemoryStream msSource = new MemoryStream(pbData, false); - MemUtil.CopyStream(msSource, gz); - gz.Close(); - msSource.Close(); + byte[] pbCompressed; + using(MemoryStream msSource = new MemoryStream(pbData, false)) + { + using(MemoryStream msCompressed = new MemoryStream()) + { + using(GZipStream gz = new GZipStream(msCompressed, + CompressionMode.Compress)) + { + MemUtil.CopyStream(msSource, gz); + } + + pbCompressed = msCompressed.ToArray(); + } + } - byte[] pbCompressed = msCompressed.ToArray(); - msCompressed.Close(); return pbCompressed; } @@ -353,15 +494,21 @@ namespace KeePassLib.Utility if(pbCompressed == null) throw new ArgumentNullException("pbCompressed"); if(pbCompressed.Length == 0) return pbCompressed; - MemoryStream msCompressed = new MemoryStream(pbCompressed, false); - GZipStream gz = new GZipStream(msCompressed, CompressionMode.Decompress); - MemoryStream msData = new MemoryStream(); - MemUtil.CopyStream(gz, msData); - gz.Close(); - msCompressed.Close(); + byte[] pbData; + using(MemoryStream msData = new MemoryStream()) + { + using(MemoryStream msCompressed = new MemoryStream(pbCompressed, false)) + { + using(GZipStream gz = new GZipStream(msCompressed, + CompressionMode.Decompress)) + { + MemUtil.CopyStream(gz, msData); + } + } + + pbData = msData.ToArray(); + } - byte[] pbData = msData.ToArray(); - msData.Close(); return pbData; } @@ -394,11 +541,81 @@ namespace KeePassLib.Utility if(v == null) throw new ArgumentNullException("v"); if(iOffset < 0) throw new ArgumentOutOfRangeException("iOffset"); if(iLength < 0) throw new ArgumentOutOfRangeException("iLength"); - if(iOffset + iLength > v.Length) throw new ArgumentException(); + if((iOffset + iLength) > v.Length) throw new ArgumentException(); T[] r = new T[iLength]; Array.Copy(v, iOffset, r, 0, iLength); return r; } + + public static IEnumerable Union(IEnumerable a, IEnumerable b, + IEqualityComparer cmp) + { + if(a == null) throw new ArgumentNullException("a"); + if(b == null) throw new ArgumentNullException("b"); + + Dictionary d = ((cmp != null) ? + (new Dictionary(cmp)) : (new Dictionary())); + + foreach(T ta in a) + { + if(d.ContainsKey(ta)) continue; // Prevent duplicates + + d[ta] = true; + yield return ta; + } + + foreach(T tb in b) + { + if(d.ContainsKey(tb)) continue; // Prevent duplicates + + d[tb] = true; + yield return tb; + } + + yield break; + } + + public static IEnumerable Intersect(IEnumerable a, IEnumerable b, + IEqualityComparer cmp) + { + if(a == null) throw new ArgumentNullException("a"); + if(b == null) throw new ArgumentNullException("b"); + + Dictionary d = ((cmp != null) ? + (new Dictionary(cmp)) : (new Dictionary())); + + foreach(T tb in b) { d[tb] = true; } + + foreach(T ta in a) + { + if(d.Remove(ta)) // Prevent duplicates + yield return ta; + } + + yield break; + } + + public static IEnumerable Except(IEnumerable a, IEnumerable b, + IEqualityComparer cmp) + { + if(a == null) throw new ArgumentNullException("a"); + if(b == null) throw new ArgumentNullException("b"); + + Dictionary d = ((cmp != null) ? + (new Dictionary(cmp)) : (new Dictionary())); + + foreach(T tb in b) { d[tb] = true; } + + foreach(T ta in a) + { + if(d.ContainsKey(ta)) continue; + + d[ta] = true; // Prevent duplicates + yield return ta; + } + + yield break; + } } } diff --git a/src/KeePassLib2Android/Utility/MessageService.cs b/src/KeePassLib2Android/Utility/MessageService.cs index 22ee8355..3d059645 100644 --- a/src/KeePassLib2Android/Utility/MessageService.cs +++ b/src/KeePassLib2Android/Utility/MessageService.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,9 +20,12 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; -using System.Text; -using System.Windows.Forms; using System.Diagnostics; +using System.Text; + +#if !KeePassUAP +using System.Windows.Forms; +#endif using KeePassLib.Resources; using KeePassLib.Serialization; @@ -94,7 +97,9 @@ namespace KeePassLib.Utility get { return m_uCurrentMessageCount; } } +#if !KeePassUAP public static event EventHandler MessageShowing; +#endif private static string ObjectsToMessage(object[] vLines) { @@ -158,7 +163,7 @@ namespace KeePassLib.Utility return sbText.ToString(); } -#if !KeePassLibSD +#if (!KeePassLibSD && !KeePassUAP) internal static Form GetTopForm() { FormCollection fc = Application.OpenForms; @@ -168,7 +173,8 @@ namespace KeePassLib.Utility } #endif - private static DialogResult SafeShowMessageBox(string strText, string strTitle, +#if !KeePassUAP + internal static DialogResult SafeShowMessageBox(string strText, string strTitle, MessageBoxButtons mb, MessageBoxIcon mi, MessageBoxDefaultButton mdb) { #if KeePassLibSD @@ -283,11 +289,13 @@ namespace KeePassLib.Utility try { -#if !KeePassLibSD - Clipboard.Clear(); - Clipboard.SetText(ObjectsToMessage(vLines, true)); + string strDetails = ObjectsToMessage(vLines, true); + +#if KeePassLibSD + Clipboard.SetDataObject(strDetails); #else - Clipboard.SetDataObject(ObjectsToMessage(vLines, true)); + Clipboard.Clear(); + Clipboard.SetText(strDetails); #endif } catch(Exception) { Debug.Assert(false); } @@ -321,7 +329,8 @@ namespace KeePassLib.Utility return dr; } - public static bool AskYesNo(string strText, string strTitle, bool bDefaultToYes) + public static bool AskYesNo(string strText, string strTitle, bool bDefaultToYes, + MessageBoxIcon mbi) { ++m_uCurrentMessageCount; @@ -330,24 +339,29 @@ namespace KeePassLib.Utility if(MessageService.MessageShowing != null) MessageService.MessageShowing(null, new MessageServiceEventArgs( - strTitleEx, strTextEx, MessageBoxButtons.YesNo, m_mbiQuestion)); + strTitleEx, strTextEx, MessageBoxButtons.YesNo, mbi)); DialogResult dr = SafeShowMessageBox(strTextEx, strTitleEx, - MessageBoxButtons.YesNo, m_mbiQuestion, bDefaultToYes ? + MessageBoxButtons.YesNo, mbi, bDefaultToYes ? MessageBoxDefaultButton.Button1 : MessageBoxDefaultButton.Button2); --m_uCurrentMessageCount; return (dr == DialogResult.Yes); } + public static bool AskYesNo(string strText, string strTitle, bool bDefaultToYes) + { + return AskYesNo(strText, strTitle, bDefaultToYes, m_mbiQuestion); + } + public static bool AskYesNo(string strText, string strTitle) { - return AskYesNo(strText, strTitle, true); + return AskYesNo(strText, strTitle, true, m_mbiQuestion); } public static bool AskYesNo(string strText) { - return AskYesNo(strText, null, true); + return AskYesNo(strText, null, true, m_mbiQuestion); } public static void ShowLoadWarning(string strFilePath, Exception ex) @@ -358,21 +372,7 @@ namespace KeePassLib.Utility public static void ShowLoadWarning(string strFilePath, Exception ex, bool bFullException) { - string str = string.Empty; - - if((strFilePath != null) && (strFilePath.Length > 0)) - str += strFilePath + MessageService.NewParagraph; - - str += KLRes.FileLoadFailed; - - if((ex != null) && (ex.Message != null) && (ex.Message.Length > 0)) - { - str += MessageService.NewParagraph; - if(!bFullException) str += ex.Message; - else str += ObjectsToMessage(new object[] { ex }, true); - } - - ShowWarning(str); + ShowWarning(GetLoadWarningMessage(strFilePath, ex, bFullException)); } public static void ShowLoadWarning(IOConnectionInfo ioConnection, Exception ex) @@ -392,18 +392,7 @@ namespace KeePassLib.Utility return; } - string str = string.Empty; - if((strFilePath != null) && (strFilePath.Length > 0)) - str += strFilePath + MessageService.NewParagraph; - - str += KLRes.FileSaveFailed; - - if((ex != null) && (ex.Message != null) && (ex.Message.Length > 0)) - str += MessageService.NewParagraph + ex.Message; - - if(bCorruptionWarning) - str += MessageService.NewParagraph + KLRes.FileSaveCorruptionWarning; - + string str = GetSaveWarningMessage(strFilePath, ex, bCorruptionWarning); ShowWarning(str); } @@ -414,6 +403,45 @@ namespace KeePassLib.Utility ShowSaveWarning(ioConnection.GetDisplayName(), ex, bCorruptionWarning); else ShowWarning(ex); } +#endif // !KeePassUAP + + internal static string GetLoadWarningMessage(string strFilePath, + Exception ex, bool bFullException) + { + string str = string.Empty; + + if(!string.IsNullOrEmpty(strFilePath)) + str += strFilePath + MessageService.NewParagraph; + + str += KLRes.FileLoadFailed; + + if((ex != null) && !string.IsNullOrEmpty(ex.Message)) + { + str += MessageService.NewParagraph; + if(!bFullException) str += ex.Message; + else str += ObjectsToMessage(new object[] { ex }, true); + } + + return str; + } + + internal static string GetSaveWarningMessage(string strFilePath, + Exception ex, bool bCorruptionWarning) + { + string str = string.Empty; + if(!string.IsNullOrEmpty(strFilePath)) + str += strFilePath + MessageService.NewParagraph; + + str += KLRes.FileSaveFailed; + + if((ex != null) && !string.IsNullOrEmpty(ex.Message)) + str += MessageService.NewParagraph + ex.Message; + + if(bCorruptionWarning) + str += MessageService.NewParagraph + KLRes.FileSaveCorruptionWarning; + + return str; + } public static void ExternalIncrementMessageCount() { diff --git a/src/KeePassLib2Android/Utility/StrUtil.cs b/src/KeePassLib2Android/Utility/StrUtil.cs index 94e590aa..39ef5282 100644 --- a/src/KeePassLib2Android/Utility/StrUtil.cs +++ b/src/KeePassLib2Android/Utility/StrUtil.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,16 +21,20 @@ using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; -using System.Text; -using System.Drawing; +using System.Globalization; using System.IO; +using System.Text; using System.Text.RegularExpressions; + +#if !KeePassUAP +using System.Drawing; using System.Security.Cryptography; +#endif using KeePassLib.Collections; +using KeePassLib.Cryptography.PasswordGenerator; using KeePassLib.Native; using KeePassLib.Security; -using KeePassLib.Resources; namespace KeePassLib.Utility { @@ -212,41 +216,47 @@ namespace KeePassLib.Utility { get { - if(m_lEncs == null) - { - m_lEncs = new List(); + if(m_lEncs != null) return m_lEncs; - m_lEncs.Add(new StrEncodingInfo(StrEncodingType.Default, -#if !KeePassLibSD - Encoding.Default.EncodingName, + List l = new List(); + + l.Add(new StrEncodingInfo(StrEncodingType.Default, +#if KeePassUAP + "Unicode (UTF-8)", StrUtil.Utf8, 1, new byte[] { 0xEF, 0xBB, 0xBF })); #else - Encoding.Default.WebName, -#endif - Encoding.Default, - (uint)Encoding.Default.GetBytes("a").Length, null)); - m_lEncs.Add(new StrEncodingInfo(StrEncodingType.Ascii, - "ASCII", Encoding.ASCII, 1, null)); - m_lEncs.Add(new StrEncodingInfo(StrEncodingType.Utf7, - "Unicode (UTF-7)", Encoding.UTF7, 1, null)); - m_lEncs.Add(new StrEncodingInfo(StrEncodingType.Utf8, - "Unicode (UTF-8)", StrUtil.Utf8, 1, new byte[] { 0xEF, 0xBB, 0xBF })); - m_lEncs.Add(new StrEncodingInfo(StrEncodingType.Utf16LE, - "Unicode (UTF-16 LE)", new UnicodeEncoding(false, false), - 2, new byte[] { 0xFF, 0xFE })); - m_lEncs.Add(new StrEncodingInfo(StrEncodingType.Utf16BE, - "Unicode (UTF-16 BE)", new UnicodeEncoding(true, false), - 2, new byte[] { 0xFE, 0xFF })); #if !KeePassLibSD - m_lEncs.Add(new StrEncodingInfo(StrEncodingType.Utf32LE, - "Unicode (UTF-32 LE)", new UTF32Encoding(false, false), - 4, new byte[] { 0xFF, 0xFE, 0x0, 0x0 })); - m_lEncs.Add(new StrEncodingInfo(StrEncodingType.Utf32BE, - "Unicode (UTF-32 BE)", new UTF32Encoding(true, false), - 4, new byte[] { 0x0, 0x0, 0xFE, 0xFF })); + Encoding.Default.EncodingName, +#else + Encoding.Default.WebName, +#endif + Encoding.Default, + (uint)Encoding.Default.GetBytes("a").Length, null)); #endif - } - return m_lEncs; + l.Add(new StrEncodingInfo(StrEncodingType.Ascii, + "ASCII", Encoding.ASCII, 1, null)); + l.Add(new StrEncodingInfo(StrEncodingType.Utf7, + "Unicode (UTF-7)", Encoding.UTF7, 1, null)); + l.Add(new StrEncodingInfo(StrEncodingType.Utf8, + "Unicode (UTF-8)", StrUtil.Utf8, 1, new byte[] { 0xEF, 0xBB, 0xBF })); + l.Add(new StrEncodingInfo(StrEncodingType.Utf16LE, + "Unicode (UTF-16 LE)", new UnicodeEncoding(false, false), + 2, new byte[] { 0xFF, 0xFE })); + l.Add(new StrEncodingInfo(StrEncodingType.Utf16BE, + "Unicode (UTF-16 BE)", new UnicodeEncoding(true, false), + 2, new byte[] { 0xFE, 0xFF })); + +#if !KeePassLibSD + l.Add(new StrEncodingInfo(StrEncodingType.Utf32LE, + "Unicode (UTF-32 LE)", new UTF32Encoding(false, false), + 4, new byte[] { 0xFF, 0xFE, 0x0, 0x0 })); + l.Add(new StrEncodingInfo(StrEncodingType.Utf32BE, + "Unicode (UTF-32 BE)", new UTF32Encoding(true, false), + 4, new byte[] { 0x0, 0x0, 0xFE, 0xFF })); +#endif + + m_lEncs = l; + return l; } } @@ -274,16 +284,21 @@ namespace KeePassLib.Utility // { // char ch = str[i]; // if((int)ch >= 256) - // { - // sbEncoded.Append("\\u"); - // sbEncoded.Append((int)ch); - // sbEncoded.Append('?'); - // } + // sbEncoded.Append(StrUtil.RtfEncodeChar(ch)); // else sbEncoded.Append(ch); // } // return sbEncoded.ToString(); // } + public static string RtfEncodeChar(char ch) + { + // Unicode character values must be encoded using + // 16-bit numbers (decimal); Unicode values greater + // than 32767 must be expressed as negative numbers + short sh = (short)ch; + return ("\\u" + sh.ToString(NumberFormatInfo.InvariantInfo) + "?"); + } + /// /// Convert a string into a valid HTML sequence representing that string. /// @@ -300,7 +315,7 @@ namespace KeePassLib.Utility str = str.Replace("\'", @"'"); str = NormalizeNewLines(str, false); - str = str.Replace("\n", @"
"); + str = str.Replace("\n", @"
" + MessageService.NewLine); return str; } @@ -343,9 +358,9 @@ namespace KeePassLib.Utility } /// - /// Split up a command-line into application and argument. + /// Split up a command line into application and argument. /// - /// Command-line to split. + /// Command line to split. /// Application path. /// Arguments. public static void SplitCommandLine(string strCmdLine, out string strApp, out string strArgs) @@ -482,8 +497,10 @@ namespace KeePassLib.Utility if(excp.StackTrace != null) strText += excp.StackTrace + MessageService.NewLine; #if !KeePassLibSD +#if !KeePassUAP if(excp.TargetSite != null) strText += excp.TargetSite.ToString() + MessageService.NewLine; +#endif if(excp.Data != null) { @@ -506,8 +523,10 @@ namespace KeePassLib.Utility if(excp.InnerException.StackTrace != null) strText += excp.InnerException.StackTrace + MessageService.NewLine; #if !KeePassLibSD +#if !KeePassUAP if(excp.InnerException.TargetSite != null) strText += excp.InnerException.TargetSite.ToString(); +#endif if(excp.InnerException.Data != null) { @@ -538,7 +557,25 @@ namespace KeePassLib.Utility return int.TryParse(str, out n); #else try { n = int.Parse(str); return true; } - catch(Exception) { n = 0; return false; } + catch(Exception) { n = 0; } + return false; +#endif + } + + public static bool TryParseIntInvariant(string str, out int n) + { +#if !KeePassLibSD + return int.TryParse(str, NumberStyles.Integer, + NumberFormatInfo.InvariantInfo, out n); +#else + try + { + n = int.Parse(str, NumberStyles.Integer, + NumberFormatInfo.InvariantInfo); + return true; + } + catch(Exception) { n = 0; } + return false; #endif } @@ -548,7 +585,25 @@ namespace KeePassLib.Utility return uint.TryParse(str, out u); #else try { u = uint.Parse(str); return true; } - catch(Exception) { u = 0; return false; } + catch(Exception) { u = 0; } + return false; +#endif + } + + public static bool TryParseUIntInvariant(string str, out uint u) + { +#if !KeePassLibSD + return uint.TryParse(str, NumberStyles.Integer, + NumberFormatInfo.InvariantInfo, out u); +#else + try + { + u = uint.Parse(str, NumberStyles.Integer, + NumberFormatInfo.InvariantInfo); + return true; + } + catch(Exception) { u = 0; } + return false; #endif } @@ -558,7 +613,25 @@ namespace KeePassLib.Utility return long.TryParse(str, out n); #else try { n = long.Parse(str); return true; } - catch(Exception) { n = 0; return false; } + catch(Exception) { n = 0; } + return false; +#endif + } + + public static bool TryParseLongInvariant(string str, out long n) + { +#if !KeePassLibSD + return long.TryParse(str, NumberStyles.Integer, + NumberFormatInfo.InvariantInfo, out n); +#else + try + { + n = long.Parse(str, NumberStyles.Integer, + NumberFormatInfo.InvariantInfo); + return true; + } + catch(Exception) { n = 0; } + return false; #endif } @@ -568,7 +641,25 @@ namespace KeePassLib.Utility return ulong.TryParse(str, out u); #else try { u = ulong.Parse(str); return true; } - catch(Exception) { u = 0; return false; } + catch(Exception) { u = 0; } + return false; +#endif + } + + public static bool TryParseULongInvariant(string str, out ulong u) + { +#if !KeePassLibSD + return ulong.TryParse(str, NumberStyles.Integer, + NumberFormatInfo.InvariantInfo, out u); +#else + try + { + u = ulong.Parse(str, NumberStyles.Integer, + NumberFormatInfo.InvariantInfo); + return true; + } + catch(Exception) { u = 0; } + return false; #endif } @@ -636,25 +727,40 @@ namespace KeePassLib.Utility Debug.Assert(strText != null); // No throw if(string.IsNullOrEmpty(strText)) return strText; - char[] vChars = strText.ToCharArray(); - StringBuilder sb = new StringBuilder(strText.Length, strText.Length); - char ch; + int nLength = strText.Length; + StringBuilder sb = new StringBuilder(nLength); - for(int i = 0; i < vChars.Length; ++i) + for(int i = 0; i < nLength; ++i) { - ch = vChars[i]; + char ch = strText[i]; - if(((ch >= 0x20) && (ch <= 0xD7FF)) || - (ch == 0x9) || (ch == 0xA) || (ch == 0xD) || - ((ch >= 0xE000) && (ch <= 0xFFFD))) + if(((ch >= '\u0020') && (ch <= '\uD7FF')) || + (ch == '\u0009') || (ch == '\u000A') || (ch == '\u000D') || + ((ch >= '\uE000') && (ch <= '\uFFFD'))) sb.Append(ch); - // Range ((ch >= 0x10000) && (ch <= 0x10FFFF)) excluded + else if((ch >= '\uD800') && (ch <= '\uDBFF')) // High surrogate + { + if((i + 1) < nLength) + { + char chLow = strText[i + 1]; + if((chLow >= '\uDC00') && (chLow <= '\uDFFF')) // Low sur. + { + sb.Append(ch); + sb.Append(chLow); + ++i; + } + else { Debug.Assert(false); } // Low sur. invalid + } + else { Debug.Assert(false); } // Low sur. missing + } + + Debug.Assert((ch < '\uDC00') || (ch > '\uDFFF')); // Lonely low sur. } return sb.ToString(); } - private static Regex m_rxNaturalSplit = null; + /* private static Regex g_rxNaturalSplit = null; public static int CompareNaturally(string strX, string strY) { Debug.Assert(strX != null); @@ -665,34 +771,31 @@ namespace KeePassLib.Utility if(NativeMethods.SupportsStrCmpNaturally) return NativeMethods.StrCmpNaturally(strX, strY); - strX = strX.ToLower(); // Case-insensitive comparison - strY = strY.ToLower(); + if(g_rxNaturalSplit == null) + g_rxNaturalSplit = new Regex(@"([0-9]+)", RegexOptions.Compiled); - if(m_rxNaturalSplit == null) - m_rxNaturalSplit = new Regex(@"([0-9]+)", RegexOptions.Compiled); + string[] vPartsX = g_rxNaturalSplit.Split(strX); + string[] vPartsY = g_rxNaturalSplit.Split(strY); - string[] vPartsX = m_rxNaturalSplit.Split(strX); - string[] vPartsY = m_rxNaturalSplit.Split(strY); - - for(int i = 0; i < Math.Min(vPartsX.Length, vPartsY.Length); ++i) + int n = Math.Min(vPartsX.Length, vPartsY.Length); + for(int i = 0; i < n; ++i) { string strPartX = vPartsX[i], strPartY = vPartsY[i]; int iPartCompare; #if KeePassLibSD - ulong uX = 0, uY = 0; try { - uX = ulong.Parse(strPartX); - uY = ulong.Parse(strPartY); + ulong uX = ulong.Parse(strPartX); + ulong uY = ulong.Parse(strPartY); iPartCompare = uX.CompareTo(uY); } - catch(Exception) { iPartCompare = strPartX.CompareTo(strPartY); } + catch(Exception) { iPartCompare = string.Compare(strPartX, strPartY, true); } #else ulong uX, uY; if(ulong.TryParse(strPartX, out uX) && ulong.TryParse(strPartY, out uY)) iPartCompare = uX.CompareTo(uY); - else iPartCompare = strPartX.CompareTo(strPartY); + else iPartCompare = string.Compare(strPartX, strPartY, true); #endif if(iPartCompare != 0) return iPartCompare; @@ -701,6 +804,106 @@ namespace KeePassLib.Utility if(vPartsX.Length == vPartsY.Length) return 0; if(vPartsX.Length < vPartsY.Length) return -1; return 1; + } */ + + public static int CompareNaturally(string strX, string strY) + { + Debug.Assert(strX != null); + if(strX == null) throw new ArgumentNullException("strX"); + Debug.Assert(strY != null); + if(strY == null) throw new ArgumentNullException("strY"); + + if(NativeMethods.SupportsStrCmpNaturally) + return NativeMethods.StrCmpNaturally(strX, strY); + + int cX = strX.Length; + int cY = strY.Length; + if(cX == 0) return ((cY == 0) ? 0 : -1); + if(cY == 0) return 1; + + char chFirstX = strX[0]; + char chFirstY = strY[0]; + bool bExpNum = ((chFirstX >= '0') && (chFirstX <= '9')); + bool bExpNumY = ((chFirstY >= '0') && (chFirstY <= '9')); + if(bExpNum != bExpNumY) return string.Compare(strX, strY, true); + + int pX = 0; + int pY = 0; + while((pX < cX) && (pY < cY)) + { + Debug.Assert(((strX[pX] >= '0') && (strX[pX] <= '9')) == bExpNum); + Debug.Assert(((strY[pY] >= '0') && (strY[pY] <= '9')) == bExpNum); + + int pExclX = pX + 1; + while(pExclX < cX) + { + char ch = strX[pExclX]; + bool bChNum = ((ch >= '0') && (ch <= '9')); + if(bChNum != bExpNum) break; + ++pExclX; + } + + int pExclY = pY + 1; + while(pExclY < cY) + { + char ch = strY[pExclY]; + bool bChNum = ((ch >= '0') && (ch <= '9')); + if(bChNum != bExpNum) break; + ++pExclY; + } + + string strPartX = strX.Substring(pX, pExclX - pX); + string strPartY = strY.Substring(pY, pExclY - pY); + + bool bStrCmp = true; + if(bExpNum) + { + // 2^64 - 1 = 18446744073709551615 has length 20 + if((strPartX.Length <= 19) && (strPartY.Length <= 19)) + { + ulong uX, uY; + if(ulong.TryParse(strPartX, out uX) && ulong.TryParse(strPartY, out uY)) + { + if(uX < uY) return -1; + if(uX > uY) return 1; + + bStrCmp = false; + } + else { Debug.Assert(false); } + } + else + { + double dX, dY; + if(double.TryParse(strPartX, out dX) && double.TryParse(strPartY, out dY)) + { + if(dX < dY) return -1; + if(dX > dY) return 1; + + bStrCmp = false; + } + else { Debug.Assert(false); } + } + } + if(bStrCmp) + { + int c = string.Compare(strPartX, strPartY, true); + if(c != 0) return c; + } + + bExpNum = !bExpNum; + pX = pExclX; + pY = pExclY; + } + + if(pX >= cX) + { + Debug.Assert(pX == cX); + if(pY >= cY) { Debug.Assert(pY == cY); return 0; } + return -1; + } + + Debug.Assert(pY == cY); + return 1; } public static string RemoveAccelerator(string strMenuText) @@ -724,6 +927,54 @@ namespace KeePassLib.Utility return str; } + public static string AddAccelerator(string strMenuText, + List lAvailKeys) + { + if(strMenuText == null) { Debug.Assert(false); return null; } + if(lAvailKeys == null) { Debug.Assert(false); return strMenuText; } + + int xa = -1, xs = 0; + for(int i = 0; i < strMenuText.Length; ++i) + { + char ch = strMenuText[i]; + +#if KeePassLibSD + char chUpper = char.ToUpper(ch); +#else + char chUpper = char.ToUpperInvariant(ch); +#endif + xa = lAvailKeys.IndexOf(chUpper); + if(xa >= 0) { xs = i; break; } + +#if KeePassLibSD + char chLower = char.ToLower(ch); +#else + char chLower = char.ToLowerInvariant(ch); +#endif + xa = lAvailKeys.IndexOf(chLower); + if(xa >= 0) { xs = i; break; } + } + + if(xa < 0) return strMenuText; + + lAvailKeys.RemoveAt(xa); + return strMenuText.Insert(xs, @"&"); + } + + public static string EncodeMenuText(string strText) + { + if(strText == null) throw new ArgumentNullException("strText"); + + return strText.Replace(@"&", @"&&"); + } + + public static string EncodeToolTipText(string strText) + { + if(strText == null) throw new ArgumentNullException("strText"); + + return strText.Replace(@"&", @"&&&"); + } + public static bool IsHexString(string str, bool bStrict) { if(str == null) throw new ArgumentNullException("str"); @@ -872,6 +1123,36 @@ namespace KeePassLib.Utility } } + public static string GetNewLineSeq(string str) + { + if(str == null) { Debug.Assert(false); return MessageService.NewLine; } + + int n = str.Length, nLf = 0, nCr = 0, nCrLf = 0; + char chLast = char.MinValue; + for(int i = 0; i < n; ++i) + { + char ch = str[i]; + + if(ch == '\r') ++nCr; + else if(ch == '\n') + { + ++nLf; + if(chLast == '\r') ++nCrLf; + } + + chLast = ch; + } + + nCr -= nCrLf; + nLf -= nCrLf; + + int nMax = Math.Max(nCrLf, Math.Max(nCr, nLf)); + if(nMax == 0) return MessageService.NewLine; + + if(nCrLf == nMax) return "\r\n"; + return ((nLf == nMax) ? "\n" : "\r"); + } + public static string AlphaNumericOnly(string str) { if(string.IsNullOrEmpty(str)) return str; @@ -949,37 +1230,44 @@ namespace KeePassLib.Utility public static string VersionToString(ulong uVersion) { - return VersionToString(uVersion, false); + return VersionToString(uVersion, 1U); } + [Obsolete] public static string VersionToString(ulong uVersion, bool bEnsureAtLeastTwoComp) { - string str = string.Empty; - bool bMultiComp = false; + return VersionToString(uVersion, (bEnsureAtLeastTwoComp ? 2U : 1U)); + } + + public static string VersionToString(ulong uVersion, uint uMinComp) + { + StringBuilder sb = new StringBuilder(); + uint uComp = 0; for(int i = 0; i < 4; ++i) { - ushort us = (ushort)(uVersion & 0xFFFFUL); + if(uVersion == 0UL) break; - if((us != 0) || (str.Length > 0)) - { - if(str.Length > 0) - { - str = "." + str; - bMultiComp = true; - } + ushort us = (ushort)(uVersion >> 48); - str = us.ToString() + str; - } + if(sb.Length > 0) sb.Append('.'); - uVersion >>= 16; + sb.Append(us.ToString(NumberFormatInfo.InvariantInfo)); + ++uComp; + + uVersion <<= 16; } - if(bEnsureAtLeastTwoComp && !bMultiComp && (str.Length > 0)) - str += ".0"; + while(uComp < uMinComp) + { + if(sb.Length > 0) sb.Append('.'); - return str; + sb.Append('0'); + ++uComp; + } + + return sb.ToString(); } private static readonly byte[] m_pbOptEnt = { 0xA5, 0x74, 0x2E, 0xEC }; @@ -994,7 +1282,7 @@ namespace KeePassLib.Utility byte[] pbEnc = ProtectedData.Protect(pbPlain, m_pbOptEnt, DataProtectionScope.CurrentUser); -#if !KeePassLibSD +#if (!KeePassLibSD && !KeePassUAP) return Convert.ToBase64String(pbEnc, Base64FormattingOptions.None); #else return Convert.ToBase64String(pbEnc); @@ -1030,7 +1318,7 @@ namespace KeePassLib.Utility for(int i = 0; i < vNumbers.Length; ++i) { if(i > 0) sb.Append(' '); - sb.Append(vNumbers[i]); + sb.Append(vNumbers[i].ToString(NumberFormatInfo.InvariantInfo)); } return sb.ToString(); @@ -1047,14 +1335,14 @@ namespace KeePassLib.Utility for(int i = 0; i < vParts.Length; ++i) { int n; - if(!TryParseInt(vParts[i], out n)) { Debug.Assert(false); } + if(!TryParseIntInvariant(vParts[i], out n)) { Debug.Assert(false); } v[i] = n; } return v; } - private static readonly char[] m_vTagSep = new char[]{ ',', ';', ':' }; + private static readonly char[] m_vTagSep = new char[] { ',', ';', ':' }; public static string TagsToString(List vTags, bool bForDisplay) { if(vTags == null) throw new ArgumentNullException("vTags"); @@ -1107,7 +1395,7 @@ namespace KeePassLib.Utility Array.Reverse(pb); for(int i = 0; i < pb.Length; ++i) pb[i] = (byte)(pb[i] ^ 0x65); -#if !KeePassLibSD +#if (!KeePassLibSD && !KeePassUAP) return Convert.ToBase64String(pb, Base64FormattingOptions.None); #else return Convert.ToBase64String(pb); @@ -1225,9 +1513,33 @@ namespace KeePassLib.Utility public static bool IsDataUri(string strUri) { - if(strUri == null) { Debug.Assert(false); return false; } + return IsDataUri(strUri, null); + } - return strUri.StartsWith("data:", StrUtil.CaseIgnoreCmp); + public static bool IsDataUri(string strUri, string strReqMimeType) + { + if(strUri == null) { Debug.Assert(false); return false; } + // strReqMimeType may be null + + const string strPrefix = "data:"; + if(!strUri.StartsWith(strPrefix, StrUtil.CaseIgnoreCmp)) + return false; + + int iC = strUri.IndexOf(','); + if(iC < 0) return false; + + if(!string.IsNullOrEmpty(strReqMimeType)) + { + int iS = strUri.IndexOf(';', 0, iC); + int iTerm = ((iS >= 0) ? iS : iC); + + string strMime = strUri.Substring(strPrefix.Length, + iTerm - strPrefix.Length); + if(!strMime.Equals(strReqMimeType, StrUtil.CaseIgnoreCmp)) + return false; + } + + return true; } /// @@ -1243,7 +1555,7 @@ namespace KeePassLib.Utility if(strMimeType == null) strMimeType = "application/octet-stream"; -#if !KeePassLibSD +#if (!KeePassLibSD && !KeePassUAP) return ("data:" + strMimeType + ";base64," + Convert.ToBase64String( pbData, Base64FormattingOptions.None)); #else @@ -1273,14 +1585,15 @@ namespace KeePassLib.Utility if(bBase64) return Convert.FromBase64String(strData); MemoryStream ms = new MemoryStream(); + Encoding enc = Encoding.ASCII; string[] v = strData.Split('%'); - byte[] pb = Encoding.ASCII.GetBytes(v[0]); + byte[] pb = enc.GetBytes(v[0]); ms.Write(pb, 0, pb.Length); for(int i = 1; i < v.Length; ++i) { ms.WriteByte(Convert.ToByte(v[i].Substring(0, 2), 16)); - pb = Encoding.ASCII.GetBytes(v[i].Substring(2)); + pb = enc.GetBytes(v[i].Substring(2)); ms.Write(pb, 0, pb.Length); } @@ -1330,5 +1643,72 @@ namespace KeePassLib.Utility return null; } + + private static string[] m_vPrefSepChars = null; + /// + /// Find a character that does not occur within a given text. + /// + public static char GetUnusedChar(string strText) + { + if(strText == null) { Debug.Assert(false); return '@'; } + + if(m_vPrefSepChars == null) + m_vPrefSepChars = new string[5] { + "@!$%#/\\:;,.*-_?", + PwCharSet.UpperCase, PwCharSet.LowerCase, + PwCharSet.Digits, PwCharSet.PrintableAsciiSpecial + }; + + for(int i = 0; i < m_vPrefSepChars.Length; ++i) + { + foreach(char ch in m_vPrefSepChars[i]) + { + if(strText.IndexOf(ch) < 0) return ch; + } + } + + for(char ch = '\u00C0'; ch < char.MaxValue; ++ch) + { + if(strText.IndexOf(ch) < 0) return ch; + } + + return char.MinValue; + } + + public static char ByteToSafeChar(byte bt) + { + const char chDefault = '.'; + + // 00-1F are C0 control chars + if(bt < 0x20) return chDefault; + + // 20-7F are basic Latin; 7F is DEL + if(bt < 0x7F) return (char)bt; + + // 80-9F are C1 control chars + if(bt < 0xA0) return chDefault; + + // A0-FF are Latin-1 supplement; AD is soft hyphen + if(bt == 0xAD) return '-'; + return (char)bt; + } + + public static int Count(string str, string strNeedle) + { + if(str == null) { Debug.Assert(false); return 0; } + if(string.IsNullOrEmpty(strNeedle)) { Debug.Assert(false); return 0; } + + int iOffset = 0, iCount = 0; + while(iOffset < str.Length) + { + int p = str.IndexOf(strNeedle, iOffset); + if(p < 0) break; + + ++iCount; + iOffset = p + 1; + } + + return iCount; + } } } diff --git a/src/KeePassLib2Android/Utility/TimeUtil.cs b/src/KeePassLib2Android/Utility/TimeUtil.cs index 94aa4cd2..80b90c85 100644 --- a/src/KeePassLib2Android/Utility/TimeUtil.cs +++ b/src/KeePassLib2Android/Utility/TimeUtil.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,7 +18,12 @@ */ using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; +using System.Text; + +using KeePassLib.Interfaces; namespace KeePassLib.Utility { @@ -33,6 +38,26 @@ namespace KeePassLib.Utility /// public const int PwTimeLength = 7; +#if !KeePassLibSD + private static string m_strDtfStd = null; + private static string m_strDtfDate = null; +#endif + + private static DateTime? m_odtUnixRoot = null; + public static DateTime UnixRoot + { + get + { + if(m_odtUnixRoot.HasValue) return m_odtUnixRoot.Value; + + DateTime dtRoot = (new DateTime(1970, 1, 1, 0, 0, 0, 0, + DateTimeKind.Utc)).ToLocalTime(); + + m_odtUnixRoot = dtRoot; + return dtRoot; + } + } + /// /// Pack a DateTime object into 5 bytes. Layout: 2 zero bits, /// year 12 bits, month 4 bits, day 5 bits, hour 5 bits, minute 6 @@ -138,17 +163,118 @@ namespace KeePassLib.Utility { DateTime dt; -#if !KeePassLibSD - if(DateTime.TryParse(strDisplay, out dt)) return dt; -#else +#if KeePassLibSD try { dt = DateTime.Parse(strDisplay); return dt; } catch(Exception) { } +#else + if(DateTime.TryParse(strDisplay, out dt)) return dt; + + // For some custom formats specified using the Control Panel, + // DateTime.ToString returns the correct string, but + // DateTime.TryParse fails (e.g. for "//dd/MMM/yyyy"); + // https://sourceforge.net/p/keepass/discussion/329221/thread/3a225b29/?limit=25&page=1#c6ae + if((m_strDtfStd == null) || (m_strDtfDate == null)) + { + DateTime dtUni = new DateTime(2111, 3, 4, 5, 6, 7); + m_strDtfStd = DeriveCustomFormat(ToDisplayString(dtUni), dtUni); + m_strDtfDate = DeriveCustomFormat(ToDisplayStringDateOnly(dtUni), dtUni); + } + const DateTimeStyles dts = DateTimeStyles.AllowWhiteSpaces; + if(DateTime.TryParseExact(strDisplay, m_strDtfStd, null, dts, out dt)) + return dt; + if(DateTime.TryParseExact(strDisplay, m_strDtfDate, null, dts, out dt)) + return dt; #endif Debug.Assert(false); return DateTime.Now; } +#if !KeePassLibSD + private static string DeriveCustomFormat(string strDT, DateTime dt) + { + string[] vPlh = new string[] { + // Names, sorted by length + "MMMM", "dddd", + "MMM", "ddd", + "gg", "g", + + // Numbers, the ones with prefix '0' first + "yyyy", "yyy", "yy", "y", + "MM", "M", + "dd", "d", + "HH", "hh", "H", "h", + "mm", "m", + "ss", "s", + + "tt", "t" + }; + + List lValues = new List(); + foreach(string strPlh in vPlh) + { + string strEval = strPlh; + if(strEval.Length == 1) strEval = @"%" + strPlh; // Make custom + + lValues.Add(dt.ToString(strEval)); + } + + StringBuilder sbAll = new StringBuilder(); + sbAll.Append("dfFghHKmMstyz:/\"\'\\%"); + sbAll.Append(strDT); + foreach(string strVEnum in lValues) { sbAll.Append(strVEnum); } + + List lCodes = new List(); + for(int i = 0; i < vPlh.Length; ++i) + { + char ch = StrUtil.GetUnusedChar(sbAll.ToString()); + lCodes.Add(ch); + sbAll.Append(ch); + } + + string str = strDT; + for(int i = 0; i < vPlh.Length; ++i) + { + string strValue = lValues[i]; + if(string.IsNullOrEmpty(strValue)) continue; + + str = str.Replace(strValue, new string(lCodes[i], 1)); + } + + StringBuilder sbFmt = new StringBuilder(); + bool bInLiteral = false; + foreach(char ch in str) + { + int iCode = lCodes.IndexOf(ch); + + // The escape character doesn't work correctly (e.g. + // "dd\\.MM\\.yyyy\\ HH\\:mm\\:ss" doesn't work, but + // "dd'.'MM'.'yyyy' 'HH':'mm':'ss" does); use '' instead + + // if(iCode >= 0) sbFmt.Append(vPlh[iCode]); + // else // Literal + // { + // sbFmt.Append('\\'); + // sbFmt.Append(ch); + // } + + if(iCode >= 0) + { + if(bInLiteral) { sbFmt.Append('\''); bInLiteral = false; } + sbFmt.Append(vPlh[iCode]); + } + else // Literal + { + if(!bInLiteral) { sbFmt.Append('\''); bInLiteral = true; } + sbFmt.Append(ch); + } + } + if(bInLiteral) sbFmt.Append('\''); + + return sbFmt.ToString(); + } +#endif + public static string SerializeUtc(DateTime dt) { string str = dt.ToUniversalTime().ToString("s"); @@ -167,17 +293,14 @@ namespace KeePassLib.Utility return bResult; } - private static DateTime? m_dtUnixRoot = null; + public static double SerializeUnix(DateTime dt) + { + return (dt - TimeUtil.UnixRoot).TotalSeconds; + } + public static DateTime ConvertUnixTime(double dtUnix) { - try - { - if(!m_dtUnixRoot.HasValue) - m_dtUnixRoot = (new DateTime(1970, 1, 1, 0, 0, 0, 0, - DateTimeKind.Utc)).ToLocalTime(); - - return m_dtUnixRoot.Value.AddSeconds(dtUnix); - } + try { return TimeUtil.UnixRoot.AddSeconds(dtUnix); } catch(Exception) { Debug.Assert(false); } return DateTime.Now; @@ -218,5 +341,44 @@ namespace KeePassLib.Utility return null; } #endif + + private static readonly DateTime m_dtInvMin = + new DateTime(2999, 12, 27, 23, 59, 59); + private static readonly DateTime m_dtInvMax = + new DateTime(2999, 12, 29, 23, 59, 59); + public static int Compare(DateTime dtA, DateTime dtB, bool bUnkIsPast) + { + if(bUnkIsPast) + { + // 2999-12-28 23:59:59 in KeePass 1.x means 'unknown'; + // expect time zone corruption (twice) + // bool bInvA = ((dtA.Year == 2999) && (dtA.Month == 12) && + // (dtA.Day >= 27) && (dtA.Day <= 29) && (dtA.Minute == 59) && + // (dtA.Second == 59)); + // bool bInvB = ((dtB.Year == 2999) && (dtB.Month == 12) && + // (dtB.Day >= 27) && (dtB.Day <= 29) && (dtB.Minute == 59) && + // (dtB.Second == 59)); + // Faster due to internal implementation of DateTime: + bool bInvA = ((dtA >= m_dtInvMin) && (dtA <= m_dtInvMax) && + (dtA.Minute == 59) && (dtA.Second == 59)); + bool bInvB = ((dtB >= m_dtInvMin) && (dtB <= m_dtInvMax) && + (dtB.Minute == 59) && (dtB.Second == 59)); + + if(bInvA) return (bInvB ? 0 : -1); + if(bInvB) return 1; + } + + return dtA.CompareTo(dtB); + } + + internal static int CompareLastMod(ITimeLogger tlA, ITimeLogger tlB, + bool bUnkIsPast) + { + if(tlA == null) { Debug.Assert(false); return ((tlB == null) ? 0 : -1); } + if(tlB == null) { Debug.Assert(false); return 1; } + + return Compare(tlA.LastModificationTime, tlB.LastModificationTime, + bUnkIsPast); + } } } diff --git a/src/KeePassLib2Android/Utility/UrlUtil.cs b/src/KeePassLib2Android/Utility/UrlUtil.cs index d9c903a4..3650942a 100644 --- a/src/KeePassLib2Android/Utility/UrlUtil.cs +++ b/src/KeePassLib2Android/Utility/UrlUtil.cs @@ -1,6 +1,6 @@ /* KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2012 Dominik Reichl + Copyright (C) 2003-2016 Dominik Reichl This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,10 +19,9 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Runtime.InteropServices; -using System.Text; using System.Diagnostics; +using System.IO; +using System.Text; using KeePassLib.Native; @@ -34,13 +33,21 @@ namespace KeePassLib.Utility /// public static class UrlUtil { - private static readonly char[] m_vDirSeps = new char[] { '\\', '/', - Path.DirectorySeparatorChar }; + private static readonly char[] m_vDirSeps = new char[] { + '\\', '/', UrlUtil.LocalDirSepChar }; + private static readonly char[] m_vPathTrimCharsWs = new char[] { + '\"', ' ', '\t', '\r', '\n' }; + + public static char LocalDirSepChar + { + get { return Path.DirectorySeparatorChar; } + } /// - /// Get the directory (path) of a file name. The returned string is + /// Get the directory (path) of a file name. The returned string may be /// terminated by a directory separator character. Example: /// passing C:\\My Documents\\My File.kdb in + /// and true to /// would produce this string: C:\\My Documents\\. /// /// Full path of a file. @@ -51,8 +58,7 @@ namespace KeePassLib.Utility /// of X:, overriding ). /// This should only be set to true, if the returned path is directly /// passed to some directory API. - /// Directory of the file. The return value is an empty string - /// ("") if the input parameter is null. + /// Directory of the file. public static string GetFileDirectory(string strFile, bool bAppendTerminatingChar, bool bEnsureValidDirSpec) { @@ -60,14 +66,15 @@ namespace KeePassLib.Utility if(strFile == null) throw new ArgumentNullException("strFile"); int nLastSep = strFile.LastIndexOfAny(m_vDirSeps); - if(nLastSep < 0) return strFile; // None + if(nLastSep < 0) return string.Empty; // No directory if(bEnsureValidDirSpec && (nLastSep == 2) && (strFile[1] == ':') && (strFile[2] == '\\')) // Length >= 3 and Windows root directory bAppendTerminatingChar = true; if(!bAppendTerminatingChar) return strFile.Substring(0, nLastSep); - return EnsureTerminatingSeparator(strFile.Substring(0, nLastSep), false); + return EnsureTerminatingSeparator(strFile.Substring(0, nLastSep), + (strFile[nLastSep] == '/')); } /// @@ -148,7 +155,7 @@ namespace KeePassLib.Utility } if(bUrl) return (strPath + '/'); - return (strPath + Path.DirectorySeparatorChar); + return (strPath + UrlUtil.LocalDirSepChar); } /* /// @@ -211,13 +218,22 @@ namespace KeePassLib.Utility public static string GetQuotedAppPath(string strPath) { - int nFirst = strPath.IndexOf('\"'); - int nSecond = strPath.IndexOf('\"', nFirst + 1); + if(strPath == null) { Debug.Assert(false); return string.Empty; } - if((nFirst >= 0) && (nSecond >= 0)) - return strPath.Substring(nFirst + 1, nSecond - nFirst - 1); + // int nFirst = strPath.IndexOf('\"'); + // int nSecond = strPath.IndexOf('\"', nFirst + 1); + // if((nFirst >= 0) && (nSecond >= 0)) + // return strPath.Substring(nFirst + 1, nSecond - nFirst - 1); + // return strPath; - return strPath; + string str = strPath.Trim(); + if(str.Length <= 1) return str; + if(str[0] != '\"') return str; + + int iSecond = str.IndexOf('\"', 1); + if(iSecond <= 0) return str; + + return str.Substring(1, iSecond - 1); } public static string FileUrlToPath(string strUrl) @@ -229,7 +245,7 @@ namespace KeePassLib.Utility if(str.StartsWith(@"file:///", StrUtil.CaseIgnoreCmp)) str = str.Substring(8, str.Length - 8); - str = str.Replace('/', Path.DirectorySeparatorChar); + str = str.Replace('/', UrlUtil.LocalDirSepChar); return str; } @@ -297,8 +313,10 @@ namespace KeePassLib.Utility return strTargetFile; } +#if (!KeePassLibSD && !KeePassUAP) if(NativeLib.IsUnix()) { +#endif bool bBaseUnc = IsUncPath(strBaseFile); bool bTargetUnc = IsUncPath(strTargetFile); if((!bBaseUnc && bTargetUnc) || (bBaseUnc && !bTargetUnc)) @@ -316,21 +334,19 @@ namespace KeePassLib.Utility StringBuilder sbRel = new StringBuilder(); for(int j = i; j < (vBase.Length - 1); ++j) { - if(sbRel.Length > 0) sbRel.Append(Path.DirectorySeparatorChar); + if(sbRel.Length > 0) sbRel.Append(UrlUtil.LocalDirSepChar); sbRel.Append(".."); } for(int k = i; k < vTarget.Length; ++k) { - if(sbRel.Length > 0) sbRel.Append(Path.DirectorySeparatorChar); + if(sbRel.Length > 0) sbRel.Append(UrlUtil.LocalDirSepChar); sbRel.Append(vTarget[k]); } return sbRel.ToString(); +#if (!KeePassLibSD && !KeePassUAP) } -#if KeePassLibSD - return strTargetFile; -#else try // Windows { const int nMaxPath = NativeMethods.MAX_PATH * 2; @@ -344,7 +360,8 @@ namespace KeePassLib.Utility return str; } - catch(Exception) { Debug.Assert(false); return strTargetFile; } + catch(Exception) { Debug.Assert(false); } + return strTargetFile; #endif } @@ -476,7 +493,7 @@ namespace KeePassLib.Utility public static string ConvertSeparators(string strPath) { - return ConvertSeparators(strPath, Path.DirectorySeparatorChar); + return ConvertSeparators(strPath, UrlUtil.LocalDirSepChar); } public static string ConvertSeparators(string strPath, char chSeparator) @@ -594,16 +611,97 @@ namespace KeePassLib.Utility string strDir; if(NativeLib.IsUnix()) strDir = NativeMethods.GetUserRuntimeDir(); +#if KeePassUAP + else strDir = Windows.Storage.ApplicationData.Current.TemporaryFolder.Path; +#else else strDir = Path.GetTempPath(); +#endif try { - if(Directory.Exists(strDir) == false) - Directory.CreateDirectory(strDir); + if(!Directory.Exists(strDir)) Directory.CreateDirectory(strDir); } catch(Exception) { Debug.Assert(false); } return strDir; } + +#if !KeePassLibSD + // Structurally mostly equivalent to UrlUtil.GetFileInfos + public static List GetFilePaths(string strDir, string strPattern, + SearchOption opt) + { + List l = new List(); + if(strDir == null) { Debug.Assert(false); return l; } + if(strPattern == null) { Debug.Assert(false); return l; } + + string[] v = Directory.GetFiles(strDir, strPattern, opt); + if(v == null) { Debug.Assert(false); return l; } + + // Only accept files with the correct extension; GetFiles may + // return additional files, see GetFiles documentation + string strExt = GetExtension(strPattern); + if(!string.IsNullOrEmpty(strExt) && (strExt.IndexOf('*') < 0) && + (strExt.IndexOf('?') < 0)) + { + strExt = "." + strExt; + + foreach(string strPathRaw in v) + { + if(strPathRaw == null) { Debug.Assert(false); continue; } + string strPath = strPathRaw.Trim(m_vPathTrimCharsWs); + if(strPath.Length == 0) { Debug.Assert(false); continue; } + Debug.Assert(strPath == strPathRaw); + + if(!strPath.EndsWith(strExt, StrUtil.CaseIgnoreCmp)) + continue; + + l.Add(strPathRaw); + } + } + else l.AddRange(v); + + return l; + } + + // Structurally mostly equivalent to UrlUtil.GetFilePaths + public static List GetFileInfos(DirectoryInfo di, string strPattern, + SearchOption opt) + { + List l = new List(); + if(di == null) { Debug.Assert(false); return l; } + if(strPattern == null) { Debug.Assert(false); return l; } + + FileInfo[] v = di.GetFiles(strPattern, opt); + if(v == null) { Debug.Assert(false); return l; } + + // Only accept files with the correct extension; GetFiles may + // return additional files, see GetFiles documentation + string strExt = GetExtension(strPattern); + if(!string.IsNullOrEmpty(strExt) && (strExt.IndexOf('*') < 0) && + (strExt.IndexOf('?') < 0)) + { + strExt = "." + strExt; + + foreach(FileInfo fi in v) + { + if(fi == null) { Debug.Assert(false); continue; } + string strPathRaw = fi.FullName; + if(strPathRaw == null) { Debug.Assert(false); continue; } + string strPath = strPathRaw.Trim(m_vPathTrimCharsWs); + if(strPath.Length == 0) { Debug.Assert(false); continue; } + Debug.Assert(strPath == strPathRaw); + + if(!strPath.EndsWith(strExt, StrUtil.CaseIgnoreCmp)) + continue; + + l.Add(fi); + } + } + else l.AddRange(v); + + return l; + } +#endif } } From a2f2e3d6f89d81b59f5e6e8c845fff2d38a34504 Mon Sep 17 00:00:00 2001 From: Philipp Crocoll Date: Tue, 30 Aug 2016 04:11:48 +0200 Subject: [PATCH 3/3] Keepass Orig KeePass_160826 --- .../Collections/StringDictionaryEx.cs | 56 +- .../Collections/VariantDictionary.cs | 415 ++++++++++++ .../Cryptography/Cipher/ChaCha20Cipher.cs | 251 +++++++ .../Cryptography/Cipher/ChaCha20Engine.cs | 174 +++++ .../Cryptography/Cipher/CipherPool.cs | 13 +- .../Cryptography/Cipher/CtrBlockCipher.cs | 101 +++ .../Cryptography/Cipher/ICipherEngine.cs | 21 + .../Cryptography/Cipher/Salsa20Cipher.cs | 239 +++---- .../Cryptography/Cipher/StandardAesEngine.cs | 21 +- .../Cryptography/CryptoRandom.cs | 200 +++--- .../Cryptography/CryptoRandomStream.cs | 104 +-- .../Cryptography/CryptoUtil.cs | 126 ++++ .../Cryptography/Hash/Blake2b.cs | 229 +++++++ .../Cryptography/HashingStreamEx.cs | 6 +- .../Cryptography/KeyDerivation/AesKdf.cs | 270 ++++++++ .../KeyDerivation/Argon2Kdf.Core.cs | 610 ++++++++++++++++++ .../Cryptography/KeyDerivation/Argon2Kdf.cs | 144 +++++ .../Cryptography/KeyDerivation/KdfEngine.cs | 142 ++++ .../KeyDerivation/KdfParameters.cs | 80 +++ .../Cryptography/KeyDerivation/KdfPool.cs | 96 +++ .../PasswordGenerator/PwGenerator.cs | 16 +- .../Cryptography/SelfTest.cs | 531 ++++++++++++++- src/KeePassLib2Android/Keys/CompositeKey.cs | 232 +------ src/KeePassLib2Android/Keys/KcpCustomKey.cs | 8 +- src/KeePassLib2Android/Keys/KcpKeyFile.cs | 24 +- src/KeePassLib2Android/Keys/KcpPassword.cs | 8 +- src/KeePassLib2Android/PwDatabase.cs | 97 ++- src/KeePassLib2Android/PwEntry.cs | 43 +- src/KeePassLib2Android/PwGroup.cs | 25 + .../Resources/KLRes.Generated.cs | 53 +- .../Security/ProtectedBinary.cs | 30 +- .../Serialization/FileLock.cs | 2 +- .../Serialization/HashedBlockStream.cs | 67 +- .../Serialization/HmacBlockStream.cs | 325 ++++++++++ .../Serialization/KdbxFile.Read.Streamed.cs | 84 ++- .../Serialization/KdbxFile.Read.cs | 221 ++++--- .../Serialization/KdbxFile.Write.cs | 276 +++++--- .../Serialization/KdbxFile.cs | 175 ++++- .../Translation/KPControlCustomization.cs | 6 +- src/KeePassLib2Android/Utility/GfxUtil.cs | 13 +- src/KeePassLib2Android/Utility/MemUtil.cs | 195 +++++- .../Utility/MonoWorkarounds.cs | 6 + src/KeePassLib2Android/Utility/StrUtil.cs | 30 +- 43 files changed, 4888 insertions(+), 877 deletions(-) create mode 100644 src/KeePassLib2Android/Collections/VariantDictionary.cs create mode 100644 src/KeePassLib2Android/Cryptography/Cipher/ChaCha20Cipher.cs create mode 100644 src/KeePassLib2Android/Cryptography/Cipher/ChaCha20Engine.cs create mode 100644 src/KeePassLib2Android/Cryptography/Cipher/CtrBlockCipher.cs create mode 100644 src/KeePassLib2Android/Cryptography/CryptoUtil.cs create mode 100644 src/KeePassLib2Android/Cryptography/Hash/Blake2b.cs create mode 100644 src/KeePassLib2Android/Cryptography/KeyDerivation/AesKdf.cs create mode 100644 src/KeePassLib2Android/Cryptography/KeyDerivation/Argon2Kdf.Core.cs create mode 100644 src/KeePassLib2Android/Cryptography/KeyDerivation/Argon2Kdf.cs create mode 100644 src/KeePassLib2Android/Cryptography/KeyDerivation/KdfEngine.cs create mode 100644 src/KeePassLib2Android/Cryptography/KeyDerivation/KdfParameters.cs create mode 100644 src/KeePassLib2Android/Cryptography/KeyDerivation/KdfPool.cs create mode 100644 src/KeePassLib2Android/Serialization/HmacBlockStream.cs diff --git a/src/KeePassLib2Android/Collections/StringDictionaryEx.cs b/src/KeePassLib2Android/Collections/StringDictionaryEx.cs index 2c55b306..79e21b34 100644 --- a/src/KeePassLib2Android/Collections/StringDictionaryEx.cs +++ b/src/KeePassLib2Android/Collections/StringDictionaryEx.cs @@ -32,14 +32,14 @@ using KeePassLibSD; namespace KeePassLib.Collections { public sealed class StringDictionaryEx : IDeepCloneable, - IEnumerable> + IEnumerable>, IEquatable { - private SortedDictionary m_vDict = + private SortedDictionary m_dict = new SortedDictionary(); public int Count { - get { return m_vDict.Count; } + get { return m_dict.Count; } } public StringDictionaryEx() @@ -48,39 +48,53 @@ namespace KeePassLib.Collections IEnumerator IEnumerable.GetEnumerator() { - return m_vDict.GetEnumerator(); + return m_dict.GetEnumerator(); } public IEnumerator> GetEnumerator() { - return m_vDict.GetEnumerator(); + return m_dict.GetEnumerator(); } public StringDictionaryEx CloneDeep() { - StringDictionaryEx plNew = new StringDictionaryEx(); + StringDictionaryEx sdNew = new StringDictionaryEx(); - foreach(KeyValuePair kvpStr in m_vDict) - plNew.Set(kvpStr.Key, kvpStr.Value); + foreach(KeyValuePair kvp in m_dict) + sdNew.m_dict[kvp.Key] = kvp.Value; // Strings are immutable - return plNew; + return sdNew; + } + + public bool Equals(StringDictionaryEx sdOther) + { + if(sdOther == null) { Debug.Assert(false); return false; } + + if(m_dict.Count != sdOther.m_dict.Count) return false; + + foreach(KeyValuePair kvp in sdOther.m_dict) + { + string str = Get(kvp.Key); + if((str == null) || (str != kvp.Value)) return false; + } + + return true; } public string Get(string strName) { - Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); + if(strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); } string s; - if(m_vDict.TryGetValue(strName, out s)) return s; - + if(m_dict.TryGetValue(strName, out s)) return s; return null; } public bool Exists(string strName) { - Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); + if(strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); } - return m_vDict.ContainsKey(strName); + return m_dict.ContainsKey(strName); } /// @@ -92,25 +106,25 @@ namespace KeePassLib.Collections /// parameters is null. public void Set(string strField, string strNewValue) { - Debug.Assert(strField != null); if(strField == null) throw new ArgumentNullException("strField"); - Debug.Assert(strNewValue != null); if(strNewValue == null) throw new ArgumentNullException("strNewValue"); + if(strField == null) { Debug.Assert(false); throw new ArgumentNullException("strField"); } + if(strNewValue == null) { Debug.Assert(false); throw new ArgumentNullException("strNewValue"); } - m_vDict[strField] = strNewValue; + m_dict[strField] = strNewValue; } /// /// Delete a string. /// /// Name of the string field to delete. - /// Returns true if the field has been successfully - /// removed, otherwise the return value is false. + /// Returns true, if the field has been successfully + /// removed. Otherwise, the return value is false. /// Thrown if the input /// parameter is null. public bool Remove(string strField) { - Debug.Assert(strField != null); if(strField == null) throw new ArgumentNullException("strField"); + if(strField == null) { Debug.Assert(false); throw new ArgumentNullException("strField"); } - return m_vDict.Remove(strField); + return m_dict.Remove(strField); } } } diff --git a/src/KeePassLib2Android/Collections/VariantDictionary.cs b/src/KeePassLib2Android/Collections/VariantDictionary.cs new file mode 100644 index 00000000..8aa5a48c --- /dev/null +++ b/src/KeePassLib2Android/Collections/VariantDictionary.cs @@ -0,0 +1,415 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +using KeePassLib.Resources; +using KeePassLib.Utility; + +namespace KeePassLib.Collections +{ + public class VariantDictionary : ICloneable + { + private const ushort VdVersion = 0x0100; + private const ushort VdmCritical = 0xFF00; + private const ushort VdmInfo = 0x00FF; + + private Dictionary m_d = new Dictionary(); + + private enum VdType : byte + { + None = 0, + + // Byte = 0x02, + // UInt16 = 0x03, + UInt32 = 0x04, + UInt64 = 0x05, + + // Signed mask: 0x08 + Bool = 0x08, + // SByte = 0x0A, + // Int16 = 0x0B, + Int32 = 0x0C, + Int64 = 0x0D, + + // Float = 0x10, + // Double = 0x11, + // Decimal = 0x12, + + // Char = 0x17, // 16-bit Unicode character + String = 0x18, + + // Array mask: 0x40 + ByteArray = 0x42 + } + + public int Count + { + get { return m_d.Count; } + } + + public VariantDictionary() + { + Debug.Assert((VdmCritical & VdmInfo) == ushort.MinValue); + Debug.Assert((VdmCritical | VdmInfo) == ushort.MaxValue); + } + + private bool Get(string strName, out T t) + { + t = default(T); + + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return false; } + + object o; + if(!m_d.TryGetValue(strName, out o)) return false; // No assert + + if(o == null) { Debug.Assert(false); return false; } + if(o.GetType() != typeof(T)) { Debug.Assert(false); return false; } + + t = (T)o; + return true; + } + + private void SetStruct(string strName, T t) + where T : struct + { + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return; } + +#if DEBUG + T tEx; + Get(strName, out tEx); // Assert same type +#endif + + m_d[strName] = t; + } + + private void SetRef(string strName, T t) + where T : class + { + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return; } + if(t == null) { Debug.Assert(false); return; } + +#if DEBUG + T tEx; + Get(strName, out tEx); // Assert same type +#endif + + m_d[strName] = t; + } + + public bool Remove(string strName) + { + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return false; } + + return m_d.Remove(strName); + } + + public void CopyTo(VariantDictionary d) + { + if(d == null) { Debug.Assert(false); return; } + + // Do not clear the target + foreach(KeyValuePair kvp in m_d) + { + d.m_d[kvp.Key] = kvp.Value; + } + } + + public Type GetTypeOf(string strName) + { + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return null; } + + object o; + m_d.TryGetValue(strName, out o); + if(o == null) return null; // No assert + + return o.GetType(); + } + + public uint GetUInt32(string strName, uint uDefault) + { + uint u; + if(Get(strName, out u)) return u; + return uDefault; + } + + public void SetUInt32(string strName, uint uValue) + { + SetStruct(strName, uValue); + } + + public ulong GetUInt64(string strName, ulong uDefault) + { + ulong u; + if(Get(strName, out u)) return u; + return uDefault; + } + + public void SetUInt64(string strName, ulong uValue) + { + SetStruct(strName, uValue); + } + + public bool GetBool(string strName, bool bDefault) + { + bool b; + if(Get(strName, out b)) return b; + return bDefault; + } + + public void SetBool(string strName, bool bValue) + { + SetStruct(strName, bValue); + } + + public int GetInt32(string strName, int iDefault) + { + int i; + if(Get(strName, out i)) return i; + return iDefault; + } + + public void SetInt32(string strName, int iValue) + { + SetStruct(strName, iValue); + } + + public long GetInt64(string strName, long lDefault) + { + long l; + if(Get(strName, out l)) return l; + return lDefault; + } + + public void SetInt64(string strName, long lValue) + { + SetStruct(strName, lValue); + } + + public string GetString(string strName) + { + string str; + Get(strName, out str); + return str; + } + + public void SetString(string strName, string strValue) + { + SetRef(strName, strValue); + } + + public byte[] GetByteArray(string strName) + { + byte[] pb; + Get(strName, out pb); + return pb; + } + + public void SetByteArray(string strName, byte[] pbValue) + { + SetRef(strName, pbValue); + } + + /// + /// Create a deep copy. + /// + public virtual object Clone() + { + VariantDictionary vdNew = new VariantDictionary(); + + foreach(KeyValuePair kvp in m_d) + { + object o = kvp.Value; + if(o == null) { Debug.Assert(false); continue; } + + Type t = o.GetType(); + if(t == typeof(byte[])) + { + byte[] p = (byte[])o; + byte[] pNew = new byte[p.Length]; + if(p.Length > 0) Array.Copy(p, pNew, p.Length); + + o = pNew; + } + + vdNew.m_d[kvp.Key] = o; + } + + return vdNew; + } + + public static byte[] Serialize(VariantDictionary p) + { + if(p == null) { Debug.Assert(false); return null; } + + byte[] pbRet; + using(MemoryStream ms = new MemoryStream()) + { + MemUtil.Write(ms, MemUtil.UInt16ToBytes(VdVersion)); + + foreach(KeyValuePair kvp in p.m_d) + { + string strName = kvp.Key; + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); continue; } + byte[] pbName = StrUtil.Utf8.GetBytes(strName); + + object o = kvp.Value; + if(o == null) { Debug.Assert(false); continue; } + + Type t = o.GetType(); + VdType vt = VdType.None; + byte[] pbValue = null; + if(t == typeof(uint)) + { + vt = VdType.UInt32; + pbValue = MemUtil.UInt32ToBytes((uint)o); + } + else if(t == typeof(ulong)) + { + vt = VdType.UInt64; + pbValue = MemUtil.UInt64ToBytes((ulong)o); + } + else if(t == typeof(bool)) + { + vt = VdType.Bool; + pbValue = new byte[1]; + pbValue[0] = ((bool)o ? (byte)1 : (byte)0); + } + else if(t == typeof(int)) + { + vt = VdType.Int32; + pbValue = MemUtil.Int32ToBytes((int)o); + } + else if(t == typeof(long)) + { + vt = VdType.Int64; + pbValue = MemUtil.Int64ToBytes((long)o); + } + else if(t == typeof(string)) + { + vt = VdType.String; + pbValue = StrUtil.Utf8.GetBytes((string)o); + } + else if(t == typeof(byte[])) + { + vt = VdType.ByteArray; + pbValue = (byte[])o; + } + else { Debug.Assert(false); continue; } // Unknown type + + ms.WriteByte((byte)vt); + MemUtil.Write(ms, MemUtil.Int32ToBytes(pbName.Length)); + MemUtil.Write(ms, pbName); + MemUtil.Write(ms, MemUtil.Int32ToBytes(pbValue.Length)); + MemUtil.Write(ms, pbValue); + } + + ms.WriteByte((byte)VdType.None); + pbRet = ms.ToArray(); + } + + return pbRet; + } + + public static VariantDictionary Deserialize(byte[] pb) + { + if(pb == null) { Debug.Assert(false); return null; } + + VariantDictionary d = new VariantDictionary(); + using(MemoryStream ms = new MemoryStream(pb, false)) + { + ushort uVersion = MemUtil.BytesToUInt16(MemUtil.Read(ms, 2)); + if((uVersion & VdmCritical) > (VdVersion & VdmCritical)) + throw new FormatException(KLRes.FileNewVerReq); + + while(true) + { + int iType = ms.ReadByte(); + if(iType < 0) throw new EndOfStreamException(KLRes.FileCorrupted); + byte btType = (byte)iType; + if(btType == (byte)VdType.None) break; + + int cbName = MemUtil.BytesToInt32(MemUtil.Read(ms, 4)); + byte[] pbName = MemUtil.Read(ms, cbName); + if(pbName.Length != cbName) + throw new EndOfStreamException(KLRes.FileCorrupted); + string strName = StrUtil.Utf8.GetString(pbName); + + int cbValue = MemUtil.BytesToInt32(MemUtil.Read(ms, 4)); + byte[] pbValue = MemUtil.Read(ms, cbValue); + if(pbValue.Length != cbValue) + throw new EndOfStreamException(KLRes.FileCorrupted); + + switch(btType) + { + case (byte)VdType.UInt32: + if(cbValue == 4) + d.SetUInt32(strName, MemUtil.BytesToUInt32(pbValue)); + else { Debug.Assert(false); } + break; + + case (byte)VdType.UInt64: + if(cbValue == 8) + d.SetUInt64(strName, MemUtil.BytesToUInt64(pbValue)); + else { Debug.Assert(false); } + break; + + case (byte)VdType.Bool: + if(cbValue == 1) + d.SetBool(strName, (pbValue[0] != 0)); + else { Debug.Assert(false); } + break; + + case (byte)VdType.Int32: + if(cbValue == 4) + d.SetInt32(strName, MemUtil.BytesToInt32(pbValue)); + else { Debug.Assert(false); } + break; + + case (byte)VdType.Int64: + if(cbValue == 8) + d.SetInt64(strName, MemUtil.BytesToInt64(pbValue)); + else { Debug.Assert(false); } + break; + + case (byte)VdType.String: + d.SetString(strName, StrUtil.Utf8.GetString(pbValue)); + break; + + case (byte)VdType.ByteArray: + d.SetByteArray(strName, pbValue); + break; + + default: + Debug.Assert(false); // Unknown type + break; + } + } + + Debug.Assert(ms.ReadByte() < 0); + } + + return d; + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/Cipher/ChaCha20Cipher.cs b/src/KeePassLib2Android/Cryptography/Cipher/ChaCha20Cipher.cs new file mode 100644 index 00000000..ad0c93b4 --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/Cipher/ChaCha20Cipher.cs @@ -0,0 +1,251 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; + +using KeePassLib.Resources; +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.Cipher +{ + /// + /// Implementation of the ChaCha20 cipher with a 96-bit nonce, + /// as specified in RFC 7539. + /// https://tools.ietf.org/html/rfc7539 + /// + public sealed class ChaCha20Cipher : CtrBlockCipher + { + private uint[] m_s = new uint[16]; // State + private uint[] m_x = new uint[16]; // Working buffer + + private bool m_bLargeCounter; // See constructor documentation + + private static readonly uint[] g_sigma = new uint[4] { + 0x61707865, 0x3320646E, 0x79622D32, 0x6B206574 + }; + + private const string StrNameRfc = "ChaCha20 (RFC 7539)"; + + public override int BlockSize + { + get { return 64; } + } + + public ChaCha20Cipher(byte[] pbKey32, byte[] pbIV12) : + this(pbKey32, pbIV12, false) + { + } + + /// + /// Constructor. + /// + /// Key (32 bytes). + /// Nonce (12 bytes). + /// If false, the RFC 7539 version + /// of ChaCha20 is used. In this case, only 256 GB of data can be + /// encrypted securely (because the block counter is a 32-bit variable); + /// an attempt to encrypt more data throws an exception. + /// If is true, the 32-bit + /// counter overflows to another 32-bit variable (i.e. the counter + /// effectively is a 64-bit variable), like in the original ChaCha20 + /// specification by D. J. Bernstein (which has a 64-bit counter and a + /// 64-bit nonce). To be compatible with this version, the 64-bit nonce + /// must be stored in the last 8 bytes of + /// and the first 4 bytes must be 0. + /// If the IV was generated randomly, a 12-byte IV and a large counter + /// can be used to securely encrypt more than 256 GB of data (but note + /// this is incompatible with RFC 7539 and the original specification). + public ChaCha20Cipher(byte[] pbKey32, byte[] pbIV12, bool bLargeCounter) : + base() + { + if(pbKey32 == null) throw new ArgumentNullException("pbKey32"); + if(pbKey32.Length != 32) throw new ArgumentOutOfRangeException("pbKey32"); + if(pbIV12 == null) throw new ArgumentNullException("pbIV12"); + if(pbIV12.Length != 12) throw new ArgumentOutOfRangeException("pbIV12"); + + m_bLargeCounter = bLargeCounter; + + // Key setup + m_s[4] = MemUtil.BytesToUInt32(pbKey32, 0); + m_s[5] = MemUtil.BytesToUInt32(pbKey32, 4); + m_s[6] = MemUtil.BytesToUInt32(pbKey32, 8); + m_s[7] = MemUtil.BytesToUInt32(pbKey32, 12); + m_s[8] = MemUtil.BytesToUInt32(pbKey32, 16); + m_s[9] = MemUtil.BytesToUInt32(pbKey32, 20); + m_s[10] = MemUtil.BytesToUInt32(pbKey32, 24); + m_s[11] = MemUtil.BytesToUInt32(pbKey32, 28); + m_s[0] = g_sigma[0]; + m_s[1] = g_sigma[1]; + m_s[2] = g_sigma[2]; + m_s[3] = g_sigma[3]; + + // IV setup + m_s[12] = 0; // Counter + m_s[13] = MemUtil.BytesToUInt32(pbIV12, 0); + m_s[14] = MemUtil.BytesToUInt32(pbIV12, 4); + m_s[15] = MemUtil.BytesToUInt32(pbIV12, 8); + } + + protected override void Dispose(bool bDisposing) + { + MemUtil.ZeroArray(m_s); + MemUtil.ZeroArray(m_x); + + base.Dispose(bDisposing); + } + + protected override void NextBlock(byte[] pBlock) + { + if(pBlock == null) throw new ArgumentNullException("pBlock"); + if(pBlock.Length != 64) throw new ArgumentOutOfRangeException("pBlock"); + + // x is a local alias for the working buffer; with this, + // the compiler/runtime might remove some checks + uint[] x = m_x; + if(x == null) throw new InvalidOperationException(); + if(x.Length < 16) throw new InvalidOperationException(); + + uint[] s = m_s; + if(s == null) throw new InvalidOperationException(); + if(s.Length < 16) throw new InvalidOperationException(); + + Array.Copy(s, x, 16); + + unchecked + { + // 10 * 8 quarter rounds = 20 rounds + for(int i = 0; i < 10; ++i) + { + // Column quarter rounds + x[ 0] += x[ 4]; + x[12] = MemUtil.RotateLeft32(x[12] ^ x[ 0], 16); + x[ 8] += x[12]; + x[ 4] = MemUtil.RotateLeft32(x[ 4] ^ x[ 8], 12); + x[ 0] += x[ 4]; + x[12] = MemUtil.RotateLeft32(x[12] ^ x[ 0], 8); + x[ 8] += x[12]; + x[ 4] = MemUtil.RotateLeft32(x[ 4] ^ x[ 8], 7); + + x[ 1] += x[ 5]; + x[13] = MemUtil.RotateLeft32(x[13] ^ x[ 1], 16); + x[ 9] += x[13]; + x[ 5] = MemUtil.RotateLeft32(x[ 5] ^ x[ 9], 12); + x[ 1] += x[ 5]; + x[13] = MemUtil.RotateLeft32(x[13] ^ x[ 1], 8); + x[ 9] += x[13]; + x[ 5] = MemUtil.RotateLeft32(x[ 5] ^ x[ 9], 7); + + x[ 2] += x[ 6]; + x[14] = MemUtil.RotateLeft32(x[14] ^ x[ 2], 16); + x[10] += x[14]; + x[ 6] = MemUtil.RotateLeft32(x[ 6] ^ x[10], 12); + x[ 2] += x[ 6]; + x[14] = MemUtil.RotateLeft32(x[14] ^ x[ 2], 8); + x[10] += x[14]; + x[ 6] = MemUtil.RotateLeft32(x[ 6] ^ x[10], 7); + + x[ 3] += x[ 7]; + x[15] = MemUtil.RotateLeft32(x[15] ^ x[ 3], 16); + x[11] += x[15]; + x[ 7] = MemUtil.RotateLeft32(x[ 7] ^ x[11], 12); + x[ 3] += x[ 7]; + x[15] = MemUtil.RotateLeft32(x[15] ^ x[ 3], 8); + x[11] += x[15]; + x[ 7] = MemUtil.RotateLeft32(x[ 7] ^ x[11], 7); + + // Diagonal quarter rounds + x[ 0] += x[ 5]; + x[15] = MemUtil.RotateLeft32(x[15] ^ x[ 0], 16); + x[10] += x[15]; + x[ 5] = MemUtil.RotateLeft32(x[ 5] ^ x[10], 12); + x[ 0] += x[ 5]; + x[15] = MemUtil.RotateLeft32(x[15] ^ x[ 0], 8); + x[10] += x[15]; + x[ 5] = MemUtil.RotateLeft32(x[ 5] ^ x[10], 7); + + x[ 1] += x[ 6]; + x[12] = MemUtil.RotateLeft32(x[12] ^ x[ 1], 16); + x[11] += x[12]; + x[ 6] = MemUtil.RotateLeft32(x[ 6] ^ x[11], 12); + x[ 1] += x[ 6]; + x[12] = MemUtil.RotateLeft32(x[12] ^ x[ 1], 8); + x[11] += x[12]; + x[ 6] = MemUtil.RotateLeft32(x[ 6] ^ x[11], 7); + + x[ 2] += x[ 7]; + x[13] = MemUtil.RotateLeft32(x[13] ^ x[ 2], 16); + x[ 8] += x[13]; + x[ 7] = MemUtil.RotateLeft32(x[ 7] ^ x[ 8], 12); + x[ 2] += x[ 7]; + x[13] = MemUtil.RotateLeft32(x[13] ^ x[ 2], 8); + x[ 8] += x[13]; + x[ 7] = MemUtil.RotateLeft32(x[ 7] ^ x[ 8], 7); + + x[ 3] += x[ 4]; + x[14] = MemUtil.RotateLeft32(x[14] ^ x[ 3], 16); + x[ 9] += x[14]; + x[ 4] = MemUtil.RotateLeft32(x[ 4] ^ x[ 9], 12); + x[ 3] += x[ 4]; + x[14] = MemUtil.RotateLeft32(x[14] ^ x[ 3], 8); + x[ 9] += x[14]; + x[ 4] = MemUtil.RotateLeft32(x[ 4] ^ x[ 9], 7); + } + + for(int i = 0; i < 16; ++i) x[i] += s[i]; + + for(int i = 0; i < 16; ++i) + { + int i4 = i << 2; + uint xi = x[i]; + + pBlock[i4] = (byte)xi; + pBlock[i4 + 1] = (byte)(xi >> 8); + pBlock[i4 + 2] = (byte)(xi >> 16); + pBlock[i4 + 3] = (byte)(xi >> 24); + } + + ++s[12]; + if(s[12] == 0) + { + if(!m_bLargeCounter) + throw new InvalidOperationException( + KLRes.EncDataTooLarge.Replace(@"{PARAM}", StrNameRfc)); + ++s[13]; // Increment high half of large counter + } + } + } + + public long Seek(long lOffset, SeekOrigin so) + { + if(so != SeekOrigin.Begin) throw new NotSupportedException(); + + if((lOffset < 0) || ((lOffset & 63) != 0) || + ((lOffset >> 6) > (long)uint.MaxValue)) + throw new ArgumentOutOfRangeException("lOffset"); + + m_s[12] = (uint)(lOffset >> 6); + InvalidateBlock(); + + return lOffset; + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/Cipher/ChaCha20Engine.cs b/src/KeePassLib2Android/Cryptography/Cipher/ChaCha20Engine.cs new file mode 100644 index 00000000..2fa6fe1d --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/Cipher/ChaCha20Engine.cs @@ -0,0 +1,174 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +using KeePassLib.Resources; + +namespace KeePassLib.Cryptography.Cipher +{ + public sealed class ChaCha20Engine : ICipherEngine2 + { + private PwUuid m_uuid = new PwUuid(new byte[] { + 0xD6, 0x03, 0x8A, 0x2B, 0x8B, 0x6F, 0x4C, 0xB5, + 0xA5, 0x24, 0x33, 0x9A, 0x31, 0xDB, 0xB5, 0x9A + }); + + public PwUuid CipherUuid + { + get { return m_uuid; } + } + + public string DisplayName + { + get + { + return ("ChaCha20 (" + KLRes.KeyBits.Replace(@"{PARAM}", + "256") + ", RFC 7539)"); + } + } + + public int KeyLength + { + get { return 32; } + } + + public int IVLength + { + get { return 12; } // 96 bits + } + + public Stream EncryptStream(Stream sPlainText, byte[] pbKey, byte[] pbIV) + { + return new ChaCha20Stream(sPlainText, true, pbKey, pbIV); + } + + public Stream DecryptStream(Stream sEncrypted, byte[] pbKey, byte[] pbIV) + { + return new ChaCha20Stream(sEncrypted, false, pbKey, pbIV); + } + } + + internal sealed class ChaCha20Stream : Stream + { + private Stream m_sBase; + private readonly bool m_bWriting; + private ChaCha20Cipher m_c; + + private byte[] m_pbBuffer = null; + + public override bool CanRead + { + get { return !m_bWriting; } + } + + public override bool CanSeek + { + get { return false; } + } + + public override bool CanWrite + { + get { return m_bWriting; } + } + + public override long Length + { + get { Debug.Assert(false); throw new NotSupportedException(); } + } + + public override long Position + { + get { Debug.Assert(false); throw new NotSupportedException(); } + set { Debug.Assert(false); throw new NotSupportedException(); } + } + + public ChaCha20Stream(Stream sBase, bool bWriting, byte[] pbKey32, + byte[] pbIV12) + { + if(sBase == null) throw new ArgumentNullException("sBase"); + + m_sBase = sBase; + m_bWriting = bWriting; + m_c = new ChaCha20Cipher(pbKey32, pbIV12); + } + + protected override void Dispose(bool bDisposing) + { + if(!bDisposing) return; + + if(m_sBase != null) + { + m_c.Dispose(); + m_c = null; + + m_sBase.Close(); + m_sBase = null; + } + + m_pbBuffer = null; + } + + public override void Flush() + { + Debug.Assert(m_sBase != null); + if(m_bWriting && (m_sBase != null)) m_sBase.Flush(); + } + + public override long Seek(long lOffset, SeekOrigin soOrigin) + { + Debug.Assert(false); + throw new NotImplementedException(); + } + + public override void SetLength(long lValue) + { + Debug.Assert(false); + throw new NotImplementedException(); + } + + public override int Read(byte[] pbBuffer, int iOffset, int nCount) + { + if(m_bWriting) throw new InvalidOperationException(); + + int cbRead = m_sBase.Read(pbBuffer, iOffset, nCount); + m_c.Decrypt(pbBuffer, iOffset, cbRead); + return cbRead; + } + + public override void Write(byte[] pbBuffer, int iOffset, int nCount) + { + if(nCount < 0) throw new ArgumentOutOfRangeException("nCount"); + if(nCount == 0) return; + + if(!m_bWriting) throw new InvalidOperationException(); + + if((m_pbBuffer == null) || (m_pbBuffer.Length < nCount)) + m_pbBuffer = new byte[nCount]; + Array.Copy(pbBuffer, iOffset, m_pbBuffer, 0, nCount); + + m_c.Encrypt(m_pbBuffer, 0, nCount); + m_sBase.Write(m_pbBuffer, 0, nCount); + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/Cipher/CipherPool.cs b/src/KeePassLib2Android/Cryptography/Cipher/CipherPool.cs index c7668ac7..836a157c 100644 --- a/src/KeePassLib2Android/Cryptography/Cipher/CipherPool.cs +++ b/src/KeePassLib2Android/Cryptography/Cipher/CipherPool.cs @@ -40,12 +40,17 @@ namespace KeePassLib.Cryptography.Cipher { get { - if(m_poolGlobal != null) return m_poolGlobal; + CipherPool cp = m_poolGlobal; + if(cp == null) + { + cp = new CipherPool(); + cp.AddCipher(new StandardAesEngine()); + cp.AddCipher(new ChaCha20Engine()); - m_poolGlobal = new CipherPool(); - m_poolGlobal.AddCipher(new StandardAesEngine()); + m_poolGlobal = cp; + } - return m_poolGlobal; + return cp; } } diff --git a/src/KeePassLib2Android/Cryptography/Cipher/CtrBlockCipher.cs b/src/KeePassLib2Android/Cryptography/Cipher/CtrBlockCipher.cs new file mode 100644 index 00000000..87ec3daa --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/Cipher/CtrBlockCipher.cs @@ -0,0 +1,101 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.Cipher +{ + public abstract class CtrBlockCipher : IDisposable + { + private byte[] m_pBlock; + private int m_iBlockPos; + + public abstract int BlockSize + { + get; + } + + public CtrBlockCipher() + { + int cb = this.BlockSize; + if(cb <= 0) throw new InvalidOperationException("this.BlockSize"); + + m_pBlock = new byte[cb]; + m_iBlockPos = cb; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool bDisposing) + { + MemUtil.ZeroByteArray(m_pBlock); + m_iBlockPos = m_pBlock.Length; + } + + protected void InvalidateBlock() + { + m_iBlockPos = m_pBlock.Length; + } + + protected abstract void NextBlock(byte[] pBlock); + + public void Encrypt(byte[] m, int iOffset, int cb) + { + if(m == null) throw new ArgumentNullException("m"); + if(iOffset < 0) throw new ArgumentOutOfRangeException("iOffset"); + if(cb < 0) throw new ArgumentOutOfRangeException("cb"); + if(iOffset > (m.Length - cb)) throw new ArgumentOutOfRangeException("cb"); + + int cbBlock = m_pBlock.Length; + + while(cb > 0) + { + Debug.Assert(m_iBlockPos <= cbBlock); + if(m_iBlockPos == cbBlock) + { + NextBlock(m_pBlock); + m_iBlockPos = 0; + } + + int cbCopy = Math.Min(cbBlock - m_iBlockPos, cb); + Debug.Assert(cbCopy > 0); + + MemUtil.XorArray(m_pBlock, m_iBlockPos, m, iOffset, cbCopy); + + m_iBlockPos += cbCopy; + iOffset += cbCopy; + cb -= cbCopy; + } + } + + public void Decrypt(byte[] m, int iOffset, int cb) + { + Encrypt(m, iOffset, cb); + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/Cipher/ICipherEngine.cs b/src/KeePassLib2Android/Cryptography/Cipher/ICipherEngine.cs index 734368a0..39dd696c 100644 --- a/src/KeePassLib2Android/Cryptography/Cipher/ICipherEngine.cs +++ b/src/KeePassLib2Android/Cryptography/Cipher/ICipherEngine.cs @@ -63,4 +63,25 @@ namespace KeePassLib.Cryptography.Cipher /// Stream, from which the decrypted data can be read. Stream DecryptStream(Stream sEncrypted, byte[] pbKey, byte[] pbIV); } + + public interface ICipherEngine2 : ICipherEngine + { + /// + /// Length of an encryption key in bytes. + /// The base ICipherEngine assumes 32. + /// + int KeyLength + { + get; + } + + /// + /// Length of the initialization vector in bytes. + /// The base ICipherEngine assumes 16. + /// + int IVLength + { + get; + } + } } diff --git a/src/KeePassLib2Android/Cryptography/Cipher/Salsa20Cipher.cs b/src/KeePassLib2Android/Cryptography/Cipher/Salsa20Cipher.cs index 534adfa2..55be9786 100644 --- a/src/KeePassLib2Android/Cryptography/Cipher/Salsa20Cipher.cs +++ b/src/KeePassLib2Android/Cryptography/Cipher/Salsa20Cipher.cs @@ -17,182 +17,145 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -// Implementation of the Salsa20 cipher, based on the eSTREAM submission. +// Implementation of the Salsa20 cipher, based on the eSTREAM +// submission by D. J. Bernstein. using System; +using System.Collections.Generic; using System.Diagnostics; using KeePassLib.Utility; namespace KeePassLib.Cryptography.Cipher { - public sealed class Salsa20Cipher : IDisposable + public sealed class Salsa20Cipher : CtrBlockCipher { - private uint[] m_state = new uint[16]; + private uint[] m_s = new uint[16]; // State private uint[] m_x = new uint[16]; // Working buffer - private byte[] m_output = new byte[64]; - private int m_outputPos = 64; - - private static readonly uint[] m_sigma = new uint[4] { + private static readonly uint[] g_sigma = new uint[4] { 0x61707865, 0x3320646E, 0x79622D32, 0x6B206574 }; - public Salsa20Cipher(byte[] pbKey32, byte[] pbIV8) + public override int BlockSize { - KeySetup(pbKey32); - IvSetup(pbIV8); + get { return 64; } } - ~Salsa20Cipher() + public Salsa20Cipher(byte[] pbKey32, byte[] pbIV8) : base() { - Dispose(false); + if(pbKey32 == null) throw new ArgumentNullException("pbKey32"); + if(pbKey32.Length != 32) throw new ArgumentOutOfRangeException("pbKey32"); + if(pbIV8 == null) throw new ArgumentNullException("pbIV8"); + if(pbIV8.Length != 8) throw new ArgumentOutOfRangeException("pbIV8"); + + // Key setup + m_s[1] = MemUtil.BytesToUInt32(pbKey32, 0); + m_s[2] = MemUtil.BytesToUInt32(pbKey32, 4); + m_s[3] = MemUtil.BytesToUInt32(pbKey32, 8); + m_s[4] = MemUtil.BytesToUInt32(pbKey32, 12); + m_s[11] = MemUtil.BytesToUInt32(pbKey32, 16); + m_s[12] = MemUtil.BytesToUInt32(pbKey32, 20); + m_s[13] = MemUtil.BytesToUInt32(pbKey32, 24); + m_s[14] = MemUtil.BytesToUInt32(pbKey32, 28); + m_s[0] = g_sigma[0]; + m_s[5] = g_sigma[1]; + m_s[10] = g_sigma[2]; + m_s[15] = g_sigma[3]; + + // IV setup + m_s[6] = MemUtil.BytesToUInt32(pbIV8, 0); + m_s[7] = MemUtil.BytesToUInt32(pbIV8, 4); + m_s[8] = 0; // Counter, low + m_s[9] = 0; // Counter, high } - public void Dispose() + protected override void Dispose(bool bDisposing) { - Dispose(true); - GC.SuppressFinalize(this); + MemUtil.ZeroArray(m_s); + MemUtil.ZeroArray(m_x); + + base.Dispose(bDisposing); } - private void Dispose(bool bDisposing) + protected override void NextBlock(byte[] pBlock) { - // Clear sensitive data - Array.Clear(m_state, 0, m_state.Length); - Array.Clear(m_x, 0, m_x.Length); - } + if(pBlock == null) throw new ArgumentNullException("pBlock"); + if(pBlock.Length != 64) throw new ArgumentOutOfRangeException("pBlock"); - private void NextOutput() - { - uint[] x = m_x; // Local alias for working buffer - - // Compiler/runtime might remove array bound checks after this + // x is a local alias for the working buffer; with this, + // the compiler/runtime might remove some checks + uint[] x = m_x; + if(x == null) throw new InvalidOperationException(); if(x.Length < 16) throw new InvalidOperationException(); - Array.Copy(m_state, x, 16); + uint[] s = m_s; + if(s == null) throw new InvalidOperationException(); + if(s.Length < 16) throw new InvalidOperationException(); + + Array.Copy(s, x, 16); unchecked { - for(int i = 0; i < 10; ++i) // (int i = 20; i > 0; i -= 2) + // 10 * 8 quarter rounds = 20 rounds + for(int i = 0; i < 10; ++i) { - x[ 4] ^= Rotl32(x[ 0] + x[12], 7); - x[ 8] ^= Rotl32(x[ 4] + x[ 0], 9); - x[12] ^= Rotl32(x[ 8] + x[ 4], 13); - x[ 0] ^= Rotl32(x[12] + x[ 8], 18); - x[ 9] ^= Rotl32(x[ 5] + x[ 1], 7); - x[13] ^= Rotl32(x[ 9] + x[ 5], 9); - x[ 1] ^= Rotl32(x[13] + x[ 9], 13); - x[ 5] ^= Rotl32(x[ 1] + x[13], 18); - x[14] ^= Rotl32(x[10] + x[ 6], 7); - x[ 2] ^= Rotl32(x[14] + x[10], 9); - x[ 6] ^= Rotl32(x[ 2] + x[14], 13); - x[10] ^= Rotl32(x[ 6] + x[ 2], 18); - x[ 3] ^= Rotl32(x[15] + x[11], 7); - x[ 7] ^= Rotl32(x[ 3] + x[15], 9); - x[11] ^= Rotl32(x[ 7] + x[ 3], 13); - x[15] ^= Rotl32(x[11] + x[ 7], 18); - x[ 1] ^= Rotl32(x[ 0] + x[ 3], 7); - x[ 2] ^= Rotl32(x[ 1] + x[ 0], 9); - x[ 3] ^= Rotl32(x[ 2] + x[ 1], 13); - x[ 0] ^= Rotl32(x[ 3] + x[ 2], 18); - x[ 6] ^= Rotl32(x[ 5] + x[ 4], 7); - x[ 7] ^= Rotl32(x[ 6] + x[ 5], 9); - x[ 4] ^= Rotl32(x[ 7] + x[ 6], 13); - x[ 5] ^= Rotl32(x[ 4] + x[ 7], 18); - x[11] ^= Rotl32(x[10] + x[ 9], 7); - x[ 8] ^= Rotl32(x[11] + x[10], 9); - x[ 9] ^= Rotl32(x[ 8] + x[11], 13); - x[10] ^= Rotl32(x[ 9] + x[ 8], 18); - x[12] ^= Rotl32(x[15] + x[14], 7); - x[13] ^= Rotl32(x[12] + x[15], 9); - x[14] ^= Rotl32(x[13] + x[12], 13); - x[15] ^= Rotl32(x[14] + x[13], 18); + x[ 4] ^= MemUtil.RotateLeft32(x[ 0] + x[12], 7); + x[ 8] ^= MemUtil.RotateLeft32(x[ 4] + x[ 0], 9); + x[12] ^= MemUtil.RotateLeft32(x[ 8] + x[ 4], 13); + x[ 0] ^= MemUtil.RotateLeft32(x[12] + x[ 8], 18); + + x[ 9] ^= MemUtil.RotateLeft32(x[ 5] + x[ 1], 7); + x[13] ^= MemUtil.RotateLeft32(x[ 9] + x[ 5], 9); + x[ 1] ^= MemUtil.RotateLeft32(x[13] + x[ 9], 13); + x[ 5] ^= MemUtil.RotateLeft32(x[ 1] + x[13], 18); + + x[14] ^= MemUtil.RotateLeft32(x[10] + x[ 6], 7); + x[ 2] ^= MemUtil.RotateLeft32(x[14] + x[10], 9); + x[ 6] ^= MemUtil.RotateLeft32(x[ 2] + x[14], 13); + x[10] ^= MemUtil.RotateLeft32(x[ 6] + x[ 2], 18); + + x[ 3] ^= MemUtil.RotateLeft32(x[15] + x[11], 7); + x[ 7] ^= MemUtil.RotateLeft32(x[ 3] + x[15], 9); + x[11] ^= MemUtil.RotateLeft32(x[ 7] + x[ 3], 13); + x[15] ^= MemUtil.RotateLeft32(x[11] + x[ 7], 18); + + x[ 1] ^= MemUtil.RotateLeft32(x[ 0] + x[ 3], 7); + x[ 2] ^= MemUtil.RotateLeft32(x[ 1] + x[ 0], 9); + x[ 3] ^= MemUtil.RotateLeft32(x[ 2] + x[ 1], 13); + x[ 0] ^= MemUtil.RotateLeft32(x[ 3] + x[ 2], 18); + + x[ 6] ^= MemUtil.RotateLeft32(x[ 5] + x[ 4], 7); + x[ 7] ^= MemUtil.RotateLeft32(x[ 6] + x[ 5], 9); + x[ 4] ^= MemUtil.RotateLeft32(x[ 7] + x[ 6], 13); + x[ 5] ^= MemUtil.RotateLeft32(x[ 4] + x[ 7], 18); + + x[11] ^= MemUtil.RotateLeft32(x[10] + x[ 9], 7); + x[ 8] ^= MemUtil.RotateLeft32(x[11] + x[10], 9); + x[ 9] ^= MemUtil.RotateLeft32(x[ 8] + x[11], 13); + x[10] ^= MemUtil.RotateLeft32(x[ 9] + x[ 8], 18); + + x[12] ^= MemUtil.RotateLeft32(x[15] + x[14], 7); + x[13] ^= MemUtil.RotateLeft32(x[12] + x[15], 9); + x[14] ^= MemUtil.RotateLeft32(x[13] + x[12], 13); + x[15] ^= MemUtil.RotateLeft32(x[14] + x[13], 18); } - for(int i = 0; i < 16; ++i) - x[i] += m_state[i]; + for(int i = 0; i < 16; ++i) x[i] += s[i]; for(int i = 0; i < 16; ++i) { - m_output[i << 2] = (byte)x[i]; - m_output[(i << 2) + 1] = (byte)(x[i] >> 8); - m_output[(i << 2) + 2] = (byte)(x[i] >> 16); - m_output[(i << 2) + 3] = (byte)(x[i] >> 24); + int i4 = i << 2; + uint xi = x[i]; + + pBlock[i4] = (byte)xi; + pBlock[i4 + 1] = (byte)(xi >> 8); + pBlock[i4 + 2] = (byte)(xi >> 16); + pBlock[i4 + 3] = (byte)(xi >> 24); } - m_outputPos = 0; - ++m_state[8]; - if(m_state[8] == 0) ++m_state[9]; - } - } - - private static uint Rotl32(uint x, int b) - { - unchecked - { - return ((x << b) | (x >> (32 - b))); - } - } - - private static uint U8To32Little(byte[] pb, int iOffset) - { - unchecked - { - return ((uint)pb[iOffset] | ((uint)pb[iOffset + 1] << 8) | - ((uint)pb[iOffset + 2] << 16) | ((uint)pb[iOffset + 3] << 24)); - } - } - - private void KeySetup(byte[] k) - { - if(k == null) throw new ArgumentNullException("k"); - if(k.Length != 32) throw new ArgumentException(); - - m_state[1] = U8To32Little(k, 0); - m_state[2] = U8To32Little(k, 4); - m_state[3] = U8To32Little(k, 8); - m_state[4] = U8To32Little(k, 12); - m_state[11] = U8To32Little(k, 16); - m_state[12] = U8To32Little(k, 20); - m_state[13] = U8To32Little(k, 24); - m_state[14] = U8To32Little(k, 28); - m_state[0] = m_sigma[0]; - m_state[5] = m_sigma[1]; - m_state[10] = m_sigma[2]; - m_state[15] = m_sigma[3]; - } - - private void IvSetup(byte[] pbIV) - { - if(pbIV == null) throw new ArgumentNullException("pbIV"); - if(pbIV.Length != 8) throw new ArgumentException(); - - m_state[6] = U8To32Little(pbIV, 0); - m_state[7] = U8To32Little(pbIV, 4); - m_state[8] = 0; - m_state[9] = 0; - } - - public void Encrypt(byte[] m, int nByteCount, bool bXor) - { - if(m == null) throw new ArgumentNullException("m"); - if(nByteCount > m.Length) throw new ArgumentException(); - - int nBytesRem = nByteCount, nOffset = 0; - while(nBytesRem > 0) - { - Debug.Assert((m_outputPos >= 0) && (m_outputPos <= 64)); - if(m_outputPos == 64) NextOutput(); - Debug.Assert(m_outputPos < 64); - - int nCopy = Math.Min(64 - m_outputPos, nBytesRem); - - if(bXor) MemUtil.XorArray(m_output, m_outputPos, m, nOffset, nCopy); - else Array.Copy(m_output, m_outputPos, m, nOffset, nCopy); - - m_outputPos += nCopy; - nBytesRem -= nCopy; - nOffset += nCopy; + ++s[8]; + if(s[8] == 0) ++s[9]; } } } diff --git a/src/KeePassLib2Android/Cryptography/Cipher/StandardAesEngine.cs b/src/KeePassLib2Android/Cryptography/Cipher/StandardAesEngine.cs index 4b4095e3..eebd1248 100644 --- a/src/KeePassLib2Android/Cryptography/Cipher/StandardAesEngine.cs +++ b/src/KeePassLib2Android/Cryptography/Cipher/StandardAesEngine.cs @@ -42,7 +42,7 @@ namespace KeePassLib.Cryptography.Cipher private const PaddingMode m_rCipherPadding = PaddingMode.PKCS7; #endif - private static PwUuid m_uuidAes = null; + private static PwUuid g_uuidAes = null; /// /// UUID of the cipher engine. This ID uniquely identifies the @@ -52,12 +52,16 @@ namespace KeePassLib.Cryptography.Cipher { get { - if(m_uuidAes == null) - m_uuidAes = new PwUuid(new byte[]{ + PwUuid pu = g_uuidAes; + if(pu == null) + { + pu = new PwUuid(new byte[] { 0x31, 0xC1, 0xF2, 0xE6, 0xBF, 0x71, 0x43, 0x50, 0xBE, 0x58, 0x05, 0x21, 0x6A, 0xFC, 0x5A, 0xFF }); + g_uuidAes = pu; + } - return m_uuidAes; + return pu; } } @@ -72,7 +76,14 @@ namespace KeePassLib.Cryptography.Cipher /// /// Get a displayable name describing this cipher engine. /// - public string DisplayName { get { return KLRes.EncAlgorithmAes; } } + public string DisplayName + { + get + { + return ("AES/Rijndael (" + KLRes.KeyBits.Replace(@"{PARAM}", + "256") + ", FIPS 197)"); + } + } private static void ValidateArguments(Stream stream, bool bEncrypt, byte[] pbKey, byte[] pbIV) { diff --git a/src/KeePassLib2Android/Cryptography/CryptoRandom.cs b/src/KeePassLib2Android/Cryptography/CryptoRandom.cs index a8b22618..837bbb8c 100644 --- a/src/KeePassLib2Android/Cryptography/CryptoRandom.cs +++ b/src/KeePassLib2Android/Cryptography/CryptoRandom.cs @@ -40,14 +40,14 @@ namespace KeePassLib.Cryptography public sealed class CryptoRandom { private byte[] m_pbEntropyPool = new byte[64]; - private uint m_uCounter; + private ulong m_uCounter; private RNGCryptoServiceProvider m_rng = new RNGCryptoServiceProvider(); private ulong m_uGeneratedBytesCount = 0; private static object g_oSyncRoot = new object(); private object m_oSyncRoot = new object(); - private static CryptoRandom m_pInstance = null; + private static CryptoRandom g_pInstance = null; public static CryptoRandom Instance { get @@ -55,11 +55,11 @@ namespace KeePassLib.Cryptography CryptoRandom cr; lock(g_oSyncRoot) { - cr = m_pInstance; + cr = g_pInstance; if(cr == null) { cr = new CryptoRandom(); - m_pInstance = cr; + g_pInstance = cr; } } @@ -90,10 +90,12 @@ namespace KeePassLib.Cryptography private CryptoRandom() { - Random r = new Random(); - m_uCounter = (uint)r.Next(); + Random rWeak = new Random(); + byte[] pb = new byte[8]; + rWeak.NextBytes(pb); + m_uCounter = MemUtil.BytesToUInt64(pb); - AddEntropy(GetSystemData(r)); + AddEntropy(GetSystemData(rWeak)); AddEntropy(GetCspData()); } @@ -109,32 +111,40 @@ namespace KeePassLib.Cryptography if(pbEntropy.Length == 0) { Debug.Assert(false); return; } byte[] pbNewData = pbEntropy; - if(pbEntropy.Length >= 64) + if(pbEntropy.Length > 64) { #if KeePassLibSD - SHA256Managed shaNew = new SHA256Managed(); + using(SHA256Managed shaNew = new SHA256Managed()) #else - SHA512Managed shaNew = new SHA512Managed(); + using(SHA512Managed shaNew = new SHA512Managed()) #endif - pbNewData = shaNew.ComputeHash(pbEntropy); + { + pbNewData = shaNew.ComputeHash(pbEntropy); + } } - MemoryStream ms = new MemoryStream(); lock(m_oSyncRoot) { - ms.Write(m_pbEntropyPool, 0, m_pbEntropyPool.Length); - ms.Write(pbNewData, 0, pbNewData.Length); + int cbPool = m_pbEntropyPool.Length; + int cbNew = pbNewData.Length; + + byte[] pbCmp = new byte[cbPool + cbNew]; + Array.Copy(m_pbEntropyPool, pbCmp, cbPool); + Array.Copy(pbNewData, 0, pbCmp, cbPool, cbNew); + + MemUtil.ZeroByteArray(m_pbEntropyPool); - byte[] pbFinal = ms.ToArray(); #if KeePassLibSD - SHA256Managed shaPool = new SHA256Managed(); + using(SHA256Managed shaPool = new SHA256Managed()) #else - Debug.Assert(pbFinal.Length == (64 + pbNewData.Length)); - SHA512Managed shaPool = new SHA512Managed(); + using(SHA512Managed shaPool = new SHA512Managed()) #endif - m_pbEntropyPool = shaPool.ComputeHash(pbFinal); + { + m_pbEntropyPool = shaPool.ComputeHash(pbCmp); + } + + MemUtil.ZeroByteArray(pbCmp); } - ms.Close(); } private static byte[] GetSystemData(Random rWeak) @@ -142,11 +152,11 @@ namespace KeePassLib.Cryptography MemoryStream ms = new MemoryStream(); byte[] pb; - pb = MemUtil.UInt32ToBytes((uint)Environment.TickCount); - ms.Write(pb, 0, pb.Length); + pb = MemUtil.Int32ToBytes(Environment.TickCount); + MemUtil.Write(ms, pb); - pb = TimeUtil.PackTime(DateTime.Now); - ms.Write(pb, 0, pb.Length); + pb = MemUtil.Int64ToBytes(DateTime.UtcNow.ToBinary()); + MemUtil.Write(ms, pb); #if !KeePassLibSD // In try-catch for systems without GUI; @@ -154,79 +164,79 @@ namespace KeePassLib.Cryptography try { Point pt = Cursor.Position; - pb = MemUtil.UInt32ToBytes((uint)pt.X); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt32ToBytes((uint)pt.Y); - ms.Write(pb, 0, pb.Length); + pb = MemUtil.Int32ToBytes(pt.X); + MemUtil.Write(ms, pb); + pb = MemUtil.Int32ToBytes(pt.Y); + MemUtil.Write(ms, pb); } catch(Exception) { } #endif - pb = MemUtil.UInt32ToBytes((uint)rWeak.Next()); - ms.Write(pb, 0, pb.Length); + pb = MemUtil.Int32ToBytes(rWeak.Next()); + MemUtil.Write(ms, pb); pb = MemUtil.UInt32ToBytes((uint)NativeLib.GetPlatformID()); - ms.Write(pb, 0, pb.Length); + MemUtil.Write(ms, pb); try { - pb = MemUtil.UInt32ToBytes((uint)Environment.ProcessorCount); - ms.Write(pb, 0, pb.Length); + pb = MemUtil.Int32ToBytes(Environment.ProcessorCount); + MemUtil.Write(ms, pb); #if KeePassUAP Version v = EnvironmentExt.OSVersion.Version; #else Version v = Environment.OSVersion.Version; #endif - pb = MemUtil.UInt32ToBytes((uint)v.GetHashCode()); - ms.Write(pb, 0, pb.Length); + pb = MemUtil.Int32ToBytes(v.GetHashCode()); + MemUtil.Write(ms, pb); #if !KeePassUAP - pb = MemUtil.UInt64ToBytes((ulong)Environment.WorkingSet); - ms.Write(pb, 0, pb.Length); + pb = MemUtil.Int64ToBytes(Environment.WorkingSet); + MemUtil.Write(ms, pb); #endif } catch(Exception) { Debug.Assert(false); } #if KeePassUAP pb = DiagnosticsExt.GetProcessEntropy(); - ms.Write(pb, 0, pb.Length); + MemUtil.Write(ms, pb); #elif !KeePassLibSD Process p = null; try { p = Process.GetCurrentProcess(); - pb = MemUtil.UInt64ToBytes((ulong)p.Handle.ToInt64()); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt32ToBytes((uint)p.HandleCount); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt32ToBytes((uint)p.Id); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.NonpagedSystemMemorySize64); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.PagedMemorySize64); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.PagedSystemMemorySize64); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.PeakPagedMemorySize64); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.PeakVirtualMemorySize64); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.PeakWorkingSet64); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.PrivateMemorySize64); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.StartTime.ToBinary()); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.VirtualMemorySize64); - ms.Write(pb, 0, pb.Length); - pb = MemUtil.UInt64ToBytes((ulong)p.WorkingSet64); - ms.Write(pb, 0, pb.Length); + pb = MemUtil.Int64ToBytes(p.Handle.ToInt64()); + MemUtil.Write(ms, pb); + pb = MemUtil.Int32ToBytes(p.HandleCount); + MemUtil.Write(ms, pb); + pb = MemUtil.Int32ToBytes(p.Id); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.NonpagedSystemMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PagedMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PagedSystemMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PeakPagedMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PeakVirtualMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PeakWorkingSet64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.PrivateMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.StartTime.ToBinary()); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.VirtualMemorySize64); + MemUtil.Write(ms, pb); + pb = MemUtil.Int64ToBytes(p.WorkingSet64); + MemUtil.Write(ms, pb); // Not supported in Mono 1.2.6: // pb = MemUtil.UInt32ToBytes((uint)p.SessionId); - // ms.Write(pb, 0, pb.Length); + // MemUtil.Write(ms, pb); } catch(Exception) { Debug.Assert(NativeLib.IsUnix()); } finally @@ -237,7 +247,7 @@ namespace KeePassLib.Cryptography #endif pb = Guid.NewGuid().ToByteArray(); - ms.Write(pb, 0, pb.Length); + MemUtil.Write(ms, pb); byte[] pbAll = ms.ToArray(); ms.Close(); @@ -256,28 +266,31 @@ namespace KeePassLib.Cryptography if(this.GenerateRandom256Pre != null) this.GenerateRandom256Pre(this, EventArgs.Empty); - byte[] pbFinal; + byte[] pbCmp; lock(m_oSyncRoot) { - unchecked { m_uCounter += 386047; } // Prime number - byte[] pbCounter = MemUtil.UInt32ToBytes(m_uCounter); + m_uCounter += 0x74D8B29E4D38E161UL; // Prime number + byte[] pbCounter = MemUtil.UInt64ToBytes(m_uCounter); byte[] pbCspRandom = GetCspData(); - MemoryStream ms = new MemoryStream(); - ms.Write(m_pbEntropyPool, 0, m_pbEntropyPool.Length); - ms.Write(pbCounter, 0, pbCounter.Length); - ms.Write(pbCspRandom, 0, pbCspRandom.Length); - pbFinal = ms.ToArray(); - Debug.Assert(pbFinal.Length == (m_pbEntropyPool.Length + - pbCounter.Length + pbCspRandom.Length)); - ms.Close(); + int cbPool = m_pbEntropyPool.Length; + int cbCtr = pbCounter.Length; + int cbCsp = pbCspRandom.Length; + + pbCmp = new byte[cbPool + cbCtr + cbCsp]; + Array.Copy(m_pbEntropyPool, pbCmp, cbPool); + Array.Copy(pbCounter, 0, pbCmp, cbPool, cbCtr); + Array.Copy(pbCspRandom, 0, pbCmp, cbPool + cbCtr, cbCsp); + + MemUtil.ZeroByteArray(pbCspRandom); m_uGeneratedBytesCount += 32; } - SHA256Managed sha256 = new SHA256Managed(); - return sha256.ComputeHash(pbFinal); + byte[] pbRet = CryptoUtil.HashSha256(pbCmp); + MemUtil.ZeroByteArray(pbCmp); + return pbRet; } /// @@ -289,29 +302,32 @@ namespace KeePassLib.Cryptography /// random bytes. public byte[] GetRandomBytes(uint uRequestedBytes) { - if(uRequestedBytes == 0) return new byte[0]; // Allow zero-length array + if(uRequestedBytes == 0) return MemUtil.EmptyByteArray; + if(uRequestedBytes > (uint)int.MaxValue) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("uRequestedBytes"); + } - byte[] pbRes = new byte[uRequestedBytes]; - long lPos = 0; + int cbRem = (int)uRequestedBytes; + byte[] pbRes = new byte[cbRem]; + int iPos = 0; - while(uRequestedBytes != 0) + while(cbRem != 0) { byte[] pbRandom256 = GenerateRandom256(); Debug.Assert(pbRandom256.Length == 32); - long lCopy = (long)((uRequestedBytes < 32) ? uRequestedBytes : 32); + int cbCopy = Math.Min(cbRem, 32); + Array.Copy(pbRandom256, 0, pbRes, iPos, cbCopy); -#if (!KeePassLibSD && !KeePassUAP) - Array.Copy(pbRandom256, 0, pbRes, lPos, lCopy); -#else - Array.Copy(pbRandom256, 0, pbRes, (int)lPos, (int)lCopy); -#endif + MemUtil.ZeroByteArray(pbRandom256); - lPos += lCopy; - uRequestedBytes -= (uint)lCopy; + iPos += cbCopy; + cbRem -= cbCopy; } - Debug.Assert((int)lPos == pbRes.Length); + Debug.Assert(iPos == pbRes.Length); return pbRes; } } diff --git a/src/KeePassLib2Android/Cryptography/CryptoRandomStream.cs b/src/KeePassLib2Android/Cryptography/CryptoRandomStream.cs index 44d9c63a..726edb15 100644 --- a/src/KeePassLib2Android/Cryptography/CryptoRandomStream.cs +++ b/src/KeePassLib2Android/Cryptography/CryptoRandomStream.cs @@ -25,6 +25,7 @@ using System.Security.Cryptography; #endif using KeePassLib.Cryptography.Cipher; +using KeePassLib.Utility; namespace KeePassLib.Cryptography { @@ -40,6 +41,7 @@ namespace KeePassLib.Cryptography /// /// A variant of the ARCFour algorithm (RC4 incompatible). + /// Insecure; for backward compatibility only. /// ArcFourVariant = 1, @@ -48,7 +50,12 @@ namespace KeePassLib.Cryptography /// Salsa20 = 2, - Count = 3 + /// + /// ChaCha20 stream cipher algorithm. + /// + ChaCha20 = 3, + + Count = 4 } /// @@ -59,45 +66,68 @@ namespace KeePassLib.Cryptography /// public sealed class CryptoRandomStream { - private CrsAlgorithm m_crsAlgorithm; + private readonly CrsAlgorithm m_crsAlgorithm; private byte[] m_pbState = null; private byte m_i = 0; private byte m_j = 0; private Salsa20Cipher m_salsa20 = null; + private ChaCha20Cipher m_chacha20 = null; /// /// Construct a new cryptographically secure random stream object. /// - /// Algorithm to use. + /// Algorithm to use. /// Initialization key. Must not be null and /// must contain at least 1 byte. - /// Thrown if the - /// parameter is null. - /// Thrown if the - /// parameter contains no bytes or the - /// algorithm is unknown. - public CryptoRandomStream(CrsAlgorithm genAlgorithm, byte[] pbKey) + public CryptoRandomStream(CrsAlgorithm a, byte[] pbKey) { - m_crsAlgorithm = genAlgorithm; + if(pbKey == null) { Debug.Assert(false); throw new ArgumentNullException("pbKey"); } - Debug.Assert(pbKey != null); if(pbKey == null) throw new ArgumentNullException("pbKey"); + int cbKey = pbKey.Length; + if(cbKey <= 0) + { + Debug.Assert(false); // Need at least one byte + throw new ArgumentOutOfRangeException("pbKey"); + } - uint uKeyLen = (uint)pbKey.Length; - Debug.Assert(uKeyLen != 0); if(uKeyLen == 0) throw new ArgumentException(); + m_crsAlgorithm = a; - if(genAlgorithm == CrsAlgorithm.ArcFourVariant) + if(a == CrsAlgorithm.ChaCha20) + { + byte[] pbKey32 = new byte[32]; + byte[] pbIV12 = new byte[12]; + + using(SHA512Managed h = new SHA512Managed()) + { + byte[] pbHash = h.ComputeHash(pbKey); + Array.Copy(pbHash, pbKey32, 32); + Array.Copy(pbHash, 32, pbIV12, 0, 12); + MemUtil.ZeroByteArray(pbHash); + } + + m_chacha20 = new ChaCha20Cipher(pbKey32, pbIV12, true); + } + else if(a == CrsAlgorithm.Salsa20) + { + byte[] pbKey32 = CryptoUtil.HashSha256(pbKey); + byte[] pbIV8 = new byte[8] { 0xE8, 0x30, 0x09, 0x4B, + 0x97, 0x20, 0x5D, 0x2A }; // Unique constant + + m_salsa20 = new Salsa20Cipher(pbKey32, pbIV8); + } + else if(a == CrsAlgorithm.ArcFourVariant) { // Fill the state linearly m_pbState = new byte[256]; - for(uint w = 0; w < 256; ++w) m_pbState[w] = (byte)w; + for(int w = 0; w < 256; ++w) m_pbState[w] = (byte)w; unchecked { byte j = 0, t; - uint inxKey = 0; - for(uint w = 0; w < 256; ++w) // Key setup + int inxKey = 0; + for(int w = 0; w < 256; ++w) // Key setup { j += (byte)(m_pbState[w] + pbKey[inxKey]); @@ -106,25 +136,16 @@ namespace KeePassLib.Cryptography m_pbState[j] = t; ++inxKey; - if(inxKey >= uKeyLen) inxKey = 0; + if(inxKey >= cbKey) inxKey = 0; } } GetRandomBytes(512); // Increases security, see cryptanalysis } - else if(genAlgorithm == CrsAlgorithm.Salsa20) - { - SHA256Managed sha256 = new SHA256Managed(); - byte[] pbKey32 = sha256.ComputeHash(pbKey); - byte[] pbIV = new byte[8] { 0xE8, 0x30, 0x09, 0x4B, - 0x97, 0x20, 0x5D, 0x2A }; // Unique constant - - m_salsa20 = new Salsa20Cipher(pbKey32, pbIV); - } else // Unknown algorithm { Debug.Assert(false); - throw new ArgumentException(); + throw new ArgumentOutOfRangeException("a"); } } @@ -135,15 +156,23 @@ namespace KeePassLib.Cryptography /// Returns random bytes. public byte[] GetRandomBytes(uint uRequestedCount) { - if(uRequestedCount == 0) return new byte[0]; + if(uRequestedCount == 0) return MemUtil.EmptyByteArray; - byte[] pbRet = new byte[uRequestedCount]; + if(uRequestedCount > (uint)int.MaxValue) + throw new ArgumentOutOfRangeException("uRequestedCount"); + int cb = (int)uRequestedCount; - if(m_crsAlgorithm == CrsAlgorithm.ArcFourVariant) + byte[] pbRet = new byte[cb]; + + if(m_crsAlgorithm == CrsAlgorithm.ChaCha20) + m_chacha20.Encrypt(pbRet, 0, cb); + else if(m_crsAlgorithm == CrsAlgorithm.Salsa20) + m_salsa20.Encrypt(pbRet, 0, cb); + else if(m_crsAlgorithm == CrsAlgorithm.ArcFourVariant) { unchecked { - for(uint w = 0; w < uRequestedCount; ++w) + for(int w = 0; w < cb; ++w) { ++m_i; m_j += m_pbState[m_i]; @@ -157,8 +186,6 @@ namespace KeePassLib.Cryptography } } } - else if(m_crsAlgorithm == CrsAlgorithm.Salsa20) - m_salsa20.Encrypt(pbRet, pbRet.Length, false); else { Debug.Assert(false); } return pbRet; @@ -167,14 +194,7 @@ namespace KeePassLib.Cryptography public ulong GetRandomUInt64() { byte[] pb = GetRandomBytes(8); - - unchecked - { - return ((ulong)pb[0]) | ((ulong)pb[1] << 8) | - ((ulong)pb[2] << 16) | ((ulong)pb[3] << 24) | - ((ulong)pb[4] << 32) | ((ulong)pb[5] << 40) | - ((ulong)pb[6] << 48) | ((ulong)pb[7] << 56); - } + return MemUtil.BytesToUInt64(pb); } #if CRSBENCHMARK diff --git a/src/KeePassLib2Android/Cryptography/CryptoUtil.cs b/src/KeePassLib2Android/Cryptography/CryptoUtil.cs new file mode 100644 index 00000000..9cb7bcd6 --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/CryptoUtil.cs @@ -0,0 +1,126 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 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.Security.Cryptography; +using System.Text; + +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography +{ + public static class CryptoUtil + { + public static byte[] HashSha256(byte[] pbData) + { + if(pbData == null) throw new ArgumentNullException("pbData"); + + return HashSha256(pbData, 0, pbData.Length); + } + + public static byte[] HashSha256(byte[] pbData, int iOffset, int cbCount) + { + if(pbData == null) throw new ArgumentNullException("pbData"); + +#if DEBUG + byte[] pbCopy = new byte[pbData.Length]; + Array.Copy(pbData, pbCopy, pbData.Length); +#endif + + byte[] pbHash; + using(SHA256Managed h = new SHA256Managed()) + { + pbHash = h.ComputeHash(pbData, iOffset, cbCount); + } + +#if DEBUG + // Ensure the data has not been modified + Debug.Assert(MemUtil.ArraysEqual(pbData, pbCopy)); + + Debug.Assert((pbHash != null) && (pbHash.Length == 32)); + byte[] pbZero = new byte[32]; + Debug.Assert(!MemUtil.ArraysEqual(pbHash, pbZero)); +#endif + + return pbHash; + } + + /// + /// Create a cryptographic key of length + /// (in bytes) from . + /// + public static byte[] ResizeKey(byte[] pbIn, int iInOffset, + int cbIn, int cbOut) + { + if(pbIn == null) throw new ArgumentNullException("pbIn"); + if(cbOut < 0) throw new ArgumentOutOfRangeException("cbOut"); + + if(cbOut == 0) return MemUtil.EmptyByteArray; + + byte[] pbHash; + if(cbOut <= 32) pbHash = HashSha256(pbIn, iInOffset, cbIn); + else + { + using(SHA512Managed h = new SHA512Managed()) + { + pbHash = h.ComputeHash(pbIn, iInOffset, cbIn); + } + } + + if(cbOut == pbHash.Length) return pbHash; + + byte[] pbRet = new byte[cbOut]; + if(cbOut < pbHash.Length) + Array.Copy(pbHash, pbRet, cbOut); + else + { + int iPos = 0; + ulong r = 0; + while(iPos < cbOut) + { + Debug.Assert(pbHash.Length == 64); + using(HMACSHA256 h = new HMACSHA256(pbHash)) + { + byte[] pbR = MemUtil.UInt64ToBytes(r); + byte[] pbPart = h.ComputeHash(pbR); + + int cbCopy = Math.Min(cbOut - iPos, pbPart.Length); + Debug.Assert(cbCopy > 0); + + Array.Copy(pbPart, 0, pbRet, iPos, cbCopy); + iPos += cbCopy; + ++r; + + MemUtil.ZeroByteArray(pbPart); + } + } + Debug.Assert(iPos == cbOut); + } + +#if DEBUG + byte[] pbZero = new byte[pbHash.Length]; + Debug.Assert(!MemUtil.ArraysEqual(pbHash, pbZero)); +#endif + MemUtil.ZeroByteArray(pbHash); + return pbRet; + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/Hash/Blake2b.cs b/src/KeePassLib2Android/Cryptography/Hash/Blake2b.cs new file mode 100644 index 00000000..1a446aaa --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/Hash/Blake2b.cs @@ -0,0 +1,229 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// This implementation is based on the official reference C +// implementation by Samuel Neves (CC0 1.0 Universal). + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Security.Cryptography; +using System.Text; + +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.Hash +{ + public sealed class Blake2b : HashAlgorithm + { + private const int NbRounds = 12; + private const int NbBlockBytes = 128; + private const int NbMaxOutBytes = 64; + + private static readonly ulong[] g_vIV = new ulong[8] { + 0x6A09E667F3BCC908UL, 0xBB67AE8584CAA73BUL, + 0x3C6EF372FE94F82BUL, 0xA54FF53A5F1D36F1UL, + 0x510E527FADE682D1UL, 0x9B05688C2B3E6C1FUL, + 0x1F83D9ABFB41BD6BUL, 0x5BE0CD19137E2179UL + }; + + private static readonly int[] g_vSigma = new int[NbRounds * 16] { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3, + 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4, + 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8, + 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13, + 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9, + 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11, + 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10, + 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5, + 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 + }; + + private readonly int m_cbHashLength; + + private ulong[] m_h = new ulong[8]; + private ulong[] m_t = new ulong[2]; + private ulong[] m_f = new ulong[2]; + private byte[] m_buf = new byte[NbBlockBytes]; + private int m_cbBuf = 0; + + private ulong[] m_m = new ulong[16]; + private ulong[] m_v = new ulong[16]; + + public Blake2b() + { + m_cbHashLength = NbMaxOutBytes; + this.HashSizeValue = NbMaxOutBytes * 8; // Bits + + Initialize(); + } + + public Blake2b(int cbHashLength) + { + if((cbHashLength < 0) || (cbHashLength > NbMaxOutBytes)) + throw new ArgumentOutOfRangeException("cbHashLength"); + + m_cbHashLength = cbHashLength; + this.HashSizeValue = cbHashLength * 8; // Bits + + Initialize(); + } + + public override void Initialize() + { + Debug.Assert(m_h.Length == g_vIV.Length); + Array.Copy(g_vIV, m_h, m_h.Length); + + // Fan-out = 1, depth = 1 + m_h[0] ^= 0x0000000001010000UL ^ (ulong)m_cbHashLength; + + Array.Clear(m_t, 0, m_t.Length); + Array.Clear(m_f, 0, m_f.Length); + Array.Clear(m_buf, 0, m_buf.Length); + m_cbBuf = 0; + + Array.Clear(m_m, 0, m_m.Length); + Array.Clear(m_v, 0, m_v.Length); + } + + private static void G(ulong[] v, ulong[] m, int r16, int i, + int a, int b, int c, int d) + { + int p = r16 + i; + + v[a] += v[b] + m[g_vSigma[p]]; + v[d] = MemUtil.RotateRight64(v[d] ^ v[a], 32); + v[c] += v[d]; + v[b] = MemUtil.RotateRight64(v[b] ^ v[c], 24); + v[a] += v[b] + m[g_vSigma[p + 1]]; + v[d] = MemUtil.RotateRight64(v[d] ^ v[a], 16); + v[c] += v[d]; + v[b] = MemUtil.RotateRight64(v[b] ^ v[c], 63); + } + + private void Compress(byte[] pb, int iOffset) + { + ulong[] v = m_v; + ulong[] m = m_m; + ulong[] h = m_h; + + for(int i = 0; i < 16; ++i) + m[i] = MemUtil.BytesToUInt64(pb, iOffset + (i << 3)); + + Array.Copy(h, v, 8); + v[8] = g_vIV[0]; + v[9] = g_vIV[1]; + v[10] = g_vIV[2]; + v[11] = g_vIV[3]; + v[12] = g_vIV[4] ^ m_t[0]; + v[13] = g_vIV[5] ^ m_t[1]; + v[14] = g_vIV[6] ^ m_f[0]; + v[15] = g_vIV[7] ^ m_f[1]; + + for(int r = 0; r < NbRounds; ++r) + { + int r16 = r << 4; + + G(v, m, r16, 0, 0, 4, 8, 12); + G(v, m, r16, 2, 1, 5, 9, 13); + G(v, m, r16, 4, 2, 6, 10, 14); + G(v, m, r16, 6, 3, 7, 11, 15); + G(v, m, r16, 8, 0, 5, 10, 15); + G(v, m, r16, 10, 1, 6, 11, 12); + G(v, m, r16, 12, 2, 7, 8, 13); + G(v, m, r16, 14, 3, 4, 9, 14); + } + + for(int i = 0; i < 8; ++i) + h[i] ^= v[i] ^ v[i + 8]; + } + + private void IncrementCounter(ulong cb) + { + m_t[0] += cb; + if(m_t[0] < cb) ++m_t[1]; + } + + protected override void HashCore(byte[] array, int ibStart, int cbSize) + { + Debug.Assert(m_f[0] == 0); + + if((m_cbBuf + cbSize) > NbBlockBytes) // Not '>=' (buffer must not be empty) + { + int cbFill = NbBlockBytes - m_cbBuf; + if(cbFill > 0) Array.Copy(array, ibStart, m_buf, m_cbBuf, cbFill); + + IncrementCounter((ulong)NbBlockBytes); + Compress(m_buf, 0); + + m_cbBuf = 0; + cbSize -= cbFill; + ibStart += cbFill; + + while(cbSize > NbBlockBytes) // Not '>=' (buffer must not be empty) + { + IncrementCounter((ulong)NbBlockBytes); + Compress(array, ibStart); + + cbSize -= NbBlockBytes; + ibStart += NbBlockBytes; + } + } + + if(cbSize > 0) + { + Debug.Assert((m_cbBuf + cbSize) <= NbBlockBytes); + + Array.Copy(array, ibStart, m_buf, m_cbBuf, cbSize); + m_cbBuf += cbSize; + } + } + + protected override byte[] HashFinal() + { + if(m_f[0] != 0) { Debug.Assert(false); throw new InvalidOperationException(); } + Debug.Assert(((m_t[1] == 0) && (m_t[0] == 0)) || + (m_cbBuf > 0)); // Buffer must not be empty for last block processing + + m_f[0] = ulong.MaxValue; // Indicate last block + + int cbFill = NbBlockBytes - m_cbBuf; + if(cbFill > 0) Array.Clear(m_buf, m_cbBuf, cbFill); + + IncrementCounter((ulong)m_cbBuf); + Compress(m_buf, 0); + + byte[] pbHash = new byte[NbMaxOutBytes]; + for(int i = 0; i < m_h.Length; ++i) + MemUtil.UInt64ToBytesEx(m_h[i], pbHash, i << 3); + + if(m_cbHashLength == NbMaxOutBytes) return pbHash; + Debug.Assert(m_cbHashLength < NbMaxOutBytes); + + byte[] pbShort = new byte[m_cbHashLength]; + if(m_cbHashLength > 0) + Array.Copy(pbHash, pbShort, m_cbHashLength); + MemUtil.ZeroByteArray(pbHash); + return pbShort; + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/HashingStreamEx.cs b/src/KeePassLib2Android/Cryptography/HashingStreamEx.cs index ec7279c1..27446d69 100644 --- a/src/KeePassLib2Android/Cryptography/HashingStreamEx.cs +++ b/src/KeePassLib2Android/Cryptography/HashingStreamEx.cs @@ -34,7 +34,7 @@ namespace KeePassLib.Cryptography public sealed class HashingStreamEx : Stream { private Stream m_sBaseStream; - private bool m_bWriting; + private readonly bool m_bWriting; private HashAlgorithm m_hash; private byte[] m_pbFinalHash = null; @@ -67,7 +67,7 @@ namespace KeePassLib.Cryptography public override long Position { get { return m_sBaseStream.Position; } - set { throw new NotSupportedException(); } + set { Debug.Assert(false); throw new NotSupportedException(); } } public HashingStreamEx(Stream sBaseStream, bool bWriting, HashAlgorithm hashAlgorithm) @@ -114,7 +114,7 @@ namespace KeePassLib.Cryptography { try { - m_hash.TransformFinalBlock(new byte[0], 0, 0); + m_hash.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); m_pbFinalHash = m_hash.Hash; } diff --git a/src/KeePassLib2Android/Cryptography/KeyDerivation/AesKdf.cs b/src/KeePassLib2Android/Cryptography/KeyDerivation/AesKdf.cs new file mode 100644 index 00000000..b0644c4a --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/KeyDerivation/AesKdf.cs @@ -0,0 +1,270 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +#if KeePassUAP +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Parameters; +#else +using System.Security.Cryptography; +#endif + +using KeePassLib.Cryptography; +using KeePassLib.Native; +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.KeyDerivation +{ + public sealed class AesKdf : KdfEngine + { + private static readonly PwUuid g_uuid = new PwUuid(new byte[] { + 0xC9, 0xD9, 0xF3, 0x9A, 0x62, 0x8A, 0x44, 0x60, + 0xBF, 0x74, 0x0D, 0x08, 0xC1, 0x8A, 0x4F, 0xEA }); + + public const string ParamRounds = "R"; // UInt64 + public const string ParamSeed = "S"; // Byte[32] + + public override PwUuid Uuid + { + get { return g_uuid; } + } + + public override string Name + { + get { return "AES-KDF"; } + } + + public AesKdf() + { + } + + public override KdfParameters GetDefaultParameters() + { + KdfParameters p = base.GetDefaultParameters(); + p.SetUInt64(ParamRounds, PwDefs.DefaultKeyEncryptionRounds); + return p; + } + + public override void Randomize(KdfParameters p) + { + if(p == null) { Debug.Assert(false); return; } + Debug.Assert(g_uuid.Equals(p.KdfUuid)); + + byte[] pbSeed = CryptoRandom.Instance.GetRandomBytes(32); + p.SetByteArray(ParamSeed, pbSeed); + } + + public override byte[] Transform(byte[] pbMsg, KdfParameters p) + { + if(pbMsg == null) throw new ArgumentNullException("pbMsg"); + if(p == null) throw new ArgumentNullException("p"); + + Type tRounds = p.GetTypeOf(ParamRounds); + if(tRounds == null) throw new ArgumentNullException("p.Rounds"); + if(tRounds != typeof(ulong)) throw new ArgumentOutOfRangeException("p.Rounds"); + ulong uRounds = p.GetUInt64(ParamRounds, 0); + + byte[] pbSeed = p.GetByteArray(ParamSeed); + if(pbSeed == null) throw new ArgumentNullException("p.Seed"); + + if(pbMsg.Length != 32) + { + Debug.Assert(false); + pbMsg = CryptoUtil.HashSha256(pbMsg); + } + + if(pbSeed.Length != 32) + { + Debug.Assert(false); + pbSeed = CryptoUtil.HashSha256(pbSeed); + } + + return TransformKey(pbMsg, pbSeed, uRounds); + } + + private static byte[] TransformKey(byte[] pbOriginalKey32, byte[] pbKeySeed32, + ulong uNumRounds) + { + Debug.Assert((pbOriginalKey32 != null) && (pbOriginalKey32.Length == 32)); + if(pbOriginalKey32 == null) throw new ArgumentNullException("pbOriginalKey32"); + if(pbOriginalKey32.Length != 32) throw new ArgumentException(); + + Debug.Assert((pbKeySeed32 != null) && (pbKeySeed32.Length == 32)); + if(pbKeySeed32 == null) throw new ArgumentNullException("pbKeySeed32"); + if(pbKeySeed32.Length != 32) throw new ArgumentException(); + + byte[] pbNewKey = new byte[32]; + Array.Copy(pbOriginalKey32, pbNewKey, pbNewKey.Length); + + try + { + // Try to use the native library first + if(NativeLib.TransformKey256(pbNewKey, pbKeySeed32, uNumRounds)) + return CryptoUtil.HashSha256(pbNewKey); + + if(TransformKeyManaged(pbNewKey, pbKeySeed32, uNumRounds)) + return CryptoUtil.HashSha256(pbNewKey); + } + finally { MemUtil.ZeroByteArray(pbNewKey); } + + return null; + } + + internal static bool TransformKeyManaged(byte[] pbNewKey32, byte[] pbKeySeed32, + ulong uNumRounds) + { +#if KeePassUAP + KeyParameter kp = new KeyParameter(pbKeySeed32); + AesEngine aes = new AesEngine(); + aes.Init(true, kp); + + for(ulong i = 0; i < uNumRounds; ++i) + { + aes.ProcessBlock(pbNewKey32, 0, pbNewKey32, 0); + aes.ProcessBlock(pbNewKey32, 16, pbNewKey32, 16); + } +#else + byte[] pbIV = new byte[16]; + Array.Clear(pbIV, 0, pbIV.Length); + + RijndaelManaged r = new RijndaelManaged(); + if(r.BlockSize != 128) // AES block size + { + Debug.Assert(false); + r.BlockSize = 128; + } + + r.IV = pbIV; + r.Mode = CipherMode.ECB; + r.KeySize = 256; + r.Key = pbKeySeed32; + ICryptoTransform iCrypt = r.CreateEncryptor(); + + // !iCrypt.CanReuseTransform -- doesn't work with Mono + if((iCrypt == null) || (iCrypt.InputBlockSize != 16) || + (iCrypt.OutputBlockSize != 16)) + { + Debug.Assert(false, "Invalid ICryptoTransform."); + Debug.Assert((iCrypt.InputBlockSize == 16), "Invalid input block size!"); + Debug.Assert((iCrypt.OutputBlockSize == 16), "Invalid output block size!"); + return false; + } + + for(ulong i = 0; i < uNumRounds; ++i) + { + iCrypt.TransformBlock(pbNewKey32, 0, 16, pbNewKey32, 0); + iCrypt.TransformBlock(pbNewKey32, 16, 16, pbNewKey32, 16); + } +#endif + + return true; + } + + public override KdfParameters GetBestParameters(uint uMilliseconds) + { + const ulong uStep = 3001; + ulong uRounds; + + KdfParameters p = GetDefaultParameters(); + + // Try native method + if(NativeLib.TransformKeyBenchmark256(uMilliseconds, out uRounds)) + { + p.SetUInt64(ParamRounds, uRounds); + return p; + } + + byte[] pbKey = new byte[32]; + byte[] pbNewKey = new byte[32]; + for(int i = 0; i < pbKey.Length; ++i) + { + pbKey[i] = (byte)i; + pbNewKey[i] = (byte)i; + } + +#if KeePassUAP + KeyParameter kp = new KeyParameter(pbKey); + AesEngine aes = new AesEngine(); + aes.Init(true, kp); +#else + byte[] pbIV = new byte[16]; + Array.Clear(pbIV, 0, pbIV.Length); + + RijndaelManaged r = new RijndaelManaged(); + if(r.BlockSize != 128) // AES block size + { + Debug.Assert(false); + r.BlockSize = 128; + } + + r.IV = pbIV; + r.Mode = CipherMode.ECB; + r.KeySize = 256; + r.Key = pbKey; + ICryptoTransform iCrypt = r.CreateEncryptor(); + + // !iCrypt.CanReuseTransform -- doesn't work with Mono + if((iCrypt == null) || (iCrypt.InputBlockSize != 16) || + (iCrypt.OutputBlockSize != 16)) + { + Debug.Assert(false, "Invalid ICryptoTransform."); + Debug.Assert(iCrypt.InputBlockSize == 16, "Invalid input block size!"); + Debug.Assert(iCrypt.OutputBlockSize == 16, "Invalid output block size!"); + + p.SetUInt64(ParamRounds, PwDefs.DefaultKeyEncryptionRounds); + return p; + } +#endif + + uRounds = 0; + int tStart = Environment.TickCount; + while(true) + { + for(ulong j = 0; j < uStep; ++j) + { +#if KeePassUAP + aes.ProcessBlock(pbNewKey, 0, pbNewKey, 0); + aes.ProcessBlock(pbNewKey, 16, pbNewKey, 16); +#else + iCrypt.TransformBlock(pbNewKey, 0, 16, pbNewKey, 0); + iCrypt.TransformBlock(pbNewKey, 16, 16, pbNewKey, 16); +#endif + } + + uRounds += uStep; + if(uRounds < uStep) // Overflow check + { + uRounds = ulong.MaxValue; + break; + } + + uint tElapsed = (uint)(Environment.TickCount - tStart); + if(tElapsed > uMilliseconds) break; + } + + p.SetUInt64(ParamRounds, uRounds); + return p; + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/KeyDerivation/Argon2Kdf.Core.cs b/src/KeePassLib2Android/Cryptography/KeyDerivation/Argon2Kdf.Core.cs new file mode 100644 index 00000000..bd90c427 --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/KeyDerivation/Argon2Kdf.Core.cs @@ -0,0 +1,610 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// This implementation is based on the official reference C +// implementation by Daniel Dinu and Dmitry Khovratovich (CC0 1.0). + +// Relative iterations (* = B2ROUND_ARRAYS \\ G_INLINED): +// * | false true +// ------+----------- +// false | 8885 9618 +// true | 9009 9636 +#define ARGON2_B2ROUND_ARRAYS +#define ARGON2_G_INLINED + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; + +using KeePassLib.Cryptography.Hash; +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.KeyDerivation +{ + public sealed partial class Argon2Kdf : KdfEngine + { + private const ulong NbBlockSize = 1024; + private const ulong NbBlockSizeInQW = NbBlockSize / 8UL; + private const ulong NbSyncPoints = 4; + + private const int NbPreHashDigestLength = 64; + private const int NbPreHashSeedLength = NbPreHashDigestLength + 8; + +#if ARGON2_B2ROUND_ARRAYS + private static int[][] g_vFBCols = null; + private static int[][] g_vFBRows = null; +#endif + + private sealed class Argon2Ctx + { + public uint Version = 0; + + public ulong Lanes = 0; + public ulong TCost = 0; + public ulong MCost = 0; + public ulong MemoryBlocks = 0; + public ulong SegmentLength = 0; + public ulong LaneLength = 0; + + public ulong[] Mem = null; + } + + private sealed class Argon2ThreadInfo + { + public Argon2Ctx Context = null; + public ManualResetEvent Finished = new ManualResetEvent(false); + + public ulong Pass = 0; + public ulong Lane = 0; + public ulong Slice = 0; + public ulong Index = 0; + } + + private static byte[] Argon2d(byte[] pbMsg, byte[] pbSalt, uint uParallel, + ulong uMem, ulong uIt, int cbOut, uint uVersion, byte[] pbSecretKey, + byte[] pbAssocData) + { + pbSecretKey = (pbSecretKey ?? MemUtil.EmptyByteArray); + pbAssocData = (pbAssocData ?? MemUtil.EmptyByteArray); + +#if ARGON2_B2ROUND_ARRAYS + InitB2RoundIndexArrays(); +#endif + + Argon2Ctx ctx = new Argon2Ctx(); + ctx.Version = uVersion; + + ctx.Lanes = uParallel; + ctx.TCost = uIt; + ctx.MCost = uMem / NbBlockSize; + ctx.MemoryBlocks = Math.Max(ctx.MCost, 2UL * NbSyncPoints * ctx.Lanes); + + ctx.SegmentLength = ctx.MemoryBlocks / (ctx.Lanes * NbSyncPoints); + ctx.MemoryBlocks = ctx.SegmentLength * ctx.Lanes * NbSyncPoints; + + ctx.LaneLength = ctx.SegmentLength * NbSyncPoints; + + Debug.Assert(NbBlockSize == (NbBlockSizeInQW * + (ulong)Marshal.SizeOf(typeof(ulong)))); + ctx.Mem = new ulong[ctx.MemoryBlocks * NbBlockSizeInQW]; + + Blake2b h = new Blake2b(); + + // Initial hash + Debug.Assert(h.HashSize == (NbPreHashDigestLength * 8)); + byte[] pbBuf = new byte[4]; + MemUtil.UInt32ToBytesEx(uParallel, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + MemUtil.UInt32ToBytesEx((uint)cbOut, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + MemUtil.UInt32ToBytesEx((uint)ctx.MCost, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + MemUtil.UInt32ToBytesEx((uint)uIt, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + MemUtil.UInt32ToBytesEx(uVersion, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + MemUtil.UInt32ToBytesEx(0, pbBuf, 0); // Argon2d type = 0 + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + MemUtil.UInt32ToBytesEx((uint)pbMsg.Length, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + h.TransformBlock(pbMsg, 0, pbMsg.Length, pbMsg, 0); + MemUtil.UInt32ToBytesEx((uint)pbSalt.Length, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + h.TransformBlock(pbSalt, 0, pbSalt.Length, pbSalt, 0); + MemUtil.UInt32ToBytesEx((uint)pbSecretKey.Length, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + h.TransformBlock(pbSecretKey, 0, pbSecretKey.Length, pbSecretKey, 0); + MemUtil.UInt32ToBytesEx((uint)pbAssocData.Length, pbBuf, 0); + h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); + h.TransformBlock(pbAssocData, 0, pbAssocData.Length, pbAssocData, 0); + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + byte[] pbH0 = h.Hash; + Debug.Assert(pbH0.Length == 64); + + byte[] pbBlockHash = new byte[NbPreHashSeedLength]; + Array.Copy(pbH0, pbBlockHash, pbH0.Length); + MemUtil.ZeroByteArray(pbH0); + + FillFirstBlocks(ctx, pbBlockHash, h); + MemUtil.ZeroByteArray(pbBlockHash); + + FillMemoryBlocks(ctx); + + byte[] pbOut = FinalHash(ctx, cbOut, h); + + h.Clear(); + MemUtil.ZeroArray(ctx.Mem); + return pbOut; + } + + private static void LoadBlock(ulong[] pqDst, ulong uDstOffset, byte[] pbIn) + { + // for(ulong i = 0; i < NbBlockSizeInQW; ++i) + // pqDst[uDstOffset + i] = MemUtil.BytesToUInt64(pbIn, (int)(i << 3)); + + Debug.Assert((uDstOffset + NbBlockSizeInQW - 1UL) <= (ulong)int.MaxValue); + int iDstOffset = (int)uDstOffset; + for(int i = 0; i < (int)NbBlockSizeInQW; ++i) + pqDst[iDstOffset + i] = MemUtil.BytesToUInt64(pbIn, i << 3); + } + + private static void StoreBlock(byte[] pbDst, ulong[] pqSrc) + { + for(int i = 0; i < (int)NbBlockSizeInQW; ++i) + MemUtil.UInt64ToBytesEx(pqSrc[i], pbDst, i << 3); + } + + private static void CopyBlock(ulong[] vDst, ulong uDstOffset, ulong[] vSrc, + ulong uSrcOffset) + { + // for(ulong i = 0; i < NbBlockSizeInQW; ++i) + // vDst[uDstOffset + i] = vSrc[uSrcOffset + i]; + + // Debug.Assert((uDstOffset + NbBlockSizeInQW - 1UL) <= (ulong)int.MaxValue); + // Debug.Assert((uSrcOffset + NbBlockSizeInQW - 1UL) <= (ulong)int.MaxValue); + // int iDstOffset = (int)uDstOffset; + // int iSrcOffset = (int)uSrcOffset; + // for(int i = 0; i < (int)NbBlockSizeInQW; ++i) + // vDst[iDstOffset + i] = vSrc[iSrcOffset + i]; + + Array.Copy(vSrc, (long)uSrcOffset, vDst, (long)uDstOffset, + (long)NbBlockSizeInQW); + } + + private static void XorBlock(ulong[] vDst, ulong uDstOffset, ulong[] vSrc, + ulong uSrcOffset) + { + // for(ulong i = 0; i < NbBlockSizeInQW; ++i) + // vDst[uDstOffset + i] ^= vSrc[uSrcOffset + i]; + + Debug.Assert((uDstOffset + NbBlockSizeInQW - 1UL) <= (ulong)int.MaxValue); + Debug.Assert((uSrcOffset + NbBlockSizeInQW - 1UL) <= (ulong)int.MaxValue); + int iDstOffset = (int)uDstOffset; + int iSrcOffset = (int)uSrcOffset; + for(int i = 0; i < (int)NbBlockSizeInQW; ++i) + vDst[iDstOffset + i] ^= vSrc[iSrcOffset + i]; + } + + private static void Blake2bLong(byte[] pbOut, int cbOut, + byte[] pbIn, int cbIn, Blake2b h) + { + Debug.Assert((h != null) && (h.HashSize == (64 * 8))); + + byte[] pbOutLen = new byte[4]; + MemUtil.UInt32ToBytesEx((uint)cbOut, pbOutLen, 0); + + if(cbOut <= 64) + { + Blake2b hOut = ((cbOut == 64) ? h : new Blake2b(cbOut)); + if(cbOut == 64) hOut.Initialize(); + + hOut.TransformBlock(pbOutLen, 0, pbOutLen.Length, pbOutLen, 0); + hOut.TransformBlock(pbIn, 0, cbIn, pbIn, 0); + hOut.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + Array.Copy(hOut.Hash, pbOut, cbOut); + + if(cbOut < 64) hOut.Clear(); + return; + } + + h.Initialize(); + h.TransformBlock(pbOutLen, 0, pbOutLen.Length, pbOutLen, 0); + h.TransformBlock(pbIn, 0, cbIn, pbIn, 0); + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + byte[] pbOutBuffer = new byte[64]; + Array.Copy(h.Hash, pbOutBuffer, pbOutBuffer.Length); + + int ibOut = 64 / 2; + Array.Copy(pbOutBuffer, pbOut, ibOut); + int cbToProduce = cbOut - ibOut; + + h.Initialize(); + while(cbToProduce > 64) + { + byte[] pbHash = h.ComputeHash(pbOutBuffer); + Array.Copy(pbHash, pbOutBuffer, 64); + + Array.Copy(pbHash, 0, pbOut, ibOut, 64 / 2); + ibOut += 64 / 2; + cbToProduce -= 64 / 2; + + MemUtil.ZeroByteArray(pbHash); + } + + using(Blake2b hOut = new Blake2b(cbToProduce)) + { + byte[] pbHash = hOut.ComputeHash(pbOutBuffer); + Array.Copy(pbHash, 0, pbOut, ibOut, cbToProduce); + + MemUtil.ZeroByteArray(pbHash); + } + + MemUtil.ZeroByteArray(pbOutBuffer); + } + +#if !ARGON2_G_INLINED + private static ulong BlaMka(ulong x, ulong y) + { + ulong xy = (x & 0xFFFFFFFFUL) * (y & 0xFFFFFFFFUL); + return (x + y + (xy << 1)); + } + + private static void G(ulong[] v, int a, int b, int c, int d) + { + ulong va = v[a], vb = v[b], vc = v[c], vd = v[d]; + + va = BlaMka(va, vb); + vd = MemUtil.RotateRight64(vd ^ va, 32); + vc = BlaMka(vc, vd); + vb = MemUtil.RotateRight64(vb ^ vc, 24); + va = BlaMka(va, vb); + vd = MemUtil.RotateRight64(vd ^ va, 16); + vc = BlaMka(vc, vd); + vb = MemUtil.RotateRight64(vb ^ vc, 63); + + v[a] = va; + v[b] = vb; + v[c] = vc; + v[d] = vd; + } +#else + private static void G(ulong[] v, int a, int b, int c, int d) + { + ulong va = v[a], vb = v[b], vc = v[c], vd = v[d]; + + ulong xy = (va & 0xFFFFFFFFUL) * (vb & 0xFFFFFFFFUL); + va += vb + (xy << 1); + + vd = MemUtil.RotateRight64(vd ^ va, 32); + + xy = (vc & 0xFFFFFFFFUL) * (vd & 0xFFFFFFFFUL); + vc += vd + (xy << 1); + + vb = MemUtil.RotateRight64(vb ^ vc, 24); + + xy = (va & 0xFFFFFFFFUL) * (vb & 0xFFFFFFFFUL); + va += vb + (xy << 1); + + vd = MemUtil.RotateRight64(vd ^ va, 16); + + xy = (vc & 0xFFFFFFFFUL) * (vd & 0xFFFFFFFFUL); + vc += vd + (xy << 1); + + vb = MemUtil.RotateRight64(vb ^ vc, 63); + + v[a] = va; + v[b] = vb; + v[c] = vc; + v[d] = vd; + } +#endif + +#if ARGON2_B2ROUND_ARRAYS + private static void Blake2RoundNoMsg(ulong[] pbR, int[] v) + { + G(pbR, v[0], v[4], v[8], v[12]); + G(pbR, v[1], v[5], v[9], v[13]); + G(pbR, v[2], v[6], v[10], v[14]); + G(pbR, v[3], v[7], v[11], v[15]); + G(pbR, v[0], v[5], v[10], v[15]); + G(pbR, v[1], v[6], v[11], v[12]); + G(pbR, v[2], v[7], v[8], v[13]); + G(pbR, v[3], v[4], v[9], v[14]); + } +#else + private static void Blake2RoundNoMsgCols16i(ulong[] pbR, int i) + { + G(pbR, i, i + 4, i + 8, i + 12); + G(pbR, i + 1, i + 5, i + 9, i + 13); + G(pbR, i + 2, i + 6, i + 10, i + 14); + G(pbR, i + 3, i + 7, i + 11, i + 15); + G(pbR, i, i + 5, i + 10, i + 15); + G(pbR, i + 1, i + 6, i + 11, i + 12); + G(pbR, i + 2, i + 7, i + 8, i + 13); + G(pbR, i + 3, i + 4, i + 9, i + 14); + } + + private static void Blake2RoundNoMsgRows2i(ulong[] pbR, int i) + { + G(pbR, i, i + 32, i + 64, i + 96); + G(pbR, i + 1, i + 33, i + 65, i + 97); + G(pbR, i + 16, i + 48, i + 80, i + 112); + G(pbR, i + 17, i + 49, i + 81, i + 113); + G(pbR, i, i + 33, i + 80, i + 113); + G(pbR, i + 1, i + 48, i + 81, i + 96); + G(pbR, i + 16, i + 49, i + 64, i + 97); + G(pbR, i + 17, i + 32, i + 65, i + 112); + } +#endif + + private static void FillFirstBlocks(Argon2Ctx ctx, byte[] pbBlockHash, + Blake2b h) + { + byte[] pbBlock = new byte[NbBlockSize]; + + for(ulong l = 0; l < ctx.Lanes; ++l) + { + MemUtil.UInt32ToBytesEx(0, pbBlockHash, NbPreHashDigestLength); + MemUtil.UInt32ToBytesEx((uint)l, pbBlockHash, NbPreHashDigestLength + 4); + + Blake2bLong(pbBlock, (int)NbBlockSize, pbBlockHash, + NbPreHashSeedLength, h); + LoadBlock(ctx.Mem, l * ctx.LaneLength * NbBlockSizeInQW, pbBlock); + + MemUtil.UInt32ToBytesEx(1, pbBlockHash, NbPreHashDigestLength); + + Blake2bLong(pbBlock, (int)NbBlockSize, pbBlockHash, + NbPreHashSeedLength, h); + LoadBlock(ctx.Mem, (l * ctx.LaneLength + 1UL) * NbBlockSizeInQW, pbBlock); + } + + MemUtil.ZeroByteArray(pbBlock); + } + + private static ulong IndexAlpha(Argon2Ctx ctx, Argon2ThreadInfo ti, + uint uPseudoRand, bool bSameLane) + { + ulong uRefAreaSize; + if(ti.Pass == 0) + { + if(ti.Slice == 0) + { + Debug.Assert(ti.Index > 0); + uRefAreaSize = ti.Index - 1UL; + } + else + { + if(bSameLane) + uRefAreaSize = ti.Slice * ctx.SegmentLength + + ti.Index - 1UL; + else + uRefAreaSize = ti.Slice * ctx.SegmentLength - + ((ti.Index == 0UL) ? 1UL : 0UL); + } + } + else + { + if(bSameLane) + uRefAreaSize = ctx.LaneLength - ctx.SegmentLength + + ti.Index - 1UL; + else + uRefAreaSize = ctx.LaneLength - ctx.SegmentLength - + ((ti.Index == 0) ? 1UL : 0UL); + } + Debug.Assert(uRefAreaSize <= (ulong)uint.MaxValue); + + ulong uRelPos = uPseudoRand; + uRelPos = (uRelPos * uRelPos) >> 32; + uRelPos = uRefAreaSize - 1UL - ((uRefAreaSize * uRelPos) >> 32); + + ulong uStart = 0; + if(ti.Pass != 0) + uStart = (((ti.Slice + 1UL) == NbSyncPoints) ? 0UL : + ((ti.Slice + 1UL) * ctx.SegmentLength)); + Debug.Assert(uStart <= (ulong)uint.MaxValue); + + Debug.Assert(ctx.LaneLength <= (ulong)uint.MaxValue); + return ((uStart + uRelPos) % ctx.LaneLength); + } + + private static void FillMemoryBlocks(Argon2Ctx ctx) + { + int np = (int)ctx.Lanes; + Argon2ThreadInfo[] v = new Argon2ThreadInfo[np]; + + for(ulong r = 0; r < ctx.TCost; ++r) + { + for(ulong s = 0; s < NbSyncPoints; ++s) + { + for(int l = 0; l < np; ++l) + { + Argon2ThreadInfo ti = new Argon2ThreadInfo(); + ti.Context = ctx; + + ti.Pass = r; + ti.Lane = (ulong)l; + ti.Slice = s; + + if(!ThreadPool.QueueUserWorkItem(FillSegmentThr, ti)) + { + Debug.Assert(false); + throw new OutOfMemoryException(); + } + + v[l] = ti; + } + + for(int l = 0; l < np; ++l) + v[l].Finished.WaitOne(); + } + } + } + + private static void FillSegmentThr(object o) + { + Argon2ThreadInfo ti = (o as Argon2ThreadInfo); + if(ti == null) { Debug.Assert(false); return; } + + try + { + Argon2Ctx ctx = ti.Context; + if(ctx == null) { Debug.Assert(false); return; } + + Debug.Assert(ctx.Version >= MinVersion); + bool bCanXor = (ctx.Version >= 0x13U); + + ulong uStart = 0; + if((ti.Pass == 0) && (ti.Slice == 0)) uStart = 2; + + ulong uCur = (ti.Lane * ctx.LaneLength) + (ti.Slice * + ctx.SegmentLength) + uStart; + + ulong uPrev = (((uCur % ctx.LaneLength) == 0) ? + (uCur + ctx.LaneLength - 1UL) : (uCur - 1UL)); + + ulong[] pbR = new ulong[NbBlockSizeInQW]; + ulong[] pbTmp = new ulong[NbBlockSizeInQW]; + + for(ulong i = uStart; i < ctx.SegmentLength; ++i) + { + if((uCur % ctx.LaneLength) == 1) + uPrev = uCur - 1UL; + + ulong uPseudoRand = ctx.Mem[uPrev * NbBlockSizeInQW]; + ulong uRefLane = (uPseudoRand >> 32) % ctx.Lanes; + if((ti.Pass == 0) && (ti.Slice == 0)) + uRefLane = ti.Lane; + + ti.Index = i; + ulong uRefIndex = IndexAlpha(ctx, ti, (uint)uPseudoRand, + (uRefLane == ti.Lane)); + + ulong uRefBlockIndex = (ctx.LaneLength * uRefLane + + uRefIndex) * NbBlockSizeInQW; + ulong uCurBlockIndex = uCur * NbBlockSizeInQW; + + FillBlock(ctx.Mem, uPrev * NbBlockSizeInQW, uRefBlockIndex, + uCurBlockIndex, ((ti.Pass != 0) && bCanXor), pbR, pbTmp); + + ++uCur; + ++uPrev; + } + + MemUtil.ZeroArray(pbR); + MemUtil.ZeroArray(pbTmp); + } + catch(Exception) { Debug.Assert(false); } + + try { ti.Finished.Set(); } + catch(Exception) { Debug.Assert(false); } + } + +#if ARGON2_B2ROUND_ARRAYS + private static void InitB2RoundIndexArrays() + { + int[][] vCols = g_vFBCols; + if(vCols == null) + { + vCols = new int[8][]; + Debug.Assert(vCols.Length == 8); + int e = 0; + for(int i = 0; i < 8; ++i) + { + vCols[i] = new int[16]; + for(int j = 0; j < 16; ++j) + { + vCols[i][j] = e; + ++e; + } + } + + g_vFBCols = vCols; + } + + int[][] vRows = g_vFBRows; + if(vRows == null) + { + vRows = new int[8][]; + for(int i = 0; i < 8; ++i) + { + vRows[i] = new int[16]; + for(int j = 0; j < 16; ++j) + { + int jh = j / 2; + vRows[i][j] = (2 * i) + (16 * jh) + (j & 1); + } + } + + g_vFBRows = vRows; + } + } +#endif + + private static void FillBlock(ulong[] pMem, ulong uPrev, ulong uRef, + ulong uNext, bool bXor, ulong[] pbR, ulong[] pbTmp) + { + CopyBlock(pbR, 0, pMem, uRef); + XorBlock(pbR, 0, pMem, uPrev); + CopyBlock(pbTmp, 0, pbR, 0); + if(bXor) XorBlock(pbTmp, 0, pMem, uNext); + +#if ARGON2_B2ROUND_ARRAYS + int[][] vCols = g_vFBCols; + int[][] vRows = g_vFBRows; + for(int i = 0; i < 8; ++i) + Blake2RoundNoMsg(pbR, vCols[i]); + for(int i = 0; i < 8; ++i) + Blake2RoundNoMsg(pbR, vRows[i]); +#else + for(int i = 0; i < (8 * 16); i += 16) + Blake2RoundNoMsgCols16i(pbR, i); + for(int i = 0; i < (8 * 2); i += 2) + Blake2RoundNoMsgRows2i(pbR, i); +#endif + + CopyBlock(pMem, uNext, pbTmp, 0); + XorBlock(pMem, uNext, pbR, 0); + } + + private static byte[] FinalHash(Argon2Ctx ctx, int cbOut, Blake2b h) + { + ulong[] pqBlockHash = new ulong[NbBlockSizeInQW]; + CopyBlock(pqBlockHash, 0, ctx.Mem, (ctx.LaneLength - 1UL) * + NbBlockSizeInQW); + for(ulong l = 1; l < ctx.Lanes; ++l) + XorBlock(pqBlockHash, 0, ctx.Mem, (l * ctx.LaneLength + + ctx.LaneLength - 1UL) * NbBlockSizeInQW); + + byte[] pbBlockHashBytes = new byte[NbBlockSize]; + StoreBlock(pbBlockHashBytes, pqBlockHash); + + byte[] pbOut = new byte[cbOut]; + Blake2bLong(pbOut, cbOut, pbBlockHashBytes, (int)NbBlockSize, h); + + MemUtil.ZeroArray(pqBlockHash); + MemUtil.ZeroByteArray(pbBlockHashBytes); + return pbOut; + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/KeyDerivation/Argon2Kdf.cs b/src/KeePassLib2Android/Cryptography/KeyDerivation/Argon2Kdf.cs new file mode 100644 index 00000000..69416ad2 --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/KeyDerivation/Argon2Kdf.cs @@ -0,0 +1,144 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace KeePassLib.Cryptography.KeyDerivation +{ + public sealed partial class Argon2Kdf : KdfEngine + { + private static readonly PwUuid g_uuid = new PwUuid(new byte[] { + 0xEF, 0x63, 0x6D, 0xDF, 0x8C, 0x29, 0x44, 0x4B, + 0x91, 0xF7, 0xA9, 0xA4, 0x03, 0xE3, 0x0A, 0x0C }); + + public const string ParamSalt = "S"; // Byte[] + public const string ParamParallelism = "P"; // UInt32 + public const string ParamMemory = "M"; // UInt64 + public const string ParamIterations = "I"; // UInt64 + public const string ParamVersion = "V"; // UInt32 + public const string ParamSecretKey = "K"; // Byte[] + public const string ParamAssocData = "A"; // Byte[] + + private const uint MinVersion = 0x10; + private const uint MaxVersion = 0x13; + + private const int MinSalt = 8; + private const int MaxSalt = int.MaxValue; // .NET limit; 2^32 - 1 in spec + + internal const ulong MinIterations = 1; + internal const ulong MaxIterations = uint.MaxValue; + + internal const ulong MinMemory = 1024 * 8; // For parallelism = 1 + // internal const ulong MaxMemory = (ulong)uint.MaxValue * 1024UL; // Spec + internal const ulong MaxMemory = int.MaxValue; // .NET limit + + internal const uint MinParallelism = 1; + internal const uint MaxParallelism = (1 << 24) - 1; + + internal const ulong DefaultIterations = 2; + internal const ulong DefaultMemory = 1024 * 1024; // 1 MB + internal const uint DefaultParallelism = 2; + + public override PwUuid Uuid + { + get { return g_uuid; } + } + + public override string Name + { + get { return "Argon2"; } + } + + public Argon2Kdf() + { + } + + public override KdfParameters GetDefaultParameters() + { + KdfParameters p = base.GetDefaultParameters(); + + p.SetUInt32(ParamVersion, MaxVersion); + + p.SetUInt64(ParamIterations, DefaultIterations); + p.SetUInt64(ParamMemory, DefaultMemory); + p.SetUInt32(ParamParallelism, DefaultParallelism); + + return p; + } + + public override void Randomize(KdfParameters p) + { + if(p == null) { Debug.Assert(false); return; } + Debug.Assert(g_uuid.Equals(p.KdfUuid)); + + byte[] pb = CryptoRandom.Instance.GetRandomBytes(32); + p.SetByteArray(ParamSalt, pb); + } + + public override byte[] Transform(byte[] pbMsg, KdfParameters p) + { + if(pbMsg == null) throw new ArgumentNullException("pbMsg"); + if(p == null) throw new ArgumentNullException("p"); + + byte[] pbSalt = p.GetByteArray(ParamSalt); + if(pbSalt == null) + throw new ArgumentNullException("p.Salt"); + if((pbSalt.Length < MinSalt) || (pbSalt.Length > MaxSalt)) + throw new ArgumentOutOfRangeException("p.Salt"); + + uint uPar = p.GetUInt32(ParamParallelism, 0); + if((uPar < MinParallelism) || (uPar > MaxParallelism)) + throw new ArgumentOutOfRangeException("p.Parallelism"); + + ulong uMem = p.GetUInt64(ParamMemory, 0); + if((uMem < MinMemory) || (uMem > MaxMemory)) + throw new ArgumentOutOfRangeException("p.Memory"); + + ulong uIt = p.GetUInt64(ParamIterations, 0); + if((uIt < MinIterations) || (uIt > MaxIterations)) + throw new ArgumentOutOfRangeException("p.Iterations"); + + uint v = p.GetUInt32(ParamVersion, 0); + if((v < MinVersion) || (v > MaxVersion)) + throw new ArgumentOutOfRangeException("p.Version"); + + byte[] pbSecretKey = p.GetByteArray(ParamSecretKey); + byte[] pbAssocData = p.GetByteArray(ParamAssocData); + + byte[] pbRet = Argon2d(pbMsg, pbSalt, uPar, uMem, uIt, + 32, v, pbSecretKey, pbAssocData); + + if(uMem > (100UL * 1024UL * 1024UL)) GC.Collect(); + return pbRet; + } + + public override KdfParameters GetBestParameters(uint uMilliseconds) + { + KdfParameters p = GetDefaultParameters(); + Randomize(p); + + MaximizeParamUInt64(p, ParamIterations, MinIterations, + MaxIterations, uMilliseconds, true); + return p; + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfEngine.cs b/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfEngine.cs new file mode 100644 index 00000000..74d0dd25 --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfEngine.cs @@ -0,0 +1,142 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace KeePassLib.Cryptography.KeyDerivation +{ + public abstract class KdfEngine + { + public abstract PwUuid Uuid + { + get; + } + + public abstract string Name + { + get; + } + + public virtual KdfParameters GetDefaultParameters() + { + return new KdfParameters(this.Uuid); + } + + /// + /// Generate random seeds and store them in . + /// + public virtual void Randomize(KdfParameters p) + { + Debug.Assert(p != null); + Debug.Assert(p.KdfUuid.Equals(this.Uuid)); + } + + public abstract byte[] Transform(byte[] pbMsg, KdfParameters p); + + public virtual KdfParameters GetBestParameters(uint uMilliseconds) + { + throw new NotImplementedException(); + } + + protected void MaximizeParamUInt64(KdfParameters p, string strName, + ulong uMin, ulong uMax, uint uMilliseconds, bool bInterpSearch) + { + if(p == null) { Debug.Assert(false); return; } + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return; } + if(uMin > uMax) { Debug.Assert(false); return; } + + if(uMax > (ulong.MaxValue >> 1)) + { + Debug.Assert(false); + uMax = ulong.MaxValue >> 1; + + if(uMin > uMax) { p.SetUInt64(strName, uMin); return; } + } + + byte[] pbMsg = new byte[32]; + for(int i = 0; i < pbMsg.Length; ++i) pbMsg[i] = (byte)i; + + ulong uLow = uMin; + ulong uHigh = uMin + 1UL; + long tLow = 0; + long tHigh = 0; + long tTarget = (long)uMilliseconds; + + // Determine range + while(uHigh <= uMax) + { + p.SetUInt64(strName, uHigh); + + // GC.Collect(); + Stopwatch sw = Stopwatch.StartNew(); + Transform(pbMsg, p); + sw.Stop(); + + tHigh = sw.ElapsedMilliseconds; + if(tHigh > tTarget) break; + + uLow = uHigh; + tLow = tHigh; + uHigh <<= 1; + } + if(uHigh > uMax) { uHigh = uMax; tHigh = 0; } + if(uLow > uHigh) uLow = uHigh; // Skips to end + + // Find optimal number of iterations + while((uHigh - uLow) >= 2UL) + { + ulong u = (uHigh + uLow) >> 1; // Binary search + // Interpolation search, if possible + if(bInterpSearch && (tLow > 0) && (tHigh > tTarget) && + (tLow <= tTarget)) + { + u = uLow + (((uHigh - uLow) * (ulong)(tTarget - tLow)) / + (ulong)(tHigh - tLow)); + if((u >= uLow) && (u <= uHigh)) + { + u = Math.Max(u, uLow + 1UL); + u = Math.Min(u, uHigh - 1UL); + } + else + { + Debug.Assert(false); + u = (uHigh + uLow) >> 1; + } + } + + p.SetUInt64(strName, u); + + // GC.Collect(); + Stopwatch sw = Stopwatch.StartNew(); + Transform(pbMsg, p); + sw.Stop(); + + long t = sw.ElapsedMilliseconds; + if(t == tTarget) { uLow = u; break; } + else if(t > tTarget) { uHigh = u; tHigh = t; } + else { uLow = u; tLow = t; } + } + + p.SetUInt64(strName, uLow); + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfParameters.cs b/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfParameters.cs new file mode 100644 index 00000000..3723b78f --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfParameters.cs @@ -0,0 +1,80 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +using KeePassLib.Collections; +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.KeyDerivation +{ + public sealed class KdfParameters : VariantDictionary + { + private const string ParamUuid = @"$UUID"; + + private readonly PwUuid m_puKdf; + public PwUuid KdfUuid + { + get { return m_puKdf; } + } + + public KdfParameters(PwUuid puKdf) + { + if(puKdf == null) throw new ArgumentNullException("puKdf"); + + m_puKdf = puKdf; + SetByteArray(ParamUuid, puKdf.UuidBytes); + } + + /// + /// Unsupported. + /// + public override object Clone() + { + throw new NotSupportedException(); + } + + public static byte[] SerializeExt(KdfParameters p) + { + return VariantDictionary.Serialize(p); + } + + public static KdfParameters DeserializeExt(byte[] pb) + { + VariantDictionary d = VariantDictionary.Deserialize(pb); + if(d == null) { Debug.Assert(false); return null; } + + byte[] pbUuid = d.GetByteArray(ParamUuid); + if((pbUuid == null) || (pbUuid.Length != (int)PwUuid.UuidSize)) + { + Debug.Assert(false); + return null; + } + + PwUuid pu = new PwUuid(pbUuid); + KdfParameters p = new KdfParameters(pu); + d.CopyTo(p); + return p; + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfPool.cs b/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfPool.cs new file mode 100644 index 00000000..08979e23 --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/KeyDerivation/KdfPool.cs @@ -0,0 +1,96 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.KeyDerivation +{ + public static class KdfPool + { + private static List g_l = new List(); + + public static IEnumerable Engines + { + get + { + EnsureInitialized(); + return g_l; + } + } + + private static void EnsureInitialized() + { + if(g_l.Count > 0) return; + + g_l.Add(new AesKdf()); + g_l.Add(new Argon2Kdf()); + } + + internal static KdfParameters GetDefaultParameters() + { + EnsureInitialized(); + return g_l[0].GetDefaultParameters(); + } + + public static KdfEngine Get(PwUuid pu) + { + if(pu == null) { Debug.Assert(false); return null; } + + EnsureInitialized(); + + foreach(KdfEngine kdf in g_l) + { + if(pu.Equals(kdf.Uuid)) return kdf; + } + + return null; + } + + public static KdfEngine Get(string strName) + { + if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return null; } + + EnsureInitialized(); + + foreach(KdfEngine kdf in g_l) + { + if(strName.Equals(kdf.Name, StrUtil.CaseIgnoreCmp)) return kdf; + } + + return null; + } + + public static void Add(KdfEngine kdf) + { + if(kdf == null) { Debug.Assert(false); return; } + + EnsureInitialized(); + + if(Get(kdf.Uuid) != null) { Debug.Assert(false); return; } + if(Get(kdf.Name) != null) { Debug.Assert(false); return; } + + g_l.Add(kdf); + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwGenerator.cs b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwGenerator.cs index 285bfee9..b810d92e 100644 --- a/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwGenerator.cs +++ b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwGenerator.cs @@ -19,10 +19,12 @@ using System; using System.Collections.Generic; -using System.Text; using System.Diagnostics; +using System.Security.Cryptography; +using System.Text; using KeePassLib.Security; +using KeePassLib.Utility; namespace KeePassLib.Cryptography.PasswordGenerator { @@ -62,16 +64,20 @@ namespace KeePassLib.Cryptography.PasswordGenerator private static CryptoRandomStream CreateCryptoStream(byte[] pbAdditionalEntropy) { - byte[] pbKey = CryptoRandom.Instance.GetRandomBytes(256); + byte[] pbKey = CryptoRandom.Instance.GetRandomBytes(128); // Mix in additional entropy + Debug.Assert(pbKey.Length >= 64); if((pbAdditionalEntropy != null) && (pbAdditionalEntropy.Length > 0)) { - for(int nKeyPos = 0; nKeyPos < pbKey.Length; ++nKeyPos) - pbKey[nKeyPos] ^= pbAdditionalEntropy[nKeyPos % pbAdditionalEntropy.Length]; + using(SHA512Managed h = new SHA512Managed()) + { + byte[] pbHash = h.ComputeHash(pbAdditionalEntropy); + MemUtil.XorArray(pbHash, 0, pbKey, 0, pbHash.Length); + } } - return new CryptoRandomStream(CrsAlgorithm.Salsa20, pbKey); + return new CryptoRandomStream(CrsAlgorithm.ChaCha20, pbKey); } internal static char GenerateCharacter(PwProfile pwProfile, diff --git a/src/KeePassLib2Android/Cryptography/SelfTest.cs b/src/KeePassLib2Android/Cryptography/SelfTest.cs index 069d3c34..4e09636f 100644 --- a/src/KeePassLib2Android/Cryptography/SelfTest.cs +++ b/src/KeePassLib2Android/Cryptography/SelfTest.cs @@ -21,6 +21,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using System.IO; using System.Security; using System.Text; @@ -33,6 +34,8 @@ using System.Security.Cryptography; #endif using KeePassLib.Cryptography.Cipher; +using KeePassLib.Cryptography.Hash; +using KeePassLib.Cryptography.KeyDerivation; using KeePassLib.Keys; using KeePassLib.Native; using KeePassLib.Resources; @@ -45,17 +48,6 @@ using KeePassLib.Utility; namespace KeePassLib.Cryptography { - /* /// - /// Return values of the SelfTest.Perform method. - /// - public enum SelfTestResult - { - Success = 0, - RijndaelEcbError = 1, - Salsa20Error = 2, - NativeKeyTransformationError = 3 - } */ - /// /// Class containing self-test methods. /// @@ -70,7 +62,11 @@ namespace KeePassLib.Cryptography TestRijndael(); TestSalsa20(); - + TestChaCha20(); + TestBlake2b(); + TestArgon2(); + TestHmac(); + TestNativeKeyTransform(); TestHmacOtp(); @@ -92,14 +88,14 @@ namespace KeePassLib.Cryptography internal static void TestFipsComplianceProblems() { #if !KeePassUAP - try { new RijndaelManaged(); } + try { using(RijndaelManaged r = new RijndaelManaged()) { } } catch(Exception exAes) { throw new SecurityException("AES/Rijndael: " + exAes.Message); } #endif - try { new SHA256Managed(); } + try { using(SHA256Managed h = new SHA256Managed()) { } } catch(Exception exSha256) { throw new SecurityException("SHA-256: " + exSha256.Message); @@ -126,7 +122,7 @@ namespace KeePassLib.Cryptography AesEngine r = new AesEngine(); r.Init(true, new KeyParameter(pbTestKey)); if(r.GetBlockSize() != pbTestData.Length) - throw new SecurityException(KLRes.EncAlgorithmAes + " (BS)."); + throw new SecurityException("AES (BC)"); r.ProcessBlock(pbTestData, 0, pbTestData, 0); #else RijndaelManaged r = new RijndaelManaged(); @@ -147,13 +143,14 @@ namespace KeePassLib.Cryptography #endif if(!MemUtil.ArraysEqual(pbTestData, pbReferenceCT)) - throw new SecurityException(KLRes.EncAlgorithmAes + "."); + throw new SecurityException("AES"); } private static void TestSalsa20() { +#if DEBUG // Test values from official set 6, vector 3 - byte[] pbKey= new byte[32] { + byte[] pbKey = new byte[32] { 0x0F, 0x62, 0xB5, 0x08, 0x5B, 0xAE, 0x01, 0x54, 0xA7, 0xFA, 0x4D, 0xA0, 0xF3, 0x46, 0x99, 0xEC, 0x3F, 0x92, 0xE5, 0x38, 0x8B, 0xDE, 0x31, 0x84, @@ -168,12 +165,11 @@ namespace KeePassLib.Cryptography byte[] pb = new byte[16]; Salsa20Cipher c = new Salsa20Cipher(pbKey, pbIV); - c.Encrypt(pb, pb.Length, false); + c.Encrypt(pb, 0, pb.Length); if(!MemUtil.ArraysEqual(pb, pbExpected)) throw new SecurityException("Salsa20-1"); -#if DEBUG - // Extended test in debug mode + // Extended test byte[] pbExpected2 = new byte[16] { 0xAB, 0xF3, 0x9A, 0x21, 0x0E, 0xEE, 0x89, 0x59, 0x8B, 0x71, 0x33, 0x37, 0x70, 0x56, 0xC2, 0xFE @@ -185,13 +181,14 @@ namespace KeePassLib.Cryptography Random r = new Random(); int nPos = Salsa20ToPos(c, r, pb.Length, 65536); - c.Encrypt(pb, pb.Length, false); + Array.Clear(pb, 0, pb.Length); + c.Encrypt(pb, 0, pb.Length); if(!MemUtil.ArraysEqual(pb, pbExpected2)) throw new SecurityException("Salsa20-2"); nPos = Salsa20ToPos(c, r, nPos + pb.Length, 131008); Array.Clear(pb, 0, pb.Length); - c.Encrypt(pb, pb.Length, true); + c.Encrypt(pb, 0, pb.Length); if(!MemUtil.ArraysEqual(pb, pbExpected3)) throw new SecurityException("Salsa20-3"); @@ -200,8 +197,8 @@ namespace KeePassLib.Cryptography for(int i = 0; i < nRounds; ++i) { byte[] z = new byte[32]; - c = new Salsa20Cipher(z, BitConverter.GetBytes((long)i)); - c.Encrypt(z, z.Length, true); + c = new Salsa20Cipher(z, MemUtil.Int64ToBytes(i)); + c.Encrypt(z, 0, z.Length); d[MemUtil.ByteArrayToHexString(z)] = true; } if(d.Count != nRounds) throw new SecurityException("Salsa20-4"); @@ -218,7 +215,7 @@ namespace KeePassLib.Cryptography { int x = r.Next(1, 513); int nGen = Math.Min(nTargetPos - nPos, x); - c.Encrypt(pb, nGen, r.Next(0, 2) == 0); + c.Encrypt(pb, 0, nGen); nPos += nGen; } @@ -226,6 +223,475 @@ namespace KeePassLib.Cryptography } #endif + private static void TestChaCha20() + { + // ====================================================== + // Test vector from RFC 7539, section 2.3.2 + + byte[] pbKey = new byte[32]; + for(int i = 0; i < 32; ++i) pbKey[i] = (byte)i; + + byte[] pbIV = new byte[12]; + pbIV[3] = 0x09; + pbIV[7] = 0x4A; + + byte[] pbExpc = new byte[64] { + 0x10, 0xF1, 0xE7, 0xE4, 0xD1, 0x3B, 0x59, 0x15, + 0x50, 0x0F, 0xDD, 0x1F, 0xA3, 0x20, 0x71, 0xC4, + 0xC7, 0xD1, 0xF4, 0xC7, 0x33, 0xC0, 0x68, 0x03, + 0x04, 0x22, 0xAA, 0x9A, 0xC3, 0xD4, 0x6C, 0x4E, + 0xD2, 0x82, 0x64, 0x46, 0x07, 0x9F, 0xAA, 0x09, + 0x14, 0xC2, 0xD7, 0x05, 0xD9, 0x8B, 0x02, 0xA2, + 0xB5, 0x12, 0x9C, 0xD1, 0xDE, 0x16, 0x4E, 0xB9, + 0xCB, 0xD0, 0x83, 0xE8, 0xA2, 0x50, 0x3C, 0x4E + }; + + byte[] pb = new byte[64]; + + using(ChaCha20Cipher c = new ChaCha20Cipher(pbKey, pbIV)) + { + c.Seek(64, SeekOrigin.Begin); // Skip first block + c.Encrypt(pb, 0, pb.Length); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("ChaCha20-1"); + } + +#if DEBUG + // ====================================================== + // Test vector from RFC 7539, section 2.4.2 + + pbIV[3] = 0; + + pb = StrUtil.Utf8.GetBytes("Ladies and Gentlemen of the clas" + + @"s of '99: If I could offer you only one tip for " + + @"the future, sunscreen would be it."); + + pbExpc = new byte[] { + 0x6E, 0x2E, 0x35, 0x9A, 0x25, 0x68, 0xF9, 0x80, + 0x41, 0xBA, 0x07, 0x28, 0xDD, 0x0D, 0x69, 0x81, + 0xE9, 0x7E, 0x7A, 0xEC, 0x1D, 0x43, 0x60, 0xC2, + 0x0A, 0x27, 0xAF, 0xCC, 0xFD, 0x9F, 0xAE, 0x0B, + 0xF9, 0x1B, 0x65, 0xC5, 0x52, 0x47, 0x33, 0xAB, + 0x8F, 0x59, 0x3D, 0xAB, 0xCD, 0x62, 0xB3, 0x57, + 0x16, 0x39, 0xD6, 0x24, 0xE6, 0x51, 0x52, 0xAB, + 0x8F, 0x53, 0x0C, 0x35, 0x9F, 0x08, 0x61, 0xD8, + 0x07, 0xCA, 0x0D, 0xBF, 0x50, 0x0D, 0x6A, 0x61, + 0x56, 0xA3, 0x8E, 0x08, 0x8A, 0x22, 0xB6, 0x5E, + 0x52, 0xBC, 0x51, 0x4D, 0x16, 0xCC, 0xF8, 0x06, + 0x81, 0x8C, 0xE9, 0x1A, 0xB7, 0x79, 0x37, 0x36, + 0x5A, 0xF9, 0x0B, 0xBF, 0x74, 0xA3, 0x5B, 0xE6, + 0xB4, 0x0B, 0x8E, 0xED, 0xF2, 0x78, 0x5E, 0x42, + 0x87, 0x4D + }; + + byte[] pb64 = new byte[64]; + + using(ChaCha20Cipher c = new ChaCha20Cipher(pbKey, pbIV)) + { + c.Encrypt(pb64, 0, pb64.Length); // Skip first block + c.Encrypt(pb, 0, pb.Length); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("ChaCha20-2"); + } + + // ====================================================== + // Test vector from RFC 7539, appendix A.2 #2 + + Array.Clear(pbKey, 0, pbKey.Length); + pbKey[31] = 1; + + Array.Clear(pbIV, 0, pbIV.Length); + pbIV[11] = 2; + + pb = StrUtil.Utf8.GetBytes("Any submission to the IETF inten" + + "ded by the Contributor for publication as all or" + + " part of an IETF Internet-Draft or RFC and any s" + + "tatement made within the context of an IETF acti" + + "vity is considered an \"IETF Contribution\". Such " + + "statements include oral statements in IETF sessi" + + "ons, as well as written and electronic communica" + + "tions made at any time or place, which are addressed to"); + + pbExpc = MemUtil.HexStringToByteArray( + "A3FBF07DF3FA2FDE4F376CA23E82737041605D9F4F4F57BD8CFF2C1D4B7955EC" + + "2A97948BD3722915C8F3D337F7D370050E9E96D647B7C39F56E031CA5EB6250D" + + "4042E02785ECECFA4B4BB5E8EAD0440E20B6E8DB09D881A7C6132F420E527950" + + "42BDFA7773D8A9051447B3291CE1411C680465552AA6C405B7764D5E87BEA85A" + + "D00F8449ED8F72D0D662AB052691CA66424BC86D2DF80EA41F43ABF937D3259D" + + "C4B2D0DFB48A6C9139DDD7F76966E928E635553BA76C5C879D7B35D49EB2E62B" + + "0871CDAC638939E25E8A1E0EF9D5280FA8CA328B351C3C765989CBCF3DAA8B6C" + + "CC3AAF9F3979C92B3720FC88DC95ED84A1BE059C6499B9FDA236E7E818B04B0B" + + "C39C1E876B193BFE5569753F88128CC08AAA9B63D1A16F80EF2554D7189C411F" + + "5869CA52C5B83FA36FF216B9C1D30062BEBCFD2DC5BCE0911934FDA79A86F6E6" + + "98CED759C3FF9B6477338F3DA4F9CD8514EA9982CCAFB341B2384DD902F3D1AB" + + "7AC61DD29C6F21BA5B862F3730E37CFDC4FD806C22F221"); + + Random r = new Random(); + using(MemoryStream msEnc = new MemoryStream()) + { + using(ChaCha20Stream c = new ChaCha20Stream(msEnc, true, pbKey, pbIV)) + { + r.NextBytes(pb64); + c.Write(pb64, 0, pb64.Length); // Skip first block + + int p = 0; + while(p < pb.Length) + { + int cb = r.Next(1, pb.Length - p + 1); + c.Write(pb, p, cb); + p += cb; + } + Debug.Assert(p == pb.Length); + } + + byte[] pbEnc0 = msEnc.ToArray(); + byte[] pbEnc = MemUtil.Mid(pbEnc0, 64, pbEnc0.Length - 64); + if(!MemUtil.ArraysEqual(pbEnc, pbExpc)) + throw new SecurityException("ChaCha20-3"); + + using(MemoryStream msCT = new MemoryStream(pbEnc0, false)) + { + using(ChaCha20Stream cDec = new ChaCha20Stream(msCT, false, + pbKey, pbIV)) + { + byte[] pbPT = MemUtil.Read(cDec, pbEnc0.Length); + if(cDec.ReadByte() >= 0) + throw new SecurityException("ChaCha20-4"); + if(!MemUtil.ArraysEqual(MemUtil.Mid(pbPT, 0, 64), pb64)) + throw new SecurityException("ChaCha20-5"); + if(!MemUtil.ArraysEqual(MemUtil.Mid(pbPT, 64, pbEnc.Length), pb)) + throw new SecurityException("ChaCha20-6"); + } + } + } + + // ====================================================== + // Test vector TC8 from RFC draft by J. Strombergson: + // https://tools.ietf.org/html/draft-strombergson-chacha-test-vectors-01 + + pbKey = new byte[32] { + 0xC4, 0x6E, 0xC1, 0xB1, 0x8C, 0xE8, 0xA8, 0x78, + 0x72, 0x5A, 0x37, 0xE7, 0x80, 0xDF, 0xB7, 0x35, + 0x1F, 0x68, 0xED, 0x2E, 0x19, 0x4C, 0x79, 0xFB, + 0xC6, 0xAE, 0xBE, 0xE1, 0xA6, 0x67, 0x97, 0x5D + }; + + // The first 4 bytes are set to zero and a large counter + // is used; this makes the RFC 7539 version of ChaCha20 + // compatible with the original specification by + // D. J. Bernstein. + pbIV = new byte[12] { 0x00, 0x00, 0x00, 0x00, + 0x1A, 0xDA, 0x31, 0xD5, 0xCF, 0x68, 0x82, 0x21 + }; + + pb = new byte[128]; + + pbExpc = new byte[128] { + 0xF6, 0x3A, 0x89, 0xB7, 0x5C, 0x22, 0x71, 0xF9, + 0x36, 0x88, 0x16, 0x54, 0x2B, 0xA5, 0x2F, 0x06, + 0xED, 0x49, 0x24, 0x17, 0x92, 0x30, 0x2B, 0x00, + 0xB5, 0xE8, 0xF8, 0x0A, 0xE9, 0xA4, 0x73, 0xAF, + 0xC2, 0x5B, 0x21, 0x8F, 0x51, 0x9A, 0xF0, 0xFD, + 0xD4, 0x06, 0x36, 0x2E, 0x8D, 0x69, 0xDE, 0x7F, + 0x54, 0xC6, 0x04, 0xA6, 0xE0, 0x0F, 0x35, 0x3F, + 0x11, 0x0F, 0x77, 0x1B, 0xDC, 0xA8, 0xAB, 0x92, + + 0xE5, 0xFB, 0xC3, 0x4E, 0x60, 0xA1, 0xD9, 0xA9, + 0xDB, 0x17, 0x34, 0x5B, 0x0A, 0x40, 0x27, 0x36, + 0x85, 0x3B, 0xF9, 0x10, 0xB0, 0x60, 0xBD, 0xF1, + 0xF8, 0x97, 0xB6, 0x29, 0x0F, 0x01, 0xD1, 0x38, + 0xAE, 0x2C, 0x4C, 0x90, 0x22, 0x5B, 0xA9, 0xEA, + 0x14, 0xD5, 0x18, 0xF5, 0x59, 0x29, 0xDE, 0xA0, + 0x98, 0xCA, 0x7A, 0x6C, 0xCF, 0xE6, 0x12, 0x27, + 0x05, 0x3C, 0x84, 0xE4, 0x9A, 0x4A, 0x33, 0x32 + }; + + using(ChaCha20Cipher c = new ChaCha20Cipher(pbKey, pbIV, true)) + { + c.Decrypt(pb, 0, pb.Length); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("ChaCha20-7"); + } +#endif + } + + private static void TestBlake2b() + { +#if DEBUG + Blake2b h = new Blake2b(); + + // ====================================================== + // From https://tools.ietf.org/html/rfc7693 + + byte[] pbData = StrUtil.Utf8.GetBytes("abc"); + byte[] pbExpc = new byte[64] { + 0xBA, 0x80, 0xA5, 0x3F, 0x98, 0x1C, 0x4D, 0x0D, + 0x6A, 0x27, 0x97, 0xB6, 0x9F, 0x12, 0xF6, 0xE9, + 0x4C, 0x21, 0x2F, 0x14, 0x68, 0x5A, 0xC4, 0xB7, + 0x4B, 0x12, 0xBB, 0x6F, 0xDB, 0xFF, 0xA2, 0xD1, + 0x7D, 0x87, 0xC5, 0x39, 0x2A, 0xAB, 0x79, 0x2D, + 0xC2, 0x52, 0xD5, 0xDE, 0x45, 0x33, 0xCC, 0x95, + 0x18, 0xD3, 0x8A, 0xA8, 0xDB, 0xF1, 0x92, 0x5A, + 0xB9, 0x23, 0x86, 0xED, 0xD4, 0x00, 0x99, 0x23 + }; + + byte[] pbC = h.ComputeHash(pbData); + if(!MemUtil.ArraysEqual(pbC, pbExpc)) + throw new SecurityException("Blake2b-1"); + + // ====================================================== + // Computed using the official b2sum tool + + pbExpc = new byte[64] { + 0x78, 0x6A, 0x02, 0xF7, 0x42, 0x01, 0x59, 0x03, + 0xC6, 0xC6, 0xFD, 0x85, 0x25, 0x52, 0xD2, 0x72, + 0x91, 0x2F, 0x47, 0x40, 0xE1, 0x58, 0x47, 0x61, + 0x8A, 0x86, 0xE2, 0x17, 0xF7, 0x1F, 0x54, 0x19, + 0xD2, 0x5E, 0x10, 0x31, 0xAF, 0xEE, 0x58, 0x53, + 0x13, 0x89, 0x64, 0x44, 0x93, 0x4E, 0xB0, 0x4B, + 0x90, 0x3A, 0x68, 0x5B, 0x14, 0x48, 0xB7, 0x55, + 0xD5, 0x6F, 0x70, 0x1A, 0xFE, 0x9B, 0xE2, 0xCE + }; + + pbC = h.ComputeHash(MemUtil.EmptyByteArray); + if(!MemUtil.ArraysEqual(pbC, pbExpc)) + throw new SecurityException("Blake2b-2"); + + // ====================================================== + // Computed using the official b2sum tool + + string strS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.:,;_-\r\n"; + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < 1000; ++i) sb.Append(strS); + pbData = StrUtil.Utf8.GetBytes(sb.ToString()); + + pbExpc = new byte[64] { + 0x59, 0x69, 0x8D, 0x3B, 0x83, 0xF4, 0x02, 0x4E, + 0xD8, 0x99, 0x26, 0x0E, 0xF4, 0xE5, 0x9F, 0x20, + 0xDC, 0x31, 0xEE, 0x5B, 0x45, 0xEA, 0xBB, 0xFC, + 0x1C, 0x0A, 0x8E, 0xED, 0xAA, 0x7A, 0xFF, 0x50, + 0x82, 0xA5, 0x8F, 0xBC, 0x4A, 0x46, 0xFC, 0xC5, + 0xEF, 0x44, 0x4E, 0x89, 0x80, 0x7D, 0x3F, 0x1C, + 0xC1, 0x94, 0x45, 0xBB, 0xC0, 0x2C, 0x95, 0xAA, + 0x3F, 0x08, 0x8A, 0x93, 0xF8, 0x75, 0x91, 0xB0 + }; + + Random r = new Random(); + int p = 0; + while(p < pbData.Length) + { + int cb = r.Next(1, pbData.Length - p + 1); + h.TransformBlock(pbData, p, cb, pbData, p); + p += cb; + } + Debug.Assert(p == pbData.Length); + + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + if(!MemUtil.ArraysEqual(h.Hash, pbExpc)) + throw new SecurityException("Blake2b-3"); + + h.Clear(); +#endif + } + + private static void TestArgon2() + { +#if DEBUG + Argon2Kdf kdf = new Argon2Kdf(); + + // ====================================================== + // From the official Argon2 1.3 reference code package + // (test vector for Argon2d 1.3); also on + // https://tools.ietf.org/html/draft-irtf-cfrg-argon2-00 + + KdfParameters p = kdf.GetDefaultParameters(); + kdf.Randomize(p); + + Debug.Assert(p.GetUInt32(Argon2Kdf.ParamVersion, 0) == 0x13U); + + byte[] pbMsg = new byte[32]; + for(int i = 0; i < pbMsg.Length; ++i) pbMsg[i] = 1; + + p.SetUInt64(Argon2Kdf.ParamMemory, 32 * 1024); + p.SetUInt64(Argon2Kdf.ParamIterations, 3); + p.SetUInt32(Argon2Kdf.ParamParallelism, 4); + + byte[] pbSalt = new byte[16]; + for(int i = 0; i < pbSalt.Length; ++i) pbSalt[i] = 2; + p.SetByteArray(Argon2Kdf.ParamSalt, pbSalt); + + byte[] pbKey = new byte[8]; + for(int i = 0; i < pbKey.Length; ++i) pbKey[i] = 3; + p.SetByteArray(Argon2Kdf.ParamSecretKey, pbKey); + + byte[] pbAssoc = new byte[12]; + for(int i = 0; i < pbAssoc.Length; ++i) pbAssoc[i] = 4; + p.SetByteArray(Argon2Kdf.ParamAssocData, pbAssoc); + + byte[] pbExpc = new byte[32] { + 0x51, 0x2B, 0x39, 0x1B, 0x6F, 0x11, 0x62, 0x97, + 0x53, 0x71, 0xD3, 0x09, 0x19, 0x73, 0x42, 0x94, + 0xF8, 0x68, 0xE3, 0xBE, 0x39, 0x84, 0xF3, 0xC1, + 0xA1, 0x3A, 0x4D, 0xB9, 0xFA, 0xBE, 0x4A, 0xCB + }; + + byte[] pb = kdf.Transform(pbMsg, p); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("Argon2-1"); + + // ====================================================== + // From the official Argon2 1.3 reference code package + // (test vector for Argon2d 1.0) + + p.SetUInt32(Argon2Kdf.ParamVersion, 0x10); + + pbExpc = new byte[32] { + 0x96, 0xA9, 0xD4, 0xE5, 0xA1, 0x73, 0x40, 0x92, + 0xC8, 0x5E, 0x29, 0xF4, 0x10, 0xA4, 0x59, 0x14, + 0xA5, 0xDD, 0x1F, 0x5C, 0xBF, 0x08, 0xB2, 0x67, + 0x0D, 0xA6, 0x8A, 0x02, 0x85, 0xAB, 0xF3, 0x2B + }; + + pb = kdf.Transform(pbMsg, p); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("Argon2-2"); + + // ====================================================== + // From the official 'phc-winner-argon2-20151206.zip' + // (test vector for Argon2d 1.0) + + p.SetUInt64(Argon2Kdf.ParamMemory, 16 * 1024); + + pbExpc = new byte[32] { + 0x57, 0xB0, 0x61, 0x3B, 0xFD, 0xD4, 0x13, 0x1A, + 0x0C, 0x34, 0x88, 0x34, 0xC6, 0x72, 0x9C, 0x2C, + 0x72, 0x29, 0x92, 0x1E, 0x6B, 0xBA, 0x37, 0x66, + 0x5D, 0x97, 0x8C, 0x4F, 0xE7, 0x17, 0x5E, 0xD2 + }; + + pb = kdf.Transform(pbMsg, p); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("Argon2-3"); + +#if SELFTEST_ARGON2_LONG + // ====================================================== + // Computed using the official 'argon2' application + // (test vectors for Argon2d 1.3) + + p = kdf.GetDefaultParameters(); + + pbMsg = StrUtil.Utf8.GetBytes("ABC1234"); + + p.SetUInt64(Argon2Kdf.ParamMemory, (1 << 11) * 1024); // 2 MB + p.SetUInt64(Argon2Kdf.ParamIterations, 2); + p.SetUInt32(Argon2Kdf.ParamParallelism, 2); + + pbSalt = StrUtil.Utf8.GetBytes("somesalt"); + p.SetByteArray(Argon2Kdf.ParamSalt, pbSalt); + + pbExpc = new byte[32] { + 0x29, 0xCB, 0xD3, 0xA1, 0x93, 0x76, 0xF7, 0xA2, + 0xFC, 0xDF, 0xB0, 0x68, 0xAC, 0x0B, 0x99, 0xBA, + 0x40, 0xAC, 0x09, 0x01, 0x73, 0x42, 0xCE, 0xF1, + 0x29, 0xCC, 0xA1, 0x4F, 0xE1, 0xC1, 0xB7, 0xA3 + }; + + pb = kdf.Transform(pbMsg, p); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("Argon2-4"); + + p.SetUInt64(Argon2Kdf.ParamMemory, (1 << 10) * 1024); // 1 MB + p.SetUInt64(Argon2Kdf.ParamIterations, 3); + + pbExpc = new byte[32] { + 0x7A, 0xBE, 0x1C, 0x1C, 0x8D, 0x7F, 0xD6, 0xDC, + 0x7C, 0x94, 0x06, 0x3E, 0xD8, 0xBC, 0xD8, 0x1C, + 0x2F, 0x87, 0x84, 0x99, 0x12, 0x83, 0xFE, 0x76, + 0x00, 0x64, 0xC4, 0x58, 0xA4, 0xDA, 0x35, 0x70 + }; + + pb = kdf.Transform(pbMsg, p); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("Argon2-5"); + +#if SELFTEST_ARGON2_LONGER + p.SetUInt64(Argon2Kdf.ParamMemory, (1 << 20) * 1024); // 1 GB + p.SetUInt64(Argon2Kdf.ParamIterations, 2); + p.SetUInt32(Argon2Kdf.ParamParallelism, 3); + + pbExpc = new byte[32] { + 0xE6, 0xE7, 0xCB, 0xF5, 0x5A, 0x06, 0x93, 0x05, + 0x32, 0xBA, 0x86, 0xC6, 0x1F, 0x45, 0x17, 0x99, + 0x65, 0x41, 0x77, 0xF9, 0x30, 0x55, 0x9A, 0xE8, + 0x3D, 0x21, 0x48, 0xC6, 0x2D, 0x0C, 0x49, 0x11 + }; + + pb = kdf.Transform(pbMsg, p); + + if(!MemUtil.ArraysEqual(pb, pbExpc)) + throw new SecurityException("Argon2-6"); +#endif // SELFTEST_ARGON2_LONGER +#endif // SELFTEST_ARGON2_LONG +#endif // DEBUG + } + + private static void TestHmac() + { +#if DEBUG + // Test vectors from RFC 4231 + + byte[] pbKey = new byte[20]; + for(int i = 0; i < pbKey.Length; ++i) pbKey[i] = 0x0B; + byte[] pbMsg = StrUtil.Utf8.GetBytes("Hi There"); + byte[] pbExpc = new byte[32] { + 0xB0, 0x34, 0x4C, 0x61, 0xD8, 0xDB, 0x38, 0x53, + 0x5C, 0xA8, 0xAF, 0xCE, 0xAF, 0x0B, 0xF1, 0x2B, + 0x88, 0x1D, 0xC2, 0x00, 0xC9, 0x83, 0x3D, 0xA7, + 0x26, 0xE9, 0x37, 0x6C, 0x2E, 0x32, 0xCF, 0xF7 + }; + HmacEval(pbKey, pbMsg, pbExpc, "1"); + + pbKey = new byte[131]; + for(int i = 0; i < pbKey.Length; ++i) pbKey[i] = 0xAA; + pbMsg = StrUtil.Utf8.GetBytes( + "This is a test using a larger than block-size key and " + + "a larger than block-size data. The key needs to be " + + "hashed before being used by the HMAC algorithm."); + pbExpc = new byte[32] { + 0x9B, 0x09, 0xFF, 0xA7, 0x1B, 0x94, 0x2F, 0xCB, + 0x27, 0x63, 0x5F, 0xBC, 0xD5, 0xB0, 0xE9, 0x44, + 0xBF, 0xDC, 0x63, 0x64, 0x4F, 0x07, 0x13, 0x93, + 0x8A, 0x7F, 0x51, 0x53, 0x5C, 0x3A, 0x35, 0xE2 + }; + HmacEval(pbKey, pbMsg, pbExpc, "2"); +#endif + } + +#if DEBUG + private static void HmacEval(byte[] pbKey, byte[] pbMsg, + byte[] pbExpc, string strID) + { + using(HMACSHA256 h = new HMACSHA256(pbKey)) + { + h.TransformBlock(pbMsg, 0, pbMsg.Length, pbMsg, 0); + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + byte[] pbHash = h.Hash; + if(!MemUtil.ArraysEqual(pbHash, pbExpc)) + throw new SecurityException("HMAC-SHA-256-" + strID); + } + } +#endif + private static void TestNativeKeyTransform() { #if DEBUG @@ -235,8 +701,8 @@ namespace KeePassLib.Cryptography byte[] pbManaged = new byte[32]; Array.Copy(pbOrgKey, pbManaged, 32); - if(!CompositeKey.TransformKeyManaged(pbManaged, pbSeed, uRounds)) - throw new SecurityException("Managed transform."); + if(!AesKdf.TransformKeyManaged(pbManaged, pbSeed, uRounds)) + throw new SecurityException("AES-KDF-1"); byte[] pbNative = new byte[32]; Array.Copy(pbOrgKey, pbNative, 32); @@ -244,7 +710,7 @@ namespace KeePassLib.Cryptography return; // Native library not available ("success") if(!MemUtil.ArraysEqual(pbManaged, pbNative)) - throw new SecurityException("Native transform."); + throw new SecurityException("AES-KDF-2"); #endif } @@ -310,6 +776,15 @@ namespace KeePassLib.Cryptography pbRes = MemUtil.ParseBase32("JNSXSIDQOJXXM2LEMVZCAYTBONSWIIDPNYQG63TFFV2GS3LFEBYGC43TO5XXEZDTFY======"); pbExp = Encoding.ASCII.GetBytes("Key provider based on one-time passwords."); if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-7"); + + int i = 0 - 0x10203040; + pbRes = MemUtil.Int32ToBytes(i); + if(MemUtil.ByteArrayToHexString(pbRes) != "C0CFDFEF") + throw new Exception("MemUtil-8"); // Must be little-endian + if(MemUtil.BytesToUInt32(pbRes) != (uint)i) + throw new Exception("MemUtil-9"); + if(MemUtil.BytesToInt32(pbRes) != i) + throw new Exception("MemUtil-10"); #endif } diff --git a/src/KeePassLib2Android/Keys/CompositeKey.cs b/src/KeePassLib2Android/Keys/CompositeKey.cs index 363c7797..54bf347e 100644 --- a/src/KeePassLib2Android/Keys/CompositeKey.cs +++ b/src/KeePassLib2Android/Keys/CompositeKey.cs @@ -22,14 +22,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Text; -#if KeePassUAP -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Engines; -using Org.BouncyCastle.Crypto.Parameters; -#else -using System.Security.Cryptography; -#endif - +using KeePassLib.Cryptography; +using KeePassLib.Cryptography.KeyDerivation; using KeePassLib.Native; using KeePassLib.Resources; using KeePassLib.Security; @@ -197,8 +191,7 @@ namespace KeePassLib.Keys } Debug.Assert(p == cbData); - SHA256Managed sha256 = new SHA256Managed(); - byte[] pbHash = sha256.ComputeHash(pbAllData); + byte[] pbHash = CryptoUtil.HashSha256(pbAllData); MemUtil.ZeroByteArray(pbAllData); return pbHash; } @@ -216,15 +209,7 @@ namespace KeePassLib.Keys return bResult; } - /// - /// Generate a 32-bit wide key out of the composite key. - /// - /// Seed used in the key transformation - /// rounds. Must be a byte array containing exactly 32 bytes; must - /// not be null. - /// Number of key transformation rounds. - /// Returns a protected binary object that contains the - /// resulting 32-bit wide key. + [Obsolete] public ProtectedBinary GenerateKey32(byte[] pbKeySeed32, ulong uNumRounds) { Debug.Assert(pbKeySeed32 != null); @@ -232,18 +217,43 @@ namespace KeePassLib.Keys Debug.Assert(pbKeySeed32.Length == 32); if(pbKeySeed32.Length != 32) throw new ArgumentException("pbKeySeed32"); + AesKdf kdf = new AesKdf(); + KdfParameters p = kdf.GetDefaultParameters(); + p.SetUInt64(AesKdf.ParamRounds, uNumRounds); + p.SetByteArray(AesKdf.ParamSeed, pbKeySeed32); + + return GenerateKey32(p); + } + + /// + /// Generate a 32-byte (256-bit) key from the composite key. + /// + public ProtectedBinary GenerateKey32(KdfParameters p) + { + if(p == null) { Debug.Assert(false); throw new ArgumentNullException("p"); } + byte[] pbRaw32 = CreateRawCompositeKey32(); if((pbRaw32 == null) || (pbRaw32.Length != 32)) { Debug.Assert(false); return null; } - byte[] pbTrf32 = TransformKey(pbRaw32, pbKeySeed32, uNumRounds); - if((pbTrf32 == null) || (pbTrf32.Length != 32)) - { Debug.Assert(false); return null; } + KdfEngine kdf = KdfPool.Get(p.KdfUuid); + if(kdf == null) // CryptographicExceptions are translated to "file corrupted" + throw new Exception(KLRes.UnknownKdf + MessageService.NewParagraph + + KLRes.FileNewVerOrPlgReq + MessageService.NewParagraph + + "UUID: " + p.KdfUuid.ToHexString() + "."); + + byte[] pbTrf32 = kdf.Transform(pbRaw32, p); + if(pbTrf32 == null) { Debug.Assert(false); return null; } + + if(pbTrf32.Length != 32) + { + Debug.Assert(false); + pbTrf32 = CryptoUtil.HashSha256(pbTrf32); + } ProtectedBinary pbRet = new ProtectedBinary(true, pbTrf32); MemUtil.ZeroByteArray(pbTrf32); MemUtil.ZeroByteArray(pbRaw32); - return pbRet; } @@ -263,182 +273,6 @@ namespace KeePassLib.Keys throw new InvalidOperationException(); } } - - /// - /// Transform the current key uNumRounds times. - /// - /// The original key which will be transformed. - /// This parameter won't be modified. - /// Seed used for key transformations. Must not - /// be null. This parameter won't be modified. - /// Transformation count. - /// 256-bit transformed key. - private static byte[] TransformKey(byte[] pbOriginalKey32, byte[] pbKeySeed32, - ulong uNumRounds) - { - Debug.Assert((pbOriginalKey32 != null) && (pbOriginalKey32.Length == 32)); - if(pbOriginalKey32 == null) throw new ArgumentNullException("pbOriginalKey32"); - if(pbOriginalKey32.Length != 32) throw new ArgumentException(); - - Debug.Assert((pbKeySeed32 != null) && (pbKeySeed32.Length == 32)); - if(pbKeySeed32 == null) throw new ArgumentNullException("pbKeySeed32"); - if(pbKeySeed32.Length != 32) throw new ArgumentException(); - - byte[] pbNewKey = new byte[32]; - Array.Copy(pbOriginalKey32, pbNewKey, pbNewKey.Length); - - try - { - // Try to use the native library first - if(NativeLib.TransformKey256(pbNewKey, pbKeySeed32, uNumRounds)) - return (new SHA256Managed()).ComputeHash(pbNewKey); - - if(!TransformKeyManaged(pbNewKey, pbKeySeed32, uNumRounds)) - return null; - - return (new SHA256Managed()).ComputeHash(pbNewKey); - } - finally { MemUtil.ZeroByteArray(pbNewKey); } - } - - public static bool TransformKeyManaged(byte[] pbNewKey32, byte[] pbKeySeed32, - ulong uNumRounds) - { -#if KeePassUAP - KeyParameter kp = new KeyParameter(pbKeySeed32); - AesEngine aes = new AesEngine(); - aes.Init(true, kp); - - for(ulong i = 0; i < uNumRounds; ++i) - { - aes.ProcessBlock(pbNewKey32, 0, pbNewKey32, 0); - aes.ProcessBlock(pbNewKey32, 16, pbNewKey32, 16); - } -#else - byte[] pbIV = new byte[16]; - Array.Clear(pbIV, 0, pbIV.Length); - - RijndaelManaged r = new RijndaelManaged(); - if(r.BlockSize != 128) // AES block size - { - Debug.Assert(false); - r.BlockSize = 128; - } - - r.IV = pbIV; - r.Mode = CipherMode.ECB; - r.KeySize = 256; - r.Key = pbKeySeed32; - ICryptoTransform iCrypt = r.CreateEncryptor(); - - // !iCrypt.CanReuseTransform -- doesn't work with Mono - if((iCrypt == null) || (iCrypt.InputBlockSize != 16) || - (iCrypt.OutputBlockSize != 16)) - { - Debug.Assert(false, "Invalid ICryptoTransform."); - Debug.Assert((iCrypt.InputBlockSize == 16), "Invalid input block size!"); - Debug.Assert((iCrypt.OutputBlockSize == 16), "Invalid output block size!"); - return false; - } - - for(ulong i = 0; i < uNumRounds; ++i) - { - iCrypt.TransformBlock(pbNewKey32, 0, 16, pbNewKey32, 0); - iCrypt.TransformBlock(pbNewKey32, 16, 16, pbNewKey32, 16); - } -#endif - - return true; - } - - /// - /// Benchmark the TransformKey method. Within - /// ms, random keys will be transformed - /// and the number of performed transformations are returned. - /// - /// Test duration in ms. - /// Stepping. - /// should be a prime number. For fast processors - /// (PCs) a value of 3001 is recommended, for slower processors (PocketPC) - /// a value of 401 is recommended. - /// Number of transformations performed in the specified - /// amount of time. Maximum value is uint.MaxValue. - public static ulong TransformKeyBenchmark(uint uMilliseconds, ulong uStep) - { - ulong uRounds; - - // Try native method - if(NativeLib.TransformKeyBenchmark256(uMilliseconds, out uRounds)) - return uRounds; - - byte[] pbKey = new byte[32]; - byte[] pbNewKey = new byte[32]; - for(int i = 0; i < pbKey.Length; ++i) - { - pbKey[i] = (byte)i; - pbNewKey[i] = (byte)i; - } - -#if KeePassUAP - KeyParameter kp = new KeyParameter(pbKey); - AesEngine aes = new AesEngine(); - aes.Init(true, kp); -#else - byte[] pbIV = new byte[16]; - Array.Clear(pbIV, 0, pbIV.Length); - - RijndaelManaged r = new RijndaelManaged(); - if(r.BlockSize != 128) // AES block size - { - Debug.Assert(false); - r.BlockSize = 128; - } - - r.IV = pbIV; - r.Mode = CipherMode.ECB; - r.KeySize = 256; - r.Key = pbKey; - ICryptoTransform iCrypt = r.CreateEncryptor(); - - // !iCrypt.CanReuseTransform -- doesn't work with Mono - if((iCrypt == null) || (iCrypt.InputBlockSize != 16) || - (iCrypt.OutputBlockSize != 16)) - { - Debug.Assert(false, "Invalid ICryptoTransform."); - Debug.Assert(iCrypt.InputBlockSize == 16, "Invalid input block size!"); - Debug.Assert(iCrypt.OutputBlockSize == 16, "Invalid output block size!"); - return PwDefs.DefaultKeyEncryptionRounds; - } -#endif - - uRounds = 0; - int tStart = Environment.TickCount; - while(true) - { - for(ulong j = 0; j < uStep; ++j) - { -#if KeePassUAP - aes.ProcessBlock(pbNewKey, 0, pbNewKey, 0); - aes.ProcessBlock(pbNewKey, 16, pbNewKey, 16); -#else - iCrypt.TransformBlock(pbNewKey, 0, 16, pbNewKey, 0); - iCrypt.TransformBlock(pbNewKey, 16, 16, pbNewKey, 16); -#endif - } - - uRounds += uStep; - if(uRounds < uStep) // Overflow check - { - uRounds = ulong.MaxValue; - break; - } - - uint tElapsed = (uint)(Environment.TickCount - tStart); - if(tElapsed > uMilliseconds) break; - } - - return uRounds; - } } public sealed class InvalidCompositeKeyException : Exception diff --git a/src/KeePassLib2Android/Keys/KcpCustomKey.cs b/src/KeePassLib2Android/Keys/KcpCustomKey.cs index 0f0c369b..7be09e58 100644 --- a/src/KeePassLib2Android/Keys/KcpCustomKey.cs +++ b/src/KeePassLib2Android/Keys/KcpCustomKey.cs @@ -22,10 +22,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Text; -#if !KeePassUAP -using System.Security.Cryptography; -#endif - +using KeePassLib.Cryptography; using KeePassLib.Security; namespace KeePassLib.Keys @@ -57,8 +54,7 @@ namespace KeePassLib.Keys if(bPerformHash) { - SHA256Managed sha256 = new SHA256Managed(); - byte[] pbRaw = sha256.ComputeHash(pbKeyData); + byte[] pbRaw = CryptoUtil.HashSha256(pbKeyData); m_pbKey = new ProtectedBinary(true, pbRaw); } else m_pbKey = new ProtectedBinary(true, pbKeyData); diff --git a/src/KeePassLib2Android/Keys/KcpKeyFile.cs b/src/KeePassLib2Android/Keys/KcpKeyFile.cs index 07726bcc..4b3f104e 100644 --- a/src/KeePassLib2Android/Keys/KcpKeyFile.cs +++ b/src/KeePassLib2Android/Keys/KcpKeyFile.cs @@ -133,10 +133,7 @@ namespace KeePassLib.Keys else if(iLength == 64) pbKey = LoadHexKey32(pbFileData); if(pbKey == null) - { - SHA256Managed sha256 = new SHA256Managed(); - pbKey = sha256.ComputeHash(pbFileData); - } + pbKey = CryptoUtil.HashSha256(pbFileData); return pbKey; } @@ -156,12 +153,15 @@ namespace KeePassLib.Keys try { - string strHex = StrUtil.Utf8.GetString(pbFileData, 0, 64); - if(!StrUtil.IsHexString(strHex, true)) return null; + if(!StrUtil.IsHexString(pbFileData, true)) return null; + string strHex = StrUtil.Utf8.GetString(pbFileData); byte[] pbKey = MemUtil.HexStringToByteArray(strHex); if((pbKey == null) || (pbKey.Length != 32)) + { + Debug.Assert(false); return null; + } return pbKey; } @@ -189,13 +189,13 @@ namespace KeePassLib.Keys pbFinalKey32 = pbKey32; else { - MemoryStream ms = new MemoryStream(); - ms.Write(pbAdditionalEntropy, 0, pbAdditionalEntropy.Length); - ms.Write(pbKey32, 0, 32); + using(MemoryStream ms = new MemoryStream()) + { + MemUtil.Write(ms, pbAdditionalEntropy); + MemUtil.Write(ms, pbKey32); - SHA256Managed sha256 = new SHA256Managed(); - pbFinalKey32 = sha256.ComputeHash(ms.ToArray()); - ms.Close(); + pbFinalKey32 = CryptoUtil.HashSha256(ms.ToArray()); + } } CreateXmlKeyFile(strFilePath, pbFinalKey32); diff --git a/src/KeePassLib2Android/Keys/KcpPassword.cs b/src/KeePassLib2Android/Keys/KcpPassword.cs index b5526665..a347867f 100644 --- a/src/KeePassLib2Android/Keys/KcpPassword.cs +++ b/src/KeePassLib2Android/Keys/KcpPassword.cs @@ -21,10 +21,7 @@ using System; using System.Diagnostics; using System.Text; -#if !KeePassUAP -using System.Security.Cryptography; -#endif - +using KeePassLib.Cryptography; using KeePassLib.Security; using KeePassLib.Utility; @@ -75,8 +72,7 @@ namespace KeePassLib.Keys Debug.Assert(ValidatePassword(pbPasswordUtf8)); #endif - SHA256Managed sha256 = new SHA256Managed(); - byte[] pbRaw = sha256.ComputeHash(pbPasswordUtf8); + byte[] pbRaw = CryptoUtil.HashSha256(pbPasswordUtf8); m_psPassword = new ProtectedString(true, pbPasswordUtf8); m_pbKeyData = new ProtectedBinary(true, pbRaw); diff --git a/src/KeePassLib2Android/PwDatabase.cs b/src/KeePassLib2Android/PwDatabase.cs index 5f3f7129..bf0242ea 100644 --- a/src/KeePassLib2Android/PwDatabase.cs +++ b/src/KeePassLib2Android/PwDatabase.cs @@ -29,6 +29,7 @@ using System.Drawing; using KeePassLib.Collections; using KeePassLib.Cryptography; using KeePassLib.Cryptography.Cipher; +using KeePassLib.Cryptography.KeyDerivation; using KeePassLib.Delegates; using KeePassLib.Interfaces; using KeePassLib.Keys; @@ -50,13 +51,14 @@ namespace KeePassLib private static bool m_bPrimaryCreated = false; - // Initializations see Clear() + // Initializations: see Clear() private PwGroup m_pgRootGroup = null; private PwObjectList m_vDeletedObjects = new PwObjectList(); private PwUuid m_uuidDataCipher = StandardAesEngine.AesUuid; private PwCompressionAlgorithm m_caCompression = PwCompressionAlgorithm.GZip; - private ulong m_uKeyEncryptionRounds = PwDefs.DefaultKeyEncryptionRounds; + // private ulong m_uKeyEncryptionRounds = PwDefs.DefaultKeyEncryptionRounds; + private KdfParameters m_kdfParams = KdfPool.GetDefaultParameters(); private CompositeKey m_pwUserKey = null; private MemoryProtectionConfig m_memProtConfig = new MemoryProtectionConfig(); @@ -64,6 +66,7 @@ namespace KeePassLib private List m_vCustomIcons = new List(); private bool m_bUINeedsIconUpdate = true; + private DateTime m_dtSettingsChanged = PwDefs.DtDefaultNow; private string m_strName = string.Empty; private DateTime m_dtNameChanged = PwDefs.DtDefaultNow; private string m_strDesc = string.Empty; @@ -93,7 +96,8 @@ namespace KeePassLib private int m_nHistoryMaxItems = DefaultHistoryMaxItems; private long m_lHistoryMaxSize = DefaultHistoryMaxSize; // In bytes - private StringDictionaryEx m_vCustomData = new StringDictionaryEx(); + private StringDictionaryEx m_dCustomData = new StringDictionaryEx(); + private VariantDictionary m_dPublicCustomData = new VariantDictionary(); private byte[] m_pbHashOfFileOnDisk = null; private byte[] m_pbHashOfLastIO = null; @@ -168,6 +172,12 @@ namespace KeePassLib } } + public DateTime SettingsChanged + { + get { return m_dtSettingsChanged; } + set { m_dtSettingsChanged = value; } + } + /// /// Name of the database. /// @@ -281,14 +291,23 @@ namespace KeePassLib set { m_caCompression = value; } } - /// - /// Number of key transformation rounds (in order to make dictionary - /// attacks harder). - /// - public ulong KeyEncryptionRounds + // /// + // /// Number of key transformation rounds (KDF parameter). + // /// + // public ulong KeyEncryptionRounds + // { + // get { return m_uKeyEncryptionRounds; } + // set { m_uKeyEncryptionRounds = value; } + // } + + public KdfParameters KdfParameters { - get { return m_uKeyEncryptionRounds; } - set { m_uKeyEncryptionRounds = value; } + get { return m_kdfParams; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_kdfParams = value; + } } /// @@ -408,14 +427,37 @@ namespace KeePassLib /// /// Custom data container that can be used by plugins to store /// own data in KeePass databases. + /// The data is stored in the encrypted part of encrypted + /// database files. + /// Use unique names for your items, e.g. "PluginName_ItemName". /// public StringDictionaryEx CustomData { - get { return m_vCustomData; } - set + get { return m_dCustomData; } + internal set { - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - m_vCustomData = value; + if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_dCustomData = value; + } + } + + /// + /// Custom data container that can be used by plugins to store + /// own data in KeePass databases. + /// The data is stored in the *unencrypted* part of database files, + /// and it is not supported by all file formats (e.g. supported by KDBX, + /// unsupported by XML). + /// It is highly recommended to use CustomData instead, + /// if possible. + /// Use unique names for your items, e.g. "PluginName_ItemName". + /// + public VariantDictionary PublicCustomData + { + get { return m_dPublicCustomData; } + internal set + { + if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_dPublicCustomData = value; } } @@ -484,7 +526,8 @@ namespace KeePassLib m_uuidDataCipher = StandardAesEngine.AesUuid; m_caCompression = PwCompressionAlgorithm.GZip; - m_uKeyEncryptionRounds = PwDefs.DefaultKeyEncryptionRounds; + // m_uKeyEncryptionRounds = PwDefs.DefaultKeyEncryptionRounds; + m_kdfParams = KdfPool.GetDefaultParameters(); m_pwUserKey = null; m_memProtConfig = new MemoryProtectionConfig(); @@ -494,6 +537,7 @@ namespace KeePassLib DateTime dtNow = DateTime.Now; + m_dtSettingsChanged = dtNow; m_strName = string.Empty; m_dtNameChanged = dtNow; m_strDesc = string.Empty; @@ -523,7 +567,8 @@ namespace KeePassLib m_nHistoryMaxItems = DefaultHistoryMaxItems; m_lHistoryMaxSize = DefaultHistoryMaxSize; - m_vCustomData = new StringDictionaryEx(); + m_dCustomData = new StringDictionaryEx(); + m_dPublicCustomData = new VariantDictionary(); m_pbHashOfFileOnDisk = null; m_pbHashOfLastIO = null; @@ -1393,6 +1438,14 @@ namespace KeePassLib return; bool bForce = (mm == PwMergeMethod.OverwriteExisting); + bool bSourceNewer = (pdSource.m_dtSettingsChanged > m_dtSettingsChanged); + + if(bForce || bSourceNewer) + { + m_dtSettingsChanged = pdSource.m_dtSettingsChanged; + + m_clr = pdSource.m_clr; + } if(bForce || (pdSource.m_dtNameChanged > m_dtNameChanged)) { @@ -1412,8 +1465,6 @@ namespace KeePassLib m_dtDefaultUserChanged = pdSource.m_dtDefaultUserChanged; } - if(bForce) m_clr = pdSource.m_clr; - PwUuid pwPrefBin = m_pwRecycleBin, pwAltBin = pdSource.m_pwRecycleBin; if(bForce || (pdSource.m_dtRecycleBinChanged > m_dtRecycleBinChanged)) { @@ -1440,6 +1491,16 @@ namespace KeePassLib else if(m_pgRootGroup.FindGroup(pwAltTmp, true) != null) m_pwEntryTemplatesGroup = pwAltTmp; else m_pwEntryTemplatesGroup = PwUuid.Zero; // Debug.Assert(false); + + foreach(KeyValuePair kvp in pdSource.m_dCustomData) + { + if(bSourceNewer || !m_dCustomData.Exists(kvp.Key)) + m_dCustomData.Set(kvp.Key, kvp.Value); + } + + VariantDictionary vdLocal = m_dPublicCustomData; // Backup + m_dPublicCustomData = (VariantDictionary)pdSource.m_dPublicCustomData.Clone(); + if(!bSourceNewer) vdLocal.CopyTo(m_dPublicCustomData); // Merge } private void MergeEntryHistory(PwEntry pe, PwEntry peSource, diff --git a/src/KeePassLib2Android/PwEntry.cs b/src/KeePassLib2Android/PwEntry.cs index 4d2694c6..fa722273 100644 --- a/src/KeePassLib2Android/PwEntry.cs +++ b/src/KeePassLib2Android/PwEntry.cs @@ -65,6 +65,8 @@ namespace KeePassLib private List m_vTags = new List(); + private StringDictionaryEx m_dCustomData = new StringDictionaryEx(); + /// /// UUID of this entry. /// @@ -274,6 +276,23 @@ namespace KeePassLib } } + /// + /// Custom data container that can be used by plugins to store + /// own data in KeePass entries. + /// The data is stored in the encrypted part of encrypted + /// database files. + /// Use unique names for your items, e.g. "PluginName_ItemName". + /// + public StringDictionaryEx CustomData + { + get { return m_dCustomData; } + internal set + { + if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_dCustomData = value; + } + } + public static EventHandler EntryTouched; public EventHandler Touched; @@ -366,6 +385,8 @@ namespace KeePassLib peNew.m_vTags = new List(m_vTags); + peNew.m_dCustomData = m_dCustomData.CloneDeep(); + return peNew; } @@ -486,6 +507,8 @@ namespace KeePassLib if(m_vTags[iTag] != pe.m_vTags[iTag]) return false; } + if(!m_dCustomData.Equals(pe.m_dCustomData)) return false; + return true; } @@ -502,10 +525,10 @@ namespace KeePassLib public void AssignProperties(PwEntry peTemplate, bool bOnlyIfNewer, bool bIncludeHistory, bool bAssignLocationChanged) { - Debug.Assert(peTemplate != null); if(peTemplate == null) throw new ArgumentNullException("peTemplate"); + if(peTemplate == null) { Debug.Assert(false); throw new ArgumentNullException("peTemplate"); } - if(bOnlyIfNewer && (TimeUtil.Compare(peTemplate.m_tLastMod, m_tLastMod, - true) < 0)) + if(bOnlyIfNewer && (TimeUtil.Compare(peTemplate.m_tLastMod, + m_tLastMod, true) < 0)) return; // Template UUID should be the same as the current one @@ -515,10 +538,11 @@ namespace KeePassLib if(bAssignLocationChanged) m_tParentGroupLastMod = peTemplate.m_tParentGroupLastMod; - m_listStrings = peTemplate.m_listStrings; - m_listBinaries = peTemplate.m_listBinaries; - m_listAutoType = peTemplate.m_listAutoType; - if(bIncludeHistory) m_listHistory = peTemplate.m_listHistory; + m_listStrings = peTemplate.m_listStrings.CloneDeep(); + m_listBinaries = peTemplate.m_listBinaries.CloneDeep(); + m_listAutoType = peTemplate.m_listAutoType.CloneDeep(); + if(bIncludeHistory) + m_listHistory = peTemplate.m_listHistory.CloneDeep(); m_pwIcon = peTemplate.m_pwIcon; m_pwCustomIconID = peTemplate.m_pwCustomIconID; // Immutable @@ -536,6 +560,8 @@ namespace KeePassLib m_strOverrideUrl = peTemplate.m_strOverrideUrl; m_vTags = new List(peTemplate.m_vTags); + + m_dCustomData = peTemplate.m_dCustomData.CloneDeep(); } /// @@ -787,6 +813,9 @@ namespace KeePassLib foreach(string strTag in m_vTags) uSize += (ulong)strTag.Length; + foreach(KeyValuePair kvp in m_dCustomData) + uSize += (ulong)kvp.Key.Length + (ulong)kvp.Value.Length; + return uSize; } diff --git a/src/KeePassLib2Android/PwGroup.cs b/src/KeePassLib2Android/PwGroup.cs index 142e6902..010c4f9b 100644 --- a/src/KeePassLib2Android/PwGroup.cs +++ b/src/KeePassLib2Android/PwGroup.cs @@ -67,6 +67,8 @@ namespace KeePassLib private PwUuid m_pwLastTopVisibleEntry = PwUuid.Zero; + private StringDictionaryEx m_dCustomData = new StringDictionaryEx(); + /// /// UUID of this group. /// @@ -281,6 +283,23 @@ namespace KeePassLib } } + /// + /// Custom data container that can be used by plugins to store + /// own data in KeePass groups. + /// The data is stored in the encrypted part of encrypted + /// database files. + /// Use unique names for your items, e.g. "PluginName_ItemName". + /// + public StringDictionaryEx CustomData + { + get { return m_dCustomData; } + internal set + { + if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } + m_dCustomData = value; + } + } + public static EventHandler GroupTouched; public EventHandler Touched; @@ -376,6 +395,8 @@ namespace KeePassLib pg.m_pwLastTopVisibleEntry = m_pwLastTopVisibleEntry; + pg.m_dCustomData = m_dCustomData.CloneDeep(); + return pg; } @@ -444,6 +465,8 @@ namespace KeePassLib if(!m_pwLastTopVisibleEntry.Equals(pg.m_pwLastTopVisibleEntry)) return false; + if(!m_dCustomData.Equals(pg.m_dCustomData)) return false; + if((pwOpt & PwCompareOptions.PropertiesOnly) == PwCompareOptions.None) { if(m_listEntries.UCount != pg.m_listEntries.UCount) return false; @@ -509,6 +532,8 @@ namespace KeePassLib m_bEnableSearching = pgTemplate.m_bEnableSearching; m_pwLastTopVisibleEntry = pgTemplate.m_pwLastTopVisibleEntry; + + m_dCustomData = pgTemplate.m_dCustomData.CloneDeep(); } /// diff --git a/src/KeePassLib2Android/Resources/KLRes.Generated.cs b/src/KeePassLib2Android/Resources/KLRes.Generated.cs index 84c7f3e3..af6f5485 100644 --- a/src/KeePassLib2Android/Resources/KLRes.Generated.cs +++ b/src/KeePassLib2Android/Resources/KLRes.Generated.cs @@ -27,7 +27,7 @@ namespace KeePassLib.Resources if(dictNew == null) throw new ArgumentNullException("dictNew"); m_strCryptoStreamFailed = TryGetEx(dictNew, "CryptoStreamFailed", m_strCryptoStreamFailed); - m_strEncAlgorithmAes = TryGetEx(dictNew, "EncAlgorithmAes", m_strEncAlgorithmAes); + m_strEncDataTooLarge = TryGetEx(dictNew, "EncDataTooLarge", m_strEncDataTooLarge); m_strErrorInClipboard = TryGetEx(dictNew, "ErrorInClipboard", m_strErrorInClipboard); m_strExpect100Continue = TryGetEx(dictNew, "Expect100Continue", m_strExpect100Continue); m_strFatalError = TryGetEx(dictNew, "FatalError", m_strFatalError); @@ -36,6 +36,7 @@ namespace KeePassLib.Resources m_strFileHeaderEndEarly = TryGetEx(dictNew, "FileHeaderEndEarly", m_strFileHeaderEndEarly); m_strFileLoadFailed = TryGetEx(dictNew, "FileLoadFailed", m_strFileLoadFailed); m_strFileLockedWrite = TryGetEx(dictNew, "FileLockedWrite", m_strFileLockedWrite); + m_strFileNewVerOrPlgReq = TryGetEx(dictNew, "FileNewVerOrPlgReq", m_strFileNewVerOrPlgReq); m_strFileNewVerReq = TryGetEx(dictNew, "FileNewVerReq", m_strFileNewVerReq); m_strFileSaveCorruptionWarning = TryGetEx(dictNew, "FileSaveCorruptionWarning", m_strFileSaveCorruptionWarning); m_strFileSaveFailed = TryGetEx(dictNew, "FileSaveFailed", m_strFileSaveFailed); @@ -50,6 +51,7 @@ namespace KeePassLib.Resources m_strInvalidCompositeKeyHint = TryGetEx(dictNew, "InvalidCompositeKeyHint", m_strInvalidCompositeKeyHint); m_strInvalidDataWhileDecoding = TryGetEx(dictNew, "InvalidDataWhileDecoding", m_strInvalidDataWhileDecoding); m_strKeePass1xHint = TryGetEx(dictNew, "KeePass1xHint", m_strKeePass1xHint); + m_strKeyBits = TryGetEx(dictNew, "KeyBits", m_strKeyBits); m_strKeyFileDbSel = TryGetEx(dictNew, "KeyFileDbSel", m_strKeyFileDbSel); m_strMasterSeedLengthInvalid = TryGetEx(dictNew, "MasterSeedLengthInvalid", m_strMasterSeedLengthInvalid); m_strOldFormat = TryGetEx(dictNew, "OldFormat", m_strOldFormat); @@ -58,13 +60,14 @@ namespace KeePassLib.Resources m_strTimeout = TryGetEx(dictNew, "Timeout", m_strTimeout); m_strTryAgainSecs = TryGetEx(dictNew, "TryAgainSecs", m_strTryAgainSecs); m_strUnknownHeaderId = TryGetEx(dictNew, "UnknownHeaderId", m_strUnknownHeaderId); + m_strUnknownKdf = TryGetEx(dictNew, "UnknownKdf", m_strUnknownKdf); m_strUserAccountKeyError = TryGetEx(dictNew, "UserAccountKeyError", m_strUserAccountKeyError); m_strUserAgent = TryGetEx(dictNew, "UserAgent", m_strUserAgent); } private static readonly string[] m_vKeyNames = { "CryptoStreamFailed", - "EncAlgorithmAes", + "EncDataTooLarge", "ErrorInClipboard", "Expect100Continue", "FatalError", @@ -73,6 +76,7 @@ namespace KeePassLib.Resources "FileHeaderEndEarly", "FileLoadFailed", "FileLockedWrite", + "FileNewVerOrPlgReq", "FileNewVerReq", "FileSaveCorruptionWarning", "FileSaveFailed", @@ -87,6 +91,7 @@ namespace KeePassLib.Resources "InvalidCompositeKeyHint", "InvalidDataWhileDecoding", "KeePass1xHint", + "KeyBits", "KeyFileDbSel", "MasterSeedLengthInvalid", "OldFormat", @@ -95,6 +100,7 @@ namespace KeePassLib.Resources "Timeout", "TryAgainSecs", "UnknownHeaderId", + "UnknownKdf", "UserAccountKeyError", "UserAgent" }; @@ -115,15 +121,15 @@ namespace KeePassLib.Resources get { return m_strCryptoStreamFailed; } } - private static string m_strEncAlgorithmAes = - @"AES/Rijndael (256-Bit Key)"; + private static string m_strEncDataTooLarge = + @"The data is too large to be encrypted/decrypted securely using {PARAM}."; /// /// Look up a localized string similar to - /// 'AES/Rijndael (256-Bit Key)'. + /// 'The data is too large to be encrypted/decrypted securely using {PARAM}.'. /// - public static string EncAlgorithmAes + public static string EncDataTooLarge { - get { return m_strEncAlgorithmAes; } + get { return m_strEncDataTooLarge; } } private static string m_strErrorInClipboard = @@ -214,6 +220,17 @@ namespace KeePassLib.Resources get { return m_strFileLockedWrite; } } + private static string m_strFileNewVerOrPlgReq = + @"A newer KeePass version or a plugin is required to open this file."; + /// + /// Look up a localized string similar to + /// 'A newer KeePass version or a plugin is required to open this file.'. + /// + public static string FileNewVerOrPlgReq + { + get { return m_strFileNewVerOrPlgReq; } + } + private static string m_strFileNewVerReq = @"A newer KeePass version is required to open this file."; /// @@ -368,6 +385,17 @@ namespace KeePassLib.Resources get { return m_strKeePass1xHint; } } + private static string m_strKeyBits = + @"{PARAM}-bit key"; + /// + /// Look up a localized string similar to + /// '{PARAM}-bit key'. + /// + public static string KeyBits + { + get { return m_strKeyBits; } + } + private static string m_strKeyFileDbSel = @"Database files cannot be used as key files."; /// @@ -456,6 +484,17 @@ namespace KeePassLib.Resources get { return m_strUnknownHeaderId; } } + private static string m_strUnknownKdf = + @"Unknown key derivation function!"; + /// + /// Look up a localized string similar to + /// 'Unknown key derivation function!'. + /// + public static string UnknownKdf + { + get { return m_strUnknownKdf; } + } + private static string m_strUserAccountKeyError = @"The operating system did not grant KeePass read/write access to the user profile folder, where the protected user key is stored."; /// diff --git a/src/KeePassLib2Android/Security/ProtectedBinary.cs b/src/KeePassLib2Android/Security/ProtectedBinary.cs index fd9ca2f2..ab636157 100644 --- a/src/KeePassLib2Android/Security/ProtectedBinary.cs +++ b/src/KeePassLib2Android/Security/ProtectedBinary.cs @@ -76,7 +76,7 @@ namespace KeePassLib.Security { None = 0, ProtectedMemory, - Salsa20, + ChaCha20, ExtCrypt } @@ -166,7 +166,7 @@ namespace KeePassLib.Security /// public ProtectedBinary() { - Init(false, new byte[0]); + Init(false, MemUtil.EmptyByteArray); } /// @@ -263,11 +263,13 @@ namespace KeePassLib.Security if(pbUpd != null) pbKey32 = pbUpd; } - Salsa20Cipher s = new Salsa20Cipher(pbKey32, - BitConverter.GetBytes(m_lID)); - s.Encrypt(m_pbData, m_pbData.Length, true); - s.Dispose(); - m_mp = PbMemProt.Salsa20; + byte[] pbIV = new byte[12]; + MemUtil.UInt64ToBytesEx((ulong)m_lID, pbIV, 4); + using(ChaCha20Cipher c = new ChaCha20Cipher(pbKey32, pbIV, true)) + { + c.Encrypt(m_pbData, 0, m_pbData.Length); + } + m_mp = PbMemProt.ChaCha20; } private void Decrypt() @@ -276,12 +278,14 @@ namespace KeePassLib.Security if(m_mp == PbMemProt.ProtectedMemory) ProtectedMemory.Unprotect(m_pbData, MemoryProtectionScope.SameProcess); - else if(m_mp == PbMemProt.Salsa20) + else if(m_mp == PbMemProt.ChaCha20) { - Salsa20Cipher s = new Salsa20Cipher(g_pbKey32, - BitConverter.GetBytes(m_lID)); - s.Encrypt(m_pbData, m_pbData.Length, true); - s.Dispose(); + byte[] pbIV = new byte[12]; + MemUtil.UInt64ToBytesEx((ulong)m_lID, pbIV, 4); + using(ChaCha20Cipher c = new ChaCha20Cipher(g_pbKey32, pbIV, true)) + { + c.Decrypt(m_pbData, 0, m_pbData.Length); + } } else if(m_mp == PbMemProt.ExtCrypt) m_fExtCrypt(m_pbData, PbCryptFlags.Decrypt, m_lID); @@ -300,7 +304,7 @@ namespace KeePassLib.Security /// protected data and can therefore be cleared safely. public byte[] ReadData() { - if(m_uDataLen == 0) return new byte[0]; + if(m_uDataLen == 0) return MemUtil.EmptyByteArray; byte[] pbReturn = new byte[m_uDataLen]; diff --git a/src/KeePassLib2Android/Serialization/FileLock.cs b/src/KeePassLib2Android/Serialization/FileLock.cs index 9fa81777..7cc591a0 100644 --- a/src/KeePassLib2Android/Serialization/FileLock.cs +++ b/src/KeePassLib2Android/Serialization/FileLock.cs @@ -152,7 +152,7 @@ namespace KeePassLib.Serialization try { byte[] pbID = CryptoRandom.Instance.GetRandomBytes(16); - string strTime = TimeUtil.SerializeUtc(DateTime.Now); + string strTime = TimeUtil.SerializeUtc(DateTime.UtcNow); lfi = new LockFileInfo(Convert.ToBase64String(pbID), strTime, #if KeePassUAP diff --git a/src/KeePassLib2Android/Serialization/HashedBlockStream.cs b/src/KeePassLib2Android/Serialization/HashedBlockStream.cs index ead4855d..003fab96 100644 --- a/src/KeePassLib2Android/Serialization/HashedBlockStream.cs +++ b/src/KeePassLib2Android/Serialization/HashedBlockStream.cs @@ -22,10 +22,7 @@ using System.Diagnostics; using System.IO; using System.Text; -#if !KeePassUAP -using System.Security.Cryptography; -#endif - +using KeePassLib.Cryptography; using KeePassLib.Native; using KeePassLib.Utility; @@ -37,7 +34,7 @@ namespace KeePassLib.Serialization { public sealed class HashedBlockStream : Stream { - private const int m_nDefaultBufferSize = 1024 * 1024; // 1 MB + private const int NbDefaultBufferSize = 1024 * 1024; // 1 MB private Stream m_sBaseStream; private bool m_bWriting; @@ -50,7 +47,7 @@ namespace KeePassLib.Serialization private byte[] m_pbBuffer; private int m_nBufferPos = 0; - private uint m_uBufferIndex = 0; + private uint m_uBlockIndex = 0; public override bool CanRead { @@ -69,13 +66,13 @@ namespace KeePassLib.Serialization public override long Length { - get { throw new NotSupportedException(); } + get { Debug.Assert(false); throw new NotSupportedException(); } } public override long Position { - get { throw new NotSupportedException(); } - set { throw new NotSupportedException(); } + get { Debug.Assert(false); throw new NotSupportedException(); } + set { Debug.Assert(false); throw new NotSupportedException(); } } public HashedBlockStream(Stream sBaseStream, bool bWriting) @@ -100,7 +97,7 @@ namespace KeePassLib.Serialization if(sBaseStream == null) throw new ArgumentNullException("sBaseStream"); if(nBufferSize < 0) throw new ArgumentOutOfRangeException("nBufferSize"); - if(nBufferSize == 0) nBufferSize = m_nDefaultBufferSize; + if(nBufferSize == 0) nBufferSize = NbDefaultBufferSize; m_sBaseStream = sBaseStream; m_bWriting = bWriting; @@ -114,7 +111,7 @@ namespace KeePassLib.Serialization m_brInput = new BinaryReader(sBaseStream, utf8); - m_pbBuffer = new byte[0]; + m_pbBuffer = MemUtil.EmptyByteArray; } else // Writing mode { @@ -142,7 +139,7 @@ namespace KeePassLib.Serialization #endif if(m_sBaseStream != null) { - if(m_bWriting == false) // Reading mode + if(!m_bWriting) // Reading mode { m_brInput.Close(); m_brInput = null; @@ -209,9 +206,9 @@ namespace KeePassLib.Serialization m_nBufferPos = 0; - if(m_brInput.ReadUInt32() != m_uBufferIndex) + if(m_brInput.ReadUInt32() != m_uBlockIndex) throw new InvalidDataException(); - ++m_uBufferIndex; + ++m_uBlockIndex; byte[] pbStoredHash = m_brInput.ReadBytes(32); if((pbStoredHash == null) || (pbStoredHash.Length != 32)) @@ -236,7 +233,7 @@ namespace KeePassLib.Serialization } m_bEos = true; - m_pbBuffer = new byte[0]; + m_pbBuffer = MemUtil.EmptyByteArray; return false; } @@ -246,16 +243,12 @@ namespace KeePassLib.Serialization if(m_bVerify) { - SHA256Managed sha256 = new SHA256Managed(); - byte[] pbComputedHash = sha256.ComputeHash(m_pbBuffer); + byte[] pbComputedHash = CryptoUtil.HashSha256(m_pbBuffer); if((pbComputedHash == null) || (pbComputedHash.Length != 32)) throw new InvalidOperationException(); - for(int iHashPos = 0; iHashPos < 32; ++iHashPos) - { - if(pbStoredHash[iHashPos] != pbComputedHash[iHashPos]) - throw new InvalidDataException(); - } + if(!MemUtil.ArraysEqual(pbStoredHash, pbComputedHash)) + throw new InvalidDataException(); } return true; @@ -283,26 +276,24 @@ namespace KeePassLib.Serialization private void WriteHashedBlock() { - m_bwOutput.Write(m_uBufferIndex); - ++m_uBufferIndex; + m_bwOutput.Write(m_uBlockIndex); + ++m_uBlockIndex; if(m_nBufferPos > 0) { - SHA256Managed sha256 = new SHA256Managed(); + byte[] pbHash = CryptoUtil.HashSha256(m_pbBuffer, 0, m_nBufferPos); -#if !KeePassLibSD - byte[] pbHash = sha256.ComputeHash(m_pbBuffer, 0, m_nBufferPos); -#else - byte[] pbHash; - if(m_nBufferPos == m_pbBuffer.Length) - pbHash = sha256.ComputeHash(m_pbBuffer); - else - { - byte[] pbData = new byte[m_nBufferPos]; - Array.Copy(m_pbBuffer, 0, pbData, 0, m_nBufferPos); - pbHash = sha256.ComputeHash(pbData); - } -#endif + // For KeePassLibSD: + // SHA256Managed sha256 = new SHA256Managed(); + // byte[] pbHash; + // if(m_nBufferPos == m_pbBuffer.Length) + // pbHash = sha256.ComputeHash(m_pbBuffer); + // else + // { + // byte[] pbData = new byte[m_nBufferPos]; + // Array.Copy(m_pbBuffer, 0, pbData, 0, m_nBufferPos); + // pbHash = sha256.ComputeHash(pbData); + // } m_bwOutput.Write(pbHash); } diff --git a/src/KeePassLib2Android/Serialization/HmacBlockStream.cs b/src/KeePassLib2Android/Serialization/HmacBlockStream.cs new file mode 100644 index 00000000..59295742 --- /dev/null +++ b/src/KeePassLib2Android/Serialization/HmacBlockStream.cs @@ -0,0 +1,325 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2016 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; + +#if !KeePassUAP +using System.Security.Cryptography; +#endif + +using KeePassLib.Resources; +using KeePassLib.Utility; + +namespace KeePassLib.Serialization +{ + public sealed class HmacBlockStream : Stream + { + private const int NbDefaultBufferSize = 1024 * 1024; // 1 MB + + private Stream m_sBase; + private readonly bool m_bWriting; + private readonly bool m_bVerify; + private byte[] m_pbKey; + + private bool m_bEos = false; + private byte[] m_pbBuffer; + private int m_iBufferPos = 0; + + private ulong m_uBlockIndex = 0; + + public override bool CanRead + { + get { return !m_bWriting; } + } + + public override bool CanSeek + { + get { return false; } + } + + public override bool CanWrite + { + get { return m_bWriting; } + } + + public override long Length + { + get { Debug.Assert(false); throw new NotSupportedException(); } + } + + public override long Position + { + get { Debug.Assert(false); throw new NotSupportedException(); } + set { Debug.Assert(false); throw new NotSupportedException(); } + } + + public HmacBlockStream(Stream sBase, bool bWriting, bool bVerify, + byte[] pbKey) + { + if(sBase == null) throw new ArgumentNullException("sBase"); + if(pbKey == null) throw new ArgumentNullException("pbKey"); + + m_sBase = sBase; + m_bWriting = bWriting; + m_bVerify = bVerify; + m_pbKey = pbKey; + + if(!m_bWriting) // Reading mode + { + if(!m_sBase.CanRead) throw new InvalidOperationException(); + + m_pbBuffer = MemUtil.EmptyByteArray; + } + else // Writing mode + { + if(!m_sBase.CanWrite) throw new InvalidOperationException(); + + m_pbBuffer = new byte[NbDefaultBufferSize]; + } + } + + public override void Flush() + { + Debug.Assert(m_sBase != null); // Object should not be disposed + if(m_bWriting && (m_sBase != null)) m_sBase.Flush(); + } + +#if KeePassUAP + protected override void Dispose(bool disposing) + { + if(!disposing) return; +#else + public override void Close() + { +#endif + if(m_sBase != null) + { + if(m_bWriting) + { + if(m_iBufferPos == 0) // No data left in buffer + WriteSafeBlock(); // Write terminating block + else + { + WriteSafeBlock(); // Write remaining buffered data + WriteSafeBlock(); // Write terminating block + } + + Flush(); + } + + m_sBase.Close(); + m_sBase = null; + } + } + + public override long Seek(long lOffset, SeekOrigin soOrigin) + { + Debug.Assert(false); + throw new NotSupportedException(); + } + + public override void SetLength(long lValue) + { + Debug.Assert(false); + throw new NotSupportedException(); + } + + internal static byte[] GetHmacKey64(byte[] pbKey, ulong uBlockIndex) + { + if(pbKey == null) throw new ArgumentNullException("pbKey"); + Debug.Assert(pbKey.Length == 64); + + // We are computing the HMAC using SHA-256, whose internal + // block size is 512 bits; thus create a key that is 512 + // bits long (using SHA-512) + + byte[] pbBlockKey; + using(SHA512Managed h = new SHA512Managed()) + { + byte[] pbIndex = MemUtil.UInt64ToBytes(uBlockIndex); + + h.TransformBlock(pbIndex, 0, pbIndex.Length, pbIndex, 0); + h.TransformBlock(pbKey, 0, pbKey.Length, pbKey, 0); + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + pbBlockKey = h.Hash; + } + +#if DEBUG + byte[] pbZero = new byte[64]; + Debug.Assert((pbBlockKey.Length == 64) && !MemUtil.ArraysEqual( + pbBlockKey, pbZero)); // Ensure we own pbBlockKey +#endif + return pbBlockKey; + } + + public override int Read(byte[] pbBuffer, int iOffset, int nCount) + { + if(m_bWriting) throw new InvalidOperationException(); + + int nRemaining = nCount; + while(nRemaining > 0) + { + if(m_iBufferPos == m_pbBuffer.Length) + { + if(!ReadSafeBlock()) + return (nCount - nRemaining); // Bytes actually read + } + + int nCopy = Math.Min(m_pbBuffer.Length - m_iBufferPos, nRemaining); + Debug.Assert(nCopy > 0); + + Array.Copy(m_pbBuffer, m_iBufferPos, pbBuffer, iOffset, nCopy); + + iOffset += nCopy; + m_iBufferPos += nCopy; + + nRemaining -= nCopy; + } + + return nCount; + } + + private bool ReadSafeBlock() + { + if(m_bEos) return false; // End of stream reached already + + byte[] pbStoredHmac = MemUtil.Read(m_sBase, 32); + if((pbStoredHmac == null) || (pbStoredHmac.Length != 32)) + throw new EndOfStreamException(); + + // Block index is implicit: it's used in the HMAC computation, + // but does not need to be stored + // byte[] pbBlockIndex = MemUtil.Read(m_sBase, 8); + // if((pbBlockIndex == null) || (pbBlockIndex.Length != 8)) + // throw new EndOfStreamException(); + // ulong uBlockIndex = MemUtil.BytesToUInt64(pbBlockIndex); + // if((uBlockIndex != m_uBlockIndex) && m_bVerify) + // throw new InvalidDataException(); + byte[] pbBlockIndex = MemUtil.UInt64ToBytes(m_uBlockIndex); + + byte[] pbBlockSize = MemUtil.Read(m_sBase, 4); + if((pbBlockSize == null) || (pbBlockSize.Length != 4)) + throw new EndOfStreamException(); + int nBlockSize = MemUtil.BytesToInt32(pbBlockSize); + if(nBlockSize < 0) + throw new InvalidDataException(KLRes.FileCorrupted); + + m_iBufferPos = 0; + + m_pbBuffer = MemUtil.Read(m_sBase, nBlockSize); + if((m_pbBuffer == null) || ((m_pbBuffer.Length != nBlockSize) && m_bVerify)) + throw new EndOfStreamException(); + + if(m_bVerify) + { + byte[] pbCmpHmac; + byte[] pbBlockKey = GetHmacKey64(m_pbKey, m_uBlockIndex); + using(HMACSHA256 h = new HMACSHA256(pbBlockKey)) + { + h.TransformBlock(pbBlockIndex, 0, pbBlockIndex.Length, + pbBlockIndex, 0); + h.TransformBlock(pbBlockSize, 0, pbBlockSize.Length, + pbBlockSize, 0); + + if(m_pbBuffer.Length > 0) + h.TransformBlock(m_pbBuffer, 0, m_pbBuffer.Length, + m_pbBuffer, 0); + + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + pbCmpHmac = h.Hash; + } + MemUtil.ZeroByteArray(pbBlockKey); + + if(!MemUtil.ArraysEqual(pbCmpHmac, pbStoredHmac)) + throw new InvalidDataException(KLRes.FileCorrupted); + } + + ++m_uBlockIndex; + + if(nBlockSize == 0) + { + m_bEos = true; + return false; // No further data available + } + return true; + } + + public override void Write(byte[] pbBuffer, int iOffset, int nCount) + { + if(!m_bWriting) throw new InvalidOperationException(); + + while(nCount > 0) + { + if(m_iBufferPos == m_pbBuffer.Length) + WriteSafeBlock(); + + int nCopy = Math.Min(m_pbBuffer.Length - m_iBufferPos, nCount); + Debug.Assert(nCopy > 0); + + Array.Copy(pbBuffer, iOffset, m_pbBuffer, m_iBufferPos, nCopy); + + iOffset += nCopy; + m_iBufferPos += nCopy; + + nCount -= nCopy; + } + } + + private void WriteSafeBlock() + { + byte[] pbBlockIndex = MemUtil.UInt64ToBytes(m_uBlockIndex); + + int cbBlockSize = m_iBufferPos; + byte[] pbBlockSize = MemUtil.Int32ToBytes(cbBlockSize); + + byte[] pbBlockHmac; + byte[] pbBlockKey = GetHmacKey64(m_pbKey, m_uBlockIndex); + using(HMACSHA256 h = new HMACSHA256(pbBlockKey)) + { + h.TransformBlock(pbBlockIndex, 0, pbBlockIndex.Length, + pbBlockIndex, 0); + h.TransformBlock(pbBlockSize, 0, pbBlockSize.Length, + pbBlockSize, 0); + + if(cbBlockSize > 0) + h.TransformBlock(m_pbBuffer, 0, cbBlockSize, m_pbBuffer, 0); + + h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); + + pbBlockHmac = h.Hash; + } + MemUtil.ZeroByteArray(pbBlockKey); + + MemUtil.Write(m_sBase, pbBlockHmac); + // MemUtil.Write(m_sBase, pbBlockIndex); // Implicit + MemUtil.Write(m_sBase, pbBlockSize); + if(cbBlockSize > 0) + m_sBase.Write(m_pbBuffer, 0, cbBlockSize); + + ++m_uBlockIndex; + m_iBufferPos = 0; + } + } +} diff --git a/src/KeePassLib2Android/Serialization/KdbxFile.Read.Streamed.cs b/src/KeePassLib2Android/Serialization/KdbxFile.Read.Streamed.cs index 5f39afda..5cbc4e14 100644 --- a/src/KeePassLib2Android/Serialization/KdbxFile.Read.Streamed.cs +++ b/src/KeePassLib2Android/Serialization/KdbxFile.Read.Streamed.cs @@ -58,13 +58,17 @@ namespace KeePassLib.Serialization DeletedObject, Group, GroupTimes, + GroupCustomData, + GroupCustomDataItem, Entry, EntryTimes, EntryString, EntryBinary, EntryAutoType, EntryAutoTypeItem, - EntryHistory + EntryHistory, + EntryCustomData, + EntryCustomDataItem } private bool m_bReadNextNode = true; @@ -84,10 +88,14 @@ namespace KeePassLib.Serialization private byte[] m_pbCustomIconData = null; private string m_strCustomDataKey = null; private string m_strCustomDataValue = null; + private string m_strGroupCustomDataKey = null; + private string m_strGroupCustomDataValue = null; + private string m_strEntryCustomDataKey = null; + private string m_strEntryCustomDataValue = null; - private void ReadXmlStreamed(Stream readerStream, Stream sParentStream) + private void ReadXmlStreamed(Stream sXml, Stream sParent) { - ReadDocumentStreamed(CreateXmlReader(readerStream), sParentStream); + ReadDocumentStreamed(CreateXmlReader(sXml), sParent); } internal static XmlReaderSettings CreateStdXmlReaderSettings() @@ -215,15 +223,25 @@ namespace KeePassLib.Serialization ReadString(xr); // Ignore else if(xr.Name == ElemHeaderHash) { + // The header hash is typically only stored in + // KDBX <= 3.1 files, not in KDBX >= 4 files + // (here, the header is verified via a HMAC), + // but we also support it for KDBX >= 4 files + // (i.e. if it's present, we check it) + string strHash = ReadString(xr); if(!string.IsNullOrEmpty(strHash) && (m_pbHashOfHeader != null) && !m_bRepairMode) { + Debug.Assert(m_uFileVersion <= FileVersion32_3); + byte[] pbHash = Convert.FromBase64String(strHash); if(!MemUtil.ArraysEqual(pbHash, m_pbHashOfHeader)) throw new IOException(KLRes.FileCorrupted); } } + else if(xr.Name == ElemSettingsChanged) + m_pwDatabase.SettingsChanged = ReadTime(xr); else if(xr.Name == ElemDbName) m_pwDatabase.Name = ReadString(xr); else if(xr.Name == ElemDbNameChanged) @@ -383,6 +401,8 @@ namespace KeePassLib.Serialization m_ctxGroup.EnableSearching = StrUtil.StringToBoolEx(ReadString(xr)); else if(xr.Name == ElemLastTopVisibleEntry) m_ctxGroup.LastTopVisibleEntry = ReadUuid(xr); + else if(xr.Name == ElemCustomData) + return SwitchContext(ctx, KdbContext.GroupCustomData, xr); else if(xr.Name == ElemGroup) { m_ctxGroup = new PwGroup(false, false); @@ -403,6 +423,20 @@ namespace KeePassLib.Serialization else ReadUnknown(xr); break; + case KdbContext.GroupCustomData: + if(xr.Name == ElemStringDictExItem) + return SwitchContext(ctx, KdbContext.GroupCustomDataItem, xr); + else ReadUnknown(xr); + break; + + case KdbContext.GroupCustomDataItem: + if(xr.Name == ElemKey) + m_strGroupCustomDataKey = ReadString(xr); + else if(xr.Name == ElemValue) + m_strGroupCustomDataValue = ReadString(xr); + else ReadUnknown(xr); + break; + case KdbContext.Entry: if(xr.Name == ElemUuid) m_ctxEntry.Uuid = ReadUuid(xr); @@ -434,6 +468,8 @@ namespace KeePassLib.Serialization return SwitchContext(ctx, KdbContext.EntryBinary, xr); else if(xr.Name == ElemAutoType) return SwitchContext(ctx, KdbContext.EntryAutoType, xr); + else if(xr.Name == ElemCustomData) + return SwitchContext(ctx, KdbContext.EntryCustomData, xr); else if(xr.Name == ElemHistory) { Debug.Assert(m_bEntryInHistory == false); @@ -508,6 +544,20 @@ namespace KeePassLib.Serialization else ReadUnknown(xr); break; + case KdbContext.EntryCustomData: + if(xr.Name == ElemStringDictExItem) + return SwitchContext(ctx, KdbContext.EntryCustomDataItem, xr); + else ReadUnknown(xr); + break; + + case KdbContext.EntryCustomDataItem: + if(xr.Name == ElemKey) + m_strEntryCustomDataKey = ReadString(xr); + else if(xr.Name == ElemValue) + m_strEntryCustomDataValue = ReadString(xr); + else ReadUnknown(xr); + break; + case KdbContext.EntryHistory: if(xr.Name == ElemEntry) { @@ -609,6 +659,19 @@ namespace KeePassLib.Serialization } else if((ctx == KdbContext.GroupTimes) && (xr.Name == ElemTimes)) return KdbContext.Group; + else if((ctx == KdbContext.GroupCustomData) && (xr.Name == ElemCustomData)) + return KdbContext.Group; + else if((ctx == KdbContext.GroupCustomDataItem) && (xr.Name == ElemStringDictExItem)) + { + if((m_strGroupCustomDataKey != null) && (m_strGroupCustomDataValue != null)) + m_ctxGroup.CustomData.Set(m_strGroupCustomDataKey, m_strGroupCustomDataValue); + else { Debug.Assert(false); } + + m_strGroupCustomDataKey = null; + m_strGroupCustomDataValue = null; + + return KdbContext.GroupCustomData; + } else if((ctx == KdbContext.Entry) && (xr.Name == ElemEntry)) { // Create new UUID if absent @@ -659,6 +722,19 @@ namespace KeePassLib.Serialization m_ctxATSeq = null; return KdbContext.EntryAutoType; } + else if((ctx == KdbContext.EntryCustomData) && (xr.Name == ElemCustomData)) + return KdbContext.Entry; + else if((ctx == KdbContext.EntryCustomDataItem) && (xr.Name == ElemStringDictExItem)) + { + if((m_strEntryCustomDataKey != null) && (m_strEntryCustomDataValue != null)) + m_ctxEntry.CustomData.Set(m_strEntryCustomDataKey, m_strEntryCustomDataValue); + else { Debug.Assert(false); } + + m_strEntryCustomDataKey = null; + m_strEntryCustomDataValue = null; + + return KdbContext.EntryCustomData; + } else if((ctx == KdbContext.EntryHistory) && (xr.Name == ElemHistory)) { m_bEntryInHistory = false; @@ -883,7 +959,7 @@ namespace KeePassLib.Serialization byte[] pbEncrypted; if(strEncrypted.Length > 0) pbEncrypted = Convert.FromBase64String(strEncrypted); - else pbEncrypted = new byte[0]; + else pbEncrypted = MemUtil.EmptyByteArray; byte[] pbPad = m_randomStream.GetRandomBytes((uint)pbEncrypted.Length); diff --git a/src/KeePassLib2Android/Serialization/KdbxFile.Read.cs b/src/KeePassLib2Android/Serialization/KdbxFile.Read.cs index 8d9b4a00..68028bc2 100644 --- a/src/KeePassLib2Android/Serialization/KdbxFile.Read.cs +++ b/src/KeePassLib2Android/Serialization/KdbxFile.Read.cs @@ -35,8 +35,10 @@ using System.IO.Compression; using KeePassLibSD; #endif +using KeePassLib.Collections; using KeePassLib.Cryptography; using KeePassLib.Cryptography.Cipher; +using KeePassLib.Cryptography.KeyDerivation; using KeePassLib.Interfaces; using KeePassLib.Keys; using KeePassLib.Resources; @@ -50,74 +52,108 @@ namespace KeePassLib.Serialization public sealed partial class KdbxFile { /// - /// Load a KDB file from a file. + /// Load a KDBX file. /// /// File to load. - /// Format specifier. + /// Format. /// Status logger (optional). - public void Load(string strFilePath, KdbxFormat kdbFormat, IStatusLogger slLogger) + public void Load(string strFilePath, KdbxFormat fmt, IStatusLogger slLogger) { IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFilePath); - Load(IOConnection.OpenRead(ioc), kdbFormat, slLogger); + Load(IOConnection.OpenRead(ioc), fmt, slLogger); } /// - /// Load a KDB file from a stream. + /// Load a KDBX file from a stream. /// /// Stream to read the data from. Must contain /// a KDBX stream. - /// Format specifier. + /// Format. /// Status logger (optional). - public void Load(Stream sSource, KdbxFormat kdbFormat, IStatusLogger slLogger) + public void Load(Stream sSource, KdbxFormat fmt, IStatusLogger slLogger) { Debug.Assert(sSource != null); if(sSource == null) throw new ArgumentNullException("sSource"); - m_format = kdbFormat; + m_format = fmt; m_slLogger = slLogger; - HashingStreamEx hashedStream = new HashingStreamEx(sSource, false, null); - UTF8Encoding encNoBom = StrUtil.Utf8; + byte[] pbCipherKey = null; + byte[] pbHmacKey64 = null; + + List lStreams = new List(); + lStreams.Add(sSource); + + HashingStreamEx sHashing = new HashingStreamEx(sSource, false, null); + lStreams.Add(sHashing); + try { - BinaryReaderEx br = null; - BinaryReaderEx brDecrypted = null; - Stream readerStream = null; - - if(kdbFormat == KdbxFormat.Default) + Stream sXml; + if(fmt == KdbxFormat.Default) { - br = new BinaryReaderEx(hashedStream, encNoBom, KLRes.FileCorrupted); - ReadHeader(br); + BinaryReaderEx br = new BinaryReaderEx(sHashing, + encNoBom, KLRes.FileCorrupted); + byte[] pbHeader = LoadHeader(br); - Stream sDecrypted = AttachStreamDecryptor(hashedStream); - if((sDecrypted == null) || (sDecrypted == hashedStream)) - throw new SecurityException(KLRes.CryptoStreamFailed); + int cbEncKey, cbEncIV; + ICipherEngine iCipher = GetCipher(out cbEncKey, out cbEncIV); - brDecrypted = new BinaryReaderEx(sDecrypted, encNoBom, KLRes.FileCorrupted); - byte[] pbStoredStartBytes = brDecrypted.ReadBytes(32); + ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64); - if((m_pbStreamStartBytes == null) || (m_pbStreamStartBytes.Length != 32)) - throw new InvalidDataException(); - - for(int iStart = 0; iStart < 32; ++iStart) + Stream sPlain; + if(m_uFileVersion <= FileVersion32_3) { - if(pbStoredStartBytes[iStart] != m_pbStreamStartBytes[iStart]) - throw new InvalidCompositeKeyException(); - } + Stream sDecrypted = EncryptStream(sHashing, iCipher, + pbCipherKey, cbEncIV, false); + if((sDecrypted == null) || (sDecrypted == sHashing)) + throw new SecurityException(KLRes.CryptoStreamFailed); + lStreams.Add(sDecrypted); - Stream sHashed = new HashedBlockStream(sDecrypted, false, 0, - !m_bRepairMode); + BinaryReaderEx brDecrypted = new BinaryReaderEx(sDecrypted, + encNoBom, KLRes.FileCorrupted); + byte[] pbStoredStartBytes = brDecrypted.ReadBytes(32); + + if((m_pbStreamStartBytes == null) || (m_pbStreamStartBytes.Length != 32)) + throw new InvalidDataException(); + if(!MemUtil.ArraysEqual(pbStoredStartBytes, m_pbStreamStartBytes)) + throw new InvalidCompositeKeyException(); + + sPlain = new HashedBlockStream(sDecrypted, false, 0, !m_bRepairMode); + } + else // KDBX >= 4 + { + byte[] pbHeaderHmac = ComputeHeaderHmac(pbHeader, pbHmacKey64); + byte[] pbStoredHmac = MemUtil.Read(sHashing, 32); + if((pbStoredHmac == null) || (pbStoredHmac.Length != 32)) + throw new InvalidDataException(); + if(!MemUtil.ArraysEqual(pbHeaderHmac, pbStoredHmac)) + throw new InvalidCompositeKeyException(); + + HmacBlockStream sBlocks = new HmacBlockStream(sHashing, + false, !m_bRepairMode, pbHmacKey64); + lStreams.Add(sBlocks); + + sPlain = EncryptStream(sBlocks, iCipher, pbCipherKey, + cbEncIV, false); + if((sPlain == null) || (sPlain == sBlocks)) + throw new SecurityException(KLRes.CryptoStreamFailed); + } + lStreams.Add(sPlain); if(m_pwDatabase.Compression == PwCompressionAlgorithm.GZip) - readerStream = new GZipStream(sHashed, CompressionMode.Decompress); - else readerStream = sHashed; + { + sXml = new GZipStream(sPlain, CompressionMode.Decompress); + lStreams.Add(sXml); + } + else sXml = sPlain; } - else if(kdbFormat == KdbxFormat.PlainXml) - readerStream = hashedStream; - else { Debug.Assert(false); throw new FormatException("KdbFormat"); } + else if(fmt == KdbxFormat.PlainXml) + sXml = sHashing; + else { Debug.Assert(false); throw new ArgumentOutOfRangeException("fmt"); } - if(kdbFormat != KdbxFormat.PlainXml) // Is an encrypted format + if(fmt == KdbxFormat.Default) { if(m_pbProtectedStreamKey == null) { @@ -137,7 +173,7 @@ namespace KeePassLib.Serialization // { // while(true) // { - // int b = readerStream.ReadByte(); + // int b = sXml.ReadByte(); // if(b == -1) break; // fsOut.WriteByte((byte)b); // } @@ -146,26 +182,29 @@ namespace KeePassLib.Serialization // fsOut.Close(); #endif - ReadXmlStreamed(readerStream, hashedStream); - // ReadXmlDom(readerStream); - - readerStream.Close(); - // GC.KeepAlive(br); - // GC.KeepAlive(brDecrypted); + ReadXmlStreamed(sXml, sHashing); + // ReadXmlDom(sXml); } catch(CryptographicException) // Thrown on invalid padding { throw new CryptographicException(KLRes.FileCorrupted); } - finally { CommonCleanUpRead(sSource, hashedStream); } + finally + { + if(pbCipherKey != null) MemUtil.ZeroByteArray(pbCipherKey); + if(pbHmacKey64 != null) MemUtil.ZeroByteArray(pbHmacKey64); + + CommonCleanUpRead(lStreams, sHashing); + } } - private void CommonCleanUpRead(Stream sSource, HashingStreamEx hashedStream) + private void CommonCleanUpRead(List lStreams, HashingStreamEx sHashing) { - hashedStream.Close(); - m_pbHashOfFileOnDisk = hashedStream.Hash; + CloseStreams(lStreams); - sSource.Close(); + Debug.Assert(lStreams.Contains(sHashing)); // sHashing must be closed + m_pbHashOfFileOnDisk = sHashing.Hash; + Debug.Assert(m_pbHashOfFileOnDisk != null); // Reset memory protection settings (to always use reasonable // defaults) @@ -187,7 +226,7 @@ namespace KeePassLib.Serialization m_pbHashOfHeader = null; } - private void ReadHeader(BinaryReaderEx br) + private byte[] LoadHeader(BinaryReaderEx br) { MemoryStream msHeader = new MemoryStream(); Debug.Assert(br.CopyDataTo == null); @@ -212,18 +251,19 @@ namespace KeePassLib.Serialization if((uVersion & FileVersionCriticalMask) > (FileVersion32 & FileVersionCriticalMask)) throw new FormatException(KLRes.FileVersionUnsupported + MessageService.NewParagraph + KLRes.FileNewVerReq); + m_uFileVersion = uVersion; while(true) { - if(ReadHeaderField(br) == false) - break; + if(!ReadHeaderField(br)) break; } br.CopyDataTo = null; byte[] pbHeader = msHeader.ToArray(); msHeader.Close(); - SHA256Managed sha256 = new SHA256Managed(); - m_pbHashOfHeader = sha256.ComputeHash(pbHeader); + + m_pbHashOfHeader = CryptoUtil.HashSha256(pbHeader); + return pbHeader; } private bool ReadHeaderField(BinaryReaderEx brSource) @@ -232,15 +272,21 @@ namespace KeePassLib.Serialization if(brSource == null) throw new ArgumentNullException("brSource"); byte btFieldID = brSource.ReadByte(); - ushort uSize = MemUtil.BytesToUInt16(brSource.ReadBytes(2)); - byte[] pbData = null; - if(uSize > 0) + int cbSize; + Debug.Assert(m_uFileVersion > 0); + if(m_uFileVersion <= FileVersion32_3) + cbSize = (int)MemUtil.BytesToUInt16(brSource.ReadBytes(2)); + else cbSize = MemUtil.BytesToInt32(brSource.ReadBytes(4)); + if(cbSize < 0) throw new FormatException(KLRes.FileCorrupted); + + byte[] pbData = MemUtil.EmptyByteArray; + if(cbSize > 0) { string strPrevExcpText = brSource.ReadExceptionText; brSource.ReadExceptionText = KLRes.FileHeaderEndEarly; - pbData = brSource.ReadBytes(uSize); + pbData = brSource.ReadBytes(cbSize); brSource.ReadExceptionText = strPrevExcpText; } @@ -266,13 +312,27 @@ namespace KeePassLib.Serialization CryptoRandom.Instance.AddEntropy(pbData); break; + // Obsolete; for backward compatibility only case KdbxHeaderFieldID.TransformSeed: - m_pbTransformSeed = pbData; + AesKdf kdfS = new AesKdf(); + if(!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfS.Uuid)) + m_pwDatabase.KdfParameters = kdfS.GetDefaultParameters(); + + // m_pbTransformSeed = pbData; + m_pwDatabase.KdfParameters.SetByteArray(AesKdf.ParamSeed, pbData); + CryptoRandom.Instance.AddEntropy(pbData); break; + // Obsolete; for backward compatibility only case KdbxHeaderFieldID.TransformRounds: - m_pwDatabase.KeyEncryptionRounds = MemUtil.BytesToUInt64(pbData); + AesKdf kdfR = new AesKdf(); + if(!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfR.Uuid)) + m_pwDatabase.KdfParameters = kdfR.GetDefaultParameters(); + + // m_pwDatabase.KeyEncryptionRounds = MemUtil.BytesToUInt64(pbData); + m_pwDatabase.KdfParameters.SetUInt64(AesKdf.ParamRounds, + MemUtil.BytesToUInt64(pbData)); break; case KdbxHeaderFieldID.EncryptionIV: @@ -285,6 +345,7 @@ namespace KeePassLib.Serialization break; case KdbxHeaderFieldID.StreamStartBytes: + Debug.Assert(m_uFileVersion <= FileVersion32_3); m_pbStreamStartBytes = pbData; break; @@ -292,6 +353,15 @@ namespace KeePassLib.Serialization SetInnerRandomStreamID(pbData); break; + case KdbxHeaderFieldID.KdfParameters: + m_pwDatabase.KdfParameters = KdfParameters.DeserializeExt(pbData); + break; + + case KdbxHeaderFieldID.PublicCustomData: + Debug.Assert(m_pwDatabase.PublicCustomData.Count == 0); + m_pwDatabase.PublicCustomData = VariantDictionary.Deserialize(pbData); + break; + default: Debug.Assert(false); if(m_slLogger != null) @@ -305,7 +375,7 @@ namespace KeePassLib.Serialization private void SetCipher(byte[] pbID) { - if((pbID == null) || (pbID.Length != 16)) + if((pbID == null) || (pbID.Length != (int)PwUuid.UuidSize)) throw new FormatException(KLRes.FileUnknownCipher); m_pwDatabase.DataCipherUuid = new PwUuid(pbID); @@ -329,35 +399,6 @@ namespace KeePassLib.Serialization m_craInnerRandomStream = (CrsAlgorithm)uID; } - private Stream AttachStreamDecryptor(Stream s) - { - MemoryStream ms = new MemoryStream(); - - Debug.Assert(m_pbMasterSeed.Length == 32); - if(m_pbMasterSeed.Length != 32) - throw new FormatException(KLRes.MasterSeedLengthInvalid); - ms.Write(m_pbMasterSeed, 0, 32); - - byte[] pKey32 = m_pwDatabase.MasterKey.GenerateKey32(m_pbTransformSeed, - m_pwDatabase.KeyEncryptionRounds).ReadData(); - if((pKey32 == null) || (pKey32.Length != 32)) - throw new SecurityException(KLRes.InvalidCompositeKey); - ms.Write(pKey32, 0, 32); - - SHA256Managed sha256 = new SHA256Managed(); - byte[] aesKey = sha256.ComputeHash(ms.ToArray()); - - ms.Close(); - Array.Clear(pKey32, 0, 32); - - if((aesKey == null) || (aesKey.Length != 32)) - throw new SecurityException(KLRes.FinalKeyCreationFailed); - - ICipherEngine iEngine = CipherPool.GlobalPool.GetCipher(m_pwDatabase.DataCipherUuid); - if(iEngine == null) throw new SecurityException(KLRes.FileUnknownCipher); - return iEngine.DecryptStream(s, aesKey, m_pbEncryptionIV); - } - [Obsolete] public static List ReadEntries(PwDatabase pwDatabase, Stream msData) { diff --git a/src/KeePassLib2Android/Serialization/KdbxFile.Write.cs b/src/KeePassLib2Android/Serialization/KdbxFile.Write.cs index 27a1853e..ea80fc17 100644 --- a/src/KeePassLib2Android/Serialization/KdbxFile.Write.cs +++ b/src/KeePassLib2Android/Serialization/KdbxFile.Write.cs @@ -40,6 +40,7 @@ using System.IO.Compression; using KeePassLib.Collections; using KeePassLib.Cryptography; using KeePassLib.Cryptography.Cipher; +using KeePassLib.Cryptography.KeyDerivation; using KeePassLib.Delegates; using KeePassLib.Interfaces; using KeePassLib.Keys; @@ -54,7 +55,7 @@ namespace KeePassLib.Serialization /// public sealed partial class KdbxFile { - // public void Save(string strFile, PwGroup pgDataSource, KdbxFormat format, + // public void Save(string strFile, PwGroup pgDataSource, KdbxFormat fmt, // IStatusLogger slLogger) // { // bool bMadeUnhidden = UrlUtil.UnhideFile(strFile); @@ -72,56 +73,117 @@ namespace KeePassLib.Serialization /// Group containing all groups and /// entries to write. If null, the complete database will /// be written. - /// Format of the file to create. + /// Format of the file to create. /// Logger that recieves status information. - public void Save(Stream sSaveTo, PwGroup pgDataSource, KdbxFormat format, + public void Save(Stream sSaveTo, PwGroup pgDataSource, KdbxFormat fmt, IStatusLogger slLogger) { Debug.Assert(sSaveTo != null); if(sSaveTo == null) throw new ArgumentNullException("sSaveTo"); - m_format = format; + m_format = fmt; m_slLogger = slLogger; - HashingStreamEx hashedStream = new HashingStreamEx(sSaveTo, true, null); - UTF8Encoding encNoBom = StrUtil.Utf8; CryptoRandom cr = CryptoRandom.Instance; + byte[] pbCipherKey = null; + byte[] pbHmacKey64 = null; + + List lStreams = new List(); + lStreams.Add(sSaveTo); + + HashingStreamEx sHashing = new HashingStreamEx(sSaveTo, true, null); + lStreams.Add(sHashing); try { - m_pbMasterSeed = cr.GetRandomBytes(32); - m_pbTransformSeed = cr.GetRandomBytes(32); - m_pbEncryptionIV = cr.GetRandomBytes(16); + m_uFileVersion = GetMinKdbxVersion(); - m_pbProtectedStreamKey = cr.GetRandomBytes(32); - m_craInnerRandomStream = CrsAlgorithm.Salsa20; + int cbEncKey, cbEncIV; + ICipherEngine iCipher = GetCipher(out cbEncKey, out cbEncIV); + + m_pbMasterSeed = cr.GetRandomBytes(32); + m_pbEncryptionIV = cr.GetRandomBytes((uint)cbEncIV); + + // m_pbTransformSeed = cr.GetRandomBytes(32); + PwUuid puKdf = m_pwDatabase.KdfParameters.KdfUuid; + KdfEngine kdf = KdfPool.Get(puKdf); + if(kdf == null) + throw new Exception(KLRes.UnknownKdf + MessageService.NewParagraph + + // KLRes.FileNewVerOrPlgReq + MessageService.NewParagraph + + "UUID: " + puKdf.ToHexString() + "."); + kdf.Randomize(m_pwDatabase.KdfParameters); + + if(m_uFileVersion <= FileVersion32_3) + { + m_craInnerRandomStream = CrsAlgorithm.Salsa20; + m_pbProtectedStreamKey = cr.GetRandomBytes(32); + } + else // KDBX >= 4 + { + m_craInnerRandomStream = CrsAlgorithm.ChaCha20; + m_pbProtectedStreamKey = cr.GetRandomBytes(64); + } m_randomStream = new CryptoRandomStream(m_craInnerRandomStream, m_pbProtectedStreamKey); - m_pbStreamStartBytes = cr.GetRandomBytes(32); + if(m_uFileVersion <= FileVersion32_3) + m_pbStreamStartBytes = cr.GetRandomBytes(32); - Stream writerStream; + Stream sXml; if(m_format == KdbxFormat.Default) { - WriteHeader(hashedStream); // Also flushes the stream + byte[] pbHeader = GenerateHeader(); + m_pbHashOfHeader = CryptoUtil.HashSha256(pbHeader); - Stream sEncrypted = AttachStreamEncryptor(hashedStream); - if((sEncrypted == null) || (sEncrypted == hashedStream)) - throw new SecurityException(KLRes.CryptoStreamFailed); + MemUtil.Write(sHashing, pbHeader); + sHashing.Flush(); - sEncrypted.Write(m_pbStreamStartBytes, 0, m_pbStreamStartBytes.Length); + ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64); - Stream sHashed = new HashedBlockStream(sEncrypted, true); + Stream sPlain; + if(m_uFileVersion <= FileVersion32_3) + { + Stream sEncrypted = EncryptStream(sHashing, iCipher, + pbCipherKey, cbEncIV, true); + if((sEncrypted == null) || (sEncrypted == sHashing)) + throw new SecurityException(KLRes.CryptoStreamFailed); + lStreams.Add(sEncrypted); + + MemUtil.Write(sEncrypted, m_pbStreamStartBytes); + + sPlain = new HashedBlockStream(sEncrypted, true); + } + else // KDBX >= 4 + { + byte[] pbHeaderHmac = ComputeHeaderHmac(pbHeader, pbHmacKey64); + MemUtil.Write(sHashing, pbHeaderHmac); + + Stream sBlocks = new HmacBlockStream(sHashing, true, + true, pbHmacKey64); + lStreams.Add(sBlocks); + + sPlain = EncryptStream(sBlocks, iCipher, pbCipherKey, + cbEncIV, true); + if((sPlain == null) || (sPlain == sBlocks)) + throw new SecurityException(KLRes.CryptoStreamFailed); + } + lStreams.Add(sPlain); if(m_pwDatabase.Compression == PwCompressionAlgorithm.GZip) - writerStream = new GZipStream(sHashed, CompressionMode.Compress); - else - writerStream = sHashed; + { + sXml = new GZipStream(sPlain, CompressionMode.Compress); + lStreams.Add(sXml); + } + else sXml = sPlain; } else if(m_format == KdbxFormat.PlainXml) - writerStream = hashedStream; - else { Debug.Assert(false); throw new FormatException("KdbFormat"); } + sXml = sHashing; + else + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("fmt"); + } #if KeePassUAP XmlWriterSettings xws = new XmlWriterSettings(); @@ -130,122 +192,125 @@ namespace KeePassLib.Serialization xws.IndentChars = "\t"; xws.NewLineOnAttributes = false; - XmlWriter xw = XmlWriter.Create(writerStream, xws); + XmlWriter xw = XmlWriter.Create(sXml, xws); #else - XmlTextWriter xw = new XmlTextWriter(writerStream, encNoBom); + XmlTextWriter xw = new XmlTextWriter(sXml, encNoBom); xw.Formatting = Formatting.Indented; xw.IndentChar = '\t'; xw.Indentation = 1; #endif - m_xmlWriter = xw; WriteDocument(pgDataSource); m_xmlWriter.Flush(); m_xmlWriter.Close(); - writerStream.Close(); } - finally { CommonCleanUpWrite(sSaveTo, hashedStream); } + finally + { + if(pbCipherKey != null) MemUtil.ZeroByteArray(pbCipherKey); + if(pbHmacKey64 != null) MemUtil.ZeroByteArray(pbHmacKey64); + + CommonCleanUpWrite(lStreams, sHashing); + } } - private void CommonCleanUpWrite(Stream sSaveTo, HashingStreamEx hashedStream) + private void CommonCleanUpWrite(List lStreams, HashingStreamEx sHashing) { - hashedStream.Close(); - m_pbHashOfFileOnDisk = hashedStream.Hash; + CloseStreams(lStreams); - sSaveTo.Close(); + Debug.Assert(lStreams.Contains(sHashing)); // sHashing must be closed + m_pbHashOfFileOnDisk = sHashing.Hash; + Debug.Assert(m_pbHashOfFileOnDisk != null); m_xmlWriter = null; m_pbHashOfHeader = null; } - private void WriteHeader(Stream s) + private byte[] GenerateHeader() { - MemoryStream ms = new MemoryStream(); + byte[] pbHeader; + using(MemoryStream ms = new MemoryStream()) + { + MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature1)); + MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature2)); + MemUtil.Write(ms, MemUtil.UInt32ToBytes(m_uFileVersion)); - MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature1)); - MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature2)); - MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileVersion32)); + WriteHeaderField(ms, KdbxHeaderFieldID.CipherID, + m_pwDatabase.DataCipherUuid.UuidBytes); - WriteHeaderField(ms, KdbxHeaderFieldID.CipherID, - m_pwDatabase.DataCipherUuid.UuidBytes); + int nCprID = (int)m_pwDatabase.Compression; + WriteHeaderField(ms, KdbxHeaderFieldID.CompressionFlags, + MemUtil.UInt32ToBytes((uint)nCprID)); - int nCprID = (int)m_pwDatabase.Compression; - WriteHeaderField(ms, KdbxHeaderFieldID.CompressionFlags, - MemUtil.UInt32ToBytes((uint)nCprID)); + WriteHeaderField(ms, KdbxHeaderFieldID.MasterSeed, m_pbMasterSeed); - WriteHeaderField(ms, KdbxHeaderFieldID.MasterSeed, m_pbMasterSeed); - WriteHeaderField(ms, KdbxHeaderFieldID.TransformSeed, m_pbTransformSeed); - WriteHeaderField(ms, KdbxHeaderFieldID.TransformRounds, - MemUtil.UInt64ToBytes(m_pwDatabase.KeyEncryptionRounds)); - WriteHeaderField(ms, KdbxHeaderFieldID.EncryptionIV, m_pbEncryptionIV); - WriteHeaderField(ms, KdbxHeaderFieldID.ProtectedStreamKey, m_pbProtectedStreamKey); - WriteHeaderField(ms, KdbxHeaderFieldID.StreamStartBytes, m_pbStreamStartBytes); + if(m_uFileVersion <= FileVersion32_3) + { + Debug.Assert(m_pwDatabase.KdfParameters.KdfUuid.Equals( + (new AesKdf()).Uuid)); + WriteHeaderField(ms, KdbxHeaderFieldID.TransformSeed, + m_pwDatabase.KdfParameters.GetByteArray(AesKdf.ParamSeed)); + WriteHeaderField(ms, KdbxHeaderFieldID.TransformRounds, + MemUtil.UInt64ToBytes(m_pwDatabase.KdfParameters.GetUInt64( + AesKdf.ParamRounds, PwDefs.DefaultKeyEncryptionRounds))); + } + else + WriteHeaderField(ms, KdbxHeaderFieldID.KdfParameters, + KdfParameters.SerializeExt(m_pwDatabase.KdfParameters)); - int nIrsID = (int)m_craInnerRandomStream; - WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamID, - MemUtil.UInt32ToBytes((uint)nIrsID)); + if(m_pbEncryptionIV.Length > 0) + WriteHeaderField(ms, KdbxHeaderFieldID.EncryptionIV, m_pbEncryptionIV); - WriteHeaderField(ms, KdbxHeaderFieldID.EndOfHeader, new byte[]{ - (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' }); + WriteHeaderField(ms, KdbxHeaderFieldID.ProtectedStreamKey, m_pbProtectedStreamKey); - byte[] pbHeader = ms.ToArray(); - ms.Close(); + if(m_uFileVersion <= FileVersion32_3) + WriteHeaderField(ms, KdbxHeaderFieldID.StreamStartBytes, + m_pbStreamStartBytes); - SHA256Managed sha256 = new SHA256Managed(); - m_pbHashOfHeader = sha256.ComputeHash(pbHeader); + int nIrsID = (int)m_craInnerRandomStream; + WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamID, + MemUtil.Int32ToBytes(nIrsID)); - s.Write(pbHeader, 0, pbHeader.Length); - s.Flush(); + // Write public custom data only when there is at least one item, + // because KDBX 3.1 didn't support this field yet + if(m_pwDatabase.PublicCustomData.Count > 0) + WriteHeaderField(ms, KdbxHeaderFieldID.PublicCustomData, + VariantDictionary.Serialize(m_pwDatabase.PublicCustomData)); + + WriteHeaderField(ms, KdbxHeaderFieldID.EndOfHeader, new byte[] { + (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' }); + + pbHeader = ms.ToArray(); + } + + return pbHeader; } - private static void WriteHeaderField(Stream s, KdbxHeaderFieldID kdbID, + private void WriteHeaderField(Stream s, KdbxHeaderFieldID kdbID, byte[] pbData) { s.WriteByte((byte)kdbID); - if(pbData != null) + byte[] pb = (pbData ?? MemUtil.EmptyByteArray); + int cb = pb.Length; + if(cb < 0) { Debug.Assert(false); throw new OutOfMemoryException(); } + + Debug.Assert(m_uFileVersion > 0); + if(m_uFileVersion <= FileVersion32_3) { - ushort uLength = (ushort)pbData.Length; - MemUtil.Write(s, MemUtil.UInt16ToBytes(uLength)); + if(cb > (int)ushort.MaxValue) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("pbData"); + } - if(uLength > 0) s.Write(pbData, 0, pbData.Length); + MemUtil.Write(s, MemUtil.UInt16ToBytes((ushort)cb)); } - else MemUtil.Write(s, MemUtil.UInt16ToBytes((ushort)0)); - } + else MemUtil.Write(s, MemUtil.Int32ToBytes(cb)); - private Stream AttachStreamEncryptor(Stream s) - { - MemoryStream ms = new MemoryStream(); - - Debug.Assert(m_pbMasterSeed != null); - Debug.Assert(m_pbMasterSeed.Length == 32); - ms.Write(m_pbMasterSeed, 0, 32); - - Debug.Assert(m_pwDatabase != null); - Debug.Assert(m_pwDatabase.MasterKey != null); - ProtectedBinary pbinKey = m_pwDatabase.MasterKey.GenerateKey32( - m_pbTransformSeed, m_pwDatabase.KeyEncryptionRounds); - Debug.Assert(pbinKey != null); - if(pbinKey == null) - throw new SecurityException(KLRes.InvalidCompositeKey); - byte[] pKey32 = pbinKey.ReadData(); - if((pKey32 == null) || (pKey32.Length != 32)) - throw new SecurityException(KLRes.InvalidCompositeKey); - ms.Write(pKey32, 0, 32); - - SHA256Managed sha256 = new SHA256Managed(); - byte[] aesKey = sha256.ComputeHash(ms.ToArray()); - - ms.Close(); - Array.Clear(pKey32, 0, 32); - - Debug.Assert(CipherPool.GlobalPool != null); - ICipherEngine iEngine = CipherPool.GlobalPool.GetCipher(m_pwDatabase.DataCipherUuid); - if(iEngine == null) throw new SecurityException(KLRes.FileUnknownCipher); - return iEngine.EncryptStream(s, aesKey, m_pbEncryptionIV); + if(cb > 0) s.Write(pb, 0, cb); } private void WriteDocument(PwGroup pgDataSource) @@ -331,12 +396,15 @@ namespace KeePassLib.Serialization { m_xmlWriter.WriteStartElement(ElemMeta); - WriteObject(ElemGenerator, PwDatabase.LocalizedAppName, false); // Generator name + WriteObject(ElemGenerator, PwDatabase.LocalizedAppName, false); - if(m_pbHashOfHeader != null) + if((m_pbHashOfHeader != null) && (m_uFileVersion <= FileVersion32_3)) WriteObject(ElemHeaderHash, Convert.ToBase64String( m_pbHashOfHeader), false); + if(m_uFileVersion > FileVersion32_3) + WriteObject(ElemSettingsChanged, m_pwDatabase.SettingsChanged); + WriteObject(ElemDbName, m_pwDatabase.Name, true); WriteObject(ElemDbNameChanged, m_pwDatabase.NameChanged); WriteObject(ElemDbDesc, m_pwDatabase.Description, true); @@ -387,6 +455,9 @@ namespace KeePassLib.Serialization WriteObject(ElemEnableAutoType, StrUtil.BoolToStringEx(pg.EnableAutoType), false); WriteObject(ElemEnableSearching, StrUtil.BoolToStringEx(pg.EnableSearching), false); WriteObject(ElemLastTopVisibleEntry, pg.LastTopVisibleEntry); + + if(pg.CustomData.Count > 0) + WriteList(ElemCustomData, pg.CustomData); } private void EndGroup() @@ -402,7 +473,7 @@ namespace KeePassLib.Serialization WriteObject(ElemUuid, pe.Uuid); WriteObject(ElemIcon, (int)pe.IconId); - + if(!pe.CustomIconUuid.Equals(PwUuid.Zero)) WriteObject(ElemCustomIconID, pe.CustomIconUuid); @@ -417,6 +488,9 @@ namespace KeePassLib.Serialization WriteList(pe.Binaries); WriteList(ElemAutoType, pe.AutoType); + if(pe.CustomData.Count > 0) + WriteList(ElemCustomData, pe.CustomData); + if(!bIsHistory) WriteList(ElemHistory, pe.History, true); else { Debug.Assert(pe.History.UCount == 0); } diff --git a/src/KeePassLib2Android/Serialization/KdbxFile.cs b/src/KeePassLib2Android/Serialization/KdbxFile.cs index 2ca29c95..d3d2aaa9 100644 --- a/src/KeePassLib2Android/Serialization/KdbxFile.cs +++ b/src/KeePassLib2Android/Serialization/KdbxFile.cs @@ -22,13 +22,21 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; +using System.Security; using System.Text; using System.Xml; +#if !KeePassUAP +using System.Security.Cryptography; +#endif + using KeePassLib.Collections; using KeePassLib.Cryptography; +using KeePassLib.Cryptography.Cipher; +using KeePassLib.Cryptography.KeyDerivation; using KeePassLib.Delegates; using KeePassLib.Interfaces; +using KeePassLib.Resources; using KeePassLib.Security; using KeePassLib.Utility; @@ -73,7 +81,8 @@ namespace KeePassLib.Serialization /// The first 2 bytes are critical (i.e. loading will fail, if the /// file version is too high), the last 2 bytes are informational. /// - private const uint FileVersion32 = 0x00030001; + private const uint FileVersion32 = 0x00040000; + private const uint FileVersion32_3 = 0x00030001; // Old format private const uint FileVersionCriticalMask = 0xFFFF0000; @@ -92,6 +101,7 @@ namespace KeePassLib.Serialization private const string ElemGenerator = "Generator"; private const string ElemHeaderHash = "HeaderHash"; + private const string ElemSettingsChanged = "SettingsChanged"; private const string ElemDbName = "DatabaseName"; private const string ElemDbNameChanged = "DatabaseNameChanged"; private const string ElemDbDesc = "DatabaseDescription"; @@ -192,14 +202,15 @@ namespace KeePassLib.Serialization private KdbxFormat m_format = KdbxFormat.Default; private IStatusLogger m_slLogger = null; + private uint m_uFileVersion = 0; private byte[] m_pbMasterSeed = null; - private byte[] m_pbTransformSeed = null; + // private byte[] m_pbTransformSeed = null; private byte[] m_pbEncryptionIV = null; private byte[] m_pbProtectedStreamKey = null; private byte[] m_pbStreamStartBytes = null; - // ArcFourVariant only for compatibility; KeePass will default to a - // different (more secure) algorithm when *writing* databases + // ArcFourVariant only for backward compatibility; KeePass defaults + // to a more secure algorithm when *writing* databases private CrsAlgorithm m_craInnerRandomStream = CrsAlgorithm.ArcFourVariant; private Dictionary m_dictBinPool = @@ -222,12 +233,14 @@ namespace KeePassLib.Serialization CipherID = 2, CompressionFlags = 3, MasterSeed = 4, - TransformSeed = 5, - TransformRounds = 6, + TransformSeed = 5, // KDBX 3.1, for backward compatibility only + TransformRounds = 6, // KDBX 3.1, for backward compatibility only EncryptionIV = 7, ProtectedStreamKey = 8, - StreamStartBytes = 9, - InnerRandomStreamID = 10 + StreamStartBytes = 9, // KDBX 3.1, for backward compatibility only + InnerRandomStreamID = 10, + KdfParameters = 11, // KDBX 4 + PublicCustomData = 12 // KDBX 4 } public byte[] HashOfFileOnDisk @@ -286,6 +299,152 @@ namespace KeePassLib.Serialization } } + private uint GetMinKdbxVersion() + { + AesKdf kdfAes = new AesKdf(); + if(!kdfAes.Uuid.Equals(m_pwDatabase.KdfParameters.KdfUuid)) + return FileVersion32; + + if(m_pwDatabase.PublicCustomData.Count > 0) + return FileVersion32; + + bool bCustomData = false; + GroupHandler gh = delegate(PwGroup pg) + { + if(pg == null) { Debug.Assert(false); return true; } + if(pg.CustomData.Count > 0) { bCustomData = true; return false; } + return true; + }; + EntryHandler eh = delegate(PwEntry pe) + { + if(pe == null) { Debug.Assert(false); return true; } + if(pe.CustomData.Count > 0) { bCustomData = true; return false; } + return true; + }; + gh(m_pwDatabase.RootGroup); + m_pwDatabase.RootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh); + if(bCustomData) return FileVersion32; + + return FileVersion32_3; // KDBX 3.1 is sufficient + } + + private void ComputeKeys(out byte[] pbCipherKey, int cbCipherKey, + out byte[] pbHmacKey64) + { + byte[] pbCmp = new byte[32 + 32 + 1]; + try + { + Debug.Assert(m_pbMasterSeed != null); + if(m_pbMasterSeed == null) + throw new ArgumentNullException("m_pbMasterSeed"); + Debug.Assert(m_pbMasterSeed.Length == 32); + if(m_pbMasterSeed.Length != 32) + throw new FormatException(KLRes.MasterSeedLengthInvalid); + Array.Copy(m_pbMasterSeed, 0, pbCmp, 0, 32); + + Debug.Assert(m_pwDatabase != null); + Debug.Assert(m_pwDatabase.MasterKey != null); + ProtectedBinary pbinUser = m_pwDatabase.MasterKey.GenerateKey32( + m_pwDatabase.KdfParameters); + Debug.Assert(pbinUser != null); + if(pbinUser == null) + throw new SecurityException(KLRes.InvalidCompositeKey); + byte[] pUserKey32 = pbinUser.ReadData(); + if((pUserKey32 == null) || (pUserKey32.Length != 32)) + throw new SecurityException(KLRes.InvalidCompositeKey); + Array.Copy(pUserKey32, 0, pbCmp, 32, 32); + MemUtil.ZeroByteArray(pUserKey32); + + pbCipherKey = CryptoUtil.ResizeKey(pbCmp, 0, 64, cbCipherKey); + + pbCmp[64] = 1; + using(SHA512Managed h = new SHA512Managed()) + { + pbHmacKey64 = h.ComputeHash(pbCmp); + } + } + finally { MemUtil.ZeroByteArray(pbCmp); } + } + + private ICipherEngine GetCipher(out int cbEncKey, out int cbEncIV) + { + PwUuid pu = m_pwDatabase.DataCipherUuid; + ICipherEngine iCipher = CipherPool.GlobalPool.GetCipher(pu); + if(iCipher == null) // CryptographicExceptions are translated to "file corrupted" + throw new Exception(KLRes.FileUnknownCipher + + MessageService.NewParagraph + KLRes.FileNewVerOrPlgReq + + MessageService.NewParagraph + "UUID: " + pu.ToHexString() + "."); + + ICipherEngine2 iCipher2 = (iCipher as ICipherEngine2); + if(iCipher2 != null) + { + cbEncKey = iCipher2.KeyLength; + if(cbEncKey < 0) throw new InvalidOperationException("EncKey.Length"); + + cbEncIV = iCipher2.IVLength; + if(cbEncIV < 0) throw new InvalidOperationException("EncIV.Length"); + } + else + { + cbEncKey = 32; + cbEncIV = 16; + } + + return iCipher; + } + + private Stream EncryptStream(Stream s, ICipherEngine iCipher, + byte[] pbKey, int cbIV, bool bEncrypt) + { + byte[] pbIV = (m_pbEncryptionIV ?? MemUtil.EmptyByteArray); + if(pbIV.Length != cbIV) + { + Debug.Assert(false); + throw new Exception(KLRes.FileCorrupted); + } + + if(bEncrypt) + return iCipher.EncryptStream(s, pbKey, pbIV); + return iCipher.DecryptStream(s, pbKey, pbIV); + } + + private byte[] ComputeHeaderHmac(byte[] pbHeader, byte[] pbKey) + { + byte[] pbHeaderHmac; + byte[] pbBlockKey = HmacBlockStream.GetHmacKey64( + pbKey, ulong.MaxValue); + using(HMACSHA256 h = new HMACSHA256(pbBlockKey)) + { + pbHeaderHmac = h.ComputeHash(pbHeader); + } + MemUtil.ZeroByteArray(pbBlockKey); + + return pbHeaderHmac; + } + + private void CloseStreams(List lStreams) + { + if(lStreams == null) { Debug.Assert(false); return; } + + // Typically, closing a stream also closes its base + // stream; however, there may be streams that do not + // do this (e.g. some cipher plugin), thus for safety + // we close all streams manually, from the innermost + // to the outermost + + for(int i = lStreams.Count - 1; i >= 0; --i) + { + // Check for duplicates + Debug.Assert((lStreams.IndexOf(lStreams[i]) == i) && + (lStreams.LastIndexOf(lStreams[i]) == i)); + + try { lStreams[i].Close(); } + catch(Exception) { Debug.Assert(false); } + } + + // Do not clear the list + } + private void BinPoolBuild(PwGroup pgDataSource) { m_dictBinPool = new Dictionary(); diff --git a/src/KeePassLib2Android/Translation/KPControlCustomization.cs b/src/KeePassLib2Android/Translation/KPControlCustomization.cs index f71d3213..45e4e244 100644 --- a/src/KeePassLib2Android/Translation/KPControlCustomization.cs +++ b/src/KeePassLib2Android/Translation/KPControlCustomization.cs @@ -27,10 +27,10 @@ using System.Xml.Serialization; #if !KeePassUAP using System.Drawing; -using System.Security.Cryptography; using System.Windows.Forms; #endif +using KeePassLib.Cryptography; using KeePassLib.Utility; namespace KeePassLib.Translation @@ -333,9 +333,7 @@ namespace KeePassLib.Translation WriteControlDependentParams(sb, c); byte[] pb = StrUtil.Utf8.GetBytes(sb.ToString()); - - SHA256Managed sha256 = new SHA256Managed(); - byte[] pbSha = sha256.ComputeHash(pb); + byte[] pbSha = CryptoUtil.HashSha256(pb); // Also see MatchHash return "v1:" + Convert.ToBase64String(pbSha, 0, 3, diff --git a/src/KeePassLib2Android/Utility/GfxUtil.cs b/src/KeePassLib2Android/Utility/GfxUtil.cs index 2a61e370..f61fe9bd 100644 --- a/src/KeePassLib2Android/Utility/GfxUtil.cs +++ b/src/KeePassLib2Android/Utility/GfxUtil.cs @@ -214,14 +214,11 @@ namespace KeePassLib.Utility const int SizeICONDIR = 6; const int SizeICONDIRENTRY = 16; - Debug.Assert(BitConverter.ToInt32(new byte[] { 1, 2, 3, 4 }, - 0) == 0x04030201); // Little-endian - if(pb.Length < SizeICONDIR) return null; - if(BitConverter.ToUInt16(pb, 0) != 0) return null; // Reserved, 0 - if(BitConverter.ToUInt16(pb, 2) != 1) return null; // ICO type, 1 + if(MemUtil.BytesToUInt16(pb, 0) != 0) return null; // Reserved, 0 + if(MemUtil.BytesToUInt16(pb, 2) != 1) return null; // ICO type, 1 - int n = BitConverter.ToUInt16(pb, 4); + int n = MemUtil.BytesToUInt16(pb, 4); if(n < 0) { Debug.Assert(false); return null; } int cbDir = SizeICONDIR + (n * SizeICONDIRENTRY); @@ -235,10 +232,10 @@ namespace KeePassLib.Utility int h = pb[iOffset + 1]; if((w < 0) || (h < 0)) { Debug.Assert(false); return null; } - int cb = BitConverter.ToInt32(pb, iOffset + 8); + int cb = MemUtil.BytesToInt32(pb, iOffset + 8); if(cb <= 0) return null; // Data must have header (even BMP) - int p = BitConverter.ToInt32(pb, iOffset + 12); + int p = MemUtil.BytesToInt32(pb, iOffset + 12); if(p < cbDir) return null; if((p + cb) > pb.Length) return null; diff --git a/src/KeePassLib2Android/Utility/MemUtil.cs b/src/KeePassLib2Android/Utility/MemUtil.cs index 6b22b720..964e25a4 100644 --- a/src/KeePassLib2Android/Utility/MemUtil.cs +++ b/src/KeePassLib2Android/Utility/MemUtil.cs @@ -37,6 +37,8 @@ namespace KeePassLib.Utility /// public static class MemUtil { + internal static readonly byte[] EmptyByteArray = new byte[0]; + private static readonly uint[] m_vSBox = new uint[256] { 0xCD2FACB3, 0xE78A7F5C, 0x6F0803FC, 0xBCF6E230, 0x3A321712, 0x06403DB1, 0xD2F84B95, 0xDF22A6E4, @@ -262,6 +264,22 @@ namespace KeePassLib.Utility Array.Clear(pbArray, 0, pbArray.Length); } + /// + /// Set all elements of an array to the default value. + /// + /// Input array. +#if KeePassLibSD + [MethodImpl(MethodImplOptions.NoInlining)] +#else + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] +#endif + public static void ZeroArray(T[] v) + { + if(v == null) { Debug.Assert(false); throw new ArgumentNullException("v"); } + + Array.Clear(v, 0, v.Length); + } + /// /// Convert 2 bytes to a 16-bit unsigned integer (little-endian). /// @@ -269,11 +287,26 @@ namespace KeePassLib.Utility { Debug.Assert((pb != null) && (pb.Length == 2)); if(pb == null) throw new ArgumentNullException("pb"); - if(pb.Length != 2) throw new ArgumentException(); + if(pb.Length != 2) throw new ArgumentOutOfRangeException("pb"); return (ushort)((ushort)pb[0] | ((ushort)pb[1] << 8)); } + /// + /// Convert 2 bytes to a 16-bit unsigned integer (little-endian). + /// + public static ushort BytesToUInt16(byte[] pb, int iOffset) + { + if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } + if((iOffset < 0) || ((iOffset + 1) >= pb.Length)) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("iOffset"); + } + + return (ushort)((ushort)pb[iOffset] | ((ushort)pb[iOffset + 1] << 8)); + } + /// /// Convert 4 bytes to a 32-bit unsigned integer (little-endian). /// @@ -281,12 +314,28 @@ namespace KeePassLib.Utility { Debug.Assert((pb != null) && (pb.Length == 4)); if(pb == null) throw new ArgumentNullException("pb"); - if(pb.Length != 4) throw new ArgumentException(); + if(pb.Length != 4) throw new ArgumentOutOfRangeException("pb"); return ((uint)pb[0] | ((uint)pb[1] << 8) | ((uint)pb[2] << 16) | ((uint)pb[3] << 24)); } + /// + /// Convert 4 bytes to a 32-bit unsigned integer (little-endian). + /// + public static uint BytesToUInt32(byte[] pb, int iOffset) + { + if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } + if((iOffset < 0) || ((iOffset + 3) >= pb.Length)) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("iOffset"); + } + + return ((uint)pb[iOffset] | ((uint)pb[iOffset + 1] << 8) | + ((uint)pb[iOffset + 2] << 16) | ((uint)pb[iOffset + 3] << 24)); + } + /// /// Convert 8 bytes to a 64-bit unsigned integer (little-endian). /// @@ -294,13 +343,54 @@ namespace KeePassLib.Utility { Debug.Assert((pb != null) && (pb.Length == 8)); if(pb == null) throw new ArgumentNullException("pb"); - if(pb.Length != 8) throw new ArgumentException(); + if(pb.Length != 8) throw new ArgumentOutOfRangeException("pb"); return ((ulong)pb[0] | ((ulong)pb[1] << 8) | ((ulong)pb[2] << 16) | ((ulong)pb[3] << 24) | ((ulong)pb[4] << 32) | ((ulong)pb[5] << 40) | ((ulong)pb[6] << 48) | ((ulong)pb[7] << 56)); } + /// + /// Convert 8 bytes to a 64-bit unsigned integer (little-endian). + /// + public static ulong BytesToUInt64(byte[] pb, int iOffset) + { + if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } + if((iOffset < 0) || ((iOffset + 7) >= pb.Length)) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("iOffset"); + } + + // if(BitConverter.IsLittleEndian) + // return BitConverter.ToUInt64(pb, iOffset); + + return ((ulong)pb[iOffset] | ((ulong)pb[iOffset + 1] << 8) | + ((ulong)pb[iOffset + 2] << 16) | ((ulong)pb[iOffset + 3] << 24) | + ((ulong)pb[iOffset + 4] << 32) | ((ulong)pb[iOffset + 5] << 40) | + ((ulong)pb[iOffset + 6] << 48) | ((ulong)pb[iOffset + 7] << 56)); + } + + public static int BytesToInt32(byte[] pb) + { + return (int)BytesToUInt32(pb); + } + + public static int BytesToInt32(byte[] pb, int iOffset) + { + return (int)BytesToUInt32(pb, iOffset); + } + + public static long BytesToInt64(byte[] pb) + { + return (long)BytesToUInt64(pb); + } + + public static long BytesToInt64(byte[] pb, int iOffset) + { + return (long)BytesToUInt64(pb, iOffset); + } + /// /// Convert a 16-bit unsigned integer to 2 bytes (little-endian). /// @@ -335,6 +425,27 @@ namespace KeePassLib.Utility return pb; } + /// + /// Convert a 32-bit unsigned integer to 4 bytes (little-endian). + /// + public static void UInt32ToBytesEx(uint uValue, byte[] pb, int iOffset) + { + if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } + if((iOffset < 0) || ((iOffset + 3) >= pb.Length)) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("iOffset"); + } + + unchecked + { + pb[iOffset] = (byte)uValue; + pb[iOffset + 1] = (byte)(uValue >> 8); + pb[iOffset + 2] = (byte)(uValue >> 16); + pb[iOffset + 3] = (byte)(uValue >> 24); + } + } + /// /// Convert a 64-bit unsigned integer to 8 bytes (little-endian). /// @@ -357,6 +468,61 @@ namespace KeePassLib.Utility return pb; } + /// + /// Convert a 64-bit unsigned integer to 8 bytes (little-endian). + /// + public static void UInt64ToBytesEx(ulong uValue, byte[] pb, int iOffset) + { + if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } + if((iOffset < 0) || ((iOffset + 7) >= pb.Length)) + { + Debug.Assert(false); + throw new ArgumentOutOfRangeException("iOffset"); + } + + unchecked + { + pb[iOffset] = (byte)uValue; + pb[iOffset + 1] = (byte)(uValue >> 8); + pb[iOffset + 2] = (byte)(uValue >> 16); + pb[iOffset + 3] = (byte)(uValue >> 24); + pb[iOffset + 4] = (byte)(uValue >> 32); + pb[iOffset + 5] = (byte)(uValue >> 40); + pb[iOffset + 6] = (byte)(uValue >> 48); + pb[iOffset + 7] = (byte)(uValue >> 56); + } + } + + public static byte[] Int32ToBytes(int iValue) + { + return UInt32ToBytes((uint)iValue); + } + + public static byte[] Int64ToBytes(long lValue) + { + return UInt64ToBytes((ulong)lValue); + } + + public static uint RotateLeft32(uint u, int nBits) + { + return ((u << nBits) | (u >> (32 - nBits))); + } + + public static uint RotateRight32(uint u, int nBits) + { + return ((u >> nBits) | (u << (32 - nBits))); + } + + public static ulong RotateLeft64(ulong u, int nBits) + { + return ((u << nBits) | (u >> (64 - nBits))); + } + + public static ulong RotateRight64(ulong u, int nBits) + { + return ((u >> nBits) | (u << (64 - nBits))); + } + public static bool ArraysEqual(byte[] x, byte[] y) { // Return false if one of them is null (not comparable)! @@ -372,19 +538,21 @@ namespace KeePassLib.Utility return true; } - public static void XorArray(byte[] pbSource, int nSourceOffset, - byte[] pbBuffer, int nBufferOffset, int nLength) + public static void XorArray(byte[] pbSource, int iSourceOffset, + byte[] pbBuffer, int iBufferOffset, int cb) { if(pbSource == null) throw new ArgumentNullException("pbSource"); - if(nSourceOffset < 0) throw new ArgumentException(); + if(iSourceOffset < 0) throw new ArgumentOutOfRangeException("iSourceOffset"); if(pbBuffer == null) throw new ArgumentNullException("pbBuffer"); - if(nBufferOffset < 0) throw new ArgumentException(); - if(nLength < 0) throw new ArgumentException(); - if((nSourceOffset + nLength) > pbSource.Length) throw new ArgumentException(); - if((nBufferOffset + nLength) > pbBuffer.Length) throw new ArgumentException(); + if(iBufferOffset < 0) throw new ArgumentOutOfRangeException("iBufferOffset"); + if(cb < 0) throw new ArgumentOutOfRangeException("cb"); + if(iSourceOffset > (pbSource.Length - cb)) + throw new ArgumentOutOfRangeException("cb"); + if(iBufferOffset > (pbBuffer.Length - cb)) + throw new ArgumentOutOfRangeException("cb"); - for(int i = 0; i < nLength; ++i) - pbBuffer[nBufferOffset + i] ^= pbSource[nSourceOffset + i]; + for(int i = 0; i < cb; ++i) + pbBuffer[iBufferOffset + i] ^= pbSource[iSourceOffset + i]; } /// @@ -463,7 +631,8 @@ namespace KeePassLib.Utility if(s == null) { Debug.Assert(false); return; } if(pbData == null) { Debug.Assert(false); return; } - s.Write(pbData, 0, pbData.Length); + Debug.Assert(pbData.Length >= 0); + if(pbData.Length > 0) s.Write(pbData, 0, pbData.Length); } public static byte[] Compress(byte[] pbData) diff --git a/src/KeePassLib2Android/Utility/MonoWorkarounds.cs b/src/KeePassLib2Android/Utility/MonoWorkarounds.cs index 064791c9..aa1782b7 100644 --- a/src/KeePassLib2Android/Utility/MonoWorkarounds.cs +++ b/src/KeePassLib2Android/Utility/MonoWorkarounds.cs @@ -70,6 +70,12 @@ namespace KeePassLib.Utility // 1418: // Minimizing a form while loading it doesn't work. // https://sourceforge.net/p/keepass/bugs/1418/ + // 2139: + // Shortcut keys are ignored. + // https://sourceforge.net/p/keepass/feature-requests/2139/ + // 2140: + // Explicit control focusing is ignored. + // https://sourceforge.net/p/keepass/feature-requests/2140/ // 5795: // Text in input field is incomplete. // https://bugzilla.xamarin.com/show_bug.cgi?id=5795 diff --git a/src/KeePassLib2Android/Utility/StrUtil.cs b/src/KeePassLib2Android/Utility/StrUtil.cs index 39ef5282..bbd0195c 100644 --- a/src/KeePassLib2Android/Utility/StrUtil.cs +++ b/src/KeePassLib2Android/Utility/StrUtil.cs @@ -978,13 +978,12 @@ namespace KeePassLib.Utility public static bool IsHexString(string str, bool bStrict) { if(str == null) throw new ArgumentNullException("str"); - if(str.Length == 0) return true; foreach(char ch in str) { if((ch >= '0') && (ch <= '9')) continue; - if((ch >= 'a') && (ch <= 'z')) continue; - if((ch >= 'A') && (ch <= 'Z')) continue; + if((ch >= 'a') && (ch <= 'f')) continue; + if((ch >= 'A') && (ch <= 'F')) continue; if(bStrict) return false; @@ -997,8 +996,31 @@ namespace KeePassLib.Utility return true; } + public static bool IsHexString(byte[] pbUtf8, bool bStrict) + { + if(pbUtf8 == null) throw new ArgumentNullException("pbUtf8"); + + for(int i = 0; i < pbUtf8.Length; ++i) + { + byte bt = pbUtf8[i]; + if((bt >= (byte)'0') && (bt <= (byte)'9')) continue; + if((bt >= (byte)'a') && (bt <= (byte)'f')) continue; + if((bt >= (byte)'A') && (bt <= (byte)'F')) continue; + + if(bStrict) return false; + + if((bt == (byte)' ') || (bt == (byte)'\t') || + (bt == (byte)'\r') || (bt == (byte)'\n')) + continue; + + return false; + } + + return true; + } + #if !KeePassLibSD - private static readonly char[] m_vPatternPartsSep = new char[]{ '*' }; + private static readonly char[] m_vPatternPartsSep = new char[] { '*' }; public static bool SimplePatternMatch(string strPattern, string strText, StringComparison sc) {