|
@@ -0,0 +1,159 @@
|
|
|
+using Org.BouncyCastle.Asn1;
|
|
|
+using Org.BouncyCastle.Asn1.Pkcs;
|
|
|
+using Org.BouncyCastle.Asn1.X509;
|
|
|
+using Org.BouncyCastle.Asn1.X9;
|
|
|
+using Org.BouncyCastle.Crypto;
|
|
|
+using Org.BouncyCastle.Crypto.Generators;
|
|
|
+using Org.BouncyCastle.Crypto.Operators;
|
|
|
+using Org.BouncyCastle.Crypto.Parameters;
|
|
|
+using Org.BouncyCastle.Math;
|
|
|
+using Org.BouncyCastle.OpenSsl;
|
|
|
+using Org.BouncyCastle.Pkcs;
|
|
|
+using Org.BouncyCastle.Security;
|
|
|
+using Org.BouncyCastle.X509;
|
|
|
+using Org.BouncyCastle.X509.Extension;
|
|
|
+using System;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.IO;
|
|
|
+using System.Linq;
|
|
|
+using System.Text;
|
|
|
+using X509Certificate2 = System.Security.Cryptography.X509Certificates.X509Certificate2;
|
|
|
+
|
|
|
+namespace FastGithub.ReverseProxy
|
|
|
+{
|
|
|
+ /// <summary>
|
|
|
+ /// 证书生成器
|
|
|
+ /// </summary>
|
|
|
+ static class CertGenerator
|
|
|
+ {
|
|
|
+ private static readonly SecureRandom secureRandom = new();
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 生成CA签名证书
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="domains"></param>
|
|
|
+ /// <param name="keySizeBits"></param>
|
|
|
+ /// <param name="validFrom"></param>
|
|
|
+ /// <param name="validTo"></param>
|
|
|
+ /// <param name="caPublicCerPath"></param>
|
|
|
+ /// <param name="caPrivateKeyPath"></param>
|
|
|
+ /// <returns></returns>
|
|
|
+ public static X509Certificate2 Generate(IEnumerable<string> domains, int keySizeBits, DateTime validFrom, DateTime validTo, string caPublicCerPath, string caPrivateKeyPath)
|
|
|
+ {
|
|
|
+ if (File.Exists(caPublicCerPath) == false)
|
|
|
+ {
|
|
|
+ throw new FileNotFoundException(caPublicCerPath);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (File.Exists(caPrivateKeyPath) == false)
|
|
|
+ {
|
|
|
+ throw new FileNotFoundException(caPublicCerPath);
|
|
|
+ }
|
|
|
+
|
|
|
+ using var pubReader = new StreamReader(caPublicCerPath, Encoding.ASCII);
|
|
|
+ var caCert = (X509Certificate)new PemReader(pubReader).ReadObject();
|
|
|
+
|
|
|
+ using var priReader = new StreamReader(caPrivateKeyPath, Encoding.ASCII);
|
|
|
+ var reader = new PemReader(priReader);
|
|
|
+ var caPrivateKey = ((AsymmetricCipherKeyPair)reader.ReadObject()).Private;
|
|
|
+
|
|
|
+ var caSubjectName = GetSubjectName(caCert);
|
|
|
+ var keys = GenerateRsaKeyPair(keySizeBits);
|
|
|
+ var cert = GenerateCertificate(domains, keys.Public, validFrom, validTo, caSubjectName, caCert.GetPublicKey(), caPrivateKey, null);
|
|
|
+
|
|
|
+ return GeneratePfx(cert, keys.Private, password: null);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 生成私钥
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="length"></param>
|
|
|
+ /// <returns></returns>
|
|
|
+ private static AsymmetricCipherKeyPair GenerateRsaKeyPair(int length)
|
|
|
+ {
|
|
|
+ var keygenParam = new KeyGenerationParameters(secureRandom, length);
|
|
|
+ var keyGenerator = new RsaKeyPairGenerator();
|
|
|
+ keyGenerator.Init(keygenParam);
|
|
|
+ return keyGenerator.GenerateKeyPair();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 生成证书
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="domains"></param>
|
|
|
+ /// <param name="subjectPublic"></param>
|
|
|
+ /// <param name="validFrom"></param>
|
|
|
+ /// <param name="validTo"></param>
|
|
|
+ /// <param name="issuerName"></param>
|
|
|
+ /// <param name="issuerPublic"></param>
|
|
|
+ /// <param name="issuerPrivate"></param>
|
|
|
+ /// <param name="CA_PathLengthConstraint"></param>
|
|
|
+ /// <returns></returns>
|
|
|
+ private static X509Certificate GenerateCertificate(IEnumerable<string> domains, AsymmetricKeyParameter subjectPublic, DateTime validFrom, DateTime validTo, string issuerName, AsymmetricKeyParameter issuerPublic, AsymmetricKeyParameter issuerPrivate, int? CA_PathLengthConstraint)
|
|
|
+ {
|
|
|
+ var signatureFactory = issuerPrivate is ECPrivateKeyParameters
|
|
|
+ ? new Asn1SignatureFactory(X9ObjectIdentifiers.ECDsaWithSha256.ToString(), issuerPrivate)
|
|
|
+ : new Asn1SignatureFactory(PkcsObjectIdentifiers.Sha256WithRsaEncryption.ToString(), issuerPrivate);
|
|
|
+
|
|
|
+ var certGenerator = new X509V3CertificateGenerator();
|
|
|
+ certGenerator.SetIssuerDN(new X509Name("CN=" + issuerName));
|
|
|
+ certGenerator.SetSubjectDN(new X509Name("CN=" + domains.First()));
|
|
|
+ certGenerator.SetSerialNumber(BigInteger.ProbablePrime(120, new Random()));
|
|
|
+ certGenerator.SetNotBefore(validFrom);
|
|
|
+ certGenerator.SetNotAfter(validTo);
|
|
|
+ certGenerator.SetPublicKey(subjectPublic);
|
|
|
+
|
|
|
+ if (issuerPublic != null)
|
|
|
+ {
|
|
|
+ var akis = new AuthorityKeyIdentifierStructure(issuerPublic);
|
|
|
+ certGenerator.AddExtension(X509Extensions.AuthorityKeyIdentifier, false, akis);
|
|
|
+ }
|
|
|
+ if (CA_PathLengthConstraint != null && CA_PathLengthConstraint >= 0)
|
|
|
+ {
|
|
|
+ var extension = new X509Extension(true, new DerOctetString(new BasicConstraints(CA_PathLengthConstraint.Value)));
|
|
|
+ certGenerator.AddExtension(X509Extensions.BasicConstraints, extension.IsCritical, extension.GetParsedValue());
|
|
|
+ }
|
|
|
+
|
|
|
+ var names = domains.Select(domain => new GeneralName(GeneralName.DnsName, domain)).ToArray();
|
|
|
+ var subjectAltName = new GeneralNames(names);
|
|
|
+ certGenerator.AddExtension(X509Extensions.SubjectAlternativeName, false, subjectAltName);
|
|
|
+ return certGenerator.Generate(signatureFactory);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 生成pfx
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="cert"></param>
|
|
|
+ /// <param name="privateKey"></param>
|
|
|
+ /// <param name="password"></param>
|
|
|
+ /// <returns></returns>
|
|
|
+ private static X509Certificate2 GeneratePfx(X509Certificate cert, AsymmetricKeyParameter privateKey, string? password)
|
|
|
+ {
|
|
|
+ var subject = GetSubjectName(cert);
|
|
|
+ var pkcs12Store = new Pkcs12Store();
|
|
|
+ var certEntry = new X509CertificateEntry(cert);
|
|
|
+ pkcs12Store.SetCertificateEntry(subject, certEntry);
|
|
|
+ pkcs12Store.SetKeyEntry(subject, new AsymmetricKeyEntry(privateKey), new[] { certEntry });
|
|
|
+
|
|
|
+ using var pfxStream = new MemoryStream();
|
|
|
+ pkcs12Store.Save(pfxStream, password?.ToCharArray(), secureRandom);
|
|
|
+ return new X509Certificate2(pfxStream.ToArray());
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 获取Subject
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="cert"></param>
|
|
|
+ /// <returns></returns>
|
|
|
+ private static string GetSubjectName(X509Certificate cert)
|
|
|
+ {
|
|
|
+ var subject = cert.SubjectDN.ToString();
|
|
|
+ if (subject.StartsWith("cn=", StringComparison.OrdinalIgnoreCase))
|
|
|
+ {
|
|
|
+ subject = subject[3..];
|
|
|
+ }
|
|
|
+ return subject;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|