Working with CryptoAPI 2.0

for RuBoard

We turn our attention now to the set of Win32 functions commonly known as CryptoAPI 2.0functions that deal with digital certificates and signed messages. CryptoAPI includes support for handling and processing digital certificates based on the X.509 version 3 standard as profiled by the IETF's PKIX working group . CryptoAPI 2.0 also includes functions that generate cryptographically signed messages in the Cryptographic Message Syntax (CMS) format, which is the format used by the S/MIME secure mail standard to send and receive signed and encrypted e-mail messages. In this section, we assume that the reader is familiar with X.509/PKIX Part 1 certificates and CMS-format messages.

The .NET Framework includes minimal support for X.509/PKIX certificates and no support for CMS messages, so if you want to use these features of CryptoAPI in your programs, you have two choices:

  • Use the CLR's platform invoke mechanism to call CryptoAPI functions directly.

  • Use the COM interop capabilities built into the CLR to talk to the CAPICOM library, a COM wrapper distributed by Microsoft for accessing certain CryptoAPI functions.

We will focus initially on direct invocation of CryptoAPI 2.0's Win32 APIs from the .NET Framework, and how those APIs can be used with the Framework's X509Certificate class. Later examples in this section show how CAPICOM can be used from within the .NET Framework.

The X509Certificate class (located in the System.Security.Cryptography.X509Certificates namespace) is the .NET Framework's representation of an X.509v3/PKIX Part 1 digital certificate. An instance of X509Certificate can be created in any of the following five ways:

  • From a byte array containing the ASN.1 binary encoding of a certificate via the X509Certificate(byte[]) constructor.

  • Cloned from an existing X509Certificate via the X509Certificate(X509Certificate) constructor.

  • From a file containing an ASN.1 binary encoding of a certificate via the static method CreateFromCertificateFile(String) (the String argument is the name of the file containing the certificate data).

  • From a file containing a CMS-format signed message via the static method CreateFromSignedFile(String) (the String argument is the name of the file containing the CMS-format message). The certificate obtained from the file is the one associated with the key pair used to sign the message.

  • From a pointer to a CryptoAPI CERT_CONTEXT data structure, which is the unmanaged (native Win32) representation of a certificate.

This last constructor provides an interface between Win32 CryptoAPI certificate functions and the X509Certificate class in the .NET Framework. Using platform invoke, we can call CryptoAPI 2.0 functions to manipulate certificate stores (such as the per- user and per-machine certificate stores that exist in Windows) and then import individual certificates into the .NET Framework.

The program shown in Listing 31.2 demonstrates how the X509Certificate class works with the CryptoAPI certificate functions. This program enumerates the contents of one of the user's certificate stores (the "MY" store by default, where end-entity certificates for the user are normally stored). The program uses platform invoke to call three CryptoAPI functions CertOpenStore , CertEnumCertificatesInStore , and CertCloseStore . Repeated calls to CertEnumCertificatesInStore iterate through the contents of the store (each iteration of the while loop causes the currentCertContext variable to point to a new certificate). When currentCertContext is equal to IntPtr(0) , the underlying PCERT_CONTEXT is NULL and there are no more certificates to enumerate.

Listing 31.2 Enumerating the Contents of a Certificate Store
 using System; using System.IO; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Runtime.InteropServices; public class Test {   // magic constants from wincrypt.h   private static int CERT_STORE_PROV_SYSTEM = 10;   private static int CERT_SYSTEM_STORE_CURRENT_USER = (1 << 16);   [DllImport("CRYPT32.DLL", EntryPoint="CertOpenStore", CharSet=CharSet.Auto, graphics/ccc.gif SetLastError=true)]   public static extern IntPtr CertOpenStoreStringPara( int storeProvider, int graphics/ccc.gif encodingType, int hcryptProv, int flags, String pvPara);   [DllImport("CRYPT32.DLL", EntryPoint="CertOpenStore", CharSet=CharSet.Auto, graphics/ccc.gif SetLastError=true)]   public static extern IntPtr CertOpenStoreIntPtrPara( int storeProvider, int graphics/ccc.gif encodingType, int hcryptProv, int flags, IntPtr pvPara);   [DllImport("CRYPT32.DLL", EntryPoint="CertEnumCertificatesInStore", CharSet=CharSet. graphics/ccc.gif Auto, SetLastError=true)]   public static extern IntPtr CertEnumCertificatesInStore( IntPtr storeProvider, IntPtr graphics/ccc.gif prevCertContext);   [DllImport("CRYPT32.DLL", EntryPoint="CertCloseStore", CharSet=CharSet.Auto, graphics/ccc.gif SetLastError=true)]   public static extern bool CertCloseStore(IntPtr storeProvider, int flags);   public static void Main(String[] args)   {     String store = "MY"; // default system store to search is the MY store     if (args.Length > 0)     {       store = args[0]; // first argument, if any, overrides default value     }     IntPtr storeHandle = CertOpenStoreStringPara(CERT_STORE_PROV_SYSTEM, 0, 0, graphics/ccc.gif CERT_SYSTEM_STORE_CURRENT_USER, store);     X509Certificate cert;     IntPtr currentCertContext = CertEnumCertificatesInStore(storeHandle, (IntPtr) 0);     while (currentCertContext != (IntPtr) 0)     {       cert = new X509Certificate(currentCertContext);       // Do something with the certificate...       Console.WriteLine(cert.ToString(true));       currentCertContext = CertEnumCertificatesInStore(storeHandle, currentCertContext);     }     CertCloseStore(storeHandle, 0);   } } 

This program uses platform invoke to access Win32 APIs in a similar manner to that we saw in Listing 31.1. We use the IntPtr structure to represent handles to certificates and certificate stores, and of course there are some "magic constants" that we have to import manually from the wincrypt.h header file. Every call to CertEnumCertificatesInStore that succeeds returns a pointer to a new CERT_CONTEXT . Using the X509Certificate(IntPtr) constructor, we turn the CERT_CONTEXT pointer into a X509Certificate object, which we can then use as desired. (In this example, we just pretty-print the contents of the X509Certificate to the console.)

NOTE

Often, the APIs in CryptoAPI use void* arguments to pass context-dependent structures. CertOpenStore is an example of one such API; the last argument, pvPara , can be a pointer to a string, a file handle, a registry key handle, an HCRYPTMSG handle, or even a pointer to a CRYPT_DATA_BLOB . The CLR marshaler (which is responsible for converting managed objects into unmanaged objects during platform invoke calls) obviously cannot automatically distinguish among these various types of arguments, because it does not have the knowledge inherent in the CryptoAPI implementation concerning what type of argument to expect for each possible set of API arguments. Thus, we need to provide platform invoke with some additional information so that it can marshal arguments properly. In Listing 31.2, we chose to explicitly define multiple managed proxies for the CertOpenStore function, CertOpenStoreStringPara and CertOpenStoreIntPtrPara , where each managed proxy requires a different Type for its pvPara argument. In this example, all the calls to CertOpenStore required string arguments, so we always used CertOpenStoreStringPara , but if we had wanted to pass a file or registry key handle to CertOpenStore , we would have to have used the CertOpenStoreIntPtrPara proxy.


You can always use platform invoke to access any CryptoAPI 2.0 function you want, but it often may be easier for you to use CAPICOM to accomplish your task. CAPICOM is a set of classic COM wrappers for common CryptoAPI 2.0 functions; it is available for download from the Microsoft Developer Network (MSDN) as part of the Platform SDK. CAPICOM consists of a single DLL, CAPICOM.dll , which must be present at runtime if your application uses CAPICOM. If you do not already have the Platform SDK installed you can download just the redistributable version of CAPICOM from Microsoft's Download Center by visiting the URL http://www.microsoft.com/downloads/release.asp?releaseid=30316.

After you have obtained the CAPICOM library, you must first register it with Windows. Type the following command at the command prompt to register the CAPICOM library and make its COM interfaces available for use by other COM objects:

 regsvr32 CAPICOM.dll 

To use a COM component from managed code, we must first generate metadata for the COM interfaces the component exposes. If you are using Visual Studio.NET to write your programs, the metadata generation happens automatically when you add a reference to the COM component to your project. To use CAPICOM from within a Visual Studio.NET project, simply

  1. Select Project, Add Reference from the Visual Studio.NET menu.

  2. Select the COM tab on the Add Reference dialog.

  3. Scroll down until you see CAPICOM 1.0 Type Library .

  4. Click CAPICOM 1.0 Type Library to highlight it and click the Select button.

  5. Click the OK button.

You will now see an additional reference to CAPICOM in the References section of the Solution Explorer. By default, the metadata generated by Visual Studio.NET for an imported COM object lives in a namespace identical to the name of the library DLL that was imported, so the metadata generated for CAPICOM.dll resides in the CAPICOM namespace. You will want to add a

 using CAPICOM; 

statement to your program so that you can automatically reference the types defined in CAPICOM.dll .

If you are not using Visual Studio.NET to author your programs, you need to generate the metadata for CAPICOM.dll manually. The .NET Framework SDK includes a utility called tlbimp.exe that will construct a managed proxy assembly with appropriate metadata for a COM object type library. The CAPICOM type library is contained within CAPICOM.dll , so you can generate the CLR metadata for CAPICOM by typing the following command at the command prompt:

 tlbimp CAPICOM.dll /out:CAPICOMCLR.dll /namespace:CAPICOM 

NOTE

The Tlbimp.exe program will issue some warning messages when used to generate metadata for CAPICOM.dll . These warnings can be safely ignored.


The assembly created by tlbimp is normally named the same as the input .tlb file, except that the file extension is changed from .tlb to .dll . Because the type library we imported was contained within a DLL already, we have to rename the output assembly to something else. We use the /out argument to name the output file CAPICOMCLR.dll , but you can use any name you like. The /namespace argument overrides tlbimp 's default namespace choice (the output DLL name, CAPICOMCLR in our case) and forces the metadata namespace to be just CAPICOM . To use the CAPICOM metadata in your program, you will need to explicitly reference the metadata assembly when compiling your programs. The exact method of referencing another assembly varies from compiler to compiler; the C# compiler csc.exe uses a /r command line argument, so you will want to add /r:capicomclr.dll to your other arguments to csc.exe . You will also probably want to add a using CAPICOM; statement to your C# source code (or equivalent syntax for the source language you are using) so you can more easily reference objects in the CAPICOM namespace.

Now that you have configured CAPICOM for use with the .NET Framework, let's look at some examples. Listing 31.3 contains a program that dumps the contents of the "MY" certificate store using CAPICOM ; it performs the same function as the program in Listing 31.2 but without calling any Win32 APIs directly.

Listing 31.3 Enumerating the Contents of a Certificate Store Using CAPICOM
 using System; using System.Security.Cryptography.X509Certificates; using CAPICOM; using System.IO; public class ReadCertificateStore {   public static void Main(String[] args) {     Store store = new Store();     store.Open(CAPICOM_STORE_LOCATION.CAPICOM_CURRENT_USER_STORE, "MY", graphics/ccc.gif CAPICOM_STORE_OPEN_MODE.CAPICOM_STORE_OPEN_READ_ONLY);     foreach (Certificate cert in store.Certificates) {       String certString = cert.Export( CAPICOM_ENCODING_TYPE.CAPICOM_ENCODE_BASE64);       byte[] decodedcert = Convert.FromBase64String(certString);       X509Certificate certificate = new  X509Certificate(decodedcert);       Console.WriteLine(certificate.ToString(true));     }   } } 

CAPICOM Store objects represent CryptoAPI 2.0 certificate stores. In this program, we create an empty Store object and then call its Open method to bind the object to a particular certificate store. Here, we ask CAPICOM to open up the user's "MY" store just as we did in Listing 31.2. CAPICOM provides a number of enumerations that correspond to the flags we would normally pass to the Win32 APIs; the CAPICOM_STORE_LOCATION.CAPICOM_CURRENT_USER_STORE enumeration used here corresponds to the CERT_SYSTEM_STORE_CURRENT_USER flag used previously. When open, the Store 's Certificates property returns a collection of all the certificates within the store that we can step through using the C# foreach language construct.

The most complicated portion of this program is the three lines of code needed to convert CAPICOM's representation of a certificate into an X509Certificate managed object. CAPICOM does not directly expose the CERT_CONTEXT associated with its Certificate objects, so to convert a certificate from CAPICOM to the .NET Framework, we need to export the certificate from CAPICOM in a common format and re-import it into an X509Certificate object. CAPICOM provides an Export method on the Certificate class that allows us to convert the certificate into a Base64 encoding of its ASN.1 binary representation. We convert the encoded form back into an array of bytes and construct a new X509Certificate object from the byte array.

CAPICOM also contains functions that build and verify chains of certificates; the program shown in Listing 31.4 is a sample application of these functions. This program opens the "MY" user certificate store, extracts the first certificate it finds in the store, and then asks CryptoAPI to build a certificate chain from that certificate to a trusted root certificate. Assuming that a chain exists, the program then walks the chain from end-entity certificate to root certificate, converting each certificate encountered along the way into an X509Certificate object using the export/import-as-Base64 technique used previously. The resulting X509Certificate objects are printed to the console.

Listing 31.4 Building a Certificate Chain Using CAPICOM
 using System; using System.Security.Cryptography.X509Certificates; using CAPICOM; using System.IO; public class ReadCertificateStore {   public static void Main(String[] args) {     Store store = new Store();     store.Open(CAPICOM_STORE_LOCATION.CAPICOM_CURRENT_USER_STORE, "MY", graphics/ccc.gif CAPICOM_STORE_OPEN_MODE.CAPICOM_STORE_OPEN_READ_ONLY);     // Check to make sure there's at least one cert in the MY store     if (store.Certificates.Count > 0) {       Certificate cert = (Certificate) store.Certificates[0];       Chain chain = new Chain();       chain.Build(cert);       foreach (Certificate chainCert in chain.Certificates) {         String certString = chainCert.Export( CAPICOM_ENCODING_TYPE. graphics/ccc.gif CAPICOM_ENCODE_BASE64);         byte[] decodedcert = Convert.FromBase64String(certString);         X509Certificate certificate = new  X509Certificate(decodedcert);         Console.WriteLine(certificate.ToString(true));       }     }   } } 

As you can see, both platform invoke and CAPICOM make certificate-based operations relatively easy to perform from within the .NET Framework. For very simple, straightforward applications, CAPICOM will generally be the easier of the two mechanisms to use. However, because CAPICOM exposes only a subset of CryptoAPI 2.0 functionality, it is likely that as your application requirements increase you will need to use platform invoke to call the CryptoAPI 2.0 APIs directly.

for RuBoard


. NET Framework Security
.NET Framework Security
ISBN: 067232184X
EAN: 2147483647
Year: 2000
Pages: 235

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net