using Microsoft.Extensions.Logging; using Newtonsoft.Json; using System; using System.Diagnostics; using System.Diagnostics.Metrics; using System.Reflection; using System.Runtime.ConstrainedExecution; using System.Security.AccessControl; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Xml.Linq; namespace CAUtilLib { public partial class CaUtil_openssl { /// <summary> /// 建構子生成路徑可透過SetPath進行路徑覆寫 /// </summary> public CaUtil_openssl(ILogger<CaUtil> logger) { if (string.IsNullOrWhiteSpace(this.path)) { string path = Directory.GetCurrentDirectory(); //string parentPath = Directory.GetParent(Directory.GetParent(Directory.GetParent(Directory.GetParent(path).ToString()).ToString()).ToString()) + @"\"; string filePath = Path.Combine(path, "temp"); bool exists = Directory.Exists(filePath); if (!exists) { CreateFolder(filePath); MoveFile(@"zerova_v3.cnf", filePath + @"\zerova_v3.cnf"); } this.path = filePath; } this.logger = logger; } private string path = ""; private readonly ILogger<CaUtil> logger; /// <summary> /// 檔案路徑 /// </summary> public string SavePath { get => path; set { if (this.path == value) { return; } this.path = value; string filePath = this.path; CreateFolder(filePath); MoveFile(@"zerova_v3.cnf", Path.Combine(filePath, @"zerova_v3.cnf")); } } public OSType OS { get; set; } private string ExecuteShell => OS == OSType.Windows ? "cmd.exe" : "/bin/bash"; private string CatCmd => OS == OSType.Windows ? "type " : "cat"; /// <summary> /// 生成RootCA憑證 /// </summary> /// <param name="name">Root CA</param> /// <param name="Days">憑證有效期限</param> /// <param name="SerialNumber">序號</param> /// <param name="Cn">主機名或網站地址</param> /// <param name="organizationName">組織名稱</param> /// <param name="Hash">使用SHA256/SHA512演算法進行簽名</param> /// <param name="RsaKey">生成2048位元或4096位元的私鑰</param> /// <param name="Caformat">生成crt 或者pem 格式</param> /// <returns></returns> public async Task<bool> CreateRootCA( string name, string commonName, string organizationName, string Country = "TW", string State = "Taipei", int Days = 3650, string SerialNumber = "", HashAlgorithm Hash = HashAlgorithm.SHA512, OpensslGenrsaRsa RsaKey = OpensslGenrsaRsa.Key4096) { if (string.IsNullOrEmpty(SerialNumber)) { SerialNumber = await GetOpenSSLRandSn(); } var status = false; var keyName = ""; keyName = name; string sn = "0x" + SerialNumber; HashAlgorithm ha = Hash; var sslKey = (int)RsaKey; var subjString = CreateSubjectString(CommonName: commonName, Organization: organizationName, Country: Country, State: State); if (await CreateKey(keyName, RsaKey) && await ExecShellCmd("openssl", $"req -x509 -new -nodes -set_serial {sn} -key {keyName}.key -{ha} -days {Days} -subj \"{subjString}\" -out {name}.crt ") && await MergeFile($"{name}.pem", $"{name}.crt", $"{keyName}.key") //&& await ExecShellCmd("certutil", $"-f -addstore root {RootCa}.crt ") //&& await ExecShellCmd("certutil", $"-f -addstore root {RootCa}.crt ") ) { return true; } return false; //create string[] strs = new string[5]; strs[0] = "openssl genrsa -out " + keyName + ".key " + sslKey + " "; strs[1] = "openssl req -x509 -new -nodes -set_serial " + sn + " -key " + keyName + ".key -" + ha + " -days " + Days + " -subj \"/C=TW/ST=Taipei/O=" + organizationName + "/OU=phihong_sub.IO/CN=" + commonName + "/emailAddress=phihong_sub@mail.com\" -out " + name + ".crt "; strs[2] = CatCmd + name + ".crt " + keyName + ".key > " + name + ".pem "; strs[3] = "certutil -f -addstore root " + name + ".crt "; strs[4] = "certutil -f -addstore root " + name + ".crt "; for (int i = 0; i < strs.Length; i++) { var process = new Process { StartInfo = new ProcessStartInfo { FileName = ExecuteShell, Arguments = "/C " + strs[i], WorkingDirectory = this.path, StandardOutputEncoding = Encoding.UTF8, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, Verb = "runas" } }; process.Start(); _ = process.StandardOutput.ReadToEndAsync().ContinueWith(( t => { logger.LogTrace(t.Result); })); _ = process.StandardError.ReadToEndAsync().ContinueWith((t => { logger.LogTrace(t.Result); })); await process.WaitForExitAsync(); if (process.ExitCode != 0) { return false; } } return status; } /// <summary> /// 生成SubCA子憑證 /// </summary> /// <param name="RootCaName">CA憑證名稱</param> /// <param name="Days">憑證有效期限</param> /// <param name="SubCA">子憑證名稱</param> /// <param name="SerialNumber">序號</param> /// <param name="commonName">主機名或網站地址</param> /// <param name="organizationName">組織名稱</param> /// <param name="Hash">使用SHA256/SHA512演算法進行簽名</param> /// <param name="RsaKey">生成2048位元或4096位元的私鑰</param> /// <param name="Caformat">生成crt 或者pem 格式</param> /// <returns></returns> public async Task<bool> CreateSubCA(string RootCa, string Days, string SubCA, string SerialNumber, string commonName, string organizationName, HashAlgorithm Hash, OpensslGenrsaRsa RsaKey, Certificateformat Caformat) { if (string.IsNullOrEmpty(SerialNumber)) { SerialNumber = await GetOpenSSLRandSn(); } var status = false; var str = ""; string sn = "0x" + SerialNumber; //hashAlgorithm ha = Hash; var sslKey = (int)RsaKey; Certificateformat format = Caformat; if (await ExecShellCmd("openssl", $"genrsa -out {SubCA}.key {sslKey}") && await ExecShellCmd("openssl", $"req -new -{Hash} -nodes -key {SubCA}.key -out {SubCA}.csr -subj \"/C=TW/ST=Taipei/O={organizationName}/OU=phihong_sub.IO/CN={commonName}/emailAddress=phihong_sub@mail.com\" ") && await ExecShellCmd("openssl", $"x509 -set_serial {sn} -req -in {SubCA}.csr -CA {RootCa}.crt -CAkey {RootCa}.key -CAcreateserial -out {SubCA}.crt -days {Days} -sha256 -extfile zerova_v3.cnf ") && await MergeFile($"{SubCA}.pem", $"{SubCA}.crt", $"{SubCA}.key")) { return true; } return false; string[] strs = new string[4]; strs[0] = "openssl genrsa -out " + SubCA + ".key " + sslKey + " "; strs[1] = "openssl req -new -" + Hash + " -nodes -key " + SubCA + ".key -out " + SubCA + ".csr -subj \"/C=TW/ST=Taipei/O=" + organizationName + "/OU=phihong_sub.IO/CN=" + commonName + "/emailAddress=phihong_sub@mail.com\" "; //var str8 = "openssl x509 -set_serial " + sn + " -req -in " + subKeyName + ".csr -CA " + caPath + "\\" + crtName + ".crt -CAkey " + this.path + "\\" + rootCaName + ".key -CAcreateserial -out " + subKeyName + ".crt -days " + days + " -sha256 -extfile zerova_v3.cnf "; strs[2] = "openssl x509 -set_serial " + sn + " -req -in " + SubCA + ".csr -CA " + RootCa + ".crt -CAkey " + this.path + "\\" + RootCa + ".key -CAcreateserial -out " + SubCA + ".crt -days " + Days + " -sha256 -extfile zerova_v3.cnf "; strs[3] = CatCmd + SubCA + ".crt " + SubCA + ".key > " + SubCA + ".pem "; for (int i = 0; i < strs.Length; i++) { var process = new Process { StartInfo = new ProcessStartInfo { FileName = ExecuteShell, Arguments = "/C " + strs[i], WorkingDirectory = this.path,//"D:\\project\\vs\\ConsoleApp2", RedirectStandardOutput = true, UseShellExecute = false, Verb = "runas" } }; process.Start(); string output = process.StandardOutput.ReadToEnd(); await process.WaitForExitAsync(); } return status; } public async Task<bool> SignCsr(string SubCA, string RootCa, int? Days = null, string? SerialNumber = null) { Days ??= 3650; if (string.IsNullOrEmpty(SerialNumber)) { SerialNumber = await GetOpenSSLRandSn(); } string sn = "0x" + SerialNumber; if (await ExecShellCmd("openssl", $"x509 -set_serial {sn} -req -in {SubCA}.csr -CA {RootCa}.crt -CAkey {RootCa}.key -CAcreateserial -out {SubCA}.crt -days {Days} -sha256 -extfile zerova_v3.cnf ")) { return true; } return false; } /// <summary> /// 生成Csr 檔案 /// </summary> /// <param name="Key">key 名稱</param> /// <param name="Csr">csr 名稱</param> /// <param name="commonName">主機名或網站地址</param> /// <param name="organizationName">組織名稱</param> /// <returns></returns> public async Task<bool> CreateCsr( string Key, string Csr, string commonName, string organizationName, HashAlgorithm Hash = HashAlgorithm.SHA512) { var status = false; var subjString = CreateSubjectString(CommonName: commonName, Organization: organizationName, Country: "TW", State: "Taipei"); if (await ExecShellCmd("openssl", $"req -new -{Hash} -nodes -key {Key}.key -out {Csr}.csr -subj \"{subjString}\" ")) { return true; } return false; string[] strs = new string[4]; strs[0] = "openssl req -new -SHA256 -nodes -key " + Key + ".key -out " + Csr + ".csr -subj \"/C=TW/ST=Taipei/O=" + organizationName + "/OU=phihong_sub.IO/CN=" + commonName + "/emailAddress=phihong_sub@mail.com\" "; for (int i = 0; i < strs.Length; i++) { var process = new Process { StartInfo = new ProcessStartInfo { FileName = ExecuteShell, Arguments = "/C " + strs[i], WorkingDirectory = this.path,//"D:\\project\\vs\\ConsoleApp2", RedirectStandardOutput = true, UseShellExecute = false, Verb = "runas" } }; process.Start(); string output = process.StandardOutput.ReadToEnd(); await process.WaitForExitAsync(); } return status; } private object CreateSubjectString(string CommonName = "", string Organization = "", string Country = "", string State = "") { ///C=TW/ST=Taipei/O={organizationName}/OU=phihong_sub.IO/CN={commonName}/emailAddress=phihong_sub@mail.com\ var toReturn = string.Empty; if (!string.IsNullOrEmpty(Country)) { toReturn += $"/C={Country}"; } if (!string.IsNullOrEmpty(State)) { toReturn += $"/ST={State}"; } if (!string.IsNullOrEmpty(Organization)) { toReturn += $"/O={Organization}"; } if (!string.IsNullOrEmpty(CommonName)) { toReturn += $"/CN={CommonName}"; } return toReturn; } /// <summary> /// 讀取憑證資訊 /// </summary> /// <param name="path">檔案名稱</param> /// <param name="fileName">檔案路徑</param> /// <returns></returns> public string ReadCertificateHashData(string path, string fileName)//改成接收pem string 格式 { var json = ""; var file = Path.Combine(path, fileName); X509Certificate2 certificate = new X509Certificate2(file); X509Extension extension = certificate.Extensions["2.5.29.35"]; var issuer_key_hash = ""; if (extension != null) { issuer_key_hash = extension.Format(true); } string hashAlgorithm = certificate.SignatureAlgorithm.FriendlyName; string serialNumber = certificate.SerialNumber; byte[] issuerDER = certificate.IssuerName.RawData; SHA1 sha1 = SHA1.Create(); byte[] hashBytes = sha1.ComputeHash(issuerDER); var data = new { hashAlgorithm = hashAlgorithm, issuerNameHash = BitConverter.ToString(hashBytes).Replace("-", ""), issuerKeyHash = issuer_key_hash, serialNumber = serialNumber, thumbprint = certificate.Thumbprint }; string thumbprint = certificate.Thumbprint; json = JsonConvert.SerializeObject(data); return json; } /// <summary> /// 讀取憑證資訊 /// </summary> /// <param name="FileName">檔案名稱</param> /// <returns></returns> public string ReadCertificateHashData(string fileName) { var json = ""; var file = Path.Combine(this.path, fileName); X509Certificate2 certificate = new X509Certificate2(file); X509Extension extension = certificate.Extensions["2.5.29.35"]; var issuer_key_hash = ""; if (extension != null) { issuer_key_hash = extension.Format(true); } string hashAlgorithm = certificate.SignatureAlgorithm.FriendlyName; string serialNumber = certificate.SerialNumber; byte[] issuerDER = certificate.IssuerName.RawData; SHA1 sha1 = SHA1.Create(); byte[] hashBytes = sha1.ComputeHash(issuerDER); var data = new { hashAlgorithm, issuerNameHash = BitConverter.ToString(hashBytes).Replace("-", ""), issuerKeyHash = issuer_key_hash, serialNumber, thumbprint = certificate.Thumbprint }; string thumbprint = certificate.Thumbprint; json = JsonConvert.SerializeObject(data); return json; } /// <summary> /// PEM 格式的憑證的內容 /// </summary> /// <param name="str">憑證內容</param> /// <returns></returns> public string ReadCertificateHashDataByString(string str) { var json = ""; byte[] UTF8bytes = Encoding.UTF8.GetBytes(str); X509Certificate2 certificate = new X509Certificate2(UTF8bytes); X509Extension extension = certificate.Extensions["2.5.29.35"]; var issuer_key_hash = ""; if (extension != null) { issuer_key_hash = extension.Format(true); } string hashAlgorithm = certificate.SignatureAlgorithm.FriendlyName; string serialNumber = certificate.SerialNumber; byte[] issuerDER = certificate.IssuerName.RawData; SHA1 sha1 = SHA1.Create(); byte[] hashBytes = sha1.ComputeHash(issuerDER); var data = new { hashAlgorithm = hashAlgorithm, issuerNameHash = BitConverter.ToString(hashBytes).Replace("-", ""), issuerKeyHash = issuer_key_hash, serialNumber = serialNumber, thumbprint = certificate.Thumbprint }; string thumbprint = certificate.Thumbprint; json = JsonConvert.SerializeObject(data); return json; } /// <summary> /// 驗證client憑證 /// </summary> /// <param name="StrCA">CA憑證</param> /// <param name="StrSub">子憑證</param> /// <param name="Url">url localhost:3000</param> /// <returns></returns> public async Task<bool> VerifyClient(string CA, string SubCA, string Url) { var str = ""; bool isExists = false; if (await ExecShellCmd("openssl", $"s_client -connect {Url} -CAfile {CA} -cert {SubCA} -tls1_2 -state")) { return true; } return false; var str1 = "openssl s_client -connect " + Url + " -CAfile " + CA + " -cert " + SubCA + " -tls1_2 -state "; str = await ClientCmd(str1); string searchString = "(ok)"; string line = GetLineFromString(str, searchString); if (line != null) isExists = line.Contains(searchString); return isExists; } /// <summary> /// Cmd字串指令 /// </summary> /// <param name="str1">字串指令</param> /// <returns></returns> private async Task<string> ClientCmd(string str1) { var status = false; var str = ""; string[] strs = { str1 }; for (int i = 0; i < strs.Length; i++) { var process = new Process { StartInfo = new ProcessStartInfo { FileName = ExecuteShell, Arguments = "/C " + strs[i], WorkingDirectory = this.path,//"D:\\project\\vs\\ConsoleApp2", RedirectStandardOutput = true, UseShellExecute = false, //Verb = "runas" } }; process.Start(); var output = new List<string>(); while (process.StandardOutput.Peek() > -1) { output.Add(process.StandardOutput.ReadLine()); } process.Kill(); str = string.Join("", output.ToArray()); } return str; } /// <summary> /// 將Root CA與Sub CA進行憑證驗證 /// </summary> /// <param name="Ca">CA檔案名稱</param> /// <param name="Sub">SubCA憑證名稱</param> /// <returns>True/Flase</returns> public async Task<bool> VerifyCertificateByCertificate(string Sub, string Ca) { if (await ExecShellCmd("openssl", $"verify -CAfile {Ca} {Sub}")) { return true; } return false; var status = true; var str = ""; var str1 = "openssl verify -CAfile " + Ca + " " + Sub; bool isExists = false; str = await ClientCmd(str1); string searchString = "OK"; string line = GetLineFromString(str, searchString); if (line != null) isExists = line.Contains(searchString); return isExists; } public async Task<bool> VerifyCertificateByCertificates(string Sub, string CaPath) { if (await ExecShellCmd("openssl", $"verify -CApath {CaPath} {Sub}")) { return true; } return false; } public async Task<bool> CalculateHash(string CrtName,string hashName) { if (await ExecShellCmd("openssl", $"x509 -in {CrtName} -hash -noout -out {hashName}")) { return true; } return false; } /// <summary> /// Crt轉Pem /// </summary> /// <param name="CaName">檔案名稱</param> /// <returns></returns> public async Task<bool> CrtToPem(string CaName) { if (await ExecShellCmd("openssl", $"x509 -in {CaName}.crt -out {CaName}.pem")) { return true; } return false; var status = false; var str = "openssl x509 -in " + CaName + ".crt -out " + CaName + ".pem "; var process = new Process { StartInfo = new ProcessStartInfo { FileName = ExecuteShell, Arguments = "/C " + str, WorkingDirectory = this.path, RedirectStandardOutput = true, UseShellExecute = false, Verb = "runas" } }; process.Start(); string output = process.StandardOutput.ReadToEnd(); await process.WaitForExitAsync(); return status; } /// <summary> /// Pem轉Crt /// </summary> /// <param name="CaName">檔案名稱</param> /// <returns></returns> public async Task<bool> PemToCrt(string CaName) { if (await ExecShellCmd("openssl", $"x509 -outform der -in {CaName}.pem -out {CaName}.crt")) { return true; } return false; var status = false; var str = "openssl x509 -outform der -in " + CaName + ".pem -out " + CaName + ".crt "; var process = new Process { StartInfo = new ProcessStartInfo { FileName = ExecuteShell, Arguments = "/C " + str, WorkingDirectory = this.path, RedirectStandardOutput = true, UseShellExecute = false, Verb = "runas" } }; process.Start(); string output = process.StandardOutput.ReadToEnd(); await process.WaitForExitAsync(); return status; } public async Task<bool> CreateKey(string keyName, OpensslGenrsaRsa RsaKey = OpensslGenrsaRsa.Key4096) { var sslKey = (int)RsaKey; if (await ExecShellCmd("openssl", $"genrsa -out {keyName}.key {sslKey}")) { return true; } return false; } /// <summary> /// 產生資料夾 /// </summary> /// <param name="path"></param> /// <returns></returns> private bool CreateFolder(string path) { string subPath = path; bool status = true; bool exists = Directory.Exists(subPath); status = !exists; if (status) { Directory.CreateDirectory(subPath); } return status; } /// <summary> /// 確認檔案是否存在 /// </summary> /// <param name="filePath"></param> /// <returns></returns> private bool CheckFiles(string FilePath) { bool status = true; bool exists = File.Exists(FilePath); status = exists; return status; } /// <summary> /// 檔案搬移 /// </summary> /// <param name="file"></param> /// <param name="moveTo"></param> /// <returns></returns> private bool MoveFile(string file, string moveTo) { bool status = false; if (CheckFiles(file) && !CheckFiles(moveTo)) { //File.Move(file, moveTo); File.Copy(file, moveTo, true); status = true; } else { } return status; } /// <summary> /// 取得第幾行的字串 /// </summary> /// <param name="InputString"></param> /// <param name="SearchString"></param> /// <returns></returns> private static string GetLineFromString(string InputString, string SearchString) { using (StringReader reader = new StringReader(InputString)) { int lineNumber = 1; string line = ""; while ((line = reader.ReadLine()) != null) { if (line.Contains(SearchString)) { return line; } lineNumber++; } return reader.ReadLine(); } } } }