Structure of the certificate pointed to by NCRYPT_KEY_HANDLE

0

I've written a credential provider and a key storage provider to logon to windows via certificate. As the documentation in this points is quite vague I used different samples from Microsoft to get things working.

I think I'm nearly there, but the logon behaves unpredictably. Sometimes I get through to the kerberos server (which complains about the certificate), sometimes the process fails with 0x80090029 without any information and sometimes windows crashes. As these crashes all have to do with access violations or null pointers and happen to occur in various places (kerberos.dll, Windows.UI.Logon.dll, ...) I think it has something to do with my key structure that i point the given NCRYT_KEY_HANDLE to in my OpenKey-implementation.

The KeyStorageProviderSample in the CNG-Kit has an example, but relies on a RSA-key stored in %AppData%. I don't have the private key available as it is stored in secure hardware, I just have the public part (i.e. the public certificate), that I read from another device and import via the following code:

SECURITY_STATUS WINAPI KeyHandler::ReadPemCert(__inout  KSP_KEY *keyHandle)
{
    LOG_FUNCTION;

    CERT_CONTEXT certContext = {};
    DWORD readLength = 0;

    LOG("Fetch certificate");
    const int maxSizeInBytes = 4096;
    char pemCertificateAsBytes[maxSizeInBytes];
    BluetoothClient bluetoothClient = BluetoothClient();
    bluetoothClient.getCertificate((PBYTE)pemCertificateAsBytes, readLength);

    DWORD certAsDerLen = readLength;
    BYTE* certAsDer = new BYTE[certAsDerLen];
    LOG("convert PEM to DER");
    if (!CryptStringToBinaryA(pemCertificateAsBytes, 0, CRYPT_STRING_BASE64, certAsDer, &certAsDerLen, NULL, NULL))
    {
        LOG_LAST_ERROR("CryptStringToBinary failed. Err:");
    }
    LOG_BYTES_AS_HEX("DER-Zertifikat", certAsDer, certAsDerLen);

    PCCERT_CONTEXT pcCertContext = CertCreateCertificateContext(X509_ASN_ENCODING, certAsDer, certAsDerLen);
    certContext->pCertInfo = pcCertContext->pCertInfo;
    certContext->cbCertEncoded = pcCertContext->cbCertEncoded;
    certContext->pbCertEncoded = pcCertContext->pbCertEncoded;
    certContext->dwCertEncodingType = pcCertContext->dwCertEncodingType;

    CERT_INFO *certInfo;
    certInfo = certContext.pCertInfo;

    CERT_PUBLIC_KEY_INFO pubKeyInfo = certInfo->SubjectPublicKeyInfo;

    LOG("Aquire cryptocontext");
    HCRYPTPROV hProv = 0;
    if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
    {
        {
            LOG_LAST_ERROR("CryptAcquireContext failed. Err:");
            return -1;
        }
    }

    LOG("Importing public key");
    NCRYPT_KEY_HANDLE publicKeyHandle = NULL;
    if (!CryptImportPublicKeyInfo(hProv, X509_ASN_ENCODING, &pubKeyInfo, &publicKeyHandle))
    {
        LOG_LAST_ERROR("CryptImportPublicKeyInfo failed. Err:");
        return -1;
    }

    keyHandle->fFinished = TRUE;
    keyHandle->hPublicKey = (BCRYPT_KEY_HANDLE)publicKeyHandle;
    keyHandle->pszKeyBlobType = BCRYPT_RSAPUBLIC_BLOB;

    LocalFree(certInfo);

    return ERROR_SUCCESS;
}

The key structure is initialized this way:

SECURITY_STATUS
WINAPI
KeyHandler::CreateNewKeyObject(
    __in_opt LPCWSTR pszKeyName,
    __deref_out KSP_KEY **ppKey)
{
    LOG_FUNCTION;

    KSP_KEY *pKey = NULL;
    DWORD   cbKeyName = 0;
    SECURITY_STATUS   Status = NTE_INTERNAL_ERROR;
    NTSTATUS          ntStatus = STATUS_INTERNAL_ERROR;

    pKey = (KSP_KEY *)HeapAlloc(GetProcessHeap(), 0, sizeof(KSP_KEY));
    if (pKey == NULL)
    {
        return NTE_NO_MEMORY;
    }
    pKey->cbLength = sizeof(KSP_KEY);
    pKey->dwMagic = KSP_KEY_MAGIC;
    pKey->dwAlgID = KSP_RSA_ALGID;
    pKey->pszKeyFilePath = NULL;
    pKey->pszKeyBlobType = NULL;
    pKey->dwKeyBitLength = 0;
    pKey->fFinished = FALSE;

    //Copy the keyname into the key struct.
    if (pszKeyName != NULL)
    {
        cbKeyName = (DWORD)(wcslen(pszKeyName) + 1) * sizeof(WCHAR);
        pKey->pszKeyName = (LPWSTR)HeapAlloc(
            GetProcessHeap(),
            0,
            cbKeyName + sizeof(WCHAR));
        if (pKey->pszKeyName == NULL)
        {
            return NTE_NO_MEMORY;
        }
        CopyMemory(pKey->pszKeyName, pszKeyName, cbKeyName);
        pKey->pszKeyName[cbKeyName / sizeof(WCHAR)] = L'\0';
    }
    else
    {
        pKey->pszKeyName = NULL;
    }

    if (globalRSAProviderHandle == NULL)
    {
        ntStatus = BCryptOpenAlgorithmProvider(
            &globalRSAProviderHandle,
            BCRYPT_RSA_ALGORITHM,
            NULL,
            0);
        if (!NT_SUCCESS(ntStatus))
        {
            return NormalizeNteStatus(ntStatus);
        }

    }
    pKey->hProvider = globalRSAProviderHandle;

    pKey->pbKeyFile = NULL;
    pKey->cbKeyFile = 0;

    pKey->pbPrivateKey = NULL;
    pKey->cbPrivateKey = 0;

    pKey->hPublicKey = NULL;
    pKey->hPrivateKey = NULL;

    pKey->dwExportPolicy = NCRYPT_ALLOW_EXPORT_FLAG | NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG;

    pKey->dwKeyUsagePolicy = NCRYPT_ALLOW_DECRYPT_FLAG | NCRYPT_ALLOW_SIGNING_FLAG;

    pKey->pbSecurityDescr = NULL;
    pKey->cbSecurityDescr = 0;

    InitializeListHead(&pKey->PropertyList);
    *ppKey = pKey;
    pKey = NULL;
    return ERROR_SUCCESS;
}

Somewhere in there must be the mistake leading to the various memory errors. But as I'm quite new to windows programming and c/c++ I just can't spot the point and can't find any documentation about the datastructure that windows expects for the NCRYTP_KEY_HANDLE. Does anybody know more about this structure?

windows
certificate
kerberos
cng
asked on Stack Overflow Feb 24, 2017 by Frank

1 Answer

1

NCRYPT_KEY_HANDLE is just a pointer to a structure that you defined. Windows itself doesn't care about this structure and expect that your provider knows how to work with it.

In KeyHandler::ReadPemCert you mixed legacy CryptoAPI and CNG API. Since you are implementing KSP you should use only CNG API (CryptImportPublicKeyInfoEx2).

DWORD error = NTE_FAIL;
BCRYPT_KEY_HANDLE hKey = NULL;

...

PCCERT_CONTEXT pcCertContext = CertCreateCertificateContext(X509_ASN_ENCODING, certAsDer, certAsDerLen);
if(!pcCertContext)
{
    goto Exit;
}


if (!CryptImportPublicKeyInfoEx2(X509_ASN_ENCODING, &pcCertContext->pCertInfo->SubjectPublicKeyInfo, 0, nullptr, &hKey))
{   
    goto Exit;
}

/* Also you can export key and print out the result to make sure everything works

    DWORD temp  = 0;
    status = BCryptExportKey(hKey, 0, BCRYPT_RSAPUBLIC_BLOB, nullptr, 0, &temp, 0);
    if (status != ERROR_SUCCESS)
    {
        goto Exit;
    }

    std::vector<BYTE> key(temp);
    status = BCryptExportKey(hKey, 0, BCRYPT_RSAPUBLIC_BLOB, key.data(), key.size(), &temp, 0);
    if (status != ERROR_SUCCESS)
    {
        goto Exit;
    }

    for (auto const& i : key)
    {
        std::cout << std::hex << (int)i;
    }
}
*/

keyHandle->fFinished = TRUE;
keyHandle->hPublicKey = hKey;
keyHandle->pszKeyBlobType = BCRYPT_RSAPUBLIC_BLOB;

erro = ERROR_SUCCESS;

Exit:

if(pcCertContext)
{
    CertFreeCertificateContext(pcCertContext);
}

return error;
answered on Stack Overflow Mar 21, 2017 by plstryagain

User contributions licensed under CC BY-SA 3.0