Digital certificates are the electronic version of a passport or an ID card, providing means for proving your identity for operations that must be performed securely (such as electronic payments). This article presents the basic .NET API for managing certificates.
Though this article is not a whitepaper on cryptography or certificates or other related concepts, I will start with a short introduction on certificates. If you are not familiar with these concepts I suggest you do additional readings for understanding them.
A short introduction on certificates
A certificate is an electronic document that uses a digital signature to associate a public key with an identity (either of a person or an organization), thus certifying that a public key belongs to an individual. The signing of a certificate can be done either by a certificate authority (CA) or the user, or even other users. Certificates signed by their user are called self-signed certificate, but are also known as root certificates.
A certificate authority is an entity that signs certificates. An individual that wants to prove his identity relies on certificate authority to sign his certificate. The certificate is passed to another individual, which can check it against the issuing certificate authority for validity. Both parties need to trust the certificate authority, which proves its identity with another certificate, signed by a higher ranking certificate authority. Of course, there must be one certificate authority that has no higher authority. This CA signs its own certificate, which is a self-signed certificate, also known as root certificate.
A typical example where root certificates are used is the web. Let’s take for instance online banking applications. You connect to a web site, provide a username and password and probably a security number generated by a digital device and you access the application that allows you to manage your account, viewing your balance and transactions, making payments, etc. These kind of applications need to be secure. You don’t want to end up on a fake site that steals your credentials and then empties you account. Connections to web servers used for online payments (and others) are made with HTTPS, which is a secure HTTP protocol combined with the SSL protocol for securely identifying the server. The web server has a certificate that proves its identity, signed by a recognized certificate authority, which is trusted by the browsers (or can be verified through its certificate issued by a higher, recognized certificate authority). When your browser connects to the web server, it retrieves the certificate and then checks it with the issuing certificate authority. If the web server is who it claims to be you are allowed to connect to the web site. Otherwise you get a certificate warning.
A digital certificate usually includes the following:
- Serial number: used to uniquely identify the certificate.
- Subject: the entity identified (person or organization).
- Signature algorithm: the algorithm used for creating the signature.
- Key-usage: purpose of the public key (encryption, signature verification, both).
- Public key: used to encrypt a message to the subject or to verify the signature from the subject.
- Issuer: the identify that issued the certificate.
- Valid from: the date from which the certificate is valid.
- Valid to: the date when the certificate expires.
- Thumbprint algorithm: the algorithm used for hashing.
- Thumbprint: the hash for the certificate used to verify that the certificate was not altered.
.NET support for certificates
Namespace System.Security.Cryptography.X509Certificates contains the implementation of the X.509 v3 certificate. X.509 is the standard for a public key infrastructure (PKI) for single sign-on and Privilege Management Infrastructure. The various classes from this namespace allow operations such as creating stores, importing, exporting, deleting, enumerating and retrieving information on certificates.
The most important classes are:
- X509Store: represents a X.509 store, which is a physical catalog where certificates are persisted and managed. There are several built in stores grouped in two locations: local machine (contains certificates shared by all the users) and current user (contains certificates specific to the currently logged user).
- X509Certificate and X509Certificate2: represent a X.509 certificate.
- X509Certificate2Collection: represents a collection of X509Certificate2 objects.
Enumerating Certificates
To enumerate the certificates of a store one needs to open then store and then iterate over the sequence of certificates, represented by the Certificates property of the X509Store object.
public static void PrintCertificateInfo(X509Certificate2 certificate) { Console.WriteLine("Name: {0}", certificate.FriendlyName); Console.WriteLine("Issuer: {0}", certificate.IssuerName.Name); Console.WriteLine("Subject: {0}", certificate.SubjectName.Name); Console.WriteLine("Version: {0}", certificate.Version); Console.WriteLine("Valid from: {0}", certificate.NotBefore); Console.WriteLine("Valid until: {0}", certificate.NotAfter); Console.WriteLine("Serial number: {0}", certificate.SerialNumber); Console.WriteLine("Signature Algorithm: {0}", certificate.SignatureAlgorithm.FriendlyName); Console.WriteLine("Thumbprint: {0}", certificate.Thumbprint); Console.WriteLine(); } public static void EnumCertificates(StoreName name, StoreLocation location) { X509Store store = new X509Store(name, location); try { store.Open(OpenFlags.ReadOnly); foreach(X509Certificate2 certificate in store.Certificates) { PrintCertificateInfo(certificate); } } catch(Exception ex) { Console.WriteLine(ex.Message); } finally { store.Close(); } } public static void EnumCertificates(string name, StoreLocation location) { X509Store store = new X509Store(name, location); try { store.Open(OpenFlags.ReadOnly); foreach (X509Certificate2 certificate in store.Certificates) { PrintCertificateInfo(certificate); } } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { store.Close(); } }
The examples above allow opening a named store from a specific location. The location is specified by the StoreLocation enumerator, and can have two values:
- CurrentUser: the store for the current user
- LocalMachine: the store assigned to the local machine (whose certificates are available to all the users).
The name of the store can either be a predefined one, indicated with the StoreName enumeration, or one identified by a string. The predefined ones are:
- AddressBook: store for other users
- AuthRoot: store for third-party certificate authorities
- CertificateAuthority: store for intermediate certificate authorities
- Disallowed: store for revoked certificates
- My: store for personal certificates
- Root: store for trusted root certificate authorities
- TrustedPeople: store for directly trusted people or resources
- TrustedPublisher: store for directly trusted publishers
Creating a store
You can create your own custome store, either for the local machine or for the current user. To do that you pass the name of a store that doesn’t exist to the constructor of X509Store and then open it with any OpenFlags flags, except OpenExistingOnly.
public static bool CreateStore(string name, StoreLocation location) { bool success = false; X509Store store = new X509Store(name, location); try { store.Open(OpenFlags.ReadWrite); success = true; store.Close(); } catch (Exception ex) { Console.WriteLine(ex.Message); } return success; }
Importing Certificates
To import a certificate you have to do the following:
- Create a X509Certificate2 object, using the path to the certificate file
- Open the desired store with ReadWrite access rights
- Add the certificate to the store and close the store
The following code shows how this can be done:
public static bool ImportCertificate(X509Certificate2 certificate, StoreName name, StoreLocation location) { bool success = false; X509Store store = new X509Store(name, location); try { store.Open(OpenFlags.ReadWrite); store.Add(certificate); success = true; } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { store.Close(); } return success; } public static bool ImportCertificate(X509Certificate2 certificate, string name, StoreLocation location) { bool success = false; X509Store store = new X509Store(name, location); try { store.Open(OpenFlags.ReadWrite); store.Add(certificate); success = true; } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { store.Close(); } return success; } public static bool ImportCertificate(string path, StoreName name, StoreLocation location) { bool success = false; try { X509Certificate2 certificate = new X509Certificate2(path); success = ImportCertificate(certificate, name, location); } catch(Exception ex) { Console.WriteLine(ex.Message); } return success; } public static bool ImportCertificate(string path, string name, StoreLocation location) { bool success = false; try { X509Certificate2 certificate = new X509Certificate2(path); success = ImportCertificate(certificate, name, location); } catch (Exception ex) { Console.WriteLine(ex.Message); } return success; }
Exporting certificates
The export a certificate you have to do the following:
- Open the desired store and find the certificate you want to export
- Export the certificate content to a row stream of bytes
- Save the row data to a file
public static bool ExportCertificate( string certificateName, string path, StoreName storeName, StoreLocation location) { bool success = false; X509Store store = new X509Store(storeName, location); store.Open(OpenFlags.ReadOnly); try { X509Certificate2Collection certs = store.Certificates.Find(X509FindType.FindBySubjectName, certificateName, true); if (certs != null && certs.Count > 0) { byte[] data = certs[0].Export(X509ContentType.Cert); success = WriteFile(data, path); } } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { store.Close(); } return success; } public static bool ExportCertificate( string certificateName, string path, string storeName, StoreLocation location) { bool success = false; X509Store store = new X509Store(storeName, location); store.Open(OpenFlags.ReadOnly); try { X509Certificate2Collection certs = store.Certificates.Find(X509FindType.FindBySubjectName, certificateName, true); if (certs != null && certs.Count > 0) { byte[] data = certs[0].Export(X509ContentType.Cert); success = WriteFile(data, path); } } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { store.Close(); } return success; } private static bool WriteFile(byte[] data, string filename) { bool ret = false; try { FileStream f = new FileStream(filename, FileMode.Create, FileAccess.Write); f.Write(data, 0, data.Length); f.Close(); ret = true; } catch (Exception ex) { Console.WriteLine(ex.Message); } return ret; }
When you look up the certificate in the certificates collection you can do it based on several criteria, such as by thumbprint, subject name, issuer name, validity periods, etc. The options for the search are defined by the X509FindType enumerator.
In the above sample the certificates are looked up by name. It is possible though that one store contains several certificates with the same name, but different serial number. In this case this sample locates the first certificate in the collection. You have to handle this appropriately for your application.
Deleting Certificates
To delete a certificate you need to do the following:
- Open the store with ReadWrite access
- Locate the certificate(s) you want to delete
- Remove the certificate(s) from the store
- Close the store
public static bool DeleteCertificate(string certificateName, string storeName, StoreLocation location) { bool success = false; X509Store store = new X509Store(storeName, location); try { store.Open(OpenFlags.ReadWrite); X509Certificate2Collection certificates = store.Certificates.Find(X509FindType.FindBySubjectName, certificateName, true); if(certificates != null && certificates.Count > 0) { store.RemoveRange(certificates); success = true; } } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { store.Close(); } return success; } public static bool DeleteCertificate( string certificateName, string thumbprint, string storeName, StoreLocation location) { bool success = false; X509Store store = new X509Store(storeName, location); try { store.Open(OpenFlags.ReadWrite); X509Certificate2Collection certificates = store.Certificates.Find(X509FindType.FindBySubjectName, certificateName, true); if (certificates != null && certificates.Count > 0) { foreach(X509Certificate2 certificate in certificates) { if(certificate.Thumbprint == thumbprint) { store.Remove(certificate); success = true; break; } } } } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { store.Close(); } return success; }
As you can see in the above samples, class X509Store has two methods for removing a certificate:
- Remove: deletes a certificate
- RemoveRange: removes a collection of certificates.
Conclusions
While this article is not a comprehensive tutorial on the support in the .NET framework for handling certificates it shows how one can perform basic operations such as enumerating and finding certificates, importing, exporting or deleting certificates. If you are not very familiar with digital certificates I recommend you to do additional readings for familiarizing with them.