Calling PFXExportCertStoreEx in Go does not return data

794 Views Asked by At

I'm working in Go 1.6 on Windows and trying to export a certificate container to a PFX (the ultimate goal here is to access an exportable private key from the certificate store).

I have opened a memory store and inserted a certificate into the store:

var storedCertCtx *syscall.CertContext
storeHandle, err := syscall.CertOpenStore(syscall.CERT_STORE_PROV_MEMORY, 0, 0, syscall.CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG, 0)
err = syscall.CertAddCertificateContextToStore(storeHandle, certenum, syscall.CERT_STORE_ADD_ALWAYS, &storedCertCtx)

Now I want to generate a PFX of that store. I have defined a struct for containing the data blob and want to use PFXExportCertStoreEx to get a PFX of the store:

var (
    crypt32                  = syscall.NewLazyDLL("crypt32.dll")
    procPFXExportCertStoreEx = crypt32.NewProc("PFXExportCertStoreEx")
)

type CRYPTOAPI_BLOB struct {
    DataSize uint32
    Data     *byte
}

var pfxBlob CRYPTOAPI_BLOB
err = PfxExportCertStore(storeHandle, &pfxBlob, syscall.StringToUTF16Ptr("MyPassword"), 0, 0)

syscall.Syscall6(procPFXExportCertStoreEx.Addr(), 5,
        uintptr(storeHandle),                //hStore
        uintptr(unsafe.Pointer(&pfxBlob)),   //*pPFX
        uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("password"))), //szPassword
        0,   //*pvPara
        0,   //dwFlags
        0)

And this half works.

DataSize is populated with what looks like an appropriate value (i.e. if I add more certificates to the store, it grows bigger), however Data is always <nil>.

Seeing as it's meant to be populated with a pointer, I have tried declaring it as *uintptr and uint32 (just to see if anything gets populated), but nothing. The value is always untouched (if I manually put junk data in there, the junk data stays after the syscall is executed).

Have I defined the struct incorrectly? There is precious few examples to go for getting this done in Go, but from what I can see from the numerous C examples, this should be working.

2

There are 2 best solutions below

1
On BEST ANSWER

This is the expected behavior.

According to this: https://msdn.microsoft.com/en-us/library/windows/desktop/aa387313(v=vs.85).aspx, the pPFX struct requires a pre-allocated buffer, with the size in the cbData field, which will be updated with the size of the data copied in.

If the call is made with pbData equal to NULL, only the cbData field is updated to reflect the size needed for the output buffer.

1
On

JimB's answer is most certainly correct, but I want to add this for followup in case anyone else is going down this path. The actual code that I had to use to get the PFX file into CRYPTOAPI_BLOB was:

var (
    crypt32                  = syscall.NewLazyDLL("crypt32.dll")
    procPFXExportCertStoreEx = crypt32.NewProc("PFXExportCertStoreEx")
    procCryptMemAlloc        = crypt32.NewProc("CryptMemAlloc")
    procCryptMemFree         = crypt32.NewProc("CryptMemFree")
)

type CRYPTOAPI_BLOB struct {
    cbData uint32
    pbData *byte
}

func (b *CRYPTOAPI_BLOB) ToByteArray() []byte {
    d := make([]byte, b.cbData)
    copy(d, (*[1 << 30]byte)(unsafe.Pointer(b.pbData))[:])
    return d
}

func PfxExportCertStore(storeHandle syscall.Handle, password string, flags uint32) (returnData []byte, err error) {

    var pfxBlob CRYPTOAPI_BLOB

    r1, _, _ := syscall.Syscall6(procPFXExportCertStoreEx.Addr(), 5,
        uintptr(storeHandle),                                        //hStore
        uintptr(unsafe.Pointer(&pfxBlob)),                           //*pPFX
        uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(password))), //szPassword
        0,              //*pvPara
        uintptr(flags), //dwFlags
        0)

    r2, _, _ := syscall.Syscall(procCryptMemAlloc.Addr(), 1, uintptr(unsafe.Pointer(&pfxBlob.cbData)), 0, 0)

    p := unsafe.Pointer(&r2)
    q := (*byte)(p)
    pfxBlob.pbData = q
    defer syscall.Syscall(procCryptMemFree.Addr(), 1, uintptr(unsafe.Pointer(pfxBlob.pbData)), 0, 0)

    r3, _, _ := syscall.Syscall6(procPFXExportCertStoreEx.Addr(), 5,
        uintptr(storeHandle),                                        //hStore
        uintptr(unsafe.Pointer(&pfxBlob)),                           //*pPFX
        uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(password))), //szPassword
        0,              //*pvPara
        uintptr(flags), //dwFlags
        0)


    returnData = pfxBlob.ToByteArray()
    return
}

(I have stripped the error handling to make it easier to read). The first call to PFXExportCertStoreEx just returns the size, and once we have the size we can do a call to PFXExportCertStoreEx to allocate a buffer, and then we pass the same pointer to PFXExportCertStoreEx, but this time it has the allocated buffer, and we get the full PFX file returned.