.NET Security

Support Certificates In Your Applications With The .NET Framework 2.0

Dominick Baier

This article discusses:

  • The Windows Certificate Store
  • Certificate classes in .NET
  • Validation, SSL, Web services, and code signing
  • Signing and encrypting data
This article uses the following technologies:
.NET Framework 2.0

Code download available at: Certificates 2007_03.exe(160 KB)

Contents

How to Get a Certificate
The Windows Certificate Store
Working with Certificates
Accessing Certificates
Display Certificate Details and the Certificate Picker
Validating Certificates
SSL Support
Web Service Security
Security Policy and Code Signing
ClickOnce Manifests
Signing and Encrypting Data
Decrypting Data and Verifying Signatures
Putting It All Together

Certificates are used in many places across the Microsoft® .NET Framework, from secure communication to code signing to security policies. The .NET Framework 2.0 introduced revamped support for certificates and it added a completely new namespace for standards-compliant cryptographic operations with certificates. In this article, I will discuss the background for certificates and the Windows® Certificate Store. I'll also show you how to work with the certificate APIs and how they are used by the Framework to implement security features.

A "certificate" is really an ASN.1 (Abstract Syntax Notation One) encoded file that contains a public key and additional information about that key and its owner. In addition, a certificate has a validity period and is signed with another key (the so-called issuer) which is used to provide an authenticity guarantee of those attributes and, most importantly, the public key itself. You can think of ASN.1 as a sort of binary XML. Like XML, it also has encoding rules, strong types, and tags; however, these are binary values that often don't correspond to any printable character.

For such a file to be interchangeable between systems, a standard format is needed. This is X.509 (currently version 3), which is described in RFC 3280 (tools.ietf.org/html/rfc3280). X.509 doesn't dictate the type of key embedded in the certificate, but the RSA algorithm is currently the most popular asymmetric cryptographic algorithm in use.

I'll start with a little history. The name RSA is an acronym for the surnames of three inventors of this algorithm: Ron Rivest, Adi Shamir, and Len Adleman. They formed a company, RSA Security, which published several standard documents called Public Key Cryptography Standards (PKCS). These documents describe several aspects of cryptography.

One of the most popular of these documents, PKCS #7, defines a binary format for signed and encrypted data called the Cryptographic Message Syntax (CMS). CMS is now used in many popular security protocols, including secure sockets layer (SSL) and Secure Multipurpose Internet Mail Extensions (S/MIME). Since it is a standard, it is also the format of choice for when applications need to exchange signed and encrypted data between several parties. The PKCS documents are available on the RSA Laboratories Web site (www.rsasecurity.com/rsalabs/node.asp?id=2124).

How to Get a Certificate

There are several ways to acquire a certificate. When files are being exchanged, certificates will usually appear in one of two formats. Files with the .cer extension are signed ASN.1 files in the X.509v3 format. They contain a public key and the extra information I mentioned earlier. This is what you give to business partners or friends so they can use the public key to encrypt data for you.

You may also encounter files with a .pfx (Personal Information Exchange) extension. A .pfx file contains a certificate and the corresponding private key (the format is described in the PKCS #12 standard). Such files are highly sensitive and are typically used to import key pairs on a server or for backup purposes. When exporting key pairs, Windows offers to encrypt the .pfx file with a password; you have to provide this password again when importing the key pair.

You can also generate your own certificates. How you generate them usually depends on how they will be used. For normal Internet scenarios, where you don't know who your peers are, you typically request a certificate from a commercial certification authority (CA). This approach has the advantage that these known CAs are already trusted by Windows and any other OS (and browser) that supports certificates and SSL. As a result, you don't have to do a CA key exchange.

For B2B and intranet scenarios, you can use an internal CA. Certificate Services are included in Windows 2000 and Windows Server® 2003. Combined with Active Directory®, this functionality lets you easily distribute certificates across an organization. (I will show you how to request certificates from a private CA in a moment.)

Sometimes during development you might be in a situation where the approaches just described don't work. For instance, if you need a certificate quickly for testing purposes, you can use makecert.exe. Included in the .NET Framework SDK, this tool generates certificates and key pairs. There is a similar tool, called selfssl.exe, in the IIS Resource Kit; it is specialized for creating SSL key pairs, and it can also configure IIS with such a key pair in a single step.

The Windows Certificate Store

Certificates and their corresponding private keys can be stored on a variety of devices, such as hard disks, smartcards, and USB tokens. Windows provides an abstraction layer, called the certificate store, to unify how you access certificates regardless of where they are stored. As long as the hardware device has a Windows-supported cryptographic service provider (CSP) you can access the data stored on it using the Certificate Store API.

The certificate store is buried deep in the user profile. This allows use of ACLs on the keys for a specific account. Every store is partitioned into containers. For instance, there's a container called Personal where you store your own certificates (the ones that have an associated private key). The Trusted Root Certification Authorities container holds the certificates of all CAs that you trust. The Other People container holds the certificates of people you securely communicate with. And so on. The easiest way to get to your certificate store is to run certmgr.msc.

There is also a machine-wide store, which is used by the Windows machine accounts (NETWORK, LOCAL SERVICE, and LOCAL SYSTEM) or if you want to share certificates or keys across accounts. ASP.NET applications always use the machine store; for desktop applications, you typically install certificates in the user store.

Only administrators can manage the machine and service account stores. For this purpose, you have to start the Microsoft Management Console (mmc.exe) and add the Certificates snap-in. There you can choose the store to administer. Figure 1 shows a screenshot of the MMC snap-in.

Figure 1 Certificates MMC Snap-In

Figure 1** Certificates MMC Snap-In **(Click the image for a larger view)

Besides allowing you to import, export, and search for certificates, the snap-in also lets you request certificates from an internal enterprise CA. Just right-click the personal container and select All Tasks | Request Certificate. The local machine then generates an RSA key pair and sends the public key portion to the CA for signing. Windows adds the signed certificate to the certificate store and the corresponding private key to a key container. The certificate gets linked to the key container via a storage attribute.

The private key containers are strongly ACL'd for either the corresponding account or LOCAL SYSTEM. This is a problem when you want to access keys stored in the machine profile from ASP.NET or from other user accounts. I wrote a tool that you can use to modify the ACLs of the container file.

Commercial and Windows CAs also have Web interfaces for requesting certificates. For these scenarios, typically an ActiveX® control in Internet Explorer® generates the keys and imports them into the store of the current user. As a general rule, when you want to make a certificate accessible to a user or service, you have two choices: either import it into his store or request it while being logged on as that user.

Working with Certificates

Certificates are used in various places in the .NET Framework, and at some level all of this functionality relies on the X509Certificate class from the System.Security.X509Certificates namespace. If you take a closer look, you'll also find a certificate class ending with a 2. This is because the .NET Framework 1.x had a representation of X.509 certificates called X509Certificate. This class had limited functionality and no support for cryptographic operations. In version 2.0, a new class was added called X509Certificate2. This is derived from X509Certificate and adds many capabilities. You can convert back and forth between them as necessary, but whenever possible you should use the latest version.

Accessing Certificates

You can retrieve certificates from the file system directly. However, it is better to retrieve them from the certificate store. To create an X509Certificate2 instance from a .cer file, simply pass the file name to the constructor:

X509Certificate2 cert1 = new X509Certificate2("alice.cer");

You can also load certificates from .pfx files. However, as I mentioned earlier, .pfx files can be password protected, and you should supply this password as a SecureString. SecureString encrypts the password internally and tries to minimize exposure of it in Memory, page files, and crash dumps. For this reason, you can only add a single (value type) char at a time to the string. The code in Figure 2, which disables the console echo and returns a SecureString, is useful if you want to ask your users for a password from the console.

Figure 2 Requesting a Password from the Console

private SecureString GetSecureStringFromConsole()
{
    SecureString password = new SecureString();

    Console.Write("Enter Password: ");
    while (true)
    {
        ConsoleKeyInfo cki = Console.ReadKey(true);

        if (cki.Key == ConsoleKey.Enter) break;
        else if (cki.Key == ConsoleKey.Escape) 
        {
            password.Dispose();
            return null;
        }
        else if (cki.Key == ConsoleKey.Backspace)
        {
            if (password.Length != 0)
                password.RemoveAt(password.Length - 1);
        }
        else password.AppendChar(cki.KeyChar);
    }

    return password;
}

In the article "Credential Management with the .NET Framework 2.0" (available at msdn.microsoft.com/library/en-us/dnnetsec/html/credmgmt.asp), Kenny Kerr included code to convert the result of the usual Windows credentials dialog into a SecureString. Regardless of how you obtain it, the SecureString can then be passed to the X509Certificate2 constructor to load the .pfx file, like so:

X509Certificate2 cert2 = new X509Certificate2("alice.pfx", password);

To access the Windows certificate store, you use the X509Store class. In its constructor you provide the store location (current user or machine) and the store name. You can use either a string or the StoreName enumeration to specify the container you want to open. Be aware that the internal names don't always match the names you find in the MMC snap-in. The Personal container maps to the name My, whereas Other People becomes AddressBook.

Once you have a valid instance of X509Store, you can search for, retrieve, delete, and add certificates. With the exception of deployment scenarios, you will probably use the search functionality most often. You can search for certificates on a variety of criteria, including subject name, serial number, thumbprint, issuer, and validity period. If you programmatically retrieve certificates in your applications from the store, you should use a unique property-the subject key identifier, for instance. The thumbprint is also unique, but keep in mind that this is a SHA-1 hash value of the certificate and will change if, for example, the certificate gets renewed. The code in Figure 3 shows a generic way to search for certificates.

Figure 3 Searching for Certificates

static void Main(string[] args)
{
    // search for the subject key id
    X509Certificate2 cert = FindCertificate(
      StoreLocation.CurrentUser, StoreName.My, 
      X509FindType.FindBySubjectKeyIdentifier, 
      "21f2bf447298e83056a69eb02ebe9085ed97f10a");
}

static X509Certificate2 FindCertificate(
    StoreLocation location, StoreName name,
    X509FindType findType, string findValue)
{
    X509Store store = new X509Store(name, location);
    try
    {
        // create and open store for read-only access
        store.Open(OpenFlags.ReadOnly);

        // search store
        X509Certificate2Collection col = store.Certificates.Find(
          findType, findValue, true);

        // return first certificate found
        return col[0];
    }
        // always close the store
    finally { store.Close(); }
}

Once you have an instance of X509 Certificate2, you can inspect the various properties of the certificate (such as the subject name, expiration dates, issuer, and the friendly name). The HasPrivateKey property tells you if there is an associated private key. The PrivateKey and PublicKey properties return the corresponding key as an RSACryptoServiceProvider instance.

To import a certificate, you call the Add method on the X509Store instance. When you specify a store name that doesn't exist in the constructor of the store, a new container will be created. Here's how you would import a certificate in a file named alice.cer into a new container called Test:

static void ImportCert()
{
    X509Certificate2 cert = new X509Certificate2("alice.cer");
    X509Store store = new X509Store("Test", StoreLocation.CurrentUser);
    try
    {
        store.Open(OpenFlags.ReadWrite);
        store.Add(cert);
    } 
    finally { store.Close(); }
}

Display Certificate Details and the Certificate Picker

Windows offers two standard dialogs for working with certificates: one for showing certificate details (properties and certification path) and one for letting users pick a certificate from a list. You can access these dialogs from the two static methods of the X509Certificate2UI class: SelectFromCollection and DisplayCertificate.

To show a list of certificates you have to populate an X509Certificate2Collection and pass that to SelectFromCollection. It is very common to let a user choose from one of his personal certificates in the store. For this, you simply pass in the Certificates property of an opened X509Store. You can also control the dialog caption, a message, and whether multiple selections are allowed. The DisplayCertificate method shows the same dialog you see when double-clicking on a .cer file in Windows Explorer. Figure 4 shows the dialog used for picking a certificate and Figure 5 provides the corresponding code.

Figure 5 Code for Choosing a Certificate

private static X509Certificate2 PickCertificate(
  StoreLocation location, StoreName name)
{
    X509Store store = new X509Store(name, location);
    try
    {
        store.Open(OpenFlags.ReadOnly);
        
        // pick a certificate from the store
        X509Certificate2 cert =  
            X509Certificate2UI.SelectFromCollection(
                store.Certificates, "Caption", 
                "Message", X509SelectionFlag.SingleSelection)[0];

        // show certificate details dialog
        X509Certificate2UI.DisplayCertificate(cert);
        return cert;
    }
    finally { store.Close(); }
}

Figure 4 Dialog for Choosing Certificates

Figure 4** Dialog for Choosing Certificates **(Click the image for a larger view)

Validating Certificates

There are a few criteria to consider when validating a certificate, especially the issuing party (generally, you only trust certificates that were issued by a CA in your trusted CA list) and its current validity (certificates can become invalid, such as when they expire or are revoked by the issuing CA). You can use the X509Chain class to check these various properties. Using this class, you can specify a policy for validity checking-for example, you can demand a trusted root CA or specify whether to check online or local revocation lists. If you need to check certificates that were used to sign data, it is important to check whether the certificate was valid when the signature was computed-for this, X509Chain allows you to change the verification time.

After constructing a policy, you call the Build method to get information about the validation outcome on the ChainStatus property. If there are multiple validation errors, you can iterate over the ChainElement collection to get more details. Figure 6 shows how to perform a strict validation of a certificate and its issuer against offline and online revocation lists.

Figure 6 Strict Certificate Validation

static void ValidateCert(X509Certificate2 cert)
{
    X509Chain chain = new X509Chain();
    
    // check entire chain for revocation
    chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;

    // check online and offline revocation lists
    chain.ChainPolicy.RevocationMode = 
        X509RevocationMode.Online | X509RevocationMode.Offline;

    // timeout for online revocation list
    chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 30);

    // no exceptions, check all properties
    chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;

    // modify time of verification
    //chain.ChainPolicy.VerificationTime = new DateTime(1999, 1, 1);

    chain.Build(cert);

    if (chain.ChainStatus.Length != 0)
        Console.WriteLine(chain.ChainStatus[0].Status);
}

SSL Support

The SSL authentication protocol relies on certificates. Support for SSL in the .NET Framework consists of two parts. The special (but most widely used) case of SSL over HTTP is implemented by the HttpWebRequest class (this is also ultimately used for Web service client proxies). To enable SSL, you don't have to do anything special besides specify a URL that uses the https: protocol.

When connecting to an SSL secured endpoint, the server certificate is validated on the client. If validation fails, by default the connection is immediately closed. You can override this behavior by providing a callback to a class called ServicePointManager. Whenever the HTTP client stack does certificate validation, it first checks if a callback is provided-if that's the case, it executes your code. To hook up the callback, you have to provide a delegate of type RemoteCertificateValidationCallback:

// override default certificate policy 
// (for example, for testing purposes) 
ServicePointManager.ServerCertificateValidationCallback =  
    new RemoteCertificateValidationCallback(VerifyServerCertificate);

In your callback, you get the server certificate, an error code, and a chain object passed in. You can then do your own check and return true or false. It can be helpful to turn off one of these checks if, for instance, your certificate has expired during development or testing. On the other hand, this also allows you to implement stricter validation policies than provided by default. Figure 7 provides a sample validation callback.

Figure 7 Validation Callback

private bool VerifyServerCertificate(
    object sender, X509Certificate certificate, 
    X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
    if (sslPolicyErrors == SslPolicyErrors.None) return true;

    foreach (X509ChainStatus s in chain.ChainStatus)
    {
        // allows expired certificates
        if (string.Equals(s.Status.ToString(), "NotTimeValid", 
            StringComparison.OrdinalIgnoreCase))
                return true;
    }

    return false;
}

SSL also supports client authentication using a certificate. If the Web site or service you want to access mandates a client certificate, both the Web service client proxy and HttpWebRequest provide a ClientCertificates property of type X509Certicate:

  proxy.Url = 
    "https://server/app/service.asmx";
  proxy.ClientCertificates.Add(
    PickCertificate(...));

In addition, the .NET Framework 2.0 introduces a new class called SslStream. This lets you layer SSL on top of any stream, not just HTTP, which makes it possible to SSL-enable a custom socket based protocol. SslStream makes use of the standard .NET certificates support in multiple ways, for example, using the validation callback mechanism I discussed:

public SslStream(Stream innerStream, bool leaveInnerStreamOpen, 
    RemoteCertificateValidationCallback ValidationCallback) {...}

And to start an SSL authentication with SslStream, you pass an X509Certificate to its AuthenticateAsServer method:

ssl.AuthenticateAsServer(PickCertificate(...));

Web Service Security

The WS-Security standard specifies client and server authentication and secure communication using certificates. Toolkits like the Web Services Enhancements (WSE) for the .NET Framework and technologies like the Windows Communication Foundation fully support this. Again, this boils down to supplying a certificate either in code or through configuration. The following snippet shows how to add a client certificate to a Web service proxy using WSE3:

X509SecurityToken token = new X509SecurityToken(PickCertificate(...));
proxy.RequestSoapContext.Security.Tokens.Add(token);

With Windows Communication Foundation, you typically provide a reference to a certificate store in a configuration file (see Figure 8). As you can see, all configuration attributes map directly to the enums used earlier in code.

Figure 8 Providing Certificate Reference in WCF

<system.serviceModel>
  <behaviors>
    <serviceBehaviors>
      <behavior name="ServiceBehavior">
        <serviceCredentials>
          <serviceCertificate storeLocation="LocalMachine"
            storeName="My" x509FindType="FindBySubjectKeyIdentifier"
            findValue="1a7b..." />
        </serviceCredentials>
      </behavior>
    </serviceBehaviors>
  </behaviors>
</system.serviceModel>

Security Policy and Code Signing

Certificates are also used in Authenticode® code signing. By signing a binary, you can add information about the publisher and make sure the signed file can be reliably validated after it has been signed. You can use the signtool.exe tool from the .NET Framework SDK to sign .exe and .dll files. Afterwards, you can verify the signature and view the certificate using the properties dialog in Windows Explorer. Note that if both Authenticode and strong name signatures are going to be used, the strong name signature needs to be applied first. Additionally, Authenticode signed assemblies can experience delays at load time, which translates to a longer application startup time if it's the entry point executable that's been signed.

Signed files can also be used for security policies. Using software restriction policies, you can restrict execution of unmanaged executables based on signatures or the absence of signatures. And the .NET Framework code access security (CAS) policy supports code groups based on a publisher certificate.

To create a CAS policy, you use mscorcfg.msc to create a new code group based on a publisher membership condition. You can then assign a permission set to all applications signed by that publisher (see Figure 9).

Figure 9 Assigning Permissions to a Publisher

Figure 9** Assigning Permissions to a Publisher **(Click the image for a larger view)

ClickOnce Manifests

Another technology that uses certificates for publisher information is ClickOnce. When you publish a ClickOnce application, you have to sign the deployment and application manifest. This again adds publisher information to the application and ensures that sensitive information in the manifests (such as the security policy and application dependencies) cannot be modified without invalidating the signature. ClickOnce makes the publisher information available to clients during the installation so they can make intelligent decisions about the trustworthiness of the application. Depending on the certificate (and its validation outcome) the ClickOnce installer also uses different visual cues. Figure 10 shows the Visual Studio® manifest signing dialog.

Figure 10 Visutal Studio Manifest Signing Dialog

Figure 10** Visutal Studio Manifest Signing Dialog **(Click the image for a larger view)

Signing and Encrypting Data

So far, I've focused on the fundamental certificate-related APIs and how other technologies make use of them. Now I want to discuss cryptographic operations, like encrypting and signing data with certificates, and the new PKCS #7 implementation found in the .NET Framework 2.0.

Protecting data is always a two-step process. First you sign the data to make it tamper-proof. Then you encrypt the data to protect it from disclosure. Before you can perform any cryptographic operation with the PKCS #7 classes, however, you first have to wrap the data in a ContentInfo object, representing a CMS data structure. From there you can transform the data into signed or encrypted data, represented respectively by the SignedCms and EnvelopedCms classes.

Technically, a digital signature is the hash of the data that's then encrypted with your private key. This means you need a certificate with an associated private key or a .pfx file. Based on such a certificate, you can create a CmsSigner object, which represents the signer of the data. The SignedCms class in turn computes the signature and outputs a PKCS #7, CMS-compliant byte array. Figure 11 shows the corresponding code. The encoded byte array contains your data, the signature, and the certificate used to sign the data.

Figure 11 Outputting CMS-Compliant Byte Array

byte[] Sign(byte[] data, X509Certificate2 signingCert)
{
    // create ContentInfo
    ContentInfo content = new ContentInfo(data);

    // SignedCms represents signed data
    SignedCms signedMessage = new SignedCms(content);

    // create a signer
    CmsSigner signer = new CmsSigner(signingCert);

    // sign the data
    signedMessage.ComputeSignature(signer);

    // create PKCS #7 byte array
    byte[] signedBytes = signedMessage.Encode();

    // return signed data
    return signedBytes;
}

This might not be critical if you are signing large amounts of data, but if the amount of data is small, this adds some overhead. For example, signing a 10 byte array with a 2KB public key results in a roughly 2,400 byte array. Keep that in mind if you want to store the signed data in, say, a database. An alternative approach is to use a so-called detached signature. This lets you remove your data from the signature and store it separately. You could, for example, first combine multiple small pieces of data and sign them altogether. To create a detached signature you have to pass an additional true to the SignedCms constructor, as shown in Figure 12.

Figure 12 Creating Detached Signature

byte[] SignDetached(byte[] data, X509Certificate2 signingCert)
{
    // create ContentInfo
    ContentInfo content = new ContentInfo(data);

    // pass true to the constructor to indicate
    // we want to sign detached
    SignedCms signedMessage = new SignedCms(content, true);

    // these steps are the same 
    CmsSigner signer = new CmsSigner(signingCert);
    signedMessage.ComputeSignature(signer);
    byte[] signedBytes = signedMessage.Encode();

    // return only the signature (not the data)
    return signedBytes;
}

Once you've signed the data, you can encrypt it. You'll need the public keys of the recipients that should be able to decrypt the data. You usually get these from your Other People store (or from a .cer file if you don't want to use the certificate store). This time the EnvelopedCms class does all the heavy lifting. You specify the public keys used for encryption in a CmsRecipientCollection, which you pass into the Encrypt method. As with SignedCms, here the Encode method creates the PKCS #7, CMS-compliant byte array (see Figure 13).

Figure 13 Encode Method

byte[] Encrypt(byte[] data, X509Certificate2 encryptingCert)
{
    // create ContentInfo
    ContentInfo plainContent = new ContentInfo(data);

    // EnvelopedCms represents encrypted data
    EnvelopedCms encryptedData = new EnvelopedCms(plainContent);

    // add a recipient
    CmsRecipient recipient = new CmsRecipient(encryptingCert);

    // encrypt data with public key of recipient
    encryptedData.Encrypt(recipient);

    // create PKCS #7 byte array
    byte[] encryptedBytes = encryptedMessage.Encode();

    // return encrypted data
    return encryptedBytes;
}

Internally, EnvelopedCms generates a random session key with which the data is symmetrically encrypted. Afterwards, the session key is encrypted with the public key of each recipient. Thus, you don't need a separate encrypted version of the data for each of your recipients. In addition, some extra information is embedded, allowing the recipient to find the matching private key for decryption in his certificate store.

Decrypting Data and Verifying Signatures

At the receiving end, the whole process is reversed. That means you first decrypt the data and then validate the signature and the signing certificate. In code, you first have to call the Decode methods of the SignedCms and EnvelopedCms classes to deserialize the CMS byte array back to an object representation. Then you can call Decrypt and CheckSignature, respectively.

The process looks in the encrypted package to see if the session key can be decrypted by searching for a corresponding private key in the certificate store. Afterwards, the decrypted session key is used to decrypt the actual data. You can also supply a list of additional certificates that should be taken into consideration during decryption in case the private key is not stored in the certificate store:

static byte[] Decrypt(byte[] data)
{
    // create EnvelopedCms
    EnvelopedCms encryptedMessage = new EnvelopedCms();

    // deserialize PKCS#7 byte array
    encryptedMessage.Decode(data);

    // decryt data
    encryptedMessage.Decrypt();

    // return plain text data
    return encryptedMessage.ContentInfo.Content;
}

Verifying the data is a two-step process. You first make sure the signature is valid, which means the data has not been tampered with. You then check the signing certificate. The CheckSignature method of the SignedCms class lets you do both steps at once. In this case, the certificate is validated against the default system policy. If you want more control over that process, you can do your own checking using an X509Chain object and code like that in Figure 6. And Figure 14 shows the code used to check and remove a signature, while Figure 15 provides the code used for detached signature validation.

Figure 15 Detached Signature Validation

static bool VerifyDetached(byte[] data, byte[] signature)
{
    ContentInfo content = new ContentInfo(data);

    // pass true for detached
    SignedCms signedMessage = new SignedCms(content, true);

    // deserialize signature
    signedMessage.Decode(signature);

    try
    {
        // check if signature matches data
        // the certificate is also checked
        signedMessage.CheckSignature(false);
        return true;
    }
    catch { return false; }
}

Figure 14 Verify and Remove a Signature

byte[] VerifyAndRemoveSignature(byte[] data)
{
    // create SignedCms
    SignedCms signedMessage = new SignedCms();

    // deserialize PKCS #7 byte array
    signedMessage.Decode(data);

    // check signature
    // false checks signature and certificate
    // true only checks signature
    signedMessage.CheckSignature(false);

    // access signature certificates (if needed)
    foreach (SignerInfo signer in signedMessage.SignerInfos)
    {
        Console.WriteLine("Subject: {0}", 
          signer.Certificate.Subject);
    }

    // return plain data without signature
    return signedMessage.ContentInfo.Content;
}

Putting It All Together

When working with security policies or communication protocols, you will encounter certificates in all kinds of situations. In this article I explained the basic APIs used to retrieve and search certificates and showed you how to use them for encryption and digital signatures. In addition, I provided some examples of higher level application services that require you to understand the Windows certificate store and the relationship between public and private keys.

The source code for this article, available for download from the MSDN®Magazine Web site, includes a small Windows Forms application that supports signing and encrypting files. It uses many of the techniques I've discussed in this article, like selecting certificates from different stores and protecting/verifying data using encryption and signatures.

Dominick Baier is an independent security consultant in Germany. He helps companies with secure design and architecture, content development, penetration testing, and code auditing. He is also the security curriculum lead at DevelopMentor, a Developer Security MVP, and author of Developing More-Secure Microsoft ASP.NET 2.0 Applications (Microsoft Press, 2006). His blog is located at www.leastprivilege.com.