.NET and Certificate Stores


Introduction:
The cryptography support included with .NET Framework 1.1 and 1.0 does not include support for accessing Windows cryptographic certificate stores. While support for hashing, encryption and X509v3 certificates is available using classes of the System.Security.Cryptography and System.Security.Cryptography.X509Certificates namespaces, there is no direct support for instantiating managed certificates from system certificate stores, nor is there currently support for generation or verification of CMS/PKCS#7 signatures. While there are indications that broader support for certificate stores and pkcs will be available in future releases of the .net framework, it is useful to have basic system certificate store access from managed code which can target .net framework 1.0/1.1. This article shows two approaches in C# for accessing certificate stores from .net framework using: CAPICOM 2 Interop:
Much of the useful certificate store functionality of cryptoAPI has been encapsulated in CAPICOM. CAPICOM provides advanced capability, including support for pkcs7 signatures, envelopes, encryption, Authenticode signatures and advanced certificate store access. CAPICOM requires an end user component dll installation and registration, and deployment of a .net interop assembly. An interop assembly for CAPICOM can be easily built using:
     tlbimp capicom.dll /namespace:CAPICOM /out:Interop.CAPICOM.dll
This is essentially the same command executed by VS.net when the COM reference is added. Note that the interop assembly has explicitly been given the namespace CAPICOM while the assembly is titled Interop.CAPICOM. The simple C# application below is compiled using:
      csc /r:interop.capicom.dll FindCertnet.cs
To execute the application, the Interop.CAPICOM.dll assembly must be in the application directory, or if Strong Name signed, can be deployed to the GAC for shared access by other .net applications referencing CAPICOM. The application demonstrates basic certificate store access using CAPICOM. It also demonstrates different ways of using a Certificates enumeration, including the indexer. Casting to the ICertContext interface provides the context that can be used with X509Certificate(IntPtr handle) constructor, although with CAPICOM installed, all the functionality is available from the interop assembly. A few properties of an X509Certificate object are displayed:

using System; using System.Collections; using System.Security.Cryptography.X509Certificates; using CAPICOM; class FindCertnet { static private String storeName = "My"; static StoreClass oStore; static Certificates oCerts; static String filter = "yoursearchstring"; [STAThread] static void Main(string[] args) { oStore = new StoreClass(); oStore.Open( CAPICOM_STORE_LOCATION.CAPICOM_CURRENT_USER_STORE, storeName,CAPICOM_STORE_OPEN_MODE.CAPICOM_STORE_OPEN_EXISTING_ONLY | CAPICOM_STORE_OPEN_MODE.CAPICOM_STORE_OPEN_READ_ONLY ); oCerts = (Certificates)oStore.Certificates; oCerts = (Certificates)oCerts.Find( CAPICOM_CERTIFICATE_FIND_TYPE.CAPICOM_CERTIFICATE_FIND_SUBJECT_NAME,filter, false); Console.WriteLine("\n{0} certificates match substring \"{1}\"", oCerts.Count, filter ); foreach(Certificate ocert in oCerts) //Certificates is IEnumerable ocert.Display() ; //---- alernate equivalent way to enumerate ---------- //IEnumerator certenum = oCerts.GetEnumerator() ; //while(certenum.MoveNext()){ // Certificate ocert = (Certificate) certenum.Current; // ocert.Display(); //} //--- get first cert using indexer, retrieve ICertContext and use from managed code ----- if(oCerts.Count>0){ Certificate firstcert = (Certificate)oCerts[1] ; firstcert.Display() ; ICertContext iCertCntxt = (ICertContext) firstcert; int certcntxt = iCertCntxt.CertContext ; IntPtr hCertCntxt = new IntPtr(certcntxt); if(hCertCntxt != IntPtr.Zero) { //use certcontext from managed code Console.WriteLine("CertContext:\t0x{0:X}", hCertCntxt.ToInt32()) ; X509Certificate foundcert = new X509Certificate(hCertCntxt); Console.WriteLine("\nFound certificate with SubjectName string \"{0}\"",filter); Console.WriteLine("SubjectName:\t{0}", foundcert.GetName()); Console.WriteLine("Serial No:\t{0}", foundcert.GetSerialNumberString()); Console.WriteLine("HashString:\t{0}" , foundcert.GetCertHashString()); } } } }

P/Invoke and cryptoAPI:
For simple cryptographic tasks, like accessing system certificate stores, another solution which requires no additional deployment to clients is Platform Invoke (P/Invoke) on the cryptoAPI libraries which are included with most recent Windows OS. The example below shows how to use .net P/Invoke to call several cryptoAPI unmanaged native functions from crypt32.dll and retrieving an IntPtr representing a certificate context which is used to instantiate an X509Certificate object in a similar way to the CAPICOM example above:


using System; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; using System.ComponentModel; public class WinCapi { /* HCERTSTORE WINAPI CertOpenSystemStore(HCRYPTPROV hprov, LPTCSTR szSubsystemProtocol); BOOL WINAPI CertCloseStore(HCERTSTORE hCertStore, DWORD dwFlags); PCCERT_CONTEXT WINAPI CertFindCertificateInStore( HCERTSTORE hCertStore, DWORD dwCertEncodingType, DWORD dwFindFlags, DWORD dwFindType, const void* pvFindPara, PCCERT_CONTEXT pPrevCertContext); BOOL WINAPI CertFreeCertificateContext( PCCERT_CONTEXT pCertContext ); */ [DllImport("crypt32.dll", CharSet=CharSet.Auto, SetLastError=true)] public static extern IntPtr CertOpenSystemStore( IntPtr hCryptProv, string storename) ; [DllImport("crypt32.dll", SetLastError=true)] public static extern bool CertCloseStore( IntPtr hCertStore, uint dwFlags) ; [DllImport("crypt32.dll", SetLastError=true)] public static extern IntPtr CertFindCertificateInStore( IntPtr hCertStore, uint dwCertEncodingType, uint dwFindFlags, uint dwFindType, [In, MarshalAs(UnmanagedType.LPWStr)]String pszFindString, IntPtr pPrevCertCntxt) ; [DllImport("crypt32.dll", SetLastError=true)] public static extern bool CertFreeCertificateContext( IntPtr hCertStore) ; } public class SimpleCert { const string MY = "MY"; const string OTHERS = "AddressBook"; const uint PKCS_7_ASN_ENCODING = 0x00010000; const uint X509_ASN_ENCODING = 0x00000001; const uint CERT_FIND_SUBJECT_STR = 0x00080007; static uint MY_ENCODING_TYPE = PKCS_7_ASN_ENCODING | X509_ASN_ENCODING ; static string lpszCertSubject = "yoursearchstring" ; public static void Main(){ IntPtr hSysStore = IntPtr.Zero; IntPtr hCertCntxt = IntPtr.Zero; hSysStore = WinCapi.CertOpenSystemStore(IntPtr.Zero, MY) ; Console.WriteLine("Store Handle:\t0x{0:X}", hSysStore.ToInt32()); if(hSysStore != IntPtr.Zero) { hCertCntxt=WinCapi.CertFindCertificateInStore( hSysStore, MY_ENCODING_TYPE, 0, CERT_FIND_SUBJECT_STR, lpszCertSubject , IntPtr.Zero) ; if(hCertCntxt != IntPtr.Zero){ //use certcontext from managed code Console.WriteLine("CertContext:\t0x{0:X}", hCertCntxt.ToInt32()) ; X509Certificate foundcert = new X509Certificate(hCertCntxt); Console.WriteLine("\nFound certificate with SubjectName string \"{0}\"",lpszCertSubject); Console.WriteLine("SubjectName:\t{0}", foundcert.GetName()); Console.WriteLine("Serial No:\t{0}", foundcert.GetSerialNumberString()); Console.WriteLine("HashString:\t{0}" , foundcert.GetCertHashString()); } else Console.WriteLine("Could not find SubjectName containing string \"{0}\"", lpszCertSubject); } //------- Clean Up ----------- if(hCertCntxt != IntPtr.Zero) WinCapi.CertFreeCertificateContext(hCertCntxt); if(hSysStore != IntPtr.Zero) WinCapi.CertCloseStore(hSysStore, 0) ; } }
CertFindCertificateInStore() has comprehensive support and options for searching certificate stores in many different ways. The example above shows one simple implementation.

For more advanced certificate store access via P/Invoke (including memory stores, deleting stores etc..), the cryptoAPI function CertOpenStore() can be used. A typical managed prototype (for C#) might be:

 [DllImport("crypt32.dll", CharSet=CharSet.Auto, SetLastError=true)]
    public static extern IntPtr CertOpenStore(
	IntPtr storeProvider,
	uint dwMsgAndCertEncodingType,
	IntPtr hCryptProv,
	uint dwFlags,
	String cchNameString) ;


References:



Michel I. Gallant
neutron@istar.ca