From 0ddc22a299fae0deb40e568089ff4c6d81c3d4e5 Mon Sep 17 00:00:00 2001 From: xljiulang <366193849@qq.com> Date: Sun, 18 Sep 2022 00:39:03 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E5=9F=BA=E7=A1=80=E5=BA=93?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0CertGenerator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FastGithub.HttpServer/Certs/CertGenerator.cs | 267 ++++++++---------- FastGithub.HttpServer/Certs/CertService.cs | 66 +++-- .../FastGithub.HttpServer.csproj | 1 - 3 files changed, 153 insertions(+), 181 deletions(-) diff --git a/FastGithub.HttpServer/Certs/CertGenerator.cs b/FastGithub.HttpServer/Certs/CertGenerator.cs index 9162680..b1ed7f3 100644 --- a/FastGithub.HttpServer/Certs/CertGenerator.cs +++ b/FastGithub.HttpServer/Certs/CertGenerator.cs @@ -1,23 +1,9 @@ -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; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Net; -using System.Text; -using X509Certificate2 = System.Security.Cryptography.X509Certificates.X509Certificate2; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; namespace FastGithub.HttpServer.Certs { @@ -26,178 +12,145 @@ namespace FastGithub.HttpServer.Certs /// static class CertGenerator { - private static readonly SecureRandom secureRandom = new(); + private static readonly Oid tlsServerOid = new("1.3.6.1.5.5.7.3.1"); + private static readonly Oid tlsClientOid = new("1.3.6.1.5.5.7.3.2"); /// - /// 生成自签名证书 + /// 生成ca证书 /// - /// - /// - /// - /// - /// - /// - public static void GenerateBySelf(IEnumerable domains, int keySizeBits, DateTime validFrom, DateTime validTo, string caPublicCerPath, string caPrivateKeyPath) + /// + /// + /// + /// + /// + /// + public static X509Certificate2 CreateCACertificate( + X500DistinguishedName subjectName, + DateTimeOffset notBefore, + DateTimeOffset notAfter, + int rsaKeySizeInBits = 2048, + int pathLengthConstraint = 1) { - var keys = GenerateRsaKeyPair(keySizeBits); - var cert = GenerateCertificate(domains, keys.Public, validFrom, validTo, domains.First(), null, keys.Private, 1); + using var rsa = RSA.Create(rsaKeySizeInBits); + var request = new CertificateRequest(subjectName, rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); - using var priWriter = new StreamWriter(caPrivateKeyPath); - var priPemWriter = new PemWriter(priWriter); - priPemWriter.WriteObject(keys.Private); - priPemWriter.Writer.Flush(); + var basicConstraints = new X509BasicConstraintsExtension(true, pathLengthConstraint > 0, pathLengthConstraint, true); + request.CertificateExtensions.Add(basicConstraints); - using var pubWriter = new StreamWriter(caPublicCerPath); - var pubPemWriter = new PemWriter(pubWriter); - pubPemWriter.WriteObject(cert); - pubPemWriter.Writer.Flush(); + var keyUsage = new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.CrlSign | X509KeyUsageFlags.KeyCertSign, true); + request.CertificateExtensions.Add(keyUsage); + + var oids = new OidCollection { tlsServerOid, tlsClientOid }; + var enhancedKeyUsage = new X509EnhancedKeyUsageExtension(oids, true); + request.CertificateExtensions.Add(enhancedKeyUsage); + + var dnsBuilder = new SubjectAlternativeNameBuilder(); + dnsBuilder.Add(subjectName.Name[3..]); + request.CertificateExtensions.Add(dnsBuilder.Build()); + + var subjectKeyId = new X509SubjectKeyIdentifierExtension(request.PublicKey, false); + request.CertificateExtensions.Add(subjectKeyId); + + return request.CreateSelfSigned(notBefore, notAfter); } /// - /// 生成CA签名证书 + /// 生成服务器证书 /// - /// - /// - /// - /// - /// - /// + /// + /// + /// + /// + /// + /// /// - public static X509Certificate2 GenerateByCa(IEnumerable domains, int keySizeBits, DateTime validFrom, DateTime validTo, string caPublicCerPath, string caPrivateKeyPath, string? password = default) + public static X509Certificate2 CreateEndCertificate( + X509Certificate2 issuerCertificate, + X500DistinguishedName subjectName, + IEnumerable? extraDnsNames = default, + DateTimeOffset? notBefore = default, + DateTimeOffset? notAfter = default, + int rsaKeySizeInBits = 2048) { - if (File.Exists(caPublicCerPath) == false) + using var rsa = RSA.Create(rsaKeySizeInBits); + var request = new CertificateRequest(subjectName, rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + + var basicConstraints = new X509BasicConstraintsExtension(false, false, 0, true); + request.CertificateExtensions.Add(basicConstraints); + + var keyUsage = new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment, true); + request.CertificateExtensions.Add(keyUsage); + + var oids = new OidCollection { tlsServerOid, tlsClientOid }; + var enhancedKeyUsage = new X509EnhancedKeyUsageExtension(oids, true); + request.CertificateExtensions.Add(enhancedKeyUsage); + + var authorityKeyId = GetAuthorityKeyIdentifierExtension(issuerCertificate); + request.CertificateExtensions.Add(authorityKeyId); + + var subjectKeyId = new X509SubjectKeyIdentifierExtension(request.PublicKey, false); + request.CertificateExtensions.Add(subjectKeyId); + + var dnsBuilder = new SubjectAlternativeNameBuilder(); + dnsBuilder.Add(subjectName.Name[3..]); + + if (extraDnsNames != null) { - throw new FileNotFoundException(caPublicCerPath); + foreach (var dnsName in extraDnsNames) + { + dnsBuilder.Add(dnsName); + } } - if (File.Exists(caPrivateKeyPath) == false) + var dnsNames = dnsBuilder.Build(); + request.CertificateExtensions.Add(dnsNames); + + if (notBefore == null || notBefore.Value < issuerCertificate.NotBefore) { - throw new FileNotFoundException(caPublicCerPath); + notBefore = issuerCertificate.NotBefore; } - using var pubReader = new StreamReader(caPublicCerPath, Encoding.ASCII); - var caCert = (X509Certificate)new PemReader(pubReader).ReadObject(); + if (notAfter == null || notAfter.Value > issuerCertificate.NotAfter) + { + notAfter = issuerCertificate.NotAfter; + } - 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); + var serialNumber = BitConverter.GetBytes(Random.Shared.NextInt64()); + using var certOnly = request.Create(issuerCertificate, notBefore.Value, notAfter.Value, serialNumber); + return certOnly.CopyWithPrivateKey(rsa); } - /// - /// 生成私钥 - /// - /// - /// - private static AsymmetricCipherKeyPair GenerateRsaKeyPair(int length) + + + private static void Add(this SubjectAlternativeNameBuilder builder, string name) { - var keygenParam = new KeyGenerationParameters(secureRandom, length); - var keyGenerator = new RsaKeyPairGenerator(); - keyGenerator.Init(keygenParam); - return keyGenerator.GenerateKeyPair(); - } - - /// - /// 生成证书 - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - private static X509Certificate GenerateCertificate(IEnumerable domains, AsymmetricKeyParameter subjectPublic, DateTime validFrom, DateTime validTo, string issuerName, AsymmetricKeyParameter? issuerPublic, AsymmetricKeyParameter issuerPrivate, int? caPathLengthConstraint) - { - 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) + if (IPAddress.TryParse(name, out var address)) { - var akis = new AuthorityKeyIdentifierStructure(issuerPublic); - certGenerator.AddExtension(X509Extensions.AuthorityKeyIdentifier, false, akis); - } - - if (caPathLengthConstraint != null && caPathLengthConstraint >= 0) - { - var basicConstraints = new BasicConstraints(caPathLengthConstraint.Value); - certGenerator.AddExtension(X509Extensions.BasicConstraints, true, basicConstraints); - certGenerator.AddExtension(X509Extensions.KeyUsage, false, new KeyUsage(KeyUsage.DigitalSignature | KeyUsage.CrlSign | KeyUsage.KeyCertSign)); + builder.AddIpAddress(address); } else { - var basicConstraints = new BasicConstraints(cA: false); - certGenerator.AddExtension(X509Extensions.BasicConstraints, true, basicConstraints); - certGenerator.AddExtension(X509Extensions.KeyUsage, false, new KeyUsage(KeyUsage.DigitalSignature | KeyUsage.KeyEncipherment)); + builder.AddDnsName(name); } - certGenerator.AddExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage(KeyPurposeID.IdKPServerAuth)); - - 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); } - /// - /// 生成pfx - /// - /// - /// - /// - /// - private static X509Certificate2 GeneratePfx(X509Certificate cert, AsymmetricKeyParameter privateKey, string? password) + private static X509Extension GetAuthorityKeyIdentifierExtension(X509Certificate2 certificate) { - 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 }); +#if NET7_0_OR_GREATER + return X509AuthorityKeyIdentifierExtension.CreateFromCertificate(certificate, true, false); +#else + var extension = certificate.Extensions.OfType().First(); + var subjectKeyIdentifier = extension.RawData.AsSpan(2); + var rawData = new byte[subjectKeyIdentifier.Length + 4]; + rawData[0] = 0x30; + rawData[1] = 0x16; + rawData[2] = 0x80; + rawData[3] = 0x14; + subjectKeyIdentifier.CopyTo(rawData); - using var pfxStream = new MemoryStream(); - pkcs12Store.Save(pfxStream, password?.ToCharArray(), secureRandom); - return new X509Certificate2(pfxStream.ToArray()); - } - - - /// - /// 获取Subject - /// - /// - /// - private static string GetSubjectName(X509Certificate cert) - { - var subject = cert.SubjectDN.ToString(); - if (subject.StartsWith("CN=", StringComparison.OrdinalIgnoreCase)) - { - subject = subject[3..]; - } - return subject; + return new X509Extension("2.5.29.35", rawData, false); +#endif } } } diff --git a/FastGithub.HttpServer/Certs/CertService.cs b/FastGithub.HttpServer/Certs/CertService.cs index 0b04d5d..7296fa8 100644 --- a/FastGithub.HttpServer/Certs/CertService.cs +++ b/FastGithub.HttpServer/Certs/CertService.cs @@ -6,7 +6,9 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Net; +using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; +using System.Text; namespace FastGithub.HttpServer.Certs { @@ -16,10 +18,10 @@ namespace FastGithub.HttpServer.Certs sealed class CertService { private const string CACERT_PATH = "cacert"; - private const int KEY_SIZE_BITS = 2048; private readonly IMemoryCache serverCertCache; private readonly IEnumerable certInstallers; private readonly ILogger logger; + private X509Certificate2? caCert; /// @@ -54,17 +56,28 @@ namespace FastGithub.HttpServer.Certs /// public bool CreateCaCertIfNotExists() { - if (File.Exists(CaCerFilePath) && File.Exists(CaKeyFilePath)) + if (File.Exists(this.CaCerFilePath) && File.Exists(this.CaKeyFilePath)) { return false; } - File.Delete(CaCerFilePath); - File.Delete(CaKeyFilePath); + File.Delete(this.CaCerFilePath); + File.Delete(this.CaKeyFilePath); + + var notBefore = DateTimeOffset.Now.AddDays(-1); + var notAfter = DateTimeOffset.Now.AddYears(10); + + var subjectName = new X500DistinguishedName($"CN={nameof(FastGithub)}"); + this.caCert = CertGenerator.CreateCACertificate(subjectName, notBefore, notAfter); + + var privateKey = this.caCert.GetRSAPrivateKey()?.ExportRSAPrivateKey(); + var privateKeyPem = PemEncoding.Write("RSA PRIVATE KEY", privateKey); + File.WriteAllText(this.CaKeyFilePath, new string(privateKeyPem), Encoding.ASCII); + + var cert = this.caCert.Export(X509ContentType.Cert); + var certPem = PemEncoding.Write("CERTIFICATE", cert); + File.WriteAllText(this.CaCerFilePath, new string(certPem), Encoding.ASCII); - var validFrom = DateTime.Today.AddDays(-1); - var validTo = DateTime.Today.AddYears(10); - CertGenerator.GenerateBySelf(new[] { nameof(FastGithub) }, KEY_SIZE_BITS, validFrom, validTo, CaCerFilePath, CaKeyFilePath); return true; } @@ -73,14 +86,14 @@ namespace FastGithub.HttpServer.Certs /// public void InstallAndTrustCaCert() { - var installer = certInstallers.FirstOrDefault(item => item.IsSupported()); + var installer = this.certInstallers.FirstOrDefault(item => item.IsSupported()); if (installer != null) { - installer.Install(CaCerFilePath); + installer.Install(this.CaCerFilePath); } else { - logger.LogWarning($"请根据你的系统平台手动安装和信任CA证书{CaCerFilePath}"); + this.logger.LogWarning($"请根据你的系统平台手动安装和信任CA证书{this.CaCerFilePath}"); } GitConfigSslverify(false); @@ -118,18 +131,31 @@ namespace FastGithub.HttpServer.Certs /// public X509Certificate2 GetOrCreateServerCert(string? domain) { + if (this.caCert == null) + { + using var rsa = RSA.Create(); + rsa.ImportFromPem(File.ReadAllText(this.CaKeyFilePath)); + this.caCert = new X509Certificate2(this.CaCerFilePath).CopyWithPrivateKey(rsa); + } + var key = $"{nameof(CertService)}:{domain}"; - return serverCertCache.GetOrCreate(key, GetOrCreateCert); + var endCert = this.serverCertCache.GetOrCreate(key, GetOrCreateCert); + return endCert!; // 生成域名的1年证书 X509Certificate2 GetOrCreateCert(ICacheEntry entry) { - var domains = GetDomains(domain).Distinct(); - var validFrom = DateTime.Today.AddDays(-1); - var validTo = DateTime.Today.AddYears(1); + var notBefore = DateTimeOffset.Now.AddDays(-1); + var notAfter = DateTimeOffset.Now.AddYears(1); + entry.SetAbsoluteExpiration(notAfter); - entry.SetAbsoluteExpiration(validTo); - return CertGenerator.GenerateByCa(domains, KEY_SIZE_BITS, validFrom, validTo, CaCerFilePath, CaKeyFilePath); + var extraDomains = GetExtraDomains(); + + var subjectName = new X500DistinguishedName($"CN={domain}"); + var endCert = CertGenerator.CreateEndCertificate(this.caCert, subjectName, extraDomains, notBefore, notAfter); + + // 重新初始化证书,以兼容win平台不能使用内存证书 + return new X509Certificate2(endCert.Export(X509ContentType.Pfx)); } } @@ -138,14 +164,8 @@ namespace FastGithub.HttpServer.Certs /// /// /// - private static IEnumerable GetDomains(string? domain) + private static IEnumerable GetExtraDomains() { - if (string.IsNullOrEmpty(domain) == false) - { - yield return domain; - yield break; - } - yield return Environment.MachineName; yield return IPAddress.Loopback.ToString(); yield return IPAddress.IPv6Loopback.ToString(); diff --git a/FastGithub.HttpServer/FastGithub.HttpServer.csproj b/FastGithub.HttpServer/FastGithub.HttpServer.csproj index 2d5a9d5..d5c76a3 100644 --- a/FastGithub.HttpServer/FastGithub.HttpServer.csproj +++ b/FastGithub.HttpServer/FastGithub.HttpServer.csproj @@ -6,7 +6,6 @@ -