c++sslschannel

How do I load a private key with the Schannel library?


I need to add client authentication to my client application. I can't figure out how to provide the private key to my application. I can't figure out how t get the file that contains the private key into the correct format to be loaded by the Schannel API.

I have a private.key file created with "openssl req" command. I've copied the contents of this file into the cPrivateKey variable and I'm trying to load it with the following (note: I've omitted the Base64 encoded part of cPrivateKey).

// Contents of private.key
const char* cPrivateKey = "\
-----BEGIN ENCRYPTED PRIVATE KEY-----\n\
<omitted>\n\
-----END ENCRYPTED PRIVATE KEY-----";

DWORD iBinaryCertBytes = 0;

//   Get the size of the binary data
if (!CryptStringToBinary(cPrivateKey, 0, CRYPT_STRING_BASE64_ANY, NULL, (DWORD*)&iBinaryCertBytes, NULL, NULL))
   {
   Error("Unable to get private key length.");
   return;
   }

//   Allocate binary data
UINT8* aiBinaryCert = new UINT8[iBinaryCertBytes];

//   Get the binary data
if (!CryptStringToBinary(cPrivateKey, 0, CRYPT_STRING_BASE64_ANY, aiBinaryCert, (DWORD*)&iBinaryCertBytes, NULL, NULL))
   {
   Error("Unable to convert private key to binary.");
   return;
   }

//   Create the certificate context 
sCertContext = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, &aiBinaryCert[0], aiBinaryCert.GetCount());
if (sCertContext == NULL)
   {
// ERROR: CRYPT_E_ASN1_BADTAG
   Error("Failed to create certificate key.  0x%08x", GetLastError());
   return;
   }

SCHANNEL_CRED sCred = {};
sCred.dwVersion = SCHANNEL_CRED_VERSION;
sCred.dwFlags = 0;
sCred.dwFlags |= SCH_USE_STRONG_CRYPTO;
sCred.dwFlags |= SCH_CRED_AUTO_CRED_VALIDATION;
sCred.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS;
sCred.grbitEnabledProtocols = SP_PROT_TLS1_2;
sCred.cCreds = 1;
sCred.paCred = &sCertContext;

//   Get the credentials
CredHandle sCredential = {};
int iStatus = AcquireCredentialsHandle(NULL, UNISP_NAME, SECPKG_CRED_OUTBOUND, NULL, &sCred, NULL, NULL, &sCredential, NULL);
if (iStatus != SEC_E_OK)
   {
   Error("TLS Credential Failed.  <%d>", iStatus);
   return;
   }

ZeroMemory(aiBinaryCert, iBinaryCertBytes);
delete [] aiBinaryCert;
CertFreeCertificateContext(sCertContext);
return

The call to CertCreateCertificateContext() fails with 0x8009310B(CRYPT_E_ASN1_BADTAG).

I initially created the key with a command like this.

openssl req -sha256 -new -newkey rsa:2048 -nodes -keyout private.key -subj "/C=US/ST=state/L=city/O=org/OU=IT/CN=identifier/emailAddress=me@example.com" -out request.csr"

I then sent the .csr file to the organization that will be running the server and they sent me back a certificate.p7b file.

The client is a C++ program I am developing. The server will be HiveMQ.


Solution

  • It looks like I had to create a PKCS#12 file that contains both the certificate and the private key. Then, I had to install this to the Windows Certificate Store. Finally, I was able to load it and pass it to AcquireCredentialsHandle.

    First, I had to extract the certificate from the certificate.p7b file. I could double click on this and Windows shows the certificates in the file. Right click, All Tasks, Export.... I selected DER as the format. I saved the file to certificate.cer.

    Create PKCS#12 file:

    openssl pkcs12 -export -out key.pfx -inkey private.key -in certificate.cer
    

    Install this by double clicking on the key.pfx file. For the Store Location, I selected "Current User". The certificate then is visible in certmgr in the "Personal" certificate store.

    Then, I was able to find the certificate using CertEnumCertificatesInStore(), which I passed to AcquireCredentialsHandle()