Using P/Invoke and CertFindCertificateInStore

G

Guest

I'm trying to invoke CertFindCertificateInStore to find all certificates that
have the Code Signing enhanced key attribute. I'd just like to find 1 cert
(not even all at this point), however, I keep coming up with the error - "
Can not marshal parameter #5: Invalid managed/unmanaged type combination
(this value type must be paired with Struct)."

The structs are defined as:
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
public struct CRYPT_OID_INFO
{
public uint cbSize;
[MarshalAs(UnmanagedType.LPStr)] public String pszOID;
[MarshalAs(UnmanagedType.LPWStr)]public String pwszName;
public uint dwGroupID;
public uint dwValue;
public int cbData; //ExtraInfo blob
public IntPtr pbData;
}

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
public struct _CTL_USAGE
{
public int cUsageIdentifier;
[MarshalAs(UnmanagedType.LPArray)]
public CRYPT_OID_INFO[] rgpszUseageIdentifier;
}

The CertFindCertificateInStore is defined as:

[DllImport("crypt32.dll", SetLastError=true)]
public static extern IntPtr CertFindCertificateInStore(
IntPtr hCertStore,
uint dwCertEncodingType,
uint dwFindFlags,
uint dwFindType,
[In, MarshalAs(UnmanagedType.LPStruct)]
WinCapi._CTL_USAGE pvFindPara,
IntPtr pPrevCertCntxt) ;

The code essentially does the following:

Gets a handle to the store by calling CertOpenSystemStore.
Gets a handle for the OID structure for CodeSigning by calling
CryptFindOIDInfo.

I then build a structure object of _CTL_USAGE and assign cUsageIdentifier to
1 and rgpszUseageIdentifier to an array of CRYPT_OID_INFO structs. The array
consists of one element (the located OID structure).

I then attempt to call CertFindCertificateInStore which fails the the
mentioned error. Any ideas? The following is the snippet of code that I just
desciibed:

IntPtr hOID = OIDName(szOID_PKIX_KP_CODE_SIGNING);
if(hOID != IntPtr.Zero)
{
WinCapi.CRYPT_OID_INFO coiInfo =
(WinCapi.CRYPT_OID_INFO)Marshal.PtrToStructure(hOID,typeof(WinCapi.CRYPT_OID_INFO));

test.cUsageIdentifier = 1;
test.rgpszUseageIdentifier = new WinCapi.CRYPT_OID_INFO[1];
test.rgpszUseageIdentifier[0] = coiInfo;

if(hSysStore != IntPtr.Zero)
{
hCertCntxt=WinCapi.CertFindCertificateInStore(
hSysStore,
MY_ENCODING_TYPE,
0,
CERT_FIND_ENHKEY_USAGE,
test,
IntPtr.Zero) ;
 
M

Mattias Sjögren

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
public struct _CTL_USAGE
{
public int cUsageIdentifier;
[MarshalAs(UnmanagedType.LPArray)]
public CRYPT_OID_INFO[] rgpszUseageIdentifier;
}

Having a nested struct array inside another array isn't supported by
the .NET marshaler. But I'm not sure why you declared it as a
CRYPT_OID_INFO[] to begin with, MSDN says it should be a string array.

[DllImport("crypt32.dll", SetLastError=true)]
public static extern IntPtr CertFindCertificateInStore(
IntPtr hCertStore,
uint dwCertEncodingType,
uint dwFindFlags,
uint dwFindType,
[In, MarshalAs(UnmanagedType.LPStruct)]
WinCapi._CTL_USAGE pvFindPara,
IntPtr pPrevCertCntxt) ;

Rhe pvFindPara parameter should be "ref _CTl_USAGE", and you can get
rid of the MarshalAs attribute.


Mattias
 
G

Guest

Mattias,

Thanks for the response. I'm not sure why I changed the string array to an
array of OID structs. I've changed it back so that it's listed as:

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
public struct _CTL_USAGE
{
public int cUsageIdentifier;
[MarshalAs(UnmanagedType.LPArray)]
public string[] rgpszUseageIdentifier;
}

I've also changed removed the [In,MarshalAs... in the signature of
CertFindCertificateInStore and added the ref keyword.

I setup a _CTL_USAGE struct to assign the following:

test.cUsageIdentifier = 1;
test.rgpszUseageIdentifier = new string[1];
test.rgpszUseageIdentifier[0] = szOID_PKIX_KP_CODE_SIGNING; //OID For Code
Signing

However, when I make the call to CertFindCertificateInStore:

hCertCntxt=WinCapi.CertFindCertificateInStore(
hSysStore,
MY_ENCODING_TYPE,
0,
CERT_FIND_ENHKEY_USAGE,
ref test,
IntPtr.Zero) ;

I end up with a new Type.LoadException error message of: "Can not marshal
field rgpszUseageIdentifier of type _CTL_USAGE: This type can not be
marshaled as a structure field."

Regards,
Charles



Mattias Sjögren said:
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
public struct _CTL_USAGE
{
public int cUsageIdentifier;
[MarshalAs(UnmanagedType.LPArray)]
public CRYPT_OID_INFO[] rgpszUseageIdentifier;
}

Having a nested struct array inside another array isn't supported by
the .NET marshaler. But I'm not sure why you declared it as a
CRYPT_OID_INFO[] to begin with, MSDN says it should be a string array.

[DllImport("crypt32.dll", SetLastError=true)]
public static extern IntPtr CertFindCertificateInStore(
IntPtr hCertStore,
uint dwCertEncodingType,
uint dwFindFlags,
uint dwFindType,
[In, MarshalAs(UnmanagedType.LPStruct)]
WinCapi._CTL_USAGE pvFindPara,
IntPtr pPrevCertCntxt) ;

Rhe pvFindPara parameter should be "ref _CTl_USAGE", and you can get
rid of the MarshalAs attribute.


Mattias
 
G

Guest

Ok, I got it working. But I am extremely confused as to why this works.

On the _CTL_USAGE structure I removed the [MarshalAs(UnmanagedType.LPArray)].

When I initialize the test structure as follows:

WinCapi._CTL_USAGE test = new WinCapi._CTL_USAGE();
test.cUsageIdentifier = 0;
test.rgpszUseageIdentifier = new string[1];
test.rgpszUseageIdentifier[0] = szOID_PKIX_KP_CODE_SIGNING;

I then make the call to WinCapi.CertFindCertificateInStore passing test. I
successfully find the certificate I was looking for.

Now what I don't understand is that why I have to set the
test.cUsageIdentifier to 0 and not 1. The c_UsageIdentifier description is
"Number of elements in rgpszUsageIdentifier array." Clearly I have 1 element,
not 0.

Mattias Sjögren said:
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
public struct _CTL_USAGE
{
public int cUsageIdentifier;
[MarshalAs(UnmanagedType.LPArray)]
public CRYPT_OID_INFO[] rgpszUseageIdentifier;
}

Having a nested struct array inside another array isn't supported by
the .NET marshaler. But I'm not sure why you declared it as a
CRYPT_OID_INFO[] to begin with, MSDN says it should be a string array.

[DllImport("crypt32.dll", SetLastError=true)]
public static extern IntPtr CertFindCertificateInStore(
IntPtr hCertStore,
uint dwCertEncodingType,
uint dwFindFlags,
uint dwFindType,
[In, MarshalAs(UnmanagedType.LPStruct)]
WinCapi._CTL_USAGE pvFindPara,
IntPtr pPrevCertCntxt) ;

Rhe pvFindPara parameter should be "ref _CTl_USAGE", and you can get
rid of the MarshalAs attribute.


Mattias
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top