namespace ProofOfConcept.Utilities;
using System.Net;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
public static class CertificateAuthority
{
///
/// Create a self-signed Root CA certificate.
///
/// Distinguished Name (e.g., "CN=My Root CA, O=MyOrg, C=HU").
/// Validity in years.
/// X509Certificate2 with private key.
public static X509Certificate2 CreateRootCA(string subjectName, int validYears = 10)
{
using var key = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var req = new CertificateRequest(
new X500DistinguishedName(subjectName),
key,
HashAlgorithmName.SHA256);
req.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, true, 1, true));
req.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyCertSign | X509KeyUsageFlags.CrlSign, true));
req.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(req.PublicKey, false));
var notBefore = DateTimeOffset.UtcNow.AddMinutes(-5);
var notAfter = notBefore.AddYears(validYears);
var cert = req.CreateSelfSigned(notBefore, notAfter);
// Attach private key so we can export
return cert.CopyWithPrivateKey(key);
}
///
/// Create a TLS/HTTPS certificate for a domain, signed by the given Root CA.
///
/// Root CA certificate (must include private key).
/// Common Name (e.g., "CN=myapp.local").
/// DNS SAN entries.
/// Optional IP SAN entries.
/// Validity in days (max ~397 for Apple clients).
/// X509Certificate2 with private key.
public static X509Certificate2 CreateHttpsCertificate(X509Certificate2 rootCA, string subjectName, IEnumerable dnsNames, IEnumerable? ipAddresses = null, int validDays = 397)
{
if (!rootCA.HasPrivateKey)
throw new ArgumentException("Root CA must have a private key", nameof(rootCA));
using var leafKey = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var req = new CertificateRequest(
new X500DistinguishedName(subjectName),
leafKey,
HashAlgorithmName.SHA256);
// Basic constraints: not a CA
req.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, true));
req.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment, true));
req.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(
new OidCollection { new Oid("1.3.6.1.5.5.7.3.1") }, true)); // serverAuth
req.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(req.PublicKey, false));
// Subject Alternative Names
var sanBuilder = new SubjectAlternativeNameBuilder();
foreach (var dns in dnsNames)
sanBuilder.AddDnsName(dns);
if (ipAddresses != null)
{
foreach (var ip in ipAddresses)
sanBuilder.AddIpAddress(ip);
}
req.CertificateExtensions.Add(sanBuilder.Build());
var notBefore = DateTimeOffset.UtcNow.AddMinutes(-5);
var notAfter = notBefore.AddDays(validDays);
// Generate random serial
var serial = new byte[16];
RandomNumberGenerator.Fill(serial);
serial[0] &= 0x7F; // positive
var issued = req.Create(rootCA, notBefore, notAfter, serial);
return issued.CopyWithPrivateKey(leafKey);
}
}