1

I was following this article (in it there is a link to a .cs file at the bottom of the page) to generate a self-signed X509Certificate2. The code in the article works but now I want to extend it. I am trying to pass the optional argument, _In_opt_ PCRYPT_ALGORITHM_IDENTIFIER pSignatureAlgorithm, into CertCreateSelfSignCertificate.

I have created this structure for it:

struct CryptoApiBlob
{
    public Int32 cbData;
    public IntPtr pbData;
}

struct CryptAlgorithmIdentifier {
    public String pszObjId;
    public CryptoApiBlob Parameters;
}

The code I am trying to use to create it is:

CryptAlgorithmIdentifier algorithm = new CryptAlgorithmIdentifier { pszObjId = "szOID_NIST_AES256_CBC", Parameters = new CryptoApiBlob { cbData = 0 } };
algorithmPointer = Marshal.AllocHGlobal(Marshal.SizeOf(algorithm));
Marshal.StructureToPtr(algorithm, algorithmPointer, false);

I then pass the algorithmPointer into the method.

I get this error when I try to pass it into CertCreateSelfSignCertificate:

An unhandled exception of type 'System.Runtime.InteropServices.COMException' occurred in mscorlib.dll

Additional information: ASN1 bad arguments to function call. (Exception from HRESULT: 0x80093109)

Does anyone happen to know why this happens, or can see any problems with the way I've defined my structure or allocated it in memory?

Alexandru
  • 12,264
  • 17
  • 113
  • 208
  • `COMException`? There's no need to invoke COM through P/Invoke. .NET integrates with COM quite easily. But at first glance, `string`s are tricky to interop, and you're not showing enough to really help much. Oh, and you don't need any P/Invokes to handle this anyway - .NET has everything you need. See http://stackoverflow.com/questions/22230745/generate-self-signed-certificate-on-the-fly for example. – Luaan Jun 10 '15 at 12:54
  • @Luaan .NET does self-signed certificates? I'm sorry but the example you posted uses a 3rd party API called Bouncy Castle. I don't plan on using any 3rd party APIs. – Alexandru Jun 10 '15 at 12:57
  • 1
    Uh, my bad - see this one instead, directly using COM - http://stackoverflow.com/questions/13806299/how-to-create-a-self-signed-certificate-using-c?lq=1 It's still possible to do everything in pure .NET, but COM works just fine. – Luaan Jun 10 '15 at 13:02
  • @Luaan That helps a lot, thanks! – Alexandru Jun 10 '15 at 13:12
  • PS the COM exception comes from `Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());` after the call fails. – Alexandru Jun 10 '15 at 13:13
  • 2
    Yeah, that's just the real error-location being hidden by exception-less code. But it does make it obvious that the method you're trying to P/Invoke is just a C wrapper for the underlying COM functionality. Just stick with COM - .NET is basically COM 2.0 :)) – Luaan Jun 10 '15 at 13:17
  • Yeah, might as well use the `CX509 CertificateRequest Certificate` COM object from .NET. – Alexandru Jun 10 '15 at 13:30
  • @Luaan Actually, that link you posted, I took a deeper look and it seems to generate those objects from `CERTENROLLLib`, which is basically either `C:\Windows\System32\CertEnroll.dll` or `C:\Windows\SysWOW64\CertEnroll.dll`. Now, in production code, I'm unsure if you can legally deploy this DLL yourself or if it will even work due to dependencies. Secondly I don't think it'll be able to find this DLL unless you put in the extra wrench work to find it across other platforms, so, all this being said, I'd still play it safe and P/Invoke the C++ wrappers in `crypt32.dll`. – Alexandru Jun 10 '15 at 23:25
  • You don't have to deploy it - it's on every Windows version. And `crypt32.dll` needs it anyway :) Playing it safe is using COM :D – Luaan Jun 11 '15 at 07:11
  • The COM will be used no matter what, but the point I'm trying to make is that P/Invoking makes deploying easier. Using that library directly will cause some problems in the long-run when the resolver tries to find the DLL you need for it. – Alexandru Jun 11 '15 at 11:12

2 Answers2

1

As @Luaan noted, strings can be tricky to marshal correctly in p/invoke, it's often easiest to avoid p/invoke interop when you can. However I was still curious what was going wrong here.

From the MSDN docs on PCRYPT_ALGORITHM_IDENTIFIER it looks as though you should pass in the actual OID of the algorithm "2.16.840.1.101.3.4.1.42" in this case. The szOID_NIST_AES256_CBC that is in the list there is only the C/C++ identifier (or macro) that expands to said OID string.

Captain Coder
  • 798
  • 5
  • 12
  • I tried that ealier, but the problem is that I then get the following error message back: `Invalid algorithm specified. (Exception from HRESULT: 0x80090008)`. – Alexandru Jun 10 '15 at 14:24
  • 2
    Sure, because there is no place for "szOID_NIST_AES256_CBC" algorithm. There should be something like OID values from sha1, sha2 families. – Crypt32 Jun 10 '15 at 15:28
  • @CryptoGuy I had luck with, for example, `1.2.840.113549.1.1.13` (szOID_RSA_SHA512RSA). Could you please elaborate on why it requires the use of SHA families? – Alexandru Jun 10 '15 at 19:48
  • 2
    Based on your question, you are configuring signature algorithm. It must be asymmetric algorithm suited for digital signature. AES is symmetric algorithm and is intended for encryption operations only. SHA(X) is signature algorithm. Also, I would not recommend to use SHA512 prior testing, because, for instance, it is not enabled by default in Windows. – Crypt32 Jun 10 '15 at 19:59
  • @CryptoGuy Perhaps you may be able to help me out with (almost the same question but a slight twist): http://stackoverflow.com/questions/30794112/problems-generating-a-self-signed-1024-bit-x509certificate2-using-the-rsa-aes-pr – Alexandru Jun 12 '15 at 01:48
0

This question is old, but I thought I'd share my insight. MSDN lists CRYPT_ALGORITHM_IDENTIFIER as:

typedef struct _CRYPT_ALGORITHM_IDENTIFIER {
    LPSTR            pszObjId;
    CRYPT_OBJID_BLOB Parameters;
} CRYPT_ALGORITHM_IDENTIFIER, *PCRYPT_ALGORITHM_IDENTIFIER;

The important thing to note that it's an LPSTR, not a LPWSTR. So the string needs to be marshalled as ANSI. Unless specified explicitly, .NET will marshal the strings as unicode. So our corresponding struct needs additional decoration:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
struct AlgorithmIdentifier
{
    public string ObjectId;
    public CryptoBlob Parameters;
}

Adding CharSet into the StructLayout attribute will make .NET marshal the strings in the required encoding. Then it can be used in a straightforward manner:

var algId = new AlgorithmIdentifier
{
    ObjectId = "1.2.840.113549.1.1.5" // szOID_RSA_SHA1RSA
};
atanamir
  • 4,833
  • 3
  • 24
  • 20