NCRYPT_KEY_HANDLE 指向的证书结构

Structure of the certificate pointed to by NCRYPT_KEY_HANDLE

我已经编写了一个凭据提供程序和一个密钥存储提供程序来通过证书登录 windows。由于这方面的文档非常模糊,我使用了来自 Microsoft 的不同样本来使事情正常进行。

我想我快到了,但登录行为异常。有时我会通过 kerberos 服务器(它抱怨证书),有时该过程失败并显示 0x80090029 没有任何信息,有时 windows 崩溃。由于这些崩溃都与访问冲突或空指针有关,并且恰好发生在不同的地方(kerberos.dll、Windows.UI.Logon.dll、...),我认为这与我的密钥结构有关将给定的 NCRYT_KEY_HANDLE 指向我的 OpenKey 实现。

CNG-Kit 中的 KeyStorageProviderSample 有一个示例,但依赖于存储在 %AppData% 中的 RSA 密钥。我没有可用的私钥,因为它存储在安全硬件中,我只有 public 部分(即 public 证书),我从另一台设备读取并通过以下代码导入:

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

密钥结构是这样初始化的:

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'[=12=]';
    }
    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;
}

一定是哪里出了问题,导致各种内存错误。但是由于我对 windows 编程和 c/c++ 很陌生,所以我无法找到要点,也找不到任何关于 windows 期望 NCRYTP_KEY_HANDLE。 有人知道更多关于这个结构的信息吗?

NCRYPT_KEY_HANDLE 只是指向您定义的结构的指针。 Windows 本身并不关心此结构,并希望您的提供商知道如何使用它。

KeyHandler::ReadPemCert 中,您混合了旧版 CryptoAPI 和 CNG API。由于您正在实施 KSP,因此您应该仅使用 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;