using System.Globalization; using System.Xml; using System.Text; namespace Wayne.Lib.Log { /// /// Static support class for managing masking of debug logs. /// public static class DebugLoggerMask { #region Fields private static bool forceMask; #endregion #region XmlWriter Support /// /// Writes processing instructions to an XML document to mask a node. /// The masking will be active from this point forward in the XML document (until the end or the instruction is redefined). /// /// The XML writer. /// The name of the XML node. /// The type of masking to use. public static void MaskXmlNode(XmlWriter xmlWriter, string nodeName, DebugLogMaskKind maskKind) { xmlWriter.WriteProcessingInstruction(XmlLogObject.XmlMaskNode, string.Concat(XmlLogObject.XmlNodeName, "=\"", nodeName, "\" ", XmlLogObject.XmlMaskKind, "=\"", maskKind, "\"")); } /// /// Writes processing instructions to an XML document to mask the next node. /// /// The XML writer. /// The type of masking to use. public static void MaskNextXmlNode(XmlWriter xmlWriter, DebugLogMaskKind maskKind) { xmlWriter.WriteProcessingInstruction(XmlLogObject.XmlMaskNode, string.Concat(XmlLogObject.XmlNodeName, "=\"", XmlLogObject.XmlMaskNextNodeName, "\" ", XmlLogObject.XmlMaskKind, "=\"", maskKind, "\"")); } /// /// Writes processing instructions to an XML document to mask an attribute. /// The masking will be active from this point forward in the XML document (until the end or the instruction is redefined). /// /// The XML writer. /// The name of the XML node. /// The name of the node attribute. /// The type of masking to use. public static void MaskXmlAttribute(XmlWriter xmlWriter, string nodeName, string attributeName, DebugLogMaskKind maskKind) { xmlWriter.WriteProcessingInstruction(XmlLogObject.XmlMaskAttribute, string.Concat(XmlLogObject.XmlNodeName, "=\"", nodeName, "\" ", XmlLogObject.XmlAttributeName, "=\"", attributeName, "\" ", XmlLogObject.XmlMaskKind, "=\"", maskKind, "\"")); } #endregion #region XmlDocument Support /// /// Returns the XmlNode needed to be added to an XML document to mask a node. /// The masking will be active from the point forward where the node is inserted (until the end or the instruction is redefined). /// /// The XML document. /// The name of the XML node. /// The type of masking to use. public static XmlProcessingInstruction CreateXmlNodeToMaskNode(XmlDocument xmlDocument, string nodeName, DebugLogMaskKind maskKind) { return xmlDocument.CreateProcessingInstruction(XmlLogObject.XmlMaskNode, string.Concat(XmlLogObject.XmlNodeName, "=\"", nodeName, "\" ", XmlLogObject.XmlMaskKind, "=\"", maskKind, "\"")); } /// /// Returns the XmlNode needed to be added to an XML document to mask the next node. /// /// The XML document. /// The type of masking to use. public static XmlProcessingInstruction CreateXmlNodeToMaskNextNode(XmlDocument xmlDocument, DebugLogMaskKind maskKind) { return xmlDocument.CreateProcessingInstruction(XmlLogObject.XmlMaskNode, string.Concat(XmlLogObject.XmlNodeName, "=\"", XmlLogObject.XmlMaskNextNodeName, "\" ", XmlLogObject.XmlMaskKind, "=\"", maskKind, "\"")); } /// /// Returns the XmlNode needed to be added to an XML document to mask an attribute. /// The masking will be active from this point forward in the XML document (until the end or the instruction is redefined). /// /// The XML document. /// The name of the XML node. /// The name of the node attribute. /// The type of masking to use. public static XmlProcessingInstruction CreateXmlNodeToMaskAttribute(XmlDocument xmlDocument, string nodeName, string attributeName, DebugLogMaskKind maskKind) { return xmlDocument.CreateProcessingInstruction(XmlLogObject.XmlMaskAttribute, string.Concat(XmlLogObject.XmlNodeName, "=\"", nodeName, "\" ", XmlLogObject.XmlAttributeName, "=\"", attributeName, "\" ", XmlLogObject.XmlMaskKind, "=\"", maskKind, "\"")); } #endregion #region Properties /// /// Turn on/off masking in debug mode. /// public static bool ForceMask { get { return forceMask; } set { forceMask = value; } } /// /// Should sensitive data be masked? /// public static bool MaskSensitiveData { get { #if DEBUG return forceMask; #else return true; #endif } } #endregion #region Methods /// /// Masks the data the requested way. /// /// The data to mask. /// The kind of masking to perform. /// public static string MaskData(string data, DebugLogMaskKind maskKind) { #if DEBUG if (forceMask) #endif { switch (maskKind) { case DebugLogMaskKind.Remove: return string.Empty; case DebugLogMaskKind.Empty: return string.Empty; case DebugLogMaskKind.Mask: return "****"; case DebugLogMaskKind.MaskDigits: { StringBuilder maskedData = new StringBuilder(); if (!string.IsNullOrEmpty(data)) { for (int i = 0; i < data.Length; i++) { char dataChar = data[i]; if ((dataChar >= '0') && (dataChar <= '9')) maskedData.Append('*'); else maskedData.Append(dataChar); } } return maskedData.ToString(); } case DebugLogMaskKind.MaskDigitsAppendHash: { StringBuilder maskedData = new StringBuilder(); StringBuilder digits = new StringBuilder(); if (!string.IsNullOrEmpty(data)) { for (int i = 0; i < data.Length; i++) { char dataChar = data[i]; if ((dataChar >= '0') && (dataChar <= '9')) { maskedData.Append('*'); digits.Append(dataChar); } else maskedData.Append(dataChar); } } return string.Concat(maskedData, "[#", GetHashCode(digits.ToString()), "#]"); } case DebugLogMaskKind.MaskHex: { StringBuilder maskedData = new StringBuilder(); if (!string.IsNullOrEmpty(data)) { for (int i = 0; i < data.Length; i++) { char dataChar = data[i]; if ((dataChar >= '0') && (dataChar <= '9') || (dataChar >= 'a') && (dataChar <= 'f') || (dataChar >= 'A') && (dataChar <= 'F')) maskedData.Append('*'); else maskedData.Append(dataChar); } } return maskedData.ToString(); } case DebugLogMaskKind.MaskHexAppendHash: { StringBuilder maskedData = new StringBuilder(); StringBuilder hex = new StringBuilder(); if (!string.IsNullOrEmpty(data)) { for (int i = 0; i < data.Length; i++) { char dataChar = data[i]; if ((dataChar >= '0') && (dataChar <= '9') || (dataChar >= 'a') && (dataChar <= 'f') || (dataChar >= 'A') && (dataChar <= 'F')) { maskedData.Append('*'); hex.Append(dataChar); } else maskedData.Append(dataChar); } } return string.Concat(maskedData, "[#", GetHashCode(hex.ToString()), "#]"); } case DebugLogMaskKind.MaskPan: { if (!string.IsNullOrEmpty(data) && (data.Length > 8)) return string.Concat(data.Substring(0, 4), string.Empty.PadLeft(data.Length - 8, '*'), data.Substring(data.Length - 4, 4)); return data; } case DebugLogMaskKind.MaskPan64: { if (!string.IsNullOrEmpty(data) && (data.Length > 10)) return string.Concat(data.Substring(0, 6), string.Empty.PadLeft(data.Length - 10, '*'), data.Substring(data.Length - 4, 4)); return data; } case DebugLogMaskKind.MaskTrack64: { if (!string.IsNullOrEmpty(data) && (data.Length > 10)) { int sepIndex = data.IndexOf("="); if (sepIndex < 0) { return string.Concat(data.Substring(0, 6), string.Empty.PadLeft(data.Length - 10, '*'), data.Substring(data.Length - 4, 4)); } else { string panData = data.Substring(0, sepIndex); string otherData = string.Empty; if (data.Length > sepIndex + 1) { otherData = data.Substring(sepIndex + 1, data.Length - sepIndex - 1); } return string.Concat(panData.Substring(0, 6), string.Empty.PadLeft(panData.Length - 10, '*'), panData.Substring(panData.Length - 4, 4), "=", otherData); } } return data; } case DebugLogMaskKind.MaskPanAppendHash: { if (!string.IsNullOrEmpty(data) && (data.Length > 8)) return string.Concat(data.Substring(0, 4), string.Empty.PadLeft(data.Length - 8, '*'), data.Substring(data.Length - 4, 4), "[#", GetHashCode(data), "#]"); return data; } case DebugLogMaskKind.MaskEmbeddedPan: case DebugLogMaskKind.MaskEmbeddedPanAppendHash: { StringBuilder maskedData = new StringBuilder(); int stringBuilderOffset = 0; if (!string.IsNullOrEmpty(data)) { int digitStart = -1; for (int i = 0; i < data.Length; i++) { char dataChar = data[i]; bool isDigit = (dataChar >= '0') && (dataChar <= '9'); bool isLastChar = (i == data.Length - 1); bool inDigitSequence = (digitStart > -1); maskedData.Append(dataChar); if (isDigit && (!isLastChar || !inDigitSequence)) { if (digitStart == -1) digitStart = i + stringBuilderOffset; } else { if (digitStart > -1) { int digitLength = i + stringBuilderOffset - digitStart; if (isLastChar) digitLength++; if (digitLength > 8) { if (maskKind == DebugLogMaskKind.MaskEmbeddedPanAppendHash) { string hashedPan = string.Concat("[#", GetHashCode(maskedData.ToString(digitStart, digitLength)), "#]"); maskedData.Remove(digitStart + 4, digitLength - 8).Insert(digitStart + 4, "*", digitLength - 8).Insert(digitStart + digitLength, hashedPan); stringBuilderOffset += hashedPan.Length; } else maskedData.Remove(digitStart + 4, digitLength - 8).Insert(digitStart + 4, "*", digitLength - 8); } } digitStart = -1; } } } return maskedData.ToString(); } } } return data; } /// /// Masks the data the requested way. /// /// The binary data to mask. /// Should the original length of the bytes be shown in the log? /// public static string MaskData(byte[] bytes, bool showLength) { return MaskData(bytes, showLength, false); } /// /// Masks the data the requested way. /// /// The binary data to mask. /// Should the original length of the bytes be shown in the log? /// Should a hash value be calculated and shown in the log? /// public static string MaskData(byte[] bytes, bool showLength, bool showHashValue) { bool isDebug = false; #if DEBUG isDebug = true; #endif if (forceMask || !isDebug) { StringBuilder text = new StringBuilder(); if (!showLength) text.Append("***# bytes***"); else if (bytes == null) text.Append("***null bytes***"); else { text.Append(bytes.Length); text.Append(" bytes"); } if (showHashValue) { if (showLength) text.Append(","); if (bytes != null) { int hashValue = 0; for (int i = 0; i < bytes.Length; i++) hashValue = (hashValue * 31) ^ bytes[i]; // Just a simple "hash" calculation of a byte array. text.Append("[#"); text.Append(hashValue.ToString("X8")); text.Append("#]"); } else text.Append("[#--------#]"); } return string.Concat("***", text, "***"); } return Strings.ByteArrayToString(bytes, StringsByteArrayFormat.CompactHex); } private static string GetHashCode(string text) { string fullHashCode = text.GetHashCode().ToString("X8"); int firstHashHalf = int.Parse(fullHashCode.Substring(0, 4), NumberStyles.HexNumber); int lastHashHalf = int.Parse(fullHashCode.Substring(4, 4), NumberStyles.HexNumber); int hashedHash = firstHashHalf ^ lastHashHalf; return hashedHash.ToString("X4"); } #endregion } }