Search code examples
windowsdigital-signatureprivate-keycryptoapi

How CSP find the private key of certificate to perform cryptographic operations?


My question is: when an application invoke CSP for performing cryptographic operation, such as signing, how CSP find private key of certifcate respectively?
If a certificate imported to cert store that the private key not in local computer (on USB token, external storage, e.g. mobile device), it can found?


Solution

  • When you import a certificate to the system store, Windows creates a BLOB structure that contains an encoded certificate itself and it properties. BLOB has following structure:

    property1_id (4 bytes)
    reserved = 0x00000001
    property1_length (4 bytes)
    property1_data[property1_length]
    ...
    cert_property_id = 0x00000020
    reserved = 0x00000001
    cert_data_length (4 bytes)
    cert_data[cert_data_length]
    

    Therefore, if you want that your imported certificate to have link to a private key, you need to set CERT_KEY_PROV_INFO_PROP_ID. You can achieve that with CRYPT_KEY_PROV_INFO structure and CertSetCertificateContextProperty function.

    For example:

    #include <Windows.h>
    #include <wincrypt.h>
    
    void SetKeyLink()
    {
        HCERTSTORE hStore = NULL;
        CRYPT_KEY_PROV_INFO key_prov_info = { 0 };
        PCCERT_CONTEXT pCertContext = nullptr;
        std::vector<BYTE> der_encoded_cert;
    
        hStore = CertOpenSystemStore(NULL, L"MY");
        if (!hStore)
        {
            goto Exit;
        }
    
        der_encoded_cert = LoadFromFile();
    
        pCertContext = CertCreateCertificateContext(X509_ASN_ENCODING, der_encoded_cert.data(), der_encoded_cert.size());
        if (!pCertContext)
        {
            goto Exit;
        }
    
        /* For legacy CSP */
        key_prov_info.dwProvType = PROV_RSA_AES; // Or YOUR_PROVIDER_TYPE
        key_prov_info.dwKeySpec = AT_SIGNATURE; // Or AT_KEYEXCHANGE
        key_prov_info.pwszContainerName = L"Your_key_name";
        key_prov_info.dwFlags = CERT_SET_KEY_PROV_HANDLE_PROP_ID;
        key_prov_info.cProvParam = 0;
        key_prov_info.pwszProvName = nullptr;
        key_prov_info.rgProvParam = 0;
    
        /*
        Or if you use CNG Key storage provider:
    
        // Or L"Your_CNG_key_storage_provider_name"
        key_prov_info.pwszProvName = L"Microsoft Software Key Storage Provider"; 
        key_prov_info.pwszContainerName = L"Your_key_name";
        key_prov_info.dwFlags = CERT_SET_KEY_PROV_HANDLE_PROP_ID;
        key_prov_info.dwProvType = 0;
        key_prov_info.dwKeySpec = 0;
        key_prov_info.cProvParam = 0;
        key_prov_info.rgProvParam = 0;
        */
    
        if (!CertSetCertificateContextProperty(pCertContext, CERT_KEY_PROV_INFO_PROP_ID, 0, &key_prov_info))
        {
            goto Exit;
        }
    
        if (!CertAddCertificateContextToStore(hStore, pCertContext, CERT_STORE_ADD_ALWAYS, NULL))
        {
            goto Exit;
        }
    
        std::cout << "success";
    
    Exit:
    
        if (pCertContext)
        {
            CertFreeCertificateContext(pCertContext);
        }
    
        if (hStore)
        {
            CertCloseStore(hStore, 0);
        }
        return;
    }
    

    For now your certificate will look something like this (sorry for not English):

    test_cert

    When Windows wants to get private key, it calls CryptAcquireCertificatePrivateKey which in turn calls CertGetCertificateContextProperty(..., CERT_KEY_PROV_INFO_PROP_ID, ...).