/// <summary> /// Deserializes this node by using the <see cref="KdbxMetadata"/> binary collection /// to dereference @Ref. /// </summary> /// <param name="xml"></param> /// <param name="metadata">Used to dereference the Ref attribute.</param> /// <param name="parameters"></param> public KdbxBinAttachment(XElement xml, KdbxMetadata metadata, KdbxSerializationParameters parameters) : base(xml) { FileName = GetString("Key", true); XElement valueNode = GetNode("Value"); if (valueNode == null) { throw new KdbxParseException( ReaderResult.FromXmlParseFailure($"Node {rootName} missing required child Value") ); } int refId; string refAttr = valueNode.Attribute("Ref")?.Value; if (refAttr == null || !int.TryParse(refAttr, out refId)) { throw new KdbxParseException( ReaderResult.FromXmlParseFailure($"Child Value node of {rootName} missing required int @Ref") ); } Data = metadata.Binaries.GetById(refId); this.binaryCollection = metadata.Binaries; }
public KdbxTimes(XElement xml, KdbxSerializationParameters parameters) : base(xml) { LastModificationTime = GetDate("LastModificationTime", parameters); CreationTime = GetDate("CreationTime", parameters); LastAccessTime = GetDate("LastAccessTime", parameters); ExpiryTime = GetDate("ExpiryTime", parameters); Expires = GetBool("Expires"); UsageCount = GetInt("UsageCount"); LocationChanged = GetDate("LocationChanged", parameters); }
private XElement GetBizarroNullableBool(string name, bool?value, KdbxSerializationParameters parameters) { XElement node = GetKeePassNode(name, value, parameters); if (String.IsNullOrEmpty(node.Value)) { node.SetValue("null"); } else { node.Value = node.Value.ToLower(); } return(node); }
protected KdbxNode(XElement xml, KdbxSerializationParameters parameters) : base(xml) { Uuid = GetUuid("UUID"); IconID = GetInt("IconID"); CustomIconUuid = GetUuid("CustomIconUUID", false); Times = new KdbxTimes(GetNode(KdbxTimes.RootName), parameters); XElement dataElement = GetNode(KdbxCustomData.RootName); if (dataElement != null) { CustomData = new KdbxCustomData(dataElement); } }
/// <summary> /// Parses a collection of binaries from the given XML. /// </summary> /// <param name="xml"></param> /// <param name="parameters"></param> public KdbxBinaries(XElement xml, KdbxSerializationParameters parameters) : base(xml) { this.binaries = new SortedDictionary <int, KdbxBinary>(); foreach (XElement ele in GetNodes(KdbxBinary.RootName)) { KdbxBinary bin = new KdbxBinary(ele, parameters); if (this.binaries.ContainsKey(bin.Id)) { throw new KdbxParseException( ReaderResult.FromXmlParseFailure($"Duplicate binary key {bin.Id}") ); } this.binaries.Add(bin.Id, bin); } }
public static string ToKeePassDate(DateTime?dt, KdbxSerializationParameters parameters) { if (!dt.HasValue) { return(null); } if (parameters.UseBase64DateTimeEncoding) { long elapsedSeconds = dt.Value.Ticks / TimeSpan.TicksPerSecond; byte[] buffer = ByteHelper.GetLittleEndianBytes((ulong)elapsedSeconds); return(CryptographicBuffer.EncodeToBase64String(buffer.AsBuffer())); } else { // ToString("s") does not contain the Z UTC timezone specifier, which we want. return(dt.Value.ToUniversalTime().ToString("s") + "Z"); } }
/// <summary> /// Creates an <see cref="XElement"/> that represents this object. /// </summary> /// <param name="rng">Random number generator used for serializing protected strings.</param> /// <param name="parameters">Parameters controlling serialization.</param> /// <returns>An XML object that represents the current instance.</returns> public XElement ToXml(IRandomNumberGenerator rng, KdbxSerializationParameters parameters) { XElement xml = new XElement(rootName); PopulateChildren(xml, rng, parameters); // For each child we didn't parse out earlier during deserialization, add it // as-is. if (this._pristine != null) { foreach (var kvp in this._pristine) { foreach (XElement node in kvp.Value) { xml.Add(node); } } } return(xml); }
public DateTime?GetDate(string name, KdbxSerializationParameters parameters, bool required = false) { string dtString = GetString(name, required); if (String.IsNullOrEmpty(dtString)) { return(null); } // KeePass interop weirdness - they trim the UTC timezone specifier off, then parse, // then convert to local time. When serializing they convert back to UTC and add the "Z". // Can't say why, but mimicing the behavior to avoid bugs. if (dtString.EndsWith("Z")) { dtString = dtString.Substring(0, dtString.Length - 1); } DateTime dt; if (DateTime.TryParse(dtString, out dt)) { dt = dt.ToLocalTime(); return(dt); } else if (parameters.UseBase64DateTimeEncoding) { // Try to parse the DateTime as a base64 string IBuffer data = CryptographicBuffer.DecodeFromBase64String(dtString); if (data.Length == 8) { long elapsedSeconds = (long)ByteHelper.BufferToLittleEndianUInt64(data.ToArray(), 0); return(new DateTime(elapsedSeconds * TimeSpan.TicksPerSecond, DateTimeKind.Utc)); } } // This used to be a parse failure, but due to the strangeness of parsing dates, and because KeePass only considers // this an assertion failure with a fallback, we will also fallback. DebugHelper.Assert(false, $"Investigate why this DateTime failed to parse: {dtString}"); return(DateTime.Now); }
// Internal constructor for initializing fields and checking edge cases private KdbxDecryptionResult(ReaderResult error, KdbxSerializationParameters kdbxParameters, KdbxDocument document, IBuffer rawKey) { DebugHelper.Assert(error != null); if (error == null) { throw new ArgumentNullException(nameof(error)); } if (error != ReaderResult.Success) { DebugHelper.Assert(document == null); if (document != null) { throw new ArgumentException("If error is defined, the other arguments must be null"); } } else { // Result is guaranteed to be Success at this point DebugHelper.Assert(document != null); if (document == null) { throw new ArgumentNullException(nameof(document)); } if (rawKey == null) { throw new ArgumentNullException(nameof(rawKey)); } } Result = error; this.kdbxParameters = kdbxParameters; this.kdbxDocument = document; this.rawKey = rawKey; }
public static XElement GetKeePassNode <T>(string name, T tData, KdbxSerializationParameters kdbxParams) { XElement element = new XElement(name); if (tData == null) { return(element); } string strValue; Type tType = typeof(T); object data = (object)tData; if (tType == typeof(Color?) || tType == typeof(Color)) { strValue = ToKeePassColor((Color?)data); } else if (tType == typeof(bool?) || tType == typeof(bool)) { strValue = ToKeePassBool((bool?)data); } else if (tType == typeof(DateTime?) || tType == typeof(DateTime)) { strValue = ToKeePassDate((DateTime?)data, kdbxParams); } else { strValue = data.ToString(); } if (!string.IsNullOrEmpty(strValue)) { element.SetValue(strValue); } return(element); }
public KdbxRoot(XElement xml, IRandomNumberGenerator rng, KdbxMetadata metadata, KdbxSerializationParameters parameters) : base(xml) { DatabaseGroup = new KdbxGroup(GetNode(KdbxGroup.RootName), null, rng, metadata, parameters); this.deletedObjs = GetNode("DeletedObjects"); }
/// <summary> /// Decodes an instance from serialized XML. /// </summary> /// <param name="xml">The XML to deserialize.</param> /// <param name="parameters">Parameters controlling serialization.</param> public KdbxBinary(XElement xml, KdbxSerializationParameters parameters) { // Parse out int ID attribute string idAttr = xml?.Attribute("ID")?.Value; if (idAttr == null) { throw new KdbxParseException( ReaderResult.FromXmlParseFailure($"KdbxBinary was missing required ID attribute") ); } if (!Int32.TryParse(idAttr, out int id)) { throw new KdbxParseException( ReaderResult.FromXmlParseFailure($"KdbxBinary ID attribute could not be parsed into an int") ); } Id = id; // Parse out bool Compressed attribute string compressAttr = xml?.Attribute("Compressed")?.Value ?? "false"; if (!Boolean.TryParse(compressAttr, out bool compressed)) { throw new KdbxParseException( ReaderResult.FromXmlParseFailure($"KdbxBinary Compressed attribute could not be parsed into a bool") ); } // Parse base64-encoded content byte[] content; try { content = CryptographicBuffer.DecodeFromBase64String(xml?.Value)?.ToArray(); if (content == null) { content = new byte[0]; } } catch (Exception) { throw new KdbxParseException( ReaderResult.FromXmlParseFailure($"Could not decode KdbxBinary content as base64 data") ); } // Decompress content if needed if (compressed && content.Length > 0) { byte[] decompressed; using (Stream memStream = new MemoryStream(content)) { using (Stream gzipStream = new GZipStream(memStream, CompressionMode.Decompress)) { byte[] buffer = new byte[1024]; int read = gzipStream.Read(buffer, 0, buffer.Length); List <byte> bytes = new List <byte>(); while (read > 0) { bytes.AddRange(buffer.Take(read)); read = gzipStream.Read(buffer, 0, buffer.Length); } decompressed = bytes.ToArray(); } } content = decompressed; } this.binaryData = new ProtectedBinary(content, false); }
/// <summary> /// Sets attributes and encoded base64 data, compressed if needed. /// </summary> /// <param name="xml">The Binary node to populate with attributes and a value.</param> /// <param name="rng">Not used.</param> /// <param name="parameters">Parameters for serialization.</param> public override void PopulateChildren(XElement xml, IRandomNumberGenerator rng, KdbxSerializationParameters parameters) { xml.Add(new XAttribute("ID", Id)); byte[] data = BinaryData.GetClearData(); if (ShouldCompress(parameters)) { xml.Add(new XAttribute("Compressed", ToKeePassBool(true))); // Compress data if needed if (data.Length > 0) { using (MemoryStream memStream = new MemoryStream()) { using (Stream gzipStream = new GZipStream(memStream, CompressionMode.Compress)) { gzipStream.Write(data, 0, data.Length); } memStream.Flush(); data = memStream.ToArray(); } } } string encoded = CryptographicBuffer.EncodeToBase64String(data.AsBuffer()); xml.SetValue(encoded); }
/// <summary> /// Helper to get a value for <see cref="Compressed"/> based on serialization parameters. /// </summary> /// <param name="parameters">Parameters to evaluate.</param> /// <returns>Whether this object should use GZip compression when serializing.</returns> public static bool ShouldCompress(KdbxSerializationParameters parameters) { return(parameters?.Compression != CompressionAlgorithm.None); }
public override void PopulateChildren(XElement xml, IRandomNumberGenerator rng, KdbxSerializationParameters parameters) { xml.Add(GetKeePassNode("Generator", Generator, parameters)); if (parameters.UseXmlHeaderAuthentication && HeaderHash != null) { xml.Add(GetKeePassNode("HeaderHash", HeaderHash, parameters)); } xml.Add( GetKeePassNode("DatabaseName", DatabaseName, parameters), GetKeePassNode("DatabaseNameChanged", DatabaseNameChanged, parameters), GetKeePassNode("DatabaseDescription", DatabaseDescription, parameters), GetKeePassNode("DatabaseDescriptionChanged", DatabaseDescriptionChanged, parameters), GetKeePassNode("DefaultUserName", DefaultUserName, parameters), GetKeePassNode("DefaultUserNameChanged", DefaultUserNameChanged, parameters), GetKeePassNode("MaintenanceHistoryDays", MaintenanceHistoryDays, parameters), GetKeePassNode("Color", DbColor, parameters), GetKeePassNode("MasterKeyChanged", MasterKeyChanged, parameters), GetKeePassNode("MasterKeyChangeRec", MasterKeyChangeRec, parameters), GetKeePassNode("MasterKeyChangeForce", MasterKeyChangeForce, parameters), MemoryProtection.ToXml(rng, parameters) ); if (CustomIcons != null) { xml.Add(CustomIcons.ToXml(rng, parameters)); } xml.Add( GetKeePassNode("RecycleBinEnabled", RecycleBinEnabled, parameters), GetKeePassNode("RecycleBinUUID", RecycleBinUuid, parameters), GetKeePassNode("RecycleBinChanged", RecycleBinChanged, parameters), GetKeePassNode("EntryTemplatesGroup", EntryTemplatesGroup, parameters), GetKeePassNode("EntryTemplatesGroupChanged", EntryTemplatesGroupChanged, parameters), GetKeePassNode("HistoryMaxItems", HistoryMaxItems, parameters), GetKeePassNode("HistoryMaxSize", HistoryMaxSize, parameters), GetKeePassNode("LastSelectedGroup", LastSelectedGroup, parameters), GetKeePassNode("LastTopVisibleGroup", LastTopVisibleGroup, parameters) ); // Only both writing this node if we have binaries (compat issue with KeePass) if (parameters.BinariesInXml && Binaries != null && Binaries.Binaries.Any()) { xml.Add(Binaries.ToXml(rng, parameters)); } if (CustomData != null) { xml.Add(CustomData.ToXml(rng, parameters)); } }
public override void PopulateChildren(XElement xml, IRandomNumberGenerator rng, KdbxSerializationParameters parameters) { xml.Add( new XElement("ProtectTitle", ToKeePassBool(ProtectTitle)), new XElement("ProtectUserName", ToKeePassBool(ProtectUserName)), new XElement("ProtectPassword", ToKeePassBool(ProtectPassword)), new XElement("ProtectURL", ToKeePassBool(ProtectUrl)), new XElement("ProtectNotes", ToKeePassBool(ProtectNotes)) ); }
public override void PopulateChildren(XElement xml, IRandomNumberGenerator rng, KdbxSerializationParameters parameters) { xml.Add(new XElement("Key", Key)); XElement valueElement = new XElement("Value"); if (!string.IsNullOrEmpty(RawValue)) { string value = (Protected ? getEncrypted(ClearValue, rng.GetBytes((uint)this._xorKey.Length)) : ClearValue ); valueElement.SetValue(value); } xml.Add(valueElement); if (Protected) { valueElement.SetAttributeValue("Protected", "True"); } }
/// <summary> /// Given an <see cref="XElement"/> that is currently being constructed for serialization purposes, /// populates the children of the element. /// </summary> /// <param name="xml">The node being populated.</param> /// <param name="rng">Random number generator used for serializing protected strings.</param> /// <param name="parameters">Parameters controlling serialization.</param> public abstract void PopulateChildren(XElement xml, IRandomNumberGenerator rng, KdbxSerializationParameters parameters);
public override void PopulateChildren(XElement xml, IRandomNumberGenerator rng, KdbxSerializationParameters parameters) { xml.Add( GetKeePassNode("UUID", Uuid, parameters), GetKeePassNode("Name", Title.ClearValue, parameters), GetKeePassNode("Notes", Notes.ClearValue, parameters), GetKeePassNode("IconID", IconID, parameters) ); if (CustomIconUuid != null) { xml.Add(GetKeePassNode("CustomIconUUID", CustomIconUuid, parameters)); } xml.Add( Times.ToXml(rng, parameters), GetKeePassNode("IsExpanded", IsExpanded, parameters), GetKeePassNode("DefaultAutoTypeSequence", DefaultAutoTypeSequence, parameters), GetBizarroNullableBool("EnableAutoType", EnableAutoType, parameters), GetBizarroNullableBool("EnableSearching", EnableSearching, parameters), GetKeePassNode("LastTopVisibleEntry", LastTopVisibleEntry, parameters) ); foreach (IKeePassNode child in Children) { xml.Add(child.ToXml(rng, parameters)); } if (CustomData != null) { xml.Add(CustomData.ToXml(rng, parameters)); } }
public override void PopulateChildren(XElement xml, IRandomNumberGenerator rng, KdbxSerializationParameters parameters) { xml.Add(Metadata.ToXml(rng, parameters), Root.ToXml(rng, parameters)); }
/// <summary> /// Parses a KeePass document from the specified XML. /// </summary> /// <param name="xml">XML to deserialize.</param> /// <param name="headerBinaries">Any binaries that were parsed from a header.</param> /// <param name="rng">RNG used to encrypt protected strings.</param> /// <param name="parameters">Parameters controlling serialization.</param> public KdbxDocument(XElement xml, IEnumerable <ProtectedBinary> headerBinaries, IRandomNumberGenerator rng, KdbxSerializationParameters parameters) : base(xml) { XElement metadata = GetNode(KdbxMetadata.RootName); if (metadata == null) { throw new KdbxParseException( ReaderResult.FromXmlParseFailure($"Document has no {KdbxMetadata.RootName} node") ); } Metadata = new KdbxMetadata(metadata, headerBinaries, parameters); XElement root = GetNode(KdbxRoot.RootName); if (root == null) { throw new KdbxParseException( ReaderResult.FromXmlParseFailure($"Document has no {KdbxRoot.RootName} node") ); } Root = new KdbxRoot(root, rng, Metadata, parameters); }
/// <summary> /// Appends the data children to the XML node being serialized. /// </summary> /// <param name="xml"></param> /// <param name="rng"></param> /// <param name="parameters"></param> public override void PopulateChildren(XElement xml, IRandomNumberGenerator rng, KdbxSerializationParameters parameters) { foreach (KeyValuePair <string, string> kvp in this.data) { xml.Add( new XElement("Item", new XElement("Key", kvp.Key), new XElement("Value", kvp.Value) ) ); } }
public override void PopulateChildren(XElement xml, IRandomNumberGenerator rng, KdbxSerializationParameters parameters) { xml.Add(DatabaseGroup.ToXml(rng, parameters)); if (this.deletedObjs != null) { xml.Add(this.deletedObjs); } }
/// <summary> /// Populates the given <see cref="XElement"/> with <see cref="KdbxBinary"/> children, /// with new IDs based on the current state of this instance. /// </summary> /// <param name="xml"></param> /// <param name="rng"></param> /// <param name="parameters"></param> public override void PopulateChildren(XElement xml, IRandomNumberGenerator rng, KdbxSerializationParameters parameters) { int i = 0; foreach (KeyValuePair <int, KdbxBinary> kvp in this.binaries) { xml.Add(kvp.Value.With(id: i++).ToXml(rng, parameters)); } }
/// <summary> /// Not implemented. /// </summary> /// <param name="rng"></param> /// <returns></returns> public XElement ToXml(IRandomNumberGenerator rng, KdbxSerializationParameters parameters) { throw new NotImplementedException(); }
/// <summary> /// Parses out a metadata element from XML. /// </summary> /// <param name="xml">XML to deserialize.</param> /// <param name="headerBinaries">Binaries that have been pre-parsed from a header.</param> /// <param name="parameters">Parameters controlling serialization.</param> public KdbxMetadata(XElement xml, IEnumerable <ProtectedBinary> headerBinaries, KdbxSerializationParameters parameters) : base(xml) { if (headerBinaries == null) { throw new ArgumentNullException(nameof(headerBinaries)); } if (parameters == null) { throw new ArgumentNullException(nameof(parameters)); } Generator = GetString("Generator"); HeaderHash = GetString("HeaderHash"); DatabaseName = GetString("DatabaseName"); DatabaseNameChanged = GetDate("DatabaseNameChanged", parameters); DatabaseDescription = GetString("DatabaseDescription"); DatabaseDescriptionChanged = GetDate("DatabaseDescriptionChanged", parameters); DefaultUserName = GetString("DefaultUserName"); DefaultUserNameChanged = GetDate("DefaultUserNameChanged", parameters); MaintenanceHistoryDays = GetInt("MaintenanceHistoryDays"); DbColor = GetNullableColor("Color"); MasterKeyChanged = GetDate("MasterKeyChanged", parameters, false); MasterKeyChangeRec = GetInt("MasterKeyChangeRec", -1); MasterKeyChangeForce = GetInt("MasterKeyChangeForce", -1); MemoryProtection = new KdbxMemoryProtection(GetNode(KdbxMemoryProtection.RootName)); XElement iconsElement = GetNode(KdbxCustomIcons.RootName); if (iconsElement != null) { CustomIcons = new KdbxCustomIcons(iconsElement); } else { CustomIcons = null; } RecycleBinEnabled = GetBool("RecycleBinEnabled"); RecycleBinUuid = GetUuid("RecycleBinUUID"); RecycleBinChanged = GetDate("RecycleBinChanged", parameters); EntryTemplatesGroup = GetUuid("EntryTemplatesGroup"); EntryTemplatesGroupChanged = GetDate("EntryTemplatesGroupChanged", parameters); HistoryMaxItems = GetInt("HistoryMaxItems", -1); HistoryMaxSize = GetInt("HistoryMaxSize", -1); LastSelectedGroup = GetUuid("LastSelectedGroup"); LastTopVisibleGroup = GetUuid("LastTopVisibleGroup"); XElement binariesElement = GetNode(KdbxBinaries.RootName); if (parameters.BinariesInXml) { if (binariesElement != null) { Binaries = new KdbxBinaries(binariesElement, parameters); } else { Binaries = new KdbxBinaries(); } } else { // Populate with values from binary inner header Binaries = new KdbxBinaries(headerBinaries); } XElement customDataElement = GetNode(KdbxCustomData.RootName); if (customDataElement != null) { CustomData = new KdbxCustomData(customDataElement); } else { CustomData = null; } }
public override void PopulateChildren(XElement element, IRandomNumberGenerator rng, KdbxSerializationParameters parameters) { }
public override void PopulateChildren(XElement xml, IRandomNumberGenerator rng, KdbxSerializationParameters parameters) { xml.Add( GetKeePassNode("LastModificationTime", LastModificationTime, parameters), GetKeePassNode("CreationTime", CreationTime, parameters), GetKeePassNode("LastAccessTime", LastAccessTime, parameters), GetKeePassNode("ExpiryTime", ExpiryTime, parameters), GetKeePassNode("Expires", Expires, parameters), GetKeePassNode("UsageCount", UsageCount, parameters), GetKeePassNode("LocationChanged", LocationChanged, parameters) ); }
public KdbxGroup(XElement xml, IKeePassGroup parent, IRandomNumberGenerator rng, KdbxMetadata metadata, KdbxSerializationParameters parameters) : base(xml, parameters) { InitializeCollections(); Parent = parent; Title = new KdbxString("Name", GetString("Name"), null); Notes = new KdbxString("Notes", GetString("Notes"), null); IsExpanded = GetBool("IsExpanded"); DefaultAutoTypeSequence = GetString("DefaultAutoTypeSequence"); EnableAutoType = GetNullableBool("EnableAutoType"); EnableSearching = GetNullableBool("EnableSearching"); LastTopVisibleEntry = GetUuid("LastTopVisibleEntry", false) ?? KeePassUuid.Empty; // The order in which we deserialize entries and groups matters. // They must be constructed in the order that they appear in the XML, // or the RNG will enter undefined territory. ForgetNodes(KdbxEntry.RootName); ForgetNodes(KdbxGroup.RootName); // First, we need to select each XElement that represents either a group or an entry. // From these, we construct KdbxEntries and KdbxGroups. // Then we sort them, groups first, and add them to the child collection. IEnumerable <IKeePassNode> nodes = xml.Elements() .Where(element => element.Name == KdbxEntry.RootName || element.Name == KdbxGroup.RootName) .Select( matchedElement => { if (matchedElement.Name == KdbxEntry.RootName) { return(new KdbxEntry(matchedElement, this, rng, metadata, parameters) as IKeePassNode); } else { return(new KdbxGroup(matchedElement, this, rng, metadata, parameters) as IKeePassNode); } } ) .OrderBy( node => node is IKeePassGroup, Comparer <bool> .Create((b1, b2) => b1.CompareTo(b2)) ); foreach (IKeePassNode node in nodes) { this._children.Add(node); } }
/// <summary> /// Looks up the data represented by this attachment in the metadata binary collection /// to obtain a new @Ref ID for XML serialization. /// </summary> /// <param name="xml"></param> /// <param name="rng"></param> /// <param name="parameters"></param> public override void PopulateChildren(XElement xml, IRandomNumberGenerator rng, KdbxSerializationParameters parameters) { int refId = 0; bool foundBin = false; foreach (var bin in this.binaryCollection.Binaries) { if (bin.Equals(Data)) { foundBin = true; break; } refId++; } DebugHelper.Assert(foundBin); xml.Add( new XElement("Key", FileName), new XElement("Value", new XAttribute("Ref", refId)) ); }