183 lines
8.5 KiB
C#
183 lines
8.5 KiB
C#
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Alternative method using PEM format for AOT compatibility
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a completely random string to avoid fingerprinting
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a random 2-letter country code to avoid fingerprinting
|
|
/// </summary>
|
|
private static string GenerateRandomCountryCode(SecureRandom random)
|
|
{
|
|
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
return new string(new char[] {
|
|
chars[random.Next(chars.Length)],
|
|
chars[random.Next(chars.Length)]
|
|
});
|
|
}
|
|
}
|
|
}
|