304 lines
11 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
} |