CertGenerator.cs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. using Org.BouncyCastle.Asn1;
  2. using Org.BouncyCastle.Asn1.Pkcs;
  3. using Org.BouncyCastle.Asn1.X509;
  4. using Org.BouncyCastle.Asn1.X9;
  5. using Org.BouncyCastle.Crypto;
  6. using Org.BouncyCastle.Crypto.Generators;
  7. using Org.BouncyCastle.Crypto.Operators;
  8. using Org.BouncyCastle.Crypto.Parameters;
  9. using Org.BouncyCastle.Math;
  10. using Org.BouncyCastle.OpenSsl;
  11. using Org.BouncyCastle.Pkcs;
  12. using Org.BouncyCastle.Security;
  13. using Org.BouncyCastle.X509;
  14. using Org.BouncyCastle.X509.Extension;
  15. using System;
  16. using System.Collections.Generic;
  17. using System.IO;
  18. using System.Linq;
  19. using System.Net;
  20. using System.Text;
  21. using X509Certificate2 = System.Security.Cryptography.X509Certificates.X509Certificate2;
  22. namespace FastGithub.ReverseProxy
  23. {
  24. /// <summary>
  25. /// 证书生成器
  26. /// </summary>
  27. static class CertGenerator
  28. {
  29. private static readonly SecureRandom secureRandom = new();
  30. /// <summary>
  31. /// 生成自签名证书
  32. /// </summary>
  33. /// <param name="domains"></param>
  34. /// <param name="keySizeBits"></param>
  35. /// <param name="validFrom"></param>
  36. /// <param name="validTo"></param>
  37. /// <param name="caPublicCerPath"></param>
  38. /// <param name="caPrivateKeyPath"></param>
  39. public static void GenerateBySelf(IEnumerable<string> domains, int keySizeBits, DateTime validFrom, DateTime validTo, string caPublicCerPath, string caPrivateKeyPath)
  40. {
  41. var keys = GenerateRsaKeyPair(keySizeBits);
  42. var cert = GenerateCertificate(domains, keys.Public, validFrom, validTo, domains.First(), null, keys.Private, 1);
  43. using var priWriter = new StreamWriter(caPrivateKeyPath);
  44. var priPemWriter = new PemWriter(priWriter);
  45. priPemWriter.WriteObject(keys.Private);
  46. priPemWriter.Writer.Flush();
  47. using var pubWriter = new StreamWriter(caPublicCerPath);
  48. var pubPemWriter = new PemWriter(pubWriter);
  49. pubPemWriter.WriteObject(cert);
  50. pubPemWriter.Writer.Flush();
  51. }
  52. /// <summary>
  53. /// 生成CA签名证书
  54. /// </summary>
  55. /// <param name="domains"></param>
  56. /// <param name="keySizeBits"></param>
  57. /// <param name="validFrom"></param>
  58. /// <param name="validTo"></param>
  59. /// <param name="caPublicCerPath"></param>
  60. /// <param name="caPrivateKeyPath"></param>
  61. /// <returns></returns>
  62. public static X509Certificate2 GenerateByCa(IEnumerable<string> domains, int keySizeBits, DateTime validFrom, DateTime validTo, string caPublicCerPath, string caPrivateKeyPath, string? password = default)
  63. {
  64. if (File.Exists(caPublicCerPath) == false)
  65. {
  66. throw new FileNotFoundException(caPublicCerPath);
  67. }
  68. if (File.Exists(caPrivateKeyPath) == false)
  69. {
  70. throw new FileNotFoundException(caPublicCerPath);
  71. }
  72. using var pubReader = new StreamReader(caPublicCerPath, Encoding.ASCII);
  73. var caCert = (X509Certificate)new PemReader(pubReader).ReadObject();
  74. using var priReader = new StreamReader(caPrivateKeyPath, Encoding.ASCII);
  75. var reader = new PemReader(priReader);
  76. var caPrivateKey = ((AsymmetricCipherKeyPair)reader.ReadObject()).Private;
  77. var caSubjectName = GetSubjectName(caCert);
  78. var keys = GenerateRsaKeyPair(keySizeBits);
  79. var cert = GenerateCertificate(domains, keys.Public, validFrom, validTo, caSubjectName, caCert.GetPublicKey(), caPrivateKey, null);
  80. return GeneratePfx(cert, keys.Private, password);
  81. }
  82. /// <summary>
  83. /// 生成私钥
  84. /// </summary>
  85. /// <param name="length"></param>
  86. /// <returns></returns>
  87. private static AsymmetricCipherKeyPair GenerateRsaKeyPair(int length)
  88. {
  89. var keygenParam = new KeyGenerationParameters(secureRandom, length);
  90. var keyGenerator = new RsaKeyPairGenerator();
  91. keyGenerator.Init(keygenParam);
  92. return keyGenerator.GenerateKeyPair();
  93. }
  94. /// <summary>
  95. /// 生成证书
  96. /// </summary>
  97. /// <param name="domains"></param>
  98. /// <param name="subjectPublic"></param>
  99. /// <param name="validFrom"></param>
  100. /// <param name="validTo"></param>
  101. /// <param name="issuerName"></param>
  102. /// <param name="issuerPublic"></param>
  103. /// <param name="issuerPrivate"></param>
  104. /// <param name="caPathLengthConstraint"></param>
  105. /// <returns></returns>
  106. private static X509Certificate GenerateCertificate(IEnumerable<string> domains, AsymmetricKeyParameter subjectPublic, DateTime validFrom, DateTime validTo, string issuerName, AsymmetricKeyParameter? issuerPublic, AsymmetricKeyParameter issuerPrivate, int? caPathLengthConstraint)
  107. {
  108. var signatureFactory = issuerPrivate is ECPrivateKeyParameters
  109. ? new Asn1SignatureFactory(X9ObjectIdentifiers.ECDsaWithSha256.ToString(), issuerPrivate)
  110. : new Asn1SignatureFactory(PkcsObjectIdentifiers.Sha256WithRsaEncryption.ToString(), issuerPrivate);
  111. var certGenerator = new X509V3CertificateGenerator();
  112. certGenerator.SetIssuerDN(new X509Name("CN=" + issuerName));
  113. certGenerator.SetSubjectDN(new X509Name("CN=" + domains.First()));
  114. certGenerator.SetSerialNumber(BigInteger.ProbablePrime(120, new Random()));
  115. certGenerator.SetNotBefore(validFrom);
  116. certGenerator.SetNotAfter(validTo);
  117. certGenerator.SetPublicKey(subjectPublic);
  118. if (issuerPublic != null)
  119. {
  120. var akis = new AuthorityKeyIdentifierStructure(issuerPublic);
  121. certGenerator.AddExtension(X509Extensions.AuthorityKeyIdentifier, false, akis);
  122. }
  123. if (caPathLengthConstraint != null && caPathLengthConstraint >= 0)
  124. {
  125. var extension = new X509Extension(true, new DerOctetString(new BasicConstraints(caPathLengthConstraint.Value)));
  126. certGenerator.AddExtension(X509Extensions.BasicConstraints, extension.IsCritical, extension.GetParsedValue());
  127. }
  128. var names = domains.Select(domain =>
  129. {
  130. var nameType = GeneralName.DnsName;
  131. if (IPAddress.TryParse(domain, out _))
  132. {
  133. nameType = GeneralName.IPAddress;
  134. }
  135. return new GeneralName(nameType, domain);
  136. }).ToArray();
  137. var subjectAltName = new GeneralNames(names);
  138. certGenerator.AddExtension(X509Extensions.SubjectAlternativeName, false, subjectAltName);
  139. certGenerator.AddExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage(KeyPurposeID.IdKPServerAuth));
  140. return certGenerator.Generate(signatureFactory);
  141. }
  142. /// <summary>
  143. /// 生成pfx
  144. /// </summary>
  145. /// <param name="cert"></param>
  146. /// <param name="privateKey"></param>
  147. /// <param name="password"></param>
  148. /// <returns></returns>
  149. private static X509Certificate2 GeneratePfx(X509Certificate cert, AsymmetricKeyParameter privateKey, string? password)
  150. {
  151. var subject = GetSubjectName(cert);
  152. var pkcs12Store = new Pkcs12Store();
  153. var certEntry = new X509CertificateEntry(cert);
  154. pkcs12Store.SetCertificateEntry(subject, certEntry);
  155. pkcs12Store.SetKeyEntry(subject, new AsymmetricKeyEntry(privateKey), new[] { certEntry });
  156. using var pfxStream = new MemoryStream();
  157. pkcs12Store.Save(pfxStream, password?.ToCharArray(), secureRandom);
  158. return new X509Certificate2(pfxStream.ToArray());
  159. }
  160. /// <summary>
  161. /// 获取Subject
  162. /// </summary>
  163. /// <param name="cert"></param>
  164. /// <returns></returns>
  165. private static string GetSubjectName(X509Certificate cert)
  166. {
  167. var subject = cert.SubjectDN.ToString();
  168. if (subject.StartsWith("cn=", StringComparison.OrdinalIgnoreCase))
  169. {
  170. subject = subject[3..];
  171. }
  172. return subject;
  173. }
  174. }
  175. }