Bouncy Castle: Create a Basic Certificate


Bouncy Castle is a pretty powerful cryptographic library. Unfortunately, one of its failings is a lack of proper documentation (Although there is a pretty comprehensive test project suite). As documentation is lacking, it can feel pretty good when you finally get Bouncy Castle to do something useful.

In this article I explain how to use Bouncy Castle (C# .NET variant) to create a simple certificate. For a source code example, please refer to my Certificate Generator tool (Certificate.cs)

 

For additional information on Bouncy Castle See these previous articles:

Notes:

 

Overview

Before jumping into the actual code, I want to point out what will need to be done to generate a Certificate and save it in either DER or PKCS12 format. This is done so you have an at-a-glance view of what needs to be coded to make this work. The steps below are bouncy castle specific, although the general principles probably apply to a number of crypto APIs.

Generate the keys and the Certificate:

  • Create an Asymmetric keypair Generator
  • Generate the Asymmetric keypair
  • Create a Certificate Generator
  • Assign properties to the Certificate generator (CN, Bitstrength, Validity period, etc...)
  • Generate a Certificate signed by the issuer's private certificate (we'll be doing self signed here)

Package as a DER:

  • Grab a DER encoded byte array from the certificate
  • Use a .NET FileStream to write the bytes out to the Hard drive. That's it!

Package as a PKCS12:

  • Create a new PKCS12 Store builder
  • Add a Certificate entry to the store
  • Add a private key entry to the store
  • Write out the PKCS12 store to a .NET MemoryStream
  • Save the bytes to a FileStream (You could probably save directly to a FileStream)

 

 

Section 0: .NET using statements

One thing that bugs me a lot about Stack Overflow is that sometimes your questions get edited to remove the using statements that go at the top of the source file. This is frustrating since sometimes it can be difficult to find out which namespace a class 'lives' in. In this article I'm going to list the using statements that will be needed to make a Certificate in Bouncy Castle:

using System;
using System.IO;

using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.X509;


Create the Asymmetric keys and the Certificate

First, create a keypair generator. In this example we are instantiating a new instance of RsaKeyPairGenerator and feeding it the appropriate KeyGenerationParameters and the Bit Strength of the resulting keys:

// Keypair Generator
RsaKeyPairGenerator kpGenerator = new RsaKeyPairGenerator();
kpGenerator.Init(new KeyGenerationParameters(new SecureRandom(), 2048));

 

Generate the keypair using the GenerateKeyPair() method:

// Create a keypair
AsymmetricCipherKeyPair  kp = kpGenerator.GenerateKeyPair();

 

Instantiate a new X509V3CertificateGenerator and assign it the Certificate Properties you want the final certificate to have. In this example we create a couple of X509Name objects that hold the Common Name (Certificate DN) and IssuerDN. We also use a BigInteger to come up with a serial number and a couple of DateTime objects to store the valid from/to dates:

// Certificate Generator
X509V3CertificateGenerator cGenerator = new X509V3CertificateGenerator();
cGenerator.SetSerialNumber(BigInteger.ProbablePrime(120, new Random()));
cGenerator.SetSubjectDN(new X509Name("CN=" + "some.machine.domain.tld"));
cGenerator.SetIssuerDN(new X509Name("CN=" + "issuer's name"));
cGenerator.SetNotBefore(DateTime.Now);
cGenerator.SetNotAfter(DateTime.Now.Add(new TimeSpan(365, 0, 0, 0))); // Expire in 1 year
cGenerator.SetSignatureAlgorithm(HashType.SHA1withDSA.ToString()); // See the Appendix Below for info on the hash types supported by Bouncy Castle C#
cGenerator.SetPublicKey(kp.Public); // Only the public key should be used here!

 

Generate a Certificate signed by the issuer's private key. In this case we're making a self-signed certificate so we'll use the private key that was generated above:

X509Certificate cert = cGenerator.Generate(kp.Private); // Create a self-signed cert

 

Congratulations! At this point you have valid X509 Certificate. Read on for how to get the bytes written out to the file system.
Note: This is NOT the same as a .NET X509Certificate2. Conversions need to be done when working with both types (outside the scope of this article) 

 

 

Option 1: Package and save as a DER encoded file

This is the easiest format to save your certificate in. It involves obtaining the DER encoded bytes of the certificate object and writing them out to the file system.

 

Get the encoded bytes with the GetEncoded() method that is available on all certificate objects:

byte[] encoded = cert.GetEncoded();

 

Now, write the bytes out to the file system using a FileStream:

using (FileStream outStream = new FileStream("c:\someCertname.der", FileMode.Create, FileAccess.ReadWrite)) {
outStream.Write(encoded, 0, encoded.Length);
}

 

If everything works you should be able to double-click on the file in the Windows Explorer and import the certificate using the Cert import wizard.

 

 

Option 2: Package and save as a PKCS12 file (Includes the private key)

This is a little bit more complex since we need to create a PKCS12 store before writing anything out. On the whole it is not too difficult, though.

 

Create AND build a new Pkcs12Store object. This will let us create the actual PKCS12 store:

// Create the PKCS12 store
Pkcs12Store store = new Pkcs12StoreBuilder().Build();

 

Add a certificate entry to the store. The certificate needs to be converted to an X509CertificateEntry object for this to work. Once we have the data in the correct type, we can use the SetCertificateEntry method to add the cert data to the PKCS#12 store. It needs to have an alias so I set it to the certificate's DN in this example:

// Add a Certificate entry
X509CertificateEntry certEntry = new X509CertificateEntry(cert);
store.SetCertificateEntry(cert.SubjectDN.ToString(), certEntry); // use DN as the Alias.

 

Next add an entry for the Certificate's Private key. This is like the CertificateEntry where we need to give it an alias. I just use the Certificate's DN + "_key" and it seems to work for me. One additional thing here is that you need to include the X509CertificateEntry that are associated with the private key in an array form. Since we only have a self-signed certificate this example will only have one certificate in the 'chain'. I'm not sure how this would work with more than one certificate:

// Add a key entry
AsymmetricKeyEntry keyEntry = new AsymmetricKeyEntry(kp.Private);
store.SetKeyEntry(cert.SubjectDN.ToString() + "_key", keyEntry, new X509CertificateEntry[] { certEntry }); // Note that we only have 1 cert in the 'chain'

 

At this point you have everything you need to save the PKCS#12 store to the file system. You just need to write it out. Here is a quick way to perform the 'save' operation:

// Save to the file system
  using (var filestream = new FileStream(@"c:\certificatename.pfx", FileMode.Create, FileAccess.ReadWrite)) {
    store.Save(filestream, "passwordProtected".ToCharArray(), new SecureRandom());
  }
    1. We use the Save() method that is avaialble on the BouncyCastle Pkcs12Store object to write out to a FileStream
    2. Be sure to specify a password you can remember. You will be prompted for the password when you use the store.

 

 

Appendix: Hash Types supported by Bouncy Castle (C#.NET)

This enum can be used to simplify your code. Rather than passing in "SHA1withDSA" (String literal) you can do HashType.SHA1withDSA.ToString()

I like to use enums where possible since it restricts the number of 'magic literals' in my code.

public enum HashType {
  SHA1withDSA, //-- DSA
  SHA1withECDSA, //
  SHA224withECDSA, // ECDSA with SHA1 and SHA2 support
  SHA256withECDSA, //
  SHA384withECDSA, //
  SHA512withECDSA, //
  MD2withRSA, // --
  MD5withRSA, // --
  SHA1withRSA, // --
  SHA224withRSA, // -- RSA with MD2, MD5, SHA1, SHA2 and RIPEMD
  SHA256withRSA, // --
  SHA384withRSA, // --
  SHA512withRSA, // --
  RIPEMD160withRSA, // -- RIPEMD hash
  RIPEMD128withRSA, // --
  RIPEMD256withRSA, // --
}