GProano's .Net BLog

.Net/CLR Security Discussion

  • Installing CTLs and CRLs

    We have a great X509 story in Whidbey as revealed in Beta1 there is now support for Certificate Stores, Extensions, Chain building and a more robust X509Certificate2 class.  On the subject of chain building, quite often you will need to install Certificate Trust Lists (CTLs) and Certificate Revocation Lists (CRLs) for proper verification. 

    CRL and CTL support isn't in Whidbey and I've found very few resources for .Net sample code to be able to use these, so below is some sample code for installing a CRL and CTL into a certificate store.  I'm using the X509Store class to gain access to a store - in Beta2 we added a StoreHandle property for this very purpose.  I need the store context to be able to add various objects to the store.

    The CRLContext and CTLContext classes are very similar.  The constructors take a file and call CryptQueryObject to obtain a context to your desired object.  The context is then stored in the hContext field of each class.  A method AddToStore is then exposed which will add the object to the store - in the future I will talk about how to enumerate and remove these objects from the store.

    This will only work with Whidbey because I'm using the X509Store class, next time I will present code for opening a store so that this code can be used in Everett and RTM.

    using System;

    using System.Security.Cryptography.X509Certificates;

    using System.Runtime.InteropServices;

    using System.Runtime.CompilerServices;

     

    public class X509Sample

    {

        public static X509Store OpenStore(string StoreName, StoreLocation StoreLocation, OpenFlags Flags)

        {

            X509Store store = new X509Store(StoreName, StoreLocation);

            store.Open(Flags);

            return store;

        }

    }

    public class CTLContext

    {

        IntPtr hContext;

        public CTLContext(IntPtr Context)

        {

            hContext = CertDuplicateCTLContext(Context);

        }

        public unsafe CTLContext(string CTLFile)

        {

            IntPtr pContext = new IntPtr(Constants.CERT_QUERY_CONTENT_CTL);

     

            GCHandle handle = GCHandle.Alloc(CTLFile, GCHandleType.Pinned);

            IntPtr pbData = handle.AddrOfPinnedObject();

     

            bool ret = CryptoAPI.CryptQueryObject(

                        Constants.CERT_QUERY_OBJECT_FILE,

                        pbData,

                        Constants.CERT_QUERY_CONTENT_FLAG_CTL,

                        Constants.CERT_QUERY_FORMAT_FLAG_ALL,

                        0,

                        IntPtr.Zero,

                        IntPtr.Zero,

                        IntPtr.Zero,

                        IntPtr.Zero,

                        IntPtr.Zero,

                        new IntPtr(&pContext));

            hContext = CertDuplicateCTLContext(pContext);

            CertFreeCTLContext(pContext);

        }

        public bool AddToStore(X509Store Store, uint AddDisposition, ref IntPtr StoreContext)

        {

            return CertAddCTLContextToStore(Store.StoreHandle, this.hContext, AddDisposition, StoreContext);

        }

        ~CTLContext()

        {

            if (hContext != IntPtr.Zero)

                CertFreeCTLContext(hContext);

        }

     

        [DllImport("CRYPT32.dll", CharSet = CharSet.Auto, SetLastError = true)]

        internal static extern IntPtr CertDuplicateCTLContext(IntPtr pCrlContext);

     

        [DllImport("CRYPT32.dll", CharSet = CharSet.Auto, SetLastError = true)]

        internal static extern bool CertFreeCTLContext(IntPtr pCrlContext);

     

        [DllImport("CRYPT32.dll", CharSet = CharSet.Auto, SetLastError = true)]

        internal static extern bool CertAddCTLContextToStore(

            [In] IntPtr hCertStore,

            [In] IntPtr pCrlContext,

            [In] uint dwAddDisposition,

            [Out] IntPtr ppStoreContext);

    }

    public class CRLContext

    {

        IntPtr hContext;

        public CRLContext(IntPtr Context)

        {

            hContext = CertDuplicateCRLContext(Context);

        }

        public unsafe CRLContext(string CRLFile)

        {

            IntPtr pContext = new IntPtr(Constants.CERT_QUERY_CONTENT_CRL);

     

            GCHandle handle = GCHandle.Alloc(CRLFile, GCHandleType.Pinned);

            IntPtr pbData = handle.AddrOfPinnedObject();

     

            bool ret = CryptoAPI.CryptQueryObject(

                        Constants.CERT_QUERY_OBJECT_FILE,

                        pbData,

                        Constants.CERT_QUERY_CONTENT_FLAG_CRL,

                        Constants.CERT_QUERY_FORMAT_FLAG_ALL,

                        0,

                        IntPtr.Zero,

                        IntPtr.Zero,

                        IntPtr.Zero,

                        IntPtr.Zero,

                        IntPtr.Zero,

                        new IntPtr(&pContext));

            hContext = CertDuplicateCRLContext(pContext);

            CertFreeCRLContext(pContext);

        }

        public bool AddToStore(X509Store Store, uint AddDisposition, ref IntPtr StoreContext)

        {

            return CertAddCRLContextToStore(Store.StoreHandle, this.hContext, AddDisposition, StoreContext);

        }

        ~CRLContext()

        {

            if (hContext != IntPtr.Zero)

                CertFreeCRLContext(hContext);

        }

     

        [DllImport("CRYPT32.dll", CharSet = CharSet.Auto, SetLastError = true)]

        internal static extern IntPtr CertDuplicateCRLContext(IntPtr pCrlContext);

     

        [DllImport("CRYPT32.dll", CharSet = CharSet.Auto, SetLastError = true)]

        internal static extern bool CertFreeCRLContext(IntPtr pCrlContext);

     

        [DllImport("CRYPT32.dll", CharSet = CharSet.Auto, SetLastError = true)]

        internal static extern bool CertAddCRLContextToStore(

            [In] IntPtr hCertStore,

            [In] IntPtr pCrlContext,

            [In] uint dwAddDisposition,

            [Out] IntPtr ppStoreContext);

    }

    internal static class CryptoAPI

    {

     

        [DllImport("CRYPT32.dll", CharSet = CharSet.Auto, SetLastError = true)]

        internal static extern bool CryptQueryObject(

             [In]     uint dwObjectType,

             [In]     IntPtr pvObject,

             [In]     uint dwExpectedContentTypeFlags,

             [In]     uint dwExpectedFormatTypeFlags,

             [In]     uint dwFlags,

             [Out]    IntPtr pdwMsgAndCertEncodingType,

             [Out]    IntPtr pdwContentType,

             [Out]    IntPtr pdwFormatType,

             [In, Out] IntPtr phCertStore,

             [In, Out] IntPtr phMsg,

             [In, Out] IntPtr ppvContext);

     

    }

    internal static class Constants

    {

        //Store constants

        internal const uint CERT_STORE_ADD_NEW = 1;

        internal const uint CERT_STORE_ADD_USE_EXISTING = 2;

        internal const uint CERT_STORE_ADD_REPLACE_EXISTING = 3;

        internal const uint CERT_STORE_ADD_ALWAYS = 4;

        internal const uint CERT_STORE_ADD_REPLACE_EXISTING_INHERIT_PROPERTIES = 5;

        internal const uint CERT_STORE_ADD_NEWER = 6;

        internal const uint CERT_STORE_ADD_NEWER_INHERIT_PROPERTIES = 7;

     

        internal const uint CERT_QUERY_CONTENT_CERT = 1;

        internal const uint CERT_QUERY_CONTENT_CTL = 2;

        internal const uint CERT_QUERY_CONTENT_CRL = 3;

        internal const uint CERT_QUERY_OBJECT_FILE = 1;

        internal const uint CERT_QUERY_FORMAT_BINARY = 1;

        internal const uint CERT_QUERY_FORMAT_BASE64_ENCODED = 2;

        internal const uint CERT_QUERY_FORMAT_ASN_ASCII_HEX_ENCODED = 3;

     

        internal const uint CERT_QUERY_CONTENT_FLAG_CRL = (1 << (int)CERT_QUERY_CONTENT_CRL);

        internal const uint CERT_QUERY_CONTENT_FLAG_CTL = (1 << (int)CERT_QUERY_CONTENT_CTL);

        internal const uint CERT_QUERY_FORMAT_FLAG_BINARY = (1 << (int)CERT_QUERY_FORMAT_BINARY);

        internal const uint CERT_QUERY_FORMAT_FLAG_BASE64_ENCODED = (1 << (int)CERT_QUERY_FORMAT_BASE64_ENCODED);

        internal const uint CERT_QUERY_FORMAT_FLAG_ASN_ASCII_HEX_ENCODED = (1 << (int)CERT_QUERY_FORMAT_ASN_ASCII_HEX_ENCODED);

        internal const uint CERT_QUERY_FORMAT_FLAG_ALL =

           (CERT_QUERY_FORMAT_FLAG_BINARY |

           CERT_QUERY_FORMAT_FLAG_BASE64_ENCODED |

           CERT_QUERY_FORMAT_FLAG_ASN_ASCII_HEX_ENCODED);

    }

  • ExecutionContext, Impersonation and ASP.Net

    Late last week I was in contact with an internal team which had trouble with their ASP.Net application.  The issue was that when their webpage set impersonation to true, the impersonated identity did not flow across threads like it was supposed to.  This behavior was a change from last summer's Beta1.  So what's changed?  In Beta 2 the ExecutionContext will only flow impersonation tokens set in managed code.  ASP.Net does not do this, and therefore when the ExecutionContext was captured and placed on a new thread, the impersonation did not flow. 

    Performance and reliability were the main reasons for many of the changes to the ExecutionContext since Beta 1.  Luckily there's a very easy way around this - take a look at the code sample below

    using (WindowsIdentity.GetCurrent().Impersonate())
       {
       ThreadPool.QueueUserWorkItem(new WaitCallback(MyCallback), someObject);
       }
    In the ASP.Net scenario, there's already an impersonation token on the thread, but there's no associated managed object for it.  Calling Impersonate on the current WindowsIdentity object will ensure that the identity is flowed across to the new thread.  This is a bit of a "gotcha" - and I spent a couple hours trying to figure out why this was happening before I tried this solution - hopefully this will save someone the effort in the future.
  • ExecutionContext & WindowsIdentity.Name in new threads

    In Whidbey we've introduced a new feature called the ExecutionContext which flows a good bit of information across threads - among this information is the current impersonation token on a thread.  Now when your code creates a new thread via Thread.Start, ThreadPool, Timer, BeginInvoke, BeginRead/Write - the new thread will have an impersonation token placed on it.  Typically, WindowsIdentity.GetCurrent().Name returns the name of the current user, but in Whidbey there's a little trick to this - if a thread has an impersonation token on it, it will return the user name of the primary token, not the impersonation token.  There's an easy way to get around it with the following function:

      static string GetUserName()
       {
       NTAccount acct = WindowsIdentity.GetCurrent().User.Translate(typeof(NTAccount)) as NTAccount ;
       if( acct != null )
        return acct.ToString() ;
       return String.Empty ;
       }

    As you can see, GetCurrent() still returns the proper WindowsIdentity, but will need to convert it into an NTAccount to get the user name.  This code ends up looking pretty similar to what WindowsIdentity.User does!

    As far as ExecutionContext goes, this is pretty much the tip of the iceberg.  In the coming days I will talk more about what it does, how to change its settings and also how you can use it with the new Hosting APIs.


© 2008 Microsoft Corporation. All rights reserved. Terms of Use  |  Trademarks  |  Privacy Statement
Microsoft
Page view tracker