using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; namespace FastGithub.HttpServer.Certs { /// /// 证书服务 /// sealed class CertService { private const string CACERT_PATH = "cacert"; private readonly IMemoryCache serverCertCache; private readonly IEnumerable certInstallers; private readonly ILogger logger; private X509Certificate2? caCert; /// /// 获取证书文件路径 /// public string CaCerFilePath { get; } = OperatingSystem.IsLinux() ? $"{CACERT_PATH}/fastgithub.crt" : $"{CACERT_PATH}/fastgithub.cer"; /// /// 获取私钥文件路径 /// public string CaKeyFilePath { get; } = $"{CACERT_PATH}/fastgithub.key"; /// /// 证书服务 /// /// /// /// public CertService( IMemoryCache serverCertCache, IEnumerable certInstallers, ILogger logger) { this.serverCertCache = serverCertCache; this.certInstallers = certInstallers; this.logger = logger; Directory.CreateDirectory(CACERT_PATH); } /// /// 生成CA证书 /// public bool CreateCaCertIfNotExists() { if (File.Exists(this.CaCerFilePath) && File.Exists(this.CaKeyFilePath)) { return false; } File.Delete(this.CaCerFilePath); File.Delete(this.CaKeyFilePath); var notBefore = DateTimeOffset.Now.AddDays(-1); var notAfter = DateTimeOffset.Now.AddYears(10); var subjectName = new X500DistinguishedName($"CN={nameof(FastGithub)}"); this.caCert = CertGenerator.CreateCACertificate(subjectName, notBefore, notAfter); var privateKeyPem = this.caCert.GetRSAPrivateKey()?.ExportRSAPrivateKeyPem(); File.WriteAllText(this.CaKeyFilePath, new string(privateKeyPem), Encoding.ASCII); var certPem = this.caCert.ExportCertificatePem(); File.WriteAllText(this.CaCerFilePath, new string(certPem), Encoding.ASCII); return true; } /// /// 安装和信任CA证书 /// public void InstallAndTrustCaCert() { var installer = this.certInstallers.FirstOrDefault(item => item.IsSupported()); if (installer != null) { installer.Install(this.CaCerFilePath); } else { this.logger.LogWarning($"请根据你的系统平台手动安装和信任CA证书{this.CaCerFilePath}"); } GitConfigSslverify(false); } /// /// 设置ssl验证 /// /// 是否验证 /// public static bool GitConfigSslverify(bool value) { try { Process.Start(new ProcessStartInfo { FileName = "git", Arguments = $"config --global http.sslverify {value.ToString().ToLower()}", UseShellExecute = true, CreateNoWindow = true, WindowStyle = ProcessWindowStyle.Hidden }); return true; } catch (Exception) { return false; } } /// /// 获取颁发给指定域名的证书 /// /// /// public X509Certificate2 GetOrCreateServerCert(string? domain) { if (this.caCert == null) { using var rsa = RSA.Create(); rsa.ImportFromPem(File.ReadAllText(this.CaKeyFilePath)); this.caCert = new X509Certificate2(this.CaCerFilePath).CopyWithPrivateKey(rsa); } var key = $"{nameof(CertService)}:{domain}"; var endCert = this.serverCertCache.GetOrCreate(key, GetOrCreateCert); return endCert!; // 生成域名的1年证书 X509Certificate2 GetOrCreateCert(ICacheEntry entry) { var notBefore = DateTimeOffset.Now.AddDays(-1); var notAfter = DateTimeOffset.Now.AddYears(1); entry.SetAbsoluteExpiration(notAfter); var extraDomains = GetExtraDomains(); var subjectName = new X500DistinguishedName($"CN={domain}"); var endCert = CertGenerator.CreateEndCertificate(this.caCert, subjectName, extraDomains, notBefore, notAfter); // 重新初始化证书,以兼容win平台不能使用内存证书 return new X509Certificate2(endCert.Export(X509ContentType.Pfx)); } } /// /// 获取域名 /// /// /// private static IEnumerable GetExtraDomains() { yield return Environment.MachineName; yield return IPAddress.Loopback.ToString(); yield return IPAddress.IPv6Loopback.ToString(); } } }