0

First, I apologize if my title isn't technically accurate. I'm not sure exactly what's happening and that describes it about as well as anything.

I am attempting to decode an SSL certificate for a specific use in a program. I p/invoked all of the necessary CryptoAPI functions and structs and in debug mode, everything is working as expected. However, when the program is run in release mode as a service (that's the purpose of this program), I get an access violation when attempting to decode the extensions. In order to make extensions easier to decode, I have created a generic class that can be used to represent any extension. This class contains an object of type TStruct that represents the underlying structure that the extension is based on. It has an EncryptedValue field of type byte[] that will encrypt/decrypt the extension data and place it in the structure as appropriate. The base class is as follows:

public abstract class ExtensionBase<TStruct> : IExtensionBase where TStruct : new()
{
    //The underlying struct
    protected TStruct objectData = new TStruct();

    //The identifier of the struct, ie: szOID_BASIC_CONSTRAINTS
    public string Identifier { get; protected set; }
    //An enum representing the structure type, ie: X509_BASIC_CONSTRAINTS
    public CertStructType StructureType { get; protected set; }
    //Determines if the extension is critical
    public bool IsCritical { get; protected set; }

    //Overridden in any child class to determine if that extension actually contains
    //data that should be encoded
    public abstract bool HasData { get; }

    //Encrypts/decrypts the data from/to the underlying structure
    public virtual byte[] EncryptedValue
    {
        get
        {
            uint encodedSize = 0;

            //PinnedHandle is a class that I wrote to wrap a GCHandle.
            //It has an implicit cast to IntPtr that returns GCHandle.AddrOfPinnedObject
            //The finalizer of the class releases the GCHandle if it is a valid handle
            IntPtr dataPtr = new PinnedHandle(objectData);

            byte[] retVal = null;

            if (StructureType != CertStructType.None)
            {
                if (!Crypt32.CryptEncodeObjectEx((uint)CertEncoding.X509Asn, 
                                                 (uint)StructureType, 
                                                 dataPtr, 
                                                 0,
                                                 IntPtr.Zero, 
                                                 null, 
                                                 ref encodedSize))
                    throw new Win32Exception();

                retVal = new byte[encodedSize];

                if (!Crypt32.CryptEncodeObjectEx((uint)CertEncoding.X509Asn, 
                                                 (uint)StructureType, 
                                                 dataPtr, 
                                                 0, 
                                                 IntPtr.Zero, 
                                                 retVal, 
                                                 ref encodedSize))
                    throw new Win32Exception();
            }
            else
            {
                if (!Crypt32.CryptEncodeObjectEx((uint)CertEncoding.X509Asn, 
                                                 Identifier, 
                                                 dataPtr, 
                                                 0, 
                                                 IntPtr.Zero, 
                                                 null, 
                                                 ref encodedSize))
                    throw new Win32Exception();

                retVal = new byte[encodedSize];

                if (!Crypt32.CryptEncodeObjectEx((uint)CertEncoding.X509Asn, 
                                                  Identifier, 
                                                  dataPtr, 
                                                  0, 
                                                  IntPtr.Zero, 
                                                  retVal, 
                                                  ref encodedSize))
                    throw new Win32Exception();
            }

            return retVal;
        }
        set
        {
            uint decodedSize = 0;

            IntPtr decodedData = IntPtr.Zero;
            if(StructureType != CertStructType.None)
                decodedData = Crypt32.CryptDecodeObjectEx(StructureType, value);
            else
                decodedData = Crypt32.CryptDecodeObjectEx(Identifier, value);

            TStruct data = (TStruct)Marshal.PtrToStructure(decodedData, typeof(TStruct));

            objectData = data;

            Marshal.FreeHGlobal(decodedData);
        }
    }

    public ExtensionBase(string id)
    {
        Identifier = id;
        StructureType = CertStructType.None;
    }

    public ExtensionBase(string id, CertStructType structType)
    {
        Identifier = id;
        StructureType = structType;
    }
}

One of the child classes that is giving me problems is the CertKeyUsage class which uses a CRYPT_BIT_BLOB struct to represent its data:

public class CertKeyUsage : ExtensionBase<CertKeyUsageFlags, CRYPT_BIT_BLOB>
{
    public override bool HasData
    {
        get { return Value != CertKeyUsageFlags.None; }
    }

    public override unsafe byte[] EncryptedValue
    {
        get
        {
            CertKeyUsageFlags flags = Value;
            objectData.cbData = 2;
            objectData.cUnusedBits = 0;
            objectData.pbData = new IntPtr(&flags);

            return base.EncryptedValue;
        }
        set
        {
            try
            {
                //The following code was taken directly from Microsoft's implementation
                //of X509Certificate
                base.EncryptedValue = value;
                if (objectData.cbData > 4)
                    objectData.cbData = 4;

                byte[] keyUsage = new byte[4];

                //This if statement returns true, and the following Marshal.Copy statement
                //is where the program crashes with the Access Violation.  As it is an unmanaged
                //exception, the try/catch block doesn't do anything and the app dies
                if (objectData.pbData != IntPtr.Zero)
                    Marshal.Copy(objectData.pbData, keyUsage, 0, (int) objectData.cbData);
                Value = (CertKeyUsageFlags) BitConverter.ToUInt32(keyUsage, 0);
            }
            catch
            {

            }
        }
    }

    public CertKeyUsage()
        : base(CertOid.szOID_KEY_USAGE, CertStructType.X509KeyUsage)
    {
        IsCritical = true;
    }
}

What about this code would be different from debug to release that would cause an access violation at the spot noted above? What could I do differently to get it working properly. This particular extension isn't critical and I could just skip over it, but after commenting out the code above that is causing the exception, another of the extensions will crash. I can't comment out all extensions, and the fact that it's moving to a different location tells me that there is some underlying problem with my code that I'm missing. Any help or suggestions would be greatly appreciated.

These are the p/invoked functions I'm calling:

    [DllImport("crypt32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
    public static extern bool CryptEncodeObjectEx(uint certEncodingType,
                                                  [MarshalAs(UnmanagedType.LPStr)]
                                                  string structType, 
                                                  IntPtr structInfo, 
                                                  uint flags, 
                                                  IntPtr encodePara, 
                                                  byte[] encodedData, 
                                                  [In, Out] ref uint encodedSize);

    [DllImport("crypt32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
    public static extern bool CryptEncodeObjectEx(uint certEncodingType, 
                                                  uint structType, 
                                                  IntPtr structInfo, 
                                                  uint flags, 
                                                  IntPtr encodePara, 
                                                  byte[] encodedData,
                                                  [In, Out] ref uint encodedSize);

    [DllImport("crypt32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
    public static extern bool CryptDecodeObjectEx(uint certEncodingType, 
                                                  [MarshalAs(UnmanagedType.LPStr)]
                                                  string structType, 
                                                  byte[] encodedData, 
                                                  uint encodedDataSize,
                                                  uint flags,
                                                  IntPtr encodePara, 
                                                  IntPtr decodedData, 
                                                  [In, Out] ref uint decodedDataSize);

    [DllImport("crypt32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
    public static extern bool CryptDecodeObjectEx(uint certEncodingType, 
                                                  uint structType, 
                                                  byte[] encodedData, 
                                                  uint encodedDataSize, 
                                                  uint flags, 
                                                  IntPtr encodePara, 
                                                  IntPtr decodedData,
                                                  [In, Out] ref uint decodedDataSize);

And the function that wraps CryptDecodeObjectEx:

    public static IntPtr CryptDecodeObjectEx(string structType, byte[] encodedData)
    {
        uint encodedSize = (uint)encodedData.Length;
        uint decodedSize = 0;
        IntPtr decodedData = IntPtr.Zero;

        if (!CryptDecodeObjectEx((uint)CertEncoding.X509Asn,
                                 structType,
                                 encodedData,
                                 encodedSize, 
                                 0, 
                                 IntPtr.Zero, 
                                 decodedData, 
                                 ref decodedSize))
            throw new Win32Exception();

        decodedData = Marshal.AllocHGlobal((int)decodedSize);

        if (!CryptDecodeObjectEx((uint)CertEncoding.X509Asn, 
                                  structType, 
                                  encodedData, 
                                  encodedSize, 
                                  0, 
                                  IntPtr.Zero, 
                                  decodedData, 
                                  ref decodedSize))
            throw new Win32Exception();

        return decodedData;
    }

    public static IntPtr CryptDecodeObjectEx(uint structType, byte[] encodedData)
    {
        uint encodedSize = (uint)encodedData.Length;
        uint decodedSize = 0;
        IntPtr decodedData = IntPtr.Zero;

        if (!CryptDecodeObjectEx((uint)CertEncoding.X509Asn, 
                                  structType, 
                                  encodedData, 
                                  encodedSize, 
                                  0, 
                                  IntPtr.Zero, 
                                  decodedData, 
                                  ref decodedSize))
            throw new Win32Exception();

        decodedData = Marshal.AllocHGlobal((int)decodedSize);

        if (!CryptDecodeObjectEx((uint)CertEncoding.X509Asn, 
                                  structType, 
                                  encodedData, 
                                  encodedSize, 
                                  0, 
                                  IntPtr.Zero, 
                                  decodedData, 
                                  ref decodedSize))
            throw new Win32Exception();

        return decodedData;
    }

On a whim, I decided to compile the program as X86 rather than AnyCPU and everything works as expected. This leads me to believe that the problem is actually in this section of code:

    public static IntPtr Next<T>(this IntPtr val)
    {
        T retVal = (T)Marshal.PtrToStructure(val, typeof(T));

        if (Environment.Is64BitProcess)
            return (IntPtr)((long)val + Marshal.SizeOf(retVal));

        return (IntPtr)((int)val + Marshal.SizeOf(retVal));
    }

This is the code that I use to enumerate through a pointer to an array of structures. The structure type is passed in and an IntPtr to the next structure in the array should be returned. The 32 bit version is working just as it should, but apparently my logic in the 64 bit version is somewhat lacking.

Dirk Dastardly
  • 1,017
  • 2
  • 12
  • 23
  • Where are your P/Invoke signatures? Most likely issue is that they're close, but not quite correct. – Damien_The_Unbeliever Oct 02 '14 at 12:48
  • 1
    Why you don't use .NET Security classes, and instead to PInvoke from crypt32? – rufanov Oct 02 '14 at 12:51
  • Basically I've written my own certificate authority. The code I'm referencing above is unwrapping a root certificate that is used to sign a client certificate. Since there's no functionality in the .Net classes for creating certs and signing certs, I needed to fall back on the CryptoAPI. – Dirk Dastardly Oct 02 '14 at 12:54
  • 1
    @DrewBurchett, You can use BouncyCastle package to do this. – rufanov Oct 02 '14 at 13:00
  • I had originally tried using BouncyCastle, but something about it (and I don't remember what it was) didn't work well for my specific implementation. At this point, I really have too much time invested to be looking at reinventing the entire thing and need to just concentrate on getting what I have working. – Dirk Dastardly Oct 02 '14 at 13:18
  • 1
    Your pinvoke declarations are bad. Ensure that your Release build can only run in 32-bit mode. Project + Properties, Build tab. Do keep in mind that the settings there are distinct for the Debug and Release configurations. – Hans Passant Oct 02 '14 at 13:24
  • The horizontal scroll bars were enough to put me off – David Heffernan Oct 02 '14 at 13:34
  • If you put together a simple test program that can be copy+pasted to VisualStudio, I'll tell you what's wrong ;). – Erti-Chris Eelmaa Oct 02 '14 at 13:53
  • I'd be glad to take you up on that, but I don't think it will work. It's only failing under a specific set of circumstances. When it's running in release mode AND as a service. Plus pulling the dozens of classes and structs out of the dll they're currently in and placing them into a test program would be a monumental task. – Dirk Dastardly Oct 02 '14 at 14:02
  • Hint: it's probably the way you pInvoke things. It does not seem to right to me how you treat the structType. The right way would be to take IntPtr there and pass new IntPtr(14) for X509_KEY_USAGE, I believe. Unless I am not mistaken, new IntPtr(14) == (LPCSTR)14 – Erti-Chris Eelmaa Oct 02 '14 at 14:09
  • That sort of worked. It did allow that specific extension to decode correctly. Now the problem has moved to another extension. – Dirk Dastardly Oct 02 '14 at 15:05

0 Answers0