Will this digital signing code work with a Qualified Digital Certificate? - c++

I was recently asked if our existing signing code will work with a European Qualified Digital Certificate in place of the RSA signing certificate we normally use. As far as I can tell it should, but I haven't found anything that actually confirms this theory.
Short of figuring out how to acquire a qualified certificate and actually testing it, I'm not sure how to answer this question definitively. Anybody happen to have experience with this?
The code in question is shown below. It's a Windows-based C++ application that is using Microsoft's Cryptographic API for signing.
int SignData(
const std::string &data, // Data to be signed
const char *containerName, // Name of key container to use
std::string &signature) // Returns the signature
{
HCRYPTPROV hProv = NULL;
HCRYPTHASH hHash = NULL;
HCRYPTKEY hKey = NULL;
DWORD dwLength;
int status = 0;
// Attempt to open the key container as a LocalMachine key.
if (CryptAcquireContext(
&hProv,
containerName,
NULL,
PROV_RSA_FULL,
CRYPT_MACHINE_KEYSET | CRYPT_SILENT))
{
// Create a SHA-1 hash context.
if (CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash))
{
// Calculate hash of the buffer.
if (CryptHashData(hHash, (const BYTE *)data.data(), (DWORD)data.size(), 0))
{
// Determine the size of the signature and allocate memory.
if (CryptSignHash(hHash, AT_SIGNATURE, 0, 0, NULL, &dwLength))
{
signature.resize(dwLength);
// Sign the hash object.
if (!CryptSignHash(hHash, AT_SIGNATURE, 0, 0, (BYTE*)&signature[0], &dwLength))
status = HandleCryptError("CryptSignHash failed");
}
else
status = HandleCryptError("CryptSignHash failed");
}
else
status = HandleCryptError("CryptHashData failed");
}
else
status = HandleCryptError("CryptCreateHash failed");
}
else
status = HandleCryptError("CryptAcquireContext failed");
return status;
}

The above code is using the default Microsoft software cryptographic provider which doesn't meet the requirements of eIDAS.
If you replace the default Microsoft provider by an eIDAS compliant provider in the above code then this approach of performing signature is OK. Under the hood, the compliant CSP will take care of non-repudiation requirement of the key usage, for example by displaying a PIN popup or if the key is stored remotely in a server by sending a 2FA notification to the key owner.

Related

RegOpenKeyEx Not finding an existing value

I am checking to see if the value "XXS" exists in the run registry
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\\XXS", 0, KEY_READ | KEY_WOW64_64KEY, &hkey) == ERROR_FILE_NOT_FOUND)
{
std::cout << "KEY NOT FOUND";
LONG createStatus = RegCreateKey(HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", &hkey); //Creates a key
LONG status = RegSetValueEx(hkey, L"XXS", 0, REG_SZ, (LPBYTE)path, (size + 1) * sizeof(WCHAR));
}
I am not sure why this is happening and if I am using this function right. The key does exist but it says it does not. The program is 64 bit running on 64 bit pc
I think the issue here is that you do not differentiate between keys and values.
Registry keys are container objects similar to folders. Registry values are non-container objects similar to files. Keys may contain values and subkeys.
So you basically need to open/create a key and then handle the values in it.
In your case, i'd try something like this (but be aware, from the top of my head, not tested):
#define HKLM_BASE L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"
HKEY hKey;
wchar_t buf[256] = { 0 };
LONG regError = RegOpenKeyEx(HKEY_LOCAL_MACHINE,HKLM_BASE,0, KEY_WOW64_64KEY | KEY_ALL_ACCESS,&hKey);
if (regError == ERROR_SUCCESS)
{
DWORD dwDataSize = sizeof(buf);
regError = RegQueryValueEx(hKey,L"XXS", NULL, NULL,(LPBYTE)&buf,&dwDataSize);
if ((regError != ERROR_SUCCESS) )
{
wchar_t *path =L"your value";
if (ERROR_SUCCESS != RegSetValueEx(hKey,L"XXS",NULL,REG_SZ,((const BYTE*)&path),wcslen (path)))
{
/* handle error */
}
} else {
/* current value in buf */
}
RegFlushKey( hKey );
RegCloseKey( hKey );
} else {
/* handle error */
}
Welcome to stack overflow! While typing this I see Morphine was just a little bit quicker. But he forgot to explain most of the the changes he made. So here are my 2 cents. The example of Morphine is fine , I won't provide another one.
I think XXS is a value (since you create a value, not a key, if it does not exist). This means you need to check if the value exist, right after you open the registry key. I also advise you to check on any error returning from the function RegOpenKeyEx, not only the ERROR_FILE_NOT_FOUND error.
Another thing I recommend is using RegCreateKeyEx instead of RegCreateKey, this since the documentation says the following:
Note This function is provided only for compatibility with 16-bit
versions of Windows. Applications should use the RegCreateKeyEx
function. However, applications that back up or restore system state
including system files and registry hives should use the Volume Shadow
Copy Service instead of the registry functions.
Last but not least, don't forget to close the keys once you do not need them anymore. You don't have to close values.

Microsoft CNG - Create a self signed certificate in memory using BCrypt functions

I'm using the Microsoft CNG Cryptography API and I'm trying to create a self signed certificate.
We have existing code which uses the CertCreateSelfSignCertificate method, combined with NCryptOpenStorageProvider and NCryptCreatePersistedKey to create the key pair.
The problem I'm facing now, is that the above combination creates a key which ends up in the windows crypto store. I explicitly don't want to store keys there, I want the certificate and private key to both be in-memory as I plan to then write them to encrypted/protected storage of our own choosing.
From what I've been able to tell, the BCrypt family of functions is designed for in-memory use, and indeed I can use BCryptGenerateKeyPair to do this, but then the CertCreateSelfSignCertificate doesn't work as it expects a key storage provider.
Is there any way to solve this? I was thinking either to try make a tempory in-memory key storage provider somehow, or else to do it the hard way and build the entire X509 certificate blob myself using a complex chain of calls to CryptEncodeObjectEx. Documentation/reference and example code on this kind of stuff is terrible and I've struggled to find anything despite extensive googling :-(
Any help for different ideas or example code that might work would be much appreciated
The following code is the exact code from one of my applications that I make (except a few changes to the app name in the code.)
It creates a self signed certificate in memory and adds it to the local system store. A few tweaks and you can get the certificate context itself. It uses the CNG Next Generation API to stay modern.
bool GenerateSelfSignedCertificate()
{
auto result{ false };
CERT_NAME_BLOB nameBlob{ 0 };
CERT_EXTENSIONS certExtensions{ 0 };
NCRYPT_PROV_HANDLE providerHandle{ 0 };
NCRYPT_KEY_HANDLE keyHandle{ 0 };
PCCERT_CONTEXT certContext{ nullptr };
HCERTSTORE certStore{ nullptr };
CRYPT_KEY_PROV_INFO keyProvInfo{
const_cast<LPWSTR>(L"MyService_Key"), const_cast<LPWSTR>(MS_KEY_STORAGE_PROVIDER),
0, NCRYPT_SILENT_FLAG, 0, nullptr, AT_KEYEXCHANGE
};
if (!CertStrToNameW(X509_ASN_ENCODING, L"CN=MyService", 0, nullptr, nameBlob.pbData,
&nameBlob.cbData, nullptr))
goto fail;
nameBlob.pbData = new UCHAR[nameBlob.cbData];
if (!CertStrToNameW(X509_ASN_ENCODING, L"CN=MyService", 0, nullptr, nameBlob.pbData,
&nameBlob.cbData, nullptr))
goto fail;
if (NCryptOpenStorageProvider(&providerHandle, MS_KEY_STORAGE_PROVIDER, 0) != ERROR_SUCCESS)
goto fail;
if (NCryptCreatePersistedKey(providerHandle, &keyHandle, BCRYPT_RSA_ALGORITHM, L"MyService_Key",
AT_KEYEXCHANGE, 0) != ERROR_SUCCESS)
goto fail;
if (NCryptFinalizeKey(keyHandle, NCRYPT_SILENT_FLAG) != ERROR_SUCCESS)
goto fail;
certContext = CertCreateSelfSignCertificate(keyHandle, &nameBlob, 0, &keyProvInfo, nullptr,
nullptr, nullptr, &certExtensions);
if (!certContext)
goto fail;
certStore = CertOpenSystemStoreW(NULL, L"MY");
if (!certStore)
goto fail;
if (!CertAddCertificateContextToStore(certStore, certContext, CERT_STORE_ADD_REPLACE_EXISTING, nullptr))
goto fail;
result = true;
fail:
delete[] nameBlob.pbData;
if (keyHandle)
OutputDebugStringW(L"NCryptFinalizeKey successfully finalized the key for use in the certificate.\r\n");
if (providerHandle)
NCryptFreeObject(providerHandle);
if (certStore)
CertCloseStore(certStore, 0);
if (certContext)
CertFreeCertificateContext(certContext);
return result;
}
Please let me know if this helps you. Thanks.

AcquireCredentialsHandle fails in kernel mode, when using SCH_CRED_FORMAT_CERT_HASH

I call AcquireCredentialsHandle in a kernel driver, passing in SCHANNEL_CRED with the dwCredFormat set to SCH_CRED_FORMAT_CERT_HASH. It fails with SEC_E_NO_CREDENTIALS. Here is my code:
BYTE certHashBytes[20] = { 0x6d,0x64,0xed,0x56,0xd2,0x94,0x15,0xf4,0x49,0x08,0xaf,0x18,0xf1,0xca,0xf5,0xa2,0xc8,0x01,0x20,0x96 };
CredHandle credHandle;
RtlZeroMemory(&credHandle, sizeof(CredHandle));
SCHANNEL_CRED schannelCred;
RtlZeroMemory(&schannelCred, sizeof(SCHANNEL_CRED));
schannelCred.dwVersion = 4;
schannelCred.cCreds = 1;
schannelCred.paCred = certHashBytes;
schannelCred.dwCredFormat = 1;
UNICODE_STRING unispName;
RtlUnicodeStringInit(&unispName, L"Microsoft Unified Security Protocol Provider");
TimeStamp ts;
SECURITY_STATUS res = AcquireCredentialsHandle(NULL, &unispName, SECPKG_CRED_INBOUND, NULL, &schannelCred, NULL, NULL, &credHandle, &ts);
DbgPrintEx(DPFLTR_IHVNETWORK_ID, DPFLTR_INFO_LEVEL, "AcquireCredentialsHandle %x\n", res);
My certificate hash is definitely correct, and installed properly in the MY store, for both User Account and Local Machine. I know this because it works fine in user mode, as follows:
HCERTSTORE certStore = CertOpenSystemStore(NULL, L"MY");
BYTE certHashBytes[20] = { 0x6d,0x64,0xed,0x56,0xd2,0x94,0x15,0xf4,0x49,0x08,0xaf,0x18,0xf1,0xca,0xf5,0xa2,0xc8,0x01,0x20,0x96 };
CERT_NAME_BLOB certHash { 20, certHashBytes };
PCCERT_CONTEXT cert = CertFindCertificateInStore(certStore, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_SHA1_HASH, &certHash, NULL);
CredHandle credHandle;
ZeroMemory(&credHandle, sizeof(CredHandle));
SCHANNEL_CRED cred;
ZeroMemory(&cred, sizeof(SCHANNEL_CRED));
cred.dwVersion = SCHANNEL_CRED_VERSION;
cred.cCreds = 1;
cred.paCred = &cert;
SECURITY_STATUS res = AcquireCredentialsHandle(NULL, const_cast<LPWSTR>(UNISP_NAME), SECPKG_CRED_INBOUND, NULL, &cred, NULL, NULL, &credHandle, NULL);
I believe I followed the MSDN instructions on how to use SCH_CRED_FORMAT_CERT_HASH exactly - what's wrong?
It is difficult to know for sure without debugging, however I see some points that could be the problem:
- If the certificate chain cannot be verified; or it is self-signed; or the machine does not have access to the internet at the time your code execute to check CRL, your call will fail. If this is the case, use CRYPT_E_NO_REVOCATION_CHECK
- If the purposes of your certificate are correct for proving an identity to remote servers?
There are some recent security hardenings in Windows to be very picky when it comes to certificates. A self-signed certificate sometimes is easier to test than a signed one. I have seen an increasing number of applications that were working stopped working for having a certificate not 100% proved. Short of it, I do not see what the problem is.

Simple AES encryption using WinAPI

I need to do simple single-block AES encryption / decryption in my Qt / C++ application. This is a "keep the honest people honest" implementation, so just a basic encrypt(key, data) is necessary--I'm not worried about initialization vectors, etc. My input and key will always be exactly 16 bytes.
I'd really like to avoid another dependency to compile / link / ship with my application, so I'm trying to use what's available on each platform. On the Mac, this was a one-liner to CCCrypt. On Windows, I'm getting lost in the API from WinCrypt.h. Their example of encrypting a file is almost 600 lines long. Seriously?
I'm looking at CryptEncrypt, but I'm falling down the rabbit hole of dependencies you have to create before you can call that.
Can anyone provide a simple example of doing AES encryption using the Windows API? Surely there's a way to do this in a line or two. Assume you already have a 128-bit key and 128-bits of data to encrypt.
Here's the best I've been able to come up with. Suggestions for improvement are welcome!
static void encrypt(const QByteArray &data,
const QByteArray &key,
QByteArray *encrypted) {
// Create the crypto provider context.
HCRYPTPROV hProvider = NULL;
if (!CryptAcquireContext(&hProvider,
NULL, // pszContainer = no named container
NULL, // pszProvider = default provider
PROV_RSA_AES,
CRYPT_VERIFYCONTEXT)) {
throw std::runtime_error("Unable to create crypto provider context.");
}
// Construct the blob necessary for the key generation.
AesBlob128 aes_blob;
aes_blob.header.bType = PLAINTEXTKEYBLOB;
aes_blob.header.bVersion = CUR_BLOB_VERSION;
aes_blob.header.reserved = 0;
aes_blob.header.aiKeyAlg = CALG_AES_128;
aes_blob.key_length = kAesBytes128;
memcpy(aes_blob.key_bytes, key.constData(), kAesBytes128);
// Create the crypto key struct that Windows needs.
HCRYPTKEY hKey = NULL;
if (!CryptImportKey(hProvider,
reinterpret_cast<BYTE*>(&aes_blob),
sizeof(AesBlob128),
NULL, // hPubKey = not encrypted
0, // dwFlags
&hKey)) {
throw std::runtime_error("Unable to create crypto key.");
}
// The CryptEncrypt method uses the *same* buffer for both the input and
// output (!), so we copy the data to be encrypted into the output array.
// Also, for some reason, the AES-128 block cipher on Windows requires twice
// the block size in the output buffer. So we resize it to that length and
// then chop off the excess after we are done.
encrypted->clear();
encrypted->append(data);
encrypted->resize(kAesBytes128 * 2);
// This acts as both the length of bytes to be encoded (on input) and the
// number of bytes used in the resulting encrypted data (on output).
DWORD length = kAesBytes128;
if (!CryptEncrypt(hKey,
NULL, // hHash = no hash
true, // Final
0, // dwFlags
reinterpret_cast<BYTE*>(encrypted->data()),
&length,
encrypted->length())) {
throw std::runtime_error("Encryption failed");
}
// See comment above.
encrypted->chop(length - kAesBytes128);
CryptDestroyKey(hKey);
CryptReleaseContext(hProvider, 0);
}

Creating a temporary client certificate (including a private key)

As part of an application I am writing, I wish for my program to temporarily install a certificate on the local machine and for WinHTTP to use it as the client certificate when connecting to a web server. The aim of this is help protect the web server from unauthorised access (this certificate is only a layer of the security - I know someone could extract it from the .exe). I do not want the user to have to install the certificate and I do not want the certificate to be left on the PC when the application is not running.
At the moment, I'm trying this:
Install the certificate manually from a .p12 file
Use a C++ application to get the binary data out of the local certificate and into a C array in my application (using CryptExportKey and PCCERT_CONTEXT::pbCertEncoded)
Uninstall the certificate
When the application boots:
Open a temporary store for the certificate
m_certificateStoreHandle = CertOpenStore( CERT_STORE_PROV_MEMORY, 0, NULL, 0, NULL );
Call CertAddEncodedCertificateToStore to add the certificate
CertAddEncodedCertificateToStore( m_certificateStoreHandle,
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
reinterpret_cast< const BYTE * >( certificateData ),
dataSize,
CERT_STORE_ADD_REPLACE_EXISTING,
&m_clientCertificate )
Call CryptAcquireContext to get somewhere to store the private key (I change the name of the key on each run for the moment - ideally I plan to use CRYPT_VERIFYCONTEXT to make the key non-persistant, but that's something to ignore for now)
HCRYPTPROV cryptProvider = NULL;
HCRYPTKEY cryptKey = NULL;
CryptAcquireContext( &cryptProvider, "MyTestKeyNumber123", NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET )
Call CryptImportKey to load the private key into the key store
CryptImportKey( cryptProvider, reinterpret_cast< BYTE * >( privateKey ), keySize, 0, CRYPT_EXPORTABLE, &cryptKey )
Call CertSetCertificateContextProperty to link the certificate to the private key
char containerName[128];
DWORD containerNameSize = ARRAY_NUM_BYTES(containerName);
char providerName[128];
DWORD providerNameSize = ARRAY_NUM_BYTES(providerName);
CryptGetProvParam(cryptProvider, PP_CONTAINER, reinterpret_cast<byte *>(containerName), &containerNameSize, 0)
CryptGetProvParam(cryptProvider, PP_NAME, reinterpret_cast<byte *>(providerName), &providerNameSize, 0)
WCHAR containerNameWide[128];
convertCharToWChar(containerNameWide, containerName);
WCHAR providerNameWide[128];
convertCharToWChar(providerNameWide, providerName);
CRYPT_KEY_PROV_INFO privateKeyData;
neMemZero(&privateKeyData, sizeof(privateKeyData));
privateKeyData.pwszContainerName = containerNameWide;
privateKeyData.pwszProvName = providerNameWide;
privateKeyData.dwProvType = 0;
privateKeyData.dwFlags = CRYPT_SILENT;
privateKeyData.dwKeySpec = AT_KEYEXCHANGE;
if ( CertSetCertificateContextProperty( m_clientCertificate, CERT_KEY_PROV_INFO_PROP_ID, 0, &privateKeyData ) )
Verify I can access the private key (Works, outputs exactly the same data as I have hardcoded into the application)
byte privateKeyBuffer[2048];
DWORD privateKeyBufferSize = ARRAY_NUM_BYTES(privateKeyBuffer);
memZero(privateKeyBuffer, privateKeyBufferSize);
if(CryptExportKey(cryptKey, 0, PRIVATEKEYBLOB, 0, privateKeyBuffer, &privateKeyBufferSize))
{
TRACE("Got private key!");
LOG_BUFFER(privateKeyBuffer, privateKeyBufferSize);
}
Attempt to verify the client certificate works as expected
char certNameBuffer[128] = "";
char certUrlBuffer[128] = "";
CertGetNameString(testValue, CERT_NAME_FRIENDLY_DISPLAY_TYPE, 0, NULL, certNameBuffer, ARRAY_NUM_BYTES(certNameBuffer));
CertGetNameString(testValue, CERT_NAME_URL_TYPE , 0, NULL, certUrlBuffer, ARRAY_NUM_BYTES(certUrlBuffer));
TRACE("SSL Certificate %s [%s]", certNameBuffer, certUrlBuffer);
HCRYPTPROV_OR_NCRYPT_KEY_HANDLE privateKey;
DWORD privateKeyType;
BOOL freeKeyAfter = false;
if(CryptAcquireCertificatePrivateKey(testValue, CRYPT_ACQUIRE_NO_HEALING, NULL, &privateKey, &privateKeyType, &freeKeyAfter))
{
HCRYPTPROV privateKeyProvider = static_cast<HCRYPTPROV>(privateKey);
HCRYPTKEY privateKeyHandle;
if(CryptGetUserKey(privateKeyProvider, privateKeyType, &privateKeyHandle))
{
NEbyte privateKeyBuffer[2048];
DWORD privateKeyBufferSize = NE_ARRAY_NUM_BYTES(privateKeyBuffer);
neMemZero(privateKeyBuffer, privateKeyBufferSize);
if(CryptExportKey(privateKeyHandle, 0, PRIVATEKEYBLOB, 0, privateKeyBuffer, &privateKeyBufferSize))
{
NE_TRACE("Got private key!");
HTTP_LOG_BUFFER(neGetGlobalTraceLog(), "Key", "", privateKeyBuffer, privateKeyBufferSize);
}
At this stage, the private key is found but the call to CryptExportKey fails with NTE_BAD_KEY_STATE. When I try to use the client certificate with WinHTTP, I get ERROR_WINHTTP_CLIENT_CERT_NO_ACCESS_PRIVATE_KEY. If anyone is wondering, I tell WinHTTP to use the client certificate with this code:
if ( !WinHttpSetOption( handle,
WINHTTP_OPTION_CLIENT_CERT_CONTEXT,
const_cast<PCERT_CONTEXT>(m_clientCertificate),
sizeof( CERT_CONTEXT ) ) )
{
HTTP_LOG_ERROR( getLog(), "Setting the client certificate failed with error code %x", GetLastError() );
}
The way I see it, until I can somehow link the private key and the certificate together and make it so that I can use CryptAcquireCertificatePrivateKey with CryptExportKey to get the key data back out, WinHTTP doesn't stand a chance of being succesful.
Any thoughts as to why I can't seem to get my certificate to use the private key?
I didn't manage to get this approach working. Instead, I ended up using PFXImportCertStore along with the raw .p12 file containing the certificate and private key I wanted to use.
From what I've seen, it seems that PFXImportCertStore will create a new store in memory. The only thing I haven't been able to find out is if the private key is also stored in memory or if it ends up permenently on the PC somewhere. If I find out either way, I'll update this answer.
m_clientCertificateStoreHandle = PFXImportCertStore(&pfxData, certificatePassword, 0);
if(NULL != m_clientCertificateStoreHandle)
{
m_clientCertificateHandle = CertFindCertificateInStore( m_clientCertificateStoreHandle, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_ANY, NULL, NULL );
}