196 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			196 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
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.Net;
 | 
						|
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>
 | 
						|
        /// 生成自签名证书
 | 
						|
        /// </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>
 | 
						|
        public static void GenerateBySelf(IEnumerable<string> domains, int keySizeBits, DateTime validFrom, DateTime validTo, string caPublicCerPath, string caPrivateKeyPath)
 | 
						|
        {
 | 
						|
            var keys = GenerateRsaKeyPair(keySizeBits);
 | 
						|
            var cert = GenerateCertificate(domains, keys.Public, validFrom, validTo, domains.First(), null, keys.Private, null);
 | 
						|
 | 
						|
            using var priWriter = new StreamWriter(caPrivateKeyPath);
 | 
						|
            var priPemWriter = new PemWriter(priWriter);
 | 
						|
            priPemWriter.WriteObject(keys.Private);
 | 
						|
            priPemWriter.Writer.Flush();
 | 
						|
 | 
						|
            using var pubWriter = new StreamWriter(caPublicCerPath);
 | 
						|
            var pubPemWriter = new PemWriter(pubWriter);
 | 
						|
            pubPemWriter.WriteObject(cert);
 | 
						|
            pubPemWriter.Writer.Flush();
 | 
						|
        }
 | 
						|
 | 
						|
        /// <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 GenerateByCa(IEnumerable<string> domains, int keySizeBits, DateTime validFrom, DateTime validTo, string caPublicCerPath, string caPrivateKeyPath, string? password = default)
 | 
						|
        {
 | 
						|
            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);
 | 
						|
        }
 | 
						|
 | 
						|
        /// <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 =>
 | 
						|
            {
 | 
						|
                var nameType = GeneralName.DnsName;
 | 
						|
                if (IPAddress.TryParse(domain, out _))
 | 
						|
                {
 | 
						|
                    nameType = GeneralName.IPAddress;
 | 
						|
                }
 | 
						|
                return new GeneralName(nameType, 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;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |