Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Generating a Certificate using a C# Bouncy Castle Library

4.64/5 (7 votes)
1 Apr 2019CPOL11 min read 33.5K   2.1K  
Generating a certificate file for web server and CA server(s) using a .NET C# Bouncy Castle library

Introduction

About a year ago in a conversation with a fellow worker, we came up with the idea to create a Graphical User Interface (GUI) application that would enable us to generate a certificate request file for a web server, which is sent to an external or internal Certificate Authority (CA) to generate a web server certificate, that will be used for server authentication and encryption web traffic between server and web clients.
The entire process of creating a server certificate was as follows:

  • A certificate request file and a file with a private key for the web server were generated. Certificate request file contains the alternative names for accessing the web server.
  • After that, the generated request file will be sent to the external CA (for example, GoDaddy) or the internal Active Directory (AD) CA for signing (creating a certificate file with public key).
  • CA server signs the request file (generates a public key certificate) and sends it back to the sender.
  • The signed request file merges with previously generated files that contain the private key and generates a certificate for a web server, which is used to authenticate the web server and for encrypting communication between client and server.

Background

For the above mentioned procedure, we use a Linux Virtual Machine (VM) with installed OpenSSL application.

Since Internet Information Server (IIS) is used as a web server on the Windows platform, and Windows is the platform on which we are developing our software, we have come to the conclusion that it would be more practical to use Windows operating system (OS) instead of Linux VM to generate certificate request file. For starters, we installed OpenSSL on windows desktop machines. The main idea was to create a GUI which generates a parameter file based on the information entered in the application form. Then, the parameter file will be used as a parameter when calling openssl.exe application. After that, openssl.exe application will generate a certificate request file that will be sent to an external CA or an internal CA for generating a signed certificate file (file with public key). The openssl.exe application will be called with the necessary parameters directly from the GUI.

When a signed file is obtained from the CA server, the idea was to import it through a GUI and merge it with a private key that was generated in the previous step.

This was the main idea for an application that was to be implemented on the .NET platform using C#.

In general, I did not like the idea of calling an external command line application from a GUI, because checking whether the generation of the certificate request file was successful or not, you could only do that by parsing the outgoing messages from the oppenssl.exe command line application.

I thought it would be better to implement the entire application by using C# without calling the oppenssl.exe external command line application. Part of the work that was done by openssl.exe applications had to be implemented directly in C#.

So I began to search the internet to find an example code on how to achieve that. In the beginning, I found many examples of the code written in Java programing language on how to create a certificate file. Since I've previously programmed it in Java, one of the ways to do it was to analyze a Java code and write the same code using C#, but then I realized it was a "Sisyphus job".

I continued to search the Internet and found examples of code in C# that used Microsoft's original functions for working with certificates. These examples seemed very complicated to implement and usually did not contain nor explain what I need.

The primary requirement was to create a certificate with alternative names for the web server.

First Finding

After some time of reading different articles about certificates, I found the article “Bouncy Castle - Subject Alternative Names” which explained exactly what I needed - how to create a certificate for a web server with alternative names. I have read all the articles from the blog that relate to this topic “Using Bouncy Castle from .NET” and for the first time, I heard about the project “Bouncy Castle”.

The “Bouncy Castle” project was originally created as a Java code, which was then converted to C#. It is a great library with plenty of good examples on how to use code from the library. C# source code can be found at GitHub location https://github.com/bcgit/bc-csharp.

Using the Code

So I started developing the application. For the implementation of the GUI, I decided to use MahApps.Metro library and for working with certificates, I used Bouncy Castle C# library.

An application with two menu options was first made. The first menu option was used to generate a certificate request file and a file with a private certificate key. After that, a certificate request file will be sent to an external or internal CA server to generate a signed certificate file (file with public key).
The second option of the menu was to generate a certificate file that contains a public and a private key, based on the signed certificate file obtained from the CA server and the private key file that was previously generated.
To test whether the generated certificate request file was valid, we used Windows VM with the CA Roll installed. Each time after generating a certificate request file, it was necessary to copy that file to VM with the installed CA role, generate the signed file there and then return the file back to the machine where the application was started. The generated file was then merged with the private key file and a certificate was generated. The generated certificate was then imported into the local certificate store to check if generated certificate file has valid data.

It was a very tedious and complicated process. That's why I was trying to think of ways to simplify the entire procedure of testing the generated request file and make the verification process simpler. While reading the article on the above mentioned blog „Bouncy Castle - Being a Certificate Authority“, I found out that differences between the certificate for the CA server and the certificate used for other purposes (for example, for web server authentication and data encryption) differs only in one parameter. For the CA server parameter, the "Basic Constraints" extension is set to true, and for other certificates this value is set to false.

That's why I came up with the idea to implement a certificate for a root CA server that would sign the generated request file and in that way speed up the process of validating the generated certificate request file. In addition, it was necessary to implement a new menu option where the signing of a request file with the root CA file that we had previously generated would be done. This additional menu option would speed up the entire procedure for testing and generating web server certificates.

The Code

Therefore, the following options were added to the form for generating a certificate request:

  1. For Alternative Names for the Certificate:
    C#
    string[] subjectAlternativeNames = new string[alternativSubjectNames.Count];
    int i = 0;
    foreach (var item in alternativSubjectNames)
    {
      subjectAlternativeNames[i++] = item.AlternativSubjectName;
    }
    GeneralNames names = new GeneralNames(subjectAlternativeNames.Select(n => 
                                          new GeneralName(GeneralName.DnsName, n)).ToArray());
    Asn1OctetString asn1ost = new DerOctetString(names);
    extensions.Add(X509Extensions.SubjectAlternativeName, 
                   new Org.BouncyCastle.Asn1.X509.X509Extension(false, asn1ost));
  2. The parameter that determines whether creating a certificate request file is the request for the server to be CA:
    C#
    var extensions = new Dictionary<DerObjectIdentifier, Org.BouncyCastle.Asn1.X509.X509Extension>();
    extensions.Add(X509Extensions.BasicConstraints,
                    new Org.BouncyCastle.Asn1.X509.X509Extension(true, asn1ost0));

When applying for a certificate, it is possible to select the following parameter(s) for "Choose Key Usage":

C#
KeyUsageCCBData = new ObservableCollection<KeyUsageData>();
KeyUsageCCBData.Add(new KeyUsageData("DigitalSignature", KeyUsage.DigitalSignature));
KeyUsageCCBData.Add(new KeyUsageData("NonRepudiation", KeyUsage.NonRepudiation));
KeyUsageCCBData.Add(new KeyUsageData("KeyEncipherment", KeyUsage.KeyEncipherment));
KeyUsageCCBData.Add(new KeyUsageData("DataEncipherment", KeyUsage.DataEncipherment));
KeyUsageCCBData.Add(new KeyUsageData("KeyAgreement", KeyUsage.KeyAgreement));
KeyUsageCCBData.Add(new KeyUsageData("KeyCertSign", KeyUsage.KeyCertSign));
KeyUsageCCBData.Add(new KeyUsageData("CrlSign", KeyUsage.CrlSign));
KeyUsageCCBData.Add(new KeyUsageData("EncipherOnly", KeyUsage.EncipherOnly));
KeyUsageCCBData.Add(new KeyUsageData("DecipherOnly", KeyUsage.DecipherOnly));

And to choose the following parameter(s) for "Choose Extended Key Usage":

C#
ExtendedKeyUsageCCBData = new ObservableCollection<ExtendedKeyUsageData>();
ExtendedKeyUsageCCBData.Add(new ExtendedKeyUsageData
   ("AnyExtendedKeyUsage", KeyPurposeID.AnyExtendedKeyUsage));
ExtendedKeyUsageCCBData.Add(new ExtendedKeyUsageData
   ("ServerAuthetification", KeyPurposeID.IdKPServerAuth));
ExtendedKeyUsageCCBData.Add(new ExtendedKeyUsageData
   ("ClientAuthetification", KeyPurposeID.IdKPClientAuth));
ExtendedKeyUsageCCBData.Add(new ExtendedKeyUsageData("CodeSigning", KeyPurposeID.IdKPCodeSigning));
ExtendedKeyUsageCCBData.Add(new ExtendedKeyUsageData
   ("PEmailProtection", KeyPurposeID.IdKPEmailProtection));
ExtendedKeyUsageCCBData.Add(new ExtendedKeyUsageData
   ("IpsecEndSystem", KeyPurposeID.IdKPIpsecEndSystem));
ExtendedKeyUsageCCBData.Add(new ExtendedKeyUsageData("IpsecTunnel", KeyPurposeID.IdKPIpsecTunnel));
ExtendedKeyUsageCCBData.Add(new ExtendedKeyUsageData("IpsecUser", KeyPurposeID.IdKPIpsecUser));
ExtendedKeyUsageCCBData.Add(new ExtendedKeyUsageData("TimeStamping", KeyPurposeID.IdKPTimeStamping));
ExtendedKeyUsageCCBData.Add(new ExtendedKeyUsageData("OcspSigning", KeyPurposeID.IdKPOcspSigning));

Certificate request file

The idea with the parameter "Is this CA certificate" was to enable the generation of a certificate request file that will serve as the next CA authority in the chain of CA servers. Typical CA server(s) configuration on the Windows platform consists of 2 or 3 levels of CA servers. The first server is installed as a root CA (master CA) server that is a workgroup computer and the next one or two CA servers are computers that are installed as members of the Windows AD domain and are practically doing the job of issuing and administering the certificates. The first root CA server is usually installed as a VM that switches on only when you need to publish a list of withdrawn certificates, while the next one or two servers are always online.

The first root CA server certificate is always implemented as a self-sign certificate, while each subsequent CA certificate in the certificate chain is signed with a certificate at a higher level, which means that there exists a following structure of the certificates:

II level CA certificate: Root CA + intermediate CA
  masterCA (self-sign certificate)
    intermediateCA (certificate signed with masterCA certificate)

or:

III level CA certificate: Root CA + intermediate CA + issuer CA
  masterCA (self-sign certificate)
    intermediateCA (certificate signed with masterCA certificate)
      issuerCA (certificate signed with intermediateCA certificate)

In order to realize CA infrastructure on the windows platform for "II level CA certificate", we would need 3 VMs (root CA-> workgroup VM, DC - AD domain controller VM, intermediate CA-> AD computer with CA role installed) or for "III level CA certificate" 4 VMs (root CA-> workgroup VM, DC - AD domain controller VM, intermediate CA-> AD computer with CA role installed, issuer CA-> AD computer with CA role installed).

Note

Microsoft does not recommend installing the CA roll on an AD computer with a DC role. Therefore, it is necessary to separate the computer with a DC role as a separate VM.

Because generating certificates for intermediate CA and issuer CA servers was complicated (you needed to use the whole process of generating a separate request file and then signing and issuing a certificate separately for each CA server), I made an additional menu option.

The fifth application menu option can be used to generate certificates for I, II or III levels of CA certificates that can then be used to sign the generated certificate request file that contains the chain of certificates which they are signed with.

Image 2

Files generated for CA servers from the application in that way do not technically differ from the certificates that would be used from the VM with the CA Roll installed. Importing a generated certificate to a web server that contains CA certificate chain (a file with the .pfx extension that is used to sign the request file), automatically stores CA certificates in the appropriate certificate store on the web server machine. In order for this part to function on the client, it is necessary to import the chain of CA certificates into the appropriate certificate stalls on the client computer that accesses the web server.

The most interesting part of the code was the part for issuing certificates based on the data contained in the generated request file. It was necessary to read the data from the file, based on which I then generated signed certificate file.

C#
//
// Add certificate extensions
//
Asn1Set attributes = cerRequest.GetCertificationRequestInfo().Attributes;
if (attributes != null)
{
  for (int i = 0; i != attributes.Count; i++)
  {
    AttributePkcs attr = AttributePkcs.GetInstance(attributes[i]);
    if (attr.AttrType.Equals(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest))
    {
       X509Extensions extensions1 = X509Extensions.GetInstance(attr.AttrValues[0]);
       foreach (DerObjectIdentifier oid in extensions1.ExtensionOids)
       {
         Org.BouncyCastle.Asn1.X509.X509Extension ext = extensions1.GetExtension(oid);
         certGen.AddExtension(oid, ext.IsCritical, ext.GetParsedValue());
       }
     }
  }
}

To provide additional security when creating a file in which a private key is stored, I added the ability to enter a password that will be used to encrypt the contents of that file.

C#
var textWriter = new StringWriter();
var pemWriter = new PemWriter(textWriter);
if (!String.IsNullOrEmpty(password))
{
  pemWriter.WriteObject(subjectKeyPair.Private, "DESEDE", password.ToCharArray(), new SecureRandom());
}
else
{
  pemWriter.WriteObject(subjectKeyPair.Private);
}

pemWriter.Writer.Flush();
string privateKeyPem = textWriter.ToString();
using (var writer = new StreamWriter(outputPrivateKeyName))
{
  writer.WriteLine(privateKeyPem);
}

If the content of a private certificate key file is encrypted with a password, when reading the data from that file, it is then necessary to use the appropriate password:

C#
static AsymmetricKeyParameter ReadPrivateKey(string privateKeyFileName, string password=null)
{
   AsymmetricCipherKeyPair keyPair;
   if (password == null)
   {
      using (var reader = File.OpenText(privateKeyFileName))
       keyPair=(AsymmetricCipherKeyPair)new PemReader(reader).ReadObject();
   }
   else
   {
     using (var reader = File.OpenText(privateKeyFileName))
        keyPair=(AsymmetricCipherKeyPair) new PemReader(reader, 
                                                    new PasswordFinder(password)).ReadObject();
   }
   return keyPair.Private;
}

The generated certificate file with the .pfx extension contains the chain of CA certificates that were used when signing the request certificate file. On the Windows platform, by double clicking on this generated file, it opens the Certificate Import wizard which automatically stores all the certificates contained in the .pfx file to the appropriate certificate store.

Import wizard

How Do You Use It?

Inside the binary distribution of GenCert project on GitHub, you can find application user manual inside files "GenCert" and a step by step description on how to generate a certificate inside files "GenCert steps".

When you start the application, activate the first menu option "Create Request". Fill the form with appropriate data and press the button "Generate". After that, press the button "Continue" and repeat this process on each opened form.

Conclusion

This application has educational and real use.

  • Educative use - understanding how the process of generating certificates for CA servers and issuing certificates for different purposes by the CA server(s) are working.
  • Real use - to create a certificate request file that will be sent to an internal or external CA server(s) for generating a certificate public key and creating a certificate with a public and a private key (file with .pfx extension).

If the certificate is to be used for internal purposes, it is also possible to use the generated CA certificates from the application itself. The generated certificate for a web server needs to be imported on a web server computer and certificates from CA servers that have signed the certificate for the web server certificate need to be imported on the client computers that access the web server.

Functionally and technically, there is no difference between CA certificates generated using this application and certificates generated by external or internal CA server(s).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)