.NET Security Workshop
Home About Workshops Articles Writing Talks Books Contact

13. Certificates

Certificates are essentially a mechanism to provide public keys in a way that allows you to trust them. If I want to allow someone to send me some confidential information (for example a session key) I could give them my public key. They can then use this to encrypt the data and only I will be able to decrypt the data because only I will have the corresponding private key. This supposes that there is a mechanism for me to distribute my public key, otherwise the conversation is susceptible to a middleman attack.

In such an attack, if you ask me for my public key, Eve, who is listening into the conversation, will take interest. I send you my public key and Eve intercepts this message, extracts my public key and substitutes it with hers. You get the reply, which you think is from me but is really from Eve, and you encrypt your message with the public key. You think the key is mine when in fact it is Eve's. You send your encrypted message back to me, which Eve intercepts, decrypts using her private key, and then encrypts the cleartext with my public key that she obtained earlier and returns the message to me. You and I think that everything is happening correctly: you get a public key and I get an encrypted message that I can decrypt with my private key. Meanwhile Eve sits between us eavesdropping on our conversation. Certificates are a mechanism to prevent such a situation, because a certificate contains not just the public key, but also it contains identifying information that can be verified.

The important thing about a certificate is that it is created by a trusted third party. The third party, called a certificate authority (CA), authenticates personal information about a user and if it is happy that the user is who he says he is, the CA can issue a certificate that contains the public key and the personal information. To show that the CA is happy with the information it will sign the certificate (that is, the CA will create a hash from the certificate encrypt the hash with its private key). If both you and I trust the CA then both of us can use the certificate. When you get the certificate you can use the CA's public key to check that the hash of the certificate that you obtained from me is the same as the hash created by the CA. This ensures that the certificate has not been tampered during distribution.

Of course, this third party, the CA, is important. You might, for example, trust a global organization (perhaps run by the UN or ICANN) to provide this service, but the bureaucracy of such a body issuing many millions of certificates would be a problem. So another solution had to be used. Answer this question: what is it about the CA that that you trust? Why do you trust anyone? Usually, you trust people or organizations that have a reputation for being trustworthy (for example, many people trust the BBC as a news source) but you reserve the right to change your mind. If you trust one organization you are likely to trust organizations that they trust. The same is true of CAs. If you trust one CA then you will trust the CAs that it trusts. If you extend this logically, it means that you can receive a certificate signed by a CA that you do not know, you can then obtain the certificate of this CA to see which CA signed it. If you continue this process you will eventually get a certificate that is not signed (well, most likely self-signed which does not add any trust) or you get to a CA that you do trust.

Since we do not live in an ideal world we must be able to handle the situation of the certificate becoming compromised. There are two mechanisms involved. The first is to add an expiry date to the certificate. This means that after a certain period of time the certificate becomes invalid and users will automatically refuse to accept it. In this situation the certificate owner has to renew the certificate and if the owner cannot present the necessary credentials then they will no longer be trusted. This weeds out some invalid certificate, but to be effective periods of validity have to fairly short and this then causes more bureaucracy. There is another mechanism. If a certificate owner knows that a certificate has been compromised this user can inform the CA to revoke the certificate. Revocation is vitally important because it ensures that the users of the certificate have trust in the certificate. Without a revocation mechanism the user will only know that the certificate has been created by a trusted CA, it will not know that the certificate is valid. CAs have to maintain a list of revoked certificates and provide a mechanism for a certificate user to check this list.

13.1 Certificates And Certificate Stores

To get a certificate you need to go to a supplier like Verisign or Thawte and purchase the certificate. During the purchasing process the certificate will be returned to you, usually as a file of binary data. When you conduct a secure conversation with someone, for example, you get passed a signed S/MIME email, or you talk to a secure web site over HTTPS, you will get a certificate. Clearly, this presents an administration problem: where do you store this information? In addition, your personal certificate will also have a private key, and this must be kept somewhere secure on your machine. The generic term for this locate is the certificate store. The system certificate store is a secure part of the registry (either under HKCU or HKLM) and can contain individual certificates, certificate trust lists or certificate revocation lists. A certificate trust list is a collection of items (for example file names or certificate hashes) signed by a trusted entity. A certificate revocation list is a document published by a CA that lists the certificates that are no longer valid.

If you do not have a certificate then you can create a temporary certificate using the tools in the .NET SDK. The makecert utility will create an ASN.1 formatted file and such a certificate is signed by a test root authority and thus contains no trust. The utility merely creates a certificate in the correct format that you can use to try the examples presented here, you should not use the certificate to sign your assemblies or messages because other users will automatically reject your signed objects. To create a certificate type the following on the command line:

makecert -n "CN=Richard Grimes" cert.cer

Clearly, use your own name in the parameter for the -n switch, this string is a standard X500 name. To view the certificate use the certmgr application:

certmgr -v cert.cer

This will produce a verbose list of the certificates and it will look something like the following:

==============Certificate # 1 ==========
Subject::
   [0,0] 2.5.4.3 (CN) ValueType: 4
      52 69 63 68 61 72 64 20 47 72 69 6D 65 73 'Richard Grimes'
Issuer::
   [0,0] 2.5.4.3 (CN) ValueType: 4
      52 6F 6F 74 20 41 67 65 6E 63 79 'Root Agency'
SerialNumber::
   D0 78 AA 04 B5 D1 B5 8A 46 5A A0 F8 C7 0B A4 5A
SHA1 Thumbprint::
   560FD1D6 A660AA7D A23CEE2F EA916A0C 193AD1CC
MD5 Thumbprint::
   2EC16BEC 9D8065D2 92EFD467 2FAE4BC2
Key MD5 Thumbprint::
   872E1862 A971587F B57214DA 29395957
NotBefore::
   Thu Feb 16 16:43:07 2006
NotAfter::
   Sat Dec 31 23:59:59 2039
Version:: 2
SignatureAlgorithm:: 1.2.840.113549.1.1.4 (md5RSA)
SignatureAlgorithm.Parameters::
   05 00 '..'
SubjectPublicKeyInfo.Algorithm:: 1.2.840.113549.1.1.1 (RSA)
SubjectPublicKeyInfo.Algorithm.Parameters::
   05 00 '..'
SubjectPublicKeyInfo.PublicKey (BitLength: 1024)
   <data>
RSA_CSP_PUBLICKEYBLOB::
   <data>
Content SignatureAlgorithm:: 1.2.840.113549.1.1.4 (md5RSA)
Content SignatureAlgorithm.Parameters::
   05 00 '..'
Content Signature (little endian)::
   <data>
Extension[0] 2.5.29.1(Authority Key Identifier) Critical: False::
   <data>
KeyID=12 e4 09 2d 06 1d 1d 4f 00 8d 61 21 dc 16 64 63, Certificate Issuer: CN=Root Agency, Certificate SerialNumber=06 37 6c 00 aa 00 64 8a 11 cf b8 d4 aa 5c 35 f4
==============No CTLs ==========
==============No CRLs ==========
==============================================
CertMgr Succeeded

For space reasons I have removed the items that are marked as <data> in this list. As you can see the makecert tool has created a 1024 bit RSA public key and signed it with the certificate of a CA called Root Agency. These two utilities have a plethora of options, and I will only describe a few here.

makecert can create a private key as well as a public key, if you wish to do this, the private key can be put in a .pvk file (specified with the -sv switch) or in a key container (specified with the -sk switch), you can also use the -pe switch which indicates that the private key can be exported and hence can be provided along with the certificate. The private key is very important - it must be kept private. When you indicate that a private key should be put in a file, you are prompted to provide a password to protect the private key. If you tell makecert to put the key in the key store, or to store the certificate in the certificate store then you do not need to provide a password. It actually makes more sense to put the certificate in the store when it is created because it is easier for code to access it from there. If code loads a certificate from a file the certificate key is actually loaded into a temporary container.

The certificate can be used for signing messages or for key exchange (that is, to be used to encrypt a symmetric session key used for a secure message exchange). You specify the type of key using the -sky switch and provide the text signature or exchange. By default the certificate created by makecert is signed by an authority called the Root Agency . This is a non-existent authority and so is not trusted, indeed, if you are known to the users that will use your certificate you could argue that your users will have more trust in you than they would in the Root Agency. If you chose to, you can create a self signed certificate with the -r switch, this certificate is signed by you. If such a certificate is used, for example, for a HTTPS session, then the user will be prompted to specify whether they trust the certificate. If the user decides to trust a self signed certificate they are taking a risk.

By default, the certificate will be created in a .cer file thta you name on the command line but you can use the -ss switch to indicate that you want the certificate to be added to the specified certificate store.

One interesting switch for certmgr is s -s which indicates that the system certificate store should be used to search for certificates to display. The certmgr tool can be used to add certificates to a store (-add), extract a certificate from a store and put it into a file (-put) or remove a certificate from a store (-del). For example, type the following at the command line:

certmgr -add cert.cer -s My

This will add the certificate in cert.cer into the personal store in the system store. The My string indicates the personal store, other stores are: AddressBook, for other people's certificates; AuthRoot, for third party y CAs; CertificateAuthority, for intermediate CAs; Disallowed, for revoked certificates; Root, for trusted CAs; TrustedPeople and TrustedPublisher for certificates for people and publishers that you trust. To view the personal store use:

certmgr -s My

In this case I have not used the -v switch and so you will see an abbreviated list of the certificates in the store. This command will list all the certificates in the personal store in the system store, since there will be many lines of output it is prudent to pipe the output through the more utility (add |more to the end of the command line).

You can remove certificates from a store with certmgr but it is a little more involved and it is interactive even on the command line. Type the following at the command line:

certmgr -del -c -s My

When you use e -del you have to indicate if you want to remove certificates (-c), certificate trust lists (-ctl) or certificate revocation lists (-crl) or all types (-all). You also have to give the store: either the name of a file, or a system store (in this case -s My). The -del command will create a new store with the remaining certificates after the certificates you have specified have been removed, and you give the name of the destination store as an additional parameter. In this example I only give the name of one store, the system store for personal certificates, and so the destination store is the same as the target, in other words, the source store is modified. When you execute this on the command line certmgr will list all the possible items that match the type that you specify and each only will be listed with a number and you will then be prompted to give the number of the item that you want removed.

==============Certificate # 1 =================
Subject::
<data>
==============Certificate # 2 ===============
Subject::
<data>
==============Certificate # 3 ===============
Subject::
  [0,0] 2.5.4.3 (CN) Richard Grimes
Issuer::::
  [0,0] 2.5.4.3.(CN) Root Agency
<data>
Enter cert # from the above list to delete-->

You may find more or fewer certificates on your machine. From the display you can see that the certificate you just added is certificate #3 and so you should type 3 and then press the ENTER key to remove this certificate. Once you have done this type the following to get a list of the certificates and confirm that the appropriate certificate has been removed:

certmgr -s My

Note that if you run certmgr without any parameters then you will get the GUI version of the tool. This tool shows the certificates added by IE and added by makecert but there appears to be a bug that prevents it from displaying certificates added by certmgr itself.

13.2 .NET Version 1.0 Certificate Classeses

In version 1.0 and 1.1 there was little support for certificates. In effect, there were the PublisherIdentityPermission class (and the applicable attribute class), so that your objects can control the code that is called based on the certificate attached to the caller's code, and the X509Certificatete class that can be used to get information about an existing certificate.

The X509Certificate class has methods that allow you to get information about the dates between which the certificate is valid, the name of the certificate issuer, the name of the certificate owner and the serial number of the certificate. In addition it gives access to the public key in the certificate so that you can use this in one of the asymmetric key classes. Objects of the X509Certificate class can be created in several ways. The class has a constructor to allow you to use a byte array with the certificate in ASN.1 binary encoding, it has the CreateFromCertificateFile static method that is passed the name of a file that has the ASN.1 binary encoded certificate and the CreateFromSignedFile static method that is passed the name of a file that contains a CMS (Cryptographic Message Standardrd) formatted signed file. It is common practice to transmit certificates as Base64 encoded data, but it is trivial to decode a Base64 encoded data to get the DER ASN.1 formatted certificate.

Use the certificate file that you created in the last section, create a file (viewCert.cs) and add the following code:e:

using System;
using System.Security.Cryptography.X509Certificates;

class App
{
   static void Main(string[] args)
   {
      if (args.Length == 0) return;
      X509Certificate x509 = X509Certificate.CreateFromCertFile(args[0]);
      Console.WriteLine(
         "Issued to {0}\nIssued by {1}\nSerial# {2}\n"
         + "From {3} To {4}\nAlgo {5} Params {6}\n"
         + "Format {7}\n"
         + "Cert Hash\n{8}\nCert Data\n{9}\nPublic Key\n{10}",
         x509.GetName(), x509.GetIssuerName(), x509.GetSerialNumberString(),
         x509.GetEffectiveDateString(), x509.GetExpirationDateString(),
         x509.GetKeyAlgorithm(), x509.GetKeyAlgorithmParametersString(),
         x509.GetFormat(), x509.GetCertHashString(), x509.GetRawCertDataString(),
         x509.GetPublicKeyString());
   }
}

Compile this (csc viewCert.cs) and then run it from the command line passing the name of the certificate file you created (viewCert cert.cer). You will get something like the following:

Issued to CN=Richard Grimes
Issued by CN=Root Agency
Serial# DC98BFCD0D23F14A96786C51D8402F7F7F
From 16/02/2006 10:49:00 To 31/12/2039 15:59:59
Algo 1.2.840.113549.1.1.1 Params 0500
Format X509
Cert Hash
9FBA2E2BDD2E13B666E5D8B9083348748096EB05
Cert Data
308201BE30820168A00302010202107F2F40D8516C78964AF1230DCDBF98DC300D06092A864886F7
0D01010405003016311430120603550403130B526F6F74204167656E6379301E170D303630323136
3138343930305A170D3339313233313233353935395A3019311730150603550403130E5269636861
7264204772696D657330819F300D06092A864886F70D010101050003818D0030818902818100E0C1
BE6DE4F2F37255FCF5BC06B7F52078F5661E3B540559E0D240A7D75EE127CCD1F3994E9B78E138D3
0C742AE4B04E355C8D363993DAE6059CF69DC7441C9E10043F113F4AB68642567DA83202293DFAF6
CF89CA7800F489E301684D0AB5CEF9F86BCF5C624E36F8D97A77EC941DE1B0CE25EFE1F3C380D9D1
FEFE31B396E30203010001A34B304930470603551D010440303E801012E4092D061D1D4F008D6121
DC166463A1183016311430120603550403130B526F6F74204167656E6379821006376C00AA00648A
11CFB8D4AA5C35F4300D06092A864886F70D010104050003410025AE12118229C0DD72AF29A5FF8E
2F30E11D518B0629490FBE538B1FF00E311A7CECF2E2673B820066FFC16E5B85C6FCE905BF45F7E8
FD2F1DF12ABCDD00DC27
Public Key
30818902818100E0C1BE6DE4F2F37255FCF5BC06B7F52078F5661E3B540559E0D240A7D75EE127CC
D1F3994E9B78E138D30C742AE4B04E355C8D363993DAE6059CF69DC7441C9E10043F113F4AB68642
567DA83202293DFAF6CF89CA7800F489E301684D0AB5CEF9F86BCF5C624E36F8D97A77EC941DE1B0
CE25EFE1F3C380D9D1FEFE31B396E30203010001

Notice that you get access to the public key. This means that you can use it to encrypt data, or decrypt data that the certificate owner has encrypted with their private key. The binary data, the hash, raw data and public key are printed here as strings showing the hex data, but the actual data is held in byte arrays.

You can access a certificate from a signed file. To do this you need to create a certificate that has an associated private key that can be used for signing. Delete the certificate that you just created and create a new one:

del cert.cer
makecert -n "CN=Richard Grimes" -sv cert.pvk cert.cer

When this runs you'll see the following dialog will appear:

The private key that you will create in cert.pvk is important and must be kept secure and so the makecert tool will encrypt the private key with the pass word that you supply. Immediately after creating the key pair it will create the certificate file and this time the makecert tool will prompt you for the password:

The signcode tool will sign a PE file with a certificate, however, this tool requires the certificate in a different form called Software Publisher Certificate and you can use the cert2spc to convert a .cer file to this format:

cert2spc cert.cer cert.spc

Now you can sign a file. Create the following file and compile it as a library (csc /t:library lib.cs):

using System;
public class Test{}

As a test, use dumpbin to view the headers of the PE file:

dumpbin /headers lib.dll

Near the bottom of the list will be the data directories (before the section headers) and here you'll see that the Certificates Directory is empty:

0 [ 0] RVA [size] of Certificates Directory

Now sign the file:

signcode -spc cert.spc -v cert.pvk lib.dll

Since the private key is required to sign a file you will find that you will be asked for your password. The signcode tool will generate a hash and sign it with the private key, it will then add the signed hash and the certificate to the file in the certificates data directory. To confirm this, run dumpbin again, you will get a result similar to this:

C00 [ 388] RVA [size] of Certificates Directory

Now change the viewCert.cs file to generate a X509Certificate from a signed file:

if (args.Length == 0) return;
X509Certificate x509 = X509Certificate.CreateFromSignedFile(args[0]);

Compile this code and then run it passing lib.dll as the parameter. You'll get an exception which says this:

Unhandled Exception: System.Runtime.InteropServices.COMException (0x800B010D): The certification path terminates with the test root which is not trusted with the current policy settings.

What is happening here? Well, the problem is that the certificate you used to sign the file has the Root Agency as the CA. This certificate authority is a test authority and so it is not trusted by the system. Run certmgr with no parameters to get the GUI. Select the Intermediate Certificate Authorities tab and scroll down until you see Root Agency:

Click on the View button to get more information about the certificate, select the Certificate Path tab:

This shows the CA that has signed the certificate and as you can see there is no other CA and the message at the bottom indicates that the certificate is not trusted because the CA is an intermediate CA. Close this property dialog. To elevate the trust you need to add the certificate to the list of Trusted Root Certification Authorities, since you do not have the certificate of the root agency you must extract it from the store. With the Root Agency item selected click on the Export button, this will lead you through a series of wizard dialogs to export the certificate. On the second dialog leave the default DER encoded binary X.509 (.cer) selected and on the third dialog save the certificate file in your test folder with a name of rootAgency.cer.

Once you have finished going through all of the wizard windows select the Trusted Root Certification Authorities tab and click on the Import button. This will lead you through another set of wizard dialogs, on the second dialog you should give the path to rootAgency.cer that you have just saved. On the third dialog leave the default setting (that is, to import into the Trusted Root Certification Authorities certificate store. When you click Finish on the final dialog you will get a warning dialog:

As expected, Windows recognises that the CA is not trusted. However, we will trust this CA for the purpose of this example, so click on the Yes button to install the certificate.

Note that this is just for illustration purposes. You must not leave this certificate in the trusted root certificate store because this will means that untrusted certificates could be accepted by software on your machine.

Finally, close certmgr and run viewCert on the lib.dll signed library. This time the certificate will be read from the PE file and you'll see the details printed on the console.

Before moving on, you must lower the trust on the Root Agency CA. To do this run certmgr without any parameters (to get the GUI version) and click on the Trusted Root Certification Authorities tab. Scroll down until you get to the Root Agency item. Select this item and click the Remove button. You will see a warning dialog so click on Yes on the warning dialog. Next you'll see another warning dialog and this time it will give the name of the certificate that you are removing. Check that it is the Root Agency certificate and if so click on the Yes button. Close certmgr. The certificate for Root Agency should still be in the Intermediate Certificate Authorities certificate store. Confirm that you get the behaviour you previously experienced by running viewCert on the lib.dll signed library; you should get an exception.

13.3 .NET Version 3.0/2.0 Certificate Classes

Version 1.1 of the framework had very little other than the X509Certificate class to allow you to manipulate certificates. In fact, the v1.1 X509Certificate class gave only basic support: it only gave access to the X509 version 1 fields (like the valid from and valid to dates, subject and public key) but not version 2 fields (like the authority key identifier) nor version 3 fields (like the key usage). There was no support to load a certificate from a certificate store, nor does it have the facilities to access certificate revocation lists or certificate trust lists. Microsoft improved on this with the Web Services Enhancement (WSE) toolkit extending the certificate class and providing classes to access certificate stores. These classes can now be found in the .NET 3.0/2.0 framework library.

The first big change is a new class called X509Certificate2 which derives from X509Certificate. The methods to access the X509 certificate fields have been deprecated and now the class has properties to access those fields. In addition, if the certificate has an associated private key then the class gives access to this key. There are methods that allow you to provide a password if the private key is protected by one. The password is passed through a SecureString parameter which is a special type that makes sure that when the object is no longer being used the memory it occupied will be written over so that the password cannot be read by another process on the machine. Secure strings and other forms of protected data will be covered in a later section.

Since X509Certificate2 derives from X509Certificate it means that you can call the static methods CreateFromeCertFile and CreateFromSignedFile through the X509Certificate2 class. However, these methods return an X509Certificate object and you cannot down cast this to a X509Certificate2 object. The X509Certificate class has been improved in version 3.0/2.0: it provides properties to access some of the X509 fields; it provides Import and Export methods to initialize an object from a byte array or generate a byte array from the certificate and it has constructors that will create an object from a file (ASN.1 DER) and from a byte array. Interestingly, the X509Certificate2 class has a constructor that can create an X509Certificate2 object from an X509Certificate object. Note that although an X509Certificate object can only show the X509v1 fields it can be created from an X509v3 certificate and so if you create an X509Certificate2 object from an X509Certificate object you will be able to access the X509v3 fields.

Here's some code that gives the equivalent of the viewCert tool developed in the last example, but using the properties of X509Certificate2 (viewCert2.cs)

using System;
using System.Security.Cryptography.X509Certificates;

public class App
{
   public static void Main(string[] args)
   {
      if (args.Length == 0) return;
      X509Certificate x509 = X509Certificate.CreateFromCertFile(args[0]);
      X509Certificate2 x509_2 = new X509Certificate2(x509);
      Console.WriteLine("X509 Version {0}", x509_2.Version);
      Console.WriteLine(
         "Issued to {0}\nIssued by {1}\nSerial# {2}\n"
         + "From {3} To {4}\nAlgo {5} Params {6}\n"
         + "Format {7}\n"
         + "Cert Hash\n{8}\nCert Data\n{9}\nPublic Key\n{10}",
         x509_2.Subject, x509_2.Issuer, x509_2.SerialNumber,
         x509_2.NotBefore.ToString(), x509_2.NotAfter.ToString(),
         x509_2.SignatureAlgorithm.Value, x509_2.PublicKey.EncodedParameters.Format(false),
         x509.GetFormat(), x509.GetCertHashString(),
         BitConverter.ToString(x509_2.RawData),
         x509_2.PublicKey.EncodedKeyValue.Format(false));
      Console.WriteLine("Distiguished names:\nIssued to {0}\nIssued by {1}",
         x509_2.SubjectName.Name, x509_2.IssuerName.Name);
      Console.WriteLine("Thumbprint {0}", x509_2.Thumbprint);
   }
}

Note that I could have used the X509Certificate methods but in this example I wanted to use the new properties where possible. The first thing to note is that the raw data is provided as a byte array and so to convert this to a string I use the BitConverter class. In this case the GetRawCertDataString method is better, however, you are unlikely to want to show the raw data in a certificate, and are more likely to want to access the actual raw data. It is more logical to use RawData than to use GetRawCertData. The PublicKey property returns the public key in the certificate as a PublicKey object. This class encapsulates the key itself (EncodedKeyValue and Key) and the parameters of the public key (EncodedParameters). The encoded properties contain data as an AsnEncodedData object; this class contains an Oid property that gives access to the cryptographic object identifier for the algorithm and it contains the raw data that can be accessed as a byte array or, as in this example, formatted as a string. The Key property of the PublicKey class is an AsymmetricAlgorithm object (for example RsaCryptoServiceProvider) initialized with the public key in the certificate, so that if you have a hash that is purported to be signed with the private key you can verify this by calling VerifyHash.

13.4 Certificate Store

The version 3.0/2.0 framework provides classes that give access to the certificate store on your machine. As I mentioned earlier, certificates are categorized as to whether they are personal certificates, for other users, for certificate authorities or for other publishers. When you access the certificate store you use the StoreName enumeration to specify which of these stores to use. In addition the certificates on your machine could be for the entire machine, or could be just for the current user and you use the StoreLocation enumeration to indicate which.

The X509Store class gives you access to the store. The constructor of this class takes the store name (either from StoreName or a string) or the store location (a StoreLocation). The constructor merely indicates the store to use, to get access to the certificates you must call the Open method and use the OpenFlags enumeration to indicate the type of access that you require: read or read-write access. This method will then initialise the Certificates property with a X509Certificate2Collection that has all of the certificates in the associated store. If you do not give a store location then the certificates for the current user will be returned. For example, create a new file, certstore.cs and add the following:

using System;
using System.Security.Cryptography.X509Certificates;

class App
{
   static void Main()
   {
      X509Store store = new X509Store(StoreName.My);
      store.Open(OpenFlags.ReadOnly);
      foreach (X509Certificate2 cert in store.Certificates)
      {
         Console.WriteLine(cert.Subject);
      }
   }
}

Compile and then run this code. You'll find that it will list the certificates in your personal store, equivalent to the data returned from certmgr /s My. The foreach loop prints the certificate information on the command line. If you require a specific certificate you need to check the properties of each certificate in a loop like this until you obtain that certificate. You can also allow the users of your application to select a certificate. This is done through the X509Certificate2UI class which will provide information about one or more certificates using a dialog.

For example, lets start with certificate properties, so alter the foreach loop to look like this:

foreach (X509Certificate2 cert in store.Certificates)
{
   X509Certificate2UI.DisplayCertificate(cert);
}

Compile and run this and you'll find that you will get a modal dialog for each of the certificates in the store. The X509Certificate2UI class has one other method: SelectFromCollection. This method will give a dialog that displays every certificate in the store. It allows you to view a certificate and to select one or more certificates which will be returned in a X509Certificate2Collection when the method returns. For example, remove the foreach loop and replace it with:

X509Store store = new X509Store(StoreName.My);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certs =
   X509Certificate2UI.SelectFromCollection (
      store.Certificates, "Personal Certificates",
      "Select one certificate", X509SelectionFlag.SingleSelection);

foreach (X509Certificate2 cert in certs)
{
   Console.WriteLine(cert.Subject);
}

The first parameter is the collection to display, the second parameter is the title of the dialog and the third is a message that is shown on the dialog.

The X509Store class also allows you to add and remove certificates from a specified store, clearly, to do either you must open the store with OpenFlags.ReadWrite.

13.5 Using Certificates

In the following I will use a certificate in a file so that you can see how to access the private key from a certificate file, the certificate generation is much simpler if you use a certificate in a store, but the code is almost the same.

First create a certificate that is self signing and has an exportable private key:

makecert -r -pe -n "CN=Richard Grimes" -sky exchange -sv cert.pvk cert.cer

This creates an exchange certificate, so that the public key can be used for encryption and the private key for decryption. You will be prompted to provide a password and then prompted again for that password. You now have two files, cert.cer with the certificate and the public key and cert.pvk with the encrypted private key. The X509Certificate2 will accept the DER .cer file, but to get access to the private key it requires a PKCS#12 .pfx file. To do this you can add the certificate to the certificate store and then export it. Another option is to use the pvkimprt utility (which you can download from Microsoft). First you have to create a software publisher's certificate from the X509 certificate:

cert2spc cert.cer cert.spc

Then you run pvkimprt to create the .pfx file:

pvkimprt -pfx cert.spc cert.pvk

This utility first asks for the password for the private key in the .pvk file:

Type the password you provided earlier when you created the certificate. Next the Certificate Export Wizard will start:

Click Next, then on the next page select the Yes, export the private key option and then click on Next. You will then be asked what format you want to use, with just one option enabled, Personal Information Exchange:

Click on Next and on the next page give the password you want to be used to encrypt the key in the .pfx file. Then click on Next and give the name and path of the file to create. Call the file cert.pfx and put it in the same folder as the other certificate files you just created. Click on Next. Finally click on the Finish button on the last wizard page. The .pfx file should be created and a dialog will inform you that the operation succeeded, click on the OK button.

Now you will have two certificate files: cert.cer has the public key and cert.pfx has the private key. The following example will use the public key to encrypt a file and the private key to decrypt it. The actual file is encrypted with Rijndael symmetric algorithm. To do this a random key is created and this is encrypted with the certificate public key and then stored in the output file along with the (cleartext) initialization vector. The sizes of the encrypted key and the IV are also stored in the output file so that they can be read during decryption. Then the input file is encrypted with Rijndael and this data is appended to the output file. The parameters of this utility are shown here:

encrypt infile outfile certfile [e|d]

The first three parameters are mandatory and are the names of the input file, the output file and the certificate file. The final parameter indicates whether the input file should be encrypted (e) or decrypted (d), if you omit this parameter then the input file will be encrypted.

Create a file called encrypt.cs:

using System;
using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.IO;

class App
{
   static void Main(string[] args)
   {
      if (args.Length < 3) return;
      string inFile = args[0];
      if (!File.Exists(inFile)) return;
      string outFile = args[1];
      if (File.Exists(outFile)) File.Delete(outFile);
      string certFile = args[2];
      bool encrypt = true;
      if (args.Length == 4)
         encrypt = (args[3].ToLower()[0] == 'e');
  
      if (encrypt)
      {
         Encrypt(inFile, outFile, certFile);
      }
      else
      {
         Decrypt(inFile, outFile, certFile);
      }
   }
}

The output file will overwrite any file that exists and already has the same name. The main work is carried out by the two static methods, Encrypt and Decrypt. Here is Encrypt:

static void Encrypt(string inFile, string outFile, string certFile)
{
   using (FileStream fs = new FileStream(outFile, FileMode.CreateNew, FileAccess.Write, FileShare.None))
   {
      Rijndael r = Rijndael.Create();

      // Encrypt with public key
      X509Certificate2 cert = new X509Certificate2(certFile);
      RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)cert.PublicKey.Key;
      byte[] keyBuf = new byte[rsa.KeySize/8];
      Buffer.BlockCopy(r.Key, 0, keyBuf, 0, r.Key.Length);
      // Write key size to file
      byte[] i32 = BitConverter.GetBytes(r.Key.Length);
      fs.Write(i32, 0, i32.Length);
      // Write IV length to file
      i32 = BitConverter.GetBytes(r.IV.Length);
      fs.Write(i32, 0, i32.Length);
      // Write encrypted password to out file
      byte[] buf = rsa.Encrypt(r.Key, false);
      fs.Write(buf, 0, buf.Length);
      // Write Rijndael IV to out file
      fs.Write(r.IV, 0, r.IV.Length);

      using (FileStream data = File.OpenRead(inFile))
      {
         // encrypt as it is read in
         CryptoStream cs = new CryptoStream(
         data, r.CreateEncryptor(), CryptoStreamMode.Read);
         byte[] databuf = new byte[r.BlockSize/8];
         while (true)
         {
            int read = 0;
            read = cs.Read(databuf, 0, databuf.Length);
            if (read == 0) break;
            fs.Write(databuf, 0, read);
         }
         cs.Clear();
      }
   }
}

Creating a new instance of RijndaelManaged with Rijndael.Create means that a new random key and IV are also created. The key will be encrypted with the public key in the certificate, but this will only encrypt whole blocks of the algorithm block length, and this will be larger than the Rijndael key. So an array of the RSA block length size is created (it will be automatically filled with zeros) and then the Rijndael key is copied in. The KeySize property is the size in bits which is why it is divided by eight. The size of the key is written to the output file (the size of the key, not the encrypted key), and then the size of the initialization vector is written to the file. Then the key is encrypted with the certificate public key and the buffer is written to the file. Finally, the IV is written to the file as cleartext.

Notice the way that the public key is accessed:

X509Certificate2 cert = new X509Certificate2(certFile);
RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)cert.PublicKey.Key;

The PublicKey property is an instance of PublicKey and it is a property of this class, Key, that actually gives access to an RSACryptoServiceProvider object.

Now the data is read in from the input file, encrypted with the symmetric algorithm and written to the output file. To do this a CryptoStream is created with an encryptor using CryptoStreamMode.Read. This means that as data is read from the file it is encrypted and any extra blocks will be cached in the stream. Since the loop repeatedly reads the stream until all data is read this means that all blocks cached in the stream will be read and so you do not have to call TransformFinalBlock. 

The Decrypt method is the opposite of the Encrypt with one difference. The .pfx file has the private key encrypted so you must provide the password. The X509Certificate2 class has a constructor to do this, and the password is passed using a SecureString parameter. You cannot initialize a SecureString with a string, but you can initialize it character by character. Here is the first part of Decrypt:

static void Decrypt(string inFile, string outFile, string certFile)
{
   // Decrypt with private key
   X509Certificate2 cert = null;
   Console.Write("Password for certificate file: ");
   using(SecureString ss = new SecureString())
   {
      while (true)
      {
         ConsoleKeyInfo key = Console.ReadKey(true);
         if (key.Key == ConsoleKey.Enter || key.Key == ConsoleKey.Escape) break;
         Console.Write('*');
         ss.AppendChar(key.KeyChar);
      }
      Console.WriteLine();

      cert = new X509Certificate2(certFile, ss);
   }
   // More code to follow...
}

This code asks for a password from the command line. It uses the .NET 3.0/2.0 ReadKey method. When a key is pressed the key value is checked to see if it is Enter or Esc, which indicates that input has ended. If any other key is pressed then the character is added to the SecureString and a * is printed on the screen. Finally the name of the .pfx file and the password are used to create the X509Certificate2 object. Note that the code is bracketed in a using clause. The reason is that this ensures that the SecureString only lives as long as it is needed. When the clause is left (for whatever reason) the buffer in the SecureString is zeroed and then freed. More details about how and why can be found on the protected data page.

Decrypting the data is straightforward:

using (FileStream fs = new FileStream(inFile, FileMode.Open, FileAccess.Read, FileShare.None))
{
   byte[] i32 = new byte[4];
   Rijndael r = Rijndael.Create();

   // Read key length from file
   fs.Read(i32, 0, i32.Length);
   int keyLen = BitConverter.ToInt32(i32, 0);
   r.KeySize = keyLen * 8;
   // Read IV buffer length from file
   fs.Read(i32, 0, i32.Length);
   int ivBufLen = BitConverter.ToInt32(i32, 0);

   // Read Rijndael password from in file, must be a rsa key size
   RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)cert.PrivateKey;
   // Read encrypted Rijndael key
   byte[] buf = new byte[rsa.KeySize / 8];
   fs.Read(buf, 0, buf.Length);
   byte[] keyBuf = rsa.Decrypt(buf, false);
   byte[] key = new byte[keyLen];
   Buffer.BlockCopy(keyBuf, 0, key, 0, key.Length);
   r.Key = key;
   // Read Rijndael IV from in file
   byte[] iv = new byte[ivBufLen];
   fs.Read(iv, 0, iv.Length);
   r.IV = iv;

   CryptoStream cs = new CryptoStream(
   fs, r.CreateDecryptor(), CryptoStreamMode.Read);

   using (FileStream data = File.OpenWrite(outFile))
   {
      byte[] databuf = new byte[r.BlockSize/8];
      while (true)
      {
         int read = 0;
         read = cs.Read(databuf, 0, databuf.Length);
         if (read == 0) break;
         data.Write(databuf, 0, read);
      }
   }
   cs.Clear();
}

First, the length of the key and the length of the IV are read from the file, then the encrypted key is read and decrypted with the private key in the certificate. The symmetric algorithm is initialized with the key. Note how this is done: a new buffer is created and initialized with the appropriate number of bytes decrypted and then the Key property is assigned to this buffer. The following code will not work:

Buffer.BlockCopy(keyBuf, 0, r.Key, 0, r.Key.Length);

The reason is that the accessor for the Key property will return a clone of the field that holds the key, so the code above will affect the clone and not the actual key held in the RijndaelManaged object.

I think that this is very confusing. The documentation for SymmetricAlgorithm.Key does not say that a clone is returned from the accessor, even though this information is extremely important. The incorrect code I showed above looks valid but it has a serious bug. Because properties allow such bugs to occur I am very wary of adding properties to my own code. You should be wary too.

The same problem exists for the IV property: the accessor returns a clone, so the following code will not work:

fs.Read(r.IV, 0, r.IV.Length);

Instead, in my code I create an intermediate buffer, initialize it and then assign the IV property to this buffer.

The actual decryption is simple: I create a CryptoStream based on the input file and decryptor, and indicate that the data will be decrypted as it is read.

Now compile the code:

csc encrypt.cs

Run the file to encrypt the source code:

encrypt encrypt.cs encrypt.dat cert.cer e

This will create a binary file, confirm that it does not contain readable data by typing it to the command line (type encrypt.dat). Now decrypt the file to a new file called encrypt.txt:

encrypt encrypt.dat encrypt.txt cert.pfx d

This uses the .pfx file and you will be prompted to type the password that you provided when you ran pvkimprt. Finally, confirm that the decryption worked by typing the output file to console (type encrypt.txt).

13.6 The Strong Name Utility and Certificates

Certificates hold asymmetric keys, these keys can be used to sign and encrypt data; the strong name utility (sn.exe) also creates asymmetric keys, so can you perform the same actions with strong name keys as you can with certificates? Well, yes and no. The strong name utility creates RSA signature keys and these are used to encrypt data (the signature) with the private key and hence the signed signature is decrypted with the private key. By default, the RSACryptoServiceProvider class will use key exchange keys where the data is encrypted with the public key and decrypted with the private key.

The RSACryptoServiceProvider class is configured with a CspParameters object, and this class has a property called KeyNumber that has a value of 1 for key exchange and 2 for signature. This would suggest that you can get the public key from a strong name and use these to initialize an RSACryptoServiceProvider object and be able to encrypt data with the public key as long as the KeyNumber of its parameters is set to 1. However, it is not as straightforward as that. The reason is that the public key in the RSACryptoServiceProvider class have the modulus and exponent are big-endian numbers stored as ASN.1 encoded data. The data generated by the strong name utility are essentially the CryptoAPI PUBLICKEYBLOB  which is not ASN.1 encoded and the numbers are stored in little-endian order. Thus, you can use the public key in an .snk file, or in an assembly, with an RSACryptoServiceProvider object as long as you perform the appropriate conversions.

Rather that explaining the process in detail I will instead point you to some code written by William Stacey. This class will allow you to get a RSACryptoServiceProvider object initialized with the public key from a strong name file (.snk) or from an assembly. The code can be downloaded from here.

I hope that you enjoy this tutorial and value the knowledge that you will gain from it. I am always pleased to hear from people who use this tutorial (contact me). If you find this tutorial useful then please also email your comments to mvpga@microsoft.com.

Errata

If you see an error on this page, please contact me and I will fix the problem.

Page Fourteen

 

This page is (c) 2007 Richard Grimes, all rights reserved