using Alkami.Ops.Certificates.Data; using System; using System.Collections.Generic; using System.IO; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Xml; namespace Alkami.Ops.Certificates.Utilities { internal static class Extensions { /// /// Returns a List of certificates from an X509Certificate2Collection /// /// public static List ToList(this X509Certificate2Collection collection) { var list = new List(collection.Count); foreach (var cert in collection) { list.Add(cert); } return list; } /// /// Returns a List of certificates from an X509Certificate2Collection /// /// public static List ToList(this X509ChainElementCollection collection) { var list = new List(collection.Count); foreach (var chainelement in collection) { list.Add(chainelement.Certificate); } return list; } /// /// Adds a value to a dictionary if it doesn't exist and returns true. Returns false if the key already exists. /// /// /// /// /// The dictionary key to set. /// The value to store in the dictionary. /// public static bool TryAdd(this Dictionary dictionary, TKey key, TValue value) { if (dictionary.ContainsKey(key)) { return false; } else { dictionary[key] = value; return true; } } /// /// Deletes all files/directories in a directory without deleting the directory itself. /// /// public static void ClearDirectory(string directory) { if (!Directory.Exists(directory)) { return; } foreach (var file in Directory.GetFiles(directory)) { File.Delete(file); } foreach (var dir in Directory.GetDirectories(directory)) { foreach (var file in Directory.GetFiles(dir, "*", SearchOption.AllDirectories)) { File.Delete(file); } Directory.Delete(dir, true); } } /// /// Returns true if the two files are equal. /// /// First file to compare. /// Second file to compare. /// public static bool FilesAreEqual(FileInfo first, FileInfo second) { const int BYTES_TO_READ = sizeof(Int64); if (first.Length != second.Length) { return false; } if (string.Equals(first.FullName, second.FullName, StringComparison.OrdinalIgnoreCase)) { return true; } int iterations = (int)Math.Ceiling((double)first.Length / BYTES_TO_READ); using (FileStream fs1 = first.OpenRead()) using (FileStream fs2 = second.OpenRead()) { byte[] one = new byte[BYTES_TO_READ]; byte[] two = new byte[BYTES_TO_READ]; for (int i = 0; i < iterations; i++) { fs1.Read(one, 0, BYTES_TO_READ); fs2.Read(two, 0, BYTES_TO_READ); if (BitConverter.ToInt64(one, 0) != BitConverter.ToInt64(two, 0)) { return false; } } } return true; } /// /// Loads an EnvironmentInfo given a server to read properties from. /// /// Server name. /// public static ServerInfo GetServerInfo(string serverName) { const string MachineConfigUncPath = "\\\\{0}\\C$\\Windows\\Microsoft.NET\\Framework64\\v4.0.30319\\Config\\machine.config"; string machineConfigPath = string.Format(MachineConfigUncPath, serverName); if (!File.Exists(machineConfigPath)) { throw new Exception($"Could not locate valid machine config for server {serverName}"); } XmlReaderSettings readerSettings = new XmlReaderSettings(); readerSettings.IgnoreWhitespace = true; readerSettings.IgnoreComments = true; readerSettings.CheckCharacters = true; readerSettings.CloseInput = true; readerSettings.IgnoreProcessingInstructions = false; readerSettings.ValidationFlags = System.Xml.Schema.XmlSchemaValidationFlags.None; readerSettings.ValidationType = ValidationType.None; XmlReader reader = XmlReader.Create(machineConfigPath, readerSettings); XmlDocument doc = new XmlDocument(); doc.Load(reader); string environmentName = null; string environmentType = null; string hostingProvider = null; string serverType = null; string microUser = null; string dbUser = null; var appSettingNode = doc.SelectSingleNode("//appSettings"); if (appSettingNode.HasChildNodes) { var appSettings = appSettingNode.ChildNodes; foreach (XmlNode setting in appSettings) { string key = setting.Attributes["key"].InnerText.ToLower(); string value = setting.Attributes["value"].InnerText; switch (key) { case "environment.name": environmentName = value; break; case "environment.type": environmentType = value; break; case "environment.hosting": hostingProvider = value; break; case "environment.server": serverType = value; break; case "databasemicroserviceaccount": dbUser = value; break; case "nondatabasemicroserviceaccount": microUser = value; break; } } } // Return a null result if any piece of information is missing. if (environmentName == null || environmentType == null || hostingProvider == null || serverType == null || microUser == null || dbUser == null) { return null; } return new ServerInfo(environmentName, environmentType, hostingProvider, serverType, microUser, dbUser, null); } /// /// Returns the proper StoreName enum given the folder name for the store used in our current cert automation. /// /// /// public static StoreName GetStoreNameByFolderName(string folderName) { StoreName storeName; switch (folderName) { case "ia": storeName = StoreName.CertificateAuthority; break; case "personal": storeName = StoreName.My; break; case "root": storeName = StoreName.Root; break; case "trustedpeople": storeName = StoreName.TrustedPeople; break; default: throw new Exception($"Could not identify store name {folderName}"); } return storeName; } /// /// Stolen shamelessly from the md5 documentation https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.md5?view=netframework-4.8 /// Modified to accept a Stream rather than converting a string. /// /// Data stream to hash /// Calculated md5 hash public static string GetMd5HashString(Stream stream) { var md5Hash = MD5.Create(); // Convert the input string to a byte array and compute the hash. byte[] data = md5Hash.ComputeHash(stream); // Create a new Stringbuilder to collect the bytes // and create a string. StringBuilder sBuilder = new StringBuilder(); // Loop through each byte of the hashed data // and format each one as a hexadecimal string. for (int i = 0; i < data.Length; i++) { sBuilder.Append(data[i].ToString("x2")); } // Return the hexadecimal string. return sBuilder.ToString(); } /// /// Stolen shamelessly from the md5 documentation https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.md5?view=netframework-4.8 /// Modified to accept a Stream to hash rather than converting a string. /// /// Stream to hash and compare against provided hash string /// Hash to match against. /// public static bool VerifyMd5Hash(Stream stream, string hash) { var md5Hash = MD5.Create(); // Hash the input. string hashOfInput = GetMd5HashString(stream); // Create a StringComparer an compare the hashes. StringComparer comparer = StringComparer.OrdinalIgnoreCase; if (0 == comparer.Compare(hashOfInput, hash)) { return true; } else { return false; } } } }