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); } }