CertService.cs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. using Microsoft.Extensions.Caching.Memory;
  2. using Microsoft.Extensions.Logging;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Diagnostics;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Net;
  9. using System.Security.Cryptography;
  10. using System.Security.Cryptography.X509Certificates;
  11. using System.Text;
  12. namespace FastGithub.HttpServer.Certs
  13. {
  14. /// <summary>
  15. /// 证书服务
  16. /// </summary>
  17. sealed class CertService
  18. {
  19. private const string CACERT_PATH = "cacert";
  20. private readonly IMemoryCache serverCertCache;
  21. private readonly IEnumerable<ICaCertInstaller> certInstallers;
  22. private readonly ILogger<CertService> logger;
  23. private X509Certificate2? caCert;
  24. /// <summary>
  25. /// 获取证书文件路径
  26. /// </summary>
  27. public string CaCerFilePath { get; } = OperatingSystem.IsLinux() ? $"{CACERT_PATH}/fastgithub.crt" : $"{CACERT_PATH}/fastgithub.cer";
  28. /// <summary>
  29. /// 获取私钥文件路径
  30. /// </summary>
  31. public string CaKeyFilePath { get; } = $"{CACERT_PATH}/fastgithub.key";
  32. /// <summary>
  33. /// 证书服务
  34. /// </summary>
  35. /// <param name="serverCertCache"></param>
  36. /// <param name="certInstallers"></param>
  37. /// <param name="logger"></param>
  38. public CertService(
  39. IMemoryCache serverCertCache,
  40. IEnumerable<ICaCertInstaller> certInstallers,
  41. ILogger<CertService> logger)
  42. {
  43. this.serverCertCache = serverCertCache;
  44. this.certInstallers = certInstallers;
  45. this.logger = logger;
  46. Directory.CreateDirectory(CACERT_PATH);
  47. }
  48. /// <summary>
  49. /// 生成CA证书
  50. /// </summary>
  51. public bool CreateCaCertIfNotExists()
  52. {
  53. if (File.Exists(this.CaCerFilePath) && File.Exists(this.CaKeyFilePath))
  54. {
  55. return false;
  56. }
  57. File.Delete(this.CaCerFilePath);
  58. File.Delete(this.CaKeyFilePath);
  59. var notBefore = DateTimeOffset.Now.AddDays(-1);
  60. var notAfter = DateTimeOffset.Now.AddYears(10);
  61. var subjectName = new X500DistinguishedName($"CN={nameof(FastGithub)}");
  62. this.caCert = CertGenerator.CreateCACertificate(subjectName, notBefore, notAfter);
  63. var privateKey = this.caCert.GetRSAPrivateKey()?.ExportRSAPrivateKey();
  64. var privateKeyPem = PemEncoding.Write("RSA PRIVATE KEY", privateKey);
  65. File.WriteAllText(this.CaKeyFilePath, new string(privateKeyPem), Encoding.ASCII);
  66. var cert = this.caCert.Export(X509ContentType.Cert);
  67. var certPem = PemEncoding.Write("CERTIFICATE", cert);
  68. File.WriteAllText(this.CaCerFilePath, new string(certPem), Encoding.ASCII);
  69. return true;
  70. }
  71. /// <summary>
  72. /// 安装和信任CA证书
  73. /// </summary>
  74. public void InstallAndTrustCaCert()
  75. {
  76. var installer = this.certInstallers.FirstOrDefault(item => item.IsSupported());
  77. if (installer != null)
  78. {
  79. installer.Install(this.CaCerFilePath);
  80. }
  81. else
  82. {
  83. this.logger.LogWarning($"请根据你的系统平台手动安装和信任CA证书{this.CaCerFilePath}");
  84. }
  85. GitConfigSslverify(false);
  86. }
  87. /// <summary>
  88. /// 设置ssl验证
  89. /// </summary>
  90. /// <param name="value">是否验证</param>
  91. /// <returns></returns>
  92. public static bool GitConfigSslverify(bool value)
  93. {
  94. try
  95. {
  96. Process.Start(new ProcessStartInfo
  97. {
  98. FileName = "git",
  99. Arguments = $"config --global http.sslverify {value.ToString().ToLower()}",
  100. UseShellExecute = true,
  101. CreateNoWindow = true,
  102. WindowStyle = ProcessWindowStyle.Hidden
  103. });
  104. return true;
  105. }
  106. catch (Exception)
  107. {
  108. return false;
  109. }
  110. }
  111. /// <summary>
  112. /// 获取颁发给指定域名的证书
  113. /// </summary>
  114. /// <param name="domain"></param>
  115. /// <returns></returns>
  116. public X509Certificate2 GetOrCreateServerCert(string? domain)
  117. {
  118. if (this.caCert == null)
  119. {
  120. using var rsa = RSA.Create();
  121. rsa.ImportFromPem(File.ReadAllText(this.CaKeyFilePath));
  122. this.caCert = new X509Certificate2(this.CaCerFilePath).CopyWithPrivateKey(rsa);
  123. }
  124. var key = $"{nameof(CertService)}:{domain}";
  125. var endCert = this.serverCertCache.GetOrCreate(key, GetOrCreateCert);
  126. return endCert!;
  127. // 生成域名的1年证书
  128. X509Certificate2 GetOrCreateCert(ICacheEntry entry)
  129. {
  130. var notBefore = DateTimeOffset.Now.AddDays(-1);
  131. var notAfter = DateTimeOffset.Now.AddYears(1);
  132. entry.SetAbsoluteExpiration(notAfter);
  133. var extraDomains = GetExtraDomains();
  134. var subjectName = new X500DistinguishedName($"CN={domain}");
  135. var endCert = CertGenerator.CreateEndCertificate(this.caCert, subjectName, extraDomains, notBefore, notAfter);
  136. // 重新初始化证书,以兼容win平台不能使用内存证书
  137. return new X509Certificate2(endCert.Export(X509ContentType.Pfx));
  138. }
  139. }
  140. /// <summary>
  141. /// 获取域名
  142. /// </summary>
  143. /// <param name="domain"></param>
  144. /// <returns></returns>
  145. private static IEnumerable<string> GetExtraDomains()
  146. {
  147. yield return Environment.MachineName;
  148. yield return IPAddress.Loopback.ToString();
  149. yield return IPAddress.IPv6Loopback.ToString();
  150. }
  151. }
  152. }