CertService.cs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  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. }
  78. /// <summary>
  79. /// 安装CA证书
  80. /// </summary>
  81. private void InstallAndTrustCaCertAtWindows()
  82. {
  83. try
  84. {
  85. using var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine);
  86. store.Open(OpenFlags.ReadWrite);
  87. var caCert = new X509Certificate2(this.CaCerFilePath);
  88. var subjectName = caCert.Subject[3..];
  89. foreach (var item in store.Certificates.Find(X509FindType.FindBySubjectName, subjectName, false))
  90. {
  91. if (item.Thumbprint != caCert.Thumbprint)
  92. {
  93. store.Remove(item);
  94. }
  95. }
  96. if (store.Certificates.Find(X509FindType.FindByThumbprint, caCert.Thumbprint, true).Count == 0)
  97. {
  98. store.Add(caCert);
  99. }
  100. store.Close();
  101. }
  102. catch (Exception ex)
  103. {
  104. this.logger.LogWarning($"安装证书{this.CaCerFilePath}失败:请手动安装到“将所有的证书都放入下载存储”\\“受信任的根证书颁发机构”", ex);
  105. }
  106. }
  107. /// <summary>
  108. /// 获取颁发给指定域名的证书
  109. /// </summary>
  110. /// <param name="domain"></param>
  111. /// <returns></returns>
  112. public X509Certificate2 GetOrCreateServerCert(string? domain)
  113. {
  114. var key = $"{nameof(CertService)}:{domain}";
  115. return this.serverCertCache.GetOrCreate(key, GetOrCreateCert);
  116. // 生成域名的1年证书
  117. X509Certificate2 GetOrCreateCert(ICacheEntry entry)
  118. {
  119. var domains = GetDomains(domain).Distinct();
  120. var validFrom = DateTime.Today.AddDays(-1);
  121. var validTo = DateTime.Today.AddYears(1);
  122. entry.SetAbsoluteExpiration(validTo);
  123. return CertGenerator.GenerateByCa(domains, KEY_SIZE_BITS, validFrom, validTo, this.CaCerFilePath, this.CaKeyFilePath);
  124. }
  125. }
  126. /// <summary>
  127. /// 获取域名
  128. /// </summary>
  129. /// <param name="domain"></param>
  130. /// <returns></returns>
  131. private static IEnumerable<string> GetDomains(string? domain)
  132. {
  133. if (string.IsNullOrEmpty(domain) == false)
  134. {
  135. yield return domain;
  136. yield break;
  137. }
  138. yield return LocalMachine.Name;
  139. foreach (var address in LocalMachine.GetAllIPv4Addresses())
  140. {
  141. yield return address.ToString();
  142. }
  143. }
  144. }
  145. }