using Org.BouncyCastle.Asn1.X509; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.Operators; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto.Prng; 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.IO; using System.Security.Cryptography.X509Certificates; using System.Text; namespace Pulsar.Server.Helper { public static class CertificateHelper { public static X509Certificate2 CreateCertificateAuthority(string caName, int keyStrength = 4096) { var random = new SecureRandom(new CryptoApiRandomGenerator()); var keyStrengths = new[] { 2048, 3072, 4096 }; var randomKeyStrength = keyStrengths[random.Next(keyStrengths.Length)]; var keyPairGen = new RsaKeyPairGenerator(); keyPairGen.Init(new KeyGenerationParameters(random, randomKeyStrength)); AsymmetricCipherKeyPair keypair = keyPairGen.GenerateKeyPair(); var certificateGenerator = new X509V3CertificateGenerator(); var randomOrg = GenerateRandomString(random, 8, 15); var randomLocation = GenerateRandomString(random, 5, 12); var randomCountry = GenerateRandomCountryCode(random); var subjectString = $"CN={caName}, O={randomOrg}, L={randomLocation}, C={randomCountry}"; var CN = new X509Name(subjectString); var serialNumberBitLength = random.Next(120, 201); var SN = BigInteger.ProbablePrime(serialNumberBitLength, random); var validityYears = random.Next(5, 16); var notBeforeOffsetDays = random.Next(1, 8); certificateGenerator.SetSerialNumber(SN); certificateGenerator.SetSubjectDN(CN); certificateGenerator.SetIssuerDN(CN); certificateGenerator.SetNotAfter(DateTime.UtcNow.AddYears(validityYears)); certificateGenerator.SetNotBefore(DateTime.UtcNow.Subtract(new TimeSpan(notBeforeOffsetDays, 0, 0, 0))); certificateGenerator.SetPublicKey(keypair.Public); certificateGenerator.AddExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure(keypair.Public)); certificateGenerator.AddExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(true)); certificateGenerator.AddExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.KeyCertSign | KeyUsage.CrlSign)); certificateGenerator.AddExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage(KeyPurposeID.IdKPServerAuth)); var signatureAlgorithms = new[] { "SHA256WITHRSA", "SHA384WITHRSA", "SHA512WITHRSA" }; var randomSignatureAlgorithm = signatureAlgorithms[random.Next(signatureAlgorithms.Length)]; ISignatureFactory signatureFactory = new Asn1SignatureFactory(randomSignatureAlgorithm, keypair.Private, random); var certificate = certificateGenerator.Generate(signatureFactory); // Create PKCS#12 (PFX) format with certificate and private key var store = new Pkcs12StoreBuilder().Build(); var certificateEntry = new X509CertificateEntry(certificate); store.SetCertificateEntry(caName, certificateEntry); store.SetKeyEntry(caName, new AsymmetricKeyEntry(keypair.Private), new[] { certificateEntry }); // Convert to bytes using (var ms = new MemoryStream()) { store.Save(ms, new char[0], random); // Empty password var pfxBytes = ms.ToArray(); // Create X509Certificate2 from PFX bytes using modern approach for .NET 9.0 AOT compatibility return X509CertificateLoader.LoadPkcs12(pfxBytes, null, X509KeyStorageFlags.Exportable); } } /// /// Alternative method using PEM format for AOT compatibility /// public static X509Certificate2 CreateCertificateAuthorityFromPem(string caName, int keyStrength = 4096) { var random = new SecureRandom(new CryptoApiRandomGenerator()); var keyStrengths = new[] { 2048, 3072, 4096 }; var randomKeyStrength = keyStrengths[random.Next(keyStrengths.Length)]; var keyPairGen = new RsaKeyPairGenerator(); keyPairGen.Init(new KeyGenerationParameters(random, randomKeyStrength)); AsymmetricCipherKeyPair keypair = keyPairGen.GenerateKeyPair(); var certificateGenerator = new X509V3CertificateGenerator(); var randomOrg = GenerateRandomString(random, 8, 15); var randomLocation = GenerateRandomString(random, 5, 12); var randomCountry = GenerateRandomCountryCode(random); var subjectString = $"CN={caName}, O={randomOrg}, L={randomLocation}, C={randomCountry}"; var CN = new X509Name(subjectString); var serialNumberBitLength = random.Next(120, 201); var SN = BigInteger.ProbablePrime(serialNumberBitLength, random); var validityYears = random.Next(5, 16); var notBeforeOffsetDays = random.Next(1, 8); certificateGenerator.SetSerialNumber(SN); certificateGenerator.SetSubjectDN(CN); certificateGenerator.SetIssuerDN(CN); certificateGenerator.SetNotAfter(DateTime.UtcNow.AddYears(validityYears)); certificateGenerator.SetNotBefore(DateTime.UtcNow.Subtract(new TimeSpan(notBeforeOffsetDays, 0, 0, 0))); certificateGenerator.SetPublicKey(keypair.Public); certificateGenerator.AddExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure(keypair.Public)); certificateGenerator.AddExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(true)); certificateGenerator.AddExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.KeyCertSign | KeyUsage.CrlSign)); certificateGenerator.AddExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage(KeyPurposeID.IdKPServerAuth)); var signatureAlgorithms = new[] { "SHA256WITHRSA", "SHA384WITHRSA", "SHA512WITHRSA" }; var randomSignatureAlgorithm = signatureAlgorithms[random.Next(signatureAlgorithms.Length)]; ISignatureFactory signatureFactory = new Asn1SignatureFactory(randomSignatureAlgorithm, keypair.Private, random); var certificate = certificateGenerator.Generate(signatureFactory); // Convert to PEM format string certPem, keyPem; using (var certWriter = new StringWriter()) { var pemWriter = new PemWriter(certWriter); pemWriter.WriteObject(certificate); certPem = certWriter.ToString(); } using (var keyWriter = new StringWriter()) { var pemWriter = new PemWriter(keyWriter); pemWriter.WriteObject(keypair.Private); keyPem = keyWriter.ToString(); } // Create X509Certificate2 from PEM - this is AOT-compatible in .NET 9.0 return X509Certificate2.CreateFromPem(certPem, keyPem); } /// /// Generates a completely random string to avoid fingerprinting /// private static string GenerateRandomString(SecureRandom random, int minLength, int maxLength) { var length = random.Next(minLength, maxLength + 1); var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; var result = new char[length]; for (int i = 0; i < length; i++) { result[i] = chars[random.Next(chars.Length)]; } return new string(result); } /// /// Generates a random 2-letter country code to avoid fingerprinting /// private static string GenerateRandomCountryCode(SecureRandom random) { var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; return new string(new char[] { chars[random.Next(chars.Length)], chars[random.Next(chars.Length)] }); } } }