2
0

CertService.cs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. using FastGithub.Configuration;
  2. using Microsoft.Extensions.Caching.Memory;
  3. using Microsoft.Extensions.Logging;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Security.Cryptography.X509Certificates;
  9. namespace FastGithub.ReverseProxy
  10. {
  11. /// <summary>
  12. /// 证书服务
  13. /// </summary>
  14. sealed class CertService
  15. {
  16. private const string CAPATH = "CACert";
  17. private const int KEY_SIZE_BITS = 2048;
  18. private readonly IMemoryCache serverCertCache;
  19. private readonly ILogger<CertService> logger;
  20. /// <summary>
  21. /// 获取证书文件路径
  22. /// </summary>
  23. public string CaCerFilePath { get; } = $"{CAPATH}/{nameof(FastGithub)}.cer";
  24. /// <summary>
  25. /// 获取私钥文件路径
  26. /// </summary>
  27. public string CaKeyFilePath { get; } = $"{CAPATH}/{nameof(FastGithub)}.key";
  28. /// <summary>
  29. /// 证书服务
  30. /// </summary>
  31. /// <param name="logger"></param>
  32. public CertService(
  33. IMemoryCache serverCertCache,
  34. ILogger<CertService> logger)
  35. {
  36. this.serverCertCache = serverCertCache;
  37. this.logger = logger;
  38. Directory.CreateDirectory(CAPATH);
  39. }
  40. /// <summary>
  41. /// 生成CA证书
  42. /// </summary>
  43. public bool CreateCaCertIfNotExists()
  44. {
  45. if (File.Exists(this.CaCerFilePath) && File.Exists(this.CaKeyFilePath))
  46. {
  47. return false;
  48. }
  49. File.Delete(this.CaCerFilePath);
  50. File.Delete(this.CaKeyFilePath);
  51. var validFrom = DateTime.Today.AddDays(-1);
  52. var validTo = DateTime.Today.AddYears(10);
  53. CertGenerator.GenerateBySelf(new[] { nameof(FastGithub) }, KEY_SIZE_BITS, validFrom, validTo, this.CaCerFilePath, this.CaKeyFilePath);
  54. return true;
  55. }
  56. /// <summary>
  57. /// 安装和信任CA证书
  58. /// </summary>
  59. public void InstallAndTrustCaCert()
  60. {
  61. if (OperatingSystem.IsWindows())
  62. {
  63. this.InstallAndTrustCaCertAtWindows();
  64. }
  65. else if (OperatingSystem.IsLinux())
  66. {
  67. this.logger.LogWarning($"不支持自动安装证书{this.CaCerFilePath}:请根据具体linux发行版安装CA证书");
  68. }
  69. else if (OperatingSystem.IsMacOS())
  70. {
  71. this.logger.LogWarning($"不支持自动安装证书{this.CaCerFilePath}:请手工安装CA证书然后设置信任CA证书");
  72. }
  73. else
  74. {
  75. this.logger.LogWarning($"不支持自动安装证书{this.CaCerFilePath}:请根据你的系统平台手工安装和信任CA证书");
  76. }
  77. GitUtil.ConfigSslverify(false);
  78. }
  79. /// <summary>
  80. /// 安装CA证书
  81. /// </summary>
  82. private void InstallAndTrustCaCertAtWindows()
  83. {
  84. try
  85. {
  86. using var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
  87. store.Open(OpenFlags.ReadWrite);
  88. var caCert = new X509Certificate2(this.CaCerFilePath);
  89. var subjectName = caCert.Subject[3..];
  90. foreach (var item in store.Certificates.Find(X509FindType.FindBySubjectName, subjectName, false))
  91. {
  92. if (item.Thumbprint != caCert.Thumbprint)
  93. {
  94. store.Remove(item);
  95. }
  96. }
  97. if (store.Certificates.Find(X509FindType.FindByThumbprint, caCert.Thumbprint, true).Count == 0)
  98. {
  99. store.Add(caCert);
  100. }
  101. store.Close();
  102. }
  103. catch (Exception ex)
  104. {
  105. this.logger.LogWarning($"安装证书{this.CaCerFilePath}失败:请手动安装到“将所有的证书都放入下载存储”\\“受信任的根证书颁发机构”", ex);
  106. }
  107. }
  108. /// <summary>
  109. /// 获取颁发给指定域名的证书
  110. /// </summary>
  111. /// <param name="domain"></param>
  112. /// <returns></returns>
  113. public X509Certificate2 GetOrCreateServerCert(string? domain)
  114. {
  115. var key = $"{nameof(CertService)}:{domain}";
  116. return this.serverCertCache.GetOrCreate(key, GetOrCreateCert);
  117. // 生成域名的1年证书
  118. X509Certificate2 GetOrCreateCert(ICacheEntry entry)
  119. {
  120. var domains = GetDomains(domain).Distinct();
  121. var validFrom = DateTime.Today.AddDays(-1);
  122. var validTo = DateTime.Today.AddYears(1);
  123. entry.SetAbsoluteExpiration(validTo);
  124. return CertGenerator.GenerateByCa(domains, KEY_SIZE_BITS, validFrom, validTo, this.CaCerFilePath, this.CaKeyFilePath);
  125. }
  126. }
  127. /// <summary>
  128. /// 获取域名
  129. /// </summary>
  130. /// <param name="domain"></param>
  131. /// <returns></returns>
  132. private static IEnumerable<string> GetDomains(string? domain)
  133. {
  134. if (string.IsNullOrEmpty(domain) == false)
  135. {
  136. yield return domain;
  137. yield break;
  138. }
  139. yield return LocalMachine.Name;
  140. foreach (var address in LocalMachine.GetAllIPv4Addresses())
  141. {
  142. yield return address.ToString();
  143. }
  144. }
  145. }
  146. }