0

I'm trying to setup server-side encryption using SSPI. I'm successfully (as far as I can tell) loading a certificate stored as a PFX file, but the call to m_pSSPI->AcquireCredentialsHandleA() returns 0x8009030e.

This method seems to successfully load the file and return a CERT_CONTEXT object.

HRESULT CTLSPackage::LoadCertContextFromFilePFX (PCWSTR pcwzFile, PCWSTR pcwzPassword, __deref_out PCCERT_CONTEXT* ppctxCert)
{
    HRESULT hr;
    HANDLE hFile, hSection = NULL;
    BOOL (WINAPI* pfnPFXIsPFXBlob)(CRYPT_DATA_BLOB*);
    HCERTSTORE (WINAPI* pfnPFXImportCertStore)(CRYPT_DATA_BLOB*, LPCWSTR, DWORD);
    PCCERT_CONTEXT (WINAPI* pfnCertEnumCertificatesInStore)(HCERTSTORE hCertStore, PCCERT_CONTEXT pPrevCertContext);
    CRYPT_DATA_BLOB blob; blob.pbData = NULL;
    HCERTSTORE pfxStore = NULL;

    hFile = CreateFile(pcwzFile, FILE_READ_DATA, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
    CheckIfGetLastError(INVALID_HANDLE_VALUE == hFile);
    blob.cbData = GetFileSize(hFile, NULL);

    hSection = CreateFileMapping(hFile, 0, PAGE_READONLY, 0, 0, 0);
    CheckIfGetLastError(NULL == hSection);

    blob.pbData = reinterpret_cast<PBYTE>(MapViewOfFile(hSection, FILE_MAP_READ, 0, 0, 0));
    CheckIfGetLastError(NULL == blob.pbData);

    Check(TGetFunction(m_hCrypt32, "PFXIsPFXBlob", &pfnPFXIsPFXBlob));
    Check(TGetFunction(m_hCrypt32, "PFXImportCertStore", &pfnPFXImportCertStore));
    Check(TGetFunction(m_hCrypt32, "CertEnumCertificatesInStore", &pfnCertEnumCertificatesInStore));

    CheckIf(!pfnPFXIsPFXBlob(&blob), E_FAIL);
    pfxStore = pfnPFXImportCertStore(&blob, pcwzPassword, CRYPT_MACHINE_KEYSET | CRYPT_EXPORTABLE);
    CheckIf(NULL == pfxStore, SEC_E_NO_CREDENTIALS);

    *ppctxCert = pfnCertEnumCertificatesInStore(pfxStore, NULL);
    CheckIf(NULL == *ppctxCert, SEC_E_NO_CREDENTIALS);

Cleanup:
    if(pfxStore)
    {
        BOOL (WINAPI* pfnCertCloseStore)(HCERTSTORE, DWORD);
        if(SUCCEEDED(TGetFunction(m_hCrypt32, "CertCloseStore", &pfnCertCloseStore)))
            pfnCertCloseStore(pfxStore, 0);
    }
    if(blob.pbData)
        UnmapViewOfFile(blob.pbData);
    SafeCloseHandle(hSection);
    SafeCloseFileHandle(hFile);
    return hr;
}

The result is immediately passed to another class method, which makes the failing AcquireCredentialsHandleA() call.

HRESULT CTLSPackage::AcquireCredentials (__in_opt PCCERT_CONTEXT pCertContext, PCredHandle phCreds)
{
    SCHANNEL_CRED SchannelCred;
    TimeStamp tsExpiry;

    ZeroMemory(&SchannelCred, sizeof(SchannelCred));

    SchannelCred.dwVersion = SCHANNEL_CRED_VERSION;
    if(pCertContext)
    {
        SchannelCred.cCreds = 1;
        SchannelCred.paCred = &pCertContext;
    }
    SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1 | SP_PROT_TLS1_1 | SP_PROT_TLS1_2;

    if(!m_fServer)
        SchannelCred.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS | SCH_USE_STRONG_CRYPTO;

    //
    // Create an SSPI credential.
    //

    return m_pSSPI->AcquireCredentialsHandleA(
                        NULL,                   // Name of principal
                        m_fServer ? NEGOSSP_NAME_A : UNISP_NAME_A,  // Name of package
                        m_fServer ? SECPKG_CRED_INBOUND : SECPKG_CRED_OUTBOUND,
                        NULL,                   // Pointer to logon ID
                        &SchannelCred,          // Package specific data
                        NULL,                   // Pointer to GetKey() func
                        NULL,                   // Value to pass to GetKey()
                        phCreds,                // (out) Cred Handle
                        &tsExpiry);             // (out) Lifetime (optional)
}

My CTLSPackage::AcquireCredentials() code path is also used for setting up client-side encryption, and that works. For the server-side path, m_fServer is TRUE. The m_hCrypt32 member was loaded from Crypt32.dll.

I've cobbled together this code from samples, but I must be missing something for the server case. I only need to setup SSL/TLS-style encryption, so the "No credentials are available in the security package" error is weird because I have no need for credential authentication.

Does anyone know what might be missing? Thanks!

David
  • 76
  • 7
  • `AcquireCredentialsHandle` need information about private key for certificate - how access it. and your `pCertContext` not containing such information – RbMm Jul 06 '20 at 13:09
  • Am I loading the certificate file incorrectly? What do I need to do differently so that `pCertContext` contains the required information? – David Jul 06 '20 at 14:57
  • simply load it from *pfx* not best solution here. – RbMm Jul 06 '20 at 15:02
  • In some cases, I do want to load it from a PFX file. Do you know how I can fix my loader? – David Jul 06 '20 at 15:03
  • you(more exactly *AcquireCredentialsHandle*) need access private key. the `PFXImportCertStore` not return it (despite internal it in pfx). – RbMm Jul 06 '20 at 15:08
  • Can you tell me how to extract the private key in a way that `AcquireCredentialsHandle()` expects, please? – David Jul 06 '20 at 15:32
  • `AcquireCredentialsHandle` usual query `CERT_KEY_PROV_INFO_PROP_ID` via `CertGetCertificateContextProperty` for get info about provider and then private key from provider – RbMm Jul 06 '20 at 16:27
  • Okay, I'll look into that next. I also found https://www.codeproject.com/articles/125124/how-to-use-certificate-from-disk-with-microsoft-cr that describes using `CryptAcquireCertificatePrivateKey()` to obtain the private key after loading a certificate from disk. That call is successful, but I don't know what I need to do with the handle in order to use `AcquireCredentialsHandle()`. – David Jul 06 '20 at 16:34
  • are you sure that `CryptAcquireCertificatePrivateKey` not return `CRYPT_E_NO_KEY_PROPERTY` error ? if not then `CertGetCertificateContextProperty` with `CERT_KEY_PROV_INFO_PROP_ID` also must be ok – RbMm Jul 06 '20 at 16:53
  • Yes, the `CryptAcquireCertificatePrivateKey()` is successful. It gives me a provider handle too (key spec is 1), but I don't know what to do with that, and the `AcquireCredentialsHandle()` still fails. – David Jul 06 '20 at 16:58
  • For now, I think I've found the correct combination. The `CryptAcquireCertificatePrivateKey()` call is making a difference, but I needed to always pass `UNISP_NAME_A` to `AcquireCredentialsHandleA()` too. – David Jul 06 '20 at 20:08
  • @David You can post an answer to share your solution. That maybe helpful for others are searching on similar issue. – Rita Han Jul 17 '20 at 07:27

1 Answers1

-1

With a hint from RbMm, I then found this article: https://www.codeproject.com/articles/125124/how-to-use-certificate-from-disk-with-microsoft-cr

The short answer is that CryptAcquireCertificatePrivateKey() needed to be used when loading a PFX from a file, and UNISP_NAME_A needed to be passed to AcquireCredentialsHandleA().

For reference, here is the revised code:

HRESULT CTLSPackage::LoadCertContextFromFilePFX (PCWSTR pcwzFile, PCWSTR pcwzPassword, __deref_out PCCERT_CONTEXT* ppctxCert)
{
    HRESULT hr;
    HANDLE hFile, hSection = NULL;
    BOOL (WINAPI* pfnPFXIsPFXBlob)(CRYPT_DATA_BLOB*);
    HCERTSTORE (WINAPI* pfnPFXImportCertStore)(CRYPT_DATA_BLOB*, LPCWSTR, DWORD);
    PCCERT_CONTEXT (WINAPI* pfnCertFindCertificateInStore)(HCERTSTORE hCertStore, DWORD dwCertEncodingType, DWORD dwFindFlags, DWORD dwFindType, const void* pvFindPara, PCCERT_CONTEXT pPrevCertContext);
    BOOL (WINAPI* pfnCryptAcquireCertificatePrivateKey)(PCCERT_CONTEXT pCert, DWORD dwFlags, void* pvReserved, HCRYPTPROV_OR_NCRYPT_KEY_HANDLE *phCryptProvOrNCryptKey, DWORD* pdwKeySpec, BOOL* pfCallerFreeProvOrNCryptKey);
    HCRYPTPROV_OR_NCRYPT_KEY_HANDLE hProv;
    DWORD dwKeySpec;
    BOOL fFreeProv = FALSE;
    CRYPT_DATA_BLOB blob; blob.pbData = NULL;
    HCERTSTORE hpfxStore = NULL;

    hFile = CreateFile(pcwzFile, FILE_READ_DATA, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
    CheckIfGetLastError(INVALID_HANDLE_VALUE == hFile);
    blob.cbData = GetFileSize(hFile, NULL);

    hSection = CreateFileMapping(hFile, 0, PAGE_READONLY, 0, 0, 0);
    CheckIfGetLastError(NULL == hSection);

    blob.pbData = reinterpret_cast<PBYTE>(MapViewOfFile(hSection, FILE_MAP_READ, 0, 0, 0));
    CheckIfGetLastError(NULL == blob.pbData);

    Check(TGetFunction(m_hCrypt32, "PFXIsPFXBlob", &pfnPFXIsPFXBlob));
    Check(TGetFunction(m_hCrypt32, "PFXImportCertStore", &pfnPFXImportCertStore));
    Check(TGetFunction(m_hCrypt32, "CertFindCertificateInStore", &pfnCertFindCertificateInStore));
    Check(TGetFunction(m_hCrypt32, "CryptAcquireCertificatePrivateKey", &pfnCryptAcquireCertificatePrivateKey));

    CheckIf(!pfnPFXIsPFXBlob(&blob), HRESULT_FROM_WIN32(ERROR_BAD_FORMAT));
    hpfxStore = pfnPFXImportCertStore(&blob, pcwzPassword, 0);
    if(NULL == hpfxStore && pcwzPassword && L'\0' == *pcwzPassword)
    {
        hpfxStore = pfnPFXImportCertStore(&blob, NULL, 0);
        CheckIf(NULL == hpfxStore, SEC_E_NO_CREDENTIALS);
    }

    *ppctxCert = pfnCertFindCertificateInStore(hpfxStore, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_ANY, NULL, NULL);
    CheckIfGetLastError(NULL == *ppctxCert);

    // Acquire the private key and make it available for the later AcquireCredentalsHandle() call.
    if(!pfnCryptAcquireCertificatePrivateKey(*ppctxCert, 0, NULL, &hProv, &dwKeySpec, &fFreeProv))
    {
        DWORD dwError = GetLastError();
        FreeCertificateContext(*ppctxCert);
        *ppctxCert = NULL;
        CheckWin32Error(dwError);
    }

Cleanup:
    if(fFreeProv)
        FreeProvOrNCryptKey(hProv, dwKeySpec);
    if(hpfxStore)
    {
        BOOL (WINAPI* pfnCertCloseStore)(HCERTSTORE, DWORD);
        if(SUCCEEDED(TGetFunction(m_hCrypt32, "CertCloseStore", &pfnCertCloseStore)))
            pfnCertCloseStore(hpfxStore, 0);
    }
    if(blob.pbData)
        UnmapViewOfFile(blob.pbData);
    SafeCloseHandle(hSection);
    SafeCloseFileHandle(hFile);
    return hr;
}

HRESULT CTLSPackage::AcquireCredentials (__in_opt PCCERT_CONTEXT pCertContext, PCredHandle phCreds)
{
    SCHANNEL_CRED SchannelCred;
    TimeStamp tsExpiry;

    ZeroMemory(&SchannelCred, sizeof(SchannelCred));

    SchannelCred.dwVersion = SCHANNEL_CRED_VERSION;
    if(pCertContext)
    {
        SchannelCred.cCreds = 1;
        SchannelCred.paCred = &pCertContext;
    }
    SchannelCred.grbitEnabledProtocols = SP_PROT_SSL3 | SP_PROT_TLS1 | SP_PROT_TLS1_1 | SP_PROT_TLS1_2;

    SchannelCred.dwFlags = SCH_USE_STRONG_CRYPTO;
    if(!m_fServer)
        SchannelCred.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS;

    //
    // Create an SSPI credential.
    //

    return m_pSSPI->AcquireCredentialsHandleA(
                        NULL,                   // Name of principal
                        UNISP_NAME_A,           // Name of package
                        m_fServer ? SECPKG_CRED_INBOUND : SECPKG_CRED_OUTBOUND,
                        NULL,                   // Pointer to logon ID
                        &SchannelCred,          // Package specific data
                        NULL,                   // Pointer to GetKey() func
                        NULL,                   // Value to pass to GetKey()
                        phCreds,                // (out) Cred Handle
                        &tsExpiry);             // (out) Lifetime (optional)
}
David
  • 76
  • 7