using System;
using System.Collections.Generic;
using System.Net;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
namespace FastGithub.HttpServer.Certs
{
///
/// 证书生成器
///
static class CertGenerator
{
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 X509Certificate2 CreateCACertificate(
X500DistinguishedName subjectName,
DateTimeOffset notBefore,
DateTimeOffset notAfter,
int rsaKeySizeInBits = 2048,
int pathLengthConstraint = 1)
{
using var rsa = RSA.Create(rsaKeySizeInBits);
var request = new CertificateRequest(subjectName, rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
var basicConstraints = new X509BasicConstraintsExtension(true, pathLengthConstraint > 0, pathLengthConstraint, true);
request.CertificateExtensions.Add(basicConstraints);
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);
}
///
/// 生成服务器证书
///
///
///
///
///
///
///
///
public static X509Certificate2 CreateEndCertificate(
X509Certificate2 issuerCertificate,
X500DistinguishedName subjectName,
IEnumerable? extraDnsNames = default,
DateTimeOffset? notBefore = default,
DateTimeOffset? notAfter = default,
int rsaKeySizeInBits = 2048)
{
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)
{
foreach (var dnsName in extraDnsNames)
{
dnsBuilder.Add(dnsName);
}
}
var dnsNames = dnsBuilder.Build();
request.CertificateExtensions.Add(dnsNames);
if (notBefore == null || notBefore.Value < issuerCertificate.NotBefore)
{
notBefore = issuerCertificate.NotBefore;
}
if (notAfter == null || notAfter.Value > issuerCertificate.NotAfter)
{
notAfter = issuerCertificate.NotAfter;
}
var serialNumber = BitConverter.GetBytes(Random.Shared.NextInt64());
using var certOnly = request.Create(issuerCertificate, notBefore.Value, notAfter.Value, serialNumber);
return certOnly.CopyWithPrivateKey(rsa);
}
private static void Add(this SubjectAlternativeNameBuilder builder, string name)
{
if (IPAddress.TryParse(name, out var address))
{
builder.AddIpAddress(address);
}
else
{
builder.AddDnsName(name);
}
}
private static X509Extension GetAuthorityKeyIdentifierExtension(X509Certificate2 certificate)
{
var extension = new X509SubjectKeyIdentifierExtension(certificate.PublicKey, false);
#if NET7_0_OR_GREATER
return X509AuthorityKeyIdentifierExtension.CreateFromSubjectKeyIdentifier(extension);
#else
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);
return new X509Extension("2.5.29.35", rawData, false);
#endif
}
}
}