ps/Modules/Alkami.Ops.Certificates/Utilities/Extensions.cs
2023-05-30 22:51:22 -07:00

304 lines
11 KiB
C#

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
{
/// <summary>
/// Returns a List of certificates from an X509Certificate2Collection
/// </summary>
/// <param name="collection"></param>
public static List<X509Certificate2> ToList(this X509Certificate2Collection collection)
{
var list = new List<X509Certificate2>(collection.Count);
foreach (var cert in collection)
{
list.Add(cert);
}
return list;
}
/// <summary>
/// Returns a List of certificates from an X509Certificate2Collection
/// </summary>
/// <param name="collection"></param>
public static List<X509Certificate2> ToList(this X509ChainElementCollection collection)
{
var list = new List<X509Certificate2>(collection.Count);
foreach (var chainelement in collection)
{
list.Add(chainelement.Certificate);
}
return list;
}
/// <summary>
/// Adds a value to a dictionary if it doesn't exist and returns true. Returns false if the key already exists.
/// </summary>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
/// <param name="dictionary"></param>
/// <param name="key">The dictionary key to set.</param>
/// <param name="value">The value to store in the dictionary.</param>
/// <returns></returns>
public static bool TryAdd<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, TValue value)
{
if (dictionary.ContainsKey(key))
{
return false;
}
else
{
dictionary[key] = value;
return true;
}
}
/// <summary>
/// Deletes all files/directories in a directory without deleting the directory itself.
/// </summary>
/// <param name="directory"></param>
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);
}
}
/// <summary>
/// Returns true if the two files are equal.
/// </summary>
/// <param name="first">First file to compare.</param>
/// <param name="second">Second file to compare.</param>
/// <returns></returns>
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;
}
/// <summary>
/// Loads an EnvironmentInfo given a server to read properties from.
/// </summary>
/// <param name="serverName">Server name.</param>
/// <returns></returns>
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);
}
/// <summary>
/// Returns the proper StoreName enum given the folder name for the store used in our current cert automation.
/// </summary>
/// <param name="folderName"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="stream">Data stream to hash</param>
/// <returns>Calculated md5 hash</returns>
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();
}
/// <summary>
/// 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.
/// </summary>
/// <param name="stream">Stream to hash and compare against provided hash string</param>
/// <param name="hash">Hash to match against.</param>
/// <returns></returns>
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;
}
}
}
}