AcquireCredentialsHandle fails in kernel mode, when using SCH_CRED_FORMAT_CERT_HASH - c++

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.

Related

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

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.

LookupAccountName Always Fails With Error 122 (ERROR_INSUFFICIENT_BUFFER)

Perhaps somebody can enlighten me here.
I'm attempting to automate a WiFi connection process where the SSID is determined by a serial number. Since this is always different, I figured I need to save a temporary profile each time I wish to connect.
WlanSaveTemporaryProfile() wants a LPCWSTR strAllUserProfileSecurity to define permissions for this profile. So far the rabbit hole has led me to try using LookupAccountNameW(). I have tried to AllocateAndInitializeSid() to no avail. I tried plugging in an empty buffer with the same result. In both cases, I get an error 122, which says the buffer was too small.
Any aid here is sincerely appreciated.
Here's the relevant code. Mostly constructed from examples in Microsoft's documentation.
DWORD GetStringSecurityDescriptor(
PWCHAR ps_securityDescriptor, /* This needs to be populated when this function completes. */
PULONG pul_securityDescriptorLen,
LPWSTR ps_accountName
)
{
DWORD dw_result = NULL;
DWORD dw_lastError = NULL;
DWORD dw_bufferSizeOfUserAccount = NULL;
/* Create a security descriptor for the profile. */
SECURITY_DESCRIPTOR secDesc;
bool success = InitializeSecurityDescriptor(&secDesc, SECURITY_DESCRIPTOR_REVISION);
if (!success)
{
wprintf(L"Security Descriptor Initialization Failed.\n");
}
PSID p_userSid = NULL;
/* Attempt 2: Straight up malloc the memory. Doesn't work any better.*/
//p_userSid = malloc(100);
/* Attempt 1: Allocate and Initialize an SID for LookupAccountNameW(). */
SID_IDENTIFIER_AUTHORITY auth = SECURITY_WORLD_SID_AUTHORITY;
BOOL b_sidReady = AllocateAndInitializeSid(
&auth,
6,
SECURITY_NULL_RID,
SECURITY_WORLD_RID,
SECURITY_LOCAL_RID,
SECURITY_LOCAL_LOGON_RID,
SECURITY_CREATOR_OWNER_RID,
SECURITY_CREATOR_GROUP_RID,
0, 0,
&p_userSid
);
LPDWORD buf = &dw_bufferSizeOfUserAccount;
WCHAR domainName[1000] = { 0 }; // Perhaps DNLEN + 1 was too small?
DWORD domainNameLen = 1000;
SID_NAME_USE use = SidTypeUser;
// Currently failing. dw_bufferSizeOfUserAccount still recieves a 28, so that wasn't it.
success = LookupAccountNameW(
NULL,
ps_accountName,
p_userSid,
buf,
domainName,
&domainNameLen,
&use);
if (!success)
{
dw_lastError = GetLastError();
switch (dw_lastError)
{
case ERROR_INSUFFICIENT_BUFFER: // LookupAccountNameW() always ends up here.
wprintf(L"The data area passed to a system call is too small.\n");
FreeSid(p_userSid);
return dw_lastError;
default:
wprintf(L"Looking up Account Name failed. See Error 0x%x.\n", dw_lastError);
FreeSid(p_userSid);
return dw_lastError;
}
}
// ... more code irrelevant to this problem...
}
Great thanks to Georgy Firsov!
I missed a statement in the documentation.
By calculating the size of the SID and storing it in dw_bufferSizeOfUserAccount, the function ran successfully.
dw_bufferSizeOfUserAccount = GetLengthSid(p_userSid);

How to get DHCPV6 server IP using C++ program. I tried using DHCPv6RequestParams but always gets error code 121

In Dhcpv6RequestParams i used it's first parameter as TRUE & FALSE. but I never get server response with it's IP address.
y code is as follows:
BOOL RetrieveDHCPInfo(LPWSTR pszAdapterName)
{
DWORD dwError;
int num = 0;
CHAR TmpBufferV6[1000];
DWORD initPtr = 0;
Dhcpv6CApiInitialize(&initPtr);
DHCPV6CAPI_PARAMS DhcpApiServerNameParamsV6 = { 0,
DHCPV6_OPTION_SERVERID,
FALSE,
NULL,
0
};
DHCPV6CAPI_PARAMS paramArrayV6[1] = {
DhcpApiServerNameParamsV6
};
DHCPV6CAPI_PARAMS_ARRAY DhcpV6ApiParamsArray = {
1, // only one option to request
paramArrayV6
};
DWORD size = sizeof(TmpBufferV6);
dwError = Dhcpv6RequestParams(TRUE,
NULL,
pszAdapterName,
NULL,
DhcpV6ApiParamsArray,
(PBYTE)TmpBufferV6,
&size);
}
// I am receiving errcode as 121(Semaphore timed out).
Contacted microsoft regarding this. MSFT told that yet they have not provided any API which will return the DHCPv6 server address.
It is possible that in next releases they may give some API for the same.

How do I get the version of a driver on Windows from C++

I'm looking for a programmatic way to get the version number of a driver. I want the same number that device manager shows in the driver properties for a device.
Background: I have an application that talks to some custom hardware. The device driver for the custom hardware has known bugs before a certain version number. I want the application to check the driver version and warn the user if they need to update it. The application runs on Windows XP and 7 and is written in C++.
A previous hack I used was to read the .sys file directly from system32/drivers and search for "FileVersion" directly. This is bad for many reasons. In particular it seems to need admin privileges on Windows 7.
I know the class GUID and the hardware ID (ie "USB\VID_1234&PID_5678").
The application currently uses SetupDiGetClassDevs, SetupDiEnumDeviceInterfaces and then SetupDiGetDeviceInterfaceDetail to get the "DevicePath". It then calls CreateFile with that path to talk to the driver.
It looks like I need to get a SP_DRVINFO_DATA structure from somewhere. I've tried various functions from setupapi.h, such as SetupDiGetDeviceInterfaceDetail. Here's some code I've tried that fails:
int main(void)
{
HDEVINFO DeviceInfoSet = SetupDiGetClassDevs((LPGUID)&GUID_DEVINTERFACE_USBSPI, NULL, NULL,
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
SP_INTERFACE_DEVICE_DATA InterfaceDeviceData;
InterfaceDeviceData.cbSize = sizeof(SP_INTERFACE_DEVICE_DATA);
// Cycle through all devices.
for (int i = 0; i < 32; i++)
{
if (!SetupDiEnumDeviceInterfaces(DeviceInfoSet, 0, (LPGUID)&GUID_DEVINTERFACE_USBSPI, i, &InterfaceDeviceData))
break;
PSP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData;
DWORD RequiredSize;
SetupDiGetDeviceInterfaceDetail(DeviceInfoSet, &InterfaceDeviceData, NULL, 0, &RequiredSize, NULL);
DeviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS | HEAP_ZERO_MEMORY, RequiredSize);
try
{
DeviceInterfaceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
SetupDiGetDeviceInterfaceDetail(DeviceInfoSet, &InterfaceDeviceData, DeviceInterfaceDetailData, RequiredSize, NULL, NULL);
// Try to get the driver info. This part always fails with code
// 259 (ERROR_NO_MORE_ITEMS).
SP_DRVINFO_DATA drvInfo;
drvInfo.cbSize = sizeof(SP_DRVINFO_DATA);
if (!SetupDiEnumDriverInfo(DeviceInfoSet, NULL, SPDIT_CLASSDRIVER, i, &drvInfo))
printf("error = %d\n", GetLastError());
printf("Driver version is %08x %08x\n", drvInfo.DriverVersion >> 32, drvInfo.DriverVersion & 0xffffffff);
}
catch(...)
{
HeapFree(GetProcessHeap(), 0, DeviceInterfaceDetailData);
throw;
}
HeapFree(GetProcessHeap(), 0, DeviceInterfaceDetailData);
}
SetupDiDestroyDeviceInfoList(DeviceInfoSet);
return 0;
}
Edit - My updated code now looks like this:
HDEVINFO devInfoSet = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USBSPI, NULL, NULL,
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
// Cycle through all devices.
for (int i = 0; ; i++)
{
// Get the device info for this device
SP_DEVINFO_DATA devInfo;
devInfo.cbSize = sizeof(SP_DEVINFO_DATA);
if (!SetupDiEnumDeviceInfo(devInfoSet, i, &devInfo))
break;
// Get the first info item for this driver
SP_DRVINFO_DATA drvInfo;
drvInfo.cbSize = sizeof(SP_DRVINFO_DATA);
if (!SetupDiEnumDriverInfo(devInfoSet, &devInfo, SPDIT_COMPATDRIVER, 0, &drvInfo))
printf("err - %d\n", GetLastError()); // Still fails with "no more items"
}
SetupDiDestroyDeviceInfoList(devInfoSet);
You're incorrectly reusing i as index in SetupDiEnumDriverInfo. That should be an inner loop for each driver info element per driver. As a result, you fail to retrieve driver info #0 for device #1.
Still, that doesn't explain why info #0 for device #0 fails. For that, you have to look at the second parameter of SetupDiEnumDriverInfo. That is a SP_DEVINFO_DATA structure for your device, but you leave it set to NULL. That gets you the list of drivers associated with the device class, not the device. I.e. that works for mice and USB sticks, which have class drivers. Your device probably has a vendor-specific driver, so you need the driver for that specific device.
As you asked a nearly identical question I post only the link to my answer here:
Why does SetupDiEnumDriverInfo give two version numbers for my driver

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 );
}