using Microsoft.Extensions.Logging; using Org.BouncyCastle.Asn1.Pkcs; using Org.BouncyCastle.Asn1.Sec; using Org.BouncyCastle.Asn1.X509; using Org.BouncyCastle.Asn1; using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.Operators; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Pkcs; using Org.BouncyCastle.Security; using Org.BouncyCastle.X509; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Org.BouncyCastle.Math; using Org.BouncyCastle.OpenSsl; using static System.Runtime.InteropServices.JavaScript.JSType; namespace CAUtilLib { public static class BouncyCastleWrapper { public static ILogger? Logger = null; static SecureRandom secureRandom = new SecureRandom(); public static AsymmetricCipherKeyPair GenerateRsaKeyPair(int length = 4096) { var keygenParam = new KeyGenerationParameters(secureRandom, length); var keyGenerator = new RsaKeyPairGenerator(); keyGenerator.Init(keygenParam); return keyGenerator.GenerateKeyPair(); } public static AsymmetricCipherKeyPair GenerateEcKeyPair(string curveName) { var ecParam = SecNamedCurves.GetByName(curveName); var ecDomain = new ECDomainParameters(ecParam.Curve, ecParam.G, ecParam.N); var keygenParam = new ECKeyGenerationParameters(ecDomain, secureRandom); var keyGenerator = new ECKeyPairGenerator(); keyGenerator.Init(keygenParam); return keyGenerator.GenerateKeyPair(); } public static X509Certificate GenerateCertificate( X509Name issuer, X509Name subject, AsymmetricCipherKeyPair issuerKey, AsymmetricKeyParameter subjectPublic, BigInteger? issuerSerialNumber = null, List extensionDatas = null, int days = 36500) { var selfserilaNumber = BigInteger.ProbablePrime(120, secureRandom); if (issuerSerialNumber is null) { issuerSerialNumber = selfserilaNumber; } ISignatureFactory signatureFactory; signatureFactory = new Asn1SignatureFactory( PkcsObjectIdentifiers.Sha256WithRsaEncryption.ToString(), issuerKey.Private); //signatureFactory = new Asn1SignatureFactory( // PkcsObjectIdentifiers.Sha512WithRsaEncryption.ToString(), // issuerKey.Private); var certGenerator = new X509V3CertificateGenerator(); certGenerator.SetIssuerDN(issuer); certGenerator.SetSubjectDN(subject); certGenerator.SetSerialNumber(selfserilaNumber); certGenerator.SetNotAfter(DateTime.UtcNow.AddDays(days)); certGenerator.SetNotBefore(DateTime.UtcNow); certGenerator.SetPublicKey(subjectPublic); certGenerator.AddExtension(X509Extensions.AuthorityKeyIdentifier.Id, false, new AuthorityKeyIdentifier( SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(issuerKey.Public) ) ); certGenerator.AddExtension(X509Extensions.SubjectKeyIdentifier.Id, false, new SubjectKeyIdentifier( SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(subjectPublic) ) ); if (extensionDatas != null) { foreach (var data in extensionDatas) { certGenerator.AddExtension(data.OID, data.Critical, data.ExtensionValue); } } //if (isCertificateAuthority) //{ // certGenerator.AddExtension(X509Extensions.BasicConstraints.Id, true, // new BasicConstraints(0)); // certGenerator.AddExtension(X509Extensions.KeyUsage.Id, true, // new KeyUsage(KeyUsage.KeyCertSign)); //} return certGenerator.Generate(signatureFactory); } public static bool ValidateCertificateChain(List certificates) { for (int index = 0; index < certificates.Count - 1; index++) { var validateCertificate = certificates[index]; var sourceCertificate = certificates[index + 1]; if (!ValidateCert(validateCertificate, sourceCertificate.GetPublicKey())) { return false; } } return true; } public static bool ValidateCert(X509Certificate cert, List certChain) { if (!ValidateCertificateChain(certChain)) { return false; } for (int index = 0; index < certChain.Count; index++) { var validateCertificate = cert; var sourceCertificate = certChain[index]; if (ValidateCert(validateCertificate, sourceCertificate.GetPublicKey())) { return true; } } return false; } public static bool ValidateCert(X509Certificate cert, ICipherParameters pubKey) { try { cert.CheckValidity(DateTime.UtcNow); var tbsCert = cert.GetTbsCertificate(); var sig = cert.GetSignature(); var signer = SignerUtilities.GetSigner(cert.SigAlgName); signer.Init(false, pubKey); signer.BlockUpdate(tbsCert, 0, tbsCert.Length); var verifyResult = signer.VerifySignature(sig); if (!verifyResult) { Logger?.LogDebug("validation failed"); } return verifyResult; } catch (Exception e) { Logger?.LogDebug("validation failed: {0}", e.Message); return false; } } public static X509Certificate GenerateSelfSignedRootCertificate( X509Name subject, AsymmetricCipherKeyPair kp, int days = 36500 ) { List datas = new List() { new AddExtensionData(){ OID = X509Extensions.BasicConstraints.Id, Critical = true, ExtensionValue = new BasicConstraints(true) }, new AddExtensionData() { OID= X509Extensions.KeyUsage.Id, Critical = true, ExtensionValue = new KeyUsage(KeyUsage.DigitalSignature | KeyUsage.KeyCertSign | KeyUsage.CrlSign) } }; return GenerateCertificate(subject, subject, kp, kp.Public, extensionDatas: datas, days: days); } public static X509Certificate GenerateIntermediateCertificate( Pkcs10CertificationRequest csr, X509Certificate rca, AsymmetricCipherKeyPair issuerKey, int pathLengthConstraint ) { var csrInfo = csr.GetCertificationRequestInfo(); List datas = new List() { new AddExtensionData(){ OID = X509Extensions.BasicConstraints.Id, Critical = true, ExtensionValue = new BasicConstraints(pathLengthConstraint) }, new AddExtensionData() { OID= X509Extensions.KeyUsage.Id, Critical = true, ExtensionValue = new KeyUsage(KeyUsage.DigitalSignature | KeyUsage.KeyCertSign | KeyUsage.CrlSign) } }; return GenerateCertificate(rca.SubjectDN, csrInfo.Subject, issuerKey, csr.GetPublicKey(), extensionDatas: datas); } public static X509Certificate GenerateEndCertificate( Pkcs10CertificationRequest csr, X509Certificate rca, AsymmetricCipherKeyPair issuerKey ) { var csrInfo = csr.GetCertificationRequestInfo(); List datas = new List() { new AddExtensionData() { OID= X509Extensions.KeyUsage.Id, Critical = true, ExtensionValue = new KeyUsage(KeyUsage.DigitalSignature | KeyUsage.NonRepudiation | KeyUsage.KeyEncipherment) } }; return GenerateCertificate(rca.SubjectDN, csrInfo.Subject, issuerKey, csr.GetPublicKey(), extensionDatas: datas); } public static X509Certificate SignCertificate( Pkcs10CertificationRequest csr, X509Certificate rca, AsymmetricCipherKeyPair issuerKey) { var csrInfo = csr.GetCertificationRequestInfo(); return GenerateCertificate(rca.SubjectDN, csrInfo.Subject, issuerKey, csr.GetPublicKey(), issuerSerialNumber: rca.SerialNumber); } public static Pkcs10CertificationRequest CreateCertificateReq(AsymmetricCipherKeyPair kp, string commonName, string organizationName) { var subject = CreateX509Name(commonName: commonName, organizationName: organizationName); var csr = new Pkcs10CertificationRequest( PkcsObjectIdentifiers.Sha256WithRsaEncryption.ToString(), subject, kp.Public, null, kp.Private ); return csr; } public static X509Name CreateX509Name( string? commonName = null, string? organizationName = null, string? country = null, string? state = null) { var values = new Dictionary { }; if (!string.IsNullOrEmpty(commonName)) { values.Add(X509Name.CN, commonName); } if (!string.IsNullOrEmpty(organizationName)) { values.Add(X509Name.O, organizationName); } if (!string.IsNullOrEmpty(country)) { values.Add(X509Name.C, country); } if (!string.IsNullOrEmpty(state)) { values.Add(X509Name.ST, state); } return new X509Name(values.Keys.Reverse().ToList(), values); } public static X509Certificate ToBouncyCastle(this System.Security.Cryptography.X509Certificates.X509Certificate2 msCert) { return Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(msCert); } public static System.Security.Cryptography.X509Certificates.X509Certificate2 ToMs(this X509Certificate bcCert) { return System.Security.Cryptography.X509Certificates.X509Certificate2.CreateFromPem(ToStringAsPem(bcCert).Result); } public static string? GetFirstValue(this X509Name x509Name, DerObjectIdentifier identifier) { return x509Name.GetValueList(identifier).FirstOrDefault(); } public static async Task TrySaveAsPemAsync(string name, object[] items) { try { using TextWriter textWriter = new StreamWriter(name); using PemWriter pemWriter = new PemWriter(textWriter); foreach (object obj in items) { pemWriter.WriteObject(obj); } await pemWriter.Writer.FlushAsync(); return true; } catch (Exception ex) { Logger?.LogDebug("SaveAsPemAsync failed {0}", ex.Message); return false; } } public static Task ToStringAsPem(object item) { return ToStringAsPem(new object[] { item }); } public static async Task ToStringAsPem(object[] items) { using StringWriter stringWriter = new StringWriter(); using PemWriter pemWriter = new PemWriter(stringWriter); foreach (object obj in items) { pemWriter.WriteObject(obj); } await pemWriter.Writer.FlushAsync(); return stringWriter.ToString(); } public static Task LoadPemCertFromFile(string name) { return TryLoadPemFromFile(name); } public static X509Certificate? LoadPemCertFromString(string source) { return TryLoadPemFromString(source); } public static Task LoadPemKeyFromFile(string name) { return TryLoadPemFromFile(name); } public static AsymmetricCipherKeyPair? LoadPemKeyFromString(string source) { return TryLoadPemFromString(source); } internal static Task LoadPemCsrFromFile(string source) { return TryLoadPemFromFile(source); } public static Pkcs10CertificationRequest? LoadPemCsrFromString(string source) { return TryLoadPemFromString(source); } public static List LoadPemCrtChaingFromString(string source) { var readList = TryLoadPemListFromString(source); return readList.Where(x => x is X509Certificate).Select(x => (X509Certificate)x).ToList(); } internal static async Task TryLoadPemFromFile(string name) { try { using var fileReader = new StreamReader(name); var result = await fileReader.ReadToEndAsync(); return TryLoadPemFromString(result); } catch (Exception ex) { Logger?.LogDebug("TryLoadPemFromFile failed: {0}", ex.Message); return default(T); } } internal static T? TryLoadPemFromString(string rawString) { try { using TextReader textReader = new StringReader(rawString); using PemReader pemReader = new PemReader(textReader); return (T)pemReader.ReadObject(); } catch (Exception ex) { Logger?.LogDebug("TryLoadPemFromString failed: {0}", ex.Message); return default(T); } } internal static List TryLoadPemListFromString(string rawString) { try { using TextReader textReader = new StringReader(rawString); using PemReader pemReader = new PemReader(textReader); List toReturn = new List(); object result = null; do { result = pemReader.ReadObject(); if (result is null) { break; } toReturn.Add(result); } while (true); return toReturn; } catch (Exception ex) { Logger?.LogDebug("TryLoadPemFromString failed: {0}", ex.Message); return default; } } } }