Search code examples
c#c++cpinvokemarshalling

Interop C# IntPtr to C void* to C++ PCCERT_CONTEXT and back again


I'm needing to use C++ to get a certificate from the local machine store because Unity & Mono don't support the local machine store correctly.

To that end I've implemented the following, but my knowledge of pointers, references, address of and C++ function lifetime is stopping me from getting a valid pointer back. I know there's likely multiple sins in the below, but first, please help me get 'out' a pointer to the found certificate that isn't nuked by memory management.

X509CertificateLookup.h

#define DLLExport __declspec(dllexport)

extern "C"
{
    DLLExport bool GetMachineCertByThumb(void**& pCertContext, const char* thumbprint, const char* storeName);
    DLLExport void FreeCertificateContext(const void* certPtr);
}

#endif

X509CertificateLookup.cpp

bool GetMachineCertByThumb(void**& pCertContext, const char* thumbprint, const char* storeName) {
    HCERTSTORE hCertStore = NULL;

    if ((hCertStore = CertOpenStore(
        CERT_STORE_PROV_SYSTEM_W,        // The store provider type
        0,                               // The encoding type is not needed
        NULL,                            // Use the default HCRYPTPROV
        CERT_SYSTEM_STORE_LOCAL_MACHINE | CERT_STORE_READONLY_FLAG, // Set the store location in a registry location
        storeName                        // The store name as a Unicode string (L"MY") 
    )) != nullptr) {
        PCCERT_CONTEXT pSearchCertContext = NULL;

        if ((pSearchCertContext = CertFindCertificateInStore(
            hCertStore,
            X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
            0,
            CERT_FIND_HASH_STR,
            thumbprint,
            pSearchCertContext)) != nullptr) {

            CertCloseStore(hCertStore, CERT_CLOSE_STORE_CHECK_FLAG);

            //A pointer to a buffer that contains the encoded certificate
            pCertContext = &pSearchCertContext; //<- This is where my issues are!
            return true;
        }
    }

    if (hCertStore) {
        CertCloseStore(hCertStore, CERT_CLOSE_STORE_CHECK_FLAG);
    }

    pCertContext = nullptr;
    return false;
}

void FreeCertificateContext(const void* certPtr) {
    if (certPtr == nullptr) {
        CertFreeCertificateContext(static_cast<PCCERT_CONTEXT>(certPtr));
    }
}

X509CertLookupWorkaround.cs

class X509CertificateLookup {
    [DllImport(nameof(X509CertificateLookup), CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true)]
        [return: MarshalAs(UnmanagedType.I1)]
        public static extern bool GetMachineCertByThumb(
            ref IntPtr pCertContext,
            [MarshalAs(UnmanagedType.LPUTF8Str)]
            string thumbprint,
            [MarshalAs(UnmanagedType.LPUTF8Str)]
            string storeName);

    [DllImport(nameof(X509CertificateLookup), CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
    public static extern void FreeCertificateContext([In] IntPtr certPtr);
}

class X509CertLookupWorkaround{

    public static X509Certificate2 GetMachineCertByThumb_CPP(string thumbprint, string storeName) {

        IntPtr certPtr = IntPtr.Zero;
        
        if(!X509CertificateLookup.GetMachineCertByThumb(ref certPtr, thumbprint, storeName) || certPtr == IntPtr.Zero) {
            UnityEngine.Debug.Log("Failure, Certificate not found!");
            return null;
        }else{
            UnityEngine.Debug.Log("Success, Certificate found!");
            return new X509Certificate2(certPtr);
        }
    }
    
    public static void ReleaseCertificate(IntPtr certPtr){
        X509CertificateLookup.FreeCertificateContext(certPtr)
    }
}



Solution

  • For starters, it should be

    bool GetMachineCertByThumb(
       void*& pCertContext,    // A reference to a `void*` variable to which to write.
       const char* thumbprint,
       const char* storeName
    ) {
       ...
       pCertContext = pSearchCertContext;
    
       ...
    }
    

    You want to "return" the pointer returned by CertFindCertificateInStore, not a pointer to the local variable that contains this pointer.


    Then, there's a potential issue in the use of a reference type in an extern "C" section. C doesn't have reference types, so let's avoid it.

    bool GetMachineCertByThumb(
       void** ppCertContext,    // A pointer to a `void*` variable to which to write.
       const char* thumbprint,
       const char* storeName
    ) {
       ...
       *ppCertContext = pSearchCertContext;
       ...
    }
    

    But why return the value via a parameter? It would be a lot simpler to return it as a returned value.

    void *GetMachineCertByThumb( const char* thumbprint, const char* storeName ) {
       ...
       return pSearchCertContext;
    }
    

    I don't know if there are other issues.