CryptoAPI from C# (long code part)

G

Gionni

I'm trying to sign a message using CrytpoAPI (I can't use CAPICOM
because I have Biztalk could verify this sign).
I'm traslating the example in MSDN library at
http://msdn.microsoft.com/library/d...message_and_verifying_a_message_signature.asp

I tried this:

// CERT_CONTEXT
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
private struct CERT_CONTEXT
{
public int dwCertEncodingType;
public IntPtr pbCertEncoded;
public int cbCertEncoded;
public IntPtr pCertInfo;
public IntPtr hCertStore;
}

// CRYPT_ALGORITHM_IDENTIFIER
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
private struct CRYPT_ALGORITHM_IDENTIFIER
{
public IntPtr pszObjId;
public CRYPTOAPI_BLOB Parameters;
}

// CRYPTOAPI_BLOB
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
private struct CRYPTOAPI_BLOB
{
public int cbData;
public IntPtr pbData;
}

// CRYPTO_SIGN_MESSAGE_PARA
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
private struct CRYPT_SIGN_MESSAGE_PARA
{
public UInt32 cbSize;
public UInt32 dwMsgEncodingType;
public CERT_CONTEXT pSigningCert;
public CRYPT_ALGORITHM_IDENTIFIER HashAlgorithm;
public IntPtr pvHashAuxInfo;
public UInt32 cMsgCert;
public IntPtr rgpMsgCert;
public UInt32 cMsgCrl;
public IntPtr rgpMsgCrl;
public UInt32 cAuthAttr;
public IntPtr rgAuthAttr;
public UInt32 cUnauthAttr;
public IntPtr rgUnauthAttr;
public UInt32 dwFlags;
public UInt32 dwInnerContentType;
public CRYPT_ALGORITHM_IDENTIFIER HashEncryptionAlgorithm;
public IntPtr pvHashEncryptionAuxInfo;
}

// CERT_PUBLIC_KEY_INFO
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
private struct CERT_PUBLIC_KEY_INFO
{
public CRYPT_ALGORITHM_IDENTIFIER Algorithm;
public CRYPTOAPI_BLOB PublicKey;
}

// PCERT_EXTENSION
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
private struct PCERT_EXTENSION
{
public IntPtr pszObjId;
public bool fCritical;
public CRYPTOAPI_BLOB Value;
}

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
private struct CERT_INFO
{
public int dwVersion;
public CRYPTOAPI_BLOB SerialNumber;
public CRYPT_ALGORITHM_IDENTIFIER SignatureAlgorithm;
public CRYPTOAPI_BLOB Issuer;
public FILETIME NotBefore;
public FILETIME NotAfter;
public CRYPTOAPI_BLOB Subject;
public CERT_PUBLIC_KEY_INFO SubjectPublicKeyInfo;
public CRYPTOAPI_BLOB IssuerUniqueId;
public CRYPTOAPI_BLOB SubjectUniqueId;
public int cExtension;
public PCERT_EXTENSION rgExtension;
}
[DllImport("Crypt32.dll", EntryPoint="CertOpenStore")]
private static extern IntPtr CertOpenStore
(
IntPtr lpszStoreProvider,
UInt32 dwEncodingType,
IntPtr hCryptProv,
UInt32 dwFlags,
byte[] pvPara
);

[DllImport("Crypt32.dll", EntryPoint="CertFindCertificateInStore")]
public static extern IntPtr CertFindCertificateInStore
(
IntPtr hCertStore,
UInt32 dwCertEncodingType,
UInt32 dwFindFlags,
UInt32 dwFindType,
IntPtr pvFindPara,
IntPtr pPrevCertContext
);

[DllImport("Crypt32.dll", EntryPoint="CryptSignMessage")]
private static extern bool CryptSignMessage
(
IntPtr pSignPara,
bool fDetachedSignature,
UInt32 cToBeSigned,
string[] rgpbToBeSigned, //IntPtr rgpbToBeSigned,
int[] rgcbToBeSigned, //IntPtr rgcbToBeSigned,
IntPtr pbSignedBlob,
ref UInt32 pcbSignedBlob
);

public static void Test ()
{
const string CERT_STORE_NAME= "MY";
const int CERT_STORE_PROV_SYSTEM = 10;
const int CERT_SYSTEM_STORE_CURRENT_USER = 0x1 << 16;
const int MY_TYPE = (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING);
const int CERT_FIND_SUBJECT_STR = 458759;
const string certId = "ncd ncd";
const string szOID_RSA_MD5 = "1.2.840.113549.2.5";
IntPtr hStoreHandle;
hStoreHandle = CertOpenStore ((IntPtr) CERT_STORE_PROV_SYSTEM, 0,
IntPtr.Zero, CERT_SYSTEM_STORE_CURRENT_USER,
Encoding.Unicode.GetBytes(CERT_STORE_NAME));

IntPtr pCtx = IntPtr.Zero;
IntPtr pCertContext;
CERT_CONTEXT pSignerCert;
CERT_INFO pCertInfo;
CRYPTOAPI_BLOB subject;

IntPtr SIGNER_NAME = Marshal.StringToBSTR (certId);
if (hStoreHandle != IntPtr.Zero)
{
pCertContext = CertFindCertificateInStore(hStoreHandle, MY_TYPE, 0,
CERT_FIND_SUBJECT_STR, SIGNER_NAME, pCtx);
pSignerCert = (CERT_CONTEXT)
Marshal.PtrToStructure(pCertContext,Type.GetType("TestCryptoAPI.Class1+CERT_CONTEXT"));
CRYPT_SIGN_MESSAGE_PARA sigParams = new CRYPT_SIGN_MESSAGE_PARA();
sigParams.cbSize = (UInt32) Marshal.SizeOf
(Type.GetType("TestCryptoAPI.Class1+CRYPT_SIGN_MESSAGE_PARA"));
sigParams.dwMsgEncodingType = MY_TYPE;
sigParams.pSigningCert = pSignerCert;
Marshal.StringToBSTR (szOID_PKCS_7);
sigParams.HashAlgorithm.pszObjId = Marshal.StringToBSTR
(szOID_RSA_MD5);
sigParams.HashAlgorithm.Parameters.cbData = 0;
sigParams.HashAlgorithm.Parameters.pbData = IntPtr.Zero;
sigParams.pvHashAuxInfo = IntPtr.Zero;
sigParams.cMsgCert = 1;
sigParams.rgpMsgCert =
Marshal.AllocHGlobal(Marshal.SizeOf(Type.GetType("TestCryptoAPI.Class1+CERT_CONTEXT")));
Marshal.StructureToPtr (pSignerCert, sigParams.rgpMsgCert, false);
sigParams.cMsgCrl = 0;
sigParams.rgpMsgCrl = IntPtr.Zero;
sigParams.cAuthAttr = 0;
sigParams.rgAuthAttr = IntPtr.Zero;
sigParams.cUnauthAttr = 0;
sigParams.rgAuthAttr = IntPtr.Zero;
sigParams.dwFlags = 0;
sigParams.rgAuthAttr = IntPtr.Zero;
sigParams.dwInnerContentType = 0;
sigParams.pvHashEncryptionAuxInfo = IntPtr.Zero;

IntPtr pSigParams;
pSigParams = Marshal.AllocCoTaskMem(Marshal.SizeOf(Type.GetType("TestCryptoAPI.Class1+CRYPT_SIGN_MESSAGE_PARA")));
Marshal.StructureToPtr (sigParams, pSigParams, false);

byte[] bytePlainMessage = Encoding.Default.GetBytes("Test string to
sign");
char[] plainMessage = Encoding.Default.GetChars (bytePlainMessage);
IntPtr pPPlainMessage =
Marshal.AllocCoTaskMem(Marshal.SizeOf(Type.GetType
("System.IntPtr")));
int messageSize = plainMessage.Length;
IntPtr pPlainMessage = Marshal.AllocCoTaskMem (messageSize + 1);
Marshal.Copy (plainMessage, 0, pPlainMessage, messageSize);
Marshal.WriteIntPtr (pPPlainMessage, pPlainMessage);
char[] test = new char[messageSize];
Marshal.Copy (pPlainMessage, test, 0, messageSize);
IntPtr pMessageSize = Marshal.AllocCoTaskMem(4);
Marshal.WriteInt32 (pMessageSize, messageSize);
UInt32 signedMsgLen = 0;
try
{
if (CryptSignMessage (pSigParams, false, 1, pPPlainMessage,
pMessageSize, IntPtr.Zero, ref signedMsgLen))
}
catch (Exception ex)
{
Console.WriteLine (ex.Message);
}
}
else
{
return;
}
}


It fails at the CryptSignMessage with this error: Object reference not
set to an instance of an object.

Thanks in advance, Gionni
 
M

Mattias Sjögren

Gionni,
private struct CRYPT_SIGN_MESSAGE_PARA
{
public UInt32 cbSize;
public UInt32 dwMsgEncodingType;
public CERT_CONTEXT pSigningCert;

pSigningCert should be a pointer to a CERT_CONTEXT, i.e. IntPtr.

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
private struct PCERT_EXTENSION

The struct is actually just called CERT_EXTENSION. PCERT_EXTENSION
indicates a pointer to a CERT_EXTENSION...
public PCERT_EXTENSION rgExtension;

.... so this should also be an IntPtr.

[DllImport("Crypt32.dll", EntryPoint="CryptSignMessage")]
private static extern bool CryptSignMessage
(
IntPtr pSignPara,
bool fDetachedSignature,
UInt32 cToBeSigned,
string[] rgpbToBeSigned, //IntPtr rgpbToBeSigned,
int[] rgcbToBeSigned, //IntPtr rgcbToBeSigned,
IntPtr pbSignedBlob,
ref UInt32 pcbSignedBlob
);

You can save yourself some work by changing the pSignPara parameter
type to ref CRYPT_SIGN_MESSAGE_PARA and directly pass in your
sigParams variable.

pSignerCert = (CERT_CONTEXT)
Marshal.PtrToStructure(pCertContext,Type.GetType("TestCryptoAPI.Class1+CERT_CONTEXT"));

I suggest you use the typeof operator rather than Type.GetType where
possible

pSignerCert = (CERT_CONTEXT)
Marshal.PtrToStructure(pCertContext, typeof(CERT_CONTEXT));

IntPtr pSigParams;
pSigParams = Marshal.AllocCoTaskMem(Marshal.SizeOf(Type.GetType("TestCryptoAPI.Class1+CRYPT_SIGN_MESSAGE_PARA")));

You never seem to free the memory you allocate here and elsewhere in
the code.

byte[] bytePlainMessage = Encoding.Default.GetBytes("Test string to
sign");
char[] plainMessage = Encoding.Default.GetChars (bytePlainMessage);

These two steps are more easily done with

char[] plainMessage = "Test string to sign".ToCharArray();

Plus you don't have any data loss due to the encoding.

if (CryptSignMessage (pSigParams, false, 1, pPPlainMessage,
pMessageSize, IntPtr.Zero, ref signedMsgLen))

Your declaration of CryptSignMessage had the fourth parameter declared
as string[] but here you're passing it pPPlainMessage which is an
IntPtr. That shouldn't even compile.



Mattias
 
G

Gionni

Mattias, thanks for your suggestions.
I changed some definitions, but I still have some problems. I don't
how to translate some types, as you will see after.
You can save yourself some work by changing the pSignPara parameter
type to ref CRYPT_SIGN_MESSAGE_PARA and directly pass in your
sigParams variable.

I hope I have understood well . The definition now is this:

[DllImport("Crypt32.dll", EntryPoint="CryptSignMessage")]
private static extern bool CryptSignMessage
(
ref CRYPT_SIGN_MESSAGE_PARA pSignPara,
bool fDetachedSignature,
UInt32 cToBeSigned,
IntPtr rgpbToBeSigned,
IntPtr rgcbToBeSigned,
out IntPtr pbSignedBlob,
ref UInt32 pcbSignedBlob
);

Question: is there any general criterion to map the C types to the C#
ones?
Your declaration of CryptSignMessage had the fourth parameter declared
as string[] but here you're passing it pPPlainMessage which is an
IntPtr. That shouldn't even compile.

Yes, you are right. Copy and paste error.
Now with your suggestion the code is this (I snipped the definitions):

public static void test ()
{
const string CERT_STORE_NAME= "MY";
const int CERT_STORE_PROV_SYSTEM = 10;
const int CERT_SYSTEM_STORE_CURRENT_USER = 0x1 << 16;
const int MY_TYPE = (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING);
const int CERT_FIND_SUBJECT_STR = 458759;
const string certId = "ncd ncd";
const string szOID_RSA_MD5 = "1.2.840.113549.2.5";

IntPtr hStoreHandle;
hStoreHandle = CertOpenStore ((IntPtr) CERT_STORE_PROV_SYSTEM, 0,
IntPtr.Zero, CERT_SYSTEM_STORE_CURRENT_USER,
Encoding.Unicode.GetBytes(CERT_STORE_NAME));

IntPtr pCertContext;
int certContextSize = Marshal.SizeOf (typeof(CERT_CONTEXT));
int cryptSignMessageParaSize = Marshal.SizeOf (typeof
CRYPT_SIGN_MESSAGE_PARA));
CERT_CONTEXT signerCert;
CERT_INFO pCertInfo;
CRYPTOAPI_BLOB subject;
CRYPT_SIGN_MESSAGE_PARA sigParams;

IntPtr SIGNER_NAME = Marshal.StringToBSTR (certId);
if (hStoreHandle != IntPtr.Zero)
{
pCertContext = CertFindCertificateInStore(hStoreHandle, MY_TYPE, 0,
CERT_FIND_SUBJECT_STR, SIGNER_NAME, IntPtr.Zero);
signerCert = (CERT_CONTEXT) Marshal.PtrToStructure(pCertContext,
typeof(CERT_CONTEXT));

sigParams = new CRYPT_SIGN_MESSAGE_PARA();
sigParams.cbSize = (UInt32) cryptSignMessageParaSize;
sigParams.dwMsgEncodingType = MY_TYPE;
IntPtr pSignerCert = Marshal.AllocCoTaskMem (certContextSize);
Marshal.StructureToPtr (signerCert, pSignerCert, true);
IntPtr pPSignerCert =
Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(IntPtr)));
Marshal.WriteIntPtr (pPSignerCert, pSignerCert);
sigParams.pSigningCert = pPSignerCert;
sigParams.HashAlgorithm.pszObjId = Marshal.StringToBSTR
(szOID_RSA_MD5);
sigParams.HashAlgorithm.Parameters.cbData = 0;
sigParams.cMsgCert = 1;
sigParams.rgpMsgCert = Marshal.AllocCoTaskMem (certContextSize);
Marshal.StructureToPtr (signerCert, sigParams.rgpMsgCert, true);
sigParams.cAuthAttr = 0;
sigParams.dwInnerContentType = 0;
sigParams.cMsgCrl = 0;
sigParams.cUnauthAttr = 0;
sigParams.dwFlags = 0;
sigParams.pvHashAuxInfo = IntPtr.Zero;
sigParams.rgAuthAttr = IntPtr.Zero;

string testString = "Test string to sign";
char[] plainMessage = testString.ToCharArray ();
IntPtr pPPlainMessage =
Marshal.AllocCoTaskMem(Marshal.SizeOf(Type.GetType
("System.IntPtr")));
int messageSize = plainMessage.Length;
IntPtr pPlainMessage = Marshal.AllocCoTaskMem (messageSize + 1);
Marshal.Copy (plainMessage, 0, pPlainMessage, messageSize);
Marshal.WriteIntPtr (pPPlainMessage, pPlainMessage);
IntPtr pMessageSize = Marshal.AllocCoTaskMem(4);
Marshal.WriteInt32 (pMessageSize, messageSize);

IntPtr pSignedMsg = new IntPtr ();
UInt32 signedMsgLen = 0;
try
{
if (CryptSignMessage (ref sigParams, false, 1, pPPlainMessage,
pMessageSize, out pSignedMsg, ref signedMsgLen))
{
Marshal.FreeCoTaskMem (pPPlainMessage);
Marshal.FreeCoTaskMem (pPlainMessage);
Marshal.FreeCoTaskMem (pMessageSize);
Console.WriteLine ("Message signed");
}
else
Console.WriteLine ("Error in signing");
}
catch (Exception ex)
{
Console.WriteLine (ex.Message);
}
}
else
{
return;
}
}

It fails again with the same error: Object reference not set to an
instance of an object.

Thanks again, Gionni
 
M

Mattias Sjögren

I hope I have understood well . The definition now is this:

[DllImport("Crypt32.dll", EntryPoint="CryptSignMessage")]
private static extern bool CryptSignMessage
(
ref CRYPT_SIGN_MESSAGE_PARA pSignPara,
bool fDetachedSignature,
UInt32 cToBeSigned,
IntPtr rgpbToBeSigned,
IntPtr rgcbToBeSigned,
out IntPtr pbSignedBlob,
ref UInt32 pcbSignedBlob
);


Yes, that's pretty much what I had in mind. But I see you changed some
other parameter types as well. pbSignedBlob should be an IntPtr passed
by value like you had it before. You could also use a byte array to
put the output data into. rgpbToBeSigned and rgcbToBeSigned were also
better before when declared as arrays. I think the best declaration
would be

private static extern bool CryptSignMessage
(
ref CRYPT_SIGN_MESSAGE_PARA pSignPara,
bool fDetachedSignature,
UInt32 cToBeSigned,
string[] rgpbToBeSigned,
int[] rgcbToBeSigned,
byte[] IntPtr pbSignedBlob,
ref UInt32 pcbSignedBlob
);

And then you should be able to call it like this

string[] testStrings = {"Test string to sign"};
int[] sizes = {testStrings[0].Length};
int outBufferSize = /* some value */;
byte[] outputBuffer = new byte[outBufferSize];
if (CryptSignMessage (ref sigParams, false, 1, testStrings, sizes,
outputBuffer, ref outBufferSize))

and save yourself all the manual marshaling work.

Question: is there any general criterion to map the C types to the C#
ones?

For simple types yes, but for more complicated situations it takes
some experience to get it right. It's hard to summarize here.



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