My project is to create a script in Golang that signs a document with a PIV card digital signature and then allows you to verify the signature with the piv card's certificate. I've got the first part working. What I'm running into is getting the verification working. I'm trying to import the public key from the certificate using the Windows CNG apis and I can't get it to work. I pulled that specific part of the code out to try and get that working. I've included the snippet below. When I run it, I get this error:
CryptDecodeObjectEx failed: The system cannot find the file specified.
Note: this pops up after it's retrieved the cert.pem file and parsed it.
I used this to create a pem cert to test this with:
openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem
package main
import (
"encoding/pem"
"fmt"
"os"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
var (
bcrypt = syscall.NewLazyDLL("bcrypt.dll")
procBCryptOpenAlgorithmProvider = bcrypt.NewProc("BCryptOpenAlgorithmProvider")
procBCryptImportKeyPair = bcrypt.NewProc("BCryptImportKeyPair")
procBCryptDestroyKey = bcrypt.NewProc("BCryptDestroyKey")
crypt32 = syscall.NewLazyDLL("crypt32.dll")
procCryptDecodeObjectEx = crypt32.NewProc("CryptDecodeObjectEx")
)
const (
BCRYPT_RSA_ALGORITHM = "RSA"
MS_PRIMITIVE_PROVIDER = "Microsoft Primitive Provider"
BCRYPT_RSAPUBLIC_BLOB = "RSAPUBLICBLOB"
BCRYPT_RSAPUBLIC_MAGIC = 0x31415352 // 'RSA1'
X509_ASN_ENCODING = 0x00000001
)
type BCRYPT_RSAKEY_BLOB struct {
Magic uint32
BitLength uint32
cbPublicExp uint32
cbModulus uint32
cbPrime1 uint32
cbPrime2 uint32
}
type CRYPT_ALGORITHM_IDENTIFIER struct {
pszObjId *byte
Parameters CRYPT_OBJID_BLOB
}
type CRYPT_OBJID_BLOB struct {
CbData uint32
PbData *byte
}
type CRYPT_BIT_BLOB struct {
CbData uint32
PbData *byte
UnusedBits uint32
}
type CERT_PUBLIC_KEY_INFO struct {
Algorithm CRYPT_ALGORITHM_IDENTIFIER
PublicKey CRYPT_BIT_BLOB
}
func main() {
// Load the certificate from cert.pem file
certPEM, err := os.ReadFile("cert.pem")
if err != nil {
fmt.Printf("Failed to read certificate file: %v\n", err)
return
}
// Decode the PEM format to get the DER-encoded certificate
block, _ := pem.Decode(certPEM)
if block == nil {
fmt.Println("Failed to decode PEM block")
return
}
certData := block.Bytes
// Extract the public key info from the certificate
var publicKeyInfo *CERT_PUBLIC_KEY_INFO
var publicKeyInfoLen uint32
r, _, err := procCryptDecodeObjectEx.Call(
X509_ASN_ENCODING,
uintptr(unsafe.Pointer(syscall.StringBytePtr("X509_PUBLIC_KEY_INFO"))),
uintptr(unsafe.Pointer(&certData[0])),
uintptr(len(certData)),
0,
0,
uintptr(unsafe.Pointer(&publicKeyInfo)),
uintptr(unsafe.Pointer(&publicKeyInfoLen)),
)
if r == 0 {
fmt.Printf("CryptDecodeObjectEx failed: %v\n", err)
return
}
// Open an algorithm provider
var hAlg windows.Handle
r, _, err = procBCryptOpenAlgorithmProvider.Call(
uintptr(unsafe.Pointer(&hAlg)),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(BCRYPT_RSA_ALGORITHM))),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(MS_PRIMITIVE_PROVIDER))),
0,
)
if r != 0 {
fmt.Printf("BCryptOpenAlgorithmProvider failed: 0x%X\n", r)
return
}
defer procBCryptDestroyKey.Call(uintptr(hAlg))
// Prepare the BCRYPT_RSAKEY_BLOB structure
pubKeyData := (*[1 << 30]byte)(unsafe.Pointer(publicKeyInfo.PublicKey.PbData))[:publicKeyInfo.PublicKey.CbData:publicKeyInfo.PublicKey.CbData]
rsaKeyBlob := BCRYPT_RSAKEY_BLOB{
Magic: BCRYPT_RSAPUBLIC_MAGIC,
BitLength: uint32(len(pubKeyData) * 8),
cbPublicExp: uint32(len(pubKeyData)),
cbModulus: uint32(len(pubKeyData)),
}
// Import the public key
var hKey windows.Handle
r, _, err = procBCryptImportKeyPair.Call(
uintptr(hAlg),
0,
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(BCRYPT_RSAPUBLIC_BLOB))),
uintptr(unsafe.Pointer(&hKey)),
uintptr(unsafe.Pointer(&rsaKeyBlob)),
uintptr(unsafe.Sizeof(rsaKeyBlob)),
0,
)
if r != 0 {
fmt.Printf("BCryptImportKeyPair failed: 0x%X\n", r)
return
}
defer procBCryptDestroyKey.Call(uintptr(hKey))
fmt.Println("Public key imported successfully")
}