CertGenerator.cs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  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.Text;
  20. using X509Certificate2 = System.Security.Cryptography.X509Certificates.X509Certificate2;
  21. namespace FastGithub.ReverseProxy
  22. {
  23. /// <summary>
  24. /// 证书生成器
  25. /// </summary>
  26. static class CertGenerator
  27. {
  28. private static readonly SecureRandom secureRandom = new();
  29. /// <summary>
  30. /// 生成CA签名证书
  31. /// </summary>
  32. /// <param name="domains"></param>
  33. /// <param name="keySizeBits"></param>
  34. /// <param name="validFrom"></param>
  35. /// <param name="validTo"></param>
  36. /// <param name="caPublicCerPath"></param>
  37. /// <param name="caPrivateKeyPath"></param>
  38. /// <returns></returns>
  39. public static X509Certificate2 Generate(IEnumerable<string> domains, int keySizeBits, DateTime validFrom, DateTime validTo, string caPublicCerPath, string caPrivateKeyPath)
  40. {
  41. if (File.Exists(caPublicCerPath) == false)
  42. {
  43. throw new FileNotFoundException(caPublicCerPath);
  44. }
  45. if (File.Exists(caPrivateKeyPath) == false)
  46. {
  47. throw new FileNotFoundException(caPublicCerPath);
  48. }
  49. using var pubReader = new StreamReader(caPublicCerPath, Encoding.ASCII);
  50. var caCert = (X509Certificate)new PemReader(pubReader).ReadObject();
  51. using var priReader = new StreamReader(caPrivateKeyPath, Encoding.ASCII);
  52. var reader = new PemReader(priReader);
  53. var caPrivateKey = ((AsymmetricCipherKeyPair)reader.ReadObject()).Private;
  54. var caSubjectName = GetSubjectName(caCert);
  55. var keys = GenerateRsaKeyPair(keySizeBits);
  56. var cert = GenerateCertificate(domains, keys.Public, validFrom, validTo, caSubjectName, caCert.GetPublicKey(), caPrivateKey, null);
  57. return GeneratePfx(cert, keys.Private, password: null);
  58. }
  59. /// <summary>
  60. /// 生成私钥
  61. /// </summary>
  62. /// <param name="length"></param>
  63. /// <returns></returns>
  64. private static AsymmetricCipherKeyPair GenerateRsaKeyPair(int length)
  65. {
  66. var keygenParam = new KeyGenerationParameters(secureRandom, length);
  67. var keyGenerator = new RsaKeyPairGenerator();
  68. keyGenerator.Init(keygenParam);
  69. return keyGenerator.GenerateKeyPair();
  70. }
  71. /// <summary>
  72. /// 生成证书
  73. /// </summary>
  74. /// <param name="domains"></param>
  75. /// <param name="subjectPublic"></param>
  76. /// <param name="validFrom"></param>
  77. /// <param name="validTo"></param>
  78. /// <param name="issuerName"></param>
  79. /// <param name="issuerPublic"></param>
  80. /// <param name="issuerPrivate"></param>
  81. /// <param name="CA_PathLengthConstraint"></param>
  82. /// <returns></returns>
  83. private static X509Certificate GenerateCertificate(IEnumerable<string> domains, AsymmetricKeyParameter subjectPublic, DateTime validFrom, DateTime validTo, string issuerName, AsymmetricKeyParameter issuerPublic, AsymmetricKeyParameter issuerPrivate, int? CA_PathLengthConstraint)
  84. {
  85. var signatureFactory = issuerPrivate is ECPrivateKeyParameters
  86. ? new Asn1SignatureFactory(X9ObjectIdentifiers.ECDsaWithSha256.ToString(), issuerPrivate)
  87. : new Asn1SignatureFactory(PkcsObjectIdentifiers.Sha256WithRsaEncryption.ToString(), issuerPrivate);
  88. var certGenerator = new X509V3CertificateGenerator();
  89. certGenerator.SetIssuerDN(new X509Name("CN=" + issuerName));
  90. certGenerator.SetSubjectDN(new X509Name("CN=" + domains.First()));
  91. certGenerator.SetSerialNumber(BigInteger.ProbablePrime(120, new Random()));
  92. certGenerator.SetNotBefore(validFrom);
  93. certGenerator.SetNotAfter(validTo);
  94. certGenerator.SetPublicKey(subjectPublic);
  95. if (issuerPublic != null)
  96. {
  97. var akis = new AuthorityKeyIdentifierStructure(issuerPublic);
  98. certGenerator.AddExtension(X509Extensions.AuthorityKeyIdentifier, false, akis);
  99. }
  100. if (CA_PathLengthConstraint != null && CA_PathLengthConstraint >= 0)
  101. {
  102. var extension = new X509Extension(true, new DerOctetString(new BasicConstraints(CA_PathLengthConstraint.Value)));
  103. certGenerator.AddExtension(X509Extensions.BasicConstraints, extension.IsCritical, extension.GetParsedValue());
  104. }
  105. var names = domains.Select(domain => new GeneralName(GeneralName.DnsName, domain)).ToArray();
  106. var subjectAltName = new GeneralNames(names);
  107. certGenerator.AddExtension(X509Extensions.SubjectAlternativeName, false, subjectAltName);
  108. return certGenerator.Generate(signatureFactory);
  109. }
  110. /// <summary>
  111. /// 生成pfx
  112. /// </summary>
  113. /// <param name="cert"></param>
  114. /// <param name="privateKey"></param>
  115. /// <param name="password"></param>
  116. /// <returns></returns>
  117. private static X509Certificate2 GeneratePfx(X509Certificate cert, AsymmetricKeyParameter privateKey, string? password)
  118. {
  119. var subject = GetSubjectName(cert);
  120. var pkcs12Store = new Pkcs12Store();
  121. var certEntry = new X509CertificateEntry(cert);
  122. pkcs12Store.SetCertificateEntry(subject, certEntry);
  123. pkcs12Store.SetKeyEntry(subject, new AsymmetricKeyEntry(privateKey), new[] { certEntry });
  124. using var pfxStream = new MemoryStream();
  125. pkcs12Store.Save(pfxStream, password?.ToCharArray(), secureRandom);
  126. return new X509Certificate2(pfxStream.ToArray());
  127. }
  128. /// <summary>
  129. /// 获取Subject
  130. /// </summary>
  131. /// <param name="cert"></param>
  132. /// <returns></returns>
  133. private static string GetSubjectName(X509Certificate cert)
  134. {
  135. var subject = cert.SubjectDN.ToString();
  136. if (subject.StartsWith("cn=", StringComparison.OrdinalIgnoreCase))
  137. {
  138. subject = subject[3..];
  139. }
  140. return subject;
  141. }
  142. }
  143. }