2
0

CertService.cs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  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 privateKeyPem = this.caCert.GetRSAPrivateKey()?.ExportRSAPrivateKeyPem();
  64. File.WriteAllText(this.CaKeyFilePath, new string(privateKeyPem), Encoding.ASCII);
  65. var certPem = this.caCert.ExportCertificatePem();
  66. File.WriteAllText(this.CaCerFilePath, new string(certPem), Encoding.ASCII);
  67. return true;
  68. }
  69. /// <summary>
  70. /// 安装和信任CA证书
  71. /// </summary>
  72. public void InstallAndTrustCaCert()
  73. {
  74. var installer = this.certInstallers.FirstOrDefault(item => item.IsSupported());
  75. if (installer != null)
  76. {
  77. installer.Install(this.CaCerFilePath);
  78. }
  79. else
  80. {
  81. this.logger.LogWarning($"请根据你的系统平台手动安装和信任CA证书{this.CaCerFilePath}");
  82. }
  83. GitConfigSslverify(false);
  84. }
  85. /// <summary>
  86. /// 设置ssl验证
  87. /// </summary>
  88. /// <param name="value">是否验证</param>
  89. /// <returns></returns>
  90. public static bool GitConfigSslverify(bool value)
  91. {
  92. try
  93. {
  94. Process.Start(new ProcessStartInfo
  95. {
  96. FileName = "git",
  97. Arguments = $"config --global http.sslverify {value.ToString().ToLower()}",
  98. UseShellExecute = true,
  99. CreateNoWindow = true,
  100. WindowStyle = ProcessWindowStyle.Hidden
  101. });
  102. return true;
  103. }
  104. catch (Exception)
  105. {
  106. return false;
  107. }
  108. }
  109. /// <summary>
  110. /// 获取颁发给指定域名的证书
  111. /// </summary>
  112. /// <param name="domain"></param>
  113. /// <returns></returns>
  114. public X509Certificate2 GetOrCreateServerCert(string? domain)
  115. {
  116. if (this.caCert == null)
  117. {
  118. using var rsa = RSA.Create();
  119. rsa.ImportFromPem(File.ReadAllText(this.CaKeyFilePath));
  120. this.caCert = new X509Certificate2(this.CaCerFilePath).CopyWithPrivateKey(rsa);
  121. }
  122. var key = $"{nameof(CertService)}:{domain}";
  123. var endCert = this.serverCertCache.GetOrCreate(key, GetOrCreateCert);
  124. return endCert!;
  125. // 生成域名的1年证书
  126. X509Certificate2 GetOrCreateCert(ICacheEntry entry)
  127. {
  128. var notBefore = DateTimeOffset.Now.AddDays(-1);
  129. var notAfter = DateTimeOffset.Now.AddYears(1);
  130. entry.SetAbsoluteExpiration(notAfter);
  131. var extraDomains = GetExtraDomains();
  132. var subjectName = new X500DistinguishedName($"CN={domain}");
  133. var endCert = CertGenerator.CreateEndCertificate(this.caCert, subjectName, extraDomains, notBefore, notAfter);
  134. // 重新初始化证书,以兼容win平台不能使用内存证书
  135. return new X509Certificate2(endCert.Export(X509ContentType.Pfx));
  136. }
  137. }
  138. /// <summary>
  139. /// 获取域名
  140. /// </summary>
  141. /// <param name="domain"></param>
  142. /// <returns></returns>
  143. private static IEnumerable<string> GetExtraDomains()
  144. {
  145. yield return Environment.MachineName;
  146. yield return IPAddress.Loopback.ToString();
  147. yield return IPAddress.IPv6Loopback.ToString();
  148. }
  149. }
  150. }