Generate a new X509Certificate2 with extensions and private key, in C#

There is very little documentation and examples around on how to do this. And when you get stuck on something, you might not know what to search for. So here is a complete example for C#.

I’m using a NuGet from Bouncy Castle (bouncycastle.org).

Contents

The code

using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Operators;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.X509;
using System;
using System.Security.Cryptography.X509Certificates;
using X509Certificate = Org.BouncyCastle.X509.X509Certificate;

namespace SignedXmlValidation.CertStuff
{
    public class CertCreator
    {
        private const string SignatureAlgorithm = "SHA256WithRSA";

        public static X509Certificate2 GenerateX509Certificate(string commonName)
        {
            var keyPair = GetAsymmetricCipherKeyPair();

            var certificateGenerator = GetX509V3CertificateGenerator(keyPair);
            
            // Note: There are more parameters than just commonName
            certificateGenerator.SetSubjectAndIssuer(commonName);

            var bouncyCastleCertificate = GenerateBouncyCastleCertificate(
                keyPair, certificateGenerator);

            return GenerateX509CertificateWithPrivateKey(
                keyPair, bouncyCastleCertificate);
        }

        private static AsymmetricCipherKeyPair GetAsymmetricCipherKeyPair()
        {
            var keyPairGen = new RsaKeyPairGenerator();
            var keyParams = new KeyGenerationParameters(
                new SecureRandom(new CryptoApiRandomGenerator()), 2048);
            keyPairGen.Init(keyParams);

            var keyPair = keyPairGen.GenerateKeyPair();
            return keyPair;
        }

        private static X509V3CertificateGenerator GetX509V3CertificateGenerator(
            AsymmetricCipherKeyPair keyPair)
        {
            var gen = new X509V3CertificateGenerator();
            gen.SetSerialNumber(BigInteger.ProbablePrime(120, new Random()));
            gen.SetNotAfter(DateTime.Now.AddDays(1));
            gen.SetNotBefore(DateTime.Now.AddDays(-1));
            gen.SetPublicKey(keyPair.Public);

            gen.AddExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(false));
            var ski = new SubjectKeyIdentifier(
                SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyPair.Public));
            gen.AddExtension(X509Extensions.SubjectKeyIdentifier, false, ski);
            var keyUsage = new KeyUsage(KeyUsage.DigitalSignature | KeyUsage.KeyCertSign);
            gen.AddExtension(X509Extensions.KeyUsage, true, keyUsage);
            return gen;
        }

        private static X509Certificate GenerateBouncyCastleCertificate(
            AsymmetricCipherKeyPair keyPair, X509V3CertificateGenerator gen)
        {
            ISignatureFactory sigFact = new Asn1SignatureFactory(
                SignatureAlgorithm, keyPair.Private);

            var bcCert = gen.Generate(sigFact);
            return bcCert;
        }

        private static X509Certificate2 GenerateX509CertificateWithPrivateKey(
            AsymmetricCipherKeyPair keyPair,
            X509Certificate bcCert)
        {
            var privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(keyPair.Private);
            var asn1Seq = (Asn1Sequence)Asn1Object.FromByteArray(
                privateKeyInfo.ParsePrivateKey().GetDerEncoded());
            var rsaPrivateKeyStruct = RsaPrivateKeyStructure.GetInstance(asn1Seq);
            var rsa = DotNetUtilities.ToRSA(rsaPrivateKeyStruct);
            var x509Cert = new X509Certificate2(bcCert.GetEncoded());
            return x509Cert.CopyWithPrivateKey(rsa);
        }
    }
}

I used an extension method for setting the Subject and Issuer:

using System.Collections;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.X509;

namespace SignedXmlValidation.CertStuff
{
    public static class CertExtensions
    {
        public static void SetSubjectAndIssuer(this X509V3CertificateGenerator gen,
            string commonName = null,
            string country = null,
            string organizationalUnit = null,
            string locality = null
        )
        {
            IDictionary attrs = new Hashtable();
            if (commonName != null) attrs[X509Name.CN] = commonName;
            if (country != null) attrs[X509Name.C] = country;
            if (organizationalUnit != null) attrs[X509Name.O] = organizationalUnit;
            if (locality != null) attrs[X509Name.L] = locality;

            IList ord = new ArrayList();
            if (commonName != null) ord.Add(X509Name.CN);
            if (country != null) ord.Add(X509Name.C);
            if (organizationalUnit != null) ord.Add(X509Name.O);
            if (locality != null) ord.Add(X509Name.L);

            gen.SetSubjectDN(new X509Name(ord, attrs));
            gen.SetIssuerDN(new X509Name(ord, attrs));
        }
    }
}

Relative Distinguished Names (RDN)

Below is a list of some values you can use when you specify distinguished names.

You find the specification for X.509 PKI certificates here: https://tools.ietf.org/html/rfc5280
The spec for OID‘s are in appendix A: https://tools.ietf.org/html/rfc5280#appendix-A
You can find Short names in this spec: https://tools.ietf.org/html/rfc4519

Short NameOIDDescription
C2.5.4.6Country
O2.5.4.10Organization
OU2.5.4.11Organizational Unit
T2.5.4.12Title
Street2.5.4.9Street
DnQualifier2.5.4.46Distinguished Name Qualifier
ST2.5.4.8State or Province name
CN2.5.4.3Common name (e.g., “Susan Housley”)
SERIALNUMBER2.5.4.5Serial number
L2.5.4.7Locality
Name2.5.4.41Name
Surname2.5.4.4Surname
GivenName2.5.4.42Given name
Initials2.5.4.43Initials
Pseudonym2.5.4.65Pseudonym
BusinessCategory2.5.4.15Business Category

Signature Algorithm Strings

There were found in Bouncy Castle code (sorted):

  • “DSAWITHSHA1”
  • “ECDSAWITHSHA1”
  • “GOST3411-2012-256WITHECGOST3410-2012-256”
  • “GOST3411-2012-256WITHGOST3410-2012-256”
  • “GOST3411-2012-512WITHECGOST3410-2012-512”
  • “GOST3411-2012-512WITHGOST3410-2012-512”
  • “GOST3411WITHECGOST3410”
  • “GOST3411WITHECGOST3410-2001”
  • “GOST3411WITHECGOST3410-2012-256”
  • “GOST3411WITHECGOST3410-2012-512”
  • “GOST3411WITHGOST3410”
  • “GOST3411WITHGOST3410-2001”
  • “GOST3411WITHGOST3410-2012-256”
  • “GOST3411WITHGOST3410-2012-512”
  • “GOST3411WITHGOST3410-94”
  • “MD2WITHRSA”
  • “MD2WITHRSAENCRYPTION”
  • “MD5WITHRSA”
  • “MD5WITHRSAENCRYPTION”
  • “RIPEMD128WITHRSA”
  • “RIPEMD128WITHRSAENCRYPTION”
  • “RIPEMD160WITHPLAIN-ECDSA”
  • “RIPEMD160WITHRSA”
  • “RIPEMD160WITHRSAENCRYPTION”
  • “RIPEMD256WITHRSA”
  • “RIPEMD256WITHRSAENCRYPTION”
  • “SHA-1WITHDSA”
  • “SHA-1WITHRSA”
  • “SHA-1WITHRSAENCRYPTION”
  • “SHA-224WITHRSA”
  • “SHA-224WITHRSAENCRYPTION”
  • “SHA-256WITHRSA”
  • “SHA-256WITHRSAENCRYPTION”
  • “SHA-384WITHRSA”
  • “SHA-384WITHRSAENCRYPTION”
  • “SHA-512(224)WITHRSA”
  • “SHA-512(224)WITHRSAENCRYPTION”
  • “SHA-512(256)WITHRSA”
  • “SHA-512(256)WITHRSAENCRYPTION”
  • “SHA-512WITHRSA”
  • “SHA-512WITHRSAENCRYPTION”
  • “SHA1WITHCVC-ECDSA”
  • “SHA1WITHDSA”
  • “SHA1WITHECDSA”
  • “SHA1WITHPLAIN-ECDSA”
  • “SHA1WITHRSA”
  • “SHA1WITHRSAANDMGF1”
  • “SHA1WITHRSAENCRYPTION”
  • “SHA224WITHCVC-ECDSA”
  • “SHA224WITHDSA”
  • “SHA224WITHECDSA”
  • “SHA224WITHPLAIN-ECDSA”
  • “SHA224WITHRSA”
  • “SHA224WITHRSAANDMGF1”
  • “SHA224WITHRSAENCRYPTION”
  • “SHA256WITHCVC-ECDSA”
  • “SHA256WITHDSA”
  • “SHA256WITHECDSA”
  • “SHA256WITHPLAIN-ECDSA”
  • “SHA256WITHRSA”
  • “SHA256WITHRSAANDMGF1”
  • “SHA256WITHRSAENCRYPTION”
  • “SHA256WITHSM2”
  • “SHA256WITHXMSS”
  • “SHA256WITHXMSSMT”
  • “SHA3-224WITHDSA”
  • “SHA3-224WITHECDSA”
  • “SHA3-224WITHRSA”
  • “SHA3-224WITHRSAANDMGF1”
  • “SHA3-224WITHRSAENCRYPTION”
  • “SHA3-256WITHDSA”
  • “SHA3-256WITHECDSA”
  • “SHA3-256WITHRSA”
  • “SHA3-256WITHRSAANDMGF1”
  • “SHA3-256WITHRSAENCRYPTION”
  • “SHA3-384WITHDSA”
  • “SHA3-384WITHECDSA”
  • “SHA3-384WITHRSA”
  • “SHA3-384WITHRSAANDMGF1”
  • “SHA3-384WITHRSAENCRYPTION”
  • “SHA3-512WITHDSA”
  • “SHA3-512WITHECDSA”
  • “SHA3-512WITHRSA”
  • “SHA3-512WITHRSAANDMGF1”
  • “SHA3-512WITHRSAENCRYPTION”
  • “SHA3-512WITHSPHINCS256”
  • “SHA384WITHCVC-ECDSA”
  • “SHA384WITHDSA”
  • “SHA384WITHECDSA”
  • “SHA384WITHPLAIN-ECDSA”
  • “SHA384WITHRSA”
  • “SHA384WITHRSAANDMGF1”
  • “SHA384WITHRSAENCRYPTION”
  • “SHA512(224)WITHRSA”
  • “SHA512(224)WITHRSAENCRYPTION”
  • “SHA512(256)WITHRSA”
  • “SHA512(256)WITHRSAENCRYPTION”
  • “SHA512WITHCVC-ECDSA”
  • “SHA512WITHDSA”
  • “SHA512WITHECDSA”
  • “SHA512WITHPLAIN-ECDSA”
  • “SHA512WITHRSA”
  • “SHA512WITHRSAANDMGF1”
  • “SHA512WITHRSAENCRYPTION”
  • “SHA512WITHSPHINCS256”
  • “SHA512WITHXMSS”
  • “SHA512WITHXMSSMT”
  • “SHAKE128WITHXMSS”
  • “SHAKE128WITHXMSSMT”
  • “SHAKE256WITHXMSS”
  • “SHAKE256WITHXMSSMT”
  • “SM3WITHSM2”

X509Extensions (sorted)

Code from Bouncy Castle, but usage is documented in section 4.2 in the spec: https://tools.ietf.org/html/rfc5280#section-4.2

  • DerObjectIdentifier AuditIdentity = new DerObjectIdentifier(“1.3.6.1.5.5.7.1.4”);
  • DerObjectIdentifier AuthorityInfoAccess = new DerObjectIdentifier(“1.3.6.1.5.5.7.1.1”);
  • DerObjectIdentifier AuthorityKeyIdentifier = new DerObjectIdentifier(“2.5.29.35”);
  • DerObjectIdentifier BasicConstraints = new DerObjectIdentifier(“2.5.29.19”);
  • DerObjectIdentifier BiometricInfo = new DerObjectIdentifier(“1.3.6.1.5.5.7.1.2”);
  • DerObjectIdentifier CertificateIssuer = new DerObjectIdentifier(“2.5.29.29”);
  • DerObjectIdentifier CertificatePolicies = new DerObjectIdentifier(“2.5.29.32”);
  • DerObjectIdentifier CrlDistributionPoints = new DerObjectIdentifier(“2.5.29.31”);
  • DerObjectIdentifier CrlNumber = new DerObjectIdentifier(“2.5.29.20”);
  • DerObjectIdentifier DeltaCrlIndicator = new DerObjectIdentifier(“2.5.29.27”);
  • DerObjectIdentifier ExpiredCertsOnCrl = new DerObjectIdentifier(“2.5.29.60”);
  • DerObjectIdentifier ExtendedKeyUsage = new DerObjectIdentifier(“2.5.29.37”);
  • DerObjectIdentifier FreshestCrl = new DerObjectIdentifier(“2.5.29.46”);
  • DerObjectIdentifier InhibitAnyPolicy = new DerObjectIdentifier(“2.5.29.54”);
  • DerObjectIdentifier InstructionCode = new DerObjectIdentifier(“2.5.29.23”);
  • DerObjectIdentifier InvalidityDate = new DerObjectIdentifier(“2.5.29.24”);
  • DerObjectIdentifier IssuerAlternativeName = new DerObjectIdentifier(“2.5.29.18”);
  • DerObjectIdentifier IssuingDistributionPoint = new DerObjectIdentifier(“2.5.29.28”);
  • DerObjectIdentifier KeyUsage = new DerObjectIdentifier(“2.5.29.15”);
  • DerObjectIdentifier LogoType = new DerObjectIdentifier(“1.3.6.1.5.5.7.1.12”);
  • DerObjectIdentifier NameConstraints = new DerObjectIdentifier(“2.5.29.30”);
  • DerObjectIdentifier NoRevAvail = new DerObjectIdentifier(“2.5.29.56”);
  • DerObjectIdentifier PolicyConstraints = new DerObjectIdentifier(“2.5.29.36”);
  • DerObjectIdentifier PolicyMappings = new DerObjectIdentifier(“2.5.29.33”);
  • DerObjectIdentifier PrivateKeyUsagePeriod = new DerObjectIdentifier(“2.5.29.16”);
  • DerObjectIdentifier QCStatements = new DerObjectIdentifier(“1.3.6.1.5.5.7.1.3”);
  • DerObjectIdentifier ReasonCode = new DerObjectIdentifier(“2.5.29.21”);
  • DerObjectIdentifier SubjectAlternativeName = new DerObjectIdentifier(“2.5.29.17”);
  • DerObjectIdentifier SubjectDirectoryAttributes = new DerObjectIdentifier(“2.5.29.9”);
  • DerObjectIdentifier SubjectInfoAccess = new DerObjectIdentifier(“1.3.6.1.5.5.7.1.11”);
  • DerObjectIdentifier SubjectKeyIdentifier = new DerObjectIdentifier(“2.5.29.14”);
  • DerObjectIdentifier TargetInformation = new DerObjectIdentifier(“2.5.29.55”);

Key Usage values

Code from Bouncy Castle, but usage is documented in section 4.2.1.3 in the spec: https://tools.ietf.org/html/rfc5280#section-4.2.1.3

  • int DigitalSignature = 128;
  • int NonRepudiation = 64;
  • int KeyEncipherment = 32;
  • int DataEncipherment = 16;
  • int KeyAgreement = 8;
  • int KeyCertSign = 4;
  • int CrlSign = 2;
  • int EncipherOnly = 1;
  • int DecipherOnly = 32768;