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:
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:
This will produce a verbose list of the certificates and it will look something like the following:
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:
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:
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).
certmgr but it
is a little more involved and it is interactive even on the command line. Type
the following at the command line: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.
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:
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 thePublisherIdentityPermission 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.
viewCert.cs) and add the following code:e:
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 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.
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:
Now you can sign a file. Create the following file and compile it as a
library (csc /t:library lib.cs):
public class Test{}
As a test, use dumpbin to view the headers of the PE file:
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:
Now sign the file:
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:
Now change the viewCert.cs file to generate a
X509Certificate from a signed file:
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:
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.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.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:
{
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:
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:
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:
Then you run pvkimprt to create the .pfx file:
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:
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.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:
{
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:
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:
{
// 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:
{
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:
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:
Run the file to encrypt the source code:
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:
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.