commit 36c1df19d418e2a46b7637e388c04d048610b13c Author: PhilippC Date: Sat Feb 23 14:24:43 2013 +0100 Unmodified KeepassLib from KeePass Desktop v2.20 diff --git a/src/KeePassLib2Android/.gitignore b/src/KeePassLib2Android/.gitignore new file mode 100644 index 00000000..2416a678 --- /dev/null +++ b/src/KeePassLib2Android/.gitignore @@ -0,0 +1 @@ +obj/ diff --git a/src/KeePassLib2Android/Collections/AutoTypeConfig.cs b/src/KeePassLib2Android/Collections/AutoTypeConfig.cs new file mode 100644 index 00000000..9a58d48c --- /dev/null +++ b/src/KeePassLib2Android/Collections/AutoTypeConfig.cs @@ -0,0 +1,220 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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 KeePassLib.Interfaces; + +namespace KeePassLib.Collections +{ + [Flags] + public enum AutoTypeObfuscationOptions + { + None = 0, + UseClipboard = 1 + } + + public sealed class AutoTypeAssociation : IEquatable, + IDeepCloneable + { + private string m_strWindow = string.Empty; + public string WindowName + { + get { return m_strWindow; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + m_strWindow = value; + } + } + + private string m_strSequence = string.Empty; + public string Sequence + { + get { return m_strSequence; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + m_strSequence = value; + } + } + + public AutoTypeAssociation() { } + + public AutoTypeAssociation(string strWindow, string strSeq) + { + if(strWindow == null) throw new ArgumentNullException("strWindow"); + if(strSeq == null) throw new ArgumentNullException("strSeq"); + + m_strWindow = strWindow; + m_strSequence = strSeq; + } + + public bool Equals(AutoTypeAssociation other) + { + if(other == null) return false; + + if(m_strWindow != other.m_strWindow) return false; + if(m_strSequence != other.m_strSequence) return false; + + return true; + } + + public AutoTypeAssociation CloneDeep() + { + return (AutoTypeAssociation)this.MemberwiseClone(); + } + } + + /// + /// A list of auto-type associations. + /// + public sealed class AutoTypeConfig : IEquatable, + IDeepCloneable + { + private bool m_bEnabled = true; + private AutoTypeObfuscationOptions m_atooObfuscation = + AutoTypeObfuscationOptions.None; + private string m_strDefaultSequence = string.Empty; + private List m_lWindowAssocs = + new List(); + + /// + /// Specify whether auto-type is enabled or not. + /// + public bool Enabled + { + get { return m_bEnabled; } + set { m_bEnabled = value; } + } + + /// + /// Specify whether the typing should be obfuscated. + /// + public AutoTypeObfuscationOptions ObfuscationOptions + { + get { return m_atooObfuscation; } + set { m_atooObfuscation = value; } + } + + /// + /// The default keystroke sequence that is auto-typed if + /// no matching window is found in the Associations + /// container. + /// + public string DefaultSequence + { + get { return m_strDefaultSequence; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + m_strDefaultSequence = value; + } + } + + /// + /// Get all auto-type window/keystroke sequence pairs. + /// + public IEnumerable Associations + { + get { return m_lWindowAssocs; } + } + + public int AssociationsCount + { + get { return m_lWindowAssocs.Count; } + } + + /// + /// Construct a new auto-type associations list. + /// + public AutoTypeConfig() + { + } + + /// + /// Remove all associations. + /// + public void Clear() + { + m_lWindowAssocs.Clear(); + } + + /// + /// Clone the auto-type associations list. + /// + /// New, cloned object. + public AutoTypeConfig CloneDeep() + { + AutoTypeConfig newCfg = new AutoTypeConfig(); + + newCfg.m_bEnabled = m_bEnabled; + newCfg.m_atooObfuscation = m_atooObfuscation; + newCfg.m_strDefaultSequence = m_strDefaultSequence; + + foreach(AutoTypeAssociation a in m_lWindowAssocs) + newCfg.Add(a.CloneDeep()); + + return newCfg; + } + + public bool Equals(AutoTypeConfig other) + { + if(other == null) { Debug.Assert(false); return false; } + + if(m_bEnabled != other.m_bEnabled) return false; + if(m_atooObfuscation != other.m_atooObfuscation) return false; + if(m_strDefaultSequence != other.m_strDefaultSequence) return false; + + if(m_lWindowAssocs.Count != other.m_lWindowAssocs.Count) return false; + for(int i = 0; i < m_lWindowAssocs.Count; ++i) + { + if(!m_lWindowAssocs[i].Equals(other.m_lWindowAssocs[i])) + return false; + } + + 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)) + throw new ArgumentOutOfRangeException("iIndex"); + + return m_lWindowAssocs[iIndex]; + } + + public void RemoveAt(int iIndex) + { + if((iIndex < 0) || (iIndex >= m_lWindowAssocs.Count)) + throw new ArgumentOutOfRangeException("iIndex"); + + m_lWindowAssocs.RemoveAt(iIndex); + } + } +} diff --git a/src/KeePassLib2Android/Collections/ProtectedBinaryDictionary.cs b/src/KeePassLib2Android/Collections/ProtectedBinaryDictionary.cs new file mode 100644 index 00000000..c0df6707 --- /dev/null +++ b/src/KeePassLib2Android/Collections/ProtectedBinaryDictionary.cs @@ -0,0 +1,173 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +using KeePassLib.Interfaces; +using KeePassLib.Security; +using KeePassLib.Utility; + +#if KeePassLibSD +using KeePassLibSD; +#endif + +namespace KeePassLib.Collections +{ + /// + /// A list of ProtectedBinary objects (dictionary). + /// + public sealed class ProtectedBinaryDictionary : + IDeepCloneable, + IEnumerable> + { + private SortedDictionary m_vBinaries = + new SortedDictionary(); + + /// + /// Get the number of binaries in this entry. + /// + public uint UCount + { + get { return (uint)m_vBinaries.Count; } + } + + /// + /// Construct a new list of protected binaries. + /// + public ProtectedBinaryDictionary() + { + } + + IEnumerator IEnumerable.GetEnumerator() + { + return m_vBinaries.GetEnumerator(); + } + + public IEnumerator> GetEnumerator() + { + return m_vBinaries.GetEnumerator(); + } + + public void Clear() + { + m_vBinaries.Clear(); + } + + /// + /// Clone the current ProtectedBinaryList object, including all + /// stored protected strings. + /// + /// New ProtectedBinaryList object. + public ProtectedBinaryDictionary CloneDeep() + { + ProtectedBinaryDictionary plNew = new ProtectedBinaryDictionary(); + + foreach(KeyValuePair kvpBin in m_vBinaries) + { + // ProtectedBinary objects are immutable + plNew.Set(kvpBin.Key, kvpBin.Value); + } + + return plNew; + } + + public bool EqualsDictionary(ProtectedBinaryDictionary dict) + { + if(dict == null) { Debug.Assert(false); return false; } + + if(m_vBinaries.Count != dict.m_vBinaries.Count) return false; + + foreach(KeyValuePair kvp in m_vBinaries) + { + ProtectedBinary pb = dict.Get(kvp.Key); + if(pb == null) return false; + if(!pb.Equals(kvp.Value)) return false; + } + + return true; + } + + /// + /// Get one of the stored binaries. + /// + /// Binary identifier. + /// Protected binary. If the binary identified by + /// cannot be found, the function + /// returns null. + /// Thrown if the input + /// parameter is null. + public ProtectedBinary Get(string strName) + { + Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); + + ProtectedBinary pb; + if(m_vBinaries.TryGetValue(strName, out pb)) return pb; + + return null; + } + + /// + /// Set a binary object. + /// + /// Identifier of the binary field to modify. + /// New value. This parameter must not be null. + /// Thrown if any of the input + /// parameters is null. + public void Set(string strField, ProtectedBinary pbNewValue) + { + Debug.Assert(strField != null); if(strField == null) throw new ArgumentNullException("strField"); + Debug.Assert(pbNewValue != null); if(pbNewValue == null) throw new ArgumentNullException("pbNewValue"); + + m_vBinaries[strField] = pbNewValue; + } + + /// + /// Remove a binary object. + /// + /// Identifier of the binary field to remove. + /// Returns true if the object has been successfully + /// removed, otherwise false. + /// Thrown if the input parameter + /// is null. + public bool Remove(string strField) + { + Debug.Assert(strField != null); if(strField == null) throw new ArgumentNullException("strField"); + + return m_vBinaries.Remove(strField); + } + + public string KeysToString() + { + if(m_vBinaries.Count == 0) return string.Empty; + + StringBuilder sb = new StringBuilder(); + foreach(KeyValuePair kvp in m_vBinaries) + { + if(sb.Length > 0) sb.Append(", "); + sb.Append(kvp.Key); + } + + return sb.ToString(); + } + } +} diff --git a/src/KeePassLib2Android/Collections/ProtectedStringDictionary.cs b/src/KeePassLib2Android/Collections/ProtectedStringDictionary.cs new file mode 100644 index 00000000..ef03ff78 --- /dev/null +++ b/src/KeePassLib2Android/Collections/ProtectedStringDictionary.cs @@ -0,0 +1,306 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +using KeePassLib.Interfaces; +using KeePassLib.Security; +using KeePassLib.Utility; + +#if KeePassLibSD +using KeePassLibSD; +#endif + +namespace KeePassLib.Collections +{ + /// + /// A list of ProtectedString objects (dictionary). + /// + public sealed class ProtectedStringDictionary : + IDeepCloneable, + IEnumerable> + { + private SortedDictionary m_vStrings = + new SortedDictionary(); + + /// + /// Get the number of strings in this entry. + /// + public uint UCount + { + get { return (uint)m_vStrings.Count; } + } + + /// + /// Construct a new list of protected strings. + /// + public ProtectedStringDictionary() + { + } + + IEnumerator IEnumerable.GetEnumerator() + { + return m_vStrings.GetEnumerator(); + } + + public IEnumerator> GetEnumerator() + { + return m_vStrings.GetEnumerator(); + } + + public void Clear() + { + m_vStrings.Clear(); + } + + /// + /// Clone the current ProtectedStringList object, including all + /// stored protected strings. + /// + /// New ProtectedStringList object. + public ProtectedStringDictionary CloneDeep() + { + ProtectedStringDictionary plNew = new ProtectedStringDictionary(); + + foreach(KeyValuePair kvpStr in m_vStrings) + { + // ProtectedString objects are immutable + plNew.Set(kvpStr.Key, kvpStr.Value); + } + + return plNew; + } + + [Obsolete] + public bool EqualsDictionary(ProtectedStringDictionary dict) + { + return EqualsDictionary(dict, PwCompareOptions.None, MemProtCmpMode.None); + } + + [Obsolete] + public bool EqualsDictionary(ProtectedStringDictionary dict, + MemProtCmpMode mpCompare) + { + return EqualsDictionary(dict, PwCompareOptions.None, mpCompare); + } + + public bool EqualsDictionary(ProtectedStringDictionary dict, + PwCompareOptions pwOpt, MemProtCmpMode mpCompare) + { + if(dict == null) { Debug.Assert(false); return false; } + + bool bNeEqStd = ((pwOpt & PwCompareOptions.NullEmptyEquivStd) != + PwCompareOptions.None); + if(!bNeEqStd) + { + if(m_vStrings.Count != dict.m_vStrings.Count) return false; + } + + foreach(KeyValuePair kvp in m_vStrings) + { + bool bStdField = PwDefs.IsStandardField(kvp.Key); + ProtectedString ps = dict.Get(kvp.Key); + + if(bNeEqStd && (ps == null) && bStdField) + ps = ProtectedString.Empty; + + if(ps == null) return false; + + if(mpCompare == MemProtCmpMode.Full) + { + if(ps.IsProtected != kvp.Value.IsProtected) return false; + } + else if(mpCompare == MemProtCmpMode.CustomOnly) + { + if(!bStdField && (ps.IsProtected != kvp.Value.IsProtected)) + return false; + } + + if(ps.ReadString() != kvp.Value.ReadString()) return false; + } + + if(bNeEqStd) + { + foreach(KeyValuePair kvp in dict.m_vStrings) + { + ProtectedString ps = Get(kvp.Key); + + if(ps != null) continue; // Compared previously + if(!PwDefs.IsStandardField(kvp.Key)) return false; + if(!kvp.Value.IsEmpty) return false; + } + } + + return true; + } + + /// + /// Get one of the protected strings. + /// + /// String identifier. + /// Protected string. If the string identified by + /// cannot be found, the function + /// returns null. + /// Thrown if the input parameter + /// is null. + public ProtectedString Get(string strName) + { + Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); + + ProtectedString ps; + if(m_vStrings.TryGetValue(strName, out ps)) return ps; + + return null; + } + + /// + /// Get one of the protected strings. The return value is never null. + /// If the requested string cannot be found, an empty protected string + /// object is returned. + /// + /// String identifier. + /// Returns a protected string object. If the standard string + /// has not been set yet, the return value is an empty string (""). + /// Thrown if the input + /// parameter is null. + public ProtectedString GetSafe(string strName) + { + Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); + + ProtectedString ps; + if(m_vStrings.TryGetValue(strName, out ps)) return ps; + + return ProtectedString.Empty; + } + + /// + /// Test if a named string exists. + /// + /// Name of the string to try. + /// Returns true if the string exists, otherwise false. + /// Thrown if + /// is null. + public bool Exists(string strName) + { + Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); + + return m_vStrings.ContainsKey(strName); + } + + /// + /// Get one of the protected strings. If the string doesn't exist, the + /// return value is an empty string (""). + /// + /// Name of the requested string. + /// Requested string value or an empty string, if the named + /// string doesn't exist. + /// Thrown if the input + /// parameter is null. + public string ReadSafe(string strName) + { + Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); + + ProtectedString ps; + if(m_vStrings.TryGetValue(strName, out ps)) + return ps.ReadString(); + + return string.Empty; + } + + /// + /// Get one of the entry strings. If the string doesn't exist, the + /// return value is an empty string (""). If the string is + /// in-memory protected, the return value is PwDefs.HiddenPassword. + /// + /// Name of the requested string. + /// Returns the requested string in plain-text or + /// PwDefs.HiddenPassword if the string cannot be found. + /// Thrown if the input + /// parameter is null. + public string ReadSafeEx(string strName) + { + Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); + + ProtectedString ps; + if(m_vStrings.TryGetValue(strName, out ps)) + { + if(ps.IsProtected) return PwDefs.HiddenPassword; + return ps.ReadString(); + } + + return string.Empty; + } + + /// + /// Set a string. + /// + /// Identifier of the string field to modify. + /// New value. This parameter must not be null. + /// Thrown if one of the input + /// parameters is null. + public void Set(string strField, ProtectedString psNewValue) + { + Debug.Assert(strField != null); if(strField == null) throw new ArgumentNullException("strField"); + Debug.Assert(psNewValue != null); if(psNewValue == null) throw new ArgumentNullException("psNewValue"); + + m_vStrings[strField] = psNewValue; + } + + /// + /// 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. + /// Thrown if the input + /// parameter is null. + public bool Remove(string strField) + { + Debug.Assert(strField != null); if(strField == null) throw new ArgumentNullException("strField"); + + return m_vStrings.Remove(strField); + } + + public List GetKeys() + { + List v = new List(); + + foreach(string strKey in m_vStrings.Keys) v.Add(strKey); + + return v; + } + + public void EnableProtection(string strField, bool bProtect) + { + ProtectedString ps = Get(strField); + if(ps == null) return; // Nothing to do, no assert + + if(ps.IsProtected != bProtect) + { + byte[] pbData = ps.ReadUtf8(); + Set(strField, new ProtectedString(bProtect, pbData)); + MemUtil.ZeroByteArray(pbData); + } + } + } +} diff --git a/src/KeePassLib2Android/Collections/PwObjectList.cs b/src/KeePassLib2Android/Collections/PwObjectList.cs new file mode 100644 index 00000000..afd6fcd8 --- /dev/null +++ b/src/KeePassLib2Android/Collections/PwObjectList.cs @@ -0,0 +1,303 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; + +using KeePassLib.Interfaces; + +namespace KeePassLib.Collections +{ + /// + /// List of objects that implement IDeepCloneable, + /// and cannot be null. + /// + /// Type specifier. + public sealed class PwObjectList : IEnumerable + where T : class, IDeepCloneable + { + private List m_vObjects = new List(); + + /// + /// Get number of objects in this list. + /// + public uint UCount + { + get { return (uint)m_vObjects.Count; } + } + + /// + /// Construct a new list of objects. + /// + public PwObjectList() + { + } + + IEnumerator IEnumerable.GetEnumerator() + { + return m_vObjects.GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + return m_vObjects.GetEnumerator(); + } + + public void Clear() + { + // Do not destroy contained objects! + m_vObjects.Clear(); + } + + /// + /// Clone the current PwObjectList, including all + /// stored objects (deep copy). + /// + /// New PwObjectList. + public PwObjectList CloneDeep() + { + PwObjectList pl = new PwObjectList(); + + foreach(T po in m_vObjects) + pl.Add(po.CloneDeep()); + + return pl; + } + + public PwObjectList CloneShallow() + { + PwObjectList tNew = new PwObjectList(); + + foreach(T po in m_vObjects) tNew.Add(po); + + return tNew; + } + + public List CloneShallowToList() + { + PwObjectList tNew = CloneShallow(); + return tNew.m_vObjects; + } + + /// + /// Add an object to this list. + /// + /// Object to be added. + /// Thrown if the input + /// parameter is null. + public void Add(T pwObject) + { + Debug.Assert(pwObject != null); + if(pwObject == null) throw new ArgumentNullException("pwObject"); + + m_vObjects.Add(pwObject); + } + + public void Add(PwObjectList vObjects) + { + Debug.Assert(vObjects != null); + if(vObjects == null) throw new ArgumentNullException("vObjects"); + + foreach(T po in vObjects) + { + m_vObjects.Add(po); + } + } + + public void Add(List vObjects) + { + Debug.Assert(vObjects != null); + if(vObjects == null) throw new ArgumentNullException("vObjects"); + + foreach(T po in vObjects) + { + m_vObjects.Add(po); + } + } + + /// + /// Get an object of the list. + /// + /// Index of the object to get. Must be valid, otherwise an + /// exception is thrown. + /// Reference to an existing T object. Is never null. + public T GetAt(uint uIndex) + { + Debug.Assert(uIndex < m_vObjects.Count); + if(uIndex >= m_vObjects.Count) throw new ArgumentOutOfRangeException("uIndex"); + + return m_vObjects[(int)uIndex]; + } + + public void SetAt(uint uIndex, T pwObject) + { + Debug.Assert(pwObject != null); + if(pwObject == null) throw new ArgumentNullException("pwObject"); + if(uIndex >= (uint)m_vObjects.Count) + throw new ArgumentOutOfRangeException("uIndex"); + + m_vObjects[(int)uIndex] = pwObject; + } + + /// + /// Get a range of objects. + /// + /// Index of the first object to be + /// returned (inclusive). + /// Index of the last object to be + /// returned (inclusive). + /// + public List GetRange(uint uStartIndexIncl, uint uEndIndexIncl) + { + if(uStartIndexIncl >= (uint)m_vObjects.Count) + throw new ArgumentOutOfRangeException("uStartIndexIncl"); + if(uEndIndexIncl >= (uint)m_vObjects.Count) + throw new ArgumentOutOfRangeException("uEndIndexIncl"); + if(uStartIndexIncl > uEndIndexIncl) + throw new ArgumentException(); + + List list = new List((int)(uEndIndexIncl - uStartIndexIncl) + 1); + for(uint u = uStartIndexIncl; u <= uEndIndexIncl; ++u) + { + list.Add(m_vObjects[(int)u]); + } + + return list; + } + + public int IndexOf(T pwReference) + { + Debug.Assert(pwReference != null); if(pwReference == null) throw new ArgumentNullException("pwReference"); + + return m_vObjects.IndexOf(pwReference); + } + + /// + /// Delete an object of this list. The object to be deleted is identified + /// by a reference handle. + /// + /// Reference of the object to be deleted. + /// Returns true if the object was deleted, false if + /// the object wasn't found in this list. + /// Thrown if the input + /// parameter is null. + public bool Remove(T pwReference) + { + Debug.Assert(pwReference != null); if(pwReference == null) throw new ArgumentNullException("pwReference"); + + return m_vObjects.Remove(pwReference); + } + + public void RemoveAt(uint uIndex) + { + m_vObjects.RemoveAt((int)uIndex); + } + + /// + /// Move an object up or down. + /// + /// The object to be moved. + /// Move one up. If false, move one down. + public void MoveOne(T tObject, bool bUp) + { + Debug.Assert(tObject != null); + if(tObject == null) throw new ArgumentNullException("tObject"); + + int nCount = m_vObjects.Count; + if(nCount <= 1) return; + + int nIndex = m_vObjects.IndexOf(tObject); + Debug.Assert(nIndex >= 0); + + if(bUp && (nIndex > 0)) // No assert for top item + { + T tTemp = m_vObjects[nIndex - 1]; + m_vObjects[nIndex - 1] = m_vObjects[nIndex]; + m_vObjects[nIndex] = tTemp; + } + else if(!bUp && (nIndex != (nCount - 1))) // No assert for bottom item + { + T tTemp = m_vObjects[nIndex + 1]; + m_vObjects[nIndex + 1] = m_vObjects[nIndex]; + m_vObjects[nIndex] = tTemp; + } + } + + /// + /// Move some of the objects in this list to the top/bottom. + /// + /// List of objects to be moved. + /// Move to top. If false, move to bottom. + public void MoveTopBottom(T[] vObjects, bool bTop) + { + Debug.Assert(vObjects != null); + if(vObjects == null) throw new ArgumentNullException("vObjects"); + + if(vObjects.Length == 0) return; + + int nCount = m_vObjects.Count; + foreach(T t in vObjects) m_vObjects.Remove(t); + + if(bTop) + { + int nPos = 0; + foreach(T t in vObjects) + { + m_vObjects.Insert(nPos, t); + ++nPos; + } + } + else // Move to bottom + { + foreach(T t in vObjects) m_vObjects.Add(t); + } + + Debug.Assert(nCount == m_vObjects.Count); + if(nCount != m_vObjects.Count) + throw new ArgumentException("At least one of the T objects in the vObjects list doesn't exist!"); + } + + public void Sort(IComparer tComparer) + { + if(tComparer == null) throw new ArgumentNullException("tComparer"); + + m_vObjects.Sort(tComparer); + } + + public static PwObjectList FromArray(T[] tArray) + { + if(tArray == null) throw new ArgumentNullException("tArray"); + + PwObjectList l = new PwObjectList(); + foreach(T t in tArray) { l.Add(t); } + return l; + } + + public static PwObjectList FromList(List tList) + { + if(tList == null) throw new ArgumentNullException("tList"); + + PwObjectList l = new PwObjectList(); + l.Add(tList); + return l; + } + } +} diff --git a/src/KeePassLib2Android/Collections/PwObjectPool.cs b/src/KeePassLib2Android/Collections/PwObjectPool.cs new file mode 100644 index 00000000..90c980b8 --- /dev/null +++ b/src/KeePassLib2Android/Collections/PwObjectPool.cs @@ -0,0 +1,80 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +using KeePassLib.Delegates; +using KeePassLib.Interfaces; + +#if KeePassLibSD +using KeePassLibSD; +#endif + +namespace KeePassLib.Collections +{ + public sealed class PwObjectPool + { + private SortedDictionary m_dict = + new SortedDictionary(); + + public static PwObjectPool FromGroupRecursive(PwGroup pgRoot, bool bEntries) + { + if(pgRoot == null) throw new ArgumentNullException("pgRoot"); + + PwObjectPool p = new PwObjectPool(); + + if(!bEntries) p.m_dict[new PwUuidComparable(pgRoot.Uuid)] = pgRoot; + GroupHandler gh = delegate(PwGroup pg) + { + p.m_dict[new PwUuidComparable(pg.Uuid)] = pg; + return true; + }; + + EntryHandler eh = delegate(PwEntry pe) + { + p.m_dict[new PwUuidComparable(pe.Uuid)] = pe; + return true; + }; + + pgRoot.TraverseTree(TraversalMethod.PreOrder, bEntries ? null : gh, + bEntries ? eh : null); + return p; + } + + public IStructureItem Get(PwUuid pwUuid) + { + IStructureItem pItem; + m_dict.TryGetValue(new PwUuidComparable(pwUuid), out pItem); + return pItem; + } + + public bool ContainsOnlyType(Type t) + { + foreach(KeyValuePair kvp in m_dict) + { + if(kvp.Value.GetType() != t) return false; + } + + return true; + } + } +} diff --git a/src/KeePassLib2Android/Collections/StringDictionaryEx.cs b/src/KeePassLib2Android/Collections/StringDictionaryEx.cs new file mode 100644 index 00000000..26a494f4 --- /dev/null +++ b/src/KeePassLib2Android/Collections/StringDictionaryEx.cs @@ -0,0 +1,116 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +using KeePassLib.Interfaces; + +#if KeePassLibSD +using KeePassLibSD; +#endif + +namespace KeePassLib.Collections +{ + public sealed class StringDictionaryEx : IDeepCloneable, + IEnumerable> + { + private SortedDictionary m_vDict = + new SortedDictionary(); + + public int Count + { + get { return m_vDict.Count; } + } + + public StringDictionaryEx() + { + } + + IEnumerator IEnumerable.GetEnumerator() + { + return m_vDict.GetEnumerator(); + } + + public IEnumerator> GetEnumerator() + { + return m_vDict.GetEnumerator(); + } + + public StringDictionaryEx CloneDeep() + { + StringDictionaryEx plNew = new StringDictionaryEx(); + + foreach(KeyValuePair kvpStr in m_vDict) + plNew.Set(kvpStr.Key, kvpStr.Value); + + return plNew; + } + + public string Get(string strName) + { + Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); + + string s; + if(m_vDict.TryGetValue(strName, out s)) return s; + + return null; + } + + public bool Exists(string strName) + { + Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); + + return m_vDict.ContainsKey(strName); + } + + /// + /// Set a string. + /// + /// Identifier of the string field to modify. + /// New value. This parameter must not be null. + /// Thrown if one of the input + /// 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"); + + m_vDict[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. + /// Thrown if the input + /// parameter is null. + public bool Remove(string strField) + { + Debug.Assert(strField != null); if(strField == null) throw new ArgumentNullException("strField"); + + return m_vDict.Remove(strField); + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/Cipher/CipherPool.cs b/src/KeePassLib2Android/Cryptography/Cipher/CipherPool.cs new file mode 100644 index 00000000..0367151c --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/Cipher/CipherPool.cs @@ -0,0 +1,162 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Diagnostics; +using System.Security; +using System.Security.Cryptography; + +namespace KeePassLib.Cryptography.Cipher +{ + /// + /// Pool of encryption/decryption algorithms (ciphers). + /// + public sealed class CipherPool + { + private List m_vCiphers = new List(); + private static CipherPool m_poolGlobal = null; + + /// + /// Reference to the global cipher pool. + /// + public static CipherPool GlobalPool + { + get + { + if(m_poolGlobal != null) return m_poolGlobal; + + m_poolGlobal = new CipherPool(); + m_poolGlobal.AddCipher(new StandardAesEngine()); + + return m_poolGlobal; + } + } + + /// + /// Remove all cipher engines from the current pool. + /// + public void Clear() + { + m_vCiphers.Clear(); + } + + /// + /// Add a cipher engine to the pool. + /// + /// Cipher engine to add. Must not be null. + public void AddCipher(ICipherEngine csEngine) + { + Debug.Assert(csEngine != null); + if(csEngine == null) throw new ArgumentNullException("csEngine"); + + // 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)) + return; + + m_vCiphers.Add(csEngine); + } + + /// + /// Get a cipher identified by its UUID. + /// + /// UUID of the cipher to return. + /// Reference to the requested cipher. If the cipher is + /// not found, null is returned. + public ICipherEngine GetCipher(PwUuid uuidCipher) + { + foreach(ICipherEngine iEngine in m_vCiphers) + { + if(iEngine.CipherUuid.EqualsValue(uuidCipher)) + return iEngine; + } + + return null; + } + + /// + /// Get the index of a cipher. This index is temporary and should + /// not be stored or used to identify a cipher. + /// + /// UUID of the cipher. + /// Index of the requested cipher. Returns -1 if + /// the specified cipher is not found. + public int GetCipherIndex(PwUuid uuidCipher) + { + for(int i = 0; i < m_vCiphers.Count; ++i) + { + if(m_vCiphers[i].CipherUuid.EqualsValue(uuidCipher)) + return i; + } + + Debug.Assert(false); + return -1; + } + + /// + /// Get the index of a cipher. This index is temporary and should + /// not be stored or used to identify a cipher. + /// + /// Name of the cipher. Note that + /// multiple ciphers can have the same name. In this case, the + /// first matching cipher is returned. + /// Cipher with the specified name or -1 if + /// no cipher with that name is found. + public int GetCipherIndex(string strDisplayName) + { + for(int i = 0; i < m_vCiphers.Count; ++i) + if(m_vCiphers[i].DisplayName == strDisplayName) + return i; + + Debug.Assert(false); + return -1; + } + + /// + /// Get the number of cipher engines in this pool. + /// + public int EngineCount + { + get { return m_vCiphers.Count; } + } + + /// + /// Get the cipher engine at the specified position. Throws + /// an exception if the index is invalid. You can use this + /// to iterate over all ciphers, but do not use it to + /// identify ciphers. + /// + /// Index of the requested cipher engine. + /// Reference to the cipher engine at the specified + /// position. + public ICipherEngine this[int nIndex] + { + get + { + if((nIndex < 0) || (nIndex >= m_vCiphers.Count)) + throw new ArgumentOutOfRangeException("nIndex"); + + return m_vCiphers[nIndex]; + } + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/Cipher/ICipherEngine.cs b/src/KeePassLib2Android/Cryptography/Cipher/ICipherEngine.cs new file mode 100644 index 00000000..d9dbad1b --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/Cipher/ICipherEngine.cs @@ -0,0 +1,66 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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.IO; + +namespace KeePassLib.Cryptography.Cipher +{ + /// + /// Interface of an encryption/decryption class. + /// + public interface ICipherEngine + { + /// + /// UUID of the engine. If you want to write an engine/plugin, + /// please contact the KeePass team to obtain a new UUID. + /// + PwUuid CipherUuid + { + get; + } + + /// + /// String displayed in the list of available encryption/decryption + /// engines in the GUI. + /// + string DisplayName + { + get; + } + + /// + /// Encrypt a stream. + /// + /// Stream to read the plain-text from. + /// Key to use. + /// Initialization vector. + /// Stream, from which the encrypted data can be read. + Stream EncryptStream(Stream sPlainText, byte[] pbKey, byte[] pbIV); + + /// + /// Decrypt a stream. + /// + /// Stream to read the encrypted data from. + /// Key to use. + /// Initialization vector. + /// Stream, from which the decrypted data can be read. + Stream DecryptStream(Stream sEncrypted, byte[] pbKey, byte[] pbIV); + } +} diff --git a/src/KeePassLib2Android/Cryptography/Cipher/Salsa20Cipher.cs b/src/KeePassLib2Android/Cryptography/Cipher/Salsa20Cipher.cs new file mode 100644 index 00000000..bcd7c4d8 --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/Cipher/Salsa20Cipher.cs @@ -0,0 +1,188 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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 +*/ + +// Implementation of the Salsa20 cipher, based on the eSTREAM submission. + +using System; +using System.Diagnostics; + +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.Cipher +{ + public sealed class Salsa20Cipher + { + private uint[] m_state = new uint[16]; + 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]{ + 0x61707865, 0x3320646E, 0x79622D32, 0x6B206574 + }; + + public Salsa20Cipher(byte[] pbKey32, byte[] pbIV8) + { + KeySetup(pbKey32); + IvSetup(pbIV8); + } + + ~Salsa20Cipher() + { + // Clear sensitive data + Array.Clear(m_state, 0, m_state.Length); + Array.Clear(m_x, 0, m_x.Length); + } + + private void NextOutput() + { + uint[] x = m_x; // Local alias for working buffer + + // Compiler/runtime might remove array bound checks after this + if(x.Length < 16) throw new InvalidOperationException(); + + Array.Copy(m_state, x, 16); + + unchecked + { + for(int i = 0; i < 10; ++i) // (int i = 20; i > 0; i -= 2) + { + 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); + } + + for(int i = 0; i < 16; ++i) + x[i] += m_state[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); + } + + 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; + } + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/Cipher/StandardAesEngine.cs b/src/KeePassLib2Android/Cryptography/Cipher/StandardAesEngine.cs new file mode 100644 index 00000000..0bf442b7 --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/Cipher/StandardAesEngine.cs @@ -0,0 +1,140 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Security; +using System.Security.Cryptography; +using System.Diagnostics; + +using KeePassLib.Resources; + +namespace KeePassLib.Cryptography.Cipher +{ + /// + /// Standard AES cipher implementation. + /// + public sealed class StandardAesEngine : ICipherEngine + { + private const CipherMode m_rCipherMode = CipherMode.CBC; + private const PaddingMode m_rCipherPadding = PaddingMode.PKCS7; + + private static PwUuid m_uuidAes = null; + + /// + /// UUID of the cipher engine. This ID uniquely identifies the + /// AES engine. Must not be used by other ciphers. + /// + public static PwUuid AesUuid + { + 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; + } + } + + /// + /// Get the UUID of this cipher engine as PwUuid object. + /// + public PwUuid CipherUuid + { + get { return StandardAesEngine.AesUuid; } + } + + /// + /// Get a displayable name describing this cipher engine. + /// + public string DisplayName { get { return KLRes.EncAlgorithmAes; } } + + private static void ValidateArguments(Stream stream, bool bEncrypt, byte[] pbKey, byte[] pbIV) + { + Debug.Assert(stream != null); if(stream == null) throw new ArgumentNullException("stream"); + + Debug.Assert(pbKey != null); if(pbKey == null) throw new ArgumentNullException("pbKey"); + Debug.Assert(pbKey.Length == 32); + if(pbKey.Length != 32) throw new ArgumentException("Key must be 256 bits wide!"); + + Debug.Assert(pbIV != null); if(pbIV == null) throw new ArgumentNullException("pbIV"); + Debug.Assert(pbIV.Length == 16); + if(pbIV.Length != 16) throw new ArgumentException("Initialization vector must be 128 bits wide!"); + + if(bEncrypt) + { + Debug.Assert(stream.CanWrite); + if(stream.CanWrite == false) 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!"); + } + } + + private static Stream CreateStream(Stream s, bool bEncrypt, byte[] pbKey, byte[] pbIV) + { + StandardAesEngine.ValidateArguments(s, bEncrypt, pbKey, pbIV); + + 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; + + ICryptoTransform iTransform = (bEncrypt ? r.CreateEncryptor() : r.CreateDecryptor()); + Debug.Assert(iTransform != null); + if(iTransform == null) throw new SecurityException("Unable to create Rijndael transform!"); + + return new CryptoStream(s, iTransform, bEncrypt ? CryptoStreamMode.Write : + CryptoStreamMode.Read); + } + + public Stream EncryptStream(Stream sPlainText, byte[] pbKey, byte[] pbIV) + { + return StandardAesEngine.CreateStream(sPlainText, true, pbKey, pbIV); + } + + public Stream DecryptStream(Stream sEncrypted, byte[] pbKey, byte[] pbIV) + { + return StandardAesEngine.CreateStream(sEncrypted, false, pbKey, pbIV); + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/CryptoRandom.cs b/src/KeePassLib2Android/Cryptography/CryptoRandom.cs new file mode 100644 index 00000000..1cd1523e --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/CryptoRandom.cs @@ -0,0 +1,283 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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.Security; +using System.Security.Cryptography; +using System.IO; +using System.Diagnostics; +using System.Windows.Forms; +using System.Drawing; + +using KeePassLib.Native; +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography +{ + /// + /// Cryptographically strong random number generator. The returned values + /// are unpredictable and cannot be reproduced. + /// CryptoRandom is a singleton class. + /// + public sealed class CryptoRandom + { + private byte[] m_pbEntropyPool = new byte[64]; + private uint m_uCounter; + private RNGCryptoServiceProvider m_rng = new RNGCryptoServiceProvider(); + private ulong m_uGeneratedBytesCount = 0; + + private object m_oSyncRoot = new object(); + + private static CryptoRandom m_pInstance = null; + public static CryptoRandom Instance + { + get + { + if(m_pInstance != null) return m_pInstance; + + m_pInstance = new CryptoRandom(); + return m_pInstance; + } + } + + /// + /// Get the number of random bytes that this instance generated so far. + /// Note that this number can be higher than the number of random bytes + /// actually requested using the GetRandomBytes method. + /// + public ulong GeneratedBytesCount + { + get + { + ulong u; + lock(m_oSyncRoot) { u = m_uGeneratedBytesCount; } + return u; + } + } + + /// + /// Event that is triggered whenever the internal GenerateRandom256 + /// method is called to generate random bytes. + /// + public event EventHandler GenerateRandom256Pre; + + private CryptoRandom() + { + Random r = new Random(); + m_uCounter = (uint)r.Next(); + + AddEntropy(GetSystemData(r)); + AddEntropy(GetCspData()); + } + + /// + /// Update the internal seed of the random number generator based + /// on entropy data. + /// This method is thread-safe. + /// + /// Entropy bytes. + public void AddEntropy(byte[] pbEntropy) + { + if(pbEntropy == null) { Debug.Assert(false); return; } + if(pbEntropy.Length == 0) { Debug.Assert(false); return; } + + byte[] pbNewData = pbEntropy; + if(pbEntropy.Length >= 64) + { +#if !KeePassLibSD + SHA512Managed shaNew = new SHA512Managed(); +#else + SHA256Managed shaNew = new SHA256Managed(); +#endif + 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); + + byte[] pbFinal = ms.ToArray(); +#if !KeePassLibSD + Debug.Assert(pbFinal.Length == (64 + pbNewData.Length)); + SHA512Managed shaPool = new SHA512Managed(); +#else + SHA256Managed shaPool = new SHA256Managed(); +#endif + m_pbEntropyPool = shaPool.ComputeHash(pbFinal); + } + ms.Close(); + } + + private static byte[] GetSystemData(Random rWeak) + { + MemoryStream ms = new MemoryStream(); + byte[] pb; + + pb = MemUtil.UInt32ToBytes((uint)Environment.TickCount); + ms.Write(pb, 0, pb.Length); + + pb = TimeUtil.PackTime(DateTime.Now); + 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); +#endif + + pb = MemUtil.UInt32ToBytes((uint)rWeak.Next()); + ms.Write(pb, 0, pb.Length); + + 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); + pb = MemUtil.UInt64ToBytes((ulong)Environment.WorkingSet); + ms.Write(pb, 0, pb.Length); + + 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); + + Process 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); + + // Not supported in Mono 1.2.6: + // pb = MemUtil.UInt32ToBytes((uint)p.SessionId); + // ms.Write(pb, 0, pb.Length); + } + catch(Exception) { } +#endif + + pb = Guid.NewGuid().ToByteArray(); + ms.Write(pb, 0, pb.Length); + + byte[] pbAll = ms.ToArray(); + ms.Close(); + return pbAll; + } + + private byte[] GetCspData() + { + byte[] pbCspRandom = new byte[32]; + m_rng.GetBytes(pbCspRandom); + return pbCspRandom; + } + + private byte[] GenerateRandom256() + { + if(this.GenerateRandom256Pre != null) + this.GenerateRandom256Pre(this, EventArgs.Empty); + + byte[] pbFinal; + lock(m_oSyncRoot) + { + unchecked { m_uCounter += 386047; } // Prime number + byte[] pbCounter = MemUtil.UInt32ToBytes(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(); + + m_uGeneratedBytesCount += 32; + } + + SHA256Managed sha256 = new SHA256Managed(); + return sha256.ComputeHash(pbFinal); + } + + /// + /// Get a number of cryptographically strong random bytes. + /// This method is thread-safe. + /// + /// Number of requested random bytes. + /// A byte array consisting of + /// random bytes. + public byte[] GetRandomBytes(uint uRequestedBytes) + { + if(uRequestedBytes == 0) return new byte[0]; // Allow zero-length array + + byte[] pbRes = new byte[uRequestedBytes]; + long lPos = 0; + + while(uRequestedBytes != 0) + { + byte[] pbRandom256 = GenerateRandom256(); + Debug.Assert(pbRandom256.Length == 32); + + long lCopy = (long)((uRequestedBytes < 32) ? uRequestedBytes : 32); + +#if !KeePassLibSD + Array.Copy(pbRandom256, 0, pbRes, lPos, lCopy); +#else + Array.Copy(pbRandom256, 0, pbRes, (int)lPos, (int)lCopy); +#endif + + lPos += lCopy; + uRequestedBytes -= (uint)lCopy; + } + + Debug.Assert((int)lPos == pbRes.Length); + return pbRes; + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/CryptoRandomStream.cs b/src/KeePassLib2Android/Cryptography/CryptoRandomStream.cs new file mode 100644 index 00000000..8d4e90de --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/CryptoRandomStream.cs @@ -0,0 +1,209 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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.Diagnostics; +using System.Security.Cryptography; + +using KeePassLib.Cryptography.Cipher; + +namespace KeePassLib.Cryptography +{ + /// + /// Algorithms supported by CryptoRandomStream. + /// + public enum CrsAlgorithm + { + /// + /// Not supported. + /// + Null = 0, + + /// + /// A variant of the ARCFour algorithm (RC4 incompatible). + /// + ArcFourVariant = 1, + + /// + /// Salsa20 stream cipher algorithm. + /// + Salsa20 = 2, + + Count = 3 + } + + /// + /// A random stream class. The class is initialized using random + /// bytes provided by the caller. The produced stream has random + /// properties, but for the same seed always the same stream + /// is produced, i.e. this class can be used as stream cipher. + /// + public sealed class CryptoRandomStream + { + private CrsAlgorithm m_crsAlgorithm; + + private byte[] m_pbState = null; + private byte m_i = 0; + private byte m_j = 0; + + private Salsa20Cipher m_salsa20 = null; + + /// + /// Construct a new cryptographically secure random stream object. + /// + /// 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) + { + m_crsAlgorithm = genAlgorithm; + + Debug.Assert(pbKey != null); if(pbKey == null) throw new ArgumentNullException("pbKey"); + + uint uKeyLen = (uint)pbKey.Length; + Debug.Assert(uKeyLen != 0); if(uKeyLen == 0) throw new ArgumentException(); + + if(genAlgorithm == CrsAlgorithm.ArcFourVariant) + { + // Fill the state linearly + m_pbState = new byte[256]; + for(uint 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 + { + j += (byte)(m_pbState[w] + pbKey[inxKey]); + + t = m_pbState[0]; // Swap entries + m_pbState[0] = m_pbState[j]; + m_pbState[j] = t; + + ++inxKey; + if(inxKey >= uKeyLen) 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[]{ 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(); + } + } + + /// + /// Get random bytes. + /// + /// Number of random bytes to retrieve. + /// Returns random bytes. + public byte[] GetRandomBytes(uint uRequestedCount) + { + if(uRequestedCount == 0) return new byte[0]; + + byte[] pbRet = new byte[uRequestedCount]; + + if(m_crsAlgorithm == CrsAlgorithm.ArcFourVariant) + { + unchecked + { + for(uint w = 0; w < uRequestedCount; ++w) + { + ++m_i; + m_j += m_pbState[m_i]; + + byte t = m_pbState[m_i]; // Swap entries + m_pbState[m_i] = m_pbState[m_j]; + m_pbState[m_j] = t; + + t = (byte)(m_pbState[m_i] + m_pbState[m_j]); + pbRet[w] = m_pbState[t]; + } + } + } + else if(m_crsAlgorithm == CrsAlgorithm.Salsa20) + m_salsa20.Encrypt(pbRet, pbRet.Length, false); + else { Debug.Assert(false); } + + return pbRet; + } + + 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); + } + } + +#if CRSBENCHMARK + public static string Benchmark() + { + int nRounds = 2000000; + + string str = "ArcFour small: " + BenchTime(CrsAlgorithm.ArcFourVariant, + nRounds, 16).ToString() + "\r\n"; + str += "ArcFour big: " + BenchTime(CrsAlgorithm.ArcFourVariant, + 32, 2 * 1024 * 1024).ToString() + "\r\n"; + str += "Salsa20 small: " + BenchTime(CrsAlgorithm.Salsa20, + nRounds, 16).ToString() + "\r\n"; + str += "Salsa20 big: " + BenchTime(CrsAlgorithm.Salsa20, + 32, 2 * 1024 * 1024).ToString(); + return str; + } + + private static int BenchTime(CrsAlgorithm cra, int nRounds, int nDataSize) + { + byte[] pbKey = new byte[4] { 0x00, 0x01, 0x02, 0x03 }; + + int nStart = Environment.TickCount; + for(int i = 0; i < nRounds; ++i) + { + CryptoRandomStream c = new CryptoRandomStream(cra, pbKey); + c.GetRandomBytes((uint)nDataSize); + } + int nEnd = Environment.TickCount; + + return (nEnd - nStart); + } +#endif + } +} diff --git a/src/KeePassLib2Android/Cryptography/HashingStreamEx.cs b/src/KeePassLib2Android/Cryptography/HashingStreamEx.cs new file mode 100644 index 00000000..f900b15e --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/HashingStreamEx.cs @@ -0,0 +1,180 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Security.Cryptography; +using System.Diagnostics; + +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography +{ + public sealed class HashingStreamEx : Stream + { + private Stream m_sBaseStream; + private bool m_bWriting; + private HashAlgorithm m_hash; + + private byte[] m_pbFinalHash = null; + + public byte[] Hash + { + get { return m_pbFinalHash; } + } + + 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 { return m_sBaseStream.Length; } + } + + public override long Position + { + get { return m_sBaseStream.Position; } + set { throw new NotSupportedException(); } + } + + public HashingStreamEx(Stream sBaseStream, bool bWriting, HashAlgorithm hashAlgorithm) + { + if(sBaseStream == null) throw new ArgumentNullException("sBaseStream"); + + m_sBaseStream = sBaseStream; + m_bWriting = bWriting; + +#if !KeePassLibSD + m_hash = (hashAlgorithm ?? new SHA256Managed()); +#else // KeePassLibSD + m_hash = null; + + try { m_hash = HashAlgorithm.Create("SHA256"); } + catch(Exception) { } + try { if(m_hash == null) m_hash = HashAlgorithm.Create(); } + catch(Exception) { } +#endif + 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 DEBUG + MessageService.ShowWarning("Broken HashAlgorithm object in HashingStreamEx."); +#endif + m_hash = null; + } + } + + public override void Flush() + { + m_sBaseStream.Flush(); + } + + public override void Close() + { + if(m_hash != null) + { + try + { + m_hash.TransformFinalBlock(new byte[0], 0, 0); + + m_pbFinalHash = m_hash.Hash; + } + catch(Exception) { Debug.Assert(false); } + + m_hash = null; + } + + m_sBaseStream.Close(); + } + + public override long Seek(long lOffset, SeekOrigin soOrigin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long lValue) + { + throw new NotSupportedException(); + } + + public override int Read(byte[] pbBuffer, int nOffset, int nCount) + { + if(m_bWriting) throw new InvalidOperationException(); + + int nRead = m_sBaseStream.Read(pbBuffer, nOffset, nCount); + int nPartialRead = nRead; + while((nRead < nCount) && (nPartialRead != 0)) + { + nPartialRead = m_sBaseStream.Read(pbBuffer, nOffset + nRead, + nCount - nRead); + nRead += nPartialRead; + } + +#if DEBUG + byte[] pbOrg = new byte[pbBuffer.Length]; + Array.Copy(pbBuffer, pbOrg, pbBuffer.Length); +#endif + + if((m_hash != null) && (nRead > 0)) + m_hash.TransformBlock(pbBuffer, nOffset, nRead, pbBuffer, nOffset); + +#if DEBUG + Debug.Assert(MemUtil.ArraysEqual(pbBuffer, pbOrg)); +#endif + + return nRead; + } + + public override void Write(byte[] pbBuffer, int nOffset, int nCount) + { + if(!m_bWriting) throw new InvalidOperationException(); + +#if DEBUG + byte[] pbOrg = new byte[pbBuffer.Length]; + Array.Copy(pbBuffer, pbOrg, pbBuffer.Length); +#endif + + if((m_hash != null) && (nCount > 0)) + m_hash.TransformBlock(pbBuffer, nOffset, nCount, pbBuffer, nOffset); + +#if DEBUG + Debug.Assert(MemUtil.ArraysEqual(pbBuffer, pbOrg)); +#endif + + m_sBaseStream.Write(pbBuffer, nOffset, nCount); + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/HmacOtp.cs b/src/KeePassLib2Android/Cryptography/HmacOtp.cs new file mode 100644 index 00000000..42409280 --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/HmacOtp.cs @@ -0,0 +1,90 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Security.Cryptography; + +using KeePassLib.Utility; + +#if !KeePassLibSD +namespace KeePassLib.Cryptography +{ + /// + /// Generate HMAC-based one-time passwords as specified in RFC 4226. + /// + public static class HmacOtp + { + private static readonly uint[] vDigitsPower = new uint[]{ 1, 10, 100, + 1000, 10000, 100000, 1000000, 10000000, 100000000 }; + + public static string Generate(byte[] pbSecret, ulong uFactor, + uint uCodeDigits, bool bAddChecksum, int iTruncationOffset) + { + byte[] pbText = MemUtil.UInt64ToBytes(uFactor); + Array.Reverse(pbText); // Big-Endian + + HMACSHA1 hsha1 = new HMACSHA1(pbSecret); + byte[] pbHash = hsha1.ComputeHash(pbText); + + uint uOffset = (uint)(pbHash[pbHash.Length - 1] & 0xF); + if((iTruncationOffset >= 0) && (iTruncationOffset < (pbHash.Length - 4))) + uOffset = (uint)iTruncationOffset; + + uint uBinary = (uint)(((pbHash[uOffset] & 0x7F) << 24) | + ((pbHash[uOffset + 1] & 0xFF) << 16) | + ((pbHash[uOffset + 2] & 0xFF) << 8) | + (pbHash[uOffset + 3] & 0xFF)); + + uint uOtp = (uBinary % vDigitsPower[uCodeDigits]); + if(bAddChecksum) + uOtp = ((uOtp * 10) + CalculateChecksum(uOtp, uCodeDigits)); + + uint uDigits = (bAddChecksum ? (uCodeDigits + 1) : uCodeDigits); + return uOtp.ToString().PadLeft((int)uDigits, '0'); + } + + private static readonly uint[] vDoubleDigits = new uint[]{ 0, 2, 4, 6, 8, + 1, 3, 5, 7, 9 }; + + private static uint CalculateChecksum(uint uNum, uint uDigits) + { + bool bDoubleDigit = true; + uint uTotal = 0; + + while(0 < uDigits--) + { + uint uDigit = (uNum % 10); + uNum /= 10; + + if(bDoubleDigit) uDigit = vDoubleDigits[uDigit]; + + uTotal += uDigit; + bDoubleDigit = !bDoubleDigit; + } + + uint uResult = (uTotal % 10); + if(uResult != 0) uResult = 10 - uResult; + + return uResult; + } + } +} +#endif diff --git a/src/KeePassLib2Android/Cryptography/PasswordGenerator/CharSetBasedGenerator.cs b/src/KeePassLib2Android/Cryptography/PasswordGenerator/CharSetBasedGenerator.cs new file mode 100644 index 00000000..89fb92e0 --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/PasswordGenerator/CharSetBasedGenerator.cs @@ -0,0 +1,65 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +using KeePassLib.Security; +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.PasswordGenerator +{ + internal static class CharSetBasedGenerator + { + internal static PwgError Generate(out ProtectedString psOut, + PwProfile pwProfile, CryptoRandomStream crsRandomSource) + { + psOut = ProtectedString.Empty; + if(pwProfile.Length == 0) return PwgError.Success; + + PwCharSet pcs = new PwCharSet(pwProfile.CharSet.ToString()); + char[] vGenerated = new char[pwProfile.Length]; + + PwGenerator.PrepareCharSet(pcs, pwProfile); + + for(int nIndex = 0; nIndex < (int)pwProfile.Length; ++nIndex) + { + char ch = PwGenerator.GenerateCharacter(pwProfile, pcs, + crsRandomSource); + + if(ch == char.MinValue) + { + Array.Clear(vGenerated, 0, vGenerated.Length); + return PwgError.TooFewCharacters; + } + + vGenerated[nIndex] = ch; + } + + byte[] pbUtf8 = StrUtil.Utf8.GetBytes(vGenerated); + psOut = new ProtectedString(true, pbUtf8); + MemUtil.ZeroByteArray(pbUtf8); + Array.Clear(vGenerated, 0, vGenerated.Length); + + return PwgError.Success; + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/PasswordGenerator/CustomPwGenerator.cs b/src/KeePassLib2Android/Cryptography/PasswordGenerator/CustomPwGenerator.cs new file mode 100644 index 00000000..bfd5c271 --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/PasswordGenerator/CustomPwGenerator.cs @@ -0,0 +1,66 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; + +using KeePassLib; +using KeePassLib.Security; + +namespace KeePassLib.Cryptography.PasswordGenerator +{ + public abstract class CustomPwGenerator + { + /// + /// Each custom password generation algorithm must have + /// its own unique UUID. + /// + public abstract PwUuid Uuid { get; } + + /// + /// Displayable name of the password generation algorithm. + /// + public abstract string Name { get; } + + public virtual bool SupportsOptions + { + get { return false; } + } + + /// + /// Password generation function. + /// + /// Password generation options chosen + /// by the user. This may be null, if the default + /// options should be used. + /// Source that the algorithm + /// can use to generate random numbers. + /// Generated password or null in case + /// of failure. If returning null, the caller assumes + /// that an error message has already been shown to the user. + public abstract ProtectedString Generate(PwProfile prf, + CryptoRandomStream crsRandomSource); + + public virtual string GetOptions(string strCurrentOptions) + { + return string.Empty; + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/PasswordGenerator/CustomPwGeneratorPool.cs b/src/KeePassLib2Android/Cryptography/PasswordGenerator/CustomPwGeneratorPool.cs new file mode 100644 index 00000000..c3cf9c1e --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/PasswordGenerator/CustomPwGeneratorPool.cs @@ -0,0 +1,110 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace KeePassLib.Cryptography.PasswordGenerator +{ + public sealed class CustomPwGeneratorPool : IEnumerable + { + private List m_vGens = new List(); + + public int Count + { + get { return m_vGens.Count; } + } + + public CustomPwGeneratorPool() + { + } + + IEnumerator IEnumerable.GetEnumerator() + { + return m_vGens.GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + return m_vGens.GetEnumerator(); + } + + public void Add(CustomPwGenerator pwg) + { + if(pwg == null) throw new ArgumentNullException("pwg"); + + PwUuid uuid = pwg.Uuid; + if(uuid == null) throw new ArgumentException(); + + int nIndex = FindIndex(uuid); + + if(nIndex >= 0) m_vGens[nIndex] = pwg; // Replace + else m_vGens.Add(pwg); + } + + public CustomPwGenerator Find(PwUuid uuid) + { + if(uuid == null) throw new ArgumentNullException("uuid"); + + foreach(CustomPwGenerator pwg in m_vGens) + { + if(uuid.EqualsValue(pwg.Uuid)) return pwg; + } + + return null; + } + + public CustomPwGenerator Find(string strName) + { + if(strName == null) throw new ArgumentNullException("strName"); + + foreach(CustomPwGenerator pwg in m_vGens) + { + if(pwg.Name == strName) return pwg; + } + + return null; + } + + private int FindIndex(PwUuid uuid) + { + if(uuid == null) throw new ArgumentNullException("uuid"); + + for(int i = 0; i < m_vGens.Count; ++i) + { + if(uuid.EqualsValue(m_vGens[i].Uuid)) return i; + } + + return -1; + } + + public bool Remove(PwUuid uuid) + { + if(uuid == null) throw new ArgumentNullException("uuid"); + + int nIndex = FindIndex(uuid); + if(nIndex < 0) return false; + + m_vGens.RemoveAt(nIndex); + return true; + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/PasswordGenerator/PatternBasedGenerator.cs b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PatternBasedGenerator.cs new file mode 100644 index 00000000..3aa08104 --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PatternBasedGenerator.cs @@ -0,0 +1,173 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +using KeePassLib.Security; +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.PasswordGenerator +{ + internal static class PatternBasedGenerator + { + internal static PwgError Generate(out ProtectedString psOut, + PwProfile pwProfile, CryptoRandomStream crsRandomSource) + { + psOut = ProtectedString.Empty; + LinkedList vGenerated = new LinkedList(); + PwCharSet pcsCurrent = new PwCharSet(); + PwCharSet pcsCustom = new PwCharSet(); + PwCharSet pcsUsed = new PwCharSet(); + bool bInCharSetDef = false; + + string strPattern = ExpandPattern(pwProfile.Pattern); + if(strPattern.Length == 0) return PwgError.Success; + + CharStream csStream = new CharStream(strPattern); + char ch = csStream.ReadChar(); + + while(ch != char.MinValue) + { + pcsCurrent.Clear(); + + bool bGenerateChar = false; + + if(ch == '\\') + { + ch = csStream.ReadChar(); + if(ch == char.MinValue) // Backslash at the end + { + vGenerated.AddLast('\\'); + break; + } + + if(bInCharSetDef) pcsCustom.Add(ch); + else + { + vGenerated.AddLast(ch); + pcsUsed.Add(ch); + } + } + else if(ch == '[') + { + pcsCustom.Clear(); + bInCharSetDef = true; + } + else if(ch == ']') + { + pcsCurrent.Add(pcsCustom.ToString()); + + bInCharSetDef = false; + bGenerateChar = true; + } + else if(bInCharSetDef) + { + if(pcsCustom.AddCharSet(ch) == false) + pcsCustom.Add(ch); + } + else if(pcsCurrent.AddCharSet(ch) == false) + { + vGenerated.AddLast(ch); + pcsUsed.Add(ch); + } + else bGenerateChar = true; + + if(bGenerateChar) + { + PwGenerator.PrepareCharSet(pcsCurrent, pwProfile); + + if(pwProfile.NoRepeatingCharacters) + pcsCurrent.Remove(pcsUsed.ToString()); + + char chGen = PwGenerator.GenerateCharacter(pwProfile, + pcsCurrent, crsRandomSource); + + if(chGen == char.MinValue) return PwgError.TooFewCharacters; + + vGenerated.AddLast(chGen); + pcsUsed.Add(chGen); + } + + ch = csStream.ReadChar(); + } + + if(vGenerated.Count == 0) return PwgError.Success; + + char[] vArray = new char[vGenerated.Count]; + vGenerated.CopyTo(vArray, 0); + + if(pwProfile.PatternPermutePassword) + PwGenerator.ShufflePassword(vArray, crsRandomSource); + + byte[] pbUtf8 = StrUtil.Utf8.GetBytes(vArray); + psOut = new ProtectedString(true, pbUtf8); + MemUtil.ZeroByteArray(pbUtf8); + Array.Clear(vArray, 0, vArray.Length); + vGenerated.Clear(); + + return PwgError.Success; + } + + private static string ExpandPattern(string strPattern) + { + Debug.Assert(strPattern != null); if(strPattern == null) return string.Empty; + string str = strPattern; + + while(true) + { + int nOpen = FindFirstUnescapedChar(str, '{'); + int nClose = FindFirstUnescapedChar(str, '}'); + + if((nOpen >= 0) && (nOpen < nClose)) + { + string strCount = str.Substring(nOpen + 1, nClose - nOpen - 1); + str = str.Remove(nOpen, nClose - nOpen + 1); + + uint uRepeat; + if(StrUtil.TryParseUInt(strCount, out uRepeat) && (nOpen >= 1)) + { + if(uRepeat == 0) + str = str.Remove(nOpen - 1, 1); + else + str = str.Insert(nOpen, new string(str[nOpen - 1], (int)uRepeat - 1)); + } + } + else break; + } + + return str; + } + + private static int FindFirstUnescapedChar(string str, char ch) + { + for(int i = 0; i < str.Length; ++i) + { + char chCur = str[i]; + + if(chCur == '\\') ++i; // Next is escaped, skip it + else if(chCur == ch) return i; + } + + return -1; + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwCharSet.cs b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwCharSet.cs new file mode 100644 index 00000000..9c5552a6 --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwCharSet.cs @@ -0,0 +1,318 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +namespace KeePassLib.Cryptography.PasswordGenerator +{ + public sealed class PwCharSet + { + public const string UpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + public const string LowerCase = "abcdefghijklmnopqrstuvwxyz"; + public const string Digits = "0123456789"; + + public const string UpperConsonants = "BCDFGHJKLMNPQRSTVWXYZ"; + public const string LowerConsonants = "bcdfghjklmnpqrstvwxyz"; + public const string UpperVowels = "AEIOU"; + public const string LowerVowels = "aeiou"; + + public const string Punctuation = @",.;:"; + public const string Brackets = @"[]{}()<>"; + + public const string PrintableAsciiSpecial = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; + + public const string UpperHex = "0123456789ABCDEF"; + public const string LowerHex = "0123456789abcdef"; + + public const string Invalid = "\t\r\n"; + public const string LookAlike = @"O0l1I|"; + + 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; + + /// + /// Create a new, empty character set collection object. + /// + public PwCharSet() + { + this.Initialize(true); + } + + public PwCharSet(string strCharSet) + { + this.Initialize(true); + this.Add(strCharSet); + } + + private PwCharSet(bool bFullInitialize) + { + this.Initialize(bFullInitialize); + } + + private void Initialize(bool bFullInitialize) + { + this.Clear(); + + if(bFullInitialize == false) return; + + StringBuilder sbHighAnsi = new StringBuilder(); + for(char ch = '~'; ch < 255; ++ch) + sbHighAnsi.Append(ch); + m_strHighAnsi = sbHighAnsi.ToString(); + + PwCharSet pcs = new PwCharSet(false); + pcs.AddRange('!', '/'); + pcs.AddRange(':', '@'); + pcs.AddRange('[', '`'); + pcs.Remove(@"-_ "); + pcs.Remove(PwCharSet.Brackets); + m_strSpecial = pcs.ToString(); + } + + /// + /// Number of characters in this set. + /// + public uint Size + { + get { return (uint)m_vChars.Count; } + } + + /// + /// Get a character of the set using an index. + /// + /// Index of the character to get. + /// Character at the specified position. If the index is invalid, + /// an ArgumentOutOfRangeException is thrown. + public char this[uint uPos] + { + get + { + if(uPos >= (uint)m_vChars.Count) + throw new ArgumentOutOfRangeException("uPos"); + + return m_vChars[(int)uPos]; + } + } + + public string SpecialChars { get { return m_strSpecial; } } + public string HighAnsiChars { get { return m_strHighAnsi; } } + + /// + /// Remove all characters from this set. + /// + public void Clear() + { + m_vChars.Clear(); + Array.Clear(m_vTab, 0, m_vTab.Length); + } + + public bool Contains(char ch) + { + return (((m_vTab[ch / 8] >> (ch % 8)) & 1) != char.MinValue); + } + + public bool Contains(string strCharacters) + { + Debug.Assert(strCharacters != null); + if(strCharacters == null) throw new ArgumentNullException("strCharacters"); + + foreach(char ch in strCharacters) + { + if(this.Contains(ch) == false) return false; + } + + return true; + } + + /// + /// Add characters to the set. + /// + /// Character to add. + public void Add(char ch) + { + if(ch == char.MinValue) { Debug.Assert(false); return; } + + if(this.Contains(ch) == false) + { + m_vChars.Add(ch); + m_vTab[ch / 8] |= (byte)(1 << (ch % 8)); + } + } + + /// + /// Add characters to the set. + /// + /// String containing characters to add. + public void Add(string strCharSet) + { + Debug.Assert(strCharSet != null); + if(strCharSet == null) throw new ArgumentNullException("strCharSet"); + + m_vChars.Capacity = m_vChars.Count + strCharSet.Length; + + foreach(char ch in strCharSet) + this.Add(ch); + } + + public void Add(string strCharSet1, string strCharSet2) + { + this.Add(strCharSet1); + this.Add(strCharSet2); + } + + public void Add(string strCharSet1, string strCharSet2, string strCharSet3) + { + this.Add(strCharSet1); + this.Add(strCharSet2); + this.Add(strCharSet3); + } + + public void AddRange(char chMin, char chMax) + { + m_vChars.Capacity = m_vChars.Count + (chMax - chMin) + 1; + + for(char ch = chMin; ch < chMax; ++ch) + this.Add(ch); + + this.Add(chMax); + } + + public bool AddCharSet(char chCharSetIdentifier) + { + bool bResult = true; + + switch(chCharSetIdentifier) + { + case 'a': this.Add(PwCharSet.LowerCase, PwCharSet.Digits); break; + case 'A': this.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, + 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; + default: bResult = false; break; + } + + return bResult; + } + + public bool Remove(char ch) + { + m_vTab[ch / 8] &= (byte)~(1 << (ch % 8)); + return m_vChars.Remove(ch); + } + + public bool Remove(string strCharacters) + { + Debug.Assert(strCharacters != null); + if(strCharacters == null) throw new ArgumentNullException("strCharacters"); + + bool bResult = true; + foreach(char ch in strCharacters) + { + if(!Remove(ch)) bResult = false; + } + + return bResult; + } + + public bool RemoveIfAllExist(string strCharacters) + { + Debug.Assert(strCharacters != null); + if(strCharacters == null) throw new ArgumentNullException("strCharacters"); + + if(this.Contains(strCharacters) == false) + return false; + + return this.Remove(strCharacters); + } + + /// + /// Convert the character set to a string containing all its characters. + /// + /// String containing all character set characters. + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + foreach(char ch in m_vChars) + sb.Append(ch); + + return sb.ToString(); + } + + public string PackAndRemoveCharRanges() + { + 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' : '_'); + + return sb.ToString(); + } + + public void UnpackCharRanges(string strRanges) + { + 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); + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwGenerator.cs b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwGenerator.cs new file mode 100644 index 00000000..7e0e2ca8 --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwGenerator.cs @@ -0,0 +1,146 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +using KeePassLib.Security; + +namespace KeePassLib.Cryptography.PasswordGenerator +{ + public enum PwgError + { + Success = 0, + Unknown = 1, + TooFewCharacters = 2, + UnknownAlgorithm = 3 + } + + /// + /// Utility functions for generating random passwords. + /// + public static class PwGenerator + { + public static PwgError Generate(out ProtectedString psOut, + PwProfile pwProfile, byte[] pbUserEntropy, + CustomPwGeneratorPool pwAlgorithmPool) + { + Debug.Assert(pwProfile != null); + if(pwProfile == null) throw new ArgumentNullException("pwProfile"); + + CryptoRandomStream crs = CreateCryptoStream(pbUserEntropy); + PwgError e = PwgError.Unknown; + + if(pwProfile.GeneratorType == PasswordGeneratorType.CharSet) + e = CharSetBasedGenerator.Generate(out psOut, pwProfile, crs); + else if(pwProfile.GeneratorType == PasswordGeneratorType.Pattern) + e = PatternBasedGenerator.Generate(out psOut, pwProfile, crs); + else if(pwProfile.GeneratorType == PasswordGeneratorType.Custom) + e = GenerateCustom(out psOut, pwProfile, crs, pwAlgorithmPool); + else { Debug.Assert(false); psOut = ProtectedString.Empty; } + + return e; + } + + private static CryptoRandomStream CreateCryptoStream(byte[] pbAdditionalEntropy) + { + byte[] pbKey = CryptoRandom.Instance.GetRandomBytes(256); + + // Mix in additional entropy + if((pbAdditionalEntropy != null) && (pbAdditionalEntropy.Length > 0)) + { + for(int nKeyPos = 0; nKeyPos < pbKey.Length; ++nKeyPos) + pbKey[nKeyPos] ^= pbAdditionalEntropy[nKeyPos % pbAdditionalEntropy.Length]; + } + + return new CryptoRandomStream(CrsAlgorithm.Salsa20, pbKey); + } + + internal static char GenerateCharacter(PwProfile pwProfile, + PwCharSet pwCharSet, CryptoRandomStream crsRandomSource) + { + if(pwCharSet.Size == 0) return char.MinValue; + + ulong uIndex = crsRandomSource.GetRandomUInt64(); + uIndex %= (ulong)pwCharSet.Size; + + char ch = pwCharSet[(uint)uIndex]; + + if(pwProfile.NoRepeatingCharacters) + pwCharSet.Remove(ch); + + return ch; + } + + internal static void PrepareCharSet(PwCharSet pwCharSet, PwProfile pwProfile) + { + pwCharSet.Remove(PwCharSet.Invalid); + + if(pwProfile.ExcludeLookAlike) pwCharSet.Remove(PwCharSet.LookAlike); + + if(pwProfile.ExcludeCharacters.Length > 0) + pwCharSet.Remove(pwProfile.ExcludeCharacters); + } + + internal static void ShufflePassword(char[] pPassword, + CryptoRandomStream crsRandomSource) + { + Debug.Assert(pPassword != null); if(pPassword == null) return; + Debug.Assert(crsRandomSource != null); if(crsRandomSource == null) return; + + if(pPassword.Length <= 1) return; // Nothing to shuffle + + for(int nSelect = 0; nSelect < pPassword.Length; ++nSelect) + { + ulong uRandomIndex = crsRandomSource.GetRandomUInt64(); + uRandomIndex %= (ulong)(pPassword.Length - nSelect); + + char chTemp = pPassword[nSelect]; + pPassword[nSelect] = pPassword[nSelect + (int)uRandomIndex]; + pPassword[nSelect + (int)uRandomIndex] = chTemp; + } + } + + private static PwgError GenerateCustom(out ProtectedString psOut, + PwProfile pwProfile, CryptoRandomStream crs, + CustomPwGeneratorPool pwAlgorithmPool) + { + psOut = ProtectedString.Empty; + + Debug.Assert(pwProfile.GeneratorType == PasswordGeneratorType.Custom); + if(pwAlgorithmPool == null) return PwgError.UnknownAlgorithm; + + string strID = pwProfile.CustomAlgorithmUuid; + if(string.IsNullOrEmpty(strID)) { Debug.Assert(false); return PwgError.UnknownAlgorithm; } + + byte[] pbUuid = Convert.FromBase64String(strID); + PwUuid uuid = new PwUuid(pbUuid); + CustomPwGenerator pwg = pwAlgorithmPool.Find(uuid); + if(pwg == null) { Debug.Assert(false); return PwgError.UnknownAlgorithm; } + + ProtectedString pwd = pwg.Generate(pwProfile.CloneDeep(), crs); + if(pwd == null) return PwgError.Unknown; + + psOut = pwd; + return PwgError.Success; + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwProfile.cs b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwProfile.cs new file mode 100644 index 00000000..df484aaa --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/PasswordGenerator/PwProfile.cs @@ -0,0 +1,277 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Xml.Serialization; +using System.ComponentModel; +using System.Diagnostics; + +using KeePassLib.Interfaces; +using KeePassLib.Security; +using KeePassLib.Utility; + +namespace KeePassLib.Cryptography.PasswordGenerator +{ + /// + /// Type of the password generator. Different types like generators + /// based on given patterns, based on character sets, etc. are + /// available. + /// + public enum PasswordGeneratorType + { + /// + /// Generator based on character spaces/sets, i.e. groups + /// of characters like lower-case, upper-case or numeric characters. + /// + CharSet = 0, + + /// + /// Password generation based on a pattern. The user has provided + /// a pattern, which describes how the generated password has to + /// look like. + /// + Pattern = 1, + + Custom = 2 + } + + public sealed class PwProfile : IDeepCloneable + { + private string m_strName = string.Empty; + [DefaultValue("")] + public string Name + { + get { return m_strName; } + set { m_strName = value; } + } + + private PasswordGeneratorType m_type = PasswordGeneratorType.CharSet; + public PasswordGeneratorType GeneratorType + { + get { return m_type; } + set { m_type = value; } + } + + private bool m_bUserEntropy = false; + [DefaultValue(false)] + public bool CollectUserEntropy + { + get { return m_bUserEntropy; } + set { m_bUserEntropy = value; } + } + + private uint m_uLength = 20; + public uint Length + { + get { return m_uLength; } + set { m_uLength = value; } + } + + private PwCharSet m_pwCharSet = new PwCharSet(PwCharSet.UpperCase + + PwCharSet.LowerCase + PwCharSet.Digits); + [XmlIgnore] + public PwCharSet CharSet + { + get { return m_pwCharSet; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_pwCharSet = value; + } + } + + private string m_strCharSetRanges = string.Empty; + [DefaultValue("")] + public string CharSetRanges + { + get { this.UpdateCharSet(true); return m_strCharSetRanges; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strCharSetRanges = value; + this.UpdateCharSet(false); + } + } + + private string m_strCharSetAdditional = string.Empty; + [DefaultValue("")] + public string CharSetAdditional + { + get { this.UpdateCharSet(true); return m_strCharSetAdditional; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strCharSetAdditional = value; + this.UpdateCharSet(false); + } + } + + private string m_strPattern = string.Empty; + [DefaultValue("")] + public string Pattern + { + get { return m_strPattern; } + set { m_strPattern = value; } + } + + private bool m_bPatternPermute = false; + [DefaultValue(false)] + public bool PatternPermutePassword + { + get { return m_bPatternPermute; } + set { m_bPatternPermute = value; } + } + + private bool m_bNoLookAlike = false; + [DefaultValue(false)] + public bool ExcludeLookAlike + { + get { return m_bNoLookAlike; } + set { m_bNoLookAlike = value; } + } + + private bool m_bNoRepeat = false; + [DefaultValue(false)] + public bool NoRepeatingCharacters + { + get { return m_bNoRepeat; } + set { m_bNoRepeat = value; } + } + + private string m_strExclude = string.Empty; + [DefaultValue("")] + public string ExcludeCharacters + { + get { return m_strExclude; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strExclude = value; + } + } + + private string m_strCustomID = string.Empty; + [DefaultValue("")] + public string CustomAlgorithmUuid + { + get { return m_strCustomID; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strCustomID = value; + } + } + + private string m_strCustomOpt = string.Empty; + [DefaultValue("")] + public string CustomAlgorithmOptions + { + get { return m_strCustomOpt; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strCustomOpt = value; + } + } + + public PwProfile() + { + } + + public PwProfile CloneDeep() + { + PwProfile p = new PwProfile(); + + p.m_strName = m_strName; + p.m_type = m_type; + p.m_bUserEntropy = m_bUserEntropy; + p.m_uLength = m_uLength; + p.m_pwCharSet = new PwCharSet(m_pwCharSet.ToString()); + p.m_strCharSetRanges = m_strCharSetRanges; + p.m_strCharSetAdditional = m_strCharSetAdditional; + p.m_strPattern = m_strPattern; + p.m_bPatternPermute = m_bPatternPermute; + p.m_bNoLookAlike = m_bNoLookAlike; + p.m_bNoRepeat = m_bNoRepeat; + p.m_strExclude = m_strExclude; + p.m_strCustomID = m_strCustomID; + p.m_strCustomOpt = m_strCustomOpt; + + return p; + } + + private void UpdateCharSet(bool bSetXml) + { + if(bSetXml) + { + PwCharSet pcs = new PwCharSet(m_pwCharSet.ToString()); + m_strCharSetRanges = pcs.PackAndRemoveCharRanges(); + m_strCharSetAdditional = pcs.ToString(); + } + else + { + PwCharSet pcs = new PwCharSet(m_strCharSetAdditional); + pcs.UnpackCharRanges(m_strCharSetRanges); + m_pwCharSet = pcs; + } + } + + public static PwProfile DeriveFromPassword(ProtectedString psPassword) + { + PwProfile pp = new PwProfile(); + Debug.Assert(psPassword != null); if(psPassword == null) return pp; + + byte[] pbUtf8 = psPassword.ReadUtf8(); + char[] vChars = StrUtil.Utf8.GetChars(pbUtf8); + + pp.GeneratorType = PasswordGeneratorType.CharSet; + pp.Length = (uint)vChars.Length; + + PwCharSet pcs = pp.CharSet; + pcs.Clear(); + + foreach(char ch in vChars) + { + 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(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 pcs.Add(ch); + } + + Array.Clear(vChars, 0, vChars.Length); + MemUtil.ZeroByteArray(pbUtf8); + return pp; + } + + public bool HasSecurityReducingOption() + { + return (m_bNoLookAlike || m_bNoRepeat || (m_strExclude.Length > 0)); + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/PopularPasswords.cs b/src/KeePassLib2Android/Cryptography/PopularPasswords.cs new file mode 100644 index 00000000..7e4a70f1 --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/PopularPasswords.cs @@ -0,0 +1,540 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using 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 + + // 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 + }; + + public static bool IsPopularPassword(char[] vPassword) + { + Debug.Assert(PpcTable.Length == (PpcTableSize / 64)); + + if(vPassword == null) throw new ArgumentNullException("vPassword"); + if(vPassword.Length == 0) return false; + + foreach(char ch in vPassword) + { + if(!IsPopularChar(ch)) return false; + } + + byte[] pbUtf8 = StrUtil.Utf8.GetBytes(vPassword); + + int[] vIndices = GetTableIndices(pbUtf8, PpcTableSize); + Array.Clear(pbUtf8, 0, pbUtf8.Length); + + foreach(int iIndex in vIndices) + { + if(!GetTableBit(PpcTable, iIndex)) return false; + } + + return true; + } + + private static bool IsPopularChar(char ch) + { + return (((ch >= 'A') && (ch <= 'Z')) || ((ch >= 'a') && (ch <= 'z')) || + ((ch >= '0') && (ch <= '9')) || (ch == '_') || (ch == '!')); + } + + 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)) + { + string strPassword = sbPassword.ToString(); + strPassword = strPassword.ToLower(); + + if(strPassword.Length > 3) + { + if(vPasswords.IndexOf(strPassword) < 0) + vPasswords.Add(strPassword); + } + + 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); + } + } + + 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()); + } +#endif + } +} +#endif diff --git a/src/KeePassLib2Android/Cryptography/QualityEstimation.cs b/src/KeePassLib2Android/Cryptography/QualityEstimation.cs new file mode 100644 index 00000000..0d5e6297 --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/QualityEstimation.cs @@ -0,0 +1,145 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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 +{ + /// + /// A class that offers static functions to estimate the quality of + /// passwords. + /// + public static class QualityEstimation + { + private enum CharSpaceBits : uint + { + Control = 32, + Alpha = 26, + Number = 10, + Special = 33, + High = 112 + } + + /// + /// Estimate the quality of a password. + /// + /// 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"); + + 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; + + for(int i = 0; i < vPasswordChars.Length; ++i) // Get character types + { + char tch = vPasswordChars[i]; + + 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; + + double dblDiffFactor = 1.0; + if(i >= 1) + { + int iDiff = (int)tch - (int)vPasswordChars[i - 1]; + + 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); + } + else + { + vCharCounts.Add(tch, 1); + dblEffectiveLength += dblDiffFactor; + } + } + + 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); + } + + /// + /// Estimate the quality of a password. + /// + /// Password to check, UTF-8 encoded. + /// Estimated bit-strength of the password. + public static uint EstimatePasswordBits(byte[] pbUnprotectedUtf8) + { + if(pbUnprotectedUtf8 == null) { Debug.Assert(false); return 0; } + + char[] vChars = StrUtil.Utf8.GetChars(pbUnprotectedUtf8); + uint uResult = EstimatePasswordBits(vChars); + Array.Clear(vChars, 0, vChars.Length); + + return uResult; + } + } +} diff --git a/src/KeePassLib2Android/Cryptography/SelfTest.cs b/src/KeePassLib2Android/Cryptography/SelfTest.cs new file mode 100644 index 00000000..6ebf07b9 --- /dev/null +++ b/src/KeePassLib2Android/Cryptography/SelfTest.cs @@ -0,0 +1,402 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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; +using System.Security.Cryptography; +using System.Text; + +using KeePassLib.Cryptography.Cipher; +using KeePassLib.Keys; +using KeePassLib.Native; +using KeePassLib.Utility; +using KeePassLib.Resources; +using KeePassLib.Security; + +namespace KeePassLib.Cryptography +{ +/* #pragma warning disable 1591 + /// + /// Return values of the SelfTest.Perform method. + /// + public enum SelfTestResult + { + Success = 0, + RijndaelEcbError = 1, + Salsa20Error = 2, + NativeKeyTransformationError = 3 + } +#pragma warning restore 1591 */ + + /// + /// Class containing self-test methods. + /// + public static class SelfTest + { + /// + /// Perform a self-test. + /// + public static void Perform() + { + TestFipsComplianceProblems(); // Must be the first test + + TestRijndael(); + TestSalsa20(); + + TestNativeKeyTransform(); + + TestHmacOtp(); + + TestProtectedObjects(); + TestMemUtil(); + TestStrUtil(); + TestUrlUtil(); + + Debug.Assert((int)PwIcon.World == 1); + Debug.Assert((int)PwIcon.Warning == 2); + Debug.Assert((int)PwIcon.BlackBerry == 68); + } + + internal static void TestFipsComplianceProblems() + { + try { new RijndaelManaged(); } + catch(Exception exAes) + { + throw new SecurityException("AES/Rijndael: " + exAes.Message); + } + + try { new SHA256Managed(); } + catch(Exception exSha256) + { + throw new SecurityException("SHA-256: " + exSha256.Message); + } + } + + private static void TestRijndael() + { + // Test vector (official ECB test vector #356) + byte[] pbIV = new byte[16]; + byte[] pbTestKey = new byte[32]; + byte[] pbTestData = new byte[16]; + byte[] pbReferenceCT = new byte[16] { + 0x75, 0xD1, 0x1B, 0x0E, 0x3A, 0x68, 0xC4, 0x22, + 0x3D, 0x88, 0xDB, 0xF0, 0x17, 0x97, 0x7D, 0xD7 }; + int i; + + for(i = 0; i < 16; ++i) pbIV[i] = 0; + for(i = 0; i < 32; ++i) pbTestKey[i] = 0; + for(i = 0; i < 16; ++i) pbTestData[i] = 0; + pbTestData[0] = 0x04; + + RijndaelManaged r = new RijndaelManaged(); + + if(r.BlockSize != 128) // AES block size + { + Debug.Assert(false); + r.BlockSize = 128; + } + + r.IV = pbIV; + r.KeySize = 256; + r.Key = pbTestKey; + r.Mode = CipherMode.ECB; + ICryptoTransform iCrypt = r.CreateEncryptor(); + + iCrypt.TransformBlock(pbTestData, 0, 16, pbTestData, 0); + + if(!MemUtil.ArraysEqual(pbTestData, pbReferenceCT)) + throw new SecurityException(KLRes.EncAlgorithmAes + "."); + } + + private static void TestSalsa20() + { + // Test values from official set 6, vector 3 + 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, + 0xD7, 0x2A, 0x7D, 0xD0, 0x23, 0x76, 0xC9, 0x1C + }; + byte[] pbIV = new byte[8] { 0x28, 0x8F, 0xF6, 0x5D, + 0xC4, 0x2B, 0x92, 0xF9 }; + byte[] pbExpected = new byte[16] { + 0x5E, 0x5E, 0x71, 0xF9, 0x01, 0x99, 0x34, 0x03, + 0x04, 0xAB, 0xB2, 0x2A, 0x37, 0xB6, 0x62, 0x5B + }; + + byte[] pb = new byte[16]; + Salsa20Cipher c = new Salsa20Cipher(pbKey, pbIV); + c.Encrypt(pb, pb.Length, false); + if(!MemUtil.ArraysEqual(pb, pbExpected)) + throw new SecurityException("Salsa20."); + +#if DEBUG + // Extended test in debug mode + byte[] pbExpected2 = new byte[16] { + 0xAB, 0xF3, 0x9A, 0x21, 0x0E, 0xEE, 0x89, 0x59, + 0x8B, 0x71, 0x33, 0x37, 0x70, 0x56, 0xC2, 0xFE + }; + byte[] pbExpected3 = new byte[16] { + 0x1B, 0xA8, 0x9D, 0xBD, 0x3F, 0x98, 0x83, 0x97, + 0x28, 0xF5, 0x67, 0x91, 0xD5, 0xB7, 0xCE, 0x23 + }; + + Random r = new Random(); + int nPos = Salsa20ToPos(c, r, pb.Length, 65536); + c.Encrypt(pb, pb.Length, false); + 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); + if(!MemUtil.ArraysEqual(pb, pbExpected3)) + throw new SecurityException("Salsa20-3."); +#endif + } + +#if DEBUG + private static int Salsa20ToPos(Salsa20Cipher c, Random r, int nPos, + int nTargetPos) + { + byte[] pb = new byte[512]; + + while(nPos < nTargetPos) + { + int x = r.Next(1, 513); + int nGen = Math.Min(nTargetPos - nPos, x); + c.Encrypt(pb, nGen, r.Next(0, 2) == 0); + nPos += nGen; + } + + return nTargetPos; + } +#endif + + private static void TestNativeKeyTransform() + { +#if DEBUG + byte[] pbOrgKey = CryptoRandom.Instance.GetRandomBytes(32); + byte[] pbSeed = CryptoRandom.Instance.GetRandomBytes(32); + ulong uRounds = (ulong)((new Random()).Next(1, 0x3FFF)); + + byte[] pbManaged = new byte[32]; + Array.Copy(pbOrgKey, pbManaged, 32); + if(CompositeKey.TransformKeyManaged(pbManaged, pbSeed, uRounds) == false) + throw new SecurityException("Managed transform."); + + byte[] pbNative = new byte[32]; + Array.Copy(pbOrgKey, pbNative, 32); + if(NativeLib.TransformKey256(pbNative, pbSeed, uRounds) == false) + return; // Native library not available ("success") + + if(!MemUtil.ArraysEqual(pbManaged, pbNative)) + throw new SecurityException("Native transform."); +#endif + } + + private static void TestMemUtil() + { +#if DEBUG + Random r = new Random(); + byte[] pb = CryptoRandom.Instance.GetRandomBytes((uint)r.Next( + 0, 0x2FFFF)); + + byte[] pbCompressed = MemUtil.Compress(pb); + if(!MemUtil.ArraysEqual(MemUtil.Decompress(pbCompressed), pb)) + throw new InvalidOperationException("GZip"); + + pb = Encoding.ASCII.GetBytes("012345678901234567890a"); + byte[] pbN = Encoding.ASCII.GetBytes("9012"); + if(MemUtil.IndexOf(pb, pbN) != 9) + throw new InvalidOperationException("MemUtil-1"); + pbN = Encoding.ASCII.GetBytes("01234567890123"); + if(MemUtil.IndexOf(pb, pbN) != 0) + throw new InvalidOperationException("MemUtil-2"); + pbN = Encoding.ASCII.GetBytes("a"); + if(MemUtil.IndexOf(pb, pbN) != 21) + throw new InvalidOperationException("MemUtil-3"); + pbN = Encoding.ASCII.GetBytes("0a"); + if(MemUtil.IndexOf(pb, pbN) != 20) + throw new InvalidOperationException("MemUtil-4"); + pbN = Encoding.ASCII.GetBytes("1"); + if(MemUtil.IndexOf(pb, pbN) != 1) + throw new InvalidOperationException("MemUtil-5"); + pbN = Encoding.ASCII.GetBytes("b"); + if(MemUtil.IndexOf(pb, pbN) >= 0) + throw new InvalidOperationException("MemUtil-6"); + pbN = Encoding.ASCII.GetBytes("012b"); + if(MemUtil.IndexOf(pb, pbN) >= 0) + throw new InvalidOperationException("MemUtil-7"); +#endif + } + + private static void TestHmacOtp() + { +#if (DEBUG && !KeePassLibSD) + byte[] pbSecret = Encoding.ASCII.GetBytes("12345678901234567890"); + string[] vExp = new string[]{ "755224", "287082", "359152", + "969429", "338314", "254676", "287922", "162583", "399871", + "520489" }; + + for(int i = 0; i < vExp.Length; ++i) + { + if(HmacOtp.Generate(pbSecret, (ulong)i, 6, false, -1) != vExp[i]) + throw new InvalidOperationException("HmacOtp"); + } +#endif + } + + private static void TestProtectedObjects() + { +#if DEBUG + byte[] pbData = Encoding.ASCII.GetBytes("Test Test Test Test"); + ProtectedBinary pb = new ProtectedBinary(true, pbData); + if(!pb.IsProtected) throw new SecurityException("ProtectedBinary-1"); + + byte[] pbDec = pb.ReadData(); + if(!MemUtil.ArraysEqual(pbData, pbDec)) + 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"); + ProtectedBinary pb2 = new ProtectedBinary(true, pbData2); + ProtectedBinary pb3 = new ProtectedBinary(true, pbData3); + if(!pb.Equals(pb2)) throw new SecurityException("ProtectedBinary-4"); + if(pb.Equals(pb3)) throw new SecurityException("ProtectedBinary-5"); + if(pb2.Equals(pb3)) throw new SecurityException("ProtectedBinary-6"); + + if(pb.GetHashCode() != pb2.GetHashCode()) + throw new SecurityException("ProtectedBinary-7"); + if(!((object)pb).Equals((object)pb2)) + throw new SecurityException("ProtectedBinary-8"); + if(((object)pb).Equals((object)pb3)) + throw new SecurityException("ProtectedBinary-9"); + if(((object)pb2).Equals((object)pb3)) + throw new SecurityException("ProtectedBinary-10"); + + ProtectedString ps = new ProtectedString(); + if(ps.Length != 0) throw new SecurityException("ProtectedString-1"); + if(!ps.IsEmpty) throw new SecurityException("ProtectedString-2"); + if(ps.ReadString().Length != 0) + throw new SecurityException("ProtectedString-3"); + + ps = new ProtectedString(true, "Test"); + ProtectedString ps2 = new ProtectedString(true, + StrUtil.Utf8.GetBytes("Test")); + if(ps.IsEmpty) throw new SecurityException("ProtectedString-4"); + pbData = ps.ReadUtf8(); + pbData2 = ps2.ReadUtf8(); + if(!MemUtil.ArraysEqual(pbData, pbData2)) + throw new SecurityException("ProtectedString-5"); + if(pbData.Length != 4) + throw new SecurityException("ProtectedString-6"); + if(ps.ReadString() != ps2.ReadString()) + throw new SecurityException("ProtectedString-7"); + pbData = ps.ReadUtf8(); + pbData2 = ps2.ReadUtf8(); + if(!MemUtil.ArraysEqual(pbData, pbData2)) + throw new SecurityException("ProtectedString-8"); + if(!ps.IsProtected) throw new SecurityException("ProtectedString-9"); + if(!ps2.IsProtected) throw new SecurityException("ProtectedString-10"); +#endif + } + + private static void TestStrUtil() + { +#if DEBUG + string[] vSeps = new string[]{ "ax", "b", "c" }; + const string str1 = "axbqrstcdeax"; + List v1 = StrUtil.SplitWithSep(str1, vSeps, true); + + if(v1.Count != 9) throw new InvalidOperationException("StrUtil-1"); + if(v1[0].Length > 0) throw new InvalidOperationException("StrUtil-2"); + if(!v1[1].Equals("ax")) throw new InvalidOperationException("StrUtil-3"); + if(v1[2].Length > 0) throw new InvalidOperationException("StrUtil-4"); + if(!v1[3].Equals("b")) throw new InvalidOperationException("StrUtil-5"); + if(!v1[4].Equals("qrst")) throw new InvalidOperationException("StrUtil-6"); + if(!v1[5].Equals("c")) throw new InvalidOperationException("StrUtil-7"); + if(!v1[6].Equals("de")) throw new InvalidOperationException("StrUtil-8"); + if(!v1[7].Equals("ax")) throw new InvalidOperationException("StrUtil-9"); + if(v1[8].Length > 0) throw new InvalidOperationException("StrUtil-10"); + + const string str2 = "12ab56"; + List v2 = StrUtil.SplitWithSep(str2, new string[]{ "AB" }, false); + if(v2.Count != 3) throw new InvalidOperationException("StrUtil-11"); + if(!v2[0].Equals("12")) throw new InvalidOperationException("StrUtil-12"); + if(!v2[1].Equals("AB")) throw new InvalidOperationException("StrUtil-13"); + if(!v2[2].Equals("56")) throw new InvalidOperationException("StrUtil-14"); + + List v3 = StrUtil.SplitWithSep("pqrs", vSeps, false); + if(v3.Count != 1) throw new InvalidOperationException("StrUtil-15"); + if(!v3[0].Equals("pqrs")) throw new InvalidOperationException("StrUtil-16"); + + if(StrUtil.VersionToString(0x000F000E000D000CUL) != "15.14.13.12") + throw new InvalidOperationException("StrUtil-V1"); + if(StrUtil.VersionToString(0x00FF000E00010000UL) != "255.14.1") + throw new InvalidOperationException("StrUtil-V2"); + if(StrUtil.VersionToString(0x000F00FF00000000UL) != "15.255") + throw new InvalidOperationException("StrUtil-V3"); + if(StrUtil.VersionToString(0x00FF000000000000UL) != "255") + throw new InvalidOperationException("StrUtil-V4"); + if(StrUtil.VersionToString(0x00FF000000000000UL, true) != "255.0") + throw new InvalidOperationException("StrUtil-V5"); + if(StrUtil.VersionToString(0x0000000000070000UL, true) != "0.0.7") + throw new InvalidOperationException("StrUtil-V6"); +#endif + } + + private static void TestUrlUtil() + { +#if DEBUG + if(UrlUtil.GetHost(@"scheme://domain:port/path?query_string#fragment_id") != + "domain") + throw new InvalidOperationException("UrlUtil-H1"); + if(UrlUtil.GetHost(@"http://example.org:80") != "example.org") + throw new InvalidOperationException("UrlUtil-H2"); + if(UrlUtil.GetHost(@"mailto:bob@example.com") != "example.com") + throw new InvalidOperationException("UrlUtil-H3"); + if(UrlUtil.GetHost(@"ftp://asmith@ftp.example.org") != "ftp.example.org") + throw new InvalidOperationException("UrlUtil-H4"); + if(UrlUtil.GetHost(@"scheme://username:password@domain:port/path?query_string#fragment_id") != + "domain") + throw new InvalidOperationException("UrlUtil-H5"); + if(UrlUtil.GetHost(@"bob@example.com") != "example.com") + throw new InvalidOperationException("UrlUtil-H6"); + if(UrlUtil.GetHost(@"s://u:p@d.tld:p/p?q#f") != "d.tld") + throw new InvalidOperationException("UrlUtil-H7"); + + if(NativeLib.IsUnix()) return; + + string strBase = "\\\\HOMESERVER\\Apps\\KeePass\\KeePass.exe"; + string strDoc = "\\\\HOMESERVER\\Documents\\KeePass\\NewDatabase.kdbx"; + string strRel = "..\\..\\Documents\\KeePass\\NewDatabase.kdbx"; + + string str = UrlUtil.MakeRelativePath(strBase, strDoc); + if(!str.Equals(strRel)) throw new InvalidOperationException("UrlUtil-R1"); + + str = UrlUtil.MakeAbsolutePath(strBase, strRel); + if(!str.Equals(strDoc)) throw new InvalidOperationException("UrlUtil-R2"); +#endif + } + } +} diff --git a/src/KeePassLib2Android/Delegates/Handlers.cs b/src/KeePassLib2Android/Delegates/Handlers.cs new file mode 100644 index 00000000..770e32ed --- /dev/null +++ b/src/KeePassLib2Android/Delegates/Handlers.cs @@ -0,0 +1,49 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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; + +namespace KeePassLib.Delegates +{ + /// + /// Function definition of a method that performs an action on a group. + /// When traversing the internal tree, this function will be invoked + /// for all visited groups. + /// + /// Currently visited group. + /// You must return true if you want to continue the + /// traversal. If you want to immediately stop the whole traversal, + /// return false. + public delegate bool GroupHandler(PwGroup pg); + + /// + /// Function definition of a method that performs an action on an entry. + /// When traversing the internal tree, this function will be invoked + /// for all visited entries. + /// + /// Currently visited entry. + /// You must return true if you want to continue the + /// traversal. If you want to immediately stop the whole traversal, + /// return false. + public delegate bool EntryHandler(PwEntry pe); + + public delegate void VoidDelegate(); + + public delegate string StrPwEntryDelegate(string str, PwEntry pe); +} diff --git a/src/KeePassLib2Android/Interfaces/IDeepCloneable.cs b/src/KeePassLib2Android/Interfaces/IDeepCloneable.cs new file mode 100644 index 00000000..5f80f82d --- /dev/null +++ b/src/KeePassLib2Android/Interfaces/IDeepCloneable.cs @@ -0,0 +1,37 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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; + +namespace KeePassLib.Interfaces +{ + /// + /// Interface for objects that are deeply cloneable. + /// + /// Reference type. + public interface IDeepCloneable where T : class + { + /// + /// Deeply clone the object. + /// + /// Cloned object. + T CloneDeep(); + } +} diff --git a/src/KeePassLib2Android/Interfaces/IStatusLogger.cs b/src/KeePassLib2Android/Interfaces/IStatusLogger.cs new file mode 100644 index 00000000..036ec6a7 --- /dev/null +++ b/src/KeePassLib2Android/Interfaces/IStatusLogger.cs @@ -0,0 +1,105 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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; + +namespace KeePassLib.Interfaces +{ + /// + /// Status message types. + /// + public enum LogStatusType + { + /// + /// Default type: simple information type. + /// + Info = 0, + + /// + /// Warning message. + /// + Warning, + + /// + /// Error message. + /// + Error, + + /// + /// Additional information. Depends on lines above. + /// + AdditionalInfo + } + + /// + /// Status logging interface. + /// + public interface IStatusLogger + { + /// + /// Function which needs to be called when logging is started. + /// + /// This string should roughly describe + /// the operation, of which the status is logged. + /// Specifies whether the + /// operation is written to the log or not. + void StartLogging(string strOperation, bool bWriteOperationToLog); + + /// + /// Function which needs to be called when logging is ended + /// (i.e. when no more messages will be logged and when the + /// percent value won't change any more). + /// + void EndLogging(); + + /// + /// Set the current progress in percent. + /// + /// Percent of work finished. + /// Returns true if the caller should continue + /// the current work. + bool SetProgress(uint uPercent); + + /// + /// Set the current status text. + /// + /// Status text. + /// Type of the message. + /// Returns true if the caller should continue + /// the current work. + bool SetText(string strNewText, LogStatusType lsType); + + /// + /// Check if the user cancelled the current work. + /// + /// Returns true if the caller should continue + /// the current work. + bool ContinueWork(); + } + + public sealed class NullStatusLogger : IStatusLogger + { + public void StartLogging(string strOperation, bool bWriteOperationToLog) { } + public void EndLogging() { } + public bool SetProgress(uint uPercent) { return true; } + public bool SetText(string strNewText, LogStatusType lsType) { return true; } + public bool ContinueWork() { return true; } + } +} diff --git a/src/KeePassLib2Android/Interfaces/IStructureItem.cs b/src/KeePassLib2Android/Interfaces/IStructureItem.cs new file mode 100644 index 00000000..fb2f303f --- /dev/null +++ b/src/KeePassLib2Android/Interfaces/IStructureItem.cs @@ -0,0 +1,37 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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; + +namespace KeePassLib.Interfaces +{ + public interface IStructureItem : ITimeLogger // Provides LocationChanged + { + PwUuid Uuid + { + get; + set; + } + + PwGroup ParentGroup + { + get; + } + } +} diff --git a/src/KeePassLib2Android/Interfaces/ITimeLogger.cs b/src/KeePassLib2Android/Interfaces/ITimeLogger.cs new file mode 100644 index 00000000..86a6b85d --- /dev/null +++ b/src/KeePassLib2Android/Interfaces/ITimeLogger.cs @@ -0,0 +1,105 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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; + +namespace KeePassLib.Interfaces +{ + /// + /// Interface for objects that support various times (creation time, last + /// access time, last modification time and expiry time). Offers + /// several helper functions (for example a function to touch the current + /// object). + /// + public interface ITimeLogger + { + /// + /// The date/time when the object was created. + /// + DateTime CreationTime + { + get; + set; + } + + /// + /// The date/time when the object was last accessed. + /// + DateTime LastAccessTime + { + get; + set; + } + + /// + /// The date/time when the object was last modified. + /// + DateTime LastModificationTime + { + get; + set; + } + + /// + /// The date/time when the object expires. + /// + DateTime ExpiryTime + { + get; + set; + } + + /// + /// Flag that determines if the object does expire. + /// + bool Expires + { + get; + set; + } + + /// + /// Get or set the usage count of the object. To increase the usage + /// count by one, use the Touch function. + /// + ulong UsageCount + { + get; + set; + } + + /// + /// The date/time when the location of the object was last changed. + /// + DateTime LocationChanged + { + get; + set; + } + + /// + /// Touch the object. This function updates the internal last access + /// time. If the parameter is true, + /// the last modification time gets updated, too. Each time you call + /// Touch, the usage count of the object is increased by one. + /// + /// Update last modification time. + void Touch(bool bModified); + } +} diff --git a/src/KeePassLib2Android/Interfaces/IUIOperations.cs b/src/KeePassLib2Android/Interfaces/IUIOperations.cs new file mode 100644 index 00000000..1fc054f3 --- /dev/null +++ b/src/KeePassLib2Android/Interfaces/IUIOperations.cs @@ -0,0 +1,37 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace KeePassLib.Interfaces +{ + public interface IUIOperations + { + /// + /// Let the user interface save the current database. + /// + /// If true, the UI will not ask for + /// whether to synchronize or overwrite, it'll simply overwrite the + /// file. + /// Returns true if the file has been saved. + bool UIFileSave(bool bForceSave); + } +} diff --git a/src/KeePassLib2Android/Interfaces/IXmlSerializerEx.cs b/src/KeePassLib2Android/Interfaces/IXmlSerializerEx.cs new file mode 100644 index 00000000..afd84ab2 --- /dev/null +++ b/src/KeePassLib2Android/Interfaces/IXmlSerializerEx.cs @@ -0,0 +1,33 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Xml; + +namespace KeePassLib.Interfaces +{ + public interface IXmlSerializerEx + { + void Serialize(XmlWriter xmlWriter, object o); + object Deserialize(Stream s); + } +} diff --git a/src/KeePassLib2Android/KeePassLib.pfx b/src/KeePassLib2Android/KeePassLib.pfx new file mode 100644 index 00000000..19b0155f Binary files /dev/null and b/src/KeePassLib2Android/KeePassLib.pfx differ diff --git a/src/KeePassLib2Android/KeePassLib2Android.csproj b/src/KeePassLib2Android/KeePassLib2Android.csproj new file mode 100644 index 00000000..52c4dccc --- /dev/null +++ b/src/KeePassLib2Android/KeePassLib2Android.csproj @@ -0,0 +1,140 @@ + + + + Debug + AnyCPU + 10.0.0 + 2.0 + {545B4A6B-8BBA-4FBE-92FC-4AC060122A54} + {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Library + KeePassLib2Android + Resources + Assets + Resources\Resource.designer.cs + Resource + KeePassLib2Android + + + True + full + False + bin\Debug + DEBUG; + prompt + 4 + False + None + + + none + True + bin\Release + prompt + 4 + False + False + SdkOnly + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Component + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/KeePassLib2Android/Keys/CompositeKey.cs b/src/KeePassLib2Android/Keys/CompositeKey.cs new file mode 100644 index 00000000..614408c1 --- /dev/null +++ b/src/KeePassLib2Android/Keys/CompositeKey.cs @@ -0,0 +1,408 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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.Text; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Security.Cryptography; + +using KeePassLib.Native; +using KeePassLib.Resources; +using KeePassLib.Security; +using KeePassLib.Utility; + +namespace KeePassLib.Keys +{ + /// + /// Represents a key. A key can be build up using several user key data sources + /// like a password, a key file, the currently logged on user credentials, + /// the current computer ID, etc. + /// + public sealed class CompositeKey + { + private List m_vUserKeys = new List(); + + /// + /// List of all user keys contained in the current composite key. + /// + public IEnumerable UserKeys + { + get { return m_vUserKeys; } + } + + public uint UserKeyCount + { + get { return (uint)m_vUserKeys.Count; } + } + + /// + /// Construct a new, empty key object. + /// + public CompositeKey() + { + } + + // /// + // /// Deconstructor, clears up the key. + // /// + // ~CompositeKey() + // { + // Clear(); + // } + + // /// + // /// Clears the key. This function also erases all previously stored + // /// user key data objects. + // /// + // public void Clear() + // { + // foreach(IUserKey pKey in m_vUserKeys) + // pKey.Clear(); + // m_vUserKeys.Clear(); + // } + + /// + /// Add a user key. + /// + /// User key to add. + public void AddUserKey(IUserKey pKey) + { + Debug.Assert(pKey != null); if(pKey == null) throw new ArgumentNullException("pKey"); + + m_vUserKeys.Add(pKey); + } + + /// + /// Remove a user key. + /// + /// User key to remove. + /// Returns true if the key was removed successfully. + public bool RemoveUserKey(IUserKey pKey) + { + Debug.Assert(pKey != null); if(pKey == null) throw new ArgumentNullException("pKey"); + + Debug.Assert(m_vUserKeys.IndexOf(pKey) >= 0); + return m_vUserKeys.Remove(pKey); + } + + /// + /// Test whether the composite key contains a specific type of + /// user keys (password, key file, ...). If at least one user + /// key of that type is present, the function returns true. + /// + /// User key type. + /// Returns true, if the composite key contains + /// a user key of the specified type. + public bool ContainsType(Type tUserKeyType) + { + Debug.Assert(tUserKeyType != null); + if(tUserKeyType == null) throw new ArgumentNullException("tUserKeyType"); + + foreach(IUserKey pKey in m_vUserKeys) + { + if(tUserKeyType.IsInstanceOfType(pKey)) + return true; + } + + return false; + } + + /// + /// Get the first user key of a specified type. + /// + /// Type of the user key to get. + /// Returns the first user key of the specified type + /// or null if no key of that type is found. + public IUserKey GetUserKey(Type tUserKeyType) + { + Debug.Assert(tUserKeyType != null); + if(tUserKeyType == null) throw new ArgumentNullException("tUserKeyType"); + + foreach(IUserKey pKey in m_vUserKeys) + { + if(tUserKeyType.IsInstanceOfType(pKey)) + return pKey; + } + + return null; + } + + /// + /// Creates the composite key from the supplied user key sources (password, + /// key file, user account, computer ID, etc.). + /// + private byte[] CreateRawCompositeKey32() + { + ValidateUserKeys(); + + // Concatenate user key data + MemoryStream ms = new MemoryStream(); + 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); + } + } + + SHA256Managed sha256 = new SHA256Managed(); + byte[] pbHash = sha256.ComputeHash(ms.ToArray()); + ms.Close(); + return pbHash; + } + + public bool EqualsValue(CompositeKey ckOther) + { + if(ckOther == null) throw new ArgumentNullException("ckOther"); + + 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); + + 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. + public ProtectedBinary GenerateKey32(byte[] pbKeySeed32, ulong uNumRounds) + { + Debug.Assert(pbKeySeed32 != null); + if(pbKeySeed32 == null) throw new ArgumentNullException("pbKeySeed32"); + Debug.Assert(pbKeySeed32.Length == 32); + if(pbKeySeed32.Length != 32) throw new ArgumentException("pbKeySeed32"); + + 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; } + + ProtectedBinary pbRet = new ProtectedBinary(true, pbTrf32); + MemUtil.ZeroByteArray(pbTrf32); + MemUtil.ZeroByteArray(pbRaw32); + + return pbRet; + } + + private void ValidateUserKeys() + { + int nAccounts = 0; + + foreach(IUserKey uKey in m_vUserKeys) + { + if(uKey is KcpUserAccount) + ++nAccounts; + } + + if(nAccounts >= 2) + { + Debug.Assert(false); + 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 to use the native library first + if(NativeLib.TransformKey256(pbNewKey, pbKeySeed32, uNumRounds)) + return (new SHA256Managed()).ComputeHash(pbNewKey); + + if(TransformKeyManaged(pbNewKey, pbKeySeed32, uNumRounds) == false) + return null; + + SHA256Managed sha256 = new SHA256Managed(); + return sha256.ComputeHash(pbNewKey); + } + + public static bool TransformKeyManaged(byte[] pbNewKey32, byte[] pbKeySeed32, + ulong uNumRounds) + { + 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); + } + + 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[] 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) + { + pbKey[i] = (byte)i; + pbNewKey[i] = (byte)i; + } + + 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; + } + + DateTime dtStart = DateTime.Now; + TimeSpan ts; + double dblReqMillis = uMilliseconds; + + uRounds = 0; + while(true) + { + for(ulong j = 0; j < uStep; ++j) + { + iCrypt.TransformBlock(pbNewKey, 0, 16, pbNewKey, 0); + iCrypt.TransformBlock(pbNewKey, 16, 16, pbNewKey, 16); + } + + uRounds += uStep; + if(uRounds < uStep) // Overflow check + { + uRounds = ulong.MaxValue; + break; + } + + ts = DateTime.Now - dtStart; + if(ts.TotalMilliseconds > dblReqMillis) break; + } + + return uRounds; + } + } + + public sealed class InvalidCompositeKeyException : Exception + { + public override string Message + { + get + { + return KLRes.InvalidCompositeKey + MessageService.NewParagraph + + KLRes.InvalidCompositeKeyHint; + } + } + + /// + /// Construct a new invalid composite key exception. + /// + public InvalidCompositeKeyException() + { + } + } +} diff --git a/src/KeePassLib2Android/Keys/IUserKey.cs b/src/KeePassLib2Android/Keys/IUserKey.cs new file mode 100644 index 00000000..f5bc556e --- /dev/null +++ b/src/KeePassLib2Android/Keys/IUserKey.cs @@ -0,0 +1,46 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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 KeePassLib.Security; + +namespace KeePassLib.Keys +{ + /// + /// Interface to a user key, like a password, key file data, etc. + /// + public interface IUserKey + { + /// + /// Get key data. Querying this property is fast (it returns a + /// reference to a cached ProtectedBinary object). + /// If no key data is available, null is returned. + /// + ProtectedBinary KeyData + { + get; + } + + // /// + // /// Clear the key and securely erase all security-critical information. + // /// + // void Clear(); + } +} diff --git a/src/KeePassLib2Android/Keys/KcpCustomKey.cs b/src/KeePassLib2Android/Keys/KcpCustomKey.cs new file mode 100644 index 00000000..71a3a486 --- /dev/null +++ b/src/KeePassLib2Android/Keys/KcpCustomKey.cs @@ -0,0 +1,70 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; +using System.Security.Cryptography; + +using KeePassLib.Security; +using KeePassLib.Utility; + +namespace KeePassLib.Keys +{ + public sealed class KcpCustomKey : IUserKey + { + private readonly string m_strName; + private ProtectedBinary m_pbKey; + + /// + /// Name of the provider that generated the custom key. + /// + public string Name + { + get { return m_strName; } + } + + public ProtectedBinary KeyData + { + get { return m_pbKey; } + } + + public KcpCustomKey(string strName, byte[] pbKeyData, bool bPerformHash) + { + Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); + Debug.Assert(pbKeyData != null); if(pbKeyData == null) throw new ArgumentNullException("pbKeyData"); + + m_strName = strName; + + if(bPerformHash) + { + SHA256Managed sha256 = new SHA256Managed(); + byte[] pbRaw = sha256.ComputeHash(pbKeyData); + m_pbKey = new ProtectedBinary(true, pbRaw); + } + else m_pbKey = new ProtectedBinary(true, pbKeyData); + } + + // public void Clear() + // { + // m_pbKey = null; + // } + } +} diff --git a/src/KeePassLib2Android/Keys/KcpKeyFile.cs b/src/KeePassLib2Android/Keys/KcpKeyFile.cs new file mode 100644 index 00000000..3a0a71a0 --- /dev/null +++ b/src/KeePassLib2Android/Keys/KcpKeyFile.cs @@ -0,0 +1,271 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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.Text; +using System.IO; +using System.Xml; +using System.Security; +using System.Security.Cryptography; +using System.Diagnostics; + +using KeePassLib.Cryptography; +using KeePassLib.Security; +using KeePassLib.Serialization; +using KeePassLib.Utility; + +namespace KeePassLib.Keys +{ + /// + /// Key files as provided by the user. + /// + public sealed class KcpKeyFile : IUserKey + { + private string m_strPath; + private ProtectedBinary m_pbKeyData; + + /// + /// Path to the key file. + /// + public string Path + { + get { return m_strPath; } + } + + /// + /// Get key data. Querying this property is fast (it returns a + /// reference to a cached ProtectedBinary object). + /// If no key data is available, null is returned. + /// + public ProtectedBinary KeyData + { + get { return m_pbKeyData; } + } + + public KcpKeyFile(string strKeyFile) + { + Construct(IOConnectionInfo.FromPath(strKeyFile)); + } + + public KcpKeyFile(IOConnectionInfo iocKeyFile) + { + Construct(iocKeyFile); + } + + private void Construct(IOConnectionInfo iocFile) + { + byte[] pbFileData = IOConnection.ReadFile(iocFile); + if(pbFileData == null) throw new FileNotFoundException(); + + byte[] pbKey = LoadXmlKeyFile(pbFileData); + if(pbKey == null) pbKey = LoadKeyFile(pbFileData); + + if(pbKey == null) throw new InvalidOperationException(); + + m_strPath = iocFile.Path; + m_pbKeyData = new ProtectedBinary(true, pbKey); + + MemUtil.ZeroByteArray(pbKey); + } + + // public void Clear() + // { + // m_strPath = string.Empty; + // m_pbKeyData = null; + // } + + private static byte[] LoadKeyFile(byte[] pbFileData) + { + if(pbFileData == null) { Debug.Assert(false); return null; } + + int iLength = pbFileData.Length; + + byte[] pbKey = null; + if(iLength == 32) pbKey = LoadBinaryKey32(pbFileData); + else if(iLength == 64) pbKey = LoadHexKey32(pbFileData); + + if(pbKey == null) + { + SHA256Managed sha256 = new SHA256Managed(); + pbKey = sha256.ComputeHash(pbFileData); + } + + return pbKey; + } + + private static byte[] LoadBinaryKey32(byte[] pbFileData) + { + if(pbFileData == null) { Debug.Assert(false); return null; } + if(pbFileData.Length != 32) { Debug.Assert(false); return null; } + + return pbFileData; + } + + private static byte[] LoadHexKey32(byte[] pbFileData) + { + if(pbFileData == null) { Debug.Assert(false); return null; } + if(pbFileData.Length != 64) { Debug.Assert(false); return null; } + + try + { + string strHex = Encoding.ASCII.GetString(pbFileData, 0, 64); + if(!StrUtil.IsHexString(strHex, true)) return null; + + byte[] pbKey = MemUtil.HexStringToByteArray(strHex); + if((pbKey == null) || (pbKey.Length != 32)) + return null; + + return pbKey; + } + catch(Exception) { Debug.Assert(false); } + + return null; + } + + /// + /// Create a new, random key-file. + /// + /// Path where the key-file should be saved to. + /// If the file exists already, it will be overwritten. + /// Additional entropy used to generate + /// the random key. May be null (in this case only the KeePass-internal + /// random number generator is used). + /// Returns a FileSaveResult error code. + public static void Create(string strFilePath, byte[] pbAdditionalEntropy) + { + byte[] pbKey32 = CryptoRandom.Instance.GetRandomBytes(32); + if(pbKey32 == null) throw new SecurityException(); + + byte[] pbFinalKey32; + if((pbAdditionalEntropy == null) || (pbAdditionalEntropy.Length == 0)) + pbFinalKey32 = pbKey32; + else + { + MemoryStream ms = new MemoryStream(); + ms.Write(pbAdditionalEntropy, 0, pbAdditionalEntropy.Length); + ms.Write(pbKey32, 0, 32); + + SHA256Managed sha256 = new SHA256Managed(); + pbFinalKey32 = sha256.ComputeHash(ms.ToArray()); + ms.Close(); + } + + CreateXmlKeyFile(strFilePath, pbFinalKey32); + } + + // ================================================================ + // XML Key Files + // ================================================================ + + // Sample XML file: + // + // + // + // 1.00 + // + // + // ySFoKuCcJblw8ie6RkMBdVCnAf4EedSch7ItujK6bmI= + // + // + + private const string RootElementName = "KeyFile"; + private const string MetaElementName = "Meta"; + private const string VersionElementName = "Version"; + private const string KeyElementName = "Key"; + private const string KeyDataElementName = "Data"; + + private static byte[] LoadXmlKeyFile(byte[] pbFileData) + { + if(pbFileData == null) { Debug.Assert(false); return null; } + + MemoryStream ms = new MemoryStream(pbFileData, false); + byte[] pbKeyData = null; + + try + { + XmlDocument doc = new XmlDocument(); + doc.Load(ms); + + XmlElement el = doc.DocumentElement; + if((el == null) || !el.Name.Equals(RootElementName)) return null; + if(el.ChildNodes.Count < 2) return null; + + foreach(XmlNode xmlChild in el.ChildNodes) + { + if(xmlChild.Name.Equals(MetaElementName)) { } // Ignore Meta + else if(xmlChild.Name == KeyElementName) + { + foreach(XmlNode xmlKeyChild in xmlChild.ChildNodes) + { + if(xmlKeyChild.Name == KeyDataElementName) + { + if(pbKeyData == null) + pbKeyData = Convert.FromBase64String(xmlKeyChild.InnerText); + } + } + } + } + } + catch(Exception) { pbKeyData = null; } + finally { ms.Close(); } + + return pbKeyData; + } + + private static void CreateXmlKeyFile(string strFile, byte[] pbKeyData) + { + Debug.Assert(strFile != null); + if(strFile == null) throw new ArgumentNullException("strFile"); + Debug.Assert(pbKeyData != null); + if(pbKeyData == null) throw new ArgumentNullException("pbKeyData"); + + XmlTextWriter xtw = new XmlTextWriter(strFile, StrUtil.Utf8); + + xtw.WriteStartDocument(); + xtw.WriteWhitespace("\r\n"); + xtw.WriteStartElement(RootElementName); // KeyFile + xtw.WriteWhitespace("\r\n\t"); + + xtw.WriteStartElement(MetaElementName); // Meta + xtw.WriteWhitespace("\r\n\t\t"); + xtw.WriteStartElement(VersionElementName); // Version + xtw.WriteString("1.00"); + xtw.WriteEndElement(); // End Version + xtw.WriteWhitespace("\r\n\t"); + xtw.WriteEndElement(); // End Meta + xtw.WriteWhitespace("\r\n\t"); + + xtw.WriteStartElement(KeyElementName); // Key + xtw.WriteWhitespace("\r\n\t\t"); + + xtw.WriteStartElement(KeyDataElementName); // Data + xtw.WriteString(Convert.ToBase64String(pbKeyData)); + xtw.WriteEndElement(); // End Data + xtw.WriteWhitespace("\r\n\t"); + + xtw.WriteEndElement(); // End Key + xtw.WriteWhitespace("\r\n"); + + xtw.WriteEndElement(); // RootElementName + xtw.WriteWhitespace("\r\n"); + xtw.WriteEndDocument(); // End KeyFile + xtw.Close(); + } + } +} diff --git a/src/KeePassLib2Android/Keys/KcpPassword.cs b/src/KeePassLib2Android/Keys/KcpPassword.cs new file mode 100644 index 00000000..30df57e8 --- /dev/null +++ b/src/KeePassLib2Android/Keys/KcpPassword.cs @@ -0,0 +1,84 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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.Text; +using System.Diagnostics; +using System.Security.Cryptography; + +using KeePassLib.Security; +using KeePassLib.Utility; + +namespace KeePassLib.Keys +{ + /// + /// Master password / passphrase as provided by the user. + /// + public sealed class KcpPassword : IUserKey + { + private ProtectedString m_psPassword; + private ProtectedBinary m_pbKeyData; + + /// + /// Get the password as protected string. + /// + public ProtectedString Password + { + get { return m_psPassword; } + } + + /// + /// Get key data. Querying this property is fast (it returns a + /// reference to a cached ProtectedBinary object). + /// If no key data is available, null is returned. + /// + public ProtectedBinary KeyData + { + get { return m_pbKeyData; } + } + + public KcpPassword(byte[] pbPasswordUtf8) + { + SetKey(pbPasswordUtf8); + } + + public KcpPassword(string strPassword) + { + SetKey(StrUtil.Utf8.GetBytes(strPassword)); + } + + private void SetKey(byte[] pbPasswordUtf8) + { + Debug.Assert(pbPasswordUtf8 != null); + if(pbPasswordUtf8 == null) throw new ArgumentNullException("pbPasswordUtf8"); + + SHA256Managed sha256 = new SHA256Managed(); + byte[] pbRaw = sha256.ComputeHash(pbPasswordUtf8); + + m_psPassword = new ProtectedString(true, pbPasswordUtf8); + m_pbKeyData = new ProtectedBinary(true, pbRaw); + } + + // public void Clear() + // { + // m_psPassword = null; + // m_pbKeyData = null; + // } + } +} diff --git a/src/KeePassLib2Android/Keys/KcpUserAccount.cs b/src/KeePassLib2Android/Keys/KcpUserAccount.cs new file mode 100644 index 00000000..aefb02c9 --- /dev/null +++ b/src/KeePassLib2Android/Keys/KcpUserAccount.cs @@ -0,0 +1,148 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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.Security; +using System.Security.Cryptography; +using System.IO; + +using KeePassLib.Cryptography; +using KeePassLib.Resources; +using KeePassLib.Security; +using KeePassLib.Utility; + +namespace KeePassLib.Keys +{ + /// + /// A user key depending on the currently logged on Windows user account. + /// + public sealed class KcpUserAccount : IUserKey + { + private ProtectedBinary m_pbKeyData = null; + + // Constant initialization vector (unique for KeePass) + private static readonly byte[] m_pbEntropy = new byte[]{ + 0xDE, 0x13, 0x5B, 0x5F, 0x18, 0xA3, 0x46, 0x70, + 0xB2, 0x57, 0x24, 0x29, 0x69, 0x88, 0x98, 0xE6 + }; + + private const string UserKeyFileName = "ProtectedUserKey.bin"; + + /// + /// Get key data. Querying this property is fast (it returns a + /// reference to a cached ProtectedBinary object). + /// If no key data is available, null is returned. + /// + public ProtectedBinary KeyData + { + get { return m_pbKeyData; } + } + + /// + /// Construct a user account key. + /// + public KcpUserAccount() + { + // Test if ProtectedData is supported -- throws an exception + // when running on an old system (Windows 98 / ME). + byte[] pbDummyData = new byte[128]; + ProtectedData.Protect(pbDummyData, m_pbEntropy, + DataProtectionScope.CurrentUser); + + byte[] pbKey = LoadUserKey(false); + if(pbKey == null) pbKey = CreateUserKey(); + if(pbKey == null) throw new SecurityException(KLRes.UserAccountKeyError); + + m_pbKeyData = new ProtectedBinary(true, pbKey); + Array.Clear(pbKey, 0, pbKey.Length); + } + + // public void Clear() + // { + // m_pbKeyData = null; + // } + + private static string GetUserKeyFilePath(bool bCreate) + { + string strUserDir = Environment.GetFolderPath( + Environment.SpecialFolder.ApplicationData); + + strUserDir = UrlUtil.EnsureTerminatingSeparator(strUserDir, false); + strUserDir += PwDefs.ShortProductName; + + if(bCreate && !Directory.Exists(strUserDir)) + Directory.CreateDirectory(strUserDir); + + strUserDir = UrlUtil.EnsureTerminatingSeparator(strUserDir, false); + return strUserDir + UserKeyFileName; + } + + private static byte[] LoadUserKey(bool bShowWarning) + { + byte[] pbKey = null; + +#if !KeePassLibSD + try + { + string strFilePath = GetUserKeyFilePath(false); + byte[] pbProtectedKey = File.ReadAllBytes(strFilePath); + + pbKey = ProtectedData.Unprotect(pbProtectedKey, m_pbEntropy, + DataProtectionScope.CurrentUser); + + Array.Clear(pbProtectedKey, 0, pbProtectedKey.Length); + } + catch(Exception exLoad) + { + if(bShowWarning) MessageService.ShowWarning(exLoad); + + pbKey = null; + } +#endif + + return pbKey; + } + + private static byte[] CreateUserKey() + { + byte[] pbKey = null; + +#if !KeePassLibSD + try + { + string strFilePath = GetUserKeyFilePath(true); + + byte[] pbRandomKey = CryptoRandom.Instance.GetRandomBytes(64); + byte[] pbProtectedKey = ProtectedData.Protect(pbRandomKey, + m_pbEntropy, DataProtectionScope.CurrentUser); + + File.WriteAllBytes(strFilePath, pbProtectedKey); + + Array.Clear(pbProtectedKey, 0, pbProtectedKey.Length); + Array.Clear(pbRandomKey, 0, pbRandomKey.Length); + + pbKey = LoadUserKey(true); + } + catch(Exception) { pbKey = null; } +#endif + + return pbKey; + } + } +} diff --git a/src/KeePassLib2Android/Keys/KeyProvider.cs b/src/KeePassLib2Android/Keys/KeyProvider.cs new file mode 100644 index 00000000..2efc57f7 --- /dev/null +++ b/src/KeePassLib2Android/Keys/KeyProvider.cs @@ -0,0 +1,152 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; + +using KeePassLib.Serialization; + +namespace KeePassLib.Keys +{ + public sealed class KeyProviderQueryContext + { + private IOConnectionInfo m_ioInfo; + public IOConnectionInfo DatabaseIOInfo + { + get { return m_ioInfo; } + } + + public string DatabasePath + { + get { return m_ioInfo.Path; } + } + + private bool m_bCreatingNewKey; + public bool CreatingNewKey + { + get { return m_bCreatingNewKey; } + } + + private bool m_bSecDesktop; + public bool IsOnSecureDesktop + { + get { return m_bSecDesktop; } + } + + public KeyProviderQueryContext(IOConnectionInfo ioInfo, bool bCreatingNewKey, + bool bOnSecDesktop) + { + if(ioInfo == null) throw new ArgumentNullException("ioInfo"); + + m_ioInfo = ioInfo.CloneDeep(); + m_bCreatingNewKey = bCreatingNewKey; + m_bSecDesktop = bOnSecDesktop; + } + } + + public abstract class KeyProvider + { + /// + /// Name of your key provider (should be unique). + /// + public abstract string Name + { + get; + } + + /// + /// Property indicating whether the provider is exclusive. + /// If the provider is exclusive, KeePass doesn't allow other + /// key sources (master password, Windows user account, ...) + /// to be combined with the provider. + /// Key providers typically should return false + /// (to allow non-exclusive use), i.e. don't override this + /// property. + /// + public virtual bool Exclusive + { + get { return false; } + } + + /// + /// Property that specifies whether the returned key data + /// gets hashed by KeePass first or is written directly to + /// the user key data stream. + /// Standard key provider plugins should return false + /// (i.e. don't overwrite this property). Returning true + /// may cause severe security problems and is highly + /// discouraged. + /// + public virtual bool DirectKey + { + get { return false; } + } + + // public virtual PwIcon ImageIndex + // { + // get { return PwIcon.UserKey; } + // } + + /// + /// This property specifies whether the GetKey method might + /// show a form or dialog. If there is any chance that the method shows + /// one, this property must return true. Only if it's guaranteed + /// that the GetKey method doesn't show any form or dialog, this + /// property should return false. + /// + public virtual bool GetKeyMightShowGui + { + get { return true; } + } + + /// + /// This property specifies whether the key provider is compatible + /// with the secure desktop mode. This almost never is the case, + /// so you usually won't override this property. + /// + public virtual bool SecureDesktopCompatible + { + get { return false; } + } + + public abstract byte[] GetKey(KeyProviderQueryContext ctx); + } + +#if DEBUG + public sealed class SampleKeyProvider : KeyProvider + { + public override string Name + { + get { return "Built-In Sample Key Provider"; } + } + + // Do not just copy this to your own key provider class! See above. + public override bool GetKeyMightShowGui + { + get { return false; } + } + + public override byte[] GetKey(KeyProviderQueryContext ctx) + { + return new byte[]{ 2, 3, 5, 7, 11, 13 }; + } + } +#endif +} diff --git a/src/KeePassLib2Android/Keys/KeyProviderPool.cs b/src/KeePassLib2Android/Keys/KeyProviderPool.cs new file mode 100644 index 00000000..48c547f8 --- /dev/null +++ b/src/KeePassLib2Android/Keys/KeyProviderPool.cs @@ -0,0 +1,105 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +namespace KeePassLib.Keys +{ + public sealed class KeyProviderPool : IEnumerable + { + private List m_vProviders = new List(); + + public int Count + { + get { return m_vProviders.Count; } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return m_vProviders.GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + return m_vProviders.GetEnumerator(); + } + + public void Add(KeyProvider prov) + { + Debug.Assert(prov != null); if(prov == null) throw new ArgumentNullException("prov"); + + m_vProviders.Add(prov); + } + + public bool Remove(KeyProvider prov) + { + Debug.Assert(prov != null); if(prov == null) throw new ArgumentNullException("prov"); + + return m_vProviders.Remove(prov); + } + + public KeyProvider Get(string strProviderName) + { + if(strProviderName == null) throw new ArgumentNullException("strProviderName"); + + foreach(KeyProvider prov in m_vProviders) + { + if(prov.Name == strProviderName) return prov; + } + + return null; + } + + public bool IsKeyProvider(string strName) + { + Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); + + foreach(KeyProvider prov in m_vProviders) + { + if(prov.Name == strName) return true; + } + + return false; + } + + internal byte[] GetKey(string strProviderName, KeyProviderQueryContext ctx, + out bool bPerformHash) + { + Debug.Assert(strProviderName != null); if(strProviderName == null) throw new ArgumentNullException("strProviderName"); + + bPerformHash = true; + + foreach(KeyProvider prov in m_vProviders) + { + if(prov.Name == strProviderName) + { + bPerformHash = !prov.DirectKey; + return prov.GetKey(ctx); + } + } + + Debug.Assert(false); + return null; + } + } +} diff --git a/src/KeePassLib2Android/Keys/KeyValidator.cs b/src/KeePassLib2Android/Keys/KeyValidator.cs new file mode 100644 index 00000000..61c819b9 --- /dev/null +++ b/src/KeePassLib2Android/Keys/KeyValidator.cs @@ -0,0 +1,51 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace KeePassLib.Keys +{ + public enum KeyValidationType + { + MasterPassword = 0 + } + + public abstract class KeyValidator + { + /// + /// Name of your key validator (should be unique). + /// + public abstract string Name + { + get; + } + + /// + /// Validate a key. + /// + /// Key to validate. + /// Type of the validation to perform. + /// Returns null, if the validation is successful. + /// If there's a problem with the key, the returned string describes + /// the problem. + public abstract string Validate(string strKey, KeyValidationType t); + } +} diff --git a/src/KeePassLib2Android/Keys/KeyValidatorPool.cs b/src/KeePassLib2Android/Keys/KeyValidatorPool.cs new file mode 100644 index 00000000..65e24c72 --- /dev/null +++ b/src/KeePassLib2Android/Keys/KeyValidatorPool.cs @@ -0,0 +1,86 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +using KeePassLib.Utility; + +namespace KeePassLib.Keys +{ + public sealed class KeyValidatorPool : IEnumerable + { + private List m_vValidators = new List(); + + public int Count + { + get { return m_vValidators.Count; } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return m_vValidators.GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + return m_vValidators.GetEnumerator(); + } + + public void Add(KeyValidator v) + { + Debug.Assert(v != null); if(v == null) throw new ArgumentNullException("v"); + + m_vValidators.Add(v); + } + + public bool Remove(KeyValidator v) + { + Debug.Assert(v != null); if(v == null) throw new ArgumentNullException("v"); + + return m_vValidators.Remove(v); + } + + public string Validate(string strKey, KeyValidationType t) + { + Debug.Assert(strKey != null); if(strKey == null) throw new ArgumentNullException("strKey"); + + foreach(KeyValidator v in m_vValidators) + { + string strResult = v.Validate(strKey, t); + if(strResult != null) return strResult; + } + + return null; + } + + public string Validate(byte[] pbKeyUtf8, KeyValidationType t) + { + Debug.Assert(pbKeyUtf8 != null); if(pbKeyUtf8 == null) throw new ArgumentNullException("pbKeyUtf8"); + + if(m_vValidators.Count == 0) return null; + + string strKey = StrUtil.Utf8.GetString(pbKeyUtf8, 0, pbKeyUtf8.Length); + return Validate(strKey, t); + } + } +} diff --git a/src/KeePassLib2Android/Keys/UserKeyType.cs b/src/KeePassLib2Android/Keys/UserKeyType.cs new file mode 100644 index 00000000..ce8bf6e8 --- /dev/null +++ b/src/KeePassLib2Android/Keys/UserKeyType.cs @@ -0,0 +1,33 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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; + +namespace KeePassLib.Keys +{ + [Flags] + public enum UserKeyType + { + None = 0, + Other = 1, + Password = 2, + KeyFile = 4, + UserAccount = 8 + } +} diff --git a/src/KeePassLib2Android/Native/NativeLib.cs b/src/KeePassLib2Android/Native/NativeLib.cs new file mode 100644 index 00000000..26bf031b --- /dev/null +++ b/src/KeePassLib2Android/Native/NativeLib.cs @@ -0,0 +1,234 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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.Runtime.InteropServices; +using System.Diagnostics; + +using KeePassLib.Utility; + +namespace KeePassLib.Native +{ + /// + /// Interface to native library (library containing fast versions of + /// several cryptographic functions). + /// + public static class NativeLib + { + private static bool m_bAllowNative = true; + + /// + /// If this property is set to true, the native library is used. + /// If it is false, all calls to functions in this class will fail. + /// + public static bool AllowNative + { + get { return m_bAllowNative; } + set { m_bAllowNative = value; } + } + + /// + /// Determine if the native library is installed. + /// + /// Returns true, if the native library is installed. + public static bool IsLibraryInstalled() + { + byte[] pDummy0 = new byte[32]; + byte[] pDummy1 = new byte[32]; + + // Save the native state + bool bCachedNativeState = m_bAllowNative; + + // Temporarily allow native functions and try to load the library + m_bAllowNative = true; + bool bResult = TransformKey256(pDummy0, pDummy1, 16); + + // Pop native state and return result + m_bAllowNative = bCachedNativeState; + return bResult; + } + + private static bool? m_bIsUnix = null; + public static bool IsUnix() + { + if(m_bIsUnix.HasValue) return m_bIsUnix.Value; + + PlatformID p = GetPlatformID(); + + // Mono defines Unix as 128 in early .NET versions +#if !KeePassLibSD + m_bIsUnix = ((p == PlatformID.Unix) || (p == PlatformID.MacOSX) || + ((int)p == 128)); +#else + m_bIsUnix = (((int)p == 4) || ((int)p == 6) || ((int)p == 128)); +#endif + return m_bIsUnix.Value; + } + + private static PlatformID? m_platID = null; + public static PlatformID GetPlatformID() + { + if(m_platID.HasValue) return m_platID.Value; + + m_platID = Environment.OSVersion.Platform; + +#if !KeePassLibSD + // Mono returns PlatformID.Unix on Mac OS X, workaround this + if(m_platID.Value == PlatformID.Unix) + { + if((RunConsoleApp("uname", null) ?? string.Empty).Trim().Equals( + "Darwin", StrUtil.CaseIgnoreCmp)) + m_platID = PlatformID.MacOSX; + } +#endif + + return m_platID.Value; + } + +#if !KeePassLibSD + public static string RunConsoleApp(string strAppPath, string strParams) + { + return RunConsoleApp(strAppPath, strParams, null); + } + + public static string RunConsoleApp(string strAppPath, string strParams, + string strStdInput) + { + if(strAppPath == null) throw new ArgumentNullException("strAppPath"); + if(strAppPath.Length == 0) throw new ArgumentException("strAppPath"); + + try + { + 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) + { + p.StandardInput.Write(strStdInput); + p.StandardInput.Close(); + } + + string strOutput = p.StandardOutput.ReadToEnd(); + p.WaitForExit(); + + return strOutput; + } + catch(Exception) { Debug.Assert(false); } + + return null; + } +#endif + + /// + /// Transform a key. + /// + /// Source and destination buffer. + /// Key to use in the transformation. + /// Number of transformation rounds. + /// Returns true, if the key was transformed successfully. + public static bool TransformKey256(byte[] pBuf256, byte[] pKey256, + ulong uRounds) + { + if(m_bAllowNative == false) return false; + + KeyValuePair kvp = PrepareArrays256(pBuf256, pKey256); + bool bResult = false; + + try + { + bResult = NativeMethods.TransformKey(kvp.Key, kvp.Value, uRounds); + } + catch(Exception) { bResult = false; } + + if(bResult) GetBuffers256(kvp, pBuf256, pKey256); + + NativeLib.FreeArrays(kvp); + return bResult; + } + + /// + /// Benchmark key transformation. + /// + /// Number of seconds 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; + + try { puRounds = NativeMethods.TransformKeyBenchmark(uTimeMs); } + catch(Exception) { return false; } + + return true; + } + + private static KeyValuePair PrepareArrays256(byte[] pBuf256, + byte[] pKey256) + { + Debug.Assert((pBuf256 != null) && (pBuf256.Length == 32)); + if(pBuf256 == null) throw new ArgumentNullException("pBuf256"); + if(pBuf256.Length != 32) throw new ArgumentException(); + + Debug.Assert((pKey256 != null) && (pKey256.Length == 32)); + if(pKey256 == null) throw new ArgumentNullException("pKey256"); + if(pKey256.Length != 32) throw new ArgumentException(); + + IntPtr hBuf = Marshal.AllocHGlobal(pBuf256.Length); + Marshal.Copy(pBuf256, 0, hBuf, pBuf256.Length); + + IntPtr hKey = Marshal.AllocHGlobal(pKey256.Length); + Marshal.Copy(pKey256, 0, hKey, pKey256.Length); + + return new KeyValuePair(hBuf, hKey); + } + + private static void GetBuffers256(KeyValuePair kvpSource, + byte[] pbDestBuf, byte[] pbDestKey) + { + if(kvpSource.Key != IntPtr.Zero) + Marshal.Copy(kvpSource.Key, pbDestBuf, 0, pbDestBuf.Length); + + if(kvpSource.Value != IntPtr.Zero) + Marshal.Copy(kvpSource.Value, pbDestKey, 0, pbDestKey.Length); + } + + private static void FreeArrays(KeyValuePair kvpPointers) + { + if(kvpPointers.Key != IntPtr.Zero) + Marshal.FreeHGlobal(kvpPointers.Key); + + if(kvpPointers.Value != IntPtr.Zero) + Marshal.FreeHGlobal(kvpPointers.Value); + } + } +} diff --git a/src/KeePassLib2Android/Native/NativeMethods.cs b/src/KeePassLib2Android/Native/NativeMethods.cs new file mode 100644 index 00000000..42036622 --- /dev/null +++ b/src/KeePassLib2Android/Native/NativeMethods.cs @@ -0,0 +1,179 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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.Text; +using System.Security; +using System.Runtime.InteropServices; +using System.IO; +using System.Diagnostics; + +using KeePassLib.Utility; + +namespace KeePassLib.Native +{ + internal static class NativeMethods + { + internal const int MAX_PATH = 260; + + /* [DllImport("KeePassNtv32.dll", EntryPoint = "TransformKey")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool TransformKey32(IntPtr pBuf256, + IntPtr pKey256, UInt64 uRounds); + + [DllImport("KeePassNtv64.dll", EntryPoint = "TransformKey")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool TransformKey64(IntPtr pBuf256, + IntPtr pKey256, UInt64 uRounds); + + internal static bool TransformKey(IntPtr pBuf256, IntPtr pKey256, + UInt64 uRounds) + { + if(Marshal.SizeOf(typeof(IntPtr)) == 8) + return TransformKey64(pBuf256, pKey256, uRounds); + else + return TransformKey32(pBuf256, pKey256, uRounds); + } + + [DllImport("KeePassNtv32.dll", EntryPoint = "TransformKeyTimed")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool TransformKeyTimed32(IntPtr pBuf256, + IntPtr pKey256, ref UInt64 puRounds, UInt32 uSeconds); + + [DllImport("KeePassNtv64.dll", EntryPoint = "TransformKeyTimed")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool TransformKeyTimed64(IntPtr pBuf256, + IntPtr pKey256, ref UInt64 puRounds, UInt32 uSeconds); + + internal static bool TransformKeyTimed(IntPtr pBuf256, IntPtr pKey256, + ref UInt64 puRounds, UInt32 uSeconds) + { + if(Marshal.SizeOf(typeof(IntPtr)) == 8) + return TransformKeyTimed64(pBuf256, pKey256, ref puRounds, uSeconds); + else + return TransformKeyTimed32(pBuf256, pKey256, ref puRounds, uSeconds); + } */ + + [DllImport("KeePassLibC32.dll", EntryPoint = "TransformKey256")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool TransformKey32(IntPtr pBuf256, + IntPtr pKey256, UInt64 uRounds); + + [DllImport("KeePassLibC64.dll", EntryPoint = "TransformKey256")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool TransformKey64(IntPtr pBuf256, + IntPtr pKey256, UInt64 uRounds); + + internal static bool TransformKey(IntPtr pBuf256, IntPtr pKey256, + UInt64 uRounds) + { + if(Marshal.SizeOf(typeof(IntPtr)) == 8) + return TransformKey64(pBuf256, pKey256, uRounds); + else + return TransformKey32(pBuf256, pKey256, uRounds); + } + + [DllImport("KeePassLibC32.dll", EntryPoint = "TransformKeyBenchmark256")] + private static extern UInt64 TransformKeyBenchmark32(UInt32 uTimeMs); + + [DllImport("KeePassLibC64.dll", EntryPoint = "TransformKeyBenchmark256")] + private static extern UInt64 TransformKeyBenchmark64(UInt32 uTimeMs); + + internal static UInt64 TransformKeyBenchmark(UInt32 uTimeMs) + { + if(Marshal.SizeOf(typeof(IntPtr)) == 8) + return TransformKeyBenchmark64(uTimeMs); + else + return TransformKeyBenchmark32(uTimeMs); + } + +#if !KeePassLibSD + [DllImport("ShlWApi.dll", CharSet = CharSet.Unicode, ExactSpelling = true)] + internal static extern int StrCmpLogicalW(string x, string y); + + [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 + + private static bool? m_bSupportsLogicalCmp = 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; + } + catch(Exception) { m_bSupportsLogicalCmp = false; } +#endif + } + + internal static bool SupportsStrCmpNaturally + { + get + { + if(m_bSupportsLogicalCmp.HasValue == false) + TestNaturalComparisonsSupport(); + + return m_bSupportsLogicalCmp.Value; + } + } + + internal static int StrCmpNaturally(string x, string y) + { + if(m_bSupportsLogicalCmp.HasValue == false) TestNaturalComparisonsSupport(); + if(m_bSupportsLogicalCmp.Value == false) return 0; + +#if KeePassLibSD +#warning No native natural comparisons supported. + return x.CompareTo(y); +#else + return StrCmpLogicalW(x, y); +#endif + } + + internal static string GetUserRuntimeDir() + { +#if !KeePassLibSD + string strRtDir = Environment.GetEnvironmentVariable("XDG_RUNTIME_DIR"); + if(string.IsNullOrEmpty(strRtDir)) + strRtDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + if(string.IsNullOrEmpty(strRtDir)) + { + Debug.Assert(false); + return Path.GetTempPath(); // Not UrlUtil (otherwise cyclic) + } + + 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 new file mode 100644 index 00000000..e55beedf --- /dev/null +++ b/src/KeePassLib2Android/Properties/AssemblyInfo.cs @@ -0,0 +1,42 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General assembly properties +[assembly: AssemblyTitle("KeePassLib")] +[assembly: AssemblyDescription("KeePass Password Management Library")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Dominik Reichl")] +[assembly: AssemblyProduct("KeePassLib")] +[assembly: AssemblyCopyright("Copyright © 2003-2012 Dominik Reichl")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// COM settings +[assembly: ComVisible(false)] + +// Assembly GUID +[assembly: Guid("395f6eec-a1e0-4438-aa82-b75099348134")] + +// Assembly version information +[assembly: AssemblyVersion("2.20.1.*")] +[assembly: AssemblyFileVersion("2.20.1.0")] diff --git a/src/KeePassLib2Android/PwCustomIcon.cs b/src/KeePassLib2Android/PwCustomIcon.cs new file mode 100644 index 00000000..f32bc1a8 --- /dev/null +++ b/src/KeePassLib2Android/PwCustomIcon.cs @@ -0,0 +1,76 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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.Diagnostics; +using System.Drawing; +using System.IO; + +using KeePassLib.Utility; + +namespace KeePassLib +{ + /// + /// Custom icon. PwCustomIcon objects are immutable. + /// + public sealed class PwCustomIcon + { + private PwUuid m_pwUuid; + private byte[] m_pbImageDataPng; + private Image m_pCachedImage; + + public PwUuid Uuid + { + get { return m_pwUuid; } + } + + public byte[] ImageDataPng + { + get { return m_pbImageDataPng; } + } + + public Image Image + { + get { return m_pCachedImage; } + } + + 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(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); + // ms.Close(); + m_pCachedImage = GfxUtil.LoadImage(m_pbImageDataPng); +#else + m_pCachedImage = null; +#endif + } + } +} diff --git a/src/KeePassLib2Android/PwDatabase.cs b/src/KeePassLib2Android/PwDatabase.cs new file mode 100644 index 00000000..d3ef6570 --- /dev/null +++ b/src/KeePassLib2Android/PwDatabase.cs @@ -0,0 +1,1759 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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.Drawing; + +using KeePassLib.Collections; +using KeePassLib.Cryptography; +using KeePassLib.Cryptography.Cipher; +using KeePassLib.Delegates; +using KeePassLib.Interfaces; +using KeePassLib.Keys; +using KeePassLib.Resources; +using KeePassLib.Security; +using KeePassLib.Serialization; +using KeePassLib.Utility; + +namespace KeePassLib +{ + /// + /// The core password manager class. It contains a number of groups, which + /// contain the actual entries. + /// + public sealed class PwDatabase + { + internal const int DefaultHistoryMaxItems = 10; // -1 = unlimited + internal const long DefaultHistoryMaxSize = 6 * 1024 * 1024; // -1 = unlimited + + private static bool m_bPrimaryCreated = false; + + // 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 CompositeKey m_pwUserKey = null; + private MemoryProtectionConfig m_memProtConfig = new MemoryProtectionConfig(); + + private List m_vCustomIcons = new List(); + private bool m_bUINeedsIconUpdate = true; + + private string m_strName = string.Empty; + private DateTime m_dtNameChanged = PwDefs.DtDefaultNow; + private string m_strDesc = string.Empty; + private DateTime m_dtDescChanged = PwDefs.DtDefaultNow; + private string m_strDefaultUserName = string.Empty; + private DateTime m_dtDefaultUserChanged = PwDefs.DtDefaultNow; + private uint m_uMntncHistoryDays = 365; + private Color m_clr = Color.Empty; + + private DateTime m_dtKeyLastChanged = PwDefs.DtDefaultNow; + private long m_lKeyChangeRecDays = -1; + private long m_lKeyChangeForceDays = -1; + + private IOConnectionInfo m_ioSource = new IOConnectionInfo(); + private bool m_bDatabaseOpened = false; + private bool m_bModified = false; + + private PwUuid m_pwLastSelectedGroup = PwUuid.Zero; + private PwUuid m_pwLastTopVisibleGroup = PwUuid.Zero; + + private bool m_bUseRecycleBin = true; + private PwUuid m_pwRecycleBin = PwUuid.Zero; + private DateTime m_dtRecycleBinChanged = PwDefs.DtDefaultNow; + private PwUuid m_pwEntryTemplatesGroup = PwUuid.Zero; + private DateTime m_dtEntryTemplatesChanged = PwDefs.DtDefaultNow; + + private int m_nHistoryMaxItems = DefaultHistoryMaxItems; + private long m_lHistoryMaxSize = DefaultHistoryMaxSize; // In bytes + + private StringDictionaryEx m_vCustomData = new StringDictionaryEx(); + + private byte[] m_pbHashOfFileOnDisk = null; + private byte[] m_pbHashOfLastIO = null; + + private bool m_bUseFileTransactions = false; + private bool m_bUseFileLocks = false; + + private IStatusLogger m_slStatus = null; + + private static string m_strLocalizedAppName = string.Empty; + + // private const string StrBackupExtension = ".bak"; + + /// + /// Get the root group that contains all groups and entries stored in the + /// database. + /// + /// Root group. The return value is null, if no database + /// has been opened. + public PwGroup RootGroup + { + get { return m_pgRootGroup; } + set + { + Debug.Assert(value != null); + if(value == null) throw new ArgumentNullException("value"); + + m_pgRootGroup = value; + } + } + + /// + /// IOConnection of the currently opened database file. + /// Is never null. + /// + public IOConnectionInfo IOConnectionInfo + { + get { return m_ioSource; } + } + + /// + /// If this is true, a database is currently open. + /// + public bool IsOpen + { + get { return m_bDatabaseOpened; } + } + + /// + /// Modification flag. If true, the class has been modified and the + /// user interface should prompt the user to save the changes before + /// closing the database for example. + /// + public bool Modified + { + get { return m_bModified; } + set { m_bModified = value; } + } + + /// + /// The user key used for database encryption. This key must be created + /// and set before using any of the database load/save functions. + /// + public CompositeKey MasterKey + { + get { return m_pwUserKey; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + + m_pwUserKey = value; + } + } + + /// + /// Name of the database. + /// + public string Name + { + get { return m_strName; } + set + { + Debug.Assert(value != null); + if(value != null) m_strName = value; + } + } + + public DateTime NameChanged + { + get { return m_dtNameChanged; } + set { m_dtNameChanged = value; } + } + + /// + /// Database description. + /// + public string Description + { + get { return m_strDesc; } + set + { + Debug.Assert(value != null); + if(value != null) m_strDesc = value; + } + } + + public DateTime DescriptionChanged + { + get { return m_dtDescChanged; } + set { m_dtDescChanged = value; } + } + + /// + /// Default user name used for new entries. + /// + public string DefaultUserName + { + get { return m_strDefaultUserName; } + set + { + Debug.Assert(value != null); + if(value != null) m_strDefaultUserName = value; + } + } + + public DateTime DefaultUserNameChanged + { + get { return m_dtDefaultUserChanged; } + set { m_dtDefaultUserChanged = value; } + } + + /// + /// Number of days until history entries are being deleted + /// in a database maintenance operation. + /// + public uint MaintenanceHistoryDays + { + get { return m_uMntncHistoryDays; } + set { m_uMntncHistoryDays = value; } + } + + public Color Color + { + get { return m_clr; } + set { m_clr = value; } + } + + public DateTime MasterKeyChanged + { + get { return m_dtKeyLastChanged; } + set { m_dtKeyLastChanged = value; } + } + + public long MasterKeyChangeRec + { + get { return m_lKeyChangeRecDays; } + set { m_lKeyChangeRecDays = value; } + } + + public long MasterKeyChangeForce + { + get { return m_lKeyChangeForceDays; } + set { m_lKeyChangeForceDays = value; } + } + + /// + /// The encryption algorithm used to encrypt the data part of the database. + /// + public PwUuid DataCipherUuid + { + get { return m_uuidDataCipher; } + set + { + Debug.Assert(value != null); + if(value != null) m_uuidDataCipher = value; + } + } + + /// + /// Compression algorithm used to encrypt the data part of the database. + /// + public PwCompressionAlgorithm Compression + { + get { return m_caCompression; } + set { m_caCompression = value; } + } + + /// + /// Number of key transformation rounds (in order to make dictionary + /// attacks harder). + /// + public ulong KeyEncryptionRounds + { + get { return m_uKeyEncryptionRounds; } + set { m_uKeyEncryptionRounds = value; } + } + + /// + /// Memory protection configuration (for default fields). + /// + public MemoryProtectionConfig MemoryProtection + { + get { return m_memProtConfig; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + + m_memProtConfig = value; + } + } + + /// + /// Get a list of all deleted objects. + /// + public PwObjectList DeletedObjects + { + get { return m_vDeletedObjects; } + } + + /// + /// Get all custom icons stored in this database. + /// + public List CustomIcons + { + get { return m_vCustomIcons; } + } + + /// + /// This is a dirty-flag for the UI. It is used to indicate when an + /// icon list update is required. + /// + public bool UINeedsIconUpdate + { + get { return m_bUINeedsIconUpdate; } + set { m_bUINeedsIconUpdate = value; } + } + + public PwUuid LastSelectedGroup + { + get { return m_pwLastSelectedGroup; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + m_pwLastSelectedGroup = value; + } + } + + public PwUuid LastTopVisibleGroup + { + get { return m_pwLastTopVisibleGroup; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + m_pwLastTopVisibleGroup = value; + } + } + + public bool RecycleBinEnabled + { + get { return m_bUseRecycleBin; } + set { m_bUseRecycleBin = value; } + } + + public PwUuid RecycleBinUuid + { + get { return m_pwRecycleBin; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + m_pwRecycleBin = value; + } + } + + public DateTime RecycleBinChanged + { + get { return m_dtRecycleBinChanged; } + set { m_dtRecycleBinChanged = value; } + } + + /// + /// UUID of the group containing template entries. May be + /// PwUuid.Zero, if no entry templates group has been specified. + /// + public PwUuid EntryTemplatesGroup + { + get { return m_pwEntryTemplatesGroup; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + m_pwEntryTemplatesGroup = value; + } + } + + public DateTime EntryTemplatesGroupChanged + { + get { return m_dtEntryTemplatesChanged; } + set { m_dtEntryTemplatesChanged = value; } + } + + public int HistoryMaxItems + { + get { return m_nHistoryMaxItems; } + set { m_nHistoryMaxItems = value; } + } + + public long HistoryMaxSize + { + get { return m_lHistoryMaxSize; } + set { m_lHistoryMaxSize = value; } + } + + /// + /// Custom data container that can be used by plugins to store + /// own data in KeePass databases. + /// + public StringDictionaryEx CustomData + { + get { return m_vCustomData; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + m_vCustomData = value; + } + } + + /// + /// Hash value of the primary file on disk (last read or last write). + /// A call to SaveAs without making the saved file primary will + /// not change this hash. May be null. + /// + public byte[] HashOfFileOnDisk + { + get { return m_pbHashOfFileOnDisk; } + } + + public byte[] HashOfLastIO + { + get { return m_pbHashOfLastIO; } + } + + public bool UseFileTransactions + { + get { return m_bUseFileTransactions; } + set { m_bUseFileTransactions = value; } + } + + public bool UseFileLocks + { + get { return m_bUseFileLocks; } + set { m_bUseFileLocks = value; } + } + + private string m_strDetachBins = null; + /// + /// Detach binaries when opening a file. If this isn't null, + /// all binaries are saved to the specified path and are removed + /// from the database. + /// + public string DetachBinaries + { + get { return m_strDetachBins; } + set { m_strDetachBins = value; } + } + + /// + /// Localized application name. + /// + public static string LocalizedAppName + { + get { return m_strLocalizedAppName; } + set { Debug.Assert(value != null); m_strLocalizedAppName = value; } + } + + /// + /// Constructs an empty password manager object. + /// + public PwDatabase() + { + if(m_bPrimaryCreated == false) m_bPrimaryCreated = true; + + Clear(); + } + + private void Clear() + { + m_pgRootGroup = null; + m_vDeletedObjects = new PwObjectList(); + + m_uuidDataCipher = StandardAesEngine.AesUuid; + m_caCompression = PwCompressionAlgorithm.GZip; + m_uKeyEncryptionRounds = PwDefs.DefaultKeyEncryptionRounds; + + m_pwUserKey = null; + m_memProtConfig = new MemoryProtectionConfig(); + + m_vCustomIcons = new List(); + m_bUINeedsIconUpdate = true; + + DateTime dtNow = DateTime.Now; + + m_strName = string.Empty; + m_dtNameChanged = dtNow; + m_strDesc = string.Empty; + m_dtDescChanged = dtNow; + m_strDefaultUserName = string.Empty; + m_dtDefaultUserChanged = dtNow; + m_uMntncHistoryDays = 365; + m_clr = Color.Empty; + + m_dtKeyLastChanged = dtNow; + m_lKeyChangeRecDays = -1; + m_lKeyChangeForceDays = -1; + + m_ioSource = new IOConnectionInfo(); + m_bDatabaseOpened = false; + m_bModified = false; + + m_pwLastSelectedGroup = PwUuid.Zero; + m_pwLastTopVisibleGroup = PwUuid.Zero; + + m_bUseRecycleBin = true; + m_pwRecycleBin = PwUuid.Zero; + m_dtRecycleBinChanged = dtNow; + m_pwEntryTemplatesGroup = PwUuid.Zero; + m_dtEntryTemplatesChanged = dtNow; + + m_nHistoryMaxItems = DefaultHistoryMaxItems; + m_lHistoryMaxSize = DefaultHistoryMaxSize; + + m_vCustomData = new StringDictionaryEx(); + + m_pbHashOfFileOnDisk = null; + m_pbHashOfLastIO = null; + + m_bUseFileTransactions = false; + m_bUseFileLocks = false; + } + + /// + /// Initialize the class for managing a new database. Previously loaded + /// data is deleted. + /// + /// IO connection of the new database. + /// Key to open the database. + public void New(IOConnectionInfo ioConnection, CompositeKey pwKey) + { + Debug.Assert(ioConnection != null); + if(ioConnection == null) throw new ArgumentNullException("ioConnection"); + Debug.Assert(pwKey != null); + if(pwKey == null) throw new ArgumentNullException("pwKey"); + + Close(); + + m_ioSource = ioConnection; + m_pwUserKey = pwKey; + + m_bDatabaseOpened = true; + m_bModified = true; + + m_pgRootGroup = new PwGroup(true, true, + UrlUtil.StripExtension(UrlUtil.GetFileName(ioConnection.Path)), + PwIcon.FolderOpen); + m_pgRootGroup.IsExpanded = true; + } + + /// + /// Open a database. The URL may point to any supported data source. + /// + /// IO connection to load the database from. + /// Key used to open the specified database. + /// Logger, which gets all status messages. + public void Open(IOConnectionInfo ioSource, CompositeKey pwKey, + IStatusLogger slLogger) + { + Debug.Assert(ioSource != null); + if(ioSource == null) throw new ArgumentNullException("ioSource"); + Debug.Assert(pwKey != null); + if(pwKey == null) throw new ArgumentNullException("pwKey"); + + Close(); + + try + { + m_pgRootGroup = new PwGroup(true, true, UrlUtil.StripExtension( + UrlUtil.GetFileName(ioSource.Path)), PwIcon.FolderOpen); + m_pgRootGroup.IsExpanded = true; + + m_pwUserKey = pwKey; + + m_bModified = false; + + KdbxFile kdbx = new KdbxFile(this); + kdbx.DetachBinaries = m_strDetachBins; + + Stream s = IOConnection.OpenRead(ioSource); + kdbx.Load(s, KdbxFormat.Default, slLogger); + s.Close(); + + m_pbHashOfLastIO = kdbx.HashOfFileOnDisk; + m_pbHashOfFileOnDisk = kdbx.HashOfFileOnDisk; + Debug.Assert(m_pbHashOfFileOnDisk != null); + + m_bDatabaseOpened = true; + m_ioSource = ioSource; + } + catch(Exception) + { + Clear(); + throw; + } + } + + /// + /// Save the currently opened database. The file is written to the location + /// it has been opened from. + /// + /// Logger that recieves status information. + public void Save(IStatusLogger slLogger) + { + Debug.Assert(ValidateUuidUniqueness()); + + FileLock fl = null; + if(m_bUseFileLocks) fl = new FileLock(m_ioSource); + try + { + FileTransactionEx ft = new FileTransactionEx(m_ioSource, + m_bUseFileTransactions); + Stream s = ft.OpenWrite(); + + KdbxFile kdb = new KdbxFile(this); + kdb.Save(s, null, KdbxFormat.Default, slLogger); + + ft.CommitWrite(); + + m_pbHashOfLastIO = kdb.HashOfFileOnDisk; + m_pbHashOfFileOnDisk = kdb.HashOfFileOnDisk; + Debug.Assert(m_pbHashOfFileOnDisk != null); + } + finally { if(fl != null) fl.Dispose(); } + + m_bModified = false; + } + + /// + /// Save the currently opened database to a different location. If + /// is true, the specified + /// location is made the default location for future saves + /// using SaveDatabase. + /// + /// New location to serialize the database to. + /// If true, the new location is made the + /// standard location for the database. If false, a copy of the currently + /// opened database is saved to the specified location, but it isn't + /// made the default location (i.e. no lock files will be moved for + /// example). + /// Logger that recieves status information. + public void SaveAs(IOConnectionInfo ioConnection, bool bIsPrimaryNow, + IStatusLogger slLogger) + { + Debug.Assert(ioConnection != null); + if(ioConnection == null) throw new ArgumentNullException("ioConnection"); + + IOConnectionInfo ioCurrent = m_ioSource; // Remember current + m_ioSource = ioConnection; + + byte[] pbHashCopy = m_pbHashOfFileOnDisk; + + try { this.Save(slLogger); } + catch(Exception) + { + m_ioSource = ioCurrent; // Restore + m_pbHashOfFileOnDisk = pbHashCopy; + + m_pbHashOfLastIO = null; + throw; + } + + if(!bIsPrimaryNow) + { + m_ioSource = ioCurrent; // Restore + m_pbHashOfFileOnDisk = pbHashCopy; + } + } + + /// + /// Closes the currently opened database. No confirmation message is shown + /// before closing. Unsaved changes will be lost. + /// + public void Close() + { + Clear(); + } + + public void MergeIn(PwDatabase pwSource, PwMergeMethod mm) + { + MergeIn(pwSource, 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, + IStatusLogger slStatus) + { + if(pwSource == null) throw new ArgumentNullException("pwSource"); + + PwGroup pgOrgStructure = m_pgRootGroup.CloneStructure(); + PwGroup pgSrcStructure = pwSource.m_pgRootGroup.CloneStructure(); + + if(mm == PwMergeMethod.CreateNewUuids) + pwSource.RootGroup.CreateNewItemUuids(true, true, true); + + GroupHandler gh = delegate(PwGroup pg) + { + if(pg == pwSource.m_pgRootGroup) return true; + + PwGroup pgLocal = m_pgRootGroup.FindGroup(pg.Uuid, true); + if(pgLocal == null) + { + PwGroup pgSourceParent = pg.ParentGroup; + PwGroup pgLocalContainer; + if(pgSourceParent == pwSource.m_pgRootGroup) + pgLocalContainer = m_pgRootGroup; + else + pgLocalContainer = m_pgRootGroup.FindGroup(pgSourceParent.Uuid, true); + Debug.Assert(pgLocalContainer != null); + if(pgLocalContainer == null) pgLocalContainer = m_pgRootGroup; + + PwGroup pgNew = new PwGroup(false, false); + pgNew.Uuid = pg.Uuid; + pgNew.AssignProperties(pg, false, true); + pgLocalContainer.AddGroup(pgNew, true); + } + else // pgLocal != null + { + Debug.Assert(mm != PwMergeMethod.CreateNewUuids); + + if(mm == PwMergeMethod.OverwriteExisting) + pgLocal.AssignProperties(pg, false, false); + else if((mm == PwMergeMethod.OverwriteIfNewer) || + (mm == PwMergeMethod.Synchronize)) + { + pgLocal.AssignProperties(pg, true, false); + } + // else if(mm == PwMergeMethod.KeepExisting) ... + } + + return ((slStatus != null) ? slStatus.ContinueWork() : true); + }; + + EntryHandler eh = delegate(PwEntry pe) + { + PwEntry peLocal = m_pgRootGroup.FindEntry(pe.Uuid, true); + if(peLocal == null) + { + PwGroup pgSourceParent = pe.ParentGroup; + PwGroup pgLocalContainer; + if(pgSourceParent == pwSource.m_pgRootGroup) + pgLocalContainer = m_pgRootGroup; + else + pgLocalContainer = m_pgRootGroup.FindGroup(pgSourceParent.Uuid, true); + Debug.Assert(pgLocalContainer != null); + if(pgLocalContainer == null) pgLocalContainer = m_pgRootGroup; + + PwEntry peNew = new PwEntry(false, false); + peNew.Uuid = pe.Uuid; + peNew.AssignProperties(pe, false, true, true); + pgLocalContainer.AddEntry(peNew, true); + } + else // peLocal != null + { + Debug.Assert(mm != PwMergeMethod.CreateNewUuids); + + const PwCompareOptions cmpOpt = (PwCompareOptions.IgnoreParentGroup | + PwCompareOptions.IgnoreLastAccess | PwCompareOptions.IgnoreHistory | + PwCompareOptions.NullEmptyEquivStd); + bool bEquals = peLocal.EqualsEntry(pe, cmpOpt, MemProtCmpMode.None); + + bool bOrgBackup = !bEquals; + if(mm != PwMergeMethod.OverwriteExisting) + bOrgBackup &= (pe.LastModificationTime > peLocal.LastModificationTime); + 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 &= !peLocal.HasBackupOfData(pe, false, true); + if(bSrcBackup) pe.CreateBackup(null); // Maintain at end + + if(mm == PwMergeMethod.OverwriteExisting) + peLocal.AssignProperties(pe, false, false, false); + else if((mm == PwMergeMethod.OverwriteIfNewer) || + (mm == PwMergeMethod.Synchronize)) + { + peLocal.AssignProperties(pe, true, false, false); + } + // else if(mm == PwMergeMethod.KeepExisting) ... + + MergeEntryHistory(peLocal, pe, mm); + } + + return ((slStatus != null) ? slStatus.ContinueWork() : true); + }; + + if(!pwSource.RootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh)) + throw new InvalidOperationException(); + + IStatusLogger slPrevStatus = m_slStatus; + m_slStatus = slStatus; + + if(mm == PwMergeMethod.Synchronize) + { + ApplyDeletions(pwSource.m_vDeletedObjects, true); + ApplyDeletions(m_vDeletedObjects, false); + + PwObjectPool ppOrgGroups = PwObjectPool.FromGroupRecursive( + pgOrgStructure, false); + PwObjectPool ppSrcGroups = PwObjectPool.FromGroupRecursive( + pgSrcStructure, false); + PwObjectPool ppOrgEntries = PwObjectPool.FromGroupRecursive( + pgOrgStructure, true); + PwObjectPool ppSrcEntries = PwObjectPool.FromGroupRecursive( + pgSrcStructure, true); + + RelocateGroups(ppOrgGroups, ppSrcGroups); + ReorderGroups(ppOrgGroups, ppSrcGroups); + RelocateEntries(ppOrgEntries, ppSrcEntries); + ReorderEntries(ppOrgEntries, ppSrcEntries); + Debug.Assert(ValidateUuidUniqueness()); + } + + // Must be called *after* merging groups, because group UUIDs + // are required for recycle bin and entry template UUIDs + MergeInDbProperties(pwSource, mm); + + MergeInCustomIcons(pwSource); + + MaintainBackups(); + + m_slStatus = slPrevStatus; + } + + private void MergeInCustomIcons(PwDatabase pwSource) + { + foreach(PwCustomIcon pwci in pwSource.CustomIcons) + { + if(GetCustomIconIndex(pwci.Uuid) >= 0) continue; + + m_vCustomIcons.Add(pwci); // PwCustomIcon is immutable + m_bUINeedsIconUpdate = true; + } + } + + /// + /// Apply a list of deleted objects. + /// + /// List of deleted objects. + private void ApplyDeletions(PwObjectList listDelObjects, + bool bCopyDeletionInfoToLocal) + { + Debug.Assert(listDelObjects != null); if(listDelObjects == null) throw new ArgumentNullException("listDelObjects"); + + LinkedList listGroupsToDelete = new LinkedList(); + LinkedList listEntriesToDelete = new LinkedList(); + + GroupHandler gh = delegate(PwGroup pg) + { + if(pg == m_pgRootGroup) return true; + + foreach(PwDeletedObject pdo in listDelObjects) + { + if(pg.Uuid.EqualsValue(pdo.Uuid)) + if(pg.LastModificationTime < pdo.DeletionTime) + listGroupsToDelete.AddLast(pg); + } + + return ((m_slStatus != null) ? m_slStatus.ContinueWork() : true); + }; + + EntryHandler eh = delegate(PwEntry pe) + { + foreach(PwDeletedObject pdo in listDelObjects) + { + if(pe.Uuid.EqualsValue(pdo.Uuid)) + if(pe.LastModificationTime < pdo.DeletionTime) + listEntriesToDelete.AddLast(pe); + } + + 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) + { + 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); + } + } + } + + private void RelocateGroups(PwObjectPool ppOrgStructure, + PwObjectPool ppSrcStructure) + { + PwObjectList vGroups = m_pgRootGroup.GetGroups(true); + + foreach(PwGroup pg in vGroups) + { + if((m_slStatus != null) && !m_slStatus.ContinueWork()) break; + + // PwGroup pgOrg = pgOrgStructure.FindGroup(pg.Uuid, true); + IStructureItem ptOrg = ppOrgStructure.Get(pg.Uuid); + if(ptOrg == null) continue; + // PwGroup pgSrc = pgSrcStructure.FindGroup(pg.Uuid, true); + IStructureItem ptSrc = ppSrcStructure.Get(pg.Uuid); + if(ptSrc == null) continue; + + PwGroup pgOrgParent = ptOrg.ParentGroup; + PwGroup pgSrcParent = ptSrc.ParentGroup; + if(pgOrgParent.Uuid.EqualsValue(pgSrcParent.Uuid)) + { + pg.LocationChanged = ((ptSrc.LocationChanged > ptOrg.LocationChanged) ? + ptSrc.LocationChanged : ptOrg.LocationChanged); + continue; + } + + if(ptSrc.LocationChanged > ptOrg.LocationChanged) + { + PwGroup pgLocal = m_pgRootGroup.FindGroup(pgSrcParent.Uuid, true); + if(pgLocal == null) { Debug.Assert(false); continue; } + + if(pgLocal.IsContainedIn(pg)) continue; + + pg.ParentGroup.Groups.Remove(pg); + pgLocal.AddGroup(pg, true); + pg.LocationChanged = ptSrc.LocationChanged; + } + else + { + Debug.Assert(pg.ParentGroup.Uuid.EqualsValue(pgOrgParent.Uuid)); + Debug.Assert(pg.LocationChanged == ptOrg.LocationChanged); + } + } + + Debug.Assert(m_pgRootGroup.GetGroups(true).UCount == vGroups.UCount); + } + + private void RelocateEntries(PwObjectPool ppOrgStructure, + PwObjectPool ppSrcStructure) + { + PwObjectList vEntries = m_pgRootGroup.GetEntries(true); + + foreach(PwEntry pe in vEntries) + { + if((m_slStatus != null) && !m_slStatus.ContinueWork()) break; + + // PwEntry peOrg = pgOrgStructure.FindEntry(pe.Uuid, true); + IStructureItem ptOrg = ppOrgStructure.Get(pe.Uuid); + if(ptOrg == null) continue; + // PwEntry peSrc = pgSrcStructure.FindEntry(pe.Uuid, true); + IStructureItem ptSrc = ppSrcStructure.Get(pe.Uuid); + if(ptSrc == null) continue; + + PwGroup pgOrg = ptOrg.ParentGroup; + PwGroup pgSrc = ptSrc.ParentGroup; + if(pgOrg.Uuid.EqualsValue(pgSrc.Uuid)) + { + pe.LocationChanged = ((ptSrc.LocationChanged > ptOrg.LocationChanged) ? + ptSrc.LocationChanged : ptOrg.LocationChanged); + continue; + } + + if(ptSrc.LocationChanged > ptOrg.LocationChanged) + { + PwGroup pgLocal = m_pgRootGroup.FindGroup(pgSrc.Uuid, true); + if(pgLocal == null) { Debug.Assert(false); continue; } + + pe.ParentGroup.Entries.Remove(pe); + pgLocal.AddEntry(pe, true); + pe.LocationChanged = ptSrc.LocationChanged; + } + else + { + Debug.Assert(pe.ParentGroup.Uuid.EqualsValue(pgOrg.Uuid)); + Debug.Assert(pe.LocationChanged == ptOrg.LocationChanged); + } + } + + Debug.Assert(m_pgRootGroup.GetEntries(true).UCount == vEntries.UCount); + } + + private void ReorderGroups(PwObjectPool ppOrgStructure, + PwObjectPool ppSrcStructure) + { + GroupHandler gh = delegate(PwGroup pg) + { + ReorderObjectList(pg.Groups, ppOrgStructure, + ppSrcStructure, false); + return true; + }; + + ReorderObjectList(m_pgRootGroup.Groups, ppOrgStructure, + ppSrcStructure, false); + m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, null); + } + + private void ReorderEntries(PwObjectPool ppOrgStructure, + PwObjectPool ppSrcStructure) + { + 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; + +#if DEBUG + PwObjectList vOrgListItems = vItems.CloneShallow(); +#endif + + Queue> qToDo = new Queue>(); + qToDo.Enqueue(new KeyValuePair(0, vItems.UCount - 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; } + + Queue qRelBefore = new Queue(); + Queue qRelAfter = new Queue(); + uint uPivot = FindLocationChangedPivot(vItems, kvp, ppOrgStructure, + ppSrcStructure, qRelBefore, qRelAfter, bEntries); + T ptPivot = vItems.GetAt(uPivot); + + List vToSort = vItems.GetRange(kvp.Key, kvp.Value); + Queue qBefore = new Queue(); + Queue qAfter = new Queue(); + bool bBefore = true; + + foreach(T pt in vToSort) + { + if(pt == ptPivot) { bBefore = false; continue; } + + bool bAdded = false; + foreach(PwUuid puBefore in qRelBefore) + { + if(puBefore.EqualsValue(pt.Uuid)) + { + qBefore.Enqueue(pt); + bAdded = true; + break; + } + } + if(bAdded) continue; + + 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((iNewPivot - 1) > (int)kvp.Key) + qToDo.Enqueue(new KeyValuePair(kvp.Key, + (uint)(iNewPivot - 1))); + + if((iNewPivot + 1) < (int)kvp.Value) + qToDo.Enqueue(new KeyValuePair((uint)(iNewPivot + 1), + kvp.Value)); + } + +#if DEBUG + foreach(T ptItem in vOrgListItems) + { + Debug.Assert(vItems.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 + { + uint uPosMax = kvpRange.Key; + DateTime dtMax = DateTime.MinValue; + List vNeighborSrc = null; + + for(uint u = kvpRange.Key; u <= kvpRange.Value; ++u) + { + 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)) + { + 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); + } + } + + GetNeighborItems(vNeighborSrc, vItems.GetAt(uPosMax).Uuid, qBefore, qAfter); + return uPosMax; + } + + private static void GetNeighborItems(List vItems, + PwUuid pwPivot, Queue qBefore, Queue qAfter) + { + 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) + { + PwUuid pw = vItems[i].Uuid; + + if(pw.EqualsValue(pwPivot)) bBefore = false; + else if(bBefore) qBefore.Enqueue(pw); + else qAfter.Enqueue(pw); + } + Debug.Assert(bBefore == false); + } + + /// + /// 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 + { + 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((m_slStatus != null) && !m_slStatus.ContinueWork()) return false; + + T ptFirst = vItems.GetAt(0); + // IStructureItem ptOrg = pgOrgStructure.FindObject(ptFirst.Uuid, true, bEntries); + IStructureItem ptOrg = ppOrgStructure.Get(ptFirst.Uuid); + if(ptOrg == null) return true; + // IStructureItem ptSrc = pgSrcStructure.FindObject(ptFirst.Uuid, true, bEntries); + IStructureItem ptSrc = ppSrcStructure.Get(ptFirst.Uuid); + if(ptSrc == null) return true; + + if(ptFirst.ParentGroup == null) { Debug.Assert(false); return true; } + PwGroup pgOrgParent = ptOrg.ParentGroup; + if(pgOrgParent == null) return true; // Root might be in tree + PwGroup pgSrcParent = ptSrc.ParentGroup; + if(pgSrcParent == null) return true; // Root might be in tree + + if(!ptFirst.ParentGroup.Uuid.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) + { + 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; + } + + return false; + } + + private void MergeInDbProperties(PwDatabase pwSource, PwMergeMethod mm) + { + if(pwSource == 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)) + { + m_strName = pwSource.m_strName; + m_dtNameChanged = pwSource.m_dtNameChanged; + } + + if(bForce || (pwSource.m_dtDescChanged > m_dtDescChanged)) + { + m_strDesc = pwSource.m_strDesc; + m_dtDescChanged = pwSource.m_dtDescChanged; + } + + if(bForce || (pwSource.m_dtDefaultUserChanged > m_dtDefaultUserChanged)) + { + m_strDefaultUserName = pwSource.m_strDefaultUserName; + m_dtDefaultUserChanged = pwSource.m_dtDefaultUserChanged; + } + + if(bForce) m_clr = pwSource.m_clr; + + PwUuid pwPrefBin = m_pwRecycleBin, pwAltBin = pwSource.m_pwRecycleBin; + if(bForce || (pwSource.m_dtRecycleBinChanged > m_dtRecycleBinChanged)) + { + pwPrefBin = pwSource.m_pwRecycleBin; + pwAltBin = m_pwRecycleBin; + m_bUseRecycleBin = pwSource.m_bUseRecycleBin; + m_dtRecycleBinChanged = pwSource.m_dtRecycleBinChanged; + } + if(m_pgRootGroup.FindGroup(pwPrefBin, true) != null) + m_pwRecycleBin = pwPrefBin; + else if(m_pgRootGroup.FindGroup(pwAltBin, true) != null) + 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)) + { + pwPrefTmp = pwSource.m_pwEntryTemplatesGroup; + pwAltTmp = m_pwEntryTemplatesGroup; + m_dtEntryTemplatesChanged = pwSource.m_dtEntryTemplatesChanged; + } + if(m_pgRootGroup.FindGroup(pwPrefTmp, true) != null) + m_pwEntryTemplatesGroup = pwPrefTmp; + else if(m_pgRootGroup.FindGroup(pwAltTmp, true) != null) + m_pwEntryTemplatesGroup = pwAltTmp; + else m_pwEntryTemplatesGroup = PwUuid.Zero; // Debug.Assert(false); + } + + private void MergeEntryHistory(PwEntry pe, PwEntry peSource, + PwMergeMethod mm) + { + if(!pe.Uuid.EqualsValue(peSource.Uuid)) { Debug.Assert(false); return; } + + if(pe.History.UCount == peSource.History.UCount) + { + bool bEqual = true; + for(uint uEnum = 0; uEnum < pe.History.UCount; ++uEnum) + { + if(pe.History.GetAt(uEnum).LastModificationTime != + peSource.History.GetAt(uEnum).LastModificationTime) + { + bEqual = false; + break; + } + } + + if(bEqual) return; + } + + if((m_slStatus != null) && !m_slStatus.ContinueWork()) return; + + SortedList list = new SortedList(); + foreach(PwEntry peOrg in pe.History) + { + list[peOrg.LastModificationTime] = peOrg; + } + + foreach(PwEntry peSrc in peSource.History) + { + DateTime dt = peSrc.LastModificationTime; + if(list.ContainsKey(dt)) + { + if(mm == PwMergeMethod.OverwriteExisting) + list[dt] = peSrc.CloneDeep(); + } + else list[dt] = peSrc.CloneDeep(); + } + + pe.History.Clear(); + foreach(KeyValuePair kvpCur in list) + { + Debug.Assert(kvpCur.Value.Uuid.EqualsValue(pe.Uuid)); + Debug.Assert(kvpCur.Value.History.UCount == 0); + pe.History.Add(kvpCur.Value); + } + } + + public bool MaintainBackups() + { + if(m_pgRootGroup == null) { Debug.Assert(false); return false; } + + bool bDeleted = false; + EntryHandler eh = delegate(PwEntry pe) + { + if(pe.MaintainBackups(this)) bDeleted = true; + return true; + }; + + m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, null, eh); + return bDeleted; + } + + /* /// + /// Synchronize current database with another one. + /// + /// Source file. + public void Synchronize(string strFile) + { + PwDatabase pwSource = new PwDatabase(); + + IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFile); + pwSource.Open(ioc, m_pwUserKey, null); + + MergeIn(pwSource, PwMergeMethod.Synchronize); + } */ + + /// + /// Get the index of a custom icon. + /// + /// ID of the icon. + /// Index of the icon. + public int GetCustomIconIndex(PwUuid pwIconId) + { + int nIndex = 0; + + foreach(PwCustomIcon pwci in m_vCustomIcons) + { + if(pwci.Uuid.EqualsValue(pwIconId)) + return nIndex; + + ++nIndex; + } + + // 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 Image GetCustomIcon(PwUuid pwIconId) + { + int nIndex = GetCustomIconIndex(pwIconId); + + if(nIndex >= 0) return m_vCustomIcons[nIndex].Image; + else { Debug.Assert(false); return null; } + } + + public bool DeleteCustomIcons(List vUuidsToDelete) + { + Debug.Assert(vUuidsToDelete != null); + if(vUuidsToDelete == null) throw new ArgumentNullException("vUuidsToDelete"); + if(vUuidsToDelete.Count <= 0) return true; + + GroupHandler gh = delegate(PwGroup pg) + { + PwUuid uuidThis = pg.CustomIconUuid; + if(uuidThis.EqualsValue(PwUuid.Zero)) return true; + + foreach(PwUuid uuidDelete in vUuidsToDelete) + { + if(uuidThis.EqualsValue(uuidDelete)) + { + pg.CustomIconUuid = PwUuid.Zero; + break; + } + } + + return true; + }; + + EntryHandler eh = delegate(PwEntry pe) + { + RemoveCustomIconUuid(pe, vUuidsToDelete); + return true; + }; + + gh(m_pgRootGroup); + if(!m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh)) + { + Debug.Assert(false); + return false; + } + + foreach(PwUuid pwUuid in vUuidsToDelete) + { + int nIndex = GetCustomIconIndex(pwUuid); + if(nIndex >= 0) m_vCustomIcons.RemoveAt(nIndex); + } + + return true; + } + + private static void RemoveCustomIconUuid(PwEntry pe, List vToDelete) + { + PwUuid uuidThis = pe.CustomIconUuid; + if(uuidThis.EqualsValue(PwUuid.Zero)) return; + + foreach(PwUuid uuidDelete in vToDelete) + { + if(uuidThis.EqualsValue(uuidDelete)) + { + pe.CustomIconUuid = PwUuid.Zero; + break; + } + } + + foreach(PwEntry peHistory in pe.History) + RemoveCustomIconUuid(peHistory, vToDelete); + } + + private bool ValidateUuidUniqueness() + { +#if DEBUG + List l = new List(); + bool bAllUnique = true; + + GroupHandler gh = delegate(PwGroup pg) + { + foreach(PwUuid u in l) + bAllUnique &= !pg.Uuid.EqualsValue(u); + l.Add(pg.Uuid); + return bAllUnique; + }; + + EntryHandler eh = delegate(PwEntry pe) + { + foreach(PwUuid u in l) + bAllUnique &= !pe.Uuid.EqualsValue(u); + l.Add(pe.Uuid); + return bAllUnique; + }; + + m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh); + return bAllUnique; +#else + return true; +#endif + } + + /* public void CreateBackupFile(IStatusLogger sl) + { + if(sl != null) sl.SetText(KLRes.CreatingBackupFile, LogStatusType.Info); + + IOConnectionInfo iocBk = m_ioSource.CloneDeep(); + iocBk.Path += StrBackupExtension; + + bool bMadeUnhidden = UrlUtil.UnhideFile(iocBk.Path); + + bool bFastCopySuccess = false; + if(m_ioSource.IsLocalFile() && (m_ioSource.UserName.Length == 0) && + (m_ioSource.Password.Length == 0)) + { + try + { + string strFile = m_ioSource.Path + StrBackupExtension; + File.Copy(m_ioSource.Path, strFile, true); + bFastCopySuccess = true; + } + catch(Exception) { Debug.Assert(false); } + } + + if(bFastCopySuccess == false) + { + using(Stream sIn = IOConnection.OpenRead(m_ioSource)) + { + using(Stream sOut = IOConnection.OpenWrite(iocBk)) + { + MemUtil.CopyStream(sIn, sOut); + sOut.Close(); + } + + sIn.Close(); + } + } + + if(bMadeUnhidden) UrlUtil.HideFile(iocBk.Path, true); // Hide again + } */ + + /* private static void RemoveData(PwGroup pg) + { + EntryHandler eh = delegate(PwEntry pe) + { + pe.AutoType.Clear(); + pe.Binaries.Clear(); + pe.History.Clear(); + pe.Strings.Clear(); + return true; + }; + + pg.TraverseTree(TraversalMethod.PreOrder, null, eh); + } */ + + public uint DeleteDuplicateEntries(IStatusLogger sl) + { + uint uDeleted = 0; + + PwGroup pgRecycleBin = null; + if(m_bUseRecycleBin) + pgRecycleBin = m_pgRootGroup.FindGroup(m_pwRecycleBin, true); + + DateTime dtNow = DateTime.Now; + PwObjectList l = m_pgRootGroup.GetEntries(true); + int i = 0; + while(true) + { + if(i >= ((int)l.UCount - 1)) break; + + if(sl != null) + { + long lCnt = (long)l.UCount, li = (long)i; + long nArTotal = (lCnt * lCnt) / 2L; + long nArCur = li * lCnt - ((li * li) / 2L); + long nArPct = (nArCur * 100L) / nArTotal; + if(nArPct < 0) nArPct = 0; + if(nArPct > 100) nArPct = 100; + if(!sl.SetProgress((uint)nArPct)) break; + } + + PwEntry peA = l.GetAt((uint)i); + + for(uint j = (uint)i + 1; j < l.UCount; ++j) + { + PwEntry peB = l.GetAt(j); + if(!DupEntriesEqual(peA, peB)) continue; + + bool bDeleteA = (peA.LastModificationTime <= peB.LastModificationTime); + if(pgRecycleBin != null) + { + bool bAInBin = peA.IsContainedIn(pgRecycleBin); + bool bBInBin = peB.IsContainedIn(pgRecycleBin); + + if(bAInBin && !bBInBin) bDeleteA = true; + else if(bBInBin && !bAInBin) bDeleteA = false; + } + + if(bDeleteA) + { + peA.ParentGroup.Entries.Remove(peA); + m_vDeletedObjects.Add(new PwDeletedObject(peA.Uuid, dtNow)); + + l.RemoveAt((uint)i); + --i; + } + else + { + peB.ParentGroup.Entries.Remove(peB); + m_vDeletedObjects.Add(new PwDeletedObject(peB.Uuid, dtNow)); + + l.RemoveAt(j); + } + + ++uDeleted; + break; + } + + ++i; + } + + return uDeleted; + } + + private static List m_lStdFields = null; + private static bool DupEntriesEqual(PwEntry a, PwEntry b) + { + if(m_lStdFields == null) m_lStdFields = PwDefs.GetStandardFields(); + + foreach(string strStdKey in m_lStdFields) + { + string strA = a.Strings.ReadSafe(strStdKey); + string strB = b.Strings.ReadSafe(strStdKey); + if(!strA.Equals(strB)) return false; + } + + foreach(KeyValuePair kvpA in a.Strings) + { + if(PwDefs.IsStandardField(kvpA.Key)) continue; + + ProtectedString psB = b.Strings.Get(kvpA.Key); + if(psB == null) return false; + + // Ignore protection setting, compare values only + if(!kvpA.Value.ReadString().Equals(psB.ReadString())) return false; + } + + foreach(KeyValuePair kvpB in b.Strings) + { + if(PwDefs.IsStandardField(kvpB.Key)) continue; + + ProtectedString psA = a.Strings.Get(kvpB.Key); + if(psA == null) return false; + + // Must be equal by logic + Debug.Assert(kvpB.Value.ReadString().Equals(psA.ReadString())); + } + + if(a.Binaries.UCount != b.Binaries.UCount) return false; + foreach(KeyValuePair kvpBin in a.Binaries) + { + ProtectedBinary pbB = b.Binaries.Get(kvpBin.Key); + if(pbB == null) return false; + + // Ignore protection setting, compare values only + byte[] pbDataA = kvpBin.Value.ReadData(); + byte[] pbDataB = pbB.ReadData(); + bool bBinEq = MemUtil.ArraysEqual(pbDataA, pbDataB); + MemUtil.ZeroByteArray(pbDataA); + MemUtil.ZeroByteArray(pbDataB); + if(!bBinEq) return false; + } + + return true; + } + + public uint DeleteEmptyGroups() + { + uint uDeleted = 0; + + PwObjectList l = m_pgRootGroup.GetGroups(true); + int iStart = (int)l.UCount - 1; + for(int i = iStart; i >= 0; --i) + { + PwGroup pg = l.GetAt((uint)i); + if((pg.Groups.UCount > 0) || (pg.Entries.UCount > 0)) continue; + + pg.ParentGroup.Groups.Remove(pg); + m_vDeletedObjects.Add(new PwDeletedObject(pg.Uuid, DateTime.Now)); + + ++uDeleted; + } + + return uDeleted; + } + + public uint DeleteUnusedCustomIcons() + { + List lToDelete = new List(); + foreach(PwCustomIcon pwci in m_vCustomIcons) + lToDelete.Add(pwci.Uuid); + + GroupHandler gh = delegate(PwGroup pg) + { + PwUuid pwUuid = pg.CustomIconUuid; + if((pwUuid == null) || pwUuid.EqualsValue(PwUuid.Zero)) return true; + + for(int i = 0; i < lToDelete.Count; ++i) + { + if(lToDelete[i].EqualsValue(pwUuid)) + { + lToDelete.RemoveAt(i); + break; + } + } + + return true; + }; + + EntryHandler eh = delegate(PwEntry pe) + { + PwUuid pwUuid = pe.CustomIconUuid; + if((pwUuid == null) || pwUuid.EqualsValue(PwUuid.Zero)) return true; + + for(int i = 0; i < lToDelete.Count; ++i) + { + if(lToDelete[i].EqualsValue(pwUuid)) + { + lToDelete.RemoveAt(i); + break; + } + } + + return true; + }; + + gh(m_pgRootGroup); + m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh); + + uint uDeleted = 0; + foreach(PwUuid pwDel in lToDelete) + { + int nIndex = GetCustomIconIndex(pwDel); + if(nIndex < 0) { Debug.Assert(false); continue; } + + m_vCustomIcons.RemoveAt(nIndex); + ++uDeleted; + } + + if(uDeleted > 0) m_bUINeedsIconUpdate = true; + return uDeleted; + } + } +} diff --git a/src/KeePassLib2Android/PwDefs.cs b/src/KeePassLib2Android/PwDefs.cs new file mode 100644 index 00000000..229ee44c --- /dev/null +++ b/src/KeePassLib2Android/PwDefs.cs @@ -0,0 +1,461 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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.Xml.Serialization; +using System.ComponentModel; +using System.Diagnostics; + +using KeePassLib.Delegates; +using KeePassLib.Interfaces; + +namespace KeePassLib +{ + /// + /// Contains KeePassLib-global definitions and enums. + /// + public static class PwDefs + { + /// + /// The product name. + /// + public const string ProductName = "KeePass Password Safe"; + + /// + /// A short, simple string representing the product name. The string + /// should contain no spaces, directory separator characters, etc. + /// + public const string ShortProductName = "KeePass"; + + internal const string UnixName = "keepass2"; + internal const string ResClass = "KeePass2"; // With initial capital + + /// + /// Version, encoded as 32-bit unsigned integer. + /// 2.00 = 0x02000000, 2.01 = 0x02000100, ..., 2.18 = 0x02010800. + /// As of 2.19, the version is encoded component-wise per byte, + /// e.g. 2.19 = 0x02130000. + /// It is highly recommended to use FileVersion64 instead. + /// + public const uint Version32 = 0x02140100; + + /// + /// Version, encoded as 64-bit unsigned integer + /// (component-wise, 16 bits per component). + /// + public const ulong FileVersion64 = 0x0002001400010000UL; + + /// + /// Version, encoded as string. + /// + public const string VersionString = "2.20.1"; + + public const string Copyright = @"Copyright © 2003-2012 Dominik Reichl"; + + /// + /// Product website URL. Terminated by a forward slash. + /// + public const string HomepageUrl = "http://keepass.info/"; + + /// + /// Product donations URL. + /// + public const string DonationsUrl = "http://keepass.info/donate.html"; + + /// + /// URL to the online plugins page. + /// + public const string PluginsUrl = "http://keepass.info/plugins.html"; + + /// + /// URL to the online translations page. + /// + public const string TranslationsUrl = "http://keepass.info/translations.html"; + + /// + /// 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"; + + /// + /// URL to the root path of the online KeePass help. Terminated by + /// a forward slash. + /// + public const string HelpUrl = "http://keepass.info/help/"; + + /// + /// A DateTime object that represents the time when the assembly + /// was loaded. + /// + public static readonly DateTime DtDefaultNow = DateTime.Now; + + /// + /// Default number of master key encryption/transformation rounds (making dictionary attacks harder). + /// + public const ulong DefaultKeyEncryptionRounds = 6000; + + /// + /// Default identifier string for the title field. Should not contain + /// spaces, tabs or other whitespace. + /// + public const string TitleField = "Title"; + + /// + /// Default identifier string for the user name field. Should not contain + /// spaces, tabs or other whitespace. + /// + public const string UserNameField = "UserName"; + + /// + /// Default identifier string for the password field. Should not contain + /// spaces, tabs or other whitespace. + /// + public const string PasswordField = "Password"; + + /// + /// Default identifier string for the URL field. Should not contain + /// spaces, tabs or other whitespace. + /// + public const string UrlField = "URL"; + + /// + /// Default identifier string for the notes field. Should not contain + /// spaces, tabs or other whitespace. + /// + public const string NotesField = "Notes"; + + /// + /// Default identifier string for the field which will contain TAN indices. + /// + public const string TanIndexField = UserNameField; + + /// + /// Default title of an entry that is really a TAN entry. + /// + public const string TanTitle = @""; + + /// + /// Prefix of a custom auto-type string field. + /// + public const string AutoTypeStringPrefix = "S:"; + + /// + /// Default string representing a hidden password. + /// + public const string HiddenPassword = "********"; + + /// + /// Default auto-type keystroke sequence. If no custom sequence is + /// specified, this sequence is used. + /// + public const string DefaultAutoTypeSequence = @"{USERNAME}{TAB}{PASSWORD}{ENTER}"; + + /// + /// Default auto-type keystroke sequence for TAN entries. If no custom + /// sequence is specified, this sequence is used. + /// + public const string DefaultAutoTypeSequenceTan = @"{PASSWORD}"; + + /// + /// Check if a name is a standard field name. + /// + /// Input field name. + /// Returns true, if the field name is a standard + /// field name (title, user name, password, ...), otherwise false. + public static bool IsStandardField(string strFieldName) + { + Debug.Assert(strFieldName != null); if(strFieldName == null) return false; + + if(strFieldName.Equals(TitleField)) return true; + if(strFieldName.Equals(UserNameField)) return true; + if(strFieldName.Equals(PasswordField)) return true; + if(strFieldName.Equals(UrlField)) return true; + if(strFieldName.Equals(NotesField)) return true; + + return false; + } + + public static List GetStandardFields() + { + List l = new List(); + + l.Add(TitleField); + l.Add(UserNameField); + l.Add(PasswordField); + l.Add(UrlField); + l.Add(NotesField); + + return l; + } + + /// + /// Check if an entry is a TAN. + /// + /// Password entry. + /// Returns true if the entry is a TAN. + public static bool IsTanEntry(PwEntry pe) + { + Debug.Assert(pe != null); if(pe == null) return false; + + return (pe.Strings.ReadSafe(PwDefs.TitleField) == TanTitle); + } + } + + #pragma warning disable 1591 // Missing XML comments warning + /// + /// Search parameters for group and entry searches. + /// + public sealed class SearchParameters + { + private string m_strText = string.Empty; + [DefaultValue("")] + public string SearchString + { + get { return m_strText; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strText = value; + } + } + + private bool m_bRegex = false; + [DefaultValue(false)] + public bool RegularExpression + { + get { return m_bRegex; } + set { m_bRegex = value; } + } + + private bool m_bSearchInTitles = true; + [DefaultValue(true)] + public bool SearchInTitles + { + get { return m_bSearchInTitles; } + set { m_bSearchInTitles = value; } + } + + private bool m_bSearchInUserNames = true; + [DefaultValue(true)] + public bool SearchInUserNames + { + get { return m_bSearchInUserNames; } + set { m_bSearchInUserNames = value; } + } + + private bool m_bSearchInPasswords = false; + [DefaultValue(false)] + public bool SearchInPasswords + { + get { return m_bSearchInPasswords; } + set { m_bSearchInPasswords = value; } + } + + private bool m_bSearchInUrls = true; + [DefaultValue(true)] + public bool SearchInUrls + { + get { return m_bSearchInUrls; } + set { m_bSearchInUrls = value; } + } + + private bool m_bSearchInNotes = true; + [DefaultValue(true)] + public bool SearchInNotes + { + get { return m_bSearchInNotes; } + set { m_bSearchInNotes = value; } + } + + private bool m_bSearchInOther = true; + [DefaultValue(true)] + public bool SearchInOther + { + get { return m_bSearchInOther; } + set { m_bSearchInOther = value; } + } + + private bool m_bSearchInUuids = false; + [DefaultValue(false)] + public bool SearchInUuids + { + get { return m_bSearchInUuids; } + set { m_bSearchInUuids = value; } + } + + private bool m_bSearchInGroupNames = false; + [DefaultValue(false)] + public bool SearchInGroupNames + { + get { return m_bSearchInGroupNames; } + set { m_bSearchInGroupNames = value; } + } + + private bool m_bSearchInTags = true; + [DefaultValue(true)] + public bool SearchInTags + { + get { return m_bSearchInTags; } + set { m_bSearchInTags = value; } + } + + private StringComparison m_scType = StringComparison.InvariantCultureIgnoreCase; + /// + /// String comparison type. Specifies the condition when the specified + /// text matches a group/entry string. + /// + public StringComparison ComparisonMode + { + get { return m_scType; } + set { m_scType = value; } + } + + private bool m_bExcludeExpired = false; + [DefaultValue(false)] + public bool ExcludeExpired + { + get { return m_bExcludeExpired; } + set { m_bExcludeExpired = value; } + } + + private bool m_bRespectEntrySearchingDisabled = true; + [DefaultValue(true)] + public bool RespectEntrySearchingDisabled + { + get { return m_bRespectEntrySearchingDisabled; } + set { m_bRespectEntrySearchingDisabled = value; } + } + + private StrPwEntryDelegate m_fnDataTrf = null; + [XmlIgnore] + public StrPwEntryDelegate DataTransformationFn + { + get { return m_fnDataTrf; } + set { m_fnDataTrf = value; } + } + + private string m_strDataTrf = string.Empty; + /// + /// Only for serialization. + /// + [DefaultValue("")] + public string DataTransformation + { + get { return m_strDataTrf; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strDataTrf = value; + } + } + + [XmlIgnore] + public static SearchParameters None + { + get + { + SearchParameters sp = new SearchParameters(); + + // sp.m_strText = string.Empty; + // sp.m_bRegex = false; + sp.m_bSearchInTitles = false; + sp.m_bSearchInUserNames = false; + // sp.m_bSearchInPasswords = false; + sp.m_bSearchInUrls = false; + sp.m_bSearchInNotes = false; + sp.m_bSearchInOther = false; + // sp.m_bSearchInUuids = false; + // sp.SearchInGroupNames = false; + sp.m_bSearchInTags = false; + // sp.m_scType = StringComparison.InvariantCultureIgnoreCase; + // sp.m_bExcludeExpired = false; + // m_bRespectEntrySearchingDisabled = true; + + return sp; + } + } + + /// + /// Construct a new search parameters object. + /// + public SearchParameters() + { + } + + public SearchParameters Clone() + { + return (SearchParameters)this.MemberwiseClone(); + } + } + #pragma warning restore 1591 // Missing XML comments warning + + #pragma warning disable 1591 // Missing XML comments warning + /// + /// Memory protection configuration structure (for default fields). + /// + public sealed class MemoryProtectionConfig : IDeepCloneable + { + public bool ProtectTitle = false; + public bool ProtectUserName = false; + public bool ProtectPassword = true; + public bool ProtectUrl = false; + public bool ProtectNotes = false; + + // public bool AutoEnableVisualHiding = false; + + public MemoryProtectionConfig CloneDeep() + { + return (MemoryProtectionConfig)this.MemberwiseClone(); + } + + public bool GetProtection(string strField) + { + if(strField == PwDefs.TitleField) return this.ProtectTitle; + if(strField == PwDefs.UserNameField) return this.ProtectUserName; + if(strField == PwDefs.PasswordField) return this.ProtectPassword; + if(strField == PwDefs.UrlField) return this.ProtectUrl; + if(strField == PwDefs.NotesField) return this.ProtectNotes; + + return false; + } + } + #pragma warning restore 1591 // Missing XML comments warning + + public sealed class ObjectTouchedEventArgs : EventArgs + { + private object m_o; + public object Object { get { return m_o; } } + + private bool m_bModified; + public bool Modified { get { return m_bModified; } } + + private bool m_bParentsTouched; + public bool ParentsTouched { get { return m_bParentsTouched; } } + + public ObjectTouchedEventArgs(object o, bool bModified, + bool bParentsTouched) + { + m_o = o; + m_bModified = bModified; + m_bParentsTouched = bParentsTouched; + } + } +} diff --git a/src/KeePassLib2Android/PwDeletedObject.cs b/src/KeePassLib2Android/PwDeletedObject.cs new file mode 100644 index 00000000..0b549efa --- /dev/null +++ b/src/KeePassLib2Android/PwDeletedObject.cs @@ -0,0 +1,86 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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 KeePassLib.Interfaces; + +namespace KeePassLib +{ + /// + /// Represents an object that has been deleted. + /// + public sealed class PwDeletedObject : IDeepCloneable + { + private PwUuid m_uuid = PwUuid.Zero; + /// + /// UUID of the entry that has been deleted. + /// + public PwUuid Uuid + { + get { return m_uuid; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_uuid = value; + } + } + + private DateTime m_dtDeletionTime = PwDefs.DtDefaultNow; + /// + /// The date/time when the entry has been deleted. + /// + public DateTime DeletionTime + { + get { return m_dtDeletionTime; } + set { m_dtDeletionTime = value; } + } + + /// + /// Construct a new PwDeletedObject object. + /// + public PwDeletedObject() + { + } + + public PwDeletedObject(PwUuid uuid, DateTime dtDeletionTime) + { + if(uuid == null) throw new ArgumentNullException("uuid"); + + m_uuid = uuid; + m_dtDeletionTime = dtDeletionTime; + } + + /// + /// Clone the object. + /// + /// Value copy of the current object. + public PwDeletedObject CloneDeep() + { + PwDeletedObject pdo = new PwDeletedObject(); + + pdo.m_uuid = m_uuid; // PwUuid objects are immutable + pdo.m_dtDeletionTime = m_dtDeletionTime; + + return pdo; + } + } +} diff --git a/src/KeePassLib2Android/PwEntry.cs b/src/KeePassLib2Android/PwEntry.cs new file mode 100644 index 00000000..b26bec6f --- /dev/null +++ b/src/KeePassLib2Android/PwEntry.cs @@ -0,0 +1,874 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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.Xml; +using System.Drawing; + +using KeePassLib.Collections; +using KeePassLib.Interfaces; +using KeePassLib.Security; +using KeePassLib.Utility; + +namespace KeePassLib +{ + /// + /// A class representing a password entry. A password entry consists of several + /// fields like title, user name, password, etc. Each password entry has a + /// unique ID (UUID). + /// + public sealed class PwEntry : ITimeLogger, IStructureItem, IDeepCloneable + { + private PwUuid m_uuid = PwUuid.Zero; + private PwGroup m_pParentGroup = null; + private DateTime m_tParentGroupLastMod = PwDefs.DtDefaultNow; + + private ProtectedStringDictionary m_listStrings = new ProtectedStringDictionary(); + private ProtectedBinaryDictionary m_listBinaries = new ProtectedBinaryDictionary(); + private AutoTypeConfig m_listAutoType = new AutoTypeConfig(); + private PwObjectList m_listHistory = new PwObjectList(); + + private PwIcon m_pwIcon = PwIcon.Key; + private PwUuid m_pwCustomIconID = PwUuid.Zero; + + private Color m_clrForeground = Color.Empty; + private Color m_clrBackground = Color.Empty; + + private DateTime m_tCreation = PwDefs.DtDefaultNow; + private DateTime m_tLastMod = PwDefs.DtDefaultNow; + private DateTime m_tLastAccess = PwDefs.DtDefaultNow; + private DateTime m_tExpire = PwDefs.DtDefaultNow; + private bool m_bExpires = false; + private ulong m_uUsageCount = 0; + + private string m_strOverrideUrl = string.Empty; + + private List m_vTags = new List(); + + /// + /// UUID of this entry. + /// + public PwUuid Uuid + { + get { return m_uuid; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + m_uuid = value; + } + } + + /// + /// Reference to a group which contains the current entry. + /// + public PwGroup ParentGroup + { + get { return m_pParentGroup; } + + /// Plugins: use PwGroup.AddEntry instead. + internal set { m_pParentGroup = value; } + } + + /// + /// The date/time when the location of the object was last changed. + /// + public DateTime LocationChanged + { + get { return m_tParentGroupLastMod; } + set { m_tParentGroupLastMod = value; } + } + + /// + /// Get or set all entry strings. + /// + public ProtectedStringDictionary Strings + { + get { return m_listStrings; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + m_listStrings = value; + } + } + + /// + /// Get or set all entry binaries. + /// + public ProtectedBinaryDictionary Binaries + { + get { return m_listBinaries; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + m_listBinaries = value; + } + } + + /// + /// Get or set all auto-type window/keystroke sequence associations. + /// + public AutoTypeConfig AutoType + { + get { return m_listAutoType; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + m_listAutoType = value; + } + } + + /// + /// Get all previous versions of this entry (backups). + /// + public PwObjectList History + { + get { return m_listHistory; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + m_listHistory = value; + } + } + + /// + /// Image ID specifying the icon that will be used for this entry. + /// + public PwIcon IconId + { + get { return m_pwIcon; } + set { m_pwIcon = value; } + } + + /// + /// Get the custom icon ID. This value is 0, if no custom icon is + /// being used (i.e. the icon specified by the IconID property + /// should be displayed). + /// + public PwUuid CustomIconUuid + { + get { return m_pwCustomIconID; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + m_pwCustomIconID = value; + } + } + + /// + /// Get or set the foreground color of this entry. + /// + public Color ForegroundColor + { + get { return m_clrForeground; } + set { m_clrForeground = value; } + } + + /// + /// Get or set the background color of this entry. + /// + public Color BackgroundColor + { + get { return m_clrBackground; } + set { m_clrBackground = value; } + } + + /// + /// The date/time when this entry was created. + /// + public DateTime CreationTime + { + get { return m_tCreation; } + 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. + /// + public DateTime LastModificationTime + { + get { return m_tLastMod; } + set { m_tLastMod = value; } + } + + /// + /// The date/time when this entry expires. Use the Expires property + /// to specify if the entry does actually expire or not. + /// + public DateTime ExpiryTime + { + get { return m_tExpire; } + set { m_tExpire = value; } + } + + /// + /// Specifies whether the entry expires or not. + /// + public bool Expires + { + get { return m_bExpires; } + set { m_bExpires = value; } + } + + /// + /// Get or set the usage count of the entry. To increase the usage + /// count by one, use the Touch function. + /// + public ulong UsageCount + { + get { return m_uUsageCount; } + set { m_uUsageCount = value; } + } + + /// + /// Entry-specific override URL. If this string is non-empty, + /// + public string OverrideUrl + { + get { return m_strOverrideUrl; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strOverrideUrl = value; + } + } + + /// + /// List of tags associated with this entry. + /// + public List Tags + { + get { return m_vTags; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_vTags = value; + } + } + + public static EventHandler EntryTouched; + public EventHandler Touched; + + /// + /// Construct a new, empty password entry. Member variables will be initialized + /// to their default values. + /// + /// If true, a new UUID will be created + /// for this entry. If false, the UUID is zero and you must set it + /// manually later. + /// If true, the creation, last modification + /// and last access times will be set to the current system time. + public PwEntry(bool bCreateNewUuid, bool bSetTimes) + { + if(bCreateNewUuid) m_uuid = new PwUuid(true); + + if(bSetTimes) + { + m_tCreation = m_tLastMod = m_tLastAccess = + m_tParentGroupLastMod = DateTime.Now; + } + } + + /// + /// Construct a new, empty password entry. Member variables will be initialized + /// to their default values. + /// + /// Reference to the containing group, this + /// parameter may be null and set later manually. + /// If true, a new UUID will be created + /// for this entry. If false, the UUID is zero and you must set it + /// manually later. + /// If true, the creation, last modification + /// and last access times will be set to the current system time. + [Obsolete("Use a different constructor. To add an entry to a group, use AddEntry of PwGroup.")] + public PwEntry(PwGroup pwParentGroup, bool bCreateNewUuid, bool bSetTimes) + { + m_pParentGroup = pwParentGroup; + + if(bCreateNewUuid) m_uuid = new PwUuid(true); + + if(bSetTimes) + { + m_tCreation = m_tLastMod = m_tLastAccess = + m_tParentGroupLastMod = DateTime.Now; + } + } + + /// + /// Clone the current entry. The returned entry is an exact value copy + /// of the current entry (including UUID and parent group reference). + /// All mutable members are cloned. + /// + /// Exact value clone. All references to mutable values changed. + public PwEntry CloneDeep() + { + PwEntry peNew = new PwEntry(false, false); + + peNew.m_uuid = m_uuid; // PwUuid is immutable + peNew.m_pParentGroup = m_pParentGroup; + peNew.m_tParentGroupLastMod = m_tParentGroupLastMod; + + peNew.m_listStrings = m_listStrings.CloneDeep(); + peNew.m_listBinaries = m_listBinaries.CloneDeep(); + peNew.m_listAutoType = m_listAutoType.CloneDeep(); + peNew.m_listHistory = m_listHistory.CloneDeep(); + + peNew.m_pwIcon = m_pwIcon; + peNew.m_pwCustomIconID = m_pwCustomIconID; + + peNew.m_clrForeground = m_clrForeground; + peNew.m_clrBackground = m_clrBackground; + + peNew.m_tCreation = m_tCreation; + peNew.m_tLastMod = m_tLastMod; + peNew.m_tLastAccess = m_tLastAccess; + peNew.m_tExpire = m_tExpire; + peNew.m_bExpires = m_bExpires; + peNew.m_uUsageCount = m_uUsageCount; + + peNew.m_strOverrideUrl = m_strOverrideUrl; + + peNew.m_vTags = new List(m_vTags); + + return peNew; + } + + public PwEntry CloneStructure() + { + PwEntry peNew = new PwEntry(false, false); + + peNew.m_uuid = m_uuid; // PwUuid is immutable + peNew.m_tParentGroupLastMod = m_tParentGroupLastMod; + // Do not assign m_pParentGroup + + return peNew; + } + + private static PwCompareOptions BuildCmpOpt(bool bIgnoreParentGroup, + bool bIgnoreLastMod, bool bIgnoreLastAccess, bool bIgnoreHistory, + bool bIgnoreThisLastBackup) + { + PwCompareOptions pwOpt = PwCompareOptions.None; + if(bIgnoreParentGroup) pwOpt |= PwCompareOptions.IgnoreParentGroup; + if(bIgnoreLastMod) pwOpt |= PwCompareOptions.IgnoreLastMod; + if(bIgnoreLastAccess) pwOpt |= PwCompareOptions.IgnoreLastAccess; + if(bIgnoreHistory) pwOpt |= PwCompareOptions.IgnoreHistory; + if(bIgnoreThisLastBackup) pwOpt |= PwCompareOptions.IgnoreLastBackup; + return pwOpt; + } + + [Obsolete] + public bool EqualsEntry(PwEntry pe, bool bIgnoreParentGroup, bool bIgnoreLastMod, + bool bIgnoreLastAccess, bool bIgnoreHistory, bool bIgnoreThisLastBackup) + { + return EqualsEntry(pe, BuildCmpOpt(bIgnoreParentGroup, bIgnoreLastMod, + bIgnoreLastAccess, bIgnoreHistory, bIgnoreThisLastBackup), + MemProtCmpMode.None); + } + + [Obsolete] + public bool EqualsEntry(PwEntry pe, bool bIgnoreParentGroup, bool bIgnoreLastMod, + bool bIgnoreLastAccess, bool bIgnoreHistory, bool bIgnoreThisLastBackup, + MemProtCmpMode mpCmpStr) + { + return EqualsEntry(pe, BuildCmpOpt(bIgnoreParentGroup, bIgnoreLastMod, + bIgnoreLastAccess, bIgnoreHistory, bIgnoreThisLastBackup), mpCmpStr); + } + + public bool EqualsEntry(PwEntry pe, PwCompareOptions pwOpt, + MemProtCmpMode mpCmpStr) + { + if(pe == null) { Debug.Assert(false); return false; } + + bool bNeEqStd = ((pwOpt & PwCompareOptions.NullEmptyEquivStd) != + PwCompareOptions.None); + bool bIgnoreLastAccess = ((pwOpt & PwCompareOptions.IgnoreLastAccess) != + PwCompareOptions.None); + bool bIgnoreLastMod = ((pwOpt & PwCompareOptions.IgnoreLastMod) != + PwCompareOptions.None); + + if(!m_uuid.EqualsValue(pe.m_uuid)) return false; + if((pwOpt & PwCompareOptions.IgnoreParentGroup) == PwCompareOptions.None) + { + if(m_pParentGroup != pe.m_pParentGroup) return false; + if(!bIgnoreLastMod && (m_tParentGroupLastMod != pe.m_tParentGroupLastMod)) + return false; + } + + if(!m_listStrings.EqualsDictionary(pe.m_listStrings, pwOpt, mpCmpStr)) + return false; + if(!m_listBinaries.EqualsDictionary(pe.m_listBinaries)) return false; + + if(!m_listAutoType.Equals(pe.m_listAutoType)) return false; + + if((pwOpt & PwCompareOptions.IgnoreHistory) == PwCompareOptions.None) + { + bool bIgnoreLastBackup = ((pwOpt & PwCompareOptions.IgnoreLastBackup) != + PwCompareOptions.None); + + if(!bIgnoreLastBackup && (m_listHistory.UCount != pe.m_listHistory.UCount)) + return false; + if(bIgnoreLastBackup && (m_listHistory.UCount == 0)) + { + Debug.Assert(false); + return false; + } + if(bIgnoreLastBackup && ((m_listHistory.UCount - 1) != pe.m_listHistory.UCount)) + return false; + + PwCompareOptions cmpSub = PwCompareOptions.IgnoreParentGroup; + if(bNeEqStd) cmpSub |= PwCompareOptions.NullEmptyEquivStd; + if(bIgnoreLastMod) cmpSub |= PwCompareOptions.IgnoreLastMod; + if(bIgnoreLastAccess) cmpSub |= PwCompareOptions.IgnoreLastAccess; + + for(uint uHist = 0; uHist < pe.m_listHistory.UCount; ++uHist) + { + if(!m_listHistory.GetAt(uHist).EqualsEntry(pe.m_listHistory.GetAt( + uHist), cmpSub, MemProtCmpMode.None)) + return false; + } + } + + if(m_pwIcon != pe.m_pwIcon) return false; + if(!m_pwCustomIconID.EqualsValue(pe.m_pwCustomIconID)) return false; + + if(m_clrForeground != pe.m_clrForeground) return false; + if(m_clrBackground != pe.m_clrBackground) return false; + + if(m_tCreation != pe.m_tCreation) return false; + if(!bIgnoreLastMod && (m_tLastMod != pe.m_tLastMod)) return false; + if(!bIgnoreLastAccess && (m_tLastAccess != pe.m_tLastAccess)) return false; + if(m_tExpire != pe.m_tExpire) return false; + if(m_bExpires != pe.m_bExpires) return false; + if(!bIgnoreLastAccess && (m_uUsageCount != pe.m_uUsageCount)) return false; + + if(m_strOverrideUrl != pe.m_strOverrideUrl) return false; + + if(m_vTags.Count != pe.m_vTags.Count) return false; + for(int iTag = 0; iTag < m_vTags.Count; ++iTag) + { + if(m_vTags[iTag] != pe.m_vTags[iTag]) return false; + } + + return true; + } + + /// + /// Assign properties to the current entry based on a template entry. + /// + /// Template entry. Must not be null. + /// Only set the properties of the template entry + /// if it is newer than the current one. + /// If true, the history will be + /// copied, too. + /// If true, the + /// LocationChanged property is copied, otherwise not. + public void AssignProperties(PwEntry peTemplate, bool bOnlyIfNewer, + bool bIncludeHistory, bool bAssignLocationChanged) + { + Debug.Assert(peTemplate != null); if(peTemplate == null) throw new ArgumentNullException("peTemplate"); + + if(bOnlyIfNewer && (peTemplate.m_tLastMod < m_tLastMod)) return; + + // Template UUID should be the same as the current one + Debug.Assert(m_uuid.EqualsValue(peTemplate.m_uuid)); + m_uuid = peTemplate.m_uuid; + + 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_pwIcon = peTemplate.m_pwIcon; + m_pwCustomIconID = peTemplate.m_pwCustomIconID; // Immutable + + m_clrForeground = peTemplate.m_clrForeground; + m_clrBackground = peTemplate.m_clrBackground; + + m_tCreation = peTemplate.m_tCreation; + m_tLastMod = peTemplate.m_tLastMod; + m_tLastAccess = peTemplate.m_tLastAccess; + m_tExpire = peTemplate.m_tExpire; + m_bExpires = peTemplate.m_bExpires; + m_uUsageCount = peTemplate.m_uUsageCount; + + m_strOverrideUrl = peTemplate.m_strOverrideUrl; + + m_vTags = new List(peTemplate.m_vTags); + } + + /// + /// Touch the entry. This function updates the internal last access + /// time. If the parameter is true, + /// the last modification time gets updated, too. + /// + /// Modify last modification time. + public void Touch(bool bModified) + { + Touch(bModified, true); + } + + /// + /// Touch the entry. This function updates the internal last access + /// time. If the parameter is true, + /// the last modification time gets updated, too. + /// + /// Modify last modification time. + /// If true, all parent objects + /// get touched, too. + public void Touch(bool bModified, bool bTouchParents) + { + m_tLastAccess = DateTime.Now; + ++m_uUsageCount; + + if(bModified) m_tLastMod = m_tLastAccess; + + if(this.Touched != null) + this.Touched(this, new ObjectTouchedEventArgs(this, + bModified, bTouchParents)); + if(PwEntry.EntryTouched != null) + PwEntry.EntryTouched(this, new ObjectTouchedEventArgs(this, + bModified, bTouchParents)); + + if(bTouchParents && (m_pParentGroup != null)) + m_pParentGroup.Touch(bModified, true); + } + + /// + /// Create a backup of this entry. The backup item doesn't contain any + /// history items. + /// + [Obsolete] + public void CreateBackup() + { + CreateBackup(null); + } + + /// + /// Create a backup of this entry. The backup item doesn't contain any + /// history items. + /// If this parameter isn't null, + /// the history list is maintained automatically (i.e. old backups are + /// deleted if there are too many or the history size is too large). + /// This parameter may be null (no maintenance then). + /// + public void CreateBackup(PwDatabase pwHistMntcSettings) + { + PwEntry peCopy = CloneDeep(); + peCopy.History = new PwObjectList(); // Remove history + + m_listHistory.Add(peCopy); // Must be added at end, see EqualsEntry + + if(pwHistMntcSettings != null) MaintainBackups(pwHistMntcSettings); + } + + /// + /// Restore an entry snapshot from backups. + /// + /// Index of the backup item, to which + /// should be reverted. + [Obsolete] + public void RestoreFromBackup(uint uBackupIndex) + { + RestoreFromBackup(uBackupIndex, null); + } + + /// + /// Restore an entry snapshot from backups. + /// + /// Index of the backup item, to which + /// should be reverted. + /// If this parameter isn't null, + /// the history list is maintained automatically (i.e. old backups are + /// deleted if there are too many or the history size is too large). + /// This parameter may be null (no maintenance then). + public void RestoreFromBackup(uint uBackupIndex, PwDatabase pwHistMntcSettings) + { + Debug.Assert(uBackupIndex < m_listHistory.UCount); + if(uBackupIndex >= m_listHistory.UCount) + throw new ArgumentOutOfRangeException("uBackupIndex"); + + PwEntry pe = m_listHistory.GetAt(uBackupIndex); + Debug.Assert(pe != null); if(pe == null) throw new InvalidOperationException(); + + CreateBackup(pwHistMntcSettings); // Backup current data before restoring + AssignProperties(pe, false, false, false); + } + + public bool HasBackupOfData(PwEntry peData, bool bIgnoreLastMod, + bool bIgnoreLastAccess) + { + if(peData == null) { Debug.Assert(false); return false; } + + PwCompareOptions cmpOpt = (PwCompareOptions.IgnoreParentGroup | + PwCompareOptions.IgnoreHistory | PwCompareOptions.NullEmptyEquivStd); + if(bIgnoreLastMod) cmpOpt |= PwCompareOptions.IgnoreLastMod; + if(bIgnoreLastAccess) cmpOpt |= PwCompareOptions.IgnoreLastAccess; + + foreach(PwEntry pe in m_listHistory) + { + if(pe.EqualsEntry(peData, cmpOpt, MemProtCmpMode.None)) return true; + } + + return false; + } + + /// + /// Delete old history items if there are too many or the history + /// size is too large. + /// If one or more history items have been deleted, true + /// is returned. Otherwise false. + /// + public bool MaintainBackups(PwDatabase pwSettings) + { + if(pwSettings == null) { Debug.Assert(false); return false; } + + bool bDeleted = false; + + int nMaxItems = pwSettings.HistoryMaxItems; + if(nMaxItems >= 0) + { + while(m_listHistory.UCount > (uint)nMaxItems) + { + RemoveOldestBackup(); + bDeleted = true; + } + } + + long lMaxSize = pwSettings.HistoryMaxSize; + if(lMaxSize >= 0) + { + while(true) + { + ulong uHistSize = 0; + foreach(PwEntry pe in m_listHistory) { uHistSize += pe.GetSize(); } + + if(uHistSize > (ulong)lMaxSize) + { + RemoveOldestBackup(); + bDeleted = true; + } + else break; + } + } + + return bDeleted; + } + + private void RemoveOldestBackup() + { + DateTime dtMin = DateTime.MaxValue; + uint idxRemove = uint.MaxValue; + + for(uint u = 0; u < m_listHistory.UCount; ++u) + { + PwEntry pe = m_listHistory.GetAt(u); + if(pe.LastModificationTime < dtMin) + { + idxRemove = u; + dtMin = pe.LastModificationTime; + } + } + + if(idxRemove != uint.MaxValue) m_listHistory.RemoveAt(idxRemove); + } + + public bool GetAutoTypeEnabled() + { + if(!m_listAutoType.Enabled) return false; + + if(m_pParentGroup != null) + return m_pParentGroup.GetAutoTypeEnabledInherited(); + + return PwGroup.DefaultAutoTypeEnabled; + } + + public string GetAutoTypeSequence() + { + string strSeq = m_listAutoType.DefaultSequence; + + PwGroup pg = m_pParentGroup; + while(pg != null) + { + if(strSeq.Length != 0) break; + + strSeq = pg.DefaultAutoTypeSequence; + pg = pg.ParentGroup; + } + + if(strSeq.Length != 0) return strSeq; + + if(PwDefs.IsTanEntry(this)) return PwDefs.DefaultAutoTypeSequenceTan; + return PwDefs.DefaultAutoTypeSequence; + } + + public bool GetSearchingEnabled() + { + if(m_pParentGroup != null) + return m_pParentGroup.GetSearchingEnabledInherited(); + + return PwGroup.DefaultSearchingEnabled; + } + + /// + /// Approximate the total size of this entry in bytes (including + /// strings, binaries and history entries). + /// + /// Size in bytes. + public ulong GetSize() + { + ulong uSize = 128; // Approx fixed length data + + foreach(KeyValuePair kvpStr in m_listStrings) + { + uSize += (ulong)kvpStr.Key.Length; + uSize += (ulong)kvpStr.Value.Length; + } + + foreach(KeyValuePair kvpBin in m_listBinaries) + { + uSize += (ulong)kvpBin.Key.Length; + uSize += kvpBin.Value.Length; + } + + uSize += (ulong)m_listAutoType.DefaultSequence.Length; + foreach(AutoTypeAssociation a in m_listAutoType.Associations) + { + uSize += (ulong)a.WindowName.Length; + uSize += (ulong)a.Sequence.Length; + } + + foreach(PwEntry peHistory in m_listHistory) + uSize += peHistory.GetSize(); + + uSize += (ulong)m_strOverrideUrl.Length; + + foreach(string strTag in m_vTags) + uSize += (ulong)strTag.Length; + + return uSize; + } + + public bool HasTag(string strTag) + { + if(string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; } + + for(int i = 0; i < m_vTags.Count; ++i) + { + if(m_vTags[i].Equals(strTag, StrUtil.CaseIgnoreCmp)) return true; + } + + return false; + } + + public bool AddTag(string strTag) + { + if(string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; } + + for(int i = 0; i < m_vTags.Count; ++i) + { + if(m_vTags[i].Equals(strTag, StrUtil.CaseIgnoreCmp)) return false; + } + + m_vTags.Add(strTag); + return true; + } + + public bool RemoveTag(string strTag) + { + if(string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; } + + for(int i = 0; i < m_vTags.Count; ++i) + { + if(m_vTags[i].Equals(strTag, StrUtil.CaseIgnoreCmp)) + { + m_vTags.RemoveAt(i); + return true; + } + } + + return false; + } + + public bool IsContainedIn(PwGroup pgContainer) + { + PwGroup pgCur = m_pParentGroup; + while(pgCur != null) + { + if(pgCur == pgContainer) return true; + + pgCur = pgCur.ParentGroup; + } + + return false; + } + + public void SetUuid(PwUuid pwNewUuid, bool bAlsoChangeHistoryUuids) + { + this.Uuid = pwNewUuid; + + if(bAlsoChangeHistoryUuids) + { + foreach(PwEntry peHist in m_listHistory) + { + peHist.Uuid = pwNewUuid; + } + } + } + } + + public sealed class PwEntryComparer : IComparer + { + private string m_strFieldName; + private bool m_bCaseInsensitive; + private bool m_bCompareNaturally; + + public PwEntryComparer(string strFieldName, bool bCaseInsensitive, + bool bCompareNaturally) + { + if(strFieldName == null) throw new ArgumentNullException("strFieldName"); + + m_strFieldName = strFieldName; + m_bCaseInsensitive = bCaseInsensitive; + m_bCompareNaturally = bCompareNaturally; + } + + public int Compare(PwEntry a, PwEntry b) + { + string strA = a.Strings.ReadSafe(m_strFieldName); + 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 new file mode 100644 index 00000000..27cde682 --- /dev/null +++ b/src/KeePassLib2Android/PwEnums.cs @@ -0,0 +1,207 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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; + +namespace KeePassLib +{ + /// + /// Compression algorithm specifiers. + /// + public enum PwCompressionAlgorithm + { + /// + /// No compression. + /// + None = 0, + + /// + /// GZip compression. + /// + GZip = 1, + + /// + /// Virtual field: currently known number of algorithms. Should not be used + /// by plugins or libraries -- it's used internally only. + /// + Count = 2 + } + + /// + /// Tree traversal methods. + /// + public enum TraversalMethod + { + /// + /// Don't traverse the tree. + /// + None = 0, + + /// + /// Traverse the tree in pre-order mode, i.e. first visit all items + /// in the current node, then visit all subnodes. + /// + PreOrder = 1 + } + + /// + /// Methods for merging password databases/entries. + /// + public enum PwMergeMethod + { + None = 0, + OverwriteExisting = 1, + KeepExisting = 2, + OverwriteIfNewer = 3, + CreateNewUuids = 4, + Synchronize = 5 + } + + /// + /// Icon identifiers for groups and password entries. + /// + public enum PwIcon + { + Key = 0, + World, + Warning, + NetworkServer, + MarkedDirectory, + UserCommunication, + Parts, + Notepad, + WorldSocket, + Identity, + PaperReady, + Digicam, + IRCommunication, + MultiKeys, + Energy, + Scanner, + WorldStar, + CDRom, + Monitor, + EMail, + Configuration, + ClipboardReady, + PaperNew, + Screen, + EnergyCareful, + EMailBox, + Disk, + Drive, + PaperQ, + TerminalEncrypted, + Console, + Printer, + ProgramIcons, + Run, + Settings, + WorldComputer, + Archive, + Homebanking, + DriveWindows, + Clock, + EMailSearch, + PaperFlag, + Memory, + TrashBin, + Note, + Expired, + Info, + Package, + Folder, + FolderOpen, + FolderPackage, + LockOpen, + PaperLocked, + Checked, + Pen, + Thumbnail, + Book, + List, + UserKey, + Tool, + Home, + Star, + Tux, + Feather, + Apple, + Wiki, + Money, + Certificate, + BlackBerry, + + /// + /// Virtual identifier -- represents the number of icons. + /// + Count + } + + public enum ProxyServerType + { + None = 0, + System = 1, + Manual = 2 + } + + /// + /// Comparison modes for in-memory protected objects. + /// + public enum MemProtCmpMode + { + /// + /// Ignore the in-memory protection states. + /// + None = 0, + + /// + /// Ignore the in-memory protection states of standard + /// objects; do compare in-memory protection states of + /// custom objects. + /// + CustomOnly, + + /// + /// Compare in-memory protection states. + /// + Full + } + + [Flags] + public enum PwCompareOptions + { + None = 0x0, + + /// + /// Empty standard string fields are considered to be the + /// same as non-existing standard string fields. + /// This doesn't affect custom string comparisons. + /// + NullEmptyEquivStd = 0x1, + + IgnoreParentGroup = 0x2, + IgnoreLastAccess = 0x4, + IgnoreLastMod = 0x8, + IgnoreHistory = 0x10, + IgnoreLastBackup = 0x20, + + IgnoreTimes = (IgnoreLastAccess | IgnoreLastMod) + } +} diff --git a/src/KeePassLib2Android/PwGroup.cs b/src/KeePassLib2Android/PwGroup.cs new file mode 100644 index 00000000..791e0fbe --- /dev/null +++ b/src/KeePassLib2Android/PwGroup.cs @@ -0,0 +1,1438 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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.RegularExpressions; + +using KeePassLib.Collections; +using KeePassLib.Delegates; +using KeePassLib.Interfaces; +using KeePassLib.Security; +using KeePassLib.Utility; + +namespace KeePassLib +{ + /// + /// A group containing several password entries. + /// + public sealed class PwGroup : ITimeLogger, IStructureItem, IDeepCloneable + { + public const bool DefaultAutoTypeEnabled = true; + public const bool DefaultSearchingEnabled = true; + + private PwObjectList m_listGroups = new PwObjectList(); + private PwObjectList m_listEntries = new PwObjectList(); + private PwGroup m_pParentGroup = null; + private DateTime m_tParentGroupLastMod = PwDefs.DtDefaultNow; + + private PwUuid m_uuid = PwUuid.Zero; + private string m_strName = string.Empty; + private string m_strNotes = string.Empty; + + private PwIcon m_pwIcon = PwIcon.Folder; + private PwUuid m_pwCustomIconID = PwUuid.Zero; + + private DateTime m_tCreation = PwDefs.DtDefaultNow; + private DateTime m_tLastMod = PwDefs.DtDefaultNow; + private DateTime m_tLastAccess = PwDefs.DtDefaultNow; + private DateTime m_tExpire = PwDefs.DtDefaultNow; + private bool m_bExpires = false; + private ulong m_uUsageCount = 0; + + private bool m_bIsExpanded = true; + private bool m_bVirtual = false; + + private string m_strDefaultAutoTypeSequence = string.Empty; + + private bool? m_bEnableAutoType = null; + private bool? m_bEnableSearching = null; + + private PwUuid m_pwLastTopVisibleEntry = PwUuid.Zero; + + /// + /// UUID of this group. + /// + public PwUuid Uuid + { + get { return m_uuid; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + m_uuid = value; + } + } + + /// + /// The name of this group. Cannot be null. + /// + public string Name + { + get { return m_strName; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + m_strName = value; + } + } + + /// + /// Comments about this group. Cannot be null. + /// + public string Notes + { + get { return m_strNotes; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + m_strNotes = value; + } + } + + /// + /// Icon of the group. + /// + public PwIcon IconId + { + get { return m_pwIcon; } + set { m_pwIcon = value; } + } + + /// + /// Get the custom icon ID. This value is 0, if no custom icon is + /// being used (i.e. the icon specified by the IconID property + /// should be displayed). + /// + public PwUuid CustomIconUuid + { + get { return m_pwCustomIconID; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + m_pwCustomIconID = value; + } + } + + /// + /// Reference to the group to which this group belongs. May be null. + /// + public PwGroup ParentGroup + { + get { return m_pParentGroup; } + + /// Plugins: use PwGroup.AddGroup instead. + internal set { Debug.Assert(value != this); m_pParentGroup = value; } + } + + /// + /// The date/time when the location of the object was last changed. + /// + public DateTime LocationChanged + { + get { return m_tParentGroupLastMod; } + set { m_tParentGroupLastMod = value; } + } + + /// + /// A flag that specifies if the group is shown as expanded or + /// collapsed in the user interface. + /// + public bool IsExpanded + { + get { return m_bIsExpanded; } + set { m_bIsExpanded = value; } + } + + /// + /// The date/time when this group was created. + /// + public DateTime CreationTime + { + get { return m_tCreation; } + set { m_tCreation = value; } + } + + /// + /// The date/time when this group was last modified. + /// + public DateTime LastModificationTime + { + get { return m_tLastMod; } + set { m_tLastMod = value; } + } + + /// + /// The date/time when this group was last accessed (read). + /// + public DateTime LastAccessTime + { + get { return m_tLastAccess; } + set { m_tLastAccess = value; } + } + + /// + /// The date/time when this group expires. + /// + public DateTime ExpiryTime + { + get { return m_tExpire; } + set { m_tExpire = value; } + } + + /// + /// Flag that determines if the group expires. + /// + public bool Expires + { + get { return m_bExpires; } + set { m_bExpires = value; } + } + + /// + /// Get or set the usage count of the group. To increase the usage + /// count by one, use the Touch function. + /// + public ulong UsageCount + { + get { return m_uUsageCount; } + set { m_uUsageCount = value; } + } + + /// + /// Get a list of subgroups in this group. + /// + public PwObjectList Groups + { + get { return m_listGroups; } + } + + /// + /// Get a list of entries in this group. + /// + public PwObjectList Entries + { + get { return m_listEntries; } + } + + /// + /// A flag specifying whether this group is virtual or not. Virtual + /// groups can contain links to entries stored in other groups. + /// Note that this flag has to be interpreted and set by the calling + /// code; it won't prevent you from accessing and modifying the list + /// of entries in this group in any way. + /// + public bool IsVirtual + { + get { return m_bVirtual; } + set { m_bVirtual = value; } + } + + /// + /// Default auto-type keystroke sequence for all entries in + /// this group. This property can be an empty string, which + /// means that the value should be inherited from the parent. + /// + public string DefaultAutoTypeSequence + { + get { return m_strDefaultAutoTypeSequence; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + m_strDefaultAutoTypeSequence = value; + } + } + + public bool? EnableAutoType + { + get { return m_bEnableAutoType; } + set { m_bEnableAutoType = value; } + } + + public bool? EnableSearching + { + get { return m_bEnableSearching; } + set { m_bEnableSearching = value; } + } + + public PwUuid LastTopVisibleEntry + { + get { return m_pwLastTopVisibleEntry; } + set + { + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + m_pwLastTopVisibleEntry = value; + } + } + + public static EventHandler GroupTouched; + public EventHandler Touched; + + /// + /// Construct a new, empty group. + /// + public PwGroup() + { + } + + /// + /// Construct a new, empty group. + /// + /// Create a new UUID for this group. + /// Set creation, last access and last modification times to the current time. + public PwGroup(bool bCreateNewUuid, bool bSetTimes) + { + if(bCreateNewUuid) m_uuid = new PwUuid(true); + + if(bSetTimes) + { + m_tCreation = m_tLastMod = m_tLastAccess = + m_tParentGroupLastMod = DateTime.Now; + } + } + + /// + /// Construct a new group. + /// + /// Create a new UUID for this group. + /// Set creation, last access and last modification times to the current time. + /// Name of the new group. + /// Icon of the new group. + public PwGroup(bool bCreateNewUuid, bool bSetTimes, string strName, PwIcon pwIcon) + { + if(bCreateNewUuid) m_uuid = new PwUuid(true); + + if(bSetTimes) + { + m_tCreation = m_tLastMod = m_tLastAccess = + m_tParentGroupLastMod = DateTime.Now; + } + + if(strName != null) m_strName = strName; + + m_pwIcon = pwIcon; + } + + /// + /// Deeply clone the current group. The returned group will be an exact + /// value copy of the current object (including UUID, etc.). + /// + /// Exact value copy of the current PwGroup object. + public PwGroup CloneDeep() + { + PwGroup pg = new PwGroup(false, false); + + pg.m_uuid = m_uuid; // PwUuid is immutable + + pg.m_listGroups = m_listGroups.CloneDeep(); + pg.m_listEntries = m_listEntries.CloneDeep(); + pg.m_pParentGroup = m_pParentGroup; + pg.m_tParentGroupLastMod = m_tParentGroupLastMod; + + pg.m_strName = m_strName; + pg.m_strNotes = m_strNotes; + + pg.m_pwIcon = m_pwIcon; + 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_bExpires = m_bExpires; + pg.m_uUsageCount = m_uUsageCount; + + pg.m_bIsExpanded = m_bIsExpanded; + pg.m_bVirtual = m_bVirtual; + + pg.m_strDefaultAutoTypeSequence = m_strDefaultAutoTypeSequence; + + pg.m_pwLastTopVisibleEntry = m_pwLastTopVisibleEntry; + + return pg; + } + + public PwGroup CloneStructure() + { + PwGroup pg = new PwGroup(false, false); + + pg.m_uuid = m_uuid; // PwUuid is immutable + pg.m_tParentGroupLastMod = m_tParentGroupLastMod; + // Do not assign m_pParentGroup + + foreach(PwGroup pgSub in m_listGroups) + pg.AddGroup(pgSub.CloneStructure(), true); + + foreach(PwEntry peSub in m_listEntries) + pg.AddEntry(peSub.CloneStructure(), true); + + return pg; + } + + /// + /// Assign properties to the current group based on a template group. + /// + /// Template group. Must not be null. + /// Only set the properties of the template group + /// if it is newer than the current one. + /// If true, the + /// LocationChanged property is copied, otherwise not. + public void AssignProperties(PwGroup pgTemplate, bool bOnlyIfNewer, + bool bAssignLocationChanged) + { + Debug.Assert(pgTemplate != null); if(pgTemplate == null) throw new ArgumentNullException("pgTemplate"); + + if(bOnlyIfNewer && (pgTemplate.m_tLastMod < m_tLastMod)) return; + + // Template UUID should be the same as the current one + Debug.Assert(m_uuid.EqualsValue(pgTemplate.m_uuid)); + m_uuid = pgTemplate.m_uuid; + + if(bAssignLocationChanged) + m_tParentGroupLastMod = pgTemplate.m_tParentGroupLastMod; + + m_strName = pgTemplate.m_strName; + m_strNotes = pgTemplate.m_strNotes; + + m_pwIcon = pgTemplate.m_pwIcon; + m_pwCustomIconID = pgTemplate.m_pwCustomIconID; + + m_tCreation = pgTemplate.m_tCreation; + m_tLastMod = pgTemplate.m_tLastMod; + m_tLastAccess = pgTemplate.m_tLastAccess; + m_tExpire = pgTemplate.m_tExpire; + m_bExpires = pgTemplate.m_bExpires; + m_uUsageCount = pgTemplate.m_uUsageCount; + + m_strDefaultAutoTypeSequence = pgTemplate.m_strDefaultAutoTypeSequence; + + m_pwLastTopVisibleEntry = pgTemplate.m_pwLastTopVisibleEntry; + } + + /// + /// Touch the group. This function updates the internal last access + /// time. If the parameter is true, + /// the last modification time gets updated, too. + /// + /// Modify last modification time. + public void Touch(bool bModified) + { + Touch(bModified, true); + } + + /// + /// Touch the group. This function updates the internal last access + /// time. If the parameter is true, + /// the last modification time gets updated, too. + /// + /// Modify last modification time. + /// If true, all parent objects + /// get touched, too. + public void Touch(bool bModified, bool bTouchParents) + { + m_tLastAccess = DateTime.Now; + ++m_uUsageCount; + + if(bModified) m_tLastMod = m_tLastAccess; + + if(this.Touched != null) + this.Touched(this, new ObjectTouchedEventArgs(this, + bModified, bTouchParents)); + if(PwGroup.GroupTouched != null) + PwGroup.GroupTouched(this, new ObjectTouchedEventArgs(this, + bModified, bTouchParents)); + + if(bTouchParents && (m_pParentGroup != null)) + m_pParentGroup.Touch(bModified, true); + } + + /// + /// Get number of groups and entries in the current group. This function + /// can also traverse through all subgroups and accumulate their counts + /// (recursive mode). + /// + /// If this parameter is true, all + /// subgroups and entries in subgroups will be counted and added to + /// the returned value. If it is false, only the number of + /// subgroups and entries of the current group is returned. + /// Number of subgroups. + /// Number of entries. + public void GetCounts(bool bRecursive, out uint uNumGroups, out uint uNumEntries) + { + if(bRecursive) + { + uint uTotalGroups = m_listGroups.UCount; + uint uTotalEntries = m_listEntries.UCount; + uint uSubGroupCount, uSubEntryCount; + + foreach(PwGroup pg in m_listGroups) + { + pg.GetCounts(true, out uSubGroupCount, out uSubEntryCount); + + uTotalGroups += uSubGroupCount; + uTotalEntries += uSubEntryCount; + } + + uNumGroups = uTotalGroups; + uNumEntries = uTotalEntries; + } + else // !bRecursive + { + uNumGroups = m_listGroups.UCount; + uNumEntries = m_listEntries.UCount; + } + } + + public uint GetEntriesCount(bool bRecursive) + { + uint uGroups, uEntries; + GetCounts(bRecursive, out uGroups, out uEntries); + return uEntries; + } + + /// + /// Traverse the group/entry tree in the current group. Various traversal + /// methods are available. + /// + /// Specifies the traversal method. + /// Function that performs an action on + /// the currently visited group (see GroupHandler for more). + /// This parameter may be null, in this case the tree is traversed but + /// you don't get notifications for each visited group. + /// Function that performs an action on + /// the currently visited entry (see EntryHandler for more). + /// This parameter may be null. + /// Returns true if all entries and groups have been + /// traversed. If the traversal has been canceled by one of the two + /// handlers, the return value is false. + public bool TraverseTree(TraversalMethod tm, GroupHandler groupHandler, EntryHandler entryHandler) + { + bool bRet = false; + + switch(tm) + { + case TraversalMethod.None: + bRet = true; + break; + case TraversalMethod.PreOrder: + bRet = PreOrderTraverseTree(groupHandler, entryHandler); + break; + default: + Debug.Assert(false); + break; + } + + return bRet; + } + + private bool PreOrderTraverseTree(GroupHandler groupHandler, EntryHandler entryHandler) + { + if(entryHandler != null) + { + foreach(PwEntry pe in m_listEntries) + { + if(!entryHandler(pe)) return false; + } + } + + if(groupHandler != null) + { + foreach(PwGroup pg in m_listGroups) + { + if(!groupHandler(pg)) return false; + + pg.PreOrderTraverseTree(groupHandler, entryHandler); + } + } + else // groupHandler == null + { + foreach(PwGroup pg in m_listGroups) + { + pg.PreOrderTraverseTree(null, entryHandler); + } + } + + return true; + } + + /// + /// 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() + { + LinkedList list = new LinkedList(); + + foreach(PwGroup pg in m_listGroups) + { + list.AddLast(pg); + + if(pg.Groups.UCount != 0) + LinearizeGroupRecursive(list, pg, 1); + } + + return list; + } + + private void LinearizeGroupRecursive(LinkedList list, PwGroup pg, ushort uLevel) + { + Debug.Assert(pg != null); if(pg == null) return; + + foreach(PwGroup pwg in pg.Groups) + { + list.AddLast(pwg); + + if(pwg.Groups.UCount != 0) + LinearizeGroupRecursive(list, pwg, (ushort)(uLevel + 1)); + } + } + + /// + /// Pack all entries into one flat linked list of references. Temporary + /// group IDs are assigned automatically. + /// + /// A flat group list created by + /// GetFlatGroupList. + /// Flat list of all entries. + public static LinkedList GetFlatEntryList(LinkedList flatGroupList) + { + Debug.Assert(flatGroupList != null); if(flatGroupList == null) return null; + + LinkedList list = new LinkedList(); + foreach(PwGroup pg in flatGroupList) + { + foreach(PwEntry pe in pg.Entries) + list.AddLast(pe); + } + + return list; + } + + /// + /// Enable protection of a specific string field type. + /// + /// Name of the string field to protect or unprotect. + /// Enable protection or not. + /// Returns true, if the operation completed successfully, + /// otherwise false. + public bool EnableStringFieldProtection(string strFieldName, bool bEnable) + { + Debug.Assert(strFieldName != null); + + EntryHandler eh = delegate(PwEntry pe) + { + // Enable protection of current string + pe.Strings.EnableProtection(strFieldName, bEnable); + + // Do the same for all history items + foreach(PwEntry peHistory in pe.History) + { + peHistory.Strings.EnableProtection(strFieldName, bEnable); + } + + return true; + }; + + return PreOrderTraverseTree(null, eh); + } + + /// + /// Search this group and all subgroups for entries. + /// + /// Specifies the search method. + /// Entry list in which the search results will + /// be stored. + public void SearchEntries(SearchParameters sp, PwObjectList listStorage) + { + SearchEntries(sp, listStorage, null); + } + + /// + /// Search this group and all subgroups for entries. + /// + /// Specifies the search method. + /// Entry list in which the search results will + /// be stored. + public void SearchEntries(SearchParameters sp, PwObjectList listStorage, + IStatusLogger slStatus) + { + if(sp == null) { Debug.Assert(false); return; } + if(listStorage == null) { Debug.Assert(false); return; } + + ulong uCurEntries = 0, uTotalEntries = 0; + + List lTerms = StrUtil.SplitSearchTerms(sp.SearchString); + if((lTerms.Count <= 1) || sp.RegularExpression) + { + if(slStatus != null) uTotalEntries = GetEntriesCount(true); + SearchEntriesSingle(sp, listStorage, slStatus, ref uCurEntries, + uTotalEntries); + return; + } + + // Search longer strings first (for improved performance) + lTerms.Sort(StrUtil.CompareLengthGt); + + string strFullSearch = sp.SearchString; // Backup + + PwGroup pg = this; + for(int iTerm = 0; iTerm < lTerms.Count; ++iTerm) + { + // Update counters for a better state guess + if(slStatus != null) + { + ulong uRemRounds = (ulong)(lTerms.Count - iTerm); + uTotalEntries = uCurEntries + (uRemRounds * + pg.GetEntriesCount(true)); + } + + PwGroup pgNew = new PwGroup(); + + sp.SearchString = lTerms[iTerm]; + + bool bNegate = false; + if(sp.SearchString.StartsWith("-")) + { + sp.SearchString = sp.SearchString.Substring(1); + bNegate = (sp.SearchString.Length > 0); + } + + if(!pg.SearchEntriesSingle(sp, pgNew.Entries, slStatus, + ref uCurEntries, uTotalEntries)) + { + pg = null; + break; + } + + if(bNegate) + { + PwObjectList lCand = pg.GetEntries(true); + + pg = new PwGroup(); + foreach(PwEntry peCand in lCand) + { + if(pgNew.Entries.IndexOf(peCand) < 0) pg.Entries.Add(peCand); + } + } + else pg = pgNew; + } + + if(pg != null) listStorage.Add(pg.Entries); + sp.SearchString = strFullSearch; // Restore + } + + private bool SearchEntriesSingle(SearchParameters spIn, + PwObjectList listStorage, IStatusLogger slStatus, + ref ulong uCurEntries, ulong uTotalEntries) + { + SearchParameters sp = spIn.Clone(); + if(sp.SearchString == null) { Debug.Assert(false); return true; } + sp.SearchString = sp.SearchString.Trim(); + + bool bTitle = sp.SearchInTitles; + bool bUserName = sp.SearchInUserNames; + bool bPassword = sp.SearchInPasswords; + bool bUrl = sp.SearchInUrls; + bool bNotes = sp.SearchInNotes; + bool bOther = sp.SearchInOther; + bool bUuids = sp.SearchInUuids; + bool bGroupName = sp.SearchInGroupNames; + bool bTags = sp.SearchInTags; + bool bExcludeExpired = sp.ExcludeExpired; + bool bRespectEntrySearchingDisabled = sp.RespectEntrySearchingDisabled; + + DateTime dtNow = DateTime.Now; + + Regex rx = null; + if(sp.RegularExpression) + { + RegexOptions ro = RegexOptions.Compiled; + if((sp.ComparisonMode == StringComparison.CurrentCultureIgnoreCase) || + (sp.ComparisonMode == StringComparison.InvariantCultureIgnoreCase) || + (sp.ComparisonMode == StringComparison.OrdinalIgnoreCase)) + { + ro |= RegexOptions.IgnoreCase; + } + + rx = new Regex(sp.SearchString, ro); + } + + ulong uLocalCurEntries = uCurEntries; + + EntryHandler eh = null; + if(sp.SearchString.Length <= 0) // Report all + { + eh = delegate(PwEntry pe) + { + if(slStatus != null) + { + if(!slStatus.SetProgress((uint)((uLocalCurEntries * + 100UL) / uTotalEntries))) return false; + ++uLocalCurEntries; + } + + if(bRespectEntrySearchingDisabled && !pe.GetSearchingEnabled()) + return true; // Skip + if(bExcludeExpired && pe.Expires && (dtNow > pe.ExpiryTime)) + return true; // Skip + + listStorage.Add(pe); + return true; + }; + } + else + { + eh = delegate(PwEntry pe) + { + if(slStatus != null) + { + if(!slStatus.SetProgress((uint)((uLocalCurEntries * + 100UL) / uTotalEntries))) return false; + ++uLocalCurEntries; + } + + if(bRespectEntrySearchingDisabled && !pe.GetSearchingEnabled()) + return true; // Skip + if(bExcludeExpired && pe.Expires && (dtNow > pe.ExpiryTime)) + return true; // Skip + + uint uInitialResults = listStorage.UCount; + + foreach(KeyValuePair kvp in pe.Strings) + { + string strKey = kvp.Key; + + if(strKey == PwDefs.TitleField) + { + if(bTitle) SearchEvalAdd(sp, kvp.Value.ReadString(), + rx, pe, listStorage); + } + else if(strKey == PwDefs.UserNameField) + { + if(bUserName) SearchEvalAdd(sp, kvp.Value.ReadString(), + rx, pe, listStorage); + } + else if(strKey == PwDefs.PasswordField) + { + if(bPassword) SearchEvalAdd(sp, kvp.Value.ReadString(), + rx, pe, listStorage); + } + else if(strKey == PwDefs.UrlField) + { + if(bUrl) SearchEvalAdd(sp, kvp.Value.ReadString(), + rx, pe, listStorage); + } + else if(strKey == PwDefs.NotesField) + { + if(bNotes) SearchEvalAdd(sp, kvp.Value.ReadString(), + rx, pe, listStorage); + } + else if(bOther) + SearchEvalAdd(sp, kvp.Value.ReadString(), + rx, pe, listStorage); + + // An entry can match only once => break if we have added it + if(listStorage.UCount > uInitialResults) break; + } + + if(bUuids && (listStorage.UCount == uInitialResults)) + SearchEvalAdd(sp, pe.Uuid.ToHexString(), rx, pe, listStorage); + + if(bGroupName && (listStorage.UCount == uInitialResults) && + (pe.ParentGroup != null)) + SearchEvalAdd(sp, pe.ParentGroup.Name, rx, pe, listStorage); + + if(bTags) + { + foreach(string strTag in pe.Tags) + { + if(listStorage.UCount != uInitialResults) break; // Match + + SearchEvalAdd(sp, strTag, rx, pe, listStorage); + } + } + + return true; + }; + } + + if(!PreOrderTraverseTree(null, eh)) return false; + uCurEntries = uLocalCurEntries; + return true; + } + + private static void SearchEvalAdd(SearchParameters sp, string strDataField, + Regex rx, PwEntry pe, PwObjectList lResults) + { + bool bMatch = false; + + if(rx == null) + bMatch = (strDataField.IndexOf(sp.SearchString, + sp.ComparisonMode) >= 0); + else bMatch = rx.IsMatch(strDataField); + + if(!bMatch && (sp.DataTransformationFn != null)) + { + string strCmp = sp.DataTransformationFn(strDataField, pe); + if(!object.ReferenceEquals(strCmp, strDataField)) + { + if(rx == null) + bMatch = (strCmp.IndexOf(sp.SearchString, + sp.ComparisonMode) >= 0); + else bMatch = rx.IsMatch(strCmp); + } + } + + if(bMatch) lResults.Add(pe); + } + + public List BuildEntryTagsList() + { + return BuildEntryTagsList(false); + } + + public List BuildEntryTagsList(bool bSort) + { + List vTags = new List(); + + EntryHandler eh = delegate(PwEntry pe) + { + foreach(string strTag in pe.Tags) + { + bool bFound = false; + for(int i = 0; i < vTags.Count; ++i) + { + if(vTags[i].Equals(strTag, StrUtil.CaseIgnoreCmp)) + { + bFound = true; + break; + } + } + + if(!bFound) vTags.Add(strTag); + } + + return true; + }; + + TraverseTree(TraversalMethod.PreOrder, null, eh); + if(bSort) vTags.Sort(StrUtil.CaseIgnoreComparer); + return vTags; + } + + public void FindEntriesByTag(string strTag, PwObjectList listStorage, + bool bSearchRecursive) + { + if(strTag == null) throw new ArgumentNullException("strTag"); + if(strTag.Length == 0) return; + + foreach(PwEntry pe in m_listEntries) + { + foreach(string strEntryTag in pe.Tags) + { + if(strEntryTag.Equals(strTag, StrUtil.CaseIgnoreCmp)) + { + listStorage.Add(pe); + break; + } + } + } + + if(bSearchRecursive) + { + foreach(PwGroup pg in m_listGroups) + pg.FindEntriesByTag(strTag, listStorage, true); + } + } + + /// + /// Find a group. + /// + /// UUID identifying the group the caller is looking for. + /// If true, the search is recursive. + /// Returns reference to found group, otherwise null. + public PwGroup FindGroup(PwUuid uuid, bool bSearchRecursive) + { + // Do not assert on PwUuid.Zero + if(m_uuid.EqualsValue(uuid)) return this; + + if(bSearchRecursive) + { + PwGroup pgRec; + foreach(PwGroup pg in m_listGroups) + { + pgRec = pg.FindGroup(uuid, true); + if(pgRec != null) return pgRec; + } + } + else // Not recursive + { + foreach(PwGroup pg in m_listGroups) + { + if(pg.m_uuid.EqualsValue(uuid)) + return pg; + } + } + + return null; + } + + /// + /// Find an object. + /// + /// UUID of the object to find. + /// Specifies whether to search recursively. + /// If null, groups and entries are + /// searched. If true, only entries are searched. If false, + /// only groups are searched. + /// Reference to the object, if found. Otherwise null. + public IStructureItem FindObject(PwUuid uuid, bool bRecursive, + bool? bEntries) + { + if(bEntries.HasValue) + { + if(bEntries.Value) return FindEntry(uuid, bRecursive); + else return FindGroup(uuid, bRecursive); + } + + PwGroup pg = FindGroup(uuid, bRecursive); + if(pg != null) return pg; + return FindEntry(uuid, bRecursive); + } + + /// + /// Try to find a subgroup and create it, if it doesn't exist yet. + /// + /// Name of the subgroup. + /// If the group isn't found: create it. + /// Returns a reference to the requested group or null if + /// it doesn't exist and shouldn't be created. + public PwGroup FindCreateGroup(string strName, bool bCreateIfNotFound) + { + Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); + + foreach(PwGroup pg in m_listGroups) + { + if(pg.Name == strName) return pg; + } + + if(!bCreateIfNotFound) return null; + + PwGroup pgNew = new PwGroup(true, true, strName, PwIcon.Folder); + AddGroup(pgNew, true); + return pgNew; + } + + /// + /// Find an entry. + /// + /// UUID identifying the entry the caller is looking for. + /// If true, the search is recursive. + /// Returns reference to found entry, otherwise null. + public PwEntry FindEntry(PwUuid uuid, bool bSearchRecursive) + { + foreach(PwEntry pe in m_listEntries) + { + if(pe.Uuid.EqualsValue(uuid)) return pe; + } + + if(bSearchRecursive) + { + PwEntry peSub; + foreach(PwGroup pg in m_listGroups) + { + peSub = pg.FindEntry(uuid, true); + if(peSub != null) return peSub; + } + } + + return null; + } + + /// + /// Get the full path of a group. + /// + /// Full path of the group. + public string GetFullPath() + { + return GetFullPath(".", false); + } + + /// + /// Get the full path of a group. + /// + /// String that separates the group + /// names. + /// Full path of the group. + public string GetFullPath(string strSeparator, bool bIncludeTopMostGroup) + { + Debug.Assert(strSeparator != null); + if(strSeparator == null) throw new ArgumentNullException("strSeparator"); + + string strPath = m_strName; + + PwGroup pg = m_pParentGroup; + while(pg != null) + { + if((!bIncludeTopMostGroup) && (pg.m_pParentGroup == null)) + break; + + strPath = pg.Name + strSeparator + strPath; + + pg = pg.m_pParentGroup; + } + + return strPath; + } + + /// + /// Assign new UUIDs to groups and entries. + /// + /// Create new UUIDs for subgroups. + /// Create new UUIDs for entries. + /// Recursive tree traversal. + public void CreateNewItemUuids(bool bNewGroups, bool bNewEntries, bool bRecursive) + { + if(bNewGroups) + { + foreach(PwGroup pg in m_listGroups) + pg.Uuid = new PwUuid(true); + } + + if(bNewEntries) + { + foreach(PwEntry pe in m_listEntries) + pe.SetUuid(new PwUuid(true), true); + } + + if(bRecursive) + { + foreach(PwGroup pg in m_listGroups) + pg.CreateNewItemUuids(bNewGroups, bNewEntries, true); + } + } + + public void TakeOwnership(bool bTakeSubGroups, bool bTakeEntries, bool bRecursive) + { + if(bTakeSubGroups) + { + foreach(PwGroup pg in m_listGroups) + pg.ParentGroup = this; + } + + if(bTakeEntries) + { + foreach(PwEntry pe in m_listEntries) + pe.ParentGroup = this; + } + + if(bRecursive) + { + foreach(PwGroup pg in m_listGroups) + pg.TakeOwnership(bTakeSubGroups, bTakeEntries, true); + } + } + + /// + /// Find/create a subtree of groups. + /// + /// Tree string. + /// Separators that delimit groups in the + /// strTree parameter. + public PwGroup FindCreateSubTree(string strTree, char[] vSeparators) + { + return FindCreateSubTree(strTree, vSeparators, true); + } + + public PwGroup FindCreateSubTree(string strTree, char[] vSeparators, + bool bAllowCreate) + { + Debug.Assert(strTree != null); if(strTree == null) return this; + if(strTree.Length == 0) return this; + + string[] vGroups = strTree.Split(vSeparators); + if((vGroups == null) || (vGroups.Length == 0)) return this; + + PwGroup pgContainer = this; + for(int nGroup = 0; nGroup < vGroups.Length; ++nGroup) + { + if(string.IsNullOrEmpty(vGroups[nGroup])) continue; + + bool bFound = false; + foreach(PwGroup pg in pgContainer.Groups) + { + if(pg.Name == vGroups[nGroup]) + { + pgContainer = pg; + bFound = true; + break; + } + } + + if(!bFound) + { + if(!bAllowCreate) return null; + + PwGroup pg = new PwGroup(true, true, vGroups[nGroup], PwIcon.Folder); + pgContainer.AddGroup(pg, true); + pgContainer = pg; + } + } + + return pgContainer; + } + + /// + /// Get the level of the group (i.e. the number of parent groups). + /// + /// Number of parent groups. + public uint GetLevel() + { + PwGroup pg = m_pParentGroup; + uint uLevel = 0; + + while(pg != null) + { + pg = pg.ParentGroup; + ++uLevel; + } + + return uLevel; + } + + public string GetAutoTypeSequenceInherited() + { + if(m_strDefaultAutoTypeSequence.Length > 0) + return m_strDefaultAutoTypeSequence; + + if(m_pParentGroup != null) + return m_pParentGroup.GetAutoTypeSequenceInherited(); + + return string.Empty; + } + + public bool GetAutoTypeEnabledInherited() + { + if(m_bEnableAutoType.HasValue) return m_bEnableAutoType.Value; + + if(m_pParentGroup != null) + return m_pParentGroup.GetAutoTypeEnabledInherited(); + + return DefaultAutoTypeEnabled; + } + + public bool GetSearchingEnabledInherited() + { + if(m_bEnableSearching.HasValue) return m_bEnableSearching.Value; + + if(m_pParentGroup != null) + return m_pParentGroup.GetSearchingEnabledInherited(); + + return DefaultSearchingEnabled; + } + + /// + /// Get a list of subgroups (not including this one). + /// + /// If true, subgroups are added + /// recursively, i.e. all child groups are returned, too. + /// List of subgroups. If is + /// true, it is guaranteed that subsubgroups appear after + /// subgroups. + public PwObjectList GetGroups(bool bRecursive) + { + if(bRecursive == false) return m_listGroups; + + PwObjectList list = m_listGroups.CloneShallow(); + foreach(PwGroup pgSub in m_listGroups) + { + list.Add(pgSub.GetGroups(true)); + } + + return list; + } + + public PwObjectList GetEntries(bool bIncludeSubGroupEntries) + { + if(bIncludeSubGroupEntries == false) return m_listEntries; + + PwObjectList list = m_listEntries.CloneShallow(); + foreach(PwGroup pgSub in m_listGroups) + { + list.Add(pgSub.GetEntries(true)); + } + + return list; + } + + /// + /// Get objects contained in this group. + /// + /// Specifies whether to search recursively. + /// If null, the returned list contains + /// groups and entries. If true, the returned list contains only + /// entries. If false, the returned list contains only groups. + /// List of objects. + public List GetObjects(bool bRecursive, bool? bEntries) + { + List list = new List(); + + if(!bEntries.HasValue || !bEntries.Value) + { + PwObjectList lGroups = GetGroups(bRecursive); + foreach(PwGroup pg in lGroups) list.Add(pg); + } + + if(!bEntries.HasValue || bEntries.Value) + { + PwObjectList lEntries = GetEntries(bRecursive); + foreach(PwEntry pe in lEntries) list.Add(pe); + } + + return list; + } + + public bool IsContainedIn(PwGroup pgContainer) + { + PwGroup pgCur = m_pParentGroup; + while(pgCur != null) + { + if(pgCur == pgContainer) return true; + + pgCur = pgCur.m_pParentGroup; + } + + return false; + } + + /// + /// Add a subgroup to this group. + /// + /// Group to be added. Must not be null. + /// If this parameter is true, the + /// parent group reference of the subgroup will be set to the current + /// group (i.e. the current group takes ownership of the subgroup). + public void AddGroup(PwGroup subGroup, bool bTakeOwnership) + { + AddGroup(subGroup, bTakeOwnership, false); + } + + /// + /// Add a subgroup to this group. + /// + /// Group to be added. Must not be null. + /// If this parameter is true, the + /// parent group reference of the subgroup will be set to the current + /// group (i.e. the current group takes ownership of the subgroup). + /// If true, the + /// LocationChanged property of the subgroup is updated. + public void AddGroup(PwGroup subGroup, bool bTakeOwnership, + bool bUpdateLocationChangedOfSub) + { + if(subGroup == null) throw new ArgumentNullException("subGroup"); + + m_listGroups.Add(subGroup); + + if(bTakeOwnership) subGroup.m_pParentGroup = this; + + if(bUpdateLocationChangedOfSub) subGroup.LocationChanged = DateTime.Now; + } + + /// + /// Add an entry to this group. + /// + /// Entry to be added. Must not be null. + /// If this parameter is true, the + /// parent group reference of the entry will be set to the current + /// group (i.e. the current group takes ownership of the entry). + public void AddEntry(PwEntry pe, bool bTakeOwnership) + { + AddEntry(pe, bTakeOwnership, false); + } + + /// + /// Add an entry to this group. + /// + /// Entry to be added. Must not be null. + /// If this parameter is true, the + /// parent group reference of the entry will be set to the current + /// group (i.e. the current group takes ownership of the entry). + /// If true, the + /// LocationChanged property of the entry is updated. + public void AddEntry(PwEntry pe, bool bTakeOwnership, + bool bUpdateLocationChangedOfEntry) + { + if(pe == null) throw new ArgumentNullException("pe"); + + m_listEntries.Add(pe); + + // Do not remove the entry from its previous parent group, + // only assign it to the new one + if(bTakeOwnership) pe.ParentGroup = this; + + if(bUpdateLocationChangedOfEntry) pe.LocationChanged = DateTime.Now; + } + + public void SortSubGroups(bool bRecursive) + { + m_listGroups.Sort(new PwGroupComparer()); + + if(bRecursive) + { + foreach(PwGroup pgSub in m_listGroups) + pgSub.SortSubGroups(true); + } + } + + public void DeleteAllObjects(PwDatabase pdContext) + { + DateTime dtNow = DateTime.Now; + + foreach(PwEntry pe in m_listEntries) + { + PwDeletedObject pdo = new PwDeletedObject(pe.Uuid, dtNow); + pdContext.DeletedObjects.Add(pdo); + } + m_listEntries.Clear(); + + foreach(PwGroup pg in m_listGroups) + { + pg.DeleteAllObjects(pdContext); + + PwDeletedObject pdo = new PwDeletedObject(pg.Uuid, dtNow); + pdContext.DeletedObjects.Add(pdo); + } + m_listGroups.Clear(); + } + } + + public sealed class PwGroupComparer : IComparer + { + public PwGroupComparer() + { + } + + public int Compare(PwGroup a, PwGroup b) + { + return StrUtil.CompareNaturally(a.Name, b.Name); + } + } +} diff --git a/src/KeePassLib2Android/PwUuid.cs b/src/KeePassLib2Android/PwUuid.cs new file mode 100644 index 00000000..dfb0be52 --- /dev/null +++ b/src/KeePassLib2Android/PwUuid.cs @@ -0,0 +1,179 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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.Xml; +using System.Diagnostics; + +using KeePassLib.Utility; + +namespace KeePassLib +{ + // [ImmutableObject(true)] + /// + /// Represents an UUID of a password entry or group. Once created, PwUuid + /// objects aren't modifyable anymore (immutable). + /// + public sealed class PwUuid + { + /// + /// Standard size in bytes of a UUID. + /// + public const uint UuidSize = 16; + + /// + /// Zero UUID (all bytes are zero). + /// + public static readonly PwUuid Zero = new PwUuid(); + + private byte[] m_pbUuid = new byte[UuidSize]; + + /// + /// Get the 16 UUID bytes. + /// + public byte[] UuidBytes + { + get { return m_pbUuid; } + } + + /// + /// Construct a new UUID object. Its value is initialized to zero. + /// + private PwUuid() + { + SetZero(); + } + + /// + /// Construct a new UUID object. + /// + /// If this parameter is true, a new + /// UUID is generated. If it is false, the UUID is initialized + /// to zero. + public PwUuid(bool bCreateNew) + { + if(bCreateNew) CreateNew(); + else SetZero(); + } + + /// + /// Construct a new UUID object. + /// + /// Initial value of the PwUuid object. + public PwUuid(byte[] uuidBytes) + { + SetValue(uuidBytes); + } + + /// + /// Create a new, random UUID. + /// + /// Returns true if a random UUID has been generated, + /// otherwise it returns false. + private void CreateNew() + { + while(true) + { + m_pbUuid = Guid.NewGuid().ToByteArray(); + + if((m_pbUuid == null) || (m_pbUuid.Length != UuidSize)) + throw new InvalidOperationException(); + + // Zero is a reserved value -- do not generate Zero + if(this.EqualsValue(PwUuid.Zero) == false) + break; + } + } + + /// + /// Compare this UUID with another. + /// + /// Second UUID object. + /// Returns true if both PwUuid object contain the same + /// value, otherwise false is returned. + public bool EqualsValue(PwUuid uuid) + { + Debug.Assert(uuid != null); + if(uuid == null) throw new ArgumentNullException("uuid"); + + for(int i = 0; i < UuidSize; ++i) + { + if(m_pbUuid[i] != uuid.m_pbUuid[i]) return false; + } + + return true; + } + + /// + /// Convert the UUID to its string representation. + /// + /// String containing the UUID value. + public string ToHexString() + { + 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) + { + 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); + } + } + + public sealed class PwUuidComparable : IComparable + { + private byte[] m_pbUuid = new byte[PwUuid.UuidSize]; + + public PwUuidComparable(PwUuid pwUuid) + { + if(pwUuid == null) throw new ArgumentNullException("pwUuid"); + + Array.Copy(pwUuid.UuidBytes, m_pbUuid, (int)PwUuid.UuidSize); + } + + public int CompareTo(PwUuidComparable other) + { + if(other == null) throw new ArgumentNullException("other"); + + for(int i = 0; i < (int)PwUuid.UuidSize; ++i) + { + if(m_pbUuid[i] < other.m_pbUuid[i]) return -1; + if(m_pbUuid[i] > other.m_pbUuid[i]) return 1; + } + + return 0; + } + } +} diff --git a/src/KeePassLib2Android/Resources/AboutResources.txt b/src/KeePassLib2Android/Resources/AboutResources.txt new file mode 100644 index 00000000..10f52d46 --- /dev/null +++ b/src/KeePassLib2Android/Resources/AboutResources.txt @@ -0,0 +1,44 @@ +Images, layout descriptions, binary blobs and string dictionaries can be included +in your application as resource files. Various Android APIs are designed to +operate on the resource IDs instead of dealing with images, strings or binary blobs +directly. + +For example, a sample Android app that contains a user interface layout (main.axml), +an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) +would keep its resources in the "Resources" directory of the application: + +Resources/ + drawable/ + icon.png + + layout/ + main.axml + + values/ + strings.xml + +In order to get the build system to recognize Android resources, set the build action to +"AndroidResource". The native Android APIs do not operate directly with filenames, but +instead operate on resource IDs. When you compile an Android application that uses resources, +the build system will package the resources for distribution and generate a class called "R" +(this is an Android convention) that contains the tokens for each one of the resources +included. For example, for the above Resources layout, this is what the R class would expose: + +public class R { + public class drawable { + public const int icon = 0x123; + } + + public class layout { + public const int main = 0x456; + } + + public class strings { + public const int first_string = 0xabc; + public const int second_string = 0xbcd; + } +} + +You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main +to reference the layout/main.axml file, or R.strings.first_string to reference the first +string in the dictionary file values/strings.xml. diff --git a/src/KeePassLib2Android/Resources/KLRes.Generated.cs b/src/KeePassLib2Android/Resources/KLRes.Generated.cs new file mode 100644 index 00000000..4aaf889f --- /dev/null +++ b/src/KeePassLib2Android/Resources/KLRes.Generated.cs @@ -0,0 +1,390 @@ +// This is a generated file! +// Do not edit manually, changes will be overwritten. + +using System; +using System.Collections.Generic; + +namespace KeePassLib.Resources +{ + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + public static class KLRes + { + private static string TryGetEx(Dictionary dictNew, + string strName, string strDefault) + { + string strTemp; + + if(dictNew.TryGetValue(strName, out strTemp)) + return strTemp; + + return strDefault; + } + + public static void SetTranslatedStrings(Dictionary dictNew) + { + if(dictNew == null) throw new ArgumentNullException("dictNew"); + + m_strCryptoStreamFailed = TryGetEx(dictNew, "CryptoStreamFailed", m_strCryptoStreamFailed); + m_strEncAlgorithmAes = TryGetEx(dictNew, "EncAlgorithmAes", m_strEncAlgorithmAes); + m_strErrorInClipboard = TryGetEx(dictNew, "ErrorInClipboard", m_strErrorInClipboard); + m_strFatalError = TryGetEx(dictNew, "FatalError", m_strFatalError); + m_strFatalErrorText = TryGetEx(dictNew, "FatalErrorText", m_strFatalErrorText); + m_strFileCorrupted = TryGetEx(dictNew, "FileCorrupted", m_strFileCorrupted); + m_strFileHeaderEndEarly = TryGetEx(dictNew, "FileHeaderEndEarly", m_strFileHeaderEndEarly); + m_strFileLoadFailed = TryGetEx(dictNew, "FileLoadFailed", m_strFileLoadFailed); + m_strFileLockedWrite = TryGetEx(dictNew, "FileLockedWrite", m_strFileLockedWrite); + m_strFileNewVerReq = TryGetEx(dictNew, "FileNewVerReq", m_strFileNewVerReq); + m_strFileSaveCorruptionWarning = TryGetEx(dictNew, "FileSaveCorruptionWarning", m_strFileSaveCorruptionWarning); + m_strFileSaveFailed = TryGetEx(dictNew, "FileSaveFailed", m_strFileSaveFailed); + m_strFileSigInvalid = TryGetEx(dictNew, "FileSigInvalid", m_strFileSigInvalid); + m_strFileUnknownCipher = TryGetEx(dictNew, "FileUnknownCipher", m_strFileUnknownCipher); + m_strFileUnknownCompression = TryGetEx(dictNew, "FileUnknownCompression", m_strFileUnknownCompression); + m_strFileVersionUnsupported = TryGetEx(dictNew, "FileVersionUnsupported", m_strFileVersionUnsupported); + m_strFinalKeyCreationFailed = TryGetEx(dictNew, "FinalKeyCreationFailed", m_strFinalKeyCreationFailed); + m_strFrameworkNotImplExcp = TryGetEx(dictNew, "FrameworkNotImplExcp", m_strFrameworkNotImplExcp); + 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_strMasterSeedLengthInvalid = TryGetEx(dictNew, "MasterSeedLengthInvalid", m_strMasterSeedLengthInvalid); + m_strOldFormat = TryGetEx(dictNew, "OldFormat", m_strOldFormat); + m_strTryAgainSecs = TryGetEx(dictNew, "TryAgainSecs", m_strTryAgainSecs); + m_strUnknownHeaderId = TryGetEx(dictNew, "UnknownHeaderId", m_strUnknownHeaderId); + m_strUserAccountKeyError = TryGetEx(dictNew, "UserAccountKeyError", m_strUserAccountKeyError); + } + + private static readonly string[] m_vKeyNames = { + "CryptoStreamFailed", + "EncAlgorithmAes", + "ErrorInClipboard", + "FatalError", + "FatalErrorText", + "FileCorrupted", + "FileHeaderEndEarly", + "FileLoadFailed", + "FileLockedWrite", + "FileNewVerReq", + "FileSaveCorruptionWarning", + "FileSaveFailed", + "FileSigInvalid", + "FileUnknownCipher", + "FileUnknownCompression", + "FileVersionUnsupported", + "FinalKeyCreationFailed", + "FrameworkNotImplExcp", + "InvalidCompositeKey", + "InvalidCompositeKeyHint", + "InvalidDataWhileDecoding", + "KeePass1xHint", + "MasterSeedLengthInvalid", + "OldFormat", + "TryAgainSecs", + "UnknownHeaderId", + "UserAccountKeyError" + }; + + public static string[] GetKeyNames() + { + return m_vKeyNames; + } + + private static string m_strCryptoStreamFailed = + @"Failed to initialize encryption/decryption stream!"; + /// + /// Look up a localized string similar to + /// 'Failed to initialize encryption/decryption stream!'. + /// + public static string CryptoStreamFailed + { + get { return m_strCryptoStreamFailed; } + } + + private static string m_strEncAlgorithmAes = + @"AES/Rijndael (256-Bit Key)"; + /// + /// Look up a localized string similar to + /// 'AES/Rijndael (256-Bit Key)'. + /// + public static string EncAlgorithmAes + { + get { return m_strEncAlgorithmAes; } + } + + private static string m_strErrorInClipboard = + @"An extended error report has been copied to the clipboard."; + /// + /// Look up a localized string similar to + /// 'An extended error report has been copied to the clipboard.'. + /// + public static string ErrorInClipboard + { + get { return m_strErrorInClipboard; } + } + + private static string m_strFatalError = + @"Fatal Error"; + /// + /// Look up a localized string similar to + /// 'Fatal Error'. + /// + public static string FatalError + { + get { return m_strFatalError; } + } + + private static string m_strFatalErrorText = + @"A fatal error has occurred!"; + /// + /// Look up a localized string similar to + /// 'A fatal error has occurred!'. + /// + public static string FatalErrorText + { + get { return m_strFatalErrorText; } + } + + private static string m_strFileCorrupted = + @"The file is corrupted."; + /// + /// Look up a localized string similar to + /// 'The file is corrupted.'. + /// + public static string FileCorrupted + { + get { return m_strFileCorrupted; } + } + + private static string m_strFileHeaderEndEarly = + @"The file header is corrupted! Some header data was declared but is not present."; + /// + /// Look up a localized string similar to + /// 'The file header is corrupted! Some header data was declared but is not present.'. + /// + public static string FileHeaderEndEarly + { + get { return m_strFileHeaderEndEarly; } + } + + private static string m_strFileLoadFailed = + @"Failed to load the specified file!"; + /// + /// Look up a localized string similar to + /// 'Failed to load the specified file!'. + /// + public static string FileLoadFailed + { + get { return m_strFileLoadFailed; } + } + + private static string m_strFileLockedWrite = + @"The file is locked, because the following user is currently writing to it:"; + /// + /// Look up a localized string similar to + /// 'The file is locked, because the following user is currently writing to it:'. + /// + public static string FileLockedWrite + { + get { return m_strFileLockedWrite; } + } + + private static string m_strFileNewVerReq = + @"A newer KeePass version is required to open this file."; + /// + /// Look up a localized string similar to + /// 'A newer KeePass version is required to open this file.'. + /// + public static string FileNewVerReq + { + get { return m_strFileNewVerReq; } + } + + private static string m_strFileSaveCorruptionWarning = + @"The target file might be corrupted. Please try saving again. If that fails, save the database to a different location."; + /// + /// Look up a localized string similar to + /// 'The target file might be corrupted. Please try saving again. If that fails, save the database to a different location.'. + /// + public static string FileSaveCorruptionWarning + { + get { return m_strFileSaveCorruptionWarning; } + } + + private static string m_strFileSaveFailed = + @"Failed to save the current database to the specified location!"; + /// + /// Look up a localized string similar to + /// 'Failed to save the current database to the specified location!'. + /// + public static string FileSaveFailed + { + get { return m_strFileSaveFailed; } + } + + private static string m_strFileSigInvalid = + @"The file signature is invalid. Either the file isn't a KeePass database file at all or it is corrupted."; + /// + /// Look up a localized string similar to + /// 'The file signature is invalid. Either the file isn't a KeePass database file at all or it is corrupted.'. + /// + public static string FileSigInvalid + { + get { return m_strFileSigInvalid; } + } + + private static string m_strFileUnknownCipher = + @"The file is encrypted using an unknown encryption algorithm!"; + /// + /// Look up a localized string similar to + /// 'The file is encrypted using an unknown encryption algorithm!'. + /// + public static string FileUnknownCipher + { + get { return m_strFileUnknownCipher; } + } + + private static string m_strFileUnknownCompression = + @"The file is compressed using an unknown compression algorithm!"; + /// + /// Look up a localized string similar to + /// 'The file is compressed using an unknown compression algorithm!'. + /// + public static string FileUnknownCompression + { + get { return m_strFileUnknownCompression; } + } + + private static string m_strFileVersionUnsupported = + @"The file version is unsupported."; + /// + /// Look up a localized string similar to + /// 'The file version is unsupported.'. + /// + public static string FileVersionUnsupported + { + get { return m_strFileVersionUnsupported; } + } + + private static string m_strFinalKeyCreationFailed = + @"Failed to create the final encryption/decryption key!"; + /// + /// Look up a localized string similar to + /// 'Failed to create the final encryption/decryption key!'. + /// + public static string FinalKeyCreationFailed + { + get { return m_strFinalKeyCreationFailed; } + } + + private static string m_strFrameworkNotImplExcp = + @"The .NET framework/runtime under which KeePass is currently running does not support this operation."; + /// + /// Look up a localized string similar to + /// 'The .NET framework/runtime under which KeePass is currently running does not support this operation.'. + /// + public static string FrameworkNotImplExcp + { + get { return m_strFrameworkNotImplExcp; } + } + + private static string m_strInvalidCompositeKey = + @"The composite key is invalid!"; + /// + /// Look up a localized string similar to + /// 'The composite key is invalid!'. + /// + public static string InvalidCompositeKey + { + get { return m_strInvalidCompositeKey; } + } + + private static string m_strInvalidCompositeKeyHint = + @"Make sure the composite key is correct and try again."; + /// + /// Look up a localized string similar to + /// 'Make sure the composite key is correct and try again.'. + /// + public static string InvalidCompositeKeyHint + { + get { return m_strInvalidCompositeKeyHint; } + } + + private static string m_strInvalidDataWhileDecoding = + @"Found invalid data while decoding."; + /// + /// Look up a localized string similar to + /// 'Found invalid data while decoding.'. + /// + public static string InvalidDataWhileDecoding + { + get { return m_strInvalidDataWhileDecoding; } + } + + private static string m_strKeePass1xHint = + @"In order to import KeePass 1.x KDB files, create a new 2.x database file and click 'File' -> 'Import' in the main menu. In the import dialog, choose 'KeePass KDB (1.x)' as file format."; + /// + /// Look up a localized string similar to + /// 'In order to import KeePass 1.x KDB files, create a new 2.x database file and click 'File' -> 'Import' in the main menu. In the import dialog, choose 'KeePass KDB (1.x)' as file format.'. + /// + public static string KeePass1xHint + { + get { return m_strKeePass1xHint; } + } + + private static string m_strMasterSeedLengthInvalid = + @"The length of the master key seed is invalid!"; + /// + /// Look up a localized string similar to + /// 'The length of the master key seed is invalid!'. + /// + public static string MasterSeedLengthInvalid + { + get { return m_strMasterSeedLengthInvalid; } + } + + private static string m_strOldFormat = + @"The selected file appears to be an old format"; + /// + /// Look up a localized string similar to + /// 'The selected file appears to be an old format'. + /// + public static string OldFormat + { + get { return m_strOldFormat; } + } + + private static string m_strTryAgainSecs = + @"Please try it again in a few seconds."; + /// + /// Look up a localized string similar to + /// 'Please try it again in a few seconds.'. + /// + public static string TryAgainSecs + { + get { return m_strTryAgainSecs; } + } + + private static string m_strUnknownHeaderId = + @"Unknown header ID!"; + /// + /// Look up a localized string similar to + /// 'Unknown header ID!'. + /// + public static string UnknownHeaderId + { + get { return m_strUnknownHeaderId; } + } + + 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."; + /// + /// Look up a localized string similar to + /// 'The operating system did not grant KeePass read/write access to the user profile folder, where the protected user key is stored.'. + /// + public static string UserAccountKeyError + { + get { return m_strUserAccountKeyError; } + } + } +} diff --git a/src/KeePassLib2Android/Resources/KSRes.Generated.cs b/src/KeePassLib2Android/Resources/KSRes.Generated.cs new file mode 100644 index 00000000..b1ab9708 --- /dev/null +++ b/src/KeePassLib2Android/Resources/KSRes.Generated.cs @@ -0,0 +1,52 @@ +// This is a generated file! +// Do not edit manually, changes will be overwritten. + +using System; +using System.Collections.Generic; + +namespace KeePassLib.Resources +{ + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + public static class KSRes + { + private static string TryGetEx(Dictionary dictNew, + string strName, string strDefault) + { + string strTemp; + + if(dictNew.TryGetValue(strName, out strTemp)) + return strTemp; + + return strDefault; + } + + public static void SetTranslatedStrings(Dictionary dictNew) + { + if(dictNew == null) throw new ArgumentNullException("dictNew"); + + m_strTest = TryGetEx(dictNew, "Test", m_strTest); + } + + private static readonly string[] m_vKeyNames = { + "Test" + }; + + public static string[] GetKeyNames() + { + return m_vKeyNames; + } + + private static string m_strTest = + @"Test"; + /// + /// Look up a localized string similar to + /// 'Test'. + /// + public static string Test + { + get { return m_strTest; } + } + } +} diff --git a/src/KeePassLib2Android/Resources/Resource.designer.cs b/src/KeePassLib2Android/Resources/Resource.designer.cs new file mode 100644 index 00000000..abaffe08 --- /dev/null +++ b/src/KeePassLib2Android/Resources/Resource.designer.cs @@ -0,0 +1,37 @@ +//------------------------------------------------------------------------------ +// +// Dieser Code wurde von einem Tool generiert. +// Laufzeitversion:4.0.30319.296 +// +// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn +// der Code erneut generiert wird. +// +//------------------------------------------------------------------------------ + +namespace KeePassLib2Android +{ + + + public partial class Resource + { + + public partial class Attribute + { + + private Attribute() + { + } + } + + public partial class String + { + + // aapt resource value: 0x7f020000 + public const int library_name = 2130837504; + + private String() + { + } + } + } +} diff --git a/src/KeePassLib2Android/Resources/values/Strings.xml b/src/KeePassLib2Android/Resources/values/Strings.xml new file mode 100644 index 00000000..17aa6e6a --- /dev/null +++ b/src/KeePassLib2Android/Resources/values/Strings.xml @@ -0,0 +1,4 @@ + + + KeePassLib2Android + diff --git a/src/KeePassLib2Android/Security/ProtectedBinary.cs b/src/KeePassLib2Android/Security/ProtectedBinary.cs new file mode 100644 index 00000000..b8ce4923 --- /dev/null +++ b/src/KeePassLib2Android/Security/ProtectedBinary.cs @@ -0,0 +1,242 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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.Security.Cryptography; +using System.Diagnostics; + +using KeePassLib.Cryptography; +using KeePassLib.Utility; + +#if KeePassLibSD +using KeePassLibSD; +#endif + +namespace KeePassLib.Security +{ + /// + /// Represents a protected binary, i.e. a byte array that is encrypted + /// in memory. A ProtectedBinary object is immutable and + /// thread-safe. + /// + public sealed class ProtectedBinary : IEquatable + { + private const int PmBlockSize = 16; + + // In-memory protection is supported only on Windows 2000 SP3 and + // higher. + private static bool m_bProtectionSupported; + + private byte[] m_pbData; // Never null + + // The real length of the data. This value can be different than + // m_pbData.Length, as the length of m_pbData always is a multiple + // of PmBlockSize (required for fast in-memory protection). + private uint m_uDataLen; + + private bool m_bProtected; + + private object m_objSync = new object(); + + /// + /// A flag specifying whether the ProtectedBinary object has + /// turned on in-memory protection or not. + /// + public bool IsProtected + { + get { return m_bProtected; } + } + + /// + /// Length of the stored data. + /// + public uint Length + { + 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. + /// + public ProtectedBinary() + { + Init(false, new byte[0]); + } + + /// + /// Construct a new protected binary data object. + /// + /// If this paremeter is true, + /// the data will be encrypted in memory. If it is false, the + /// data is stored in plain-text in the process memory. + /// Value of the protected object. + /// The input parameter is not modified and + /// ProtectedBinary doesn't take ownership of the data, + /// i.e. the caller is responsible for clearing it. + public ProtectedBinary(bool bEnableProtection, byte[] pbData) + { + Init(bEnableProtection, pbData); + } + + /// + /// Construct a new protected binary data object. Copy the data from + /// a XorredBuffer object. + /// + /// 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"); + + byte[] pb = xbProtected.ReadPlainText(); + Init(bEnableProtection, pb); + MemUtil.ZeroByteArray(pb); + } + + private void Init(bool bEnableProtection, byte[] pbData) + { + if(pbData == null) throw new ArgumentNullException("pbData"); + + 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); + + m_pbData = new byte[nBlocks * PmBlockSize]; + Array.Copy(pbData, m_pbData, (int)m_uDataLen); + + // Data size must be > 0, otherwise 'Protect' throws + if(m_bProtected && m_bProtectionSupported && (m_uDataLen > 0)) + ProtectedMemory.Protect(m_pbData, MemoryProtectionScope.SameProcess); + } + + /// + /// Get a copy of the protected data as a byte array. + /// Please note that the returned byte array is not protected and + /// can therefore been read by any other application. + /// Make sure that your clear it properly after usage. + /// + /// Unprotected byte array. This is always a copy of the internal + /// protected data and can therefore be cleared safely. + public byte[] ReadData() + { + if(m_uDataLen == 0) return new byte[0]; + + byte[] pbReturn = new byte[m_uDataLen]; + + if(m_bProtected && m_bProtectionSupported) + { + lock(m_objSync) + { + ProtectedMemory.Unprotect(m_pbData, MemoryProtectionScope.SameProcess); + Array.Copy(m_pbData, pbReturn, (int)m_uDataLen); + ProtectedMemory.Protect(m_pbData, MemoryProtectionScope.SameProcess); + } + } + else Array.Copy(m_pbData, pbReturn, (int)m_uDataLen); + + return pbReturn; + } + + /// + /// Read the protected data and return it protected with a sequence + /// 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); + if(crsRandomSource == null) throw new ArgumentNullException("crsRandomSource"); + + byte[] pbData = ReadData(); + uint uLen = (uint)pbData.Length; + + byte[] randomPad = crsRandomSource.GetRandomBytes(uLen); + Debug.Assert(randomPad.Length == uLen); + + for(uint i = 0; i < uLen; ++i) + pbData[i] ^= randomPad[i]; + + return pbData; + } + + public override int GetHashCode() + { + int h = (m_bProtected ? 0x7B11D289 : 0); + + byte[] pb = ReadData(); + unchecked + { + for(int i = 0; i < pb.Length; ++i) + h = (h << 3) + h + (int)pb[i]; + } + MemUtil.ZeroByteArray(pb); + + return h; + } + + public override bool Equals(object obj) + { + return Equals(obj as ProtectedBinary); + } + + public bool Equals(ProtectedBinary other) + { + if(other == null) return false; // No assert + + if(m_bProtected != other.m_bProtected) return false; + if(m_uDataLen != other.m_uDataLen) return false; + + byte[] pbL = ReadData(); + byte[] pbR = other.ReadData(); + bool bEq = MemUtil.ArraysEqual(pbL, pbR); + MemUtil.ZeroByteArray(pbL); + MemUtil.ZeroByteArray(pbR); + +#if DEBUG + if(bEq) { Debug.Assert(GetHashCode() == other.GetHashCode()); } +#endif + + return bEq; + } + } +} diff --git a/src/KeePassLib2Android/Security/ProtectedString.cs b/src/KeePassLib2Android/Security/ProtectedString.cs new file mode 100644 index 00000000..7f9d2590 --- /dev/null +++ b/src/KeePassLib2Android/Security/ProtectedString.cs @@ -0,0 +1,253 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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.Text; +using System.Diagnostics; + +using KeePassLib.Cryptography; +using KeePassLib.Utility; + +#if KeePassLibSD +using KeePassLibSD; +#endif + +// SecureString objects are limited to 65536 characters, don't use + +namespace KeePassLib.Security +{ + /// + /// Represents an in-memory encrypted string. + /// ProtectedString objects are immutable and thread-safe. + /// +#if (DEBUG && !KeePassLibSD) + [DebuggerDisplay(@"{ReadString()}")] +#endif + public sealed class ProtectedString + { + // Exactly one of the following will be non-null + private ProtectedBinary m_pbUtf8 = null; + private string m_strPlainText = null; + + private bool m_bIsProtected; + + private static ProtectedString m_psEmpty = new ProtectedString(); + public static ProtectedString Empty + { + get { return m_psEmpty; } + } + + /// + /// A flag specifying whether the ProtectedString object + /// has turned on in-memory protection or not. + /// + public bool IsProtected + { + get { return m_bIsProtected; } + } + + public bool IsEmpty + { + get + { + ProtectedBinary pBin = m_pbUtf8; // Local ref for thread-safety + if(pBin != null) return (pBin.Length == 0); + + Debug.Assert(m_strPlainText != null); + return (m_strPlainText.Length == 0); + } + } + + private int m_nCachedLength = -1; + public int Length + { + get + { + if(m_nCachedLength >= 0) return m_nCachedLength; + + ProtectedBinary pBin = m_pbUtf8; // Local ref for thread-safety + if(pBin != null) + { + byte[] pbPlain = pBin.ReadData(); + m_nCachedLength = StrUtil.Utf8.GetCharCount(pbPlain); + MemUtil.ZeroByteArray(pbPlain); + } + else + { + Debug.Assert(m_strPlainText != null); + m_nCachedLength = m_strPlainText.Length; + } + + return m_nCachedLength; + } + } + + /// + /// Construct a new protected string object. Protection is + /// disabled. + /// + public ProtectedString() + { + Init(false, string.Empty); + } + + /// + /// Construct a new protected string. The string is initialized + /// to the value supplied in the parameters. + /// + /// If this parameter is true, + /// 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. + public ProtectedString(bool bEnableProtection, string strValue) + { + Init(bEnableProtection, strValue); + } + + /// + /// Construct a new protected string. The string is initialized + /// 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 + /// 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 + /// is responsible for clearing it. + public ProtectedString(bool bEnableProtection, byte[] vUtf8Value) + { + Init(bEnableProtection, vUtf8Value); + } + + /// + /// Construct a new protected string. The string is initialized + /// to the value passed in the XorredBuffer object. + /// + /// Enable protection or not. + /// 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) + { + if(xbProtected == null) throw new ArgumentNullException("xbProtected"); + + byte[] pb = xbProtected.ReadPlainText(); + Init(bEnableProtection, pb); + MemUtil.ZeroByteArray(pb); + } + + private void Init(bool bEnableProtection, string str) + { + if(str == null) throw new ArgumentNullException("str"); + + m_bIsProtected = bEnableProtection; + + // The string already is in memory and immutable, + // protection would be useless + m_strPlainText = str; + } + + private void Init(bool bEnableProtection, byte[] pbUtf8) + { + if(pbUtf8 == null) throw new ArgumentNullException("pbUtf8"); + + m_bIsProtected = bEnableProtection; + + if(bEnableProtection) + m_pbUtf8 = new ProtectedBinary(true, pbUtf8); + else + m_strPlainText = StrUtil.Utf8.GetString(pbUtf8, 0, pbUtf8.Length); + } + + /// + /// Convert the protected string to a normal string object. + /// Be careful with this function, the returned string object + /// isn't protected anymore and stored in plain-text in the + /// process memory. + /// + /// Plain-text string. Is never null. + public string ReadString() + { + if(m_strPlainText != null) return m_strPlainText; + + byte[] pb = ReadUtf8(); + string str = ((pb.Length == 0) ? string.Empty : + StrUtil.Utf8.GetString(pb, 0, pb.Length)); + // No need to clear pb + + // As the text is now visible in process memory anyway, + // there's no need to protect it anymore + m_strPlainText = str; + m_pbUtf8 = null; // Thread-safe order + + return str; + } + + /// + /// Read out the string and return a byte array that contains the + /// string encoded using UTF-8. The returned string is not protected + /// anymore! + /// + /// Plain-text UTF-8 byte array. + public byte[] ReadUtf8() + { + ProtectedBinary pBin = m_pbUtf8; // Local ref for thread-safety + if(pBin != null) return pBin.ReadData(); + + return StrUtil.Utf8.GetBytes(m_strPlainText); + } + + /// + /// Read the protected string and return it protected with a sequence + /// of bytes generated by a random stream. + /// + /// 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"); + + byte[] pbData = ReadUtf8(); + uint uLen = (uint)pbData.Length; + + byte[] randomPad = crsRandomSource.GetRandomBytes(uLen); + Debug.Assert(randomPad.Length == uLen); + + for(uint i = 0; i < uLen; ++i) + pbData[i] ^= randomPad[i]; + + return pbData; + } + + public ProtectedString WithProtection(bool bProtect) + { + if(bProtect == m_bIsProtected) return this; + + byte[] pb = ReadUtf8(); + ProtectedString ps = new ProtectedString(bProtect, pb); + MemUtil.ZeroByteArray(pb); + return ps; + } + } +} diff --git a/src/KeePassLib2Android/Security/XorredBuffer.cs b/src/KeePassLib2Android/Security/XorredBuffer.cs new file mode 100644 index 00000000..df330751 --- /dev/null +++ b/src/KeePassLib2Android/Security/XorredBuffer.cs @@ -0,0 +1,116 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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.Diagnostics; + +namespace KeePassLib.Security +{ + /// + /// Represents an object that is encrypted using a XOR pad until + /// it is read. XorredBuffer objects are immutable and + /// thread-safe. + /// + public sealed class XorredBuffer + { + private byte[] m_pbData; // Never null + private byte[] m_pbXorPad; // Always valid for m_pbData + + /// + /// Length of the protected data in bytes. + /// + public uint Length + { + get { return (uint)m_pbData.Length; } + } + + /// + /// Construct a new XOR-protected object using a protected byte array + /// and a XOR pad that decrypts the protected data. The + /// byte array must have the same size + /// as the byte array. + /// The XorredBuffer object takes ownership of the two byte + /// arrays, i.e. the caller must not use or modify them afterwards. + /// + /// Protected data (XOR pad applied). + /// XOR pad that can be used to decrypt the + /// parameter. + /// Thrown if one of the input + /// parameters is null. + /// Thrown if the byte arrays are + /// of different size. + public XorredBuffer(byte[] pbProtectedData, byte[] pbXorPad) + { + if(pbProtectedData == null) { Debug.Assert(false); throw new ArgumentNullException("pbProtectedData"); } + if(pbXorPad == null) { Debug.Assert(false); throw new ArgumentNullException("pbXorPad"); } + + Debug.Assert(pbProtectedData.Length == pbXorPad.Length); + if(pbProtectedData.Length != pbXorPad.Length) throw new ArgumentException(); + + m_pbData = pbProtectedData; + m_pbXorPad = pbXorPad; + } + + /// + /// Get a copy of the plain-text. The caller is responsible + /// for clearing the byte array safely after using it. + /// + /// Unprotected plain-text byte array. + public byte[] ReadPlainText() + { + byte[] pbPlain = new byte[m_pbData.Length]; + + for(int i = 0; i < pbPlain.Length; ++i) + pbPlain[i] = (byte)(m_pbData[i] ^ m_pbXorPad[i]); + + return pbPlain; + } + + /* public bool EqualsValue(XorredBuffer xb) + { + if(xb == null) { Debug.Assert(false); throw new ArgumentNullException("xb"); } + + if(xb.m_pbData.Length != m_pbData.Length) return false; + + for(int i = 0; i < m_pbData.Length; ++i) + { + byte bt1 = (byte)(m_pbData[i] ^ m_pbXorPad[i]); + byte bt2 = (byte)(xb.m_pbData[i] ^ xb.m_pbXorPad[i]); + + if(bt1 != bt2) return false; + } + + return true; + } + + public bool EqualsValue(byte[] pb) + { + if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } + + if(pb.Length != m_pbData.Length) return false; + + for(int i = 0; i < m_pbData.Length; ++i) + { + if((byte)(m_pbData[i] ^ m_pbXorPad[i]) != pb[i]) return false; + } + + return true; + } */ + } +} diff --git a/src/KeePassLib2Android/Serialization/BinaryReaderEx.cs b/src/KeePassLib2Android/Serialization/BinaryReaderEx.cs new file mode 100644 index 00000000..3ce22a51 --- /dev/null +++ b/src/KeePassLib2Android/Serialization/BinaryReaderEx.cs @@ -0,0 +1,90 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; + +using KeePassLib.Utility; + +namespace KeePassLib.Serialization +{ + public sealed class BinaryReaderEx + { + private Stream m_s; + private Encoding m_enc; + + private string m_strReadExcp; + public string ReadExceptionText + { + get { return m_strReadExcp; } + set { m_strReadExcp = value; } + } + + private Stream m_sCopyTo = null; + /// + /// If this property is set to a non-null stream, all data that + /// is read from the input stream is automatically written to + /// the copy stream (before returning the read data). + /// + public Stream CopyDataTo + { + get { return m_sCopyTo; } + set { m_sCopyTo = value; } + } + + public BinaryReaderEx(Stream input, Encoding encoding, + string strReadExceptionText) + { + if(input == null) throw new ArgumentNullException("input"); + + m_s = input; + m_enc = encoding; + m_strReadExcp = strReadExceptionText; + } + + public byte[] ReadBytes(int nCount) + { + try + { + byte[] pb = MemUtil.Read(m_s, nCount); + if((pb == null) || (pb.Length != nCount)) + { + if(m_strReadExcp != null) throw new IOException(m_strReadExcp); + else throw new EndOfStreamException(); + } + + if(m_sCopyTo != null) m_sCopyTo.Write(pb, 0, pb.Length); + return pb; + } + catch(Exception) + { + if(m_strReadExcp != null) throw new IOException(m_strReadExcp); + else throw; + } + } + + public byte ReadByte() + { + byte[] pb = ReadBytes(1); + return pb[0]; + } + } +} diff --git a/src/KeePassLib2Android/Serialization/FileLock.cs b/src/KeePassLib2Android/Serialization/FileLock.cs new file mode 100644 index 00000000..ac20e00d --- /dev/null +++ b/src/KeePassLib2Android/Serialization/FileLock.cs @@ -0,0 +1,260 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Threading; +using System.Diagnostics; + +using KeePassLib.Cryptography; +using KeePassLib.Resources; +using KeePassLib.Utility; + +namespace KeePassLib.Serialization +{ + public sealed class FileLockException : Exception + { + private readonly string m_strMsg; + + public override string Message + { + get { return m_strMsg; } + } + + public FileLockException(string strBaseFile, string strUser) + { + StringBuilder sb = new StringBuilder(); + + if(!string.IsNullOrEmpty(strBaseFile)) + { + sb.Append(strBaseFile); + sb.Append(MessageService.NewParagraph); + } + + sb.Append(KLRes.FileLockedWrite); + sb.Append(MessageService.NewLine); + + if(!string.IsNullOrEmpty(strUser)) sb.Append(strUser); + else sb.Append("?"); + + sb.Append(MessageService.NewParagraph); + sb.Append(KLRes.TryAgainSecs); + + m_strMsg = sb.ToString(); + } + } + + public sealed class FileLock : IDisposable + { + private const string LockFileExt = ".lock"; + private const string LockFileHeader = "KeePass Lock File"; + + private IOConnectionInfo m_iocLockFile; + + private sealed class LockFileInfo + { + public readonly string ID; + public readonly DateTime Time; + public readonly string UserName; + public readonly string Machine; + public readonly string Domain; + + private LockFileInfo(string strID, string strTime, string strUserName, + string strMachine, string strDomain) + { + this.ID = (strID ?? string.Empty).Trim(); + + DateTime dt; + if(TimeUtil.TryDeserializeUtc(strTime.Trim(), out dt)) + this.Time = dt; + else + { + Debug.Assert(false); + this.Time = DateTime.UtcNow; + } + + this.UserName = (strUserName ?? string.Empty).Trim(); + this.Machine = (strMachine ?? string.Empty).Trim(); + this.Domain = (strDomain ?? string.Empty).Trim(); + + if(this.Domain.Equals(this.Machine, StrUtil.CaseIgnoreCmp)) + this.Domain = string.Empty; + } + + public string GetOwner() + { + StringBuilder sb = new StringBuilder(); + sb.Append((this.UserName.Length > 0) ? this.UserName : "?"); + + bool bMachine = (this.Machine.Length > 0); + bool bDomain = (this.Domain.Length > 0); + if(bMachine || bDomain) + { + sb.Append(" ("); + sb.Append(this.Machine); + if(bMachine && bDomain) sb.Append(" @ "); + sb.Append(this.Domain); + sb.Append(")"); + } + + return sb.ToString(); + } + + public static LockFileInfo Load(IOConnectionInfo iocLockFile) + { + Stream s = null; + try + { + s = IOConnection.OpenRead(iocLockFile); + if(s == null) return null; + StreamReader sr = new StreamReader(s, StrUtil.Utf8); + string str = sr.ReadToEnd(); + sr.Close(); + if(str == null) { Debug.Assert(false); return null; } + + str = StrUtil.NormalizeNewLines(str, false); + string[] v = str.Split('\n'); + if((v == null) || (v.Length < 6)) { Debug.Assert(false); return null; } + + if(!v[0].StartsWith(LockFileHeader)) { Debug.Assert(false); return null; } + return new LockFileInfo(v[1], v[2], v[3], v[4], v[5]); + } + catch(FileNotFoundException) { } + catch(Exception) { Debug.Assert(false); } + finally { if(s != null) s.Close(); } + + return null; + } + + // Throws on error + public static LockFileInfo Create(IOConnectionInfo iocLockFile) + { + LockFileInfo lfi; + Stream s = null; + try + { + byte[] pbID = CryptoRandom.Instance.GetRandomBytes(16); + string strTime = TimeUtil.SerializeUtc(DateTime.Now); + +#if !KeePassLibSD + lfi = new LockFileInfo(Convert.ToBase64String(pbID), strTime, + 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(); +#if !KeePassLibSD + sb.AppendLine(LockFileHeader); + sb.AppendLine(lfi.ID); + sb.AppendLine(strTime); + sb.AppendLine(lfi.UserName); + sb.AppendLine(lfi.Machine); + sb.AppendLine(lfi.Domain); +#else + sb.Append(LockFileHeader + MessageService.NewLine); + sb.Append(lfi.ID + MessageService.NewLine); + sb.Append(strTime + MessageService.NewLine); + sb.Append(lfi.UserName + MessageService.NewLine); + sb.Append(lfi.Machine + MessageService.NewLine); + sb.Append(lfi.Domain + MessageService.NewLine); +#endif + + byte[] pbFile = StrUtil.Utf8.GetBytes(sb.ToString()); + + s = IOConnection.OpenWrite(iocLockFile); + if(s == null) throw new IOException(iocLockFile.GetDisplayName()); + s.Write(pbFile, 0, pbFile.Length); + } + finally { if(s != null) s.Close(); } + + return lfi; + } + } + + public FileLock(IOConnectionInfo iocBaseFile) + { + if(iocBaseFile == null) throw new ArgumentNullException("strBaseFile"); + + m_iocLockFile = iocBaseFile.CloneDeep(); + m_iocLockFile.Path += LockFileExt; + + LockFileInfo lfiEx = LockFileInfo.Load(m_iocLockFile); + if(lfiEx != null) + { + m_iocLockFile = null; // Otherwise Dispose deletes the existing one + throw new FileLockException(iocBaseFile.GetDisplayName(), + lfiEx.GetOwner()); + } + + LockFileInfo.Create(m_iocLockFile); + } + + ~FileLock() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool bDisposing) + { + if(m_iocLockFile == null) return; + + bool bFileDeleted = false; + for(int r = 0; r < 5; ++r) + { + // if(!OwnLockFile()) { bFileDeleted = true; break; } + + try + { + IOConnection.DeleteFile(m_iocLockFile); + bFileDeleted = true; + } + catch(Exception) { Debug.Assert(false); } + + if(bFileDeleted) break; + + if(bDisposing) Thread.Sleep(50); + } + + if(bDisposing && !bFileDeleted) + IOConnection.DeleteFile(m_iocLockFile); // Possibly with exception + + m_iocLockFile = null; + } + + // private bool OwnLockFile() + // { + // if(m_iocLockFile == null) { Debug.Assert(false); return false; } + // if(m_strLockID == null) { Debug.Assert(false); return false; } + // LockFileInfo lfi = LockFileInfo.Load(m_iocLockFile); + // if(lfi == null) return false; + // return m_strLockID.Equals(lfi.ID); + // } + } +} diff --git a/src/KeePassLib2Android/Serialization/FileTransactionEx.cs b/src/KeePassLib2Android/Serialization/FileTransactionEx.cs new file mode 100644 index 00000000..7e179963 --- /dev/null +++ b/src/KeePassLib2Android/Serialization/FileTransactionEx.cs @@ -0,0 +1,144 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Diagnostics; + +#if !KeePassLibSD +using System.Security.AccessControl; +#endif + +using KeePassLib.Utility; + +namespace KeePassLib.Serialization +{ + public sealed class FileTransactionEx + { + private bool m_bTransacted; + private IOConnectionInfo m_iocBase; + private IOConnectionInfo m_iocTemp; + + private bool m_bMadeUnhidden = false; + + private const string StrTempSuffix = ".tmp"; + + public FileTransactionEx(IOConnectionInfo iocBaseFile) + { + Initialize(iocBaseFile, true); + } + + public FileTransactionEx(IOConnectionInfo iocBaseFile, bool bTransacted) + { + Initialize(iocBaseFile, bTransacted); + } + + private void Initialize(IOConnectionInfo iocBaseFile, bool bTransacted) + { + if(iocBaseFile == null) throw new ArgumentNullException("iocBaseFile"); + + m_bTransacted = bTransacted; + m_iocBase = iocBaseFile.CloneDeep(); + + if(m_bTransacted) + { + m_iocTemp = m_iocBase.CloneDeep(); + m_iocTemp.Path += StrTempSuffix; + } + else m_iocTemp = m_iocBase; + } + + public Stream OpenWrite() + { + if(!m_bTransacted) m_bMadeUnhidden = UrlUtil.UnhideFile(m_iocTemp.Path); + else // m_bTransacted + { + try { IOConnection.DeleteFile(m_iocTemp); } + catch(Exception) { } + } + + return IOConnection.OpenWrite(m_iocTemp); + } + + public void CommitWrite() + { + if(m_bTransacted) CommitWriteTransaction(); + else // !m_bTransacted + { + if(m_bMadeUnhidden) UrlUtil.HideFile(m_iocTemp.Path, true); // Hide again + } + } + + private void CommitWriteTransaction() + { + bool bMadeUnhidden = UrlUtil.UnhideFile(m_iocBase.Path); + +#if !KeePassLibSD + FileSecurity bkSecurity = null; + bool bEfsEncrypted = false; +#endif + + if(IOConnection.FileExists(m_iocBase)) + { +#if !KeePassLibSD + if(m_iocBase.IsLocalFile()) + { + try + { + FileAttributes faBase = File.GetAttributes(m_iocBase.Path); + bEfsEncrypted = ((long)(faBase & FileAttributes.Encrypted) != 0); + + DateTime tCreation = File.GetCreationTime(m_iocBase.Path); + bkSecurity = File.GetAccessControl(m_iocBase.Path); + + File.SetCreationTime(m_iocTemp.Path, tCreation); + } + catch(Exception) { Debug.Assert(false); } + } +#endif + + IOConnection.DeleteFile(m_iocBase); + } + + IOConnection.RenameFile(m_iocTemp, m_iocBase); + +#if !KeePassLibSD + if(m_iocBase.IsLocalFile()) + { + try + { + if(bEfsEncrypted) + { + try { File.Encrypt(m_iocBase.Path); } + catch(Exception) { Debug.Assert(false); } + } + + if(bkSecurity != null) + File.SetAccessControl(m_iocBase.Path, bkSecurity); + } + catch(Exception) { Debug.Assert(false); } + } +#endif + + if(bMadeUnhidden) UrlUtil.HideFile(m_iocBase.Path, true); // Hide again + } + } +} diff --git a/src/KeePassLib2Android/Serialization/HashedBlockStream.cs b/src/KeePassLib2Android/Serialization/HashedBlockStream.cs new file mode 100644 index 00000000..403b6dfd --- /dev/null +++ b/src/KeePassLib2Android/Serialization/HashedBlockStream.cs @@ -0,0 +1,316 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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.IO; +using System.Security.Cryptography; +using System.Diagnostics; +using System.Text; + +using KeePassLib.Native; +using KeePassLib.Utility; + +#if KeePassLibSD +using KeePassLibSD; +#endif + +namespace KeePassLib.Serialization +{ + public sealed class HashedBlockStream : Stream + { + private const int m_nDefaultBufferSize = 1024 * 1024; // 1 MB + + private Stream m_sBaseStream; + private bool m_bWriting; + private bool m_bVerify; + private bool m_bEos = false; + + private BinaryReader m_brInput; + private BinaryWriter m_bwOutput; + + private byte[] m_pbBuffer; + private int m_nBufferPos = 0; + + private uint m_uBufferIndex = 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 { throw new NotSupportedException(); } + } + + public override long Position + { + get { throw new NotSupportedException(); } + set { throw new NotSupportedException(); } + } + + public HashedBlockStream(Stream sBaseStream, bool bWriting) + { + Initialize(sBaseStream, bWriting, 0, true); + } + + public HashedBlockStream(Stream sBaseStream, bool bWriting, int nBufferSize) + { + Initialize(sBaseStream, bWriting, nBufferSize, true); + } + + public HashedBlockStream(Stream sBaseStream, bool bWriting, int nBufferSize, + bool bVerify) + { + Initialize(sBaseStream, bWriting, nBufferSize, bVerify); + } + + private void Initialize(Stream sBaseStream, bool bWriting, int nBufferSize, + bool bVerify) + { + if(sBaseStream == null) throw new ArgumentNullException("sBaseStream"); + if(nBufferSize < 0) throw new ArgumentOutOfRangeException("nBufferSize"); + + if(nBufferSize == 0) nBufferSize = m_nDefaultBufferSize; + + m_sBaseStream = sBaseStream; + m_bWriting = bWriting; + m_bVerify = bVerify; + + UTF8Encoding utf8 = StrUtil.Utf8; + if(m_bWriting == false) // Reading mode + { + if(m_sBaseStream.CanRead == false) + throw new InvalidOperationException(); + + m_brInput = new BinaryReader(sBaseStream, utf8); + + m_pbBuffer = new byte[0]; + } + else // Writing mode + { + if(m_sBaseStream.CanWrite == false) + throw new InvalidOperationException(); + + m_bwOutput = new BinaryWriter(sBaseStream, utf8); + + m_pbBuffer = new byte[nBufferSize]; + } + } + + public override void Flush() + { + if(m_bWriting) m_bwOutput.Flush(); + } + + public override void Close() + { + if(m_sBaseStream != null) + { + if(m_bWriting == false) // Reading mode + { + m_brInput.Close(); + m_brInput = null; + } + else // Writing mode + { + if(m_nBufferPos == 0) // No data left in buffer + WriteHashedBlock(); // Write terminating block + else + { + WriteHashedBlock(); // Write remaining buffered data + WriteHashedBlock(); // Write terminating block + } + + Flush(); + m_bwOutput.Close(); + m_bwOutput = null; + } + + m_sBaseStream.Close(); + m_sBaseStream = null; + } + } + + public override long Seek(long lOffset, SeekOrigin soOrigin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long lValue) + { + throw new NotSupportedException(); + } + + public override int Read(byte[] pbBuffer, int nOffset, int nCount) + { + if(m_bWriting) throw new InvalidOperationException(); + + int nRemaining = nCount; + while(nRemaining > 0) + { + if(m_nBufferPos == m_pbBuffer.Length) + { + if(ReadHashedBlock() == false) + return nCount - nRemaining; // Bytes actually read + } + + int nCopy = Math.Min(m_pbBuffer.Length - m_nBufferPos, nRemaining); + + Array.Copy(m_pbBuffer, m_nBufferPos, pbBuffer, nOffset, nCopy); + + nOffset += nCopy; + m_nBufferPos += nCopy; + + nRemaining -= nCopy; + } + + return nCount; + } + + private bool ReadHashedBlock() + { + if(m_bEos) return false; // End of stream reached already + + m_nBufferPos = 0; + + if(m_brInput.ReadUInt32() != m_uBufferIndex) + throw new InvalidDataException(); + ++m_uBufferIndex; + + byte[] pbStoredHash = m_brInput.ReadBytes(32); + if((pbStoredHash == null) || (pbStoredHash.Length != 32)) + throw new InvalidDataException(); + + int nBufferSize = 0; + try { nBufferSize = m_brInput.ReadInt32(); } + catch(NullReferenceException) // Mono bug workaround (LaunchPad 783268) + { + if(!NativeLib.IsUnix()) throw; + } + + if(nBufferSize < 0) + throw new InvalidDataException(); + + if(nBufferSize == 0) + { + for(int iHash = 0; iHash < 32; ++iHash) + { + if(pbStoredHash[iHash] != 0) + throw new InvalidDataException(); + } + + m_bEos = true; + m_pbBuffer = new byte[0]; + return false; + } + + m_pbBuffer = m_brInput.ReadBytes(nBufferSize); + if((m_pbBuffer == null) || ((m_pbBuffer.Length != nBufferSize) && m_bVerify)) + throw new InvalidDataException(); + + if(m_bVerify) + { + SHA256Managed sha256 = new SHA256Managed(); + byte[] pbComputedHash = sha256.ComputeHash(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(); + } + } + + return true; + } + + public override void Write(byte[] pbBuffer, int nOffset, int nCount) + { + if(!m_bWriting) throw new InvalidOperationException(); + + while(nCount > 0) + { + if(m_nBufferPos == m_pbBuffer.Length) + WriteHashedBlock(); + + int nCopy = Math.Min(m_pbBuffer.Length - m_nBufferPos, nCount); + + Array.Copy(pbBuffer, nOffset, m_pbBuffer, m_nBufferPos, nCopy); + + nOffset += nCopy; + m_nBufferPos += nCopy; + + nCount -= nCopy; + } + } + + private void WriteHashedBlock() + { + m_bwOutput.Write(m_uBufferIndex); + ++m_uBufferIndex; + + if(m_nBufferPos > 0) + { + SHA256Managed sha256 = new SHA256Managed(); + +#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 + + m_bwOutput.Write(pbHash); + } + else + { + m_bwOutput.Write((ulong)0); // Zero hash + m_bwOutput.Write((ulong)0); + m_bwOutput.Write((ulong)0); + m_bwOutput.Write((ulong)0); + } + + m_bwOutput.Write(m_nBufferPos); + + if(m_nBufferPos > 0) + m_bwOutput.Write(m_pbBuffer, 0, m_nBufferPos); + + m_nBufferPos = 0; + } + } +} diff --git a/src/KeePassLib2Android/Serialization/IOConnection.cs b/src/KeePassLib2Android/Serialization/IOConnection.cs new file mode 100644 index 00000000..e115ff5b --- /dev/null +++ b/src/KeePassLib2Android/Serialization/IOConnection.cs @@ -0,0 +1,453 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Net; +using System.Security.Cryptography.X509Certificates; +using System.Diagnostics; + +#if !KeePassLibSD +using System.Net.Cache; +using System.Net.Security; +#endif + +using KeePassLib.Native; +using KeePassLib.Utility; + +namespace KeePassLib.Serialization +{ +#if !KeePassLibSD + public sealed class IOWebClient : WebClient + { + protected override WebRequest GetWebRequest(Uri address) + { + WebRequest request = base.GetWebRequest(address); + IOConnection.ConfigureWebRequest(request); + return request; + } + } +#endif + + 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 string m_strProxyUserName = string.Empty; + private static string m_strProxyPassword = string.Empty; +#endif + + // Web request methods + public const string WrmDeleteFile = "DELETEFILE"; + public const string WrmMoveFile = "MOVEFILE"; + + // Web request headers + public const string WrhMoveFileTo = "MoveFileTo"; + +#if !KeePassLibSD + // Allow self-signed certificates, expired certificates, etc. + private static bool ValidateServerCertificate(object sender, + X509Certificate certificate, X509Chain chain, + SslPolicyErrors sslPolicyErrors) + { + return true; + } + + public static void SetProxy(ProxyServerType pst, string strAddr, + string strPort, string strUserName, string strPassword) + { + m_pstProxyType = pst; + m_strProxyAddr = (strAddr ?? string.Empty); + m_strProxyPort = (strPort ?? string.Empty); + m_strProxyUserName = (strUserName ?? string.Empty); + m_strProxyPassword = (strPassword ?? string.Empty); + } + + internal static void ConfigureWebRequest(WebRequest request) + { + 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); + // } + + // Not implemented and ignored in Mono < 2.10 + try + { + request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore); + } + catch(NotImplementedException) { } + catch(Exception) { Debug.Assert(false); } + + try + { + IWebProxy prx; + if(GetWebProxy(out prx)) request.Proxy = prx; + } + catch(Exception) { Debug.Assert(false); } + } + + internal static void ConfigureWebClient(WebClient wc) + { + // Not implemented and ignored in Mono < 2.10 + try + { + wc.CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore); + } + catch(NotImplementedException) { } + catch(Exception) { Debug.Assert(false); } + + try + { + IWebProxy prx; + if(GetWebProxy(out prx)) wc.Proxy = prx; + } + catch(Exception) { Debug.Assert(false); } + } + + private static bool GetWebProxy(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) + 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 + } + catch(Exception exProxy) + { + string strInfo = m_strProxyAddr; + if(m_strProxyPort.Length > 0) strInfo += ":" + m_strProxyPort; + MessageService.ShowWarning(strInfo, exProxy.Message); + } + + return false; // Use default + } + + if((m_strProxyUserName.Length == 0) && (m_strProxyPassword.Length == 0)) + return false; // Use default proxy, no auth + + try + { + prx = WebRequest.DefaultWebProxy; + if(prx == null) prx = WebRequest.GetSystemWebProxy(); + if(prx == null) throw new InvalidOperationException(); + + prx.Credentials = new NetworkCredential(m_strProxyUserName, + m_strProxyPassword); + return true; + } + catch(Exception) { Debug.Assert(false); } + + return false; + } + + private static void PrepareWebAccess() + { + ServicePointManager.ServerCertificateValidationCallback = + ValidateServerCertificate; + } + + private static IOWebClient CreateWebClient(IOConnectionInfo ioc) + { + PrepareWebAccess(); + + IOWebClient wc = new IOWebClient(); + ConfigureWebClient(wc); + + if((ioc.UserName.Length > 0) || (ioc.Password.Length > 0)) + wc.Credentials = new NetworkCredential(ioc.UserName, ioc.Password); + else if(NativeLib.IsUnix()) // Mono requires credentials + wc.Credentials = new NetworkCredential("anonymous", string.Empty); + + return wc; + } + + private static WebRequest CreateWebRequest(IOConnectionInfo ioc) + { + PrepareWebAccess(); + + WebRequest req = WebRequest.Create(ioc.Path); + ConfigureWebRequest(req); + + if((ioc.UserName.Length > 0) || (ioc.Password.Length > 0)) + req.Credentials = new NetworkCredential(ioc.UserName, ioc.Password); + else if(NativeLib.IsUnix()) // Mono requires credentials + req.Credentials = new NetworkCredential("anonymous", string.Empty); + + return req; + } + + public static Stream OpenRead(IOConnectionInfo ioc) + { + if(StrUtil.IsDataUri(ioc.Path)) + { + byte[] pbData = StrUtil.DataUriToData(ioc.Path); + if(pbData != null) return new MemoryStream(pbData, false); + } + + if(ioc.IsLocalFile()) return OpenReadLocal(ioc); + + return CreateWebClient(ioc).OpenRead(new Uri(ioc.Path)); + } +#else + public static Stream OpenRead(IOConnectionInfo ioc) + { + return OpenReadLocal(ioc); + } +#endif + + private static Stream OpenReadLocal(IOConnectionInfo ioc) + { + return new FileStream(ioc.Path, FileMode.Open, FileAccess.Read, + FileShare.Read); + } + +#if !KeePassLibSD + public static Stream OpenWrite(IOConnectionInfo ioc) + { + if(ioc == null) { Debug.Assert(false); return null; } + + if(ioc.IsLocalFile()) return OpenWriteLocal(ioc); + + Uri uri = new Uri(ioc.Path); + + // 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); + + return CreateWebClient(ioc).OpenWrite(uri); + } +#else + public static Stream OpenWrite(IOConnectionInfo ioc) + { + return OpenWriteLocal(ioc); + } +#endif + + private static Stream OpenWriteLocal(IOConnectionInfo ioc) + { + return new FileStream(ioc.Path, FileMode.Create, FileAccess.Write, + FileShare.None); + } + + public static bool FileExists(IOConnectionInfo ioc) + { + return FileExists(ioc, false); + } + + public static bool FileExists(IOConnectionInfo ioc, bool bThrowErrors) + { + if(ioc == null) { Debug.Assert(false); return false; } + + if(ioc.IsLocalFile()) return File.Exists(ioc.Path); + +#if !KeePassLibSD + if(ioc.Path.StartsWith("ftp://", StrUtil.CaseIgnoreCmp)) + { + bool b = SendCommand(ioc, WebRequestMethods.Ftp.GetDateTimestamp); + if(!b && bThrowErrors) throw new InvalidOperationException(); + return b; + } +#endif + + try + { + Stream s = OpenRead(ioc); + if(s == null) throw new FileNotFoundException(); + + try { s.ReadByte(); } + catch(Exception) { } + + // We didn't download the file completely; close may throw + // an exception -- that's okay + try { s.Close(); } + catch(Exception) { } + } + catch(Exception) + { + if(bThrowErrors) throw; + return false; + } + + return true; + } + + public static void DeleteFile(IOConnectionInfo ioc) + { + 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) + { + File.Delete(UrlUtil.FileUrlToPath(ioc.Path)); + return; + } + else req.Method = WrmDeleteFile; + + DisposeResponse(req.GetResponse(), true); + } +#endif + } + + /// + /// Rename/move a file. For local file system and WebDAV, the + /// specified file is moved, i.e. the file destination can be + /// in a different directory/path. In contrast, for FTP the + /// file is renamed, i.e. its destination must be in the same + /// directory/path. + /// + /// Source file path. + /// Target file path. + public static void RenameFile(IOConnectionInfo iocFrom, IOConnectionInfo iocTo) + { + if(iocFrom.IsLocalFile()) { File.Move(iocFrom.Path, iocTo.Path); return; } + +#if !KeePassLibSD + WebRequest req = CreateWebRequest(iocFrom); + if(req != null) + { + if(req is HttpWebRequest) + { + req.Method = "MOVE"; + req.Headers.Set("Destination", iocTo.Path); // Full URL supported + } + else if(req is FtpWebRequest) + { + req.Method = WebRequestMethods.Ftp.Rename; + ((FtpWebRequest)req).RenameTo = UrlUtil.GetFileName(iocTo.Path); + } + else if(req is FileWebRequest) + { + File.Move(UrlUtil.FileUrlToPath(iocFrom.Path), + UrlUtil.FileUrlToPath(iocTo.Path)); + return; + } + else + { + req.Method = WrmMoveFile; + req.Headers.Set(WrhMoveFileTo, iocTo.Path); + } + + DisposeResponse(req.GetResponse(), true); + } +#endif + + // using(Stream sIn = IOConnection.OpenRead(iocFrom)) + // { + // using(Stream sOut = IOConnection.OpenWrite(iocTo)) + // { + // MemUtil.CopyStream(sIn, sOut); + // sOut.Close(); + // } + // + // sIn.Close(); + // } + // DeleteFile(iocFrom); + } + +#if !KeePassLibSD + private static bool SendCommand(IOConnectionInfo ioc, string strMethod) + { + try + { + WebRequest req = CreateWebRequest(ioc); + req.Method = strMethod; + DisposeResponse(req.GetResponse(), true); + } + catch(Exception) { return false; } + + return true; + } +#endif + + private static void DisposeResponse(WebResponse wr, bool bGetStream) + { + if(wr == null) return; + + try + { + if(bGetStream) + { + Stream s = wr.GetResponseStream(); + if(s != null) s.Close(); + } + } + catch(Exception) { Debug.Assert(false); } + + try { wr.Close(); } + catch(Exception) { Debug.Assert(false); } + } + + public static byte[] ReadFile(IOConnectionInfo ioc) + { + Stream sIn = null; + MemoryStream ms = null; + try + { + sIn = IOConnection.OpenRead(ioc); + if(sIn == null) return null; + + ms = new MemoryStream(); + MemUtil.CopyStream(sIn, ms); + + return ms.ToArray(); + } + catch(Exception) { } + finally + { + if(sIn != null) sIn.Close(); + if(ms != null) ms.Close(); + } + + return null; + } + } +} diff --git a/src/KeePassLib2Android/Serialization/IOConnectionInfo.cs b/src/KeePassLib2Android/Serialization/IOConnectionInfo.cs new file mode 100644 index 00000000..2adc05ed --- /dev/null +++ b/src/KeePassLib2Android/Serialization/IOConnectionInfo.cs @@ -0,0 +1,342 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Net; +using System.ComponentModel; +using System.Diagnostics; + +using KeePassLib.Interfaces; +using KeePassLib.Utility; + +namespace KeePassLib.Serialization +{ + public enum IOCredSaveMode + { + /// + /// Do not remember user name or password. + /// + NoSave = 0, + + /// + /// Remember the user name only, not the password. + /// + UserNameOnly, + + /// + /// Save both user name and password. + /// + SaveCred + } + + public enum IOCredProtMode + { + None = 0, + Obf + } + + /* public enum IOFileFormatHint + { + None = 0, + Deprecated + } */ + + public sealed class IOConnectionInfo : IDeepCloneable + { + // private IOFileFormatHint m_ioHint = IOFileFormatHint.None; + + private string m_strUrl = string.Empty; + public string Path + { + get { return m_strUrl; } + set + { + Debug.Assert(value != null); + if(value == null) throw new ArgumentNullException("value"); + + m_strUrl = value; + } + } + + private string m_strUser = string.Empty; + [DefaultValue("")] + public string UserName + { + get { return m_strUser; } + set + { + Debug.Assert(value != null); + if(value == null) throw new ArgumentNullException("value"); + + m_strUser = value; + } + } + + private string m_strPassword = string.Empty; + [DefaultValue("")] + public string Password + { + get { return m_strPassword; } + set + { + Debug.Assert(value != null); + if(value == null) throw new ArgumentNullException("value"); + + m_strPassword = value; + } + } + + private IOCredProtMode m_ioCredProtMode = IOCredProtMode.None; + public IOCredProtMode CredProtMode + { + get { return m_ioCredProtMode; } + set { m_ioCredProtMode = value; } + } + + private IOCredSaveMode m_ioCredSaveMode = IOCredSaveMode.NoSave; + public IOCredSaveMode CredSaveMode + { + get { return m_ioCredSaveMode; } + set { m_ioCredSaveMode = value; } + } + + /* public IOFileFormatHint FileFormatHint + { + get { return m_ioHint; } + set { m_ioHint = value; } + } */ + + public IOConnectionInfo CloneDeep() + { + return (IOConnectionInfo)this.MemberwiseClone(); + } + + /* + /// + /// Serialize the current connection info to a string. Credentials + /// are only serialized if the SaveCredentials property + /// is true. + /// + /// Input object to be serialized. + /// Serialized object as string. + public static string SerializeToString(IOConnectionInfo iocToCompile) + { + Debug.Assert(iocToCompile != null); + if(iocToCompile == null) throw new ArgumentNullException("iocToCompile"); + + 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; + } + } + } + + if(chSep == char.MinValue) throw new FormatException(); + + StringBuilder sb = new StringBuilder(); + sb.Append(chSep); + sb.Append(strUrl); + sb.Append(chSep); + + if(iocToCompile.CredSaveMode == IOCredSaveMode.SaveCred) + { + sb.Append('C'); + sb.Append(chSep); + sb.Append(strUser); + sb.Append(chSep); + sb.Append(strPassword); + } + else if(iocToCompile.CredSaveMode == IOCredSaveMode.UserNameOnly) + { + sb.Append('U'); + sb.Append(chSep); + sb.Append(strUser); + sb.Append(chSep); + } + else // Don't remember credentials + { + sb.Append('N'); + sb.Append(chSep); + sb.Append(chSep); + } + + return sb.ToString(); + } + + public static IOConnectionInfo UnserializeFromString(string strToDecompile) + { + Debug.Assert(strToDecompile != null); + if(strToDecompile == null) throw new ArgumentNullException("strToDecompile"); + if(strToDecompile.Length <= 1) throw new ArgumentException(); + + char chSep = strToDecompile[0]; + string[] vParts = strToDecompile.Substring(1, strToDecompile.Length - + 1).Split(new char[]{ chSep }); + if(vParts.Length < 4) throw new ArgumentException(); + + IOConnectionInfo s = new IOConnectionInfo(); + s.Path = vParts[0]; + + if(vParts[1] == "C") + s.CredSaveMode = IOCredSaveMode.SaveCred; + else if(vParts[1] == "U") + s.CredSaveMode = IOCredSaveMode.UserNameOnly; + else + s.CredSaveMode = IOCredSaveMode.NoSave; + + s.UserName = TransformUnreadable(vParts[2], false); + s.Password = TransformUnreadable(vParts[3], false); + return s; + } + */ + + /* + /// + /// Very simple string protection. Doesn't really encrypt the input + /// string, only encodes it that it's not readable on the first glance. + /// + /// The string to encode/decode. + /// If true, the string will be encoded, + /// otherwise it'll be decoded. + /// Encoded/decoded string. + private static string TransformUnreadable(string strToEncode, bool bEncode) + { + Debug.Assert(strToEncode != null); + if(strToEncode == null) throw new ArgumentNullException("strToEncode"); + + if(bEncode) + { + byte[] pbUtf8 = StrUtil.Utf8.GetBytes(strToEncode); + + unchecked + { + for(int iPos = 0; iPos < pbUtf8.Length; ++iPos) + pbUtf8[iPos] += (byte)(iPos * 11); + } + + return Convert.ToBase64String(pbUtf8); + } + else // Decode + { + byte[] pbBase = Convert.FromBase64String(strToEncode); + + unchecked + { + for(int iPos = 0; iPos < pbBase.Length; ++iPos) + pbBase[iPos] -= (byte)(iPos * 11); + } + + return StrUtil.Utf8.GetString(pbBase, 0, pbBase.Length); + } + } + */ + + public string GetDisplayName() + { + string str = m_strUrl; + + if(m_strUser.Length > 0) + str += " (" + m_strUser + ")"; + + return str; + } + + public bool IsEmpty() + { + return (m_strUrl.Length > 0); + } + + public static IOConnectionInfo FromPath(string strPath) + { + IOConnectionInfo ioc = new IOConnectionInfo(); + + ioc.Path = strPath; + ioc.CredSaveMode = IOCredSaveMode.NoSave; + + return ioc; + } + + public bool CanProbablyAccess() + { + if(IsLocalFile()) return File.Exists(m_strUrl); + + return true; + } + + public bool IsLocalFile() + { + // Not just ":/", see e.g. AppConfigEx.ChangePathRelAbs + return (m_strUrl.IndexOf(@"://") < 0); + } + + public void ClearCredentials(bool bDependingOnRememberMode) + { + if((bDependingOnRememberMode == false) || + (m_ioCredSaveMode == IOCredSaveMode.NoSave)) + { + m_strUser = string.Empty; + } + + if((bDependingOnRememberMode == false) || + (m_ioCredSaveMode == IOCredSaveMode.NoSave) || + (m_ioCredSaveMode == IOCredSaveMode.UserNameOnly)) + { + m_strPassword = string.Empty; + } + } + + public void Obfuscate(bool bObf) + { + if(bObf && (m_ioCredProtMode == IOCredProtMode.None)) + { + m_strPassword = StrUtil.Obfuscate(m_strPassword); + m_ioCredProtMode = IOCredProtMode.Obf; + } + else if(!bObf && (m_ioCredProtMode == IOCredProtMode.Obf)) + { + m_strPassword = StrUtil.Deobfuscate(m_strPassword); + m_ioCredProtMode = IOCredProtMode.None; + } + } + } +} diff --git a/src/KeePassLib2Android/Serialization/KdbxFile.Read.Streamed.cs b/src/KeePassLib2Android/Serialization/KdbxFile.Read.Streamed.cs new file mode 100644 index 00000000..27b85c4e --- /dev/null +++ b/src/KeePassLib2Android/Serialization/KdbxFile.Read.Streamed.cs @@ -0,0 +1,877 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Security; +using System.Security.Cryptography; +using System.Drawing; +using System.Xml; +using System.IO; +using System.Diagnostics; + +using KeePassLib; +using KeePassLib.Collections; +using KeePassLib.Cryptography; +using KeePassLib.Cryptography.Cipher; +using KeePassLib.Interfaces; +using KeePassLib.Resources; +using KeePassLib.Security; +using KeePassLib.Utility; + +namespace KeePassLib.Serialization +{ + /// + /// Serialization to KeePass KDBX files. + /// + public sealed partial class KdbxFile + { + private enum KdbContext + { + Null, + KeePassFile, + Meta, + Root, + MemoryProtection, + CustomIcons, + CustomIcon, + Binaries, + CustomData, + CustomDataItem, + RootDeletedObjects, + DeletedObject, + Group, + GroupTimes, + Entry, + EntryTimes, + EntryString, + EntryBinary, + EntryAutoType, + EntryAutoTypeItem, + EntryHistory + } + + private bool m_bReadNextNode = true; + private Stack m_ctxGroups = new Stack(); + private PwGroup m_ctxGroup = null; + private PwEntry m_ctxEntry = null; + private string m_ctxStringName = null; + private ProtectedString m_ctxStringValue = null; + private string m_ctxBinaryName = null; + private ProtectedBinary m_ctxBinaryValue = null; + private string m_ctxATName = null; + private string m_ctxATSeq = null; + private bool m_bEntryInHistory = false; + private PwEntry m_ctxHistoryBase = null; + private PwDeletedObject m_ctxDeletedObject = null; + private PwUuid m_uuidCustomIconID = PwUuid.Zero; + private byte[] m_pbCustomIconData = null; + private string m_strCustomDataKey = null; + private string m_strCustomDataValue = null; + + private void ReadXmlStreamed(Stream readerStream, Stream sParentStream) + { + ReadDocumentStreamed(CreateXmlReader(readerStream), sParentStream); + } + + internal static XmlReaderSettings CreateStdXmlReaderSettings() + { + XmlReaderSettings xrs = new XmlReaderSettings(); + + xrs.CloseInput = true; + xrs.IgnoreComments = true; + xrs.IgnoreProcessingInstructions = true; + xrs.IgnoreWhitespace = true; + +#if !KeePassLibSD + xrs.ProhibitDtd = true; +#endif + + xrs.ValidationType = ValidationType.None; + + return xrs; + } + + private static XmlReader CreateXmlReader(Stream readerStream) + { + XmlReaderSettings xrs = CreateStdXmlReaderSettings(); + return XmlReader.Create(readerStream, xrs); + } + + private void ReadDocumentStreamed(XmlReader xr, Stream sParentStream) + { + Debug.Assert(xr != null); + if(xr == null) throw new ArgumentNullException("xr"); + + m_ctxGroups.Clear(); + m_dictBinPool = new Dictionary(); + + KdbContext ctx = KdbContext.Null; + + uint uTagCounter = 0; + + bool bSupportsStatus = (m_slLogger != null); + long lStreamLength = 1; + try + { + sParentStream.Position.ToString(); // Test Position support + lStreamLength = sParentStream.Length; + } + catch(Exception) { bSupportsStatus = false; } + if(lStreamLength <= 0) { Debug.Assert(false); lStreamLength = 1; } + + m_bReadNextNode = true; + + while(true) + { + if(m_bReadNextNode) + { + if(!xr.Read()) break; + } + else m_bReadNextNode = true; + + switch(xr.NodeType) + { + case XmlNodeType.Element: + ctx = ReadXmlElement(ctx, xr); + break; + + case XmlNodeType.EndElement: + ctx = EndXmlElement(ctx, xr); + break; + + case XmlNodeType.XmlDeclaration: + break; // Ignore + + default: + Debug.Assert(false); + break; + } + + ++uTagCounter; + if(((uTagCounter % 256) == 0) && bSupportsStatus) + { + Debug.Assert(lStreamLength == sParentStream.Length); + uint uPct = (uint)((sParentStream.Position * 100) / + lStreamLength); + + // Clip percent value in case the stream reports incorrect + // position/length values (M120413) + if(uPct > 100) { Debug.Assert(false); uPct = 100; } + + m_slLogger.SetProgress(uPct); + } + } + + Debug.Assert(ctx == KdbContext.Null); + if(ctx != KdbContext.Null) throw new FormatException(); + + Debug.Assert(m_ctxGroups.Count == 0); + if(m_ctxGroups.Count != 0) throw new FormatException(); + } + + private KdbContext ReadXmlElement(KdbContext ctx, XmlReader xr) + { + Debug.Assert(xr.NodeType == XmlNodeType.Element); + + switch(ctx) + { + case KdbContext.Null: + if(xr.Name == ElemDocNode) + return SwitchContext(ctx, KdbContext.KeePassFile, xr); + else ReadUnknown(xr); + break; + + case KdbContext.KeePassFile: + if(xr.Name == ElemMeta) + return SwitchContext(ctx, KdbContext.Meta, xr); + else if(xr.Name == ElemRoot) + return SwitchContext(ctx, KdbContext.Root, xr); + else ReadUnknown(xr); + break; + + case KdbContext.Meta: + if(xr.Name == ElemGenerator) + ReadString(xr); // Ignore + else if(xr.Name == ElemHeaderHash) + { + string strHash = ReadString(xr); + if(!string.IsNullOrEmpty(strHash) && (m_pbHashOfHeader != null) && + !m_bRepairMode) + { + byte[] pbHash = Convert.FromBase64String(strHash); + if(!MemUtil.ArraysEqual(pbHash, m_pbHashOfHeader)) + throw new IOException(KLRes.FileCorrupted); + } + } + else if(xr.Name == ElemDbName) + m_pwDatabase.Name = ReadString(xr); + else if(xr.Name == ElemDbNameChanged) + m_pwDatabase.NameChanged = ReadTime(xr); + else if(xr.Name == ElemDbDesc) + m_pwDatabase.Description = ReadString(xr); + else if(xr.Name == ElemDbDescChanged) + m_pwDatabase.DescriptionChanged = ReadTime(xr); + else if(xr.Name == ElemDbDefaultUser) + m_pwDatabase.DefaultUserName = ReadString(xr); + else if(xr.Name == ElemDbDefaultUserChanged) + m_pwDatabase.DefaultUserNameChanged = ReadTime(xr); + else if(xr.Name == ElemDbMntncHistoryDays) + m_pwDatabase.MaintenanceHistoryDays = ReadUInt(xr, 365); + else if(xr.Name == ElemDbColor) + { + string strColor = ReadString(xr); + if(!string.IsNullOrEmpty(strColor)) + m_pwDatabase.Color = ColorTranslator.FromHtml(strColor); + } + else if(xr.Name == ElemDbKeyChanged) + m_pwDatabase.MasterKeyChanged = ReadTime(xr); + else if(xr.Name == ElemDbKeyChangeRec) + m_pwDatabase.MasterKeyChangeRec = ReadLong(xr, -1); + else if(xr.Name == ElemDbKeyChangeForce) + m_pwDatabase.MasterKeyChangeForce = ReadLong(xr, -1); + else if(xr.Name == ElemMemoryProt) + return SwitchContext(ctx, KdbContext.MemoryProtection, xr); + else if(xr.Name == ElemCustomIcons) + return SwitchContext(ctx, KdbContext.CustomIcons, xr); + else if(xr.Name == ElemRecycleBinEnabled) + m_pwDatabase.RecycleBinEnabled = ReadBool(xr, true); + else if(xr.Name == ElemRecycleBinUuid) + m_pwDatabase.RecycleBinUuid = ReadUuid(xr); + else if(xr.Name == ElemRecycleBinChanged) + m_pwDatabase.RecycleBinChanged = ReadTime(xr); + else if(xr.Name == ElemEntryTemplatesGroup) + m_pwDatabase.EntryTemplatesGroup = ReadUuid(xr); + else if(xr.Name == ElemEntryTemplatesGroupChanged) + m_pwDatabase.EntryTemplatesGroupChanged = ReadTime(xr); + else if(xr.Name == ElemHistoryMaxItems) + m_pwDatabase.HistoryMaxItems = ReadInt(xr, -1); + else if(xr.Name == ElemHistoryMaxSize) + m_pwDatabase.HistoryMaxSize = ReadLong(xr, -1); + else if(xr.Name == ElemLastSelectedGroup) + m_pwDatabase.LastSelectedGroup = ReadUuid(xr); + else if(xr.Name == ElemLastTopVisibleGroup) + m_pwDatabase.LastTopVisibleGroup = ReadUuid(xr); + else if(xr.Name == ElemBinaries) + return SwitchContext(ctx, KdbContext.Binaries, xr); + else if(xr.Name == ElemCustomData) + return SwitchContext(ctx, KdbContext.CustomData, xr); + else ReadUnknown(xr); + break; + + case KdbContext.MemoryProtection: + if(xr.Name == ElemProtTitle) + m_pwDatabase.MemoryProtection.ProtectTitle = ReadBool(xr, false); + else if(xr.Name == ElemProtUserName) + m_pwDatabase.MemoryProtection.ProtectUserName = ReadBool(xr, false); + else if(xr.Name == ElemProtPassword) + m_pwDatabase.MemoryProtection.ProtectPassword = ReadBool(xr, true); + else if(xr.Name == ElemProtUrl) + m_pwDatabase.MemoryProtection.ProtectUrl = ReadBool(xr, false); + else if(xr.Name == ElemProtNotes) + m_pwDatabase.MemoryProtection.ProtectNotes = ReadBool(xr, false); + // else if(xr.Name == ElemProtAutoHide) + // m_pwDatabase.MemoryProtection.AutoEnableVisualHiding = ReadBool(xr, true); + else ReadUnknown(xr); + break; + + case KdbContext.CustomIcons: + if(xr.Name == ElemCustomIconItem) + return SwitchContext(ctx, KdbContext.CustomIcon, xr); + else ReadUnknown(xr); + break; + + case KdbContext.CustomIcon: + if(xr.Name == ElemCustomIconItemID) + m_uuidCustomIconID = ReadUuid(xr); + else if(xr.Name == ElemCustomIconItemData) + { + string strData = ReadString(xr); + if(!string.IsNullOrEmpty(strData)) + m_pbCustomIconData = Convert.FromBase64String(strData); + else { Debug.Assert(false); } + } + else ReadUnknown(xr); + break; + + case KdbContext.Binaries: + if(xr.Name == ElemBinary) + { + if(xr.MoveToAttribute(AttrId)) + { + string strKey = xr.Value; + ProtectedBinary pbData = ReadProtectedBinary(xr); + + m_dictBinPool[strKey ?? string.Empty] = pbData; + } + else ReadUnknown(xr); + } + else ReadUnknown(xr); + break; + + case KdbContext.CustomData: + if(xr.Name == ElemStringDictExItem) + return SwitchContext(ctx, KdbContext.CustomDataItem, xr); + else ReadUnknown(xr); + break; + + case KdbContext.CustomDataItem: + if(xr.Name == ElemKey) + m_strCustomDataKey = ReadString(xr); + else if(xr.Name == ElemValue) + m_strCustomDataValue = ReadString(xr); + else ReadUnknown(xr); + break; + + case KdbContext.Root: + if(xr.Name == ElemGroup) + { + Debug.Assert(m_ctxGroups.Count == 0); + if(m_ctxGroups.Count != 0) throw new FormatException(); + + m_pwDatabase.RootGroup = new PwGroup(false, false); + m_ctxGroups.Push(m_pwDatabase.RootGroup); + m_ctxGroup = m_ctxGroups.Peek(); + + return SwitchContext(ctx, KdbContext.Group, xr); + } + else if(xr.Name == ElemDeletedObjects) + return SwitchContext(ctx, KdbContext.RootDeletedObjects, xr); + else ReadUnknown(xr); + break; + + case KdbContext.Group: + if(xr.Name == ElemUuid) + m_ctxGroup.Uuid = ReadUuid(xr); + else if(xr.Name == ElemName) + m_ctxGroup.Name = ReadString(xr); + else if(xr.Name == ElemNotes) + m_ctxGroup.Notes = ReadString(xr); + else if(xr.Name == ElemIcon) + m_ctxGroup.IconId = (PwIcon)ReadInt(xr, (int)PwIcon.Folder); + else if(xr.Name == ElemCustomIconID) + m_ctxGroup.CustomIconUuid = ReadUuid(xr); + else if(xr.Name == ElemTimes) + return SwitchContext(ctx, KdbContext.GroupTimes, xr); + else if(xr.Name == ElemIsExpanded) + m_ctxGroup.IsExpanded = ReadBool(xr, true); + else if(xr.Name == ElemGroupDefaultAutoTypeSeq) + m_ctxGroup.DefaultAutoTypeSequence = ReadString(xr); + else if(xr.Name == ElemEnableAutoType) + m_ctxGroup.EnableAutoType = StrUtil.StringToBoolEx(ReadString(xr)); + else if(xr.Name == ElemEnableSearching) + m_ctxGroup.EnableSearching = StrUtil.StringToBoolEx(ReadString(xr)); + else if(xr.Name == ElemLastTopVisibleEntry) + m_ctxGroup.LastTopVisibleEntry = ReadUuid(xr); + else if(xr.Name == ElemGroup) + { + m_ctxGroup = new PwGroup(false, false); + m_ctxGroups.Peek().AddGroup(m_ctxGroup, true); + + m_ctxGroups.Push(m_ctxGroup); + + return SwitchContext(ctx, KdbContext.Group, xr); + } + else if(xr.Name == ElemEntry) + { + m_ctxEntry = new PwEntry(false, false); + m_ctxGroup.AddEntry(m_ctxEntry, true); + + m_bEntryInHistory = false; + return SwitchContext(ctx, KdbContext.Entry, xr); + } + else ReadUnknown(xr); + break; + + case KdbContext.Entry: + if(xr.Name == ElemUuid) + m_ctxEntry.Uuid = ReadUuid(xr); + else if(xr.Name == ElemIcon) + m_ctxEntry.IconId = (PwIcon)ReadInt(xr, (int)PwIcon.Key); + else if(xr.Name == ElemCustomIconID) + m_ctxEntry.CustomIconUuid = ReadUuid(xr); + else if(xr.Name == ElemFgColor) + { + string strColor = ReadString(xr); + if(!string.IsNullOrEmpty(strColor)) + m_ctxEntry.ForegroundColor = ColorTranslator.FromHtml(strColor); + } + else if(xr.Name == ElemBgColor) + { + string strColor = ReadString(xr); + if(!string.IsNullOrEmpty(strColor)) + m_ctxEntry.BackgroundColor = ColorTranslator.FromHtml(strColor); + } + else if(xr.Name == ElemOverrideUrl) + m_ctxEntry.OverrideUrl = ReadString(xr); + else if(xr.Name == ElemTags) + m_ctxEntry.Tags = StrUtil.StringToTags(ReadString(xr)); + else if(xr.Name == ElemTimes) + return SwitchContext(ctx, KdbContext.EntryTimes, xr); + else if(xr.Name == ElemString) + return SwitchContext(ctx, KdbContext.EntryString, xr); + else if(xr.Name == ElemBinary) + return SwitchContext(ctx, KdbContext.EntryBinary, xr); + else if(xr.Name == ElemAutoType) + return SwitchContext(ctx, KdbContext.EntryAutoType, xr); + else if(xr.Name == ElemHistory) + { + Debug.Assert(m_bEntryInHistory == false); + + if(m_bEntryInHistory == false) + { + m_ctxHistoryBase = m_ctxEntry; + return SwitchContext(ctx, KdbContext.EntryHistory, xr); + } + else ReadUnknown(xr); + } + else ReadUnknown(xr); + break; + + case KdbContext.GroupTimes: + case KdbContext.EntryTimes: + ITimeLogger tl = ((ctx == KdbContext.GroupTimes) ? + (ITimeLogger)m_ctxGroup : (ITimeLogger)m_ctxEntry); + Debug.Assert(tl != null); + + if(xr.Name == ElemLastModTime) + tl.LastModificationTime = ReadTime(xr); + else if(xr.Name == ElemCreationTime) + tl.CreationTime = ReadTime(xr); + else if(xr.Name == ElemLastAccessTime) + tl.LastAccessTime = ReadTime(xr); + else if(xr.Name == ElemExpiryTime) + tl.ExpiryTime = ReadTime(xr); + else if(xr.Name == ElemExpires) + tl.Expires = ReadBool(xr, false); + else if(xr.Name == ElemUsageCount) + tl.UsageCount = ReadULong(xr, 0); + else if(xr.Name == ElemLocationChanged) + tl.LocationChanged = ReadTime(xr); + else ReadUnknown(xr); + break; + + case KdbContext.EntryString: + if(xr.Name == ElemKey) + m_ctxStringName = ReadString(xr); + else if(xr.Name == ElemValue) + m_ctxStringValue = ReadProtectedString(xr); + else ReadUnknown(xr); + break; + + case KdbContext.EntryBinary: + if(xr.Name == ElemKey) + m_ctxBinaryName = ReadString(xr); + else if(xr.Name == ElemValue) + m_ctxBinaryValue = ReadProtectedBinary(xr); + else ReadUnknown(xr); + break; + + case KdbContext.EntryAutoType: + if(xr.Name == ElemAutoTypeEnabled) + m_ctxEntry.AutoType.Enabled = ReadBool(xr, true); + else if(xr.Name == ElemAutoTypeObfuscation) + m_ctxEntry.AutoType.ObfuscationOptions = + (AutoTypeObfuscationOptions)ReadInt(xr, 0); + else if(xr.Name == ElemAutoTypeDefaultSeq) + m_ctxEntry.AutoType.DefaultSequence = ReadString(xr); + else if(xr.Name == ElemAutoTypeItem) + return SwitchContext(ctx, KdbContext.EntryAutoTypeItem, xr); + else ReadUnknown(xr); + break; + + case KdbContext.EntryAutoTypeItem: + if(xr.Name == ElemWindow) + m_ctxATName = ReadString(xr); + else if(xr.Name == ElemKeystrokeSequence) + m_ctxATSeq = ReadString(xr); + else ReadUnknown(xr); + break; + + case KdbContext.EntryHistory: + if(xr.Name == ElemEntry) + { + m_ctxEntry = new PwEntry(false, false); + m_ctxHistoryBase.History.Add(m_ctxEntry); + + m_bEntryInHistory = true; + return SwitchContext(ctx, KdbContext.Entry, xr); + } + else ReadUnknown(xr); + break; + + case KdbContext.RootDeletedObjects: + if(xr.Name == ElemDeletedObject) + { + m_ctxDeletedObject = new PwDeletedObject(); + m_pwDatabase.DeletedObjects.Add(m_ctxDeletedObject); + + return SwitchContext(ctx, KdbContext.DeletedObject, xr); + } + else ReadUnknown(xr); + break; + + case KdbContext.DeletedObject: + if(xr.Name == ElemUuid) + m_ctxDeletedObject.Uuid = ReadUuid(xr); + else if(xr.Name == ElemDeletionTime) + m_ctxDeletedObject.DeletionTime = ReadTime(xr); + else ReadUnknown(xr); + break; + + default: + ReadUnknown(xr); + break; + } + + return ctx; + } + + private KdbContext EndXmlElement(KdbContext ctx, XmlReader xr) + { + Debug.Assert(xr.NodeType == XmlNodeType.EndElement); + + if((ctx == KdbContext.KeePassFile) && (xr.Name == ElemDocNode)) + return KdbContext.Null; + else if((ctx == KdbContext.Meta) && (xr.Name == ElemMeta)) + return KdbContext.KeePassFile; + else if((ctx == KdbContext.Root) && (xr.Name == ElemRoot)) + return KdbContext.KeePassFile; + else if((ctx == KdbContext.MemoryProtection) && (xr.Name == ElemMemoryProt)) + return KdbContext.Meta; + else if((ctx == KdbContext.CustomIcons) && (xr.Name == ElemCustomIcons)) + return KdbContext.Meta; + else if((ctx == KdbContext.CustomIcon) && (xr.Name == ElemCustomIconItem)) + { + if((m_uuidCustomIconID != PwUuid.Zero) && (m_pbCustomIconData != null)) + m_pwDatabase.CustomIcons.Add(new PwCustomIcon( + m_uuidCustomIconID, m_pbCustomIconData)); + else { Debug.Assert(false); } + + m_uuidCustomIconID = PwUuid.Zero; + m_pbCustomIconData = null; + + return KdbContext.CustomIcons; + } + else if((ctx == KdbContext.Binaries) && (xr.Name == ElemBinaries)) + return KdbContext.Meta; + else if((ctx == KdbContext.CustomData) && (xr.Name == ElemCustomData)) + return KdbContext.Meta; + else if((ctx == KdbContext.CustomDataItem) && (xr.Name == ElemStringDictExItem)) + { + if((m_strCustomDataKey != null) && (m_strCustomDataValue != null)) + m_pwDatabase.CustomData.Set(m_strCustomDataKey, m_strCustomDataValue); + else { Debug.Assert(false); } + + m_strCustomDataKey = null; + m_strCustomDataValue = null; + + return KdbContext.CustomData; + } + else if((ctx == KdbContext.Group) && (xr.Name == ElemGroup)) + { + if(PwUuid.Zero.EqualsValue(m_ctxGroup.Uuid)) + m_ctxGroup.Uuid = new PwUuid(true); // No assert (import) + + m_ctxGroups.Pop(); + + if(m_ctxGroups.Count == 0) + { + m_ctxGroup = null; + return KdbContext.Root; + } + else + { + m_ctxGroup = m_ctxGroups.Peek(); + return KdbContext.Group; + } + } + else if((ctx == KdbContext.GroupTimes) && (xr.Name == ElemTimes)) + return KdbContext.Group; + else if((ctx == KdbContext.Entry) && (xr.Name == ElemEntry)) + { + // Create new UUID if absent + if(PwUuid.Zero.EqualsValue(m_ctxEntry.Uuid)) + m_ctxEntry.Uuid = new PwUuid(true); // No assert (import) + + if(m_bEntryInHistory) + { + m_ctxEntry = m_ctxHistoryBase; + return KdbContext.EntryHistory; + } + + return KdbContext.Group; + } + else if((ctx == KdbContext.EntryTimes) && (xr.Name == ElemTimes)) + return KdbContext.Entry; + else if((ctx == KdbContext.EntryString) && (xr.Name == ElemString)) + { + m_ctxEntry.Strings.Set(m_ctxStringName, m_ctxStringValue); + m_ctxStringName = null; + m_ctxStringValue = null; + return KdbContext.Entry; + } + else if((ctx == KdbContext.EntryBinary) && (xr.Name == ElemBinary)) + { + if(string.IsNullOrEmpty(m_strDetachBins)) + m_ctxEntry.Binaries.Set(m_ctxBinaryName, m_ctxBinaryValue); + else + { + SaveBinary(m_ctxBinaryName, m_ctxBinaryValue, m_strDetachBins); + + m_ctxBinaryValue = null; + GC.Collect(); + } + + m_ctxBinaryName = null; + m_ctxBinaryValue = null; + return KdbContext.Entry; + } + else if((ctx == KdbContext.EntryAutoType) && (xr.Name == ElemAutoType)) + return KdbContext.Entry; + else if((ctx == KdbContext.EntryAutoTypeItem) && (xr.Name == ElemAutoTypeItem)) + { + AutoTypeAssociation atAssoc = new AutoTypeAssociation(m_ctxATName, + m_ctxATSeq); + m_ctxEntry.AutoType.Add(atAssoc); + m_ctxATName = null; + m_ctxATSeq = null; + return KdbContext.EntryAutoType; + } + else if((ctx == KdbContext.EntryHistory) && (xr.Name == ElemHistory)) + { + m_bEntryInHistory = false; + return KdbContext.Entry; + } + else if((ctx == KdbContext.RootDeletedObjects) && (xr.Name == ElemDeletedObjects)) + return KdbContext.Root; + else if((ctx == KdbContext.DeletedObject) && (xr.Name == ElemDeletedObject)) + { + m_ctxDeletedObject = null; + return KdbContext.RootDeletedObjects; + } + else + { + Debug.Assert(false); + throw new FormatException(); + } + } + + private string ReadString(XmlReader xr) + { + XorredBuffer xb = ProcessNode(xr); + if(xb != null) + { + byte[] pb = xb.ReadPlainText(); + if(pb.Length == 0) return string.Empty; + return StrUtil.Utf8.GetString(pb, 0, pb.Length); + } + + m_bReadNextNode = false; // ReadElementString skips end tag + return xr.ReadElementString(); + } + + private string ReadStringRaw(XmlReader xr) + { + m_bReadNextNode = false; // ReadElementString skips end tag + return xr.ReadElementString(); + } + + private bool ReadBool(XmlReader xr, bool bDefault) + { + string str = ReadString(xr); + if(str == ValTrue) return true; + else if(str == ValFalse) return false; + + Debug.Assert(false); + return bDefault; + } + + private PwUuid ReadUuid(XmlReader xr) + { + string str = ReadString(xr); + if(string.IsNullOrEmpty(str)) return PwUuid.Zero; + return new PwUuid(Convert.FromBase64String(str)); + } + + private int ReadInt(XmlReader xr, int nDefault) + { + string str = ReadString(xr); + + int n; + if(StrUtil.TryParseInt(str, out n)) return n; + + Debug.Assert(false); + return nDefault; + } + + private uint ReadUInt(XmlReader xr, uint uDefault) + { + string str = ReadString(xr); + + uint u; + if(StrUtil.TryParseUInt(str, out u)) return u; + + Debug.Assert(false); + return uDefault; + } + + private long ReadLong(XmlReader xr, long lDefault) + { + string str = ReadString(xr); + + long l; + if(StrUtil.TryParseLong(str, out l)) return l; + + Debug.Assert(false); + return lDefault; + } + + private ulong ReadULong(XmlReader xr, ulong uDefault) + { + string str = ReadString(xr); + + ulong u; + if(StrUtil.TryParseULong(str, out u)) return u; + + Debug.Assert(false); + return uDefault; + } + + private DateTime ReadTime(XmlReader xr) + { + string str = ReadString(xr); + + DateTime dt; + if(TimeUtil.TryDeserializeUtc(str, out dt)) return dt; + + Debug.Assert(false); + return m_dtNow; + } + + private ProtectedString ReadProtectedString(XmlReader xr) + { + XorredBuffer xb = ProcessNode(xr); + if(xb != null) return new ProtectedString(true, xb); + + bool bProtect = false; + if(m_format == KdbxFormat.PlainXml) + { + if(xr.MoveToAttribute(AttrProtectedInMemPlainXml)) + { + string strProtect = xr.Value; + bProtect = ((strProtect != null) && (strProtect == ValTrue)); + } + } + + ProtectedString ps = new ProtectedString(bProtect, ReadString(xr)); + return ps; + } + + private ProtectedBinary ReadProtectedBinary(XmlReader xr) + { + if(xr.MoveToAttribute(AttrRef)) + { + string strRef = xr.Value; + if(strRef != null) + { + ProtectedBinary pb = BinPoolGet(strRef); + if(pb != null) return pb; + else { Debug.Assert(false); } + } + else { Debug.Assert(false); } + } + + bool bCompressed = false; + if(xr.MoveToAttribute(AttrCompressed)) + bCompressed = (xr.Value == ValTrue); + + XorredBuffer xb = ProcessNode(xr); + if(xb != null) + { + Debug.Assert(!bCompressed); // See SubWriteValue(ProtectedBinary value) + return new ProtectedBinary(true, xb); + } + + string strValue = ReadString(xr); + if(strValue.Length == 0) return new ProtectedBinary(); + + byte[] pbData = Convert.FromBase64String(strValue); + if(bCompressed) pbData = MemUtil.Decompress(pbData); + return new ProtectedBinary(false, pbData); + } + + private void ReadUnknown(XmlReader xr) + { + Debug.Assert(false); // Unknown node! + + if(xr.IsEmptyElement) return; + + string strUnknownName = xr.Name; + ProcessNode(xr); + + while(xr.Read()) + { + if(xr.NodeType == XmlNodeType.EndElement) break; + if(xr.NodeType != XmlNodeType.Element) continue; + + ReadUnknown(xr); + } + + Debug.Assert(xr.Name == strUnknownName); + } + + private XorredBuffer ProcessNode(XmlReader xr) + { + // Debug.Assert(xr.NodeType == XmlNodeType.Element); + + XorredBuffer xb = null; + if(xr.HasAttributes) + { + if(xr.MoveToAttribute(AttrProtected)) + { + if(xr.Value == ValTrue) + { + xr.MoveToElement(); + string strEncrypted = ReadStringRaw(xr); + + byte[] pbEncrypted; + if(strEncrypted.Length > 0) + pbEncrypted = Convert.FromBase64String(strEncrypted); + else pbEncrypted = new byte[0]; + + byte[] pbPad = m_randomStream.GetRandomBytes((uint)pbEncrypted.Length); + + xb = new XorredBuffer(pbEncrypted, pbPad); + } + } + } + + return xb; + } + + private static KdbContext SwitchContext(KdbContext ctxCurrent, + KdbContext ctxNew, XmlReader xr) + { + if(xr.IsEmptyElement) return ctxCurrent; + return ctxNew; + } + } +} diff --git a/src/KeePassLib2Android/Serialization/KdbxFile.Read.cs b/src/KeePassLib2Android/Serialization/KdbxFile.Read.cs new file mode 100644 index 00000000..03456962 --- /dev/null +++ b/src/KeePassLib2Android/Serialization/KdbxFile.Read.cs @@ -0,0 +1,391 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Diagnostics; +using System.Security; +using System.Security.Cryptography; +using System.Xml; + +#if !KeePassLibSD +using System.IO.Compression; +#else +using KeePassLibSD; +#endif + +using KeePassLib.Cryptography; +using KeePassLib.Cryptography.Cipher; +using KeePassLib.Interfaces; +using KeePassLib.Keys; +using KeePassLib.Resources; +using KeePassLib.Utility; + +namespace KeePassLib.Serialization +{ + /// + /// Serialization to KeePass KDBX files. + /// + public sealed partial class KdbxFile + { + /// + /// Load a KDB file from a file. + /// + /// File to load. + /// Format specifier. + /// Status logger (optional). + public void Load(string strFilePath, KdbxFormat kdbFormat, IStatusLogger slLogger) + { + IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFilePath); + Load(IOConnection.OpenRead(ioc), kdbFormat, slLogger); + } + + /// + /// Load a KDB file from a stream. + /// + /// Stream to read the data from. Must contain + /// a KDBX stream. + /// Format specifier. + /// Status logger (optional). + public void Load(Stream sSource, KdbxFormat kdbFormat, IStatusLogger slLogger) + { + Debug.Assert(sSource != null); + if(sSource == null) throw new ArgumentNullException("sSource"); + + m_format = kdbFormat; + m_slLogger = slLogger; + + HashingStreamEx hashedStream = new HashingStreamEx(sSource, false, null); + + UTF8Encoding encNoBom = StrUtil.Utf8; + try + { + BinaryReaderEx br = null; + BinaryReaderEx brDecrypted = null; + Stream readerStream = null; + + if(kdbFormat == KdbxFormat.Default) + { + br = new BinaryReaderEx(hashedStream, encNoBom, KLRes.FileCorrupted); + ReadHeader(br); + + Stream sDecrypted = AttachStreamDecryptor(hashedStream); + if((sDecrypted == null) || (sDecrypted == hashedStream)) + throw new SecurityException(KLRes.CryptoStreamFailed); + + brDecrypted = new BinaryReaderEx(sDecrypted, encNoBom, KLRes.FileCorrupted); + byte[] pbStoredStartBytes = brDecrypted.ReadBytes(32); + + if((m_pbStreamStartBytes == null) || (m_pbStreamStartBytes.Length != 32)) + throw new InvalidDataException(); + + for(int iStart = 0; iStart < 32; ++iStart) + { + if(pbStoredStartBytes[iStart] != m_pbStreamStartBytes[iStart]) + throw new InvalidCompositeKeyException(); + } + + Stream sHashed = new HashedBlockStream(sDecrypted, false, 0, + !m_bRepairMode); + + if(m_pwDatabase.Compression == PwCompressionAlgorithm.GZip) + readerStream = new GZipStream(sHashed, CompressionMode.Decompress); + else readerStream = sHashed; + } + else if(kdbFormat == KdbxFormat.PlainXml) + readerStream = hashedStream; + else { Debug.Assert(false); throw new FormatException("KdbFormat"); } + + if(kdbFormat != KdbxFormat.PlainXml) // Is an encrypted format + { + if(m_pbProtectedStreamKey == null) + { + Debug.Assert(false); + throw new SecurityException("Invalid protected stream key!"); + } + + m_randomStream = new CryptoRandomStream(m_craInnerRandomStream, + m_pbProtectedStreamKey); + } + else m_randomStream = null; // No random stream for plain-text files + + ReadXmlStreamed(readerStream, hashedStream); + // ReadXmlDom(readerStream); + + readerStream.Close(); + // GC.KeepAlive(br); + // GC.KeepAlive(brDecrypted); + } + catch(CryptographicException) // Thrown on invalid padding + { + throw new CryptographicException(KLRes.FileCorrupted); + } + finally { CommonCleanUpRead(sSource, hashedStream); } + } + + private void CommonCleanUpRead(Stream sSource, HashingStreamEx hashedStream) + { + hashedStream.Close(); + m_pbHashOfFileOnDisk = hashedStream.Hash; + + sSource.Close(); + + // Reset memory protection settings (to always use reasonable + // defaults) + m_pwDatabase.MemoryProtection = new MemoryProtectionConfig(); + + // Remove old backups (this call is required here in order to apply + // the default history maintenance settings for people upgrading from + // KeePass <= 2.14 to >= 2.15; also it ensures history integrity in + // case a different application has created the KDBX file and ignored + // the history maintenance settings) + m_pwDatabase.MaintainBackups(); // Don't mark database as modified + + m_pbHashOfHeader = null; + } + + private void ReadHeader(BinaryReaderEx br) + { + MemoryStream msHeader = new MemoryStream(); + Debug.Assert(br.CopyDataTo == null); + br.CopyDataTo = msHeader; + + byte[] pbSig1 = br.ReadBytes(4); + uint uSig1 = MemUtil.BytesToUInt32(pbSig1); + byte[] pbSig2 = br.ReadBytes(4); + uint uSig2 = MemUtil.BytesToUInt32(pbSig2); + + if((uSig1 == FileSignatureOld1) && (uSig2 == FileSignatureOld2)) + throw new OldFormatException(PwDefs.ShortProductName + @" 1.x", + OldFormatException.OldFormatType.KeePass1x); + + if((uSig1 == FileSignature1) && (uSig2 == FileSignature2)) { } + else if((uSig1 == FileSignaturePreRelease1) && (uSig2 == + FileSignaturePreRelease2)) { } + else throw new FormatException(KLRes.FileSigInvalid); + + byte[] pb = br.ReadBytes(4); + uint uVersion = MemUtil.BytesToUInt32(pb); + if((uVersion & FileVersionCriticalMask) > (FileVersion32 & FileVersionCriticalMask)) + throw new FormatException(KLRes.FileVersionUnsupported + + MessageService.NewParagraph + KLRes.FileNewVerReq); + + while(true) + { + if(ReadHeaderField(br) == false) + break; + } + + br.CopyDataTo = null; + byte[] pbHeader = msHeader.ToArray(); + msHeader.Close(); + SHA256Managed sha256 = new SHA256Managed(); + m_pbHashOfHeader = sha256.ComputeHash(pbHeader); + } + + private bool ReadHeaderField(BinaryReaderEx brSource) + { + Debug.Assert(brSource != null); + if(brSource == null) throw new ArgumentNullException("brSource"); + + byte btFieldID = brSource.ReadByte(); + ushort uSize = MemUtil.BytesToUInt16(brSource.ReadBytes(2)); + + byte[] pbData = null; + if(uSize > 0) + { + string strPrevExcpText = brSource.ReadExceptionText; + brSource.ReadExceptionText = KLRes.FileHeaderEndEarly; + + pbData = brSource.ReadBytes(uSize); + + brSource.ReadExceptionText = strPrevExcpText; + } + + bool bResult = true; + KdbxHeaderFieldID kdbID = (KdbxHeaderFieldID)btFieldID; + switch(kdbID) + { + case KdbxHeaderFieldID.EndOfHeader: + bResult = false; // Returning false indicates end of header + break; + + case KdbxHeaderFieldID.CipherID: + SetCipher(pbData); + break; + + case KdbxHeaderFieldID.CompressionFlags: + SetCompressionFlags(pbData); + break; + + case KdbxHeaderFieldID.MasterSeed: + m_pbMasterSeed = pbData; + CryptoRandom.Instance.AddEntropy(pbData); + break; + + case KdbxHeaderFieldID.TransformSeed: + m_pbTransformSeed = pbData; + CryptoRandom.Instance.AddEntropy(pbData); + break; + + case KdbxHeaderFieldID.TransformRounds: + m_pwDatabase.KeyEncryptionRounds = MemUtil.BytesToUInt64(pbData); + break; + + case KdbxHeaderFieldID.EncryptionIV: + m_pbEncryptionIV = pbData; + break; + + case KdbxHeaderFieldID.ProtectedStreamKey: + m_pbProtectedStreamKey = pbData; + CryptoRandom.Instance.AddEntropy(pbData); + break; + + case KdbxHeaderFieldID.StreamStartBytes: + m_pbStreamStartBytes = pbData; + break; + + case KdbxHeaderFieldID.InnerRandomStreamID: + SetInnerRandomStreamID(pbData); + break; + + default: + Debug.Assert(false); + if(m_slLogger != null) + m_slLogger.SetText(KLRes.UnknownHeaderId + @": " + + kdbID.ToString() + "!", LogStatusType.Warning); + break; + } + + return bResult; + } + + private void SetCipher(byte[] pbID) + { + if((pbID == null) || (pbID.Length != 16)) + throw new FormatException(KLRes.FileUnknownCipher); + + m_pwDatabase.DataCipherUuid = new PwUuid(pbID); + } + + private void SetCompressionFlags(byte[] pbFlags) + { + int nID = (int)MemUtil.BytesToUInt32(pbFlags); + if((nID < 0) || (nID >= (int)PwCompressionAlgorithm.Count)) + throw new FormatException(KLRes.FileUnknownCompression); + + m_pwDatabase.Compression = (PwCompressionAlgorithm)nID; + } + + private void SetInnerRandomStreamID(byte[] pbID) + { + uint uID = MemUtil.BytesToUInt32(pbID); + if(uID >= (uint)CrsAlgorithm.Count) + throw new FormatException(KLRes.FileUnknownCipher); + + 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) + { + return ReadEntries(msData); + } + + /// + /// Read entries from a stream. + /// + /// Input stream to read the entries from. + /// Extracted entries. + public static List ReadEntries(Stream msData) + { + /* KdbxFile f = new KdbxFile(pwDatabase); + f.m_format = KdbxFormat.PlainXml; + + XmlDocument doc = new XmlDocument(); + doc.Load(msData); + + XmlElement el = doc.DocumentElement; + if(el.Name != ElemRoot) throw new FormatException(); + + List vEntries = new List(); + + foreach(XmlNode xmlChild in el.ChildNodes) + { + if(xmlChild.Name == ElemEntry) + { + PwEntry pe = f.ReadEntry(xmlChild); + pe.Uuid = new PwUuid(true); + + foreach(PwEntry peHistory in pe.History) + peHistory.Uuid = pe.Uuid; + + vEntries.Add(pe); + } + else { Debug.Assert(false); } + } + + return vEntries; */ + + PwDatabase pd = new PwDatabase(); + KdbxFile f = new KdbxFile(pd); + f.Load(msData, KdbxFormat.PlainXml, null); + + List vEntries = new List(); + foreach(PwEntry pe in pd.RootGroup.Entries) + { + pe.SetUuid(new PwUuid(true), true); + vEntries.Add(pe); + } + + return vEntries; + } + } +} diff --git a/src/KeePassLib2Android/Serialization/KdbxFile.Write.cs b/src/KeePassLib2Android/Serialization/KdbxFile.Write.cs new file mode 100644 index 00000000..ada3a9ed --- /dev/null +++ b/src/KeePassLib2Android/Serialization/KdbxFile.Write.cs @@ -0,0 +1,851 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using 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; + +#if !KeePassLibSD +using System.IO.Compression; +#else +using KeePassLibSD; +#endif + +using KeePassLib.Collections; +using KeePassLib.Cryptography; +using KeePassLib.Cryptography.Cipher; +using KeePassLib.Delegates; +using KeePassLib.Interfaces; +using KeePassLib.Keys; +using KeePassLib.Resources; +using KeePassLib.Security; +using KeePassLib.Utility; + +namespace KeePassLib.Serialization +{ + /// + /// Serialization to KeePass KDBX files. + /// + public sealed partial class KdbxFile + { + // public void Save(string strFile, PwGroup pgDataSource, KdbxFormat format, + // IStatusLogger slLogger) + // { + // bool bMadeUnhidden = UrlUtil.UnhideFile(strFile); + // + // IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFile); + // this.Save(IOConnection.OpenWrite(ioc), pgDataSource, format, slLogger); + // + // if(bMadeUnhidden) UrlUtil.HideFile(strFile, true); // Hide again + // } + + /// + /// Save the contents of the current PwDatabase to a KDBX file. + /// + /// Stream to write the KDBX file into. + /// Group containing all groups and + /// entries to write. If null, the complete database will + /// be written. + /// Format of the file to create. + /// Logger that recieves status information. + public void Save(Stream sSaveTo, PwGroup pgDataSource, KdbxFormat format, + IStatusLogger slLogger) + { + Debug.Assert(sSaveTo != null); + if(sSaveTo == null) throw new ArgumentNullException("sSaveTo"); + + m_format = format; + m_slLogger = slLogger; + + HashingStreamEx hashedStream = new HashingStreamEx(sSaveTo, true, null); + + UTF8Encoding encNoBom = StrUtil.Utf8; + CryptoRandom cr = CryptoRandom.Instance; + + try + { + m_pbMasterSeed = cr.GetRandomBytes(32); + m_pbTransformSeed = cr.GetRandomBytes(32); + m_pbEncryptionIV = cr.GetRandomBytes(16); + + m_pbProtectedStreamKey = cr.GetRandomBytes(32); + m_craInnerRandomStream = CrsAlgorithm.Salsa20; + m_randomStream = new CryptoRandomStream(m_craInnerRandomStream, + m_pbProtectedStreamKey); + + m_pbStreamStartBytes = cr.GetRandomBytes(32); + + Stream writerStream; + if(m_format == KdbxFormat.Default) + { + WriteHeader(hashedStream); // Also flushes the stream + + Stream sEncrypted = AttachStreamEncryptor(hashedStream); + if((sEncrypted == null) || (sEncrypted == hashedStream)) + throw new SecurityException(KLRes.CryptoStreamFailed); + + sEncrypted.Write(m_pbStreamStartBytes, 0, m_pbStreamStartBytes.Length); + + Stream sHashed = new HashedBlockStream(sEncrypted, true); + + if(m_pwDatabase.Compression == PwCompressionAlgorithm.GZip) + writerStream = new GZipStream(sHashed, CompressionMode.Compress); + else + writerStream = sHashed; + } + else if(m_format == KdbxFormat.PlainXml) + writerStream = hashedStream; + else { Debug.Assert(false); throw new FormatException("KdbFormat"); } + + m_xmlWriter = new XmlTextWriter(writerStream, encNoBom); + WriteDocument(pgDataSource); + + m_xmlWriter.Flush(); + m_xmlWriter.Close(); + writerStream.Close(); + } + finally { CommonCleanUpWrite(sSaveTo, hashedStream); } + } + + private void CommonCleanUpWrite(Stream sSaveTo, HashingStreamEx hashedStream) + { + hashedStream.Close(); + m_pbHashOfFileOnDisk = hashedStream.Hash; + + sSaveTo.Close(); + + m_xmlWriter = null; + m_pbHashOfHeader = null; + } + + private void WriteHeader(Stream s) + { + MemoryStream ms = new MemoryStream(); + + 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); + + int nCprID = (int)m_pwDatabase.Compression; + WriteHeaderField(ms, KdbxHeaderFieldID.CompressionFlags, + MemUtil.UInt32ToBytes((uint)nCprID)); + + 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); + + int nIrsID = (int)m_craInnerRandomStream; + WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamID, + MemUtil.UInt32ToBytes((uint)nIrsID)); + + WriteHeaderField(ms, KdbxHeaderFieldID.EndOfHeader, new byte[]{ + (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' }); + + byte[] pbHeader = ms.ToArray(); + ms.Close(); + + SHA256Managed sha256 = new SHA256Managed(); + m_pbHashOfHeader = sha256.ComputeHash(pbHeader); + + s.Write(pbHeader, 0, pbHeader.Length); + s.Flush(); + } + + private static void WriteHeaderField(Stream s, KdbxHeaderFieldID kdbID, + byte[] pbData) + { + s.WriteByte((byte)kdbID); + + if(pbData != null) + { + ushort uLength = (ushort)pbData.Length; + MemUtil.Write(s, MemUtil.UInt16ToBytes(uLength)); + + if(uLength > 0) s.Write(pbData, 0, pbData.Length); + } + else MemUtil.Write(s, MemUtil.UInt16ToBytes((ushort)0)); + } + + 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); + } + + private void WriteDocument(PwGroup pgDataSource) + { + Debug.Assert(m_xmlWriter != null); + if(m_xmlWriter == null) throw new InvalidOperationException(); + + PwGroup pgRoot = (pgDataSource ?? m_pwDatabase.RootGroup); + + uint uNumGroups, uNumEntries, uCurEntry = 0; + pgRoot.GetCounts(true, out uNumGroups, out uNumEntries); + + BinPoolBuild(pgRoot); + + m_xmlWriter.Formatting = Formatting.Indented; + m_xmlWriter.IndentChar = '\t'; + m_xmlWriter.Indentation = 1; + + m_xmlWriter.WriteStartDocument(true); + m_xmlWriter.WriteStartElement(ElemDocNode); + + WriteMeta(); + + m_xmlWriter.WriteStartElement(ElemRoot); + StartGroup(pgRoot); + + Stack groupStack = new Stack(); + groupStack.Push(pgRoot); + + GroupHandler gh = delegate(PwGroup pg) + { + Debug.Assert(pg != null); + if(pg == null) throw new ArgumentNullException("pg"); + + while(true) + { + if(pg.ParentGroup == groupStack.Peek()) + { + groupStack.Push(pg); + StartGroup(pg); + break; + } + else + { + groupStack.Pop(); + if(groupStack.Count <= 0) return false; + + EndGroup(); + } + } + + return true; + }; + + EntryHandler eh = delegate(PwEntry pe) + { + Debug.Assert(pe != null); + WriteEntry(pe, false); + + ++uCurEntry; + if(m_slLogger != null) + if(!m_slLogger.SetProgress((100 * uCurEntry) / uNumEntries)) + return false; + + return true; + }; + + if(!pgRoot.TraverseTree(TraversalMethod.PreOrder, gh, eh)) + throw new InvalidOperationException(); + + while(groupStack.Count > 1) + { + m_xmlWriter.WriteEndElement(); + groupStack.Pop(); + } + + EndGroup(); + + WriteList(ElemDeletedObjects, m_pwDatabase.DeletedObjects); + m_xmlWriter.WriteEndElement(); // Root + + m_xmlWriter.WriteEndElement(); // ElemDocNode + m_xmlWriter.WriteEndDocument(); + } + + private void WriteMeta() + { + m_xmlWriter.WriteStartElement(ElemMeta); + + WriteObject(ElemGenerator, PwDatabase.LocalizedAppName, false); // Generator name + + if(m_pbHashOfHeader != null) + WriteObject(ElemHeaderHash, Convert.ToBase64String( + m_pbHashOfHeader), false); + + WriteObject(ElemDbName, m_pwDatabase.Name, true); + WriteObject(ElemDbNameChanged, m_pwDatabase.NameChanged); + WriteObject(ElemDbDesc, m_pwDatabase.Description, true); + WriteObject(ElemDbDescChanged, m_pwDatabase.DescriptionChanged); + WriteObject(ElemDbDefaultUser, m_pwDatabase.DefaultUserName, true); + WriteObject(ElemDbDefaultUserChanged, m_pwDatabase.DefaultUserNameChanged); + WriteObject(ElemDbMntncHistoryDays, m_pwDatabase.MaintenanceHistoryDays); + WriteObject(ElemDbColor, StrUtil.ColorToUnnamedHtml(m_pwDatabase.Color, true), false); + WriteObject(ElemDbKeyChanged, m_pwDatabase.MasterKeyChanged); + WriteObject(ElemDbKeyChangeRec, m_pwDatabase.MasterKeyChangeRec); + WriteObject(ElemDbKeyChangeForce, m_pwDatabase.MasterKeyChangeForce); + + WriteList(ElemMemoryProt, m_pwDatabase.MemoryProtection); + + WriteCustomIconList(); + + WriteObject(ElemRecycleBinEnabled, m_pwDatabase.RecycleBinEnabled); + WriteObject(ElemRecycleBinUuid, m_pwDatabase.RecycleBinUuid); + WriteObject(ElemRecycleBinChanged, m_pwDatabase.RecycleBinChanged); + WriteObject(ElemEntryTemplatesGroup, m_pwDatabase.EntryTemplatesGroup); + WriteObject(ElemEntryTemplatesGroupChanged, m_pwDatabase.EntryTemplatesGroupChanged); + WriteObject(ElemHistoryMaxItems, m_pwDatabase.HistoryMaxItems); + WriteObject(ElemHistoryMaxSize, m_pwDatabase.HistoryMaxSize); + + WriteObject(ElemLastSelectedGroup, m_pwDatabase.LastSelectedGroup); + WriteObject(ElemLastTopVisibleGroup, m_pwDatabase.LastTopVisibleGroup); + + WriteBinPool(); + WriteList(ElemCustomData, m_pwDatabase.CustomData); + + m_xmlWriter.WriteEndElement(); + } + + private void StartGroup(PwGroup pg) + { + m_xmlWriter.WriteStartElement(ElemGroup); + WriteObject(ElemUuid, pg.Uuid); + WriteObject(ElemName, pg.Name, true); + WriteObject(ElemNotes, pg.Notes, true); + WriteObject(ElemIcon, (int)pg.IconId); + + if(pg.CustomIconUuid != PwUuid.Zero) + WriteObject(ElemCustomIconID, pg.CustomIconUuid); + + WriteList(ElemTimes, pg); + WriteObject(ElemIsExpanded, pg.IsExpanded); + WriteObject(ElemGroupDefaultAutoTypeSeq, pg.DefaultAutoTypeSequence, true); + WriteObject(ElemEnableAutoType, StrUtil.BoolToStringEx(pg.EnableAutoType), false); + WriteObject(ElemEnableSearching, StrUtil.BoolToStringEx(pg.EnableSearching), false); + WriteObject(ElemLastTopVisibleEntry, pg.LastTopVisibleEntry); + } + + private void EndGroup() + { + m_xmlWriter.WriteEndElement(); // Close group element + } + + private void WriteEntry(PwEntry pe, bool bIsHistory) + { + Debug.Assert(pe != null); if(pe == null) throw new ArgumentNullException("pe"); + + m_xmlWriter.WriteStartElement(ElemEntry); + + WriteObject(ElemUuid, pe.Uuid); + WriteObject(ElemIcon, (int)pe.IconId); + + if(pe.CustomIconUuid != PwUuid.Zero) + WriteObject(ElemCustomIconID, pe.CustomIconUuid); + + WriteObject(ElemFgColor, StrUtil.ColorToUnnamedHtml(pe.ForegroundColor, true), false); + WriteObject(ElemBgColor, StrUtil.ColorToUnnamedHtml(pe.BackgroundColor, true), false); + WriteObject(ElemOverrideUrl, pe.OverrideUrl, true); + WriteObject(ElemTags, StrUtil.TagsToString(pe.Tags, false), true); + + WriteList(ElemTimes, pe); + + WriteList(pe.Strings, true); + WriteList(pe.Binaries); + WriteList(ElemAutoType, pe.AutoType); + + if(!bIsHistory) WriteList(ElemHistory, pe.History, true); + else { Debug.Assert(pe.History.UCount == 0); } + + m_xmlWriter.WriteEndElement(); + } + + private void WriteList(ProtectedStringDictionary dictStrings, bool bEntryStrings) + { + Debug.Assert(dictStrings != null); + if(dictStrings == null) throw new ArgumentNullException("dictStrings"); + + foreach(KeyValuePair kvp in dictStrings) + WriteObject(kvp.Key, kvp.Value, bEntryStrings); + } + + private void WriteList(ProtectedBinaryDictionary dictBinaries) + { + Debug.Assert(dictBinaries != null); + if(dictBinaries == null) throw new ArgumentNullException("dictBinaries"); + + foreach(KeyValuePair kvp in dictBinaries) + WriteObject(kvp.Key, kvp.Value, true); + } + + private void WriteList(string name, AutoTypeConfig cfgAutoType) + { + Debug.Assert(name != null); + Debug.Assert(cfgAutoType != null); + if(cfgAutoType == null) throw new ArgumentNullException("cfgAutoType"); + + m_xmlWriter.WriteStartElement(name); + + WriteObject(ElemAutoTypeEnabled, cfgAutoType.Enabled); + WriteObject(ElemAutoTypeObfuscation, (int)cfgAutoType.ObfuscationOptions); + + if(cfgAutoType.DefaultSequence.Length > 0) + WriteObject(ElemAutoTypeDefaultSeq, cfgAutoType.DefaultSequence, true); + + foreach(AutoTypeAssociation a in cfgAutoType.Associations) + WriteObject(ElemAutoTypeItem, ElemWindow, ElemKeystrokeSequence, + new KeyValuePair(a.WindowName, a.Sequence)); + + m_xmlWriter.WriteEndElement(); + } + + private void WriteList(string name, ITimeLogger times) + { + Debug.Assert(name != null); + Debug.Assert(times != null); if(times == null) throw new ArgumentNullException("times"); + + m_xmlWriter.WriteStartElement(name); + + WriteObject(ElemLastModTime, times.LastModificationTime); + WriteObject(ElemCreationTime, times.CreationTime); + WriteObject(ElemLastAccessTime, times.LastAccessTime); + WriteObject(ElemExpiryTime, times.ExpiryTime); + WriteObject(ElemExpires, times.Expires); + WriteObject(ElemUsageCount, times.UsageCount); + WriteObject(ElemLocationChanged, times.LocationChanged); + + m_xmlWriter.WriteEndElement(); // Name + } + + private void WriteList(string name, PwObjectList value, bool bIsHistory) + { + Debug.Assert(name != null); + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + + m_xmlWriter.WriteStartElement(name); + + foreach(PwEntry pe in value) + WriteEntry(pe, bIsHistory); + + m_xmlWriter.WriteEndElement(); + } + + private void WriteList(string name, PwObjectList value) + { + Debug.Assert(name != null); + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + + m_xmlWriter.WriteStartElement(name); + + foreach(PwDeletedObject pdo in value) + WriteObject(ElemDeletedObject, pdo); + + m_xmlWriter.WriteEndElement(); + } + + private void WriteList(string name, MemoryProtectionConfig value) + { + Debug.Assert(name != null); + Debug.Assert(value != null); + + m_xmlWriter.WriteStartElement(name); + + WriteObject(ElemProtTitle, value.ProtectTitle); + WriteObject(ElemProtUserName, value.ProtectUserName); + WriteObject(ElemProtPassword, value.ProtectPassword); + WriteObject(ElemProtUrl, value.ProtectUrl); + WriteObject(ElemProtNotes, value.ProtectNotes); + // WriteObject(ElemProtAutoHide, value.AutoEnableVisualHiding); + + m_xmlWriter.WriteEndElement(); + } + + private void WriteList(string name, StringDictionaryEx value) + { + Debug.Assert(name != null); + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + + m_xmlWriter.WriteStartElement(name); + + foreach(KeyValuePair kvp in value) + WriteObject(ElemStringDictExItem, ElemKey, ElemValue, kvp); + + m_xmlWriter.WriteEndElement(); + } + + private void WriteCustomIconList() + { + if(m_pwDatabase.CustomIcons.Count == 0) return; + + m_xmlWriter.WriteStartElement(ElemCustomIcons); + + foreach(PwCustomIcon pwci in m_pwDatabase.CustomIcons) + { + m_xmlWriter.WriteStartElement(ElemCustomIconItem); + + WriteObject(ElemCustomIconItemID, pwci.Uuid); + + string strData = Convert.ToBase64String(pwci.ImageDataPng); + WriteObject(ElemCustomIconItemData, strData, false); + + m_xmlWriter.WriteEndElement(); + } + + m_xmlWriter.WriteEndElement(); + } + + private void WriteObject(string name, string value, + bool bFilterValueXmlChars) + { + Debug.Assert(name != null); + Debug.Assert(value != null); + + m_xmlWriter.WriteStartElement(name); + + if(bFilterValueXmlChars) + m_xmlWriter.WriteString(StrUtil.SafeXmlString(value)); + else m_xmlWriter.WriteString(value); + + m_xmlWriter.WriteEndElement(); + } + + private void WriteObject(string name, bool value) + { + Debug.Assert(name != null); + + WriteObject(name, value ? ValTrue : ValFalse, false); + } + + private void WriteObject(string name, PwUuid value) + { + Debug.Assert(name != null); + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + + WriteObject(name, Convert.ToBase64String(value.UuidBytes), false); + } + + private void WriteObject(string name, int value) + { + Debug.Assert(name != null); + + m_xmlWriter.WriteStartElement(name); + m_xmlWriter.WriteString(value.ToString()); + m_xmlWriter.WriteEndElement(); + } + + private void WriteObject(string name, uint value) + { + Debug.Assert(name != null); + + m_xmlWriter.WriteStartElement(name); + m_xmlWriter.WriteString(value.ToString()); + m_xmlWriter.WriteEndElement(); + } + + private void WriteObject(string name, long value) + { + Debug.Assert(name != null); + + m_xmlWriter.WriteStartElement(name); + m_xmlWriter.WriteString(value.ToString()); + m_xmlWriter.WriteEndElement(); + } + + private void WriteObject(string name, ulong value) + { + Debug.Assert(name != null); + + m_xmlWriter.WriteStartElement(name); + m_xmlWriter.WriteString(value.ToString()); + m_xmlWriter.WriteEndElement(); + } + + private void WriteObject(string name, DateTime value) + { + Debug.Assert(name != null); + + WriteObject(name, TimeUtil.SerializeUtc(value), false); + } + + private void WriteObject(string name, string strKeyName, + string strValueName, KeyValuePair kvp) + { + m_xmlWriter.WriteStartElement(name); + + m_xmlWriter.WriteStartElement(strKeyName); + m_xmlWriter.WriteString(StrUtil.SafeXmlString(kvp.Key)); + m_xmlWriter.WriteEndElement(); + m_xmlWriter.WriteStartElement(strValueName); + m_xmlWriter.WriteString(StrUtil.SafeXmlString(kvp.Value)); + m_xmlWriter.WriteEndElement(); + + m_xmlWriter.WriteEndElement(); + } + + private void WriteObject(string name, ProtectedString value, bool bIsEntryString) + { + Debug.Assert(name != null); + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + + m_xmlWriter.WriteStartElement(ElemString); + m_xmlWriter.WriteStartElement(ElemKey); + m_xmlWriter.WriteString(StrUtil.SafeXmlString(name)); + m_xmlWriter.WriteEndElement(); + m_xmlWriter.WriteStartElement(ElemValue); + + bool bProtected = value.IsProtected; + if(bIsEntryString) + { + // Adjust memory protection setting (which might be different + // from the database default, e.g. due to an import which + // didn't specify the correct setting) + if(name == PwDefs.TitleField) + bProtected = m_pwDatabase.MemoryProtection.ProtectTitle; + else if(name == PwDefs.UserNameField) + bProtected = m_pwDatabase.MemoryProtection.ProtectUserName; + else if(name == PwDefs.PasswordField) + bProtected = m_pwDatabase.MemoryProtection.ProtectPassword; + else if(name == PwDefs.UrlField) + bProtected = m_pwDatabase.MemoryProtection.ProtectUrl; + else if(name == PwDefs.NotesField) + bProtected = m_pwDatabase.MemoryProtection.ProtectNotes; + } + + if(bProtected && (m_format != KdbxFormat.PlainXml)) + { + m_xmlWriter.WriteAttributeString(AttrProtected, ValTrue); + + byte[] pbEncoded = value.ReadXorredString(m_randomStream); + if(pbEncoded.Length > 0) + m_xmlWriter.WriteBase64(pbEncoded, 0, pbEncoded.Length); + } + else + { + string strValue = value.ReadString(); + + // If names should be localized, we need to apply the language-dependent + // string transformation here. By default, language-dependent conversions + // should be applied, otherwise characters could be rendered incorrectly + // (code page problems). + if(m_bLocalizedNames) + { + StringBuilder sb = new StringBuilder(); + foreach(char ch in strValue) + { + char chMapped = ch; + + // Symbols and surrogates must be moved into the correct code + // page area + if(char.IsSymbol(ch) || char.IsSurrogate(ch)) + { + System.Globalization.UnicodeCategory cat = char.GetUnicodeCategory(ch); + // Map character to correct position in code page + chMapped = (char)((int)cat * 32 + ch); + } + else if(char.IsControl(ch)) + { + if(ch >= 256) // Control character in high ANSI code page + { + // Some of the control characters map to corresponding ones + // 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); + } + } + + sb.Append(chMapped); + } + + strValue = sb.ToString(); // Correct string for current code page + } + + if((m_format == KdbxFormat.PlainXml) && bProtected) + m_xmlWriter.WriteAttributeString(AttrProtectedInMemPlainXml, ValTrue); + + m_xmlWriter.WriteString(StrUtil.SafeXmlString(strValue)); + } + + m_xmlWriter.WriteEndElement(); // ElemValue + m_xmlWriter.WriteEndElement(); // ElemString + } + + private void WriteObject(string name, ProtectedBinary value, bool bAllowRef) + { + Debug.Assert(name != null); + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + + m_xmlWriter.WriteStartElement(ElemBinary); + m_xmlWriter.WriteStartElement(ElemKey); + m_xmlWriter.WriteString(StrUtil.SafeXmlString(name)); + m_xmlWriter.WriteEndElement(); + m_xmlWriter.WriteStartElement(ElemValue); + + string strRef = (bAllowRef ? BinPoolFind(value) : null); + if(strRef != null) + { + m_xmlWriter.WriteAttributeString(AttrRef, strRef); + } + else SubWriteValue(value); + + m_xmlWriter.WriteEndElement(); // ElemValue + m_xmlWriter.WriteEndElement(); // ElemBinary + } + + private void SubWriteValue(ProtectedBinary value) + { + if(value.IsProtected && (m_format != KdbxFormat.PlainXml)) + { + m_xmlWriter.WriteAttributeString(AttrProtected, ValTrue); + + byte[] pbEncoded = value.ReadXorredData(m_randomStream); + if(pbEncoded.Length > 0) + m_xmlWriter.WriteBase64(pbEncoded, 0, pbEncoded.Length); + } + else + { + if(m_pwDatabase.Compression == PwCompressionAlgorithm.GZip) + { + m_xmlWriter.WriteAttributeString(AttrCompressed, ValTrue); + + byte[] pbRaw = value.ReadData(); + byte[] pbCmp = MemUtil.Compress(pbRaw); + m_xmlWriter.WriteBase64(pbCmp, 0, pbCmp.Length); + } + else + { + byte[] pbRaw = value.ReadData(); + m_xmlWriter.WriteBase64(pbRaw, 0, pbRaw.Length); + } + } + } + + private void WriteObject(string name, PwDeletedObject value) + { + Debug.Assert(name != null); + Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); + + m_xmlWriter.WriteStartElement(name); + WriteObject(ElemUuid, value.Uuid); + WriteObject(ElemDeletionTime, value.DeletionTime); + m_xmlWriter.WriteEndElement(); + } + + private void WriteBinPool() + { + m_xmlWriter.WriteStartElement(ElemBinaries); + + foreach(KeyValuePair kvp in m_dictBinPool) + { + m_xmlWriter.WriteStartElement(ElemBinary); + m_xmlWriter.WriteAttributeString(AttrId, kvp.Key); + SubWriteValue(kvp.Value); + m_xmlWriter.WriteEndElement(); + } + + m_xmlWriter.WriteEndElement(); + } + + [Obsolete] + public static bool WriteEntries(Stream msOutput, PwDatabase pwDatabase, + PwEntry[] vEntries) + { + return WriteEntries(msOutput, vEntries); + } + + /// + /// Write entries to a stream. + /// + /// Output stream to which the entries will be written. + /// Entries to serialize. + /// Returns true, if the entries were written successfully + /// to the stream. + public static bool WriteEntries(Stream msOutput, PwEntry[] vEntries) + { + /* KdbxFile f = new KdbxFile(pwDatabase); + f.m_format = KdbxFormat.PlainXml; + + XmlTextWriter xtw = null; + try { xtw = new XmlTextWriter(msOutput, StrUtil.Utf8); } + catch(Exception) { Debug.Assert(false); return false; } + if(xtw == null) { Debug.Assert(false); return false; } + + f.m_xmlWriter = xtw; + + xtw.Formatting = Formatting.Indented; + xtw.IndentChar = '\t'; + xtw.Indentation = 1; + + xtw.WriteStartDocument(true); + xtw.WriteStartElement(ElemRoot); + + foreach(PwEntry pe in vEntries) + f.WriteEntry(pe, false); + + xtw.WriteEndElement(); + xtw.WriteEndDocument(); + + xtw.Flush(); + xtw.Close(); + return true; */ + + PwDatabase pd = new PwDatabase(); + pd.New(new IOConnectionInfo(), new CompositeKey()); + + foreach(PwEntry peCopy in vEntries) + pd.RootGroup.AddEntry(peCopy.CloneDeep(), true); + + KdbxFile f = new KdbxFile(pd); + f.Save(msOutput, null, KdbxFormat.PlainXml, null); + return true; + } + } +} diff --git a/src/KeePassLib2Android/Serialization/KdbxFile.cs b/src/KeePassLib2Android/Serialization/KdbxFile.cs new file mode 100644 index 00000000..b1cd287a --- /dev/null +++ b/src/KeePassLib2Android/Serialization/KdbxFile.cs @@ -0,0 +1,390 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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.Xml; +using System.Text; +using System.Globalization; +using System.IO; +using System.Diagnostics; + +#if !KeePassLibSD +using System.IO.Compression; +#endif + +using KeePassLib.Collections; +using KeePassLib.Cryptography; +using KeePassLib.Delegates; +using KeePassLib.Interfaces; +using KeePassLib.Security; +using KeePassLib.Utility; + +namespace KeePassLib.Serialization +{ + /// + /// The KdbxFile class supports saving the data to various + /// formats. + /// + public enum KdbxFormat + { + /// + /// The default, encrypted file format. + /// + Default = 0, + + /// + /// Use this flag when exporting data to a plain-text XML file. + /// + PlainXml + } + + /// + /// Serialization to KeePass KDBX files. + /// + public sealed partial class KdbxFile + { + /// + /// File identifier, first 32-bit value. + /// + private const uint FileSignature1 = 0x9AA2D903; + + /// + /// File identifier, second 32-bit value. + /// + private const uint FileSignature2 = 0xB54BFB67; + + /// + /// File version of files saved by the current KdbxFile class. + /// KeePass 2.07 has version 1.01, 2.08 has 1.02, 2.09 has 2.00, + /// 2.10 has 2.02, 2.11 has 2.04, 2.15 has 3.00, 2.20 has 3.01. + /// 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 FileVersionCriticalMask = 0xFFFF0000; + + // KeePass 1.x signature + private const uint FileSignatureOld1 = 0x9AA2D903; + private const uint FileSignatureOld2 = 0xB54BFB65; + // KeePass 2.x pre-release (alpha and beta) signature + private const uint FileSignaturePreRelease1 = 0x9AA2D903; + private const uint FileSignaturePreRelease2 = 0xB54BFB66; + + private const string ElemDocNode = "KeePassFile"; + private const string ElemMeta = "Meta"; + private const string ElemRoot = "Root"; + private const string ElemGroup = "Group"; + private const string ElemEntry = "Entry"; + + private const string ElemGenerator = "Generator"; + private const string ElemHeaderHash = "HeaderHash"; + private const string ElemDbName = "DatabaseName"; + private const string ElemDbNameChanged = "DatabaseNameChanged"; + private const string ElemDbDesc = "DatabaseDescription"; + private const string ElemDbDescChanged = "DatabaseDescriptionChanged"; + private const string ElemDbDefaultUser = "DefaultUserName"; + private const string ElemDbDefaultUserChanged = "DefaultUserNameChanged"; + private const string ElemDbMntncHistoryDays = "MaintenanceHistoryDays"; + private const string ElemDbColor = "Color"; + private const string ElemDbKeyChanged = "MasterKeyChanged"; + private const string ElemDbKeyChangeRec = "MasterKeyChangeRec"; + private const string ElemDbKeyChangeForce = "MasterKeyChangeForce"; + private const string ElemRecycleBinEnabled = "RecycleBinEnabled"; + private const string ElemRecycleBinUuid = "RecycleBinUUID"; + private const string ElemRecycleBinChanged = "RecycleBinChanged"; + private const string ElemEntryTemplatesGroup = "EntryTemplatesGroup"; + private const string ElemEntryTemplatesGroupChanged = "EntryTemplatesGroupChanged"; + private const string ElemHistoryMaxItems = "HistoryMaxItems"; + private const string ElemHistoryMaxSize = "HistoryMaxSize"; + private const string ElemLastSelectedGroup = "LastSelectedGroup"; + private const string ElemLastTopVisibleGroup = "LastTopVisibleGroup"; + + private const string ElemMemoryProt = "MemoryProtection"; + private const string ElemProtTitle = "ProtectTitle"; + private const string ElemProtUserName = "ProtectUserName"; + private const string ElemProtPassword = "ProtectPassword"; + private const string ElemProtUrl = "ProtectURL"; + private const string ElemProtNotes = "ProtectNotes"; + // private const string ElemProtAutoHide = "AutoEnableVisualHiding"; + + private const string ElemCustomIcons = "CustomIcons"; + private const string ElemCustomIconItem = "Icon"; + private const string ElemCustomIconItemID = "UUID"; + private const string ElemCustomIconItemData = "Data"; + + private const string ElemAutoType = "AutoType"; + private const string ElemHistory = "History"; + + private const string ElemName = "Name"; + private const string ElemNotes = "Notes"; + private const string ElemUuid = "UUID"; + private const string ElemIcon = "IconID"; + private const string ElemCustomIconID = "CustomIconUUID"; + private const string ElemFgColor = "ForegroundColor"; + private const string ElemBgColor = "BackgroundColor"; + private const string ElemOverrideUrl = "OverrideURL"; + private const string ElemTimes = "Times"; + private const string ElemTags = "Tags"; + + private const string ElemCreationTime = "CreationTime"; + private const string ElemLastModTime = "LastModificationTime"; + private const string ElemLastAccessTime = "LastAccessTime"; + private const string ElemExpiryTime = "ExpiryTime"; + private const string ElemExpires = "Expires"; + private const string ElemUsageCount = "UsageCount"; + private const string ElemLocationChanged = "LocationChanged"; + + private const string ElemGroupDefaultAutoTypeSeq = "DefaultAutoTypeSequence"; + private const string ElemEnableAutoType = "EnableAutoType"; + private const string ElemEnableSearching = "EnableSearching"; + + private const string ElemString = "String"; + private const string ElemBinary = "Binary"; + private const string ElemKey = "Key"; + private const string ElemValue = "Value"; + + private const string ElemAutoTypeEnabled = "Enabled"; + private const string ElemAutoTypeObfuscation = "DataTransferObfuscation"; + private const string ElemAutoTypeDefaultSeq = "DefaultSequence"; + private const string ElemAutoTypeItem = "Association"; + private const string ElemWindow = "Window"; + private const string ElemKeystrokeSequence = "KeystrokeSequence"; + + private const string ElemBinaries = "Binaries"; + + private const string AttrId = "ID"; + private const string AttrRef = "Ref"; + private const string AttrProtected = "Protected"; + private const string AttrProtectedInMemPlainXml = "ProtectInMemory"; + private const string AttrCompressed = "Compressed"; + + private const string ElemIsExpanded = "IsExpanded"; + private const string ElemLastTopVisibleEntry = "LastTopVisibleEntry"; + + private const string ElemDeletedObjects = "DeletedObjects"; + private const string ElemDeletedObject = "DeletedObject"; + private const string ElemDeletionTime = "DeletionTime"; + + private const string ValFalse = "False"; + private const string ValTrue = "True"; + + private const string ElemCustomData = "CustomData"; + private const string ElemStringDictExItem = "Item"; + + private PwDatabase m_pwDatabase; // Not null, see constructor + + private XmlTextWriter m_xmlWriter = null; + private CryptoRandomStream m_randomStream = null; + private KdbxFormat m_format = KdbxFormat.Default; + private IStatusLogger m_slLogger = null; + + private byte[] m_pbMasterSeed = null; + private byte[] m_pbTransformSeed = null; + private byte[] m_pbEncryptionIV = null; + private byte[] m_pbProtectedStreamKey = null; + private byte[] m_pbStreamStartBytes = null; + + // ArcFourVariant only for compatibility; KeePass will default to a + // different (more secure) algorithm when *writing* databases + private CrsAlgorithm m_craInnerRandomStream = CrsAlgorithm.ArcFourVariant; + + private Dictionary m_dictBinPool = + new Dictionary(); + + private byte[] m_pbHashOfHeader = null; + private byte[] m_pbHashOfFileOnDisk = null; + + private readonly DateTime m_dtNow = DateTime.Now; // Cache current time + + private const uint NeutralLanguageOffset = 0x100000; // 2^20, see 32-bit Unicode specs + private const uint NeutralLanguageIDSec = 0x7DC5C; // See 32-bit Unicode specs + private const uint NeutralLanguageID = NeutralLanguageOffset + NeutralLanguageIDSec; + private static bool m_bLocalizedNames = false; + + private enum KdbxHeaderFieldID : byte + { + EndOfHeader = 0, + Comment = 1, + CipherID = 2, + CompressionFlags = 3, + MasterSeed = 4, + TransformSeed = 5, + TransformRounds = 6, + EncryptionIV = 7, + ProtectedStreamKey = 8, + StreamStartBytes = 9, + InnerRandomStreamID = 10 + } + + public byte[] HashOfFileOnDisk + { + get { return m_pbHashOfFileOnDisk; } + } + + private bool m_bRepairMode = false; + public bool RepairMode + { + get { return m_bRepairMode; } + set { m_bRepairMode = value; } + } + + private string m_strDetachBins = null; + /// + /// Detach binaries when opening a file. If this isn't null, + /// all binaries are saved to the specified path and are removed + /// from the database. + /// + public string DetachBinaries + { + get { return m_strDetachBins; } + set { m_strDetachBins = value; } + } + + /// + /// Default constructor. + /// + /// The PwDatabase instance that the + /// class will load file data into or use to create a KDBX file. + public KdbxFile(PwDatabase pwDataStore) + { + Debug.Assert(pwDataStore != null); + if(pwDataStore == null) throw new ArgumentNullException("pwDataStore"); + + m_pwDatabase = pwDataStore; + } + + /// + /// Call this once to determine the current localization settings. + /// + public static void DetermineLanguageId() + { + // Test if localized names should be used. If localized names are used, + // the m_bLocalizedNames value must be set to true. By default, localized + // names should be used! (Otherwise characters could be corrupted + // because of different code pages). + unchecked + { + uint uTest = 0; + foreach(char ch in PwDatabase.LocalizedAppName) + uTest = uTest * 5 + ch; + + m_bLocalizedNames = (uTest != NeutralLanguageID); + } + } + + private void BinPoolBuild(PwGroup pgDataSource) + { + m_dictBinPool = new Dictionary(); + + if(pgDataSource == null) { Debug.Assert(false); return; } + + EntryHandler eh = delegate(PwEntry pe) + { + foreach(PwEntry peHistory in pe.History) + { + BinPoolAdd(peHistory.Binaries); + } + + BinPoolAdd(pe.Binaries); + return true; + }; + + pgDataSource.TraverseTree(TraversalMethod.PreOrder, null, eh); + } + + private void BinPoolAdd(ProtectedBinaryDictionary dict) + { + foreach(KeyValuePair kvp in dict) + { + BinPoolAdd(kvp.Value); + } + } + + private void BinPoolAdd(ProtectedBinary pb) + { + if(pb == null) { Debug.Assert(false); return; } + + if(BinPoolFind(pb) != null) return; // Exists already + + m_dictBinPool.Add(m_dictBinPool.Count.ToString(), pb); + } + + private string BinPoolFind(ProtectedBinary pb) + { + if(pb == null) { Debug.Assert(false); return null; } + + foreach(KeyValuePair kvp in m_dictBinPool) + { + if(pb.Equals(kvp.Value)) return kvp.Key; + } + + return null; + } + + private ProtectedBinary BinPoolGet(string strKey) + { + if(strKey == null) { Debug.Assert(false); return null; } + + ProtectedBinary pb; + if(m_dictBinPool.TryGetValue(strKey, out pb)) return pb; + + return null; + } + + private static void SaveBinary(string strName, ProtectedBinary pb, + string strSaveDir) + { + if(pb == null) { Debug.Assert(false); return; } + + if(string.IsNullOrEmpty(strName)) strName = "File.bin"; + + string strPath; + int iTry = 1; + do + { + strPath = UrlUtil.EnsureTerminatingSeparator(strSaveDir, false); + + string strExt = UrlUtil.GetExtension(strName); + string strDesc = UrlUtil.StripExtension(strName); + + strPath += strDesc; + if(iTry > 1) strPath += " (" + iTry.ToString() + ")"; + + if(!string.IsNullOrEmpty(strExt)) strPath += "." + strExt; + + ++iTry; + } + while(File.Exists(strPath)); + +#if !KeePassLibSD + byte[] pbData = pb.ReadData(); + File.WriteAllBytes(strPath, pbData); + MemUtil.ZeroByteArray(pbData); +#else + FileStream fs = new FileStream(strPath, FileMode.Create, + FileAccess.Write, FileShare.None); + byte[] pbData = pb.ReadData(); + fs.Write(pbData, 0, pbData.Length); + fs.Close(); +#endif + } + } +} diff --git a/src/KeePassLib2Android/Serialization/OldFormatException.cs b/src/KeePassLib2Android/Serialization/OldFormatException.cs new file mode 100644 index 00000000..06593f2b --- /dev/null +++ b/src/KeePassLib2Android/Serialization/OldFormatException.cs @@ -0,0 +1,66 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; + +using KeePassLib.Resources; +using KeePassLib.Utility; + +namespace KeePassLib.Serialization +{ + public sealed class OldFormatException : Exception + { + private string m_strFormat = string.Empty; + private OldFormatType m_type = OldFormatType.Unknown; + + public enum OldFormatType + { + Unknown = 0, + KeePass1x = 1 + } + + public override string Message + { + get + { + string str = KLRes.OldFormat + ((m_strFormat.Length > 0) ? + (@" (" + m_strFormat + @")") : string.Empty) + "."; + + if(m_type == OldFormatType.KeePass1x) + str += MessageService.NewParagraph + KLRes.KeePass1xHint; + + return str; + } + } + + public OldFormatException(string strFormatName) + { + if(strFormatName != null) m_strFormat = strFormatName; + } + + public OldFormatException(string strFormatName, OldFormatType t) + { + if(strFormatName != null) m_strFormat = strFormatName; + + m_type = t; + } + } +} diff --git a/src/KeePassLib2Android/Translation/KPControlCustomization.cs b/src/KeePassLib2Android/Translation/KPControlCustomization.cs new file mode 100644 index 00000000..1530f655 --- /dev/null +++ b/src/KeePassLib2Android/Translation/KPControlCustomization.cs @@ -0,0 +1,400 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using 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.Drawing; + +using KeePassLib.Utility; + +namespace KeePassLib.Translation +{ + public sealed class KpccLayout + { + public enum LayoutParameterEx + { + X, Y, Width, Height + } + + private const string m_strControlRelative = @"%c"; + + internal const NumberStyles m_nsParser = NumberStyles.AllowLeadingSign | + NumberStyles.AllowDecimalPoint; + internal static readonly CultureInfo m_lclInv = CultureInfo.InvariantCulture; + + private string m_strPosX = string.Empty; + [XmlAttribute] + [DefaultValue("")] + public string X + { + get { return m_strPosX; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strPosX = value; + } + } + + private string m_strPosY = string.Empty; + [XmlAttribute] + [DefaultValue("")] + public string Y + { + get { return m_strPosY; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strPosY = value; + } + } + + private string m_strSizeW = string.Empty; + [XmlAttribute] + [DefaultValue("")] + public string Width + { + get { return m_strSizeW; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strSizeW = value; + } + } + + private string m_strSizeH = string.Empty; + [XmlAttribute] + [DefaultValue("")] + public string Height + { + get { return m_strSizeH; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strSizeH = value; + } + } + + public void SetControlRelativeValue(LayoutParameterEx lp, string strValue) + { + Debug.Assert(strValue != null); + if(strValue == null) throw new ArgumentNullException("strValue"); + + if(strValue.Length > 0) strValue += m_strControlRelative; + + if(lp == LayoutParameterEx.X) m_strPosX = strValue; + else if(lp == LayoutParameterEx.Y) m_strPosY = strValue; + else if(lp == LayoutParameterEx.Width) m_strSizeW = strValue; + else if(lp == LayoutParameterEx.Height) m_strSizeH = strValue; + else { Debug.Assert(false); } + } + +#if !KeePassLibSD + internal void ApplyTo(Control c) + { + Debug.Assert(c != null); if(c == null) return; + + int? v; + v = GetModControlParameter(c, LayoutParameterEx.X, m_strPosX); + if(v.HasValue) c.Left = v.Value; + v = GetModControlParameter(c, LayoutParameterEx.Y, m_strPosY); + if(v.HasValue) c.Top = v.Value; + v = GetModControlParameter(c, LayoutParameterEx.Width, m_strSizeW); + if(v.HasValue) c.Width = v.Value; + v = GetModControlParameter(c, LayoutParameterEx.Height, m_strSizeH); + if(v.HasValue) c.Height = v.Value; + } + + private static int? GetModControlParameter(Control c, LayoutParameterEx p, + string strModParam) + { + if(strModParam.Length == 0) return null; + + Debug.Assert(c.Left == c.Location.X); + Debug.Assert(c.Top == c.Location.Y); + Debug.Assert(c.Width == c.Size.Width); + Debug.Assert(c.Height == c.Size.Height); + + int iPrev; + if(p == LayoutParameterEx.X) iPrev = c.Left; + else if(p == LayoutParameterEx.Y) iPrev = c.Top; + else if(p == LayoutParameterEx.Width) iPrev = c.Width; + else if(p == LayoutParameterEx.Height) iPrev = c.Height; + else { Debug.Assert(false); return null; } + + double? dRel = ToControlRelativePercent(strModParam); + if(dRel.HasValue) + return (iPrev + (int)((dRel.Value * (double)iPrev) / 100.0)); + + Debug.Assert(false); + return null; + } + + public static double? ToControlRelativePercent(string strEncoded) + { + Debug.Assert(strEncoded != null); + if(strEncoded == null) throw new ArgumentNullException("strEncoded"); + + if(strEncoded.Length == 0) return null; + + if(strEncoded.EndsWith(m_strControlRelative)) + { + string strValue = strEncoded.Substring(0, strEncoded.Length - + m_strControlRelative.Length); + if((strValue.Length == 1) && (strValue == "-")) + strValue = "0"; + + double dRel; + if(double.TryParse(strValue, m_nsParser, m_lclInv, out dRel)) + { + return dRel; + } + else + { + Debug.Assert(false); + return null; + } + } + + Debug.Assert(false); + return null; + } +#endif + + public static string ToControlRelativeString(string strEncoded) + { + Debug.Assert(strEncoded != null); + if(strEncoded == null) throw new ArgumentNullException("strEncoded"); + + if(strEncoded.Length == 0) return string.Empty; + + if(strEncoded.EndsWith(m_strControlRelative)) + return strEncoded.Substring(0, strEncoded.Length - + m_strControlRelative.Length); + + Debug.Assert(false); + return string.Empty; + } + } + + public sealed class KPControlCustomization : IComparable + { + private string m_strMemberName = string.Empty; + /// + /// Member variable name of the control to be translated. + /// + [XmlAttribute] + public string Name + { + get { return m_strMemberName; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strMemberName = value; + } + } + + private string m_strHash = string.Empty; + [XmlAttribute] + public string BaseHash + { + get { return m_strHash; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strHash = value; + } + } + + private string m_strText = string.Empty; + [DefaultValue("")] + public string Text + { + get { return m_strText; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strText = value; + } + } + + private string m_strEngText = string.Empty; + [XmlIgnore] + public string TextEnglish + { + get { return m_strEngText; } + set { m_strEngText = value; } + } + + private KpccLayout m_layout = new KpccLayout(); + public KpccLayout Layout + { + get { return m_layout; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_layout = value; + } + } + + public int CompareTo(KPControlCustomization kpOther) + { + if(kpOther == null) { Debug.Assert(false); return 1; } + + return m_strMemberName.CompareTo(kpOther.Name); + } + +#if !KeePassLibSD + private static readonly Type[] m_vTextControls = new Type[] { + typeof(MenuStrip), typeof(PictureBox), typeof(ListView), + typeof(TreeView), typeof(ToolStrip), typeof(WebBrowser), + typeof(Panel), typeof(StatusStrip), typeof(ProgressBar), + typeof(NumericUpDown), typeof(TabControl) + }; + + public static bool ControlSupportsText(object oControl) + { + if(oControl == null) return false; + + Type t = oControl.GetType(); + for(int i = 0; i < m_vTextControls.Length; ++i) + { + if(t == m_vTextControls[i]) return false; + } + + return true; + } + + // Name-unchecked (!) property application method + internal void ApplyTo(Control c) + { + if((m_strText.Length > 0) && ControlSupportsText(c) && + (c.Text.Length > 0)) + { + c.Text = m_strText; + } + + m_layout.ApplyTo(c); + } + + public static string HashControl(Control c) + { + if(c == null) { Debug.Assert(false); return string.Empty; } + + StringBuilder sb = new StringBuilder(); + WriteCpiParam(sb, c.Text); + + if(c is Form) + { + WriteCpiParam(sb, c.ClientSize.Width.ToString()); + WriteCpiParam(sb, c.ClientSize.Height.ToString()); + } + else // Normal control + { + WriteCpiParam(sb, c.Left.ToString(KpccLayout.m_lclInv)); + WriteCpiParam(sb, c.Top.ToString(KpccLayout.m_lclInv)); + WriteCpiParam(sb, c.Width.ToString(KpccLayout.m_lclInv)); + WriteCpiParam(sb, c.Height.ToString(KpccLayout.m_lclInv)); + WriteCpiParam(sb, c.Dock.ToString()); + } + + WriteCpiParam(sb, c.Font.Name); + WriteCpiParam(sb, c.Font.SizeInPoints.ToString(KpccLayout.m_lclInv)); + WriteCpiParam(sb, c.Font.Bold ? "B" : "N"); + WriteCpiParam(sb, c.Font.Italic ? "I" : "N"); + WriteCpiParam(sb, c.Font.Underline ? "U" : "N"); + WriteCpiParam(sb, c.Font.Strikeout ? "S" : "N"); + + WriteControlDependentParams(sb, c); + + byte[] pb = StrUtil.Utf8.GetBytes(sb.ToString()); + + SHA256Managed sha256 = new SHA256Managed(); + byte[] pbSha = sha256.ComputeHash(pb); + + // Also see MatchHash + return "v1:" + Convert.ToBase64String(pbSha, 0, 3, + Base64FormattingOptions.None); + } + + private static void WriteControlDependentParams(StringBuilder sb, Control c) + { + CheckBox cb = (c as CheckBox); + RadioButton rb = (c as RadioButton); + Button btn = (c as Button); + Label l = (c as Label); + LinkLabel ll = (c as LinkLabel); + + if(cb != null) + { + WriteCpiParam(sb, cb.AutoSize ? "A" : "F"); + WriteCpiParam(sb, cb.TextAlign.ToString()); + WriteCpiParam(sb, cb.TextImageRelation.ToString()); + WriteCpiParam(sb, cb.Appearance.ToString()); + WriteCpiParam(sb, cb.CheckAlign.ToString()); + } + else if(rb != null) + { + WriteCpiParam(sb, rb.AutoSize ? "A" : "F"); + WriteCpiParam(sb, rb.TextAlign.ToString()); + WriteCpiParam(sb, rb.TextImageRelation.ToString()); + WriteCpiParam(sb, rb.Appearance.ToString()); + WriteCpiParam(sb, rb.CheckAlign.ToString()); + } + else if(btn != null) + { + WriteCpiParam(sb, btn.AutoSize ? "A" : "F"); + WriteCpiParam(sb, btn.TextAlign.ToString()); + WriteCpiParam(sb, btn.TextImageRelation.ToString()); + } + else if(l != null) + { + WriteCpiParam(sb, l.AutoSize ? "A" : "F"); + WriteCpiParam(sb, l.TextAlign.ToString()); + } + else if(ll != null) + { + WriteCpiParam(sb, ll.AutoSize ? "A" : "F"); + WriteCpiParam(sb, ll.TextAlign.ToString()); + } + } + + private static void WriteCpiParam(StringBuilder sb, string strProp) + { + sb.Append('/'); + sb.Append(strProp); + } + + public bool MatchHash(string strHash) + { + if(strHash == null) throw new ArgumentNullException("strHash"); + + // Currently only v1: is supported, see HashControl + return (m_strHash == strHash); + } +#endif + } +} diff --git a/src/KeePassLib2Android/Translation/KPFormCustomization.cs b/src/KeePassLib2Android/Translation/KPFormCustomization.cs new file mode 100644 index 00000000..9c6a3442 --- /dev/null +++ b/src/KeePassLib2Android/Translation/KPFormCustomization.cs @@ -0,0 +1,106 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Forms; +using System.Xml.Serialization; +using System.Diagnostics; +using System.Reflection; + +namespace KeePassLib.Translation +{ + public sealed class KPFormCustomization + { + private string m_strFQName = string.Empty; + /// + /// The fully qualified name of the form. + /// + [XmlAttribute] + public string FullName + { + get { return m_strFQName; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strFQName = value; + } + } + + private KPControlCustomization m_ccWindow = new KPControlCustomization(); + public KPControlCustomization Window + { + get { return m_ccWindow; } + set { m_ccWindow = value; } + } + + private List m_vControls = + new List(); + [XmlArray("ChildControls")] + [XmlArrayItem("Control")] + public List Controls + { + get { return m_vControls; } + set + { + if(value == null) throw new ArgumentNullException("value"); + + m_vControls = value; + } + } + + private Form m_formEnglish = null; + [XmlIgnore] + public Form FormEnglish + { + get { return m_formEnglish; } + set { m_formEnglish = value; } + } + +#if !KeePassLibSD + public void ApplyTo(Form form) + { + Debug.Assert(form != null); if(form == null) throw new ArgumentNullException("form"); + + // Not supported by TrlUtil (preview form): + // Debug.Assert(form.GetType().FullName == m_strFQName); + + m_ccWindow.ApplyTo(form); + + if(m_vControls.Count == 0) return; + foreach(Control c in form.Controls) ApplyToControl(c); + } + + private void ApplyToControl(Control c) + { + foreach(KPControlCustomization cc in m_vControls) + { + if(c.Name == cc.Name) + { + cc.ApplyTo(c); + break; + } + } + + foreach(Control cSub in c.Controls) ApplyToControl(cSub); + } +#endif + } +} diff --git a/src/KeePassLib2Android/Translation/KPStringTable.cs b/src/KeePassLib2Android/Translation/KPStringTable.cs new file mode 100644 index 00000000..b5c676fb --- /dev/null +++ b/src/KeePassLib2Android/Translation/KPStringTable.cs @@ -0,0 +1,99 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Xml.Serialization; +using System.Windows.Forms; +using System.Diagnostics; + +namespace KeePassLib.Translation +{ + public sealed class KPStringTable + { + private string m_strName = string.Empty; + [XmlAttribute] + public string Name + { + get { return m_strName; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_strName = value; + } + } + + private List m_vItems = new List(); + + [XmlArrayItem("Data")] + public List Strings + { + get { return m_vItems; } + set + { + if(value == null) throw new ArgumentNullException("value"); + m_vItems = value; + } + } + + public Dictionary ToDictionary() + { + Dictionary dict = new Dictionary(); + + foreach(KPStringTableItem kpstItem in m_vItems) + { + if(kpstItem.Value.Length > 0) + dict[kpstItem.Name] = kpstItem.Value; + } + + return dict; + } + +#if !KeePassLibSD + public void ApplyTo(ToolStripItemCollection tsic) + { + if(tsic == null) throw new ArgumentNullException("tsic"); + + Dictionary dict = this.ToDictionary(); + if(dict.Count == 0) return; + + this.ApplyTo(tsic, dict); + } + + private void ApplyTo(ToolStripItemCollection tsic, Dictionary dict) + { + if(tsic == null) return; + + foreach(ToolStripItem tsi in tsic) + { + if(tsi.Text.Length == 0) continue; + + string strTrl; + if(dict.TryGetValue(tsi.Name, out strTrl)) + tsi.Text = strTrl; + + ToolStripMenuItem tsmi = tsi as ToolStripMenuItem; + if((tsmi != null) && (tsmi.DropDownItems != null)) + this.ApplyTo(tsmi.DropDownItems); + } + } +#endif + } +} diff --git a/src/KeePassLib2Android/Translation/KPStringTableItem.cs b/src/KeePassLib2Android/Translation/KPStringTableItem.cs new file mode 100644 index 00000000..3cc0bddd --- /dev/null +++ b/src/KeePassLib2Android/Translation/KPStringTableItem.cs @@ -0,0 +1,51 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Xml.Serialization; + +namespace KeePassLib.Translation +{ + public sealed class KPStringTableItem + { + private string m_strName = string.Empty; + public string Name + { + get { return m_strName; } + set { m_strName = value; } + } + + private string m_strValue = string.Empty; + public string Value + { + get { return m_strValue; } + set { m_strValue = value; } + } + + private string m_strEnglish = string.Empty; + [XmlIgnore] + public string ValueEnglish + { + get { return m_strEnglish; } + set { m_strEnglish = value; } + } + } +} diff --git a/src/KeePassLib2Android/Translation/KPTranslation.cs b/src/KeePassLib2Android/Translation/KPTranslation.cs new file mode 100644 index 00000000..2705325d --- /dev/null +++ b/src/KeePassLib2Android/Translation/KPTranslation.cs @@ -0,0 +1,252 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Xml; +using System.Xml.Serialization; +using System.Windows.Forms; +using System.ComponentModel; +using System.Drawing; +using System.Diagnostics; + +using KeePassLib.Interfaces; +using KeePassLib.Utility; + +#if !KeePassLibSD +using System.IO.Compression; +#else +using ICSharpCode.SharpZipLib.GZip; +#endif + +namespace KeePassLib.Translation +{ + [XmlRoot("Translation")] + public sealed class KPTranslation + { + public const string FileExtension = "lngx"; + + private KPTranslationProperties m_props = new KPTranslationProperties(); + public KPTranslationProperties Properties + { + get { return m_props; } + set { m_props = value; } + } + + private List m_vStringTables = new List(); + + [XmlArrayItem("StringTable")] + public List StringTables + { + get { return m_vStringTables; } + set + { + if(value == null) throw new ArgumentNullException("value"); + + m_vStringTables = value; + } + } + + private List m_vForms = new List(); + + [XmlArrayItem("Form")] + public List Forms + { + get { return m_vForms; } + set + { + if(value == null) throw new ArgumentNullException("value"); + + m_vForms = value; + } + } + + private string m_strUnusedText = string.Empty; + [DefaultValue("")] + public string UnusedText + { + get { return m_strUnusedText; } + set + { + if(value == null) throw new ArgumentNullException("value"); + + m_strUnusedText = value; + } + } + + public static void SaveToFile(KPTranslation kpTrl, string strFileName, + 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); +#else + GZipOutputStream gz = new GZipOutputStream(fs); +#endif + + XmlWriterSettings xws = new XmlWriterSettings(); + xws.CheckCharacters = true; + xws.Encoding = StrUtil.Utf8; + xws.Indent = true; + xws.IndentChars = "\t"; + + XmlWriter xw = XmlWriter.Create(gz, xws); + + xs.Serialize(xw, kpTrl); + + xw.Close(); + gz.Close(); + fs.Close(); + } + + public static KPTranslation LoadFromFile(string strFile, + 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); +#else + GZipInputStream gz = new GZipInputStream(fs); +#endif + + KPTranslation kpTrl = (xs.Deserialize(gz) as KPTranslation); + + gz.Close(); + fs.Close(); + return kpTrl; + } + + public Dictionary SafeGetStringTableDictionary( + string strTableName) + { + foreach(KPStringTable kpst in m_vStringTables) + { + if(kpst.Name == strTableName) return kpst.ToDictionary(); + } + + return new Dictionary(); + } + +#if !KeePassLibSD + public void ApplyTo(Form form) + { + if(form == null) throw new ArgumentNullException("form"); + + if(m_props.RightToLeft) + { + try + { + form.RightToLeft = RightToLeft.Yes; + form.RightToLeftLayout = true; + } + catch(Exception) { Debug.Assert(false); } + } + + string strTypeName = form.GetType().FullName; + foreach(KPFormCustomization kpfc in m_vForms) + { + if(kpfc.FullName == strTypeName) + { + kpfc.ApplyTo(form); + break; + } + } + + if(m_props.RightToLeft) + { + try { RtlApplyToControls(form.Controls); } + catch(Exception) { Debug.Assert(false); } + } + } + + private static void RtlApplyToControls(Control.ControlCollection cc) + { + foreach(Control c in cc) + { + if(c.Controls.Count > 0) RtlApplyToControls(c.Controls); + + if(c is DateTimePicker) + ((DateTimePicker)c).RightToLeftLayout = true; + else if(c is ListView) + ((ListView)c).RightToLeftLayout = true; + else if(c is MonthCalendar) + ((MonthCalendar)c).RightToLeftLayout = true; + else if(c is ProgressBar) + ((ProgressBar)c).RightToLeftLayout = true; + else if(c is TabControl) + ((TabControl)c).RightToLeftLayout = true; + else if(c is TrackBar) + ((TrackBar)c).RightToLeftLayout = true; + else if(c is TreeView) + ((TreeView)c).RightToLeftLayout = true; + else if(c is ToolStrip) + RtlApplyToToolStripItems(((ToolStrip)c).Items); + + if((c is GroupBox) || (c is Panel)) RtlMoveChildControls(c); + } + } + + private static void RtlMoveChildControls(Control cParent) + { + int nParentWidth = cParent.Size.Width; + + foreach(Control c in cParent.Controls) + { + Point ptCur = c.Location; + c.Location = new Point(nParentWidth - c.Size.Width - ptCur.X, ptCur.Y); + } + } + + private static void RtlApplyToToolStripItems(ToolStripItemCollection tsic) + { + foreach(ToolStripItem tsi in tsic) + { + tsi.RightToLeftAutoMirrorImage = true; + } + } + + public void ApplyTo(string strTableName, ToolStripItemCollection tsic) + { + if(tsic == null) throw new ArgumentNullException("tsic"); + + KPStringTable kpst = null; + foreach(KPStringTable kpstEnum in m_vStringTables) + { + if(kpstEnum.Name == strTableName) + { + kpst = kpstEnum; + break; + } + } + + if(kpst != null) kpst.ApplyTo(tsic); + } +#endif + } +} diff --git a/src/KeePassLib2Android/Translation/KPTranslationProperties.cs b/src/KeePassLib2Android/Translation/KPTranslationProperties.cs new file mode 100644 index 00000000..914268f8 --- /dev/null +++ b/src/KeePassLib2Android/Translation/KPTranslationProperties.cs @@ -0,0 +1,105 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace KeePassLib.Translation +{ + public sealed class KPTranslationProperties + { + private string m_strApp = string.Empty; + public string Application + { + get { return m_strApp; } + set { m_strApp = value; } + } + + private string m_strForVersion = PwDefs.VersionString; + public string ApplicationVersion + { + get { return m_strForVersion; } + set { m_strForVersion = value; } + } + + private string m_strNameEnglish = string.Empty; + public string NameEnglish + { + get { return m_strNameEnglish; } + set { m_strNameEnglish = value; } + } + + private string m_strNameNative = string.Empty; + public string NameNative + { + get { return m_strNameNative; } + set { m_strNameNative = value; } + } + + private string m_strIso6391Code = string.Empty; + public string Iso6391Code + { + get { return m_strIso6391Code; } + set { m_strIso6391Code = value; } + } + + private bool m_bRtl = false; + public bool RightToLeft + { + get { return m_bRtl; } + set { m_bRtl = value; } + } + + private string m_strAuthorName = string.Empty; + public string AuthorName + { + get { return m_strAuthorName; } + set { m_strAuthorName = value; } + } + + private string m_strAuthorContact = string.Empty; + public string AuthorContact + { + get { return m_strAuthorContact; } + set { m_strAuthorContact = value; } + } + + private string m_strGen = string.Empty; + public string Generator + { + get { return m_strGen; } + set { m_strGen = value; } + } + + private string m_strUuid = string.Empty; + public string FileUuid + { + get { return m_strUuid; } + set { m_strUuid = value; } + } + + private string m_strLastModified = string.Empty; + public string LastModified + { + get { return m_strLastModified; } + set { m_strLastModified = value; } + } + } +} diff --git a/src/KeePassLib2Android/Utility/AppLogEx.cs b/src/KeePassLib2Android/Utility/AppLogEx.cs new file mode 100644 index 00000000..7074b60d --- /dev/null +++ b/src/KeePassLib2Android/Utility/AppLogEx.cs @@ -0,0 +1,104 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Diagnostics; + +#if !KeePassLibSD +using System.IO.Compression; +#endif + +namespace KeePassLib.Utility +{ + /// + /// Application-wide logging services. + /// + public static class AppLogEx + { + private static StreamWriter m_swOut = null; + + public static void Open(string strPrefix) + { + return; // Logging is not enabled in normal builds of KeePass! + + /* + AppLogEx.Close(); + + Debug.Assert(strPrefix != null); + if(strPrefix == null) strPrefix = "Log"; + + try + { + string strDirSep = string.Empty; + strDirSep += Path.DirectorySeparatorChar; + + string strTemp = UrlUtil.GetTempPath(); + if(!strTemp.EndsWith(strDirSep)) + strTemp += strDirSep; + + string strPath = strTemp + strPrefix + "-"; + Debug.Assert(strPath.IndexOf('/') < 0); + + DateTime dtNow = DateTime.Now; + string strTime = dtNow.ToString("s"); + strTime = strTime.Replace('T', '-'); + strTime = strTime.Replace(':', '-'); + + strPath += strTime + "-" + Environment.TickCount.ToString( + CultureInfo.InvariantCulture) + ".log.gz"; + + FileStream fsOut = new FileStream(strPath, FileMode.Create, + FileAccess.Write, FileShare.None); + GZipStream gz = new GZipStream(fsOut, CompressionMode.Compress); + m_swOut = new StreamWriter(gz); + + AppLogEx.Log("Started logging on " + dtNow.ToString("s") + "."); + } + catch(Exception) { Debug.Assert(false); } + */ + } + + public static void Close() + { + if(m_swOut == null) return; + + m_swOut.Close(); + m_swOut = null; + } + + public static void Log(string strText) + { + if(m_swOut == null) return; + + if(strText == null) m_swOut.WriteLine(); + else m_swOut.WriteLine(strText); + } + + public static void Log(Exception ex) + { + if(m_swOut == null) return; + + if(ex == null) m_swOut.WriteLine(); + else m_swOut.WriteLine(ex.ToString()); + } + } +} diff --git a/src/KeePassLib2Android/Utility/GfxUtil.cs b/src/KeePassLib2Android/Utility/GfxUtil.cs new file mode 100644 index 00000000..2b487407 --- /dev/null +++ b/src/KeePassLib2Android/Utility/GfxUtil.cs @@ -0,0 +1,95 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Drawing; +using System.Drawing.Imaging; +using System.Diagnostics; + +namespace KeePassLib.Utility +{ + public static class GfxUtil + { + 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) + { + Image imgIco = TryLoadIco(pb); + if(imgIco != null) return imgIco; + throw; + } + finally { ms.Close(); } + } + + private static Image LoadImagePriv(Stream s) + { + // Image.FromStream wants the stream to be open during + // the whole lifetime of the image; as we can't guarantee + // this, we make a copy of the image + Image imgSrc = null; + try + { +#if !KeePassLibSD + imgSrc = Image.FromStream(s); + Bitmap bmp = new Bitmap(imgSrc.Width, imgSrc.Height, + PixelFormat.Format32bppArgb); + + try + { + bmp.SetResolution(imgSrc.HorizontalResolution, + imgSrc.VerticalResolution); + Debug.Assert(bmp.Size == imgSrc.Size); + } + catch(Exception) { Debug.Assert(false); } +#else + imgSrc = new Bitmap(s); + Bitmap bmp = new Bitmap(imgSrc.Width, imgSrc.Height); +#endif + + using(Graphics g = Graphics.FromImage(bmp)) + { + g.Clear(Color.Transparent); + g.DrawImage(imgSrc, 0, 0); + } + + return bmp; + } + 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 + + return null; + } + } +} diff --git a/src/KeePassLib2Android/Utility/MemUtil.cs b/src/KeePassLib2Android/Utility/MemUtil.cs new file mode 100644 index 00000000..af7520f2 --- /dev/null +++ b/src/KeePassLib2Android/Utility/MemUtil.cs @@ -0,0 +1,404 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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.Text; +using System.Security.Cryptography; +using System.Diagnostics; +using System.IO; + +#if !KeePassLibSD +using System.IO.Compression; +#else +using KeePassLibSD; +#endif + +namespace KeePassLib.Utility +{ + /// + /// Contains static buffer manipulation and string conversion routines. + /// + public static class MemUtil + { + /// + /// Convert a hexadecimal string to a byte array. The input string must be + /// even (i.e. its length is a multiple of 2). + /// + /// String containing hexadecimal characters. + /// Returns a byte array. Returns null if the string parameter + /// was null or is an uneven string (i.e. if its length isn't a + /// multiple of 2). + /// Thrown if + /// is null. + public static byte[] HexStringToByteArray(string strHex) + { + if(strHex == null) { Debug.Assert(false); throw new ArgumentNullException("strHex"); } + + int nStrLen = strHex.Length; + if((nStrLen & 1) != 0) { Debug.Assert(false); return null; } + + byte[] pb = new byte[nStrLen / 2]; + byte bt; + char ch; + + for(int i = 0; i < nStrLen; i += 2) + { + ch = strHex[i]; + + if((ch >= '0') && (ch <= '9')) + bt = (byte)(ch - '0'); + else if((ch >= 'a') && (ch <= 'f')) + bt = (byte)(ch - 'a' + 10); + else if((ch >= 'A') && (ch <= 'F')) + bt = (byte)(ch - 'A' + 10); + else { Debug.Assert(false); bt = 0; } + + bt <<= 4; + + ch = strHex[i + 1]; + if((ch >= '0') && (ch <= '9')) + bt += (byte)(ch - '0'); + else if((ch >= 'a') && (ch <= 'f')) + bt += (byte)(ch - 'a' + 10); + else if((ch >= 'A') && (ch <= 'F')) + bt += (byte)(ch - 'A' + 10); + else { Debug.Assert(false); } + + pb[i >> 1] = bt; + } + + return pb; + } + + /// + /// Convert a byte array to a hexadecimal string. + /// + /// Input byte array. + /// Returns the hexadecimal string representing the byte + /// array. Returns null, if the input byte array was null. Returns + /// an empty string, if the input byte array has length 0. + public static string ByteArrayToHexString(byte[] pbArray) + { + if(pbArray == null) return null; + + int nLen = pbArray.Length; + if(nLen == 0) return string.Empty; + + StringBuilder sb = new StringBuilder(); + + byte bt, btHigh, btLow; + for(int i = 0; i < nLen; ++i) + { + bt = pbArray[i]; + btHigh = bt; btHigh >>= 4; + btLow = (byte)(bt & 0x0F); + + if(btHigh >= 10) sb.Append((char)('A' + btHigh - 10)); + else sb.Append((char)('0' + btHigh)); + + if(btLow >= 10) sb.Append((char)('A' + btLow - 10)); + else sb.Append((char)('0' + btLow)); + } + + return sb.ToString(); + } + + /// + /// Set all bytes in a byte array to zero. + /// + /// Input array. All bytes of this array will be set + /// to zero. + 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; + + Array.Clear(pbArray, 0, pbArray.Length); + } + + /// + /// Convert 2 bytes to a 16-bit unsigned integer using Little-Endian + /// encoding. + /// + /// Input bytes. Array must contain at least 2 bytes. + /// 16-bit unsigned integer. + public static ushort BytesToUInt16(byte[] pb) + { + Debug.Assert((pb != null) && (pb.Length == 2)); + if(pb == null) throw new ArgumentNullException("pb"); + if(pb.Length != 2) throw new ArgumentException(); + + return (ushort)((ushort)pb[0] | ((ushort)pb[1] << 8)); + } + + /// + /// Convert 4 bytes to a 32-bit unsigned integer using Little-Endian + /// encoding. + /// + /// 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!"); + + 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. + /// + /// 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) | + ((ulong)pb[3] << 24) | ((ulong)pb[4] << 32) | ((ulong)pb[5] << 40) | + ((ulong)pb[6] << 48) | ((ulong)pb[7] << 56); + } + + /// + /// Convert a 16-bit unsigned integer to 2 bytes using Little-Endian + /// encoding. + /// + /// 16-bit input word. + /// Two bytes representing the 16-bit value. + public static byte[] UInt16ToBytes(ushort uValue) + { + byte[] pb = new byte[2]; + + unchecked + { + pb[0] = (byte)uValue; + pb[1] = (byte)(uValue >> 8); + } + + return pb; + } + + /// + /// Convert a 32-bit unsigned integer to 4 bytes using Little-Endian + /// encoding. + /// + /// 32-bit input word. + /// Four bytes representing the 32-bit value. + public static byte[] UInt32ToBytes(uint uValue) + { + byte[] pb = new byte[4]; + + unchecked + { + pb[0] = (byte)uValue; + pb[1] = (byte)(uValue >> 8); + pb[2] = (byte)(uValue >> 16); + pb[3] = (byte)(uValue >> 24); + } + + return pb; + } + + /// + /// Convert a 64-bit unsigned integer to 8 bytes using Little-Endian + /// encoding. + /// + /// 64-bit input word. + /// Eight bytes representing the 64-bit value. + public static byte[] UInt64ToBytes(ulong uValue) + { + byte[] pb = new byte[8]; + + unchecked + { + pb[0] = (byte)uValue; + pb[1] = (byte)(uValue >> 8); + pb[2] = (byte)(uValue >> 16); + pb[3] = (byte)(uValue >> 24); + pb[4] = (byte)(uValue >> 32); + pb[5] = (byte)(uValue >> 40); + pb[6] = (byte)(uValue >> 48); + pb[7] = (byte)(uValue >> 56); + } + + return pb; + } + + public static bool ArraysEqual(byte[] x, byte[] y) + { + // Return false if one of them is null (not comparable)! + if((x == null) || (y == null)) { Debug.Assert(false); return false; } + + if(x.Length != y.Length) return false; + + for(int i = 0; i < x.Length; ++i) + { + if(x[i] != y[i]) return false; + } + + return true; + } + + public static void XorArray(byte[] pbSource, int nSourceOffset, + byte[] pbBuffer, int nBufferOffset, int nLength) + { + if(pbSource == null) throw new ArgumentNullException("pbSource"); + if(nSourceOffset < 0) throw new ArgumentException(); + 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(); + + for(int i = 0; i < nLength; ++i) + pbBuffer[nBufferOffset + i] ^= pbSource[nSourceOffset + i]; + } + + public static void CopyStream(Stream sSource, Stream sTarget) + { + Debug.Assert((sSource != null) && (sTarget != null)); + if(sSource == null) throw new ArgumentNullException("sSource"); + if(sTarget == null) throw new ArgumentNullException("sTarget"); + + const int nBufSize = 4096; + byte[] pbBuf = new byte[nBufSize]; + + while(true) + { + int nRead = sSource.Read(pbBuf, 0, nBufSize); + if(nRead == 0) break; + + sTarget.Write(pbBuf, 0, nRead); + } + + // Do not close any of the streams + } + + public static byte[] Read(Stream s, int nCount) + { + if(s == null) throw new ArgumentNullException("s"); + if(nCount < 0) throw new ArgumentOutOfRangeException("nCount"); + + byte[] pb = new byte[nCount]; + int iOffset = 0; + while(nCount > 0) + { + int iRead = s.Read(pb, iOffset, nCount); + if(iRead == 0) break; + + iOffset += iRead; + nCount -= iRead; + } + + if(iOffset != pb.Length) + { + byte[] pbPart = new byte[iOffset]; + Array.Copy(pb, pbPart, iOffset); + return pbPart; + } + + return pb; + } + + public static void Write(Stream s, byte[] pbData) + { + if(s == null) { Debug.Assert(false); return; } + if(pbData == null) { Debug.Assert(false); return; } + + s.Write(pbData, 0, pbData.Length); + } + + public static byte[] Compress(byte[] pbData) + { + 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 = msCompressed.ToArray(); + msCompressed.Close(); + return pbCompressed; + } + + public static byte[] Decompress(byte[] pbCompressed) + { + 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 = msData.ToArray(); + msData.Close(); + return pbData; + } + + public static int IndexOf(T[] vHaystack, T[] vNeedle) + where T : IEquatable + { + if(vHaystack == null) throw new ArgumentNullException("vHaystack"); + if(vNeedle == null) throw new ArgumentNullException("vNeedle"); + if(vNeedle.Length == 0) return 0; + + for(int i = 0; i <= (vHaystack.Length - vNeedle.Length); ++i) + { + bool bFound = true; + for(int m = 0; m < vNeedle.Length; ++m) + { + if(!vHaystack[i + m].Equals(vNeedle[m])) + { + bFound = false; + break; + } + } + if(bFound) return i; + } + + return -1; + } + + public static T[] Mid(T[] v, int iOffset, int iLength) + { + 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(); + + T[] r = new T[iLength]; + Array.Copy(v, iOffset, r, 0, iLength); + return r; + } + } +} diff --git a/src/KeePassLib2Android/Utility/MessageService.cs b/src/KeePassLib2Android/Utility/MessageService.cs new file mode 100644 index 00000000..22ee8355 --- /dev/null +++ b/src/KeePassLib2Android/Utility/MessageService.cs @@ -0,0 +1,428 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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.Collections.Specialized; +using System.Text; +using System.Windows.Forms; +using System.Diagnostics; + +using KeePassLib.Resources; +using KeePassLib.Serialization; + +namespace KeePassLib.Utility +{ + public sealed class MessageServiceEventArgs : EventArgs + { + private string m_strTitle = string.Empty; + private string m_strText = string.Empty; + private MessageBoxButtons m_msgButtons = MessageBoxButtons.OK; + private MessageBoxIcon m_msgIcon = MessageBoxIcon.None; + + public string Title { get { return m_strTitle; } } + public string Text { get { return m_strText; } } + public MessageBoxButtons Buttons { get { return m_msgButtons; } } + public MessageBoxIcon Icon { get { return m_msgIcon; } } + + public MessageServiceEventArgs() { } + + public MessageServiceEventArgs(string strTitle, string strText, + MessageBoxButtons msgButtons, MessageBoxIcon msgIcon) + { + m_strTitle = (strTitle ?? string.Empty); + m_strText = (strText ?? string.Empty); + m_msgButtons = msgButtons; + m_msgIcon = msgIcon; + } + } + + public static class MessageService + { + private static volatile uint m_uCurrentMessageCount = 0; + +#if !KeePassLibSD + private const MessageBoxIcon m_mbiInfo = MessageBoxIcon.Information; + private const MessageBoxIcon m_mbiWarning = MessageBoxIcon.Warning; + private const MessageBoxIcon m_mbiFatal = MessageBoxIcon.Error; + + private const MessageBoxOptions m_mboRtl = (MessageBoxOptions.RtlReading | + MessageBoxOptions.RightAlign); +#else + private const MessageBoxIcon m_mbiInfo = MessageBoxIcon.Asterisk; + private const MessageBoxIcon m_mbiWarning = MessageBoxIcon.Exclamation; + private const MessageBoxIcon m_mbiFatal = MessageBoxIcon.Hand; +#endif + private const MessageBoxIcon m_mbiQuestion = MessageBoxIcon.Question; + + public static string NewLine + { +#if !KeePassLibSD + get { return Environment.NewLine; } +#else + get { return "\r\n"; } +#endif + } + + public static string NewParagraph + { +#if !KeePassLibSD + get { return Environment.NewLine + Environment.NewLine; } +#else + get { return "\r\n\r\n"; } +#endif + } + + public static uint CurrentMessageCount + { + get { return m_uCurrentMessageCount; } + } + + public static event EventHandler MessageShowing; + + private static string ObjectsToMessage(object[] vLines) + { + return ObjectsToMessage(vLines, false); + } + + private static string ObjectsToMessage(object[] vLines, bool bFullExceptions) + { + if(vLines == null) return string.Empty; + + string strNewPara = MessageService.NewParagraph; + + StringBuilder sbText = new StringBuilder(); + bool bSeparator = false; + + foreach(object obj in vLines) + { + if(obj == null) continue; + + string strAppend = null; + + Exception exObj = (obj as Exception); + string strObj = (obj as string); +#if !KeePassLibSD + StringCollection scObj = (obj as StringCollection); +#endif + + if(exObj != null) + { + if(bFullExceptions) + strAppend = StrUtil.FormatException(exObj); + else if((exObj.Message != null) && (exObj.Message.Length > 0)) + strAppend = exObj.Message; + } +#if !KeePassLibSD + else if(scObj != null) + { + StringBuilder sb = new StringBuilder(); + foreach(string strCollLine in scObj) + { + if(sb.Length > 0) sb.AppendLine(); + sb.Append(strCollLine.TrimEnd()); + } + strAppend = sb.ToString(); + } +#endif + else if(strObj != null) + strAppend = strObj; + else + strAppend = obj.ToString(); + + if(!string.IsNullOrEmpty(strAppend)) + { + if(bSeparator) sbText.Append(strNewPara); + else bSeparator = true; + + sbText.Append(strAppend); + } + } + + return sbText.ToString(); + } + +#if !KeePassLibSD + internal static Form GetTopForm() + { + FormCollection fc = Application.OpenForms; + if((fc == null) || (fc.Count == 0)) return null; + + return fc[fc.Count - 1]; + } +#endif + + private static DialogResult SafeShowMessageBox(string strText, string strTitle, + MessageBoxButtons mb, MessageBoxIcon mi, MessageBoxDefaultButton mdb) + { +#if KeePassLibSD + return MessageBox.Show(strText, strTitle, mb, mi, mdb); +#else + IWin32Window wnd = null; + try + { + Form f = GetTopForm(); + if((f != null) && f.InvokeRequired) + return (DialogResult)f.Invoke(new SafeShowMessageBoxInternalDelegate( + SafeShowMessageBoxInternal), f, strText, strTitle, mb, mi, mdb); + else wnd = f; + } + catch(Exception) { Debug.Assert(false); } + + if(wnd == null) + { + if(StrUtil.RightToLeft) + return MessageBox.Show(strText, strTitle, mb, mi, mdb, m_mboRtl); + return MessageBox.Show(strText, strTitle, mb, mi, mdb); + } + + try + { + if(StrUtil.RightToLeft) + return MessageBox.Show(wnd, strText, strTitle, mb, mi, mdb, m_mboRtl); + return MessageBox.Show(wnd, strText, strTitle, mb, mi, mdb); + } + catch(Exception) { Debug.Assert(false); } + + if(StrUtil.RightToLeft) + return MessageBox.Show(strText, strTitle, mb, mi, mdb, m_mboRtl); + return MessageBox.Show(strText, strTitle, mb, mi, mdb); +#endif + } + +#if !KeePassLibSD + internal delegate DialogResult SafeShowMessageBoxInternalDelegate(IWin32Window iParent, + string strText, string strTitle, MessageBoxButtons mb, MessageBoxIcon mi, + MessageBoxDefaultButton mdb); + + internal static DialogResult SafeShowMessageBoxInternal(IWin32Window iParent, + string strText, string strTitle, MessageBoxButtons mb, MessageBoxIcon mi, + MessageBoxDefaultButton mdb) + { + if(StrUtil.RightToLeft) + return MessageBox.Show(iParent, strText, strTitle, mb, mi, mdb, m_mboRtl); + return MessageBox.Show(iParent, strText, strTitle, mb, mi, mdb); + } +#endif + + public static void ShowInfo(params object[] vLines) + { + ShowInfoEx(null, vLines); + } + + public static void ShowInfoEx(string strTitle, params object[] vLines) + { + ++m_uCurrentMessageCount; + + strTitle = (strTitle ?? PwDefs.ShortProductName); + string strText = ObjectsToMessage(vLines); + + if(MessageService.MessageShowing != null) + MessageService.MessageShowing(null, new MessageServiceEventArgs( + strTitle, strText, MessageBoxButtons.OK, m_mbiInfo)); + + SafeShowMessageBox(strText, strTitle, MessageBoxButtons.OK, m_mbiInfo, + MessageBoxDefaultButton.Button1); + + --m_uCurrentMessageCount; + } + + public static void ShowWarning(params object[] vLines) + { + ShowWarningPriv(vLines, false); + } + + internal static void ShowWarningExcp(params object[] vLines) + { + ShowWarningPriv(vLines, true); + } + + private static void ShowWarningPriv(object[] vLines, bool bFullExceptions) + { + ++m_uCurrentMessageCount; + + string strTitle = PwDefs.ShortProductName; + string strText = ObjectsToMessage(vLines, bFullExceptions); + + if(MessageService.MessageShowing != null) + MessageService.MessageShowing(null, new MessageServiceEventArgs( + strTitle, strText, MessageBoxButtons.OK, m_mbiWarning)); + + SafeShowMessageBox(strText, strTitle, MessageBoxButtons.OK, m_mbiWarning, + MessageBoxDefaultButton.Button1); + + --m_uCurrentMessageCount; + } + + public static void ShowFatal(params object[] vLines) + { + ++m_uCurrentMessageCount; + + string strTitle = PwDefs.ShortProductName + @" - " + KLRes.FatalError; + string strText = KLRes.FatalErrorText + MessageService.NewParagraph + + KLRes.ErrorInClipboard + MessageService.NewParagraph + + // Please send it to the KeePass developers. + // KLRes.ErrorFeedbackRequest + MessageService.NewParagraph + + ObjectsToMessage(vLines); + + try + { +#if !KeePassLibSD + Clipboard.Clear(); + Clipboard.SetText(ObjectsToMessage(vLines, true)); +#else + Clipboard.SetDataObject(ObjectsToMessage(vLines, true)); +#endif + } + catch(Exception) { Debug.Assert(false); } + + if(MessageService.MessageShowing != null) + MessageService.MessageShowing(null, new MessageServiceEventArgs( + strTitle, strText, MessageBoxButtons.OK, m_mbiFatal)); + + SafeShowMessageBox(strText, strTitle, MessageBoxButtons.OK, m_mbiFatal, + MessageBoxDefaultButton.Button1); + + --m_uCurrentMessageCount; + } + + public static DialogResult Ask(string strText, string strTitle, + MessageBoxButtons mbb) + { + ++m_uCurrentMessageCount; + + string strTextEx = (strText ?? string.Empty); + string strTitleEx = (strTitle ?? PwDefs.ShortProductName); + + if(MessageService.MessageShowing != null) + MessageService.MessageShowing(null, new MessageServiceEventArgs( + strTitleEx, strTextEx, mbb, m_mbiQuestion)); + + DialogResult dr = SafeShowMessageBox(strTextEx, strTitleEx, mbb, + m_mbiQuestion, MessageBoxDefaultButton.Button1); + + --m_uCurrentMessageCount; + return dr; + } + + public static bool AskYesNo(string strText, string strTitle, bool bDefaultToYes) + { + ++m_uCurrentMessageCount; + + string strTextEx = (strText ?? string.Empty); + string strTitleEx = (strTitle ?? PwDefs.ShortProductName); + + if(MessageService.MessageShowing != null) + MessageService.MessageShowing(null, new MessageServiceEventArgs( + strTitleEx, strTextEx, MessageBoxButtons.YesNo, m_mbiQuestion)); + + DialogResult dr = SafeShowMessageBox(strTextEx, strTitleEx, + MessageBoxButtons.YesNo, m_mbiQuestion, bDefaultToYes ? + MessageBoxDefaultButton.Button1 : MessageBoxDefaultButton.Button2); + + --m_uCurrentMessageCount; + return (dr == DialogResult.Yes); + } + + public static bool AskYesNo(string strText, string strTitle) + { + return AskYesNo(strText, strTitle, true); + } + + public static bool AskYesNo(string strText) + { + return AskYesNo(strText, null, true); + } + + public static void ShowLoadWarning(string strFilePath, Exception ex) + { + ShowLoadWarning(strFilePath, ex, false); + } + + 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); + } + + public static void ShowLoadWarning(IOConnectionInfo ioConnection, Exception ex) + { + if(ioConnection != null) + ShowLoadWarning(ioConnection.GetDisplayName(), ex, false); + else ShowWarning(ex); + } + + public static void ShowSaveWarning(string strFilePath, Exception ex, + bool bCorruptionWarning) + { + FileLockException fl = (ex as FileLockException); + if(fl != null) + { + ShowWarning(fl.Message); + 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; + + ShowWarning(str); + } + + public static void ShowSaveWarning(IOConnectionInfo ioConnection, Exception ex, + bool bCorruptionWarning) + { + if(ioConnection != null) + ShowSaveWarning(ioConnection.GetDisplayName(), ex, bCorruptionWarning); + else ShowWarning(ex); + } + + public static void ExternalIncrementMessageCount() + { + ++m_uCurrentMessageCount; + } + + public static void ExternalDecrementMessageCount() + { + --m_uCurrentMessageCount; + } + } +} diff --git a/src/KeePassLib2Android/Utility/StrUtil.cs b/src/KeePassLib2Android/Utility/StrUtil.cs new file mode 100644 index 00000000..94e590aa --- /dev/null +++ b/src/KeePassLib2Android/Utility/StrUtil.cs @@ -0,0 +1,1334 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Drawing; +using System.IO; +using System.Text.RegularExpressions; +using System.Security.Cryptography; + +using KeePassLib.Collections; +using KeePassLib.Native; +using KeePassLib.Security; +using KeePassLib.Resources; + +namespace KeePassLib.Utility +{ + /// + /// Character stream class. + /// + public sealed class CharStream + { + private string m_strString = string.Empty; + private int m_nPos = 0; + + public CharStream(string str) + { + Debug.Assert(str != null); + if(str == null) throw new ArgumentNullException("str"); + + m_strString = str; + } + + public void Seek(SeekOrigin org, int nSeek) + { + if(org == SeekOrigin.Begin) + m_nPos = nSeek; + else if(org == SeekOrigin.Current) + m_nPos += nSeek; + else if(org == SeekOrigin.End) + m_nPos = m_strString.Length + nSeek; + } + + public char ReadChar() + { + if(m_nPos < 0) return char.MinValue; + if(m_nPos >= m_strString.Length) return char.MinValue; + + char chRet = m_strString[m_nPos]; + ++m_nPos; + return chRet; + } + + public char ReadChar(bool bSkipWhiteSpace) + { + if(bSkipWhiteSpace == false) return ReadChar(); + + while(true) + { + char ch = ReadChar(); + + if((ch != ' ') && (ch != '\t') && (ch != '\r') && (ch != '\n')) + return ch; + } + } + + public char PeekChar() + { + if(m_nPos < 0) return char.MinValue; + if(m_nPos >= m_strString.Length) return char.MinValue; + + return m_strString[m_nPos]; + } + + public char PeekChar(bool bSkipWhiteSpace) + { + if(bSkipWhiteSpace == false) return PeekChar(); + + int iIndex = m_nPos; + while(true) + { + if(iIndex < 0) return char.MinValue; + if(iIndex >= m_strString.Length) return char.MinValue; + + char ch = m_strString[iIndex]; + + if((ch != ' ') && (ch != '\t') && (ch != '\r') && (ch != '\n')) + return ch; + + ++iIndex; + } + } + } + + public enum StrEncodingType + { + Unknown = 0, + Default, + Ascii, + Utf7, + Utf8, + Utf16LE, + Utf16BE, + Utf32LE, + Utf32BE + } + + public sealed class StrEncodingInfo + { + private readonly StrEncodingType m_type; + public StrEncodingType Type + { + get { return m_type; } + } + + private readonly string m_strName; + public string Name + { + get { return m_strName; } + } + + private readonly Encoding m_enc; + public Encoding Encoding + { + get { return m_enc; } + } + + private readonly uint m_cbCodePoint; + /// + /// Size of a character in bytes. + /// + public uint CodePointSize + { + get { return m_cbCodePoint; } + } + + private readonly byte[] m_vSig; + /// + /// Start signature of the text (byte order mark). + /// May be null or empty, if no signature is known. + /// + public byte[] StartSignature + { + get { return m_vSig; } + } + + public StrEncodingInfo(StrEncodingType t, string strName, Encoding enc, + uint cbCodePoint, byte[] vStartSig) + { + if(strName == null) throw new ArgumentNullException("strName"); + if(enc == null) throw new ArgumentNullException("enc"); + if(cbCodePoint <= 0) throw new ArgumentOutOfRangeException("cbCodePoint"); + + m_type = t; + m_strName = strName; + m_enc = enc; + m_cbCodePoint = cbCodePoint; + m_vSig = vStartSig; + } + } + + /// + /// A class containing various string helper methods. + /// + public static class StrUtil + { + public const StringComparison CaseIgnoreCmp = StringComparison.OrdinalIgnoreCase; + + public static StringComparer CaseIgnoreComparer + { + get { return StringComparer.OrdinalIgnoreCase; } + } + + private static bool m_bRtl = false; + public static bool RightToLeft + { + get { return m_bRtl; } + set { m_bRtl = value; } + } + + private static UTF8Encoding m_encUtf8 = null; + public static UTF8Encoding Utf8 + { + get + { + if(m_encUtf8 == null) m_encUtf8 = new UTF8Encoding(false, false); + return m_encUtf8; + } + } + + private static List m_lEncs = null; + public static IEnumerable Encodings + { + get + { + if(m_lEncs == null) + { + m_lEncs = new List(); + + m_lEncs.Add(new StrEncodingInfo(StrEncodingType.Default, +#if !KeePassLibSD + Encoding.Default.EncodingName, +#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 })); +#endif + } + + return m_lEncs; + } + } + + // public static string RtfPar + // { + // // get { return (m_bRtl ? "\\rtlpar " : "\\par "); } + // get { return "\\par "; } + // } + + // /// + // /// Convert a string into a valid RTF string. + // /// + // /// Any string. + // /// RTF-encoded string. + // public static string MakeRtfString(string str) + // { + // Debug.Assert(str != null); if(str == null) throw new ArgumentNullException("str"); + // str = str.Replace("\\", "\\\\"); + // str = str.Replace("\r", string.Empty); + // str = str.Replace("{", "\\{"); + // str = str.Replace("}", "\\}"); + // str = str.Replace("\n", StrUtil.RtfPar); + // StringBuilder sbEncoded = new StringBuilder(); + // for(int i = 0; i < str.Length; ++i) + // { + // char ch = str[i]; + // if((int)ch >= 256) + // { + // sbEncoded.Append("\\u"); + // sbEncoded.Append((int)ch); + // sbEncoded.Append('?'); + // } + // else sbEncoded.Append(ch); + // } + // return sbEncoded.ToString(); + // } + + /// + /// Convert a string into a valid HTML sequence representing that string. + /// + /// String to convert. + /// String, HTML-encoded. + public static string StringToHtml(string str) + { + Debug.Assert(str != null); if(str == null) throw new ArgumentNullException("str"); + + str = str.Replace(@"&", @"&"); + str = str.Replace(@"<", @"<"); + str = str.Replace(@">", @">"); + str = str.Replace("\"", @"""); + str = str.Replace("\'", @"'"); + + str = NormalizeNewLines(str, false); + str = str.Replace("\n", @"
"); + + return str; + } + + public static string XmlToString(string str) + { + Debug.Assert(str != null); if(str == null) throw new ArgumentNullException("str"); + + str = str.Replace(@"&", @"&"); + str = str.Replace(@"<", @"<"); + str = str.Replace(@">", @">"); + str = str.Replace(@""", "\""); + str = str.Replace(@"'", "\'"); + + return str; + } + + public static string ReplaceCaseInsensitive(string strString, string strFind, + string strNew) + { + Debug.Assert(strString != null); if(strString == null) return strString; + Debug.Assert(strFind != null); if(strFind == null) return strString; + Debug.Assert(strNew != null); if(strNew == null) return strString; + + string str = strString; + + int nPos = 0; + while(nPos < str.Length) + { + nPos = str.IndexOf(strFind, nPos, StringComparison.OrdinalIgnoreCase); + if(nPos < 0) break; + + str = str.Remove(nPos, strFind.Length); + str = str.Insert(nPos, strNew); + + nPos += strNew.Length; + } + + return str; + } + + /// + /// Split up a command-line into application and argument. + /// + /// Command-line to split. + /// Application path. + /// Arguments. + public static void SplitCommandLine(string strCmdLine, out string strApp, out string strArgs) + { + Debug.Assert(strCmdLine != null); if(strCmdLine == null) throw new ArgumentNullException("strCmdLine"); + + string str = strCmdLine.Trim(); + + strApp = null; strArgs = null; + + if(str.StartsWith("\"")) + { + int nSecond = str.IndexOf('\"', 1); + if(nSecond >= 1) + { + strApp = str.Substring(1, nSecond - 1).Trim(); + strArgs = str.Remove(0, nSecond + 1).Trim(); + } + } + + if(strApp == null) + { + int nSpace = str.IndexOf(' '); + + if(nSpace >= 0) + { + strApp = str.Substring(0, nSpace); + strArgs = str.Remove(0, nSpace).Trim(); + } + else strApp = strCmdLine; + } + + if(strApp == null) strApp = string.Empty; + if(strArgs == null) strArgs = string.Empty; + } + + // /// + // /// Initialize an RTF document based on given font face and size. + // /// + // /// StringBuilder to put the generated RTF into. + // /// Face name of the font to use. + // /// Size of the font to use. + // public static void InitRtf(StringBuilder sb, string strFontFace, float fFontSize) + // { + // Debug.Assert(sb != null); if(sb == null) throw new ArgumentNullException("sb"); + // Debug.Assert(strFontFace != null); if(strFontFace == null) throw new ArgumentNullException("strFontFace"); + // sb.Append("{\\rtf1"); + // if(m_bRtl) sb.Append("\\fbidis"); + // sb.Append("\\ansi\\ansicpg"); + // sb.Append(Encoding.Default.CodePage); + // sb.Append("\\deff0{\\fonttbl{\\f0\\fswiss MS Sans Serif;}{\\f1\\froman\\fcharset2 Symbol;}{\\f2\\fswiss "); + // sb.Append(strFontFace); + // sb.Append(";}{\\f3\\fswiss Arial;}}"); + // sb.Append("{\\colortbl\\red0\\green0\\blue0;}"); + // if(m_bRtl) sb.Append("\\rtldoc"); + // sb.Append("\\deflang1031\\pard\\plain\\f2\\cf0 "); + // sb.Append("\\fs"); + // sb.Append((int)(fFontSize * 2)); + // if(m_bRtl) sb.Append("\\rtlpar\\qr\\rtlch "); + // } + + // /// + // /// Convert a simple HTML string to an RTF string. + // /// + // /// Input HTML string. + // /// RTF string representing the HTML input string. + // public static string SimpleHtmlToRtf(string strHtmlString) + // { + // StringBuilder sb = new StringBuilder(); + // StrUtil.InitRtf(sb, "Microsoft Sans Serif", 8.25f); + // sb.Append(" "); + // string str = MakeRtfString(strHtmlString); + // str = str.Replace("", "\\b "); + // str = str.Replace("", "\\b0 "); + // str = str.Replace("", "\\i "); + // str = str.Replace("", "\\i0 "); + // str = str.Replace("", "\\ul "); + // str = str.Replace("", "\\ul0 "); + // str = str.Replace("
", StrUtil.RtfPar); + // sb.Append(str); + // return sb.ToString(); + // } + + /// + /// Convert a Color to a HTML color identifier string. + /// + /// Color to convert. + /// If this is true, an empty string + /// is returned if the color is transparent. + /// HTML color identifier string. + public static string ColorToUnnamedHtml(Color color, bool bEmptyIfTransparent) + { + if(bEmptyIfTransparent && (color.A != 255)) + return string.Empty; + + StringBuilder sb = new StringBuilder(); + byte bt; + + sb.Append('#'); + + bt = (byte)(color.R >> 4); + if(bt < 10) sb.Append((char)('0' + bt)); else sb.Append((char)('A' - 10 + bt)); + bt = (byte)(color.R & 0x0F); + if(bt < 10) sb.Append((char)('0' + bt)); else sb.Append((char)('A' - 10 + bt)); + + bt = (byte)(color.G >> 4); + if(bt < 10) sb.Append((char)('0' + bt)); else sb.Append((char)('A' - 10 + bt)); + bt = (byte)(color.G & 0x0F); + if(bt < 10) sb.Append((char)('0' + bt)); else sb.Append((char)('A' - 10 + bt)); + + bt = (byte)(color.B >> 4); + if(bt < 10) sb.Append((char)('0' + bt)); else sb.Append((char)('A' - 10 + bt)); + bt = (byte)(color.B & 0x0F); + if(bt < 10) sb.Append((char)('0' + bt)); else sb.Append((char)('A' - 10 + bt)); + + return sb.ToString(); + } + + /// + /// Format an exception and convert it to a string. + /// + /// Exception to convert/format. + /// String representing the exception. + public static string FormatException(Exception excp) + { + string strText = string.Empty; + + if(excp.Message != null) + strText += excp.Message + MessageService.NewLine; +#if !KeePassLibSD + if(excp.Source != null) + strText += excp.Source + MessageService.NewLine; +#endif + if(excp.StackTrace != null) + strText += excp.StackTrace + MessageService.NewLine; +#if !KeePassLibSD + if(excp.TargetSite != null) + strText += excp.TargetSite.ToString() + MessageService.NewLine; + + if(excp.Data != null) + { + strText += MessageService.NewLine; + foreach(DictionaryEntry de in excp.Data) + strText += @"'" + de.Key + @"' -> '" + de.Value + @"'" + + MessageService.NewLine; + } +#endif + + if(excp.InnerException != null) + { + strText += MessageService.NewLine + "Inner:" + MessageService.NewLine; + if(excp.InnerException.Message != null) + strText += excp.InnerException.Message + MessageService.NewLine; +#if !KeePassLibSD + if(excp.InnerException.Source != null) + strText += excp.InnerException.Source + MessageService.NewLine; +#endif + if(excp.InnerException.StackTrace != null) + strText += excp.InnerException.StackTrace + MessageService.NewLine; +#if !KeePassLibSD + if(excp.InnerException.TargetSite != null) + strText += excp.InnerException.TargetSite.ToString(); + + if(excp.InnerException.Data != null) + { + strText += MessageService.NewLine; + foreach(DictionaryEntry de in excp.InnerException.Data) + strText += @"'" + de.Key + @"' -> '" + de.Value + @"'" + + MessageService.NewLine; + } +#endif + } + + return strText; + } + + public static bool TryParseUShort(string str, out ushort u) + { +#if !KeePassLibSD + return ushort.TryParse(str, out u); +#else + try { u = ushort.Parse(str); return true; } + catch(Exception) { u = 0; return false; } +#endif + } + + public static bool TryParseInt(string str, out int n) + { +#if !KeePassLibSD + return int.TryParse(str, out n); +#else + try { n = int.Parse(str); return true; } + catch(Exception) { n = 0; return false; } +#endif + } + + public static bool TryParseUInt(string str, out uint u) + { +#if !KeePassLibSD + return uint.TryParse(str, out u); +#else + try { u = uint.Parse(str); return true; } + catch(Exception) { u = 0; return false; } +#endif + } + + public static bool TryParseLong(string str, out long n) + { +#if !KeePassLibSD + return long.TryParse(str, out n); +#else + try { n = long.Parse(str); return true; } + catch(Exception) { n = 0; return false; } +#endif + } + + public static bool TryParseULong(string str, out ulong u) + { +#if !KeePassLibSD + return ulong.TryParse(str, out u); +#else + try { u = ulong.Parse(str); return true; } + catch(Exception) { u = 0; return false; } +#endif + } + + public static bool TryParseDateTime(string str, out DateTime dt) + { +#if !KeePassLibSD + return DateTime.TryParse(str, out dt); +#else + try { dt = DateTime.Parse(str); return true; } + catch(Exception) { dt = DateTime.MinValue; return false; } +#endif + } + + public static string CompactString3Dots(string strText, int nMaxChars) + { + Debug.Assert(strText != null); + if(strText == null) throw new ArgumentNullException("strText"); + Debug.Assert(nMaxChars >= 0); + if(nMaxChars < 0) throw new ArgumentOutOfRangeException("nMaxChars"); + + if(nMaxChars == 0) return string.Empty; + if(strText.Length <= nMaxChars) return strText; + + if(nMaxChars <= 3) return strText.Substring(0, nMaxChars); + + return strText.Substring(0, nMaxChars - 3) + "..."; + } + + public static string GetStringBetween(string strText, int nStartIndex, + string strStart, string strEnd) + { + int nTemp; + return GetStringBetween(strText, nStartIndex, strStart, strEnd, out nTemp); + } + + public static string GetStringBetween(string strText, int nStartIndex, + string strStart, string strEnd, out int nInnerStartIndex) + { + if(strText == null) throw new ArgumentNullException("strText"); + if(strStart == null) throw new ArgumentNullException("strStart"); + if(strEnd == null) throw new ArgumentNullException("strEnd"); + + nInnerStartIndex = -1; + + int nIndex = strText.IndexOf(strStart, nStartIndex); + if(nIndex < 0) return string.Empty; + + nIndex += strStart.Length; + + int nEndIndex = strText.IndexOf(strEnd, nIndex); + if(nEndIndex < 0) return string.Empty; + + nInnerStartIndex = nIndex; + return strText.Substring(nIndex, nEndIndex - nIndex); + } + + /// + /// Removes all characters that are not valid XML characters, + /// according to http://www.w3.org/TR/xml/#charsets . + /// + /// Source text. + /// Text containing only valid XML characters. + public static string SafeXmlString(string strText) + { + 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; + + for(int i = 0; i < vChars.Length; ++i) + { + ch = vChars[i]; + + if(((ch >= 0x20) && (ch <= 0xD7FF)) || + (ch == 0x9) || (ch == 0xA) || (ch == 0xD) || + ((ch >= 0xE000) && (ch <= 0xFFFD))) + sb.Append(ch); + // Range ((ch >= 0x10000) && (ch <= 0x10FFFF)) excluded + } + + return sb.ToString(); + } + + private static Regex m_rxNaturalSplit = null; + 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); + + strX = strX.ToLower(); // Case-insensitive comparison + strY = strY.ToLower(); + + if(m_rxNaturalSplit == null) + m_rxNaturalSplit = new Regex(@"([0-9]+)", RegexOptions.Compiled); + + string[] vPartsX = m_rxNaturalSplit.Split(strX); + string[] vPartsY = m_rxNaturalSplit.Split(strY); + + for(int i = 0; i < Math.Min(vPartsX.Length, vPartsY.Length); ++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); + iPartCompare = uX.CompareTo(uY); + } + catch(Exception) { iPartCompare = strPartX.CompareTo(strPartY); } +#else + ulong uX, uY; + if(ulong.TryParse(strPartX, out uX) && ulong.TryParse(strPartY, out uY)) + iPartCompare = uX.CompareTo(uY); + else iPartCompare = strPartX.CompareTo(strPartY); +#endif + + if(iPartCompare != 0) return iPartCompare; + } + + if(vPartsX.Length == vPartsY.Length) return 0; + if(vPartsX.Length < vPartsY.Length) return -1; + return 1; + } + + public static string RemoveAccelerator(string strMenuText) + { + if(strMenuText == null) throw new ArgumentNullException("strMenuText"); + + string str = strMenuText; + + for(char ch = 'A'; ch <= 'Z'; ++ch) + { + string strEnhAcc = @"(&" + ch.ToString() + @")"; + if(str.IndexOf(strEnhAcc) >= 0) + { + str = str.Replace(@" " + strEnhAcc, string.Empty); + str = str.Replace(strEnhAcc, string.Empty); + } + } + + str = str.Replace(@"&", string.Empty); + + return str; + } + + 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(bStrict) return false; + + if((ch == ' ') || (ch == '\t') || (ch == '\r') || (ch == '\n')) + continue; + + return false; + } + + return true; + } + +#if !KeePassLibSD + private static readonly char[] m_vPatternPartsSep = new char[]{ '*' }; + public static bool SimplePatternMatch(string strPattern, string strText, + StringComparison sc) + { + if(strPattern == null) throw new ArgumentNullException("strPattern"); + if(strText == null) throw new ArgumentNullException("strText"); + + if(strPattern.IndexOf('*') < 0) return strText.Equals(strPattern, sc); + + string[] vPatternParts = strPattern.Split(m_vPatternPartsSep, + StringSplitOptions.RemoveEmptyEntries); + if(vPatternParts == null) { Debug.Assert(false); return true; } + if(vPatternParts.Length == 0) return true; + + if(strText.Length == 0) return false; + + if(!strPattern.StartsWith(@"*") && !strText.StartsWith(vPatternParts[0], sc)) + { + return false; + } + + if(!strPattern.EndsWith(@"*") && !strText.EndsWith(vPatternParts[ + vPatternParts.Length - 1], sc)) + { + return false; + } + + int iOffset = 0; + for(int i = 0; i < vPatternParts.Length; ++i) + { + string strPart = vPatternParts[i]; + + int iFound = strText.IndexOf(strPart, iOffset, sc); + if(iFound < iOffset) return false; + + iOffset = iFound + strPart.Length; + if(iOffset == strText.Length) return (i == (vPatternParts.Length - 1)); + } + + return true; + } +#endif // !KeePassLibSD + + public static bool StringToBool(string str) + { + if(string.IsNullOrEmpty(str)) return false; // No assert + + string s = str.Trim().ToLower(); + if(s == "true") return true; + if(s == "yes") return true; + if(s == "1") return true; + if(s == "enabled") return true; + if(s == "checked") return true; + + return false; + } + + public static bool? StringToBoolEx(string str) + { + if(string.IsNullOrEmpty(str)) return null; + + string s = str.Trim().ToLower(); + if(s == "true") return true; + if(s == "false") return false; + + return null; + } + + public static string BoolToString(bool bValue) + { + return (bValue ? "true" : "false"); + } + + public static string BoolToStringEx(bool? bValue) + { + if(bValue.HasValue) return BoolToString(bValue.Value); + return "null"; + } + + /// + /// Normalize new line characters in a string. Input strings may + /// contain mixed new line character sequences from all commonly + /// used operating systems (i.e. \r\n from Windows, \n from Unix + /// and \r from Mac OS. + /// + /// String with mixed new line characters. + /// If true, new line characters + /// are normalized for Windows (\r\n); if false, new line + /// characters are normalized for Unix (\n). + /// String with normalized new line characters. + public static string NormalizeNewLines(string str, bool bWindows) + { + if(string.IsNullOrEmpty(str)) return str; + + str = str.Replace("\r\n", "\n"); + str = str.Replace("\r", "\n"); + + if(bWindows) str = str.Replace("\n", "\r\n"); + + return str; + } + + private static char[] m_vNewLineChars = null; + public static void NormalizeNewLines(ProtectedStringDictionary dict, + bool bWindows) + { + if(dict == null) { Debug.Assert(false); return; } + + if(m_vNewLineChars == null) + m_vNewLineChars = new char[]{ '\r', '\n' }; + + List vKeys = dict.GetKeys(); + foreach(string strKey in vKeys) + { + ProtectedString ps = dict.Get(strKey); + if(ps == null) { Debug.Assert(false); continue; } + + string strValue = ps.ReadString(); + if(strValue.IndexOfAny(m_vNewLineChars) < 0) continue; + + dict.Set(strKey, new ProtectedString(ps.IsProtected, + NormalizeNewLines(strValue, bWindows))); + } + } + + public static string AlphaNumericOnly(string str) + { + if(string.IsNullOrEmpty(str)) return str; + + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < str.Length; ++i) + { + char ch = str[i]; + if(((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')) || + ((ch >= '0') && (ch <= '9'))) + sb.Append(ch); + } + + return sb.ToString(); + } + + public static string FormatDataSize(ulong uBytes) + { + const ulong uKB = 1024; + const ulong uMB = uKB * uKB; + const ulong uGB = uMB * uKB; + const ulong uTB = uGB * uKB; + + if(uBytes == 0) return "0 KB"; + if(uBytes <= uKB) return "1 KB"; + if(uBytes <= uMB) return (((uBytes - 1UL) / uKB) + 1UL).ToString() + " KB"; + if(uBytes <= uGB) return (((uBytes - 1UL) / uMB) + 1UL).ToString() + " MB"; + if(uBytes <= uTB) return (((uBytes - 1UL) / uGB) + 1UL).ToString() + " GB"; + + return (((uBytes - 1UL)/ uTB) + 1UL).ToString() + " TB"; + } + + public static string FormatDataSizeKB(ulong uBytes) + { + const ulong uKB = 1024; + + if(uBytes == 0) return "0 KB"; + if(uBytes <= uKB) return "1 KB"; + + return (((uBytes - 1UL) / uKB) + 1UL).ToString() + " KB"; + } + + private static readonly char[] m_vVersionSep = new char[]{ '.', ',' }; + public static ulong ParseVersion(string strVersion) + { + if(strVersion == null) { Debug.Assert(false); return 0; } + + string[] vVer = strVersion.Split(m_vVersionSep); + if((vVer == null) || (vVer.Length == 0)) { Debug.Assert(false); return 0; } + + ushort uPart; + StrUtil.TryParseUShort(vVer[0].Trim(), out uPart); + ulong uVer = ((ulong)uPart << 48); + + if(vVer.Length >= 2) + { + StrUtil.TryParseUShort(vVer[1].Trim(), out uPart); + uVer |= ((ulong)uPart << 32); + } + + if(vVer.Length >= 3) + { + StrUtil.TryParseUShort(vVer[2].Trim(), out uPart); + uVer |= ((ulong)uPart << 16); + } + + if(vVer.Length >= 4) + { + StrUtil.TryParseUShort(vVer[3].Trim(), out uPart); + uVer |= (ulong)uPart; + } + + return uVer; + } + + public static string VersionToString(ulong uVersion) + { + return VersionToString(uVersion, false); + } + + public static string VersionToString(ulong uVersion, + bool bEnsureAtLeastTwoComp) + { + string str = string.Empty; + bool bMultiComp = false; + + for(int i = 0; i < 4; ++i) + { + ushort us = (ushort)(uVersion & 0xFFFFUL); + + if((us != 0) || (str.Length > 0)) + { + if(str.Length > 0) + { + str = "." + str; + bMultiComp = true; + } + + str = us.ToString() + str; + } + + uVersion >>= 16; + } + + if(bEnsureAtLeastTwoComp && !bMultiComp && (str.Length > 0)) + str += ".0"; + + return str; + } + + private static readonly byte[] m_pbOptEnt = { 0xA5, 0x74, 0x2E, 0xEC }; + + public static string EncryptString(string strPlainText) + { + if(string.IsNullOrEmpty(strPlainText)) return string.Empty; + + try + { + byte[] pbPlain = StrUtil.Utf8.GetBytes(strPlainText); + byte[] pbEnc = ProtectedData.Protect(pbPlain, m_pbOptEnt, + DataProtectionScope.CurrentUser); + +#if !KeePassLibSD + return Convert.ToBase64String(pbEnc, Base64FormattingOptions.None); +#else + return Convert.ToBase64String(pbEnc); +#endif + } + catch(Exception) { Debug.Assert(false); } + + return strPlainText; + } + + public static string DecryptString(string strCipherText) + { + if(string.IsNullOrEmpty(strCipherText)) return string.Empty; + + try + { + byte[] pbEnc = Convert.FromBase64String(strCipherText); + byte[] pbPlain = ProtectedData.Unprotect(pbEnc, m_pbOptEnt, + DataProtectionScope.CurrentUser); + + return StrUtil.Utf8.GetString(pbPlain, 0, pbPlain.Length); + } + catch(Exception) { Debug.Assert(false); } + + return strCipherText; + } + + public static string SerializeIntArray(int[] vNumbers) + { + if(vNumbers == null) throw new ArgumentNullException("vNumbers"); + + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < vNumbers.Length; ++i) + { + if(i > 0) sb.Append(' '); + sb.Append(vNumbers[i]); + } + + return sb.ToString(); + } + + public static int[] DeserializeIntArray(string strSerialized) + { + if(strSerialized == null) throw new ArgumentNullException("strSerialized"); + if(strSerialized.Length == 0) return new int[0]; + + string[] vParts = strSerialized.Split(' '); + int[] v = new int[vParts.Length]; + + for(int i = 0; i < vParts.Length; ++i) + { + int n; + if(!TryParseInt(vParts[i], out n)) { Debug.Assert(false); } + v[i] = n; + } + + return v; + } + + private static readonly char[] m_vTagSep = new char[]{ ',', ';', ':' }; + public static string TagsToString(List vTags, bool bForDisplay) + { + if(vTags == null) throw new ArgumentNullException("vTags"); + + StringBuilder sb = new StringBuilder(); + bool bFirst = true; + + foreach(string strTag in vTags) + { + if(string.IsNullOrEmpty(strTag)) { Debug.Assert(false); continue; } + Debug.Assert(strTag.IndexOfAny(m_vTagSep) < 0); + + if(!bFirst) + { + if(bForDisplay) sb.Append(", "); + else sb.Append(';'); + } + sb.Append(strTag); + + bFirst = false; + } + + return sb.ToString(); + } + + public static List StringToTags(string strTags) + { + if(strTags == null) throw new ArgumentNullException("strTags"); + + List lTags = new List(); + if(strTags.Length == 0) return lTags; + + string[] vTags = strTags.Split(m_vTagSep); + foreach(string strTag in vTags) + { + string strFlt = strTag.Trim(); + if(strFlt.Length > 0) lTags.Add(strFlt); + } + + return lTags; + } + + public static string Obfuscate(string strPlain) + { + if(strPlain == null) { Debug.Assert(false); return string.Empty; } + if(strPlain.Length == 0) return string.Empty; + + byte[] pb = StrUtil.Utf8.GetBytes(strPlain); + + Array.Reverse(pb); + for(int i = 0; i < pb.Length; ++i) pb[i] = (byte)(pb[i] ^ 0x65); + +#if !KeePassLibSD + return Convert.ToBase64String(pb, Base64FormattingOptions.None); +#else + return Convert.ToBase64String(pb); +#endif + } + + public static string Deobfuscate(string strObf) + { + if(strObf == null) { Debug.Assert(false); return string.Empty; } + if(strObf.Length == 0) return string.Empty; + + try + { + byte[] pb = Convert.FromBase64String(strObf); + + for(int i = 0; i < pb.Length; ++i) pb[i] = (byte)(pb[i] ^ 0x65); + Array.Reverse(pb); + + return StrUtil.Utf8.GetString(pb, 0, pb.Length); + } + catch(Exception) { Debug.Assert(false); } + + return string.Empty; + } + + /// + /// Split a string and include the separators in the splitted array. + /// + /// String to split. + /// Separators. + /// Specifies whether separators are + /// matched case-sensitively or not. + /// Splitted string including separators. + public static List SplitWithSep(string str, string[] vSeps, + bool bCaseSensitive) + { + if(str == null) throw new ArgumentNullException("str"); + if(vSeps == null) throw new ArgumentNullException("vSeps"); + + List v = new List(); + while(true) + { + int minIndex = int.MaxValue, minSep = -1; + for(int i = 0; i < vSeps.Length; ++i) + { + string strSep = vSeps[i]; + if(string.IsNullOrEmpty(strSep)) { Debug.Assert(false); continue; } + + int iIndex = (bCaseSensitive ? str.IndexOf(strSep) : + str.IndexOf(strSep, StrUtil.CaseIgnoreCmp)); + if((iIndex >= 0) && (iIndex < minIndex)) + { + minIndex = iIndex; + minSep = i; + } + } + + if(minIndex == int.MaxValue) break; + + v.Add(str.Substring(0, minIndex)); + v.Add(vSeps[minSep]); + + str = str.Substring(minIndex + vSeps[minSep].Length); + } + + v.Add(str); + return v; + } + + public static string MultiToSingleLine(string strMulti) + { + if(strMulti == null) { Debug.Assert(false); return string.Empty; } + if(strMulti.Length == 0) return string.Empty; + + string str = strMulti; + str = str.Replace("\r\n", " "); + str = str.Replace("\r", " "); + str = str.Replace("\n", " "); + + return str; + } + + public static List SplitSearchTerms(string strSearch) + { + List l = new List(); + if(strSearch == null) { Debug.Assert(false); return l; } + + StringBuilder sbTerm = new StringBuilder(); + bool bQuoted = false; + + for(int i = 0; i < strSearch.Length; ++i) + { + char ch = strSearch[i]; + + if(((ch == ' ') || (ch == '\t') || (ch == '\r') || + (ch == '\n')) && !bQuoted) + { + if(sbTerm.Length > 0) l.Add(sbTerm.ToString()); + + sbTerm.Remove(0, sbTerm.Length); + } + else if(ch == '\"') bQuoted = !bQuoted; + else sbTerm.Append(ch); + } + if(sbTerm.Length > 0) l.Add(sbTerm.ToString()); + + return l; + } + + public static int CompareLengthGt(string x, string y) + { + if(x.Length == y.Length) return 0; + return ((x.Length > y.Length) ? -1 : 1); + } + + public static bool IsDataUri(string strUri) + { + if(strUri == null) { Debug.Assert(false); return false; } + + return strUri.StartsWith("data:", StrUtil.CaseIgnoreCmp); + } + + /// + /// Create a data URI (according to RFC 2397). + /// + /// Data to encode. + /// Optional MIME type. If null, + /// an appropriate type is used. + /// Data URI. + public static string DataToDataUri(byte[] pbData, string strMimeType) + { + if(pbData == null) throw new ArgumentNullException("pbData"); + + if(strMimeType == null) strMimeType = "application/octet-stream"; + +#if !KeePassLibSD + return ("data:" + strMimeType + ";base64," + Convert.ToBase64String( + pbData, Base64FormattingOptions.None)); +#else + return ("data:" + strMimeType + ";base64," + Convert.ToBase64String( + pbData)); +#endif + } + + /// + /// Convert a data URI (according to RFC 2397) to binary data. + /// + /// Data URI to decode. + /// Decoded binary data. + public static byte[] DataUriToData(string strDataUri) + { + if(strDataUri == null) throw new ArgumentNullException("strDataUri"); + if(!strDataUri.StartsWith("data:", StrUtil.CaseIgnoreCmp)) return null; + + int iSep = strDataUri.IndexOf(','); + if(iSep < 0) return null; + + string strDesc = strDataUri.Substring(5, iSep - 5); + bool bBase64 = strDesc.EndsWith(";base64", StrUtil.CaseIgnoreCmp); + + string strData = strDataUri.Substring(iSep + 1); + + if(bBase64) return Convert.FromBase64String(strData); + + MemoryStream ms = new MemoryStream(); + + string[] v = strData.Split('%'); + byte[] pb = Encoding.ASCII.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)); + ms.Write(pb, 0, pb.Length); + } + + pb = ms.ToArray(); + ms.Close(); + return pb; + } + + /// + /// Remove placeholders from a string (wrapped in '{' and '}'). + /// This doesn't remove environment variables (wrapped in '%'). + /// + public static string RemovePlaceholders(string str) + { + if(str == null) { Debug.Assert(false); return string.Empty; } + + while(true) + { + int iPlhStart = str.IndexOf('{'); + if(iPlhStart < 0) break; + + int iPlhEnd = str.IndexOf('}', iPlhStart); // '{' might be at end + if(iPlhEnd < 0) break; + + str = (str.Substring(0, iPlhStart) + str.Substring(iPlhEnd + 1)); + } + + return str; + } + + public static StrEncodingInfo GetEncoding(StrEncodingType t) + { + foreach(StrEncodingInfo sei in StrUtil.Encodings) + { + if(sei.Type == t) return sei; + } + + return null; + } + + public static StrEncodingInfo GetEncoding(string strName) + { + foreach(StrEncodingInfo sei in StrUtil.Encodings) + { + if(sei.Name == strName) return sei; + } + + return null; + } + } +} diff --git a/src/KeePassLib2Android/Utility/TimeUtil.cs b/src/KeePassLib2Android/Utility/TimeUtil.cs new file mode 100644 index 00000000..94aa4cd2 --- /dev/null +++ b/src/KeePassLib2Android/Utility/TimeUtil.cs @@ -0,0 +1,222 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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.Diagnostics; + +namespace KeePassLib.Utility +{ + /// + /// Contains various static time structure manipulation and conversion + /// routines. + /// + public static class TimeUtil + { + /// + /// Length of a compressed PW_TIME structure in bytes. + /// + public const int PwTimeLength = 7; + + /// + /// Pack a DateTime object into 5 bytes. Layout: 2 zero bits, + /// year 12 bits, month 4 bits, day 5 bits, hour 5 bits, minute 6 + /// bits, second 6 bits. + /// + /// + /// + public static byte[] PackTime(DateTime dt) + { + byte[] pb = new byte[5]; + + // Pack time to 5 byte structure: + // Byte bits: 11111111 22222222 33333333 44444444 55555555 + // Contents : 00YYYYYY YYYYYYMM MMDDDDDH HHHHMMMM MMSSSSSS + pb[0] = (byte)((dt.Year >> 6) & 0x3F); + pb[1] = (byte)(((dt.Year & 0x3F) << 2) | ((dt.Month >> 2) & 0x03)); + pb[2] = (byte)(((dt.Month & 0x03) << 6) | ((dt.Day & 0x1F) << 1) | + ((dt.Hour >> 4) & 0x01)); + pb[3] = (byte)(((dt.Hour & 0x0F) << 4) | ((dt.Minute >> 2) & 0x0F)); + pb[4] = (byte)(((dt.Minute & 0x03) << 6) | (dt.Second & 0x3F)); + + return pb; + } + + /// + /// Unpack a packed time (5 bytes, packed by the PackTime + /// member function) to a DateTime object. + /// + /// Packed time, 5 bytes. + /// Unpacked DateTime object. + public static DateTime UnpackTime(byte[] pb) + { + Debug.Assert((pb != null) && (pb.Length == 5)); + if(pb == null) throw new ArgumentNullException("pb"); + if(pb.Length != 5) throw new ArgumentException(); + + int n1 = pb[0], n2 = pb[1], n3 = pb[2], n4 = pb[3], n5 = pb[4]; + + // Unpack 5 byte structure to date and time + int nYear = (n1 << 6) | (n2 >> 2); + int nMonth = ((n2 & 0x00000003) << 2) | (n3 >> 6); + int nDay = (n3 >> 1) & 0x0000001F; + int nHour = ((n3 & 0x00000001) << 4) | (n4 >> 4); + int nMinute = ((n4 & 0x0000000F) << 2) | (n5 >> 6); + int nSecond = n5 & 0x0000003F; + + return new DateTime(nYear, nMonth, nDay, nHour, nMinute, nSecond); + } + + /// + /// Pack a DateTime object into 7 bytes (PW_TIME). + /// + /// Object to be encoded. + /// Packed time, 7 bytes (PW_TIME). + public static byte[] PackPwTime(DateTime dt) + { + Debug.Assert(PwTimeLength == 7); + + byte[] pb = new byte[7]; + pb[0] = (byte)(dt.Year & 0xFF); + pb[1] = (byte)(dt.Year >> 8); + pb[2] = (byte)dt.Month; + pb[3] = (byte)dt.Day; + pb[4] = (byte)dt.Hour; + pb[5] = (byte)dt.Minute; + pb[6] = (byte)dt.Second; + + return pb; + } + + /// + /// Unpack a packed time (7 bytes, PW_TIME) to a DateTime object. + /// + /// Packed time, 7 bytes. + /// Unpacked DateTime object. + public static DateTime UnpackPwTime(byte[] pb) + { + Debug.Assert(PwTimeLength == 7); + + Debug.Assert(pb != null); if(pb == null) throw new ArgumentNullException("pb"); + Debug.Assert(pb.Length == 7); if(pb.Length != 7) throw new ArgumentException(); + + return new DateTime(((int)pb[1] << 8) | (int)pb[0], (int)pb[2], (int)pb[3], + (int)pb[4], (int)pb[5], (int)pb[6]); + } + + /// + /// Convert a DateTime object to a displayable string. + /// + /// DateTime object to convert to a string. + /// String representing the specified DateTime object. + public static string ToDisplayString(DateTime dt) + { + return dt.ToString(); + } + + public static string ToDisplayStringDateOnly(DateTime dt) + { + return dt.ToString("d"); + } + + public static DateTime FromDisplayString(string strDisplay) + { + DateTime dt; + +#if !KeePassLibSD + if(DateTime.TryParse(strDisplay, out dt)) return dt; +#else + try { dt = DateTime.Parse(strDisplay); return dt; } + catch(Exception) { } +#endif + + Debug.Assert(false); + return DateTime.Now; + } + + public static string SerializeUtc(DateTime dt) + { + string str = dt.ToUniversalTime().ToString("s"); + if(str.EndsWith("Z") == false) str += "Z"; + return str; + } + + public static bool TryDeserializeUtc(string str, out DateTime dt) + { + if(str == null) throw new ArgumentNullException("str"); + + if(str.EndsWith("Z")) str = str.Substring(0, str.Length - 1); + + bool bResult = StrUtil.TryParseDateTime(str, out dt); + if(bResult) dt = dt.ToLocalTime(); + return bResult; + } + + private static DateTime? m_dtUnixRoot = null; + 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); + } + catch(Exception) { Debug.Assert(false); } + + return DateTime.Now; + } + +#if !KeePassLibSD + private static string[] m_vUSMonths = null; + /// + /// Parse a US textual date string, like e.g. "January 02, 2012". + /// + public static DateTime? ParseUSTextDate(string strDate) + { + if(strDate == null) { Debug.Assert(false); return null; } + + if(m_vUSMonths == null) + m_vUSMonths = new string[]{ "January", "February", "March", + "April", "May", "June", "July", "August", "September", + "October", "November", "December" }; + + string str = strDate.Trim(); + for(int i = 0; i < m_vUSMonths.Length; ++i) + { + if(str.StartsWith(m_vUSMonths[i], StrUtil.CaseIgnoreCmp)) + { + str = str.Substring(m_vUSMonths[i].Length); + string[] v = str.Split(new char[]{ ',', ';' }); + if((v == null) || (v.Length != 2)) return null; + + string strDay = v[0].Trim().TrimStart('0'); + int iDay, iYear; + if(int.TryParse(strDay, out iDay) && + int.TryParse(v[1].Trim(), out iYear)) + return new DateTime(iYear, i + 1, iDay); + else { Debug.Assert(false); return null; } + } + } + + return null; + } +#endif + } +} diff --git a/src/KeePassLib2Android/Utility/UrlUtil.cs b/src/KeePassLib2Android/Utility/UrlUtil.cs new file mode 100644 index 00000000..d9c903a4 --- /dev/null +++ b/src/KeePassLib2Android/Utility/UrlUtil.cs @@ -0,0 +1,609 @@ +/* + KeePass Password Safe - The Open-Source Password Manager + Copyright (C) 2003-2012 Dominik Reichl + + This 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.IO; +using System.Runtime.InteropServices; +using System.Text; +using System.Diagnostics; + +using KeePassLib.Native; + +namespace KeePassLib.Utility +{ + /// + /// A class containing various static path utility helper methods (like + /// stripping extension from a file, etc.). + /// + public static class UrlUtil + { + private static readonly char[] m_vDirSeps = new char[] { '\\', '/', + Path.DirectorySeparatorChar }; + + /// + /// Get the directory (path) of a file name. The returned string is + /// terminated by a directory separator character. Example: + /// passing C:\\My Documents\\My File.kdb in + /// would produce this string: C:\\My Documents\\. + /// + /// Full path of a file. + /// Append a terminating directory separator + /// character to the returned path. + /// If true, the returned path + /// is guaranteed to be a valid directory path (for example X:\\ instead + /// 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. + public static string GetFileDirectory(string strFile, bool bAppendTerminatingChar, + bool bEnsureValidDirSpec) + { + Debug.Assert(strFile != null); + if(strFile == null) throw new ArgumentNullException("strFile"); + + int nLastSep = strFile.LastIndexOfAny(m_vDirSeps); + if(nLastSep < 0) return strFile; // None + + 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); + } + + /// + /// Gets the file name of the specified file (full path). Example: + /// if is C:\\My Documents\\My File.kdb + /// the returned string is My File.kdb. + /// + /// Full path of a file. + /// File name of the specified file. The return value is + /// an empty string ("") if the input parameter is null. + public static string GetFileName(string strPath) + { + Debug.Assert(strPath != null); if(strPath == null) throw new ArgumentNullException("strPath"); + + int nLastSep = strPath.LastIndexOfAny(m_vDirSeps); + + if(nLastSep < 0) return strPath; + if(nLastSep >= (strPath.Length - 1)) return string.Empty; + + return strPath.Substring(nLastSep + 1); + } + + /// + /// Strip the extension of a file. + /// + /// Full path of a file with extension. + /// File name without extension. + public static string StripExtension(string strPath) + { + Debug.Assert(strPath != null); if(strPath == null) throw new ArgumentNullException("strPath"); + + int nLastDirSep = strPath.LastIndexOfAny(m_vDirSeps); + int nLastExtDot = strPath.LastIndexOf('.'); + + if(nLastExtDot <= nLastDirSep) return strPath; + + return strPath.Substring(0, nLastExtDot); + } + + /// + /// Get the extension of a file. + /// + /// Full path of a file with extension. + /// Extension without prepending dot. + public static string GetExtension(string strPath) + { + Debug.Assert(strPath != null); if(strPath == null) throw new ArgumentNullException("strPath"); + + int nLastDirSep = strPath.LastIndexOfAny(m_vDirSeps); + int nLastExtDot = strPath.LastIndexOf('.'); + + if(nLastExtDot <= nLastDirSep) return string.Empty; + if(nLastExtDot == (strPath.Length - 1)) return string.Empty; + + return strPath.Substring(nLastExtDot + 1); + } + + /// + /// Ensure that a path is terminated with a directory separator character. + /// + /// Input path. + /// If true, a slash (/) is appended to + /// the string if it's not terminated already. If false, the + /// default system directory separator character is used. + /// Path having a directory separator as last character. + public static string EnsureTerminatingSeparator(string strPath, bool bUrl) + { + Debug.Assert(strPath != null); if(strPath == null) throw new ArgumentNullException("strPath"); + + int nLength = strPath.Length; + if(nLength <= 0) return string.Empty; + + char chLast = strPath[nLength - 1]; + + for(int i = 0; i < m_vDirSeps.Length; ++i) + { + if(chLast == m_vDirSeps[i]) return strPath; + } + + if(bUrl) return (strPath + '/'); + return (strPath + Path.DirectorySeparatorChar); + } + + /* /// + /// File access mode enumeration. Used by the FileAccessible + /// method. + /// + public enum FileAccessMode + { + /// + /// Opening a file in read mode. The specified file must exist. + /// + Read = 0, + + /// + /// Opening a file in create mode. If the file exists already, it + /// will be overwritten. If it doesn't exist, it will be created. + /// The return value is true, if data can be written to the + /// file. + /// + Create + } */ + + /* /// + /// Test if a specified path is accessible, either in read or write mode. + /// + /// Path to test. + /// Requested file access mode. + /// Returns true if the specified path is accessible in + /// the requested mode, otherwise the return value is false. + public static bool FileAccessible(string strFilePath, FileAccessMode fMode) + { + Debug.Assert(strFilePath != null); + if(strFilePath == null) throw new ArgumentNullException("strFilePath"); + + if(fMode == FileAccessMode.Read) + { + FileStream fs; + + try { fs = File.OpenRead(strFilePath); } + catch(Exception) { return false; } + if(fs == null) return false; + + fs.Close(); + return true; + } + else if(fMode == FileAccessMode.Create) + { + FileStream fs; + + try { fs = File.Create(strFilePath); } + catch(Exception) { return false; } + if(fs == null) return false; + + fs.Close(); + return true; + } + + return false; + } */ + + public static string GetQuotedAppPath(string strPath) + { + 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; + } + + public static string FileUrlToPath(string strUrl) + { + Debug.Assert(strUrl != null); + if(strUrl == null) throw new ArgumentNullException("strUrl"); + + string str = strUrl; + if(str.StartsWith(@"file:///", StrUtil.CaseIgnoreCmp)) + str = str.Substring(8, str.Length - 8); + + str = str.Replace('/', Path.DirectorySeparatorChar); + + return str; + } + + public static bool UnhideFile(string strFile) + { +#if KeePassLibSD + return false; +#else + if(strFile == null) throw new ArgumentNullException("strFile"); + + try + { + FileAttributes fa = File.GetAttributes(strFile); + if((long)(fa & FileAttributes.Hidden) == 0) return false; + + return HideFile(strFile, false); + } + catch(Exception) { } + + return false; +#endif + } + + public static bool HideFile(string strFile, bool bHide) + { +#if KeePassLibSD + return false; +#else + if(strFile == null) throw new ArgumentNullException("strFile"); + + try + { + FileAttributes fa = File.GetAttributes(strFile); + + if(bHide) fa = ((fa & ~FileAttributes.Normal) | FileAttributes.Hidden); + else // Unhide + { + fa &= ~FileAttributes.Hidden; + if((long)fa == 0) fa |= FileAttributes.Normal; + } + + File.SetAttributes(strFile, fa); + return true; + } + catch(Exception) { } + + return false; +#endif + } + + public static string MakeRelativePath(string strBaseFile, string strTargetFile) + { + if(strBaseFile == null) throw new ArgumentNullException("strBasePath"); + if(strTargetFile == null) throw new ArgumentNullException("strTargetPath"); + if(strBaseFile.Length == 0) return strTargetFile; + if(strTargetFile.Length == 0) return string.Empty; + + // Test whether on different Windows drives + if((strBaseFile.Length >= 3) && (strTargetFile.Length >= 3)) + { + if((strBaseFile[1] == ':') && (strTargetFile[1] == ':') && + (strBaseFile[2] == '\\') && (strTargetFile[2] == '\\') && + (strBaseFile[0] != strTargetFile[0])) + return strTargetFile; + } + + if(NativeLib.IsUnix()) + { + bool bBaseUnc = IsUncPath(strBaseFile); + bool bTargetUnc = IsUncPath(strTargetFile); + if((!bBaseUnc && bTargetUnc) || (bBaseUnc && !bTargetUnc)) + return strTargetFile; + + string strBase = GetShortestAbsolutePath(strBaseFile); + string strTarget = GetShortestAbsolutePath(strTargetFile); + string[] vBase = strBase.Split(m_vDirSeps); + string[] vTarget = strTarget.Split(m_vDirSeps); + + int i = 0; + while((i < (vBase.Length - 1)) && (i < (vTarget.Length - 1)) && + (vBase[i] == vTarget[i])) { ++i; } + + StringBuilder sbRel = new StringBuilder(); + for(int j = i; j < (vBase.Length - 1); ++j) + { + if(sbRel.Length > 0) sbRel.Append(Path.DirectorySeparatorChar); + sbRel.Append(".."); + } + for(int k = i; k < vTarget.Length; ++k) + { + if(sbRel.Length > 0) sbRel.Append(Path.DirectorySeparatorChar); + sbRel.Append(vTarget[k]); + } + + return sbRel.ToString(); + } + +#if KeePassLibSD + return strTargetFile; +#else + try // Windows + { + const int nMaxPath = NativeMethods.MAX_PATH * 2; + StringBuilder sb = new StringBuilder(nMaxPath + 2); + if(NativeMethods.PathRelativePathTo(sb, strBaseFile, 0, + strTargetFile, 0) == false) + return strTargetFile; + + string str = sb.ToString(); + while(str.StartsWith(".\\")) str = str.Substring(2, str.Length - 2); + + return str; + } + catch(Exception) { Debug.Assert(false); return strTargetFile; } +#endif + } + + public static string MakeAbsolutePath(string strBaseFile, string strTargetFile) + { + if(strBaseFile == null) throw new ArgumentNullException("strBasePath"); + if(strTargetFile == null) throw new ArgumentNullException("strTargetPath"); + if(strBaseFile.Length == 0) return strTargetFile; + if(strTargetFile.Length == 0) return string.Empty; + + if(IsAbsolutePath(strTargetFile)) return strTargetFile; + + string strBaseDir = GetFileDirectory(strBaseFile, true, false); + return GetShortestAbsolutePath(strBaseDir + strTargetFile); + } + + public static bool IsAbsolutePath(string strPath) + { + if(strPath == null) throw new ArgumentNullException("strPath"); + if(strPath.Length == 0) return false; + + if(IsUncPath(strPath)) return true; + + try { return Path.IsPathRooted(strPath); } + catch(Exception) { Debug.Assert(false); } + + return true; + } + + public static string GetShortestAbsolutePath(string strPath) + { + if(strPath == null) throw new ArgumentNullException("strPath"); + if(strPath.Length == 0) return string.Empty; + + // Path.GetFullPath is incompatible with UNC paths traversing over + // different server shares (which are created by PathRelativePathTo); + // we need to build the absolute path on our own... + if(IsUncPath(strPath)) + { + char chSep = strPath[0]; + Debug.Assert(Array.IndexOf(m_vDirSeps, chSep) >= 0); + + List l = new List(); +#if !KeePassLibSD + string[] v = strPath.Split(m_vDirSeps, StringSplitOptions.None); +#else + string[] v = strPath.Split(m_vDirSeps); +#endif + Debug.Assert((v.Length >= 3) && (v[0].Length == 0) && + (v[1].Length == 0)); + + foreach(string strPart in v) + { + if(strPart.Equals(".")) continue; + else if(strPart.Equals("..")) + { + if(l.Count > 0) l.RemoveAt(l.Count - 1); + else { Debug.Assert(false); } + } + else l.Add(strPart); // Do not ignore zero length parts + } + + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < l.Count; ++i) + { + // Don't test length of sb, might be 0 due to initial UNC seps + if(i > 0) sb.Append(chSep); + + sb.Append(l[i]); + } + + return sb.ToString(); + } + + string str; + try { str = Path.GetFullPath(strPath); } + catch(Exception) { Debug.Assert(false); return strPath; } + + Debug.Assert(str.IndexOf("\\..\\") < 0); + foreach(char ch in m_vDirSeps) + { + string strSep = new string(ch, 1); + str = str.Replace(strSep + "." + strSep, strSep); + } + + return str; + } + + public static int GetUrlLength(string strText, int nOffset) + { + if(strText == null) throw new ArgumentNullException("strText"); + if(nOffset > strText.Length) throw new ArgumentException(); // Not >= (0 len) + + int iPosition = nOffset, nLength = 0, nStrLen = strText.Length; + + while(iPosition < nStrLen) + { + char ch = strText[iPosition]; + ++iPosition; + + if((ch == ' ') || (ch == '\t') || (ch == '\r') || (ch == '\n')) + break; + + ++nLength; + } + + return nLength; + } + + public static string RemoveScheme(string strUrl) + { + if(string.IsNullOrEmpty(strUrl)) return string.Empty; + + int nNetScheme = strUrl.IndexOf(@"://", StrUtil.CaseIgnoreCmp); + int nShScheme = strUrl.IndexOf(@":/", StrUtil.CaseIgnoreCmp); + int nSmpScheme = strUrl.IndexOf(@":", StrUtil.CaseIgnoreCmp); + + if((nNetScheme < 0) && (nShScheme < 0) && (nSmpScheme < 0)) + return strUrl; // No scheme + + int nMin = Math.Min(Math.Min((nNetScheme >= 0) ? nNetScheme : int.MaxValue, + (nShScheme >= 0) ? nShScheme : int.MaxValue), + (nSmpScheme >= 0) ? nSmpScheme : int.MaxValue); + + if(nMin == nNetScheme) return strUrl.Substring(nMin + 3); + if(nMin == nShScheme) return strUrl.Substring(nMin + 2); + return strUrl.Substring(nMin + 1); + } + + public static string ConvertSeparators(string strPath) + { + return ConvertSeparators(strPath, Path.DirectorySeparatorChar); + } + + public static string ConvertSeparators(string strPath, char chSeparator) + { + if(string.IsNullOrEmpty(strPath)) return string.Empty; + + strPath = strPath.Replace('/', chSeparator); + strPath = strPath.Replace('\\', chSeparator); + + return strPath; + } + + public static bool IsUncPath(string strPath) + { + if(strPath == null) throw new ArgumentNullException("strPath"); + + return (strPath.StartsWith("\\\\") || strPath.StartsWith("//")); + } + + public static string FilterFileName(string strName) + { + if(strName == null) { Debug.Assert(false); return string.Empty; } + + string str = strName; + + str = str.Replace('/', '-'); + str = str.Replace('\\', '-'); + str = str.Replace(":", string.Empty); + str = str.Replace("*", string.Empty); + str = str.Replace("?", string.Empty); + str = str.Replace("\"", string.Empty); + str = str.Replace(@"'", string.Empty); + str = str.Replace('<', '('); + str = str.Replace('>', ')'); + str = str.Replace('|', '-'); + + return str; + } + + /// + /// Get the host component of an URL. + /// This method is faster and more fault-tolerant than creating + /// an Uri object and querying its Host + /// property. + /// + /// + /// For the input s://u:p@d.tld:p/p?q#f the return + /// value is d.tld. + /// + public static string GetHost(string strUrl) + { + if(strUrl == null) { Debug.Assert(false); return string.Empty; } + + StringBuilder sb = new StringBuilder(); + bool bInExtHost = false; + for(int i = 0; i < strUrl.Length; ++i) + { + char ch = strUrl[i]; + if(bInExtHost) + { + if(ch == '/') + { + if(sb.Length == 0) { } // Ignore leading '/'s + else break; + } + else sb.Append(ch); + } + else // !bInExtHost + { + if(ch == ':') bInExtHost = true; + } + } + + string str = sb.ToString(); + if(str.Length == 0) str = strUrl; + + // Remove the login part + int nLoginLen = str.IndexOf('@'); + if(nLoginLen >= 0) str = str.Substring(nLoginLen + 1); + + // Remove the port + int iPort = str.LastIndexOf(':'); + if(iPort >= 0) str = str.Substring(0, iPort); + + return str; + } + + public static bool AssemblyEquals(string strExt, string strShort) + { + if((strExt == null) || (strShort == null)) { Debug.Assert(false); return false; } + + if(strExt.Equals(strShort, StrUtil.CaseIgnoreCmp) || + strExt.StartsWith(strShort + ",", StrUtil.CaseIgnoreCmp)) + return true; + + if(!strShort.EndsWith(".dll", StrUtil.CaseIgnoreCmp)) + { + if(strExt.Equals(strShort + ".dll", StrUtil.CaseIgnoreCmp) || + strExt.StartsWith(strShort + ".dll,", StrUtil.CaseIgnoreCmp)) + return true; + } + + if(!strShort.EndsWith(".exe", StrUtil.CaseIgnoreCmp)) + { + if(strExt.Equals(strShort + ".exe", StrUtil.CaseIgnoreCmp) || + strExt.StartsWith(strShort + ".exe,", StrUtil.CaseIgnoreCmp)) + return true; + } + + return false; + } + + public static string GetTempPath() + { + string strDir; + if(NativeLib.IsUnix()) + strDir = NativeMethods.GetUserRuntimeDir(); + else strDir = Path.GetTempPath(); + + try + { + if(Directory.Exists(strDir) == false) + Directory.CreateDirectory(strDir); + } + catch(Exception) { Debug.Assert(false); } + + return strDir; + } + } +}