通过CryptAPI生成并添加到远程证书存储区的自签名证书没有私钥

问题描述 投票:0回答:1

我有一个场景,其中应用程序需要创建一个自签名证书,并将其添加到远程计算机上的证书存储中。我已经尝试了CryptAPI和CNG(尽管CNG似乎仍然使用CryptAPI来创建自签名证书并将其添加到远程证书存储中),但是我看到的行为在两者中都发生。

环境:

位于同一域上的两台计算机。一个是Windows Server 2016 Standard,另一个是Windows Server 2019 Datacenter。具有管理员特权的同一域用户用于登录两台计算机。在2016年的计算机上运行该应用程序,指示另一个的主机名。

该代码对CString使用MFC / ATL,包括wincrypt.h,并指向crypt32.lib。已针对VS2019工具集和VS2005工具集进行了测试。

我看到的是:

将创建自签名证书,并将其添加到远程存储中。如果我通过MMC查看证书,则表明该证书已附加了私钥。但是,当我尝试单击“管理私钥...”时,出现一个错误对话框:

"No keys found for certificate!"

类似地,证书导出向导说:

"Note: The associated private key cannot be found. Only the certificate can be exported."

并且“是,导出私钥”选项显示为灰色。

我的理论是,私钥没有正确地附加到证书上下文(或者当我将密钥添加到证书存储区时,否则没有传输到远程机器)。>>

这是我的代码的样子:

#include <afx.h>
#include <wincrypt.h>

#pragma comment(lib, "crypt32.lib")

CString GetCertificateThumbprintString(PCCERT_CONTEXT pCertContext)
{
    DWORD cbSize;
    if (!CryptHashCertificate(0, 0, 0, pCertContext->pbCertEncoded, pCertContext->cbCertEncoded, NULL, &cbSize)) {
        return "";
    }

    LPSTR pszString;
    if (!(pszString = (LPSTR)malloc(cbSize * sizeof(TCHAR)))) {
        return "";
    }

    if (!CryptHashCertificate(0, 0, 0, pCertContext->pbCertEncoded, pCertContext->cbCertEncoded, (BYTE*)pszString, &cbSize)) {
        free(pszString);
        return "";
    }
    else {
        pszString[cbSize] = NULL;
        LPTSTR lpszTP = NULL;
        if (!(lpszTP = (LPTSTR)malloc((cbSize + 1) * sizeof(TCHAR) * 2))) {
            free(pszString);
            return "";
        }

        for (int i = 0; i < (int)cbSize; ++i) {
            _stprintf_s(&lpszTP[i * 2], sizeof(TCHAR) + 1, _T("%.2X"), pszString[i] & 0xff);
        }

        CString result = lpszTP;

        free(lpszTP);
        free(pszString);

        return result;
    }
}

CString CreateCertificate(CString hostName)
{
    wchar_t subjectName [MAX_PATH] = L"";
    wchar_t store       [MAX_PATH] = L"";

    wsprintfW(store, L"\\\\%s\\MY", hostName);
    wsprintfW(subjectName, L"CN=%s", hostName);

    HCERTSTORE certStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, NULL, CERT_SYSTEM_STORE_LOCAL_MACHINE, store);
    if (NULL == certStore) {
        _tprintf(_T("Failed to open Personal certificate store of %s."), hostName);
        return "";
    }

    DWORD encodedSubjectSize = 0;
    if (!CertStrToName(X509_ASN_ENCODING, subjectName, CERT_X500_NAME_STR, NULL, NULL, &encodedSubjectSize, NULL)) {
        _tprintf(_T("Invalid certificate subject name. Error %d\n"), GetLastError());
        return "";
    }

    BYTE* encodedSubject = (BYTE*)malloc(encodedSubjectSize);
    if (NULL == encodedSubject) {
        _tprintf(_T("malloc() failed: %d "), GetLastError());
        return "";
    }

    if (!CertStrToName(X509_ASN_ENCODING, subjectName, CERT_X500_NAME_STR, NULL, encodedSubject, &encodedSubjectSize, NULL)) {
        _tprintf(_T("Invalid certificate subject name. Error %d\n"), GetLastError());
        free(encodedSubject);
        return "";
    }

    // Acquire key container
    HCRYPTPROV cryptProvider;
    const wchar_t* pszKeyContainerName = L"TESTKEYCONTAINERTEST";
    if (!CryptAcquireContext(&cryptProvider, pszKeyContainerName, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET | CRYPT_MACHINE_KEYSET)) {
        if (GetLastError() == NTE_EXISTS)
        {
            if (!CryptAcquireContext(&cryptProvider, pszKeyContainerName, NULL, PROV_RSA_FULL, CRYPT_MACHINE_KEYSET))
            {
                _tprintf(_T("Can't get a crypto provider. Error %d\n"), GetLastError());
                free(encodedSubject);
                return "";
            }
        }
    }

    // Generate new key pair
    HCRYPTKEY key;
    if (!CryptGenKey(cryptProvider, AT_SIGNATURE, 0x08000000 /* RSA-2048-BIT_KEY */ | CRYPT_EXPORTABLE, &key)) {
        _tprintf(_T("Can't generate a key pair. Error %d\n"), GetLastError());

        CryptReleaseContext(cryptProvider, 0);
        CertCloseStore(certStore, 0);
        free(encodedSubject);

        return "";
    }

    // Prepare key provider structure for self-signed certificate
    CRYPT_KEY_PROV_INFO keyProviderInfo;
    ZeroMemory(&keyProviderInfo, sizeof(keyProviderInfo));
    keyProviderInfo.pwszContainerName = (LPWSTR)pszKeyContainerName;
    keyProviderInfo.dwProvType = PROV_RSA_FULL;
    keyProviderInfo.dwFlags = CRYPT_MACHINE_KEYSET;
    keyProviderInfo.dwKeySpec = AT_SIGNATURE;

    // Prepare algorithm structure for self-signed certificate
    CRYPT_ALGORITHM_IDENTIFIER algorithm;
    memset(&algorithm, 0, sizeof(algorithm));
    algorithm.pszObjId = (LPSTR)szOID_RSA_SHA256RSA;

    // Prepare certificate Subject for self-signed certificate
    CERT_NAME_BLOB subjectBlob;
    ZeroMemory(&subjectBlob, sizeof(subjectBlob));
    subjectBlob.cbData = encodedSubjectSize;
    subjectBlob.pbData = encodedSubject;

    PCCERT_CONTEXT certContext = CertCreateSelfSignCertificate(NULL, &subjectBlob, 0, &keyProviderInfo, &algorithm, NULL, NULL, NULL);
    if (!certContext) {
        _tprintf(_T("Can't create a self-signed certificate. Error %d\n"), GetLastError());

        CryptDestroyKey(key);
        CryptReleaseContext(cryptProvider, 0);
        CertCloseStore(certStore, 0);
        free(encodedSubject);

        return "";
    }

    if (!CertSetCertificateContextProperty(certContext, CERT_KEY_PROV_INFO_PROP_ID, 0, &keyProviderInfo)) {
        _tprintf(_T("Unable to set key provider info property on certificate context. Error %d\n"), GetLastError());

        CertFreeCertificateContext(certContext);
        CryptDestroyKey(key);
        CryptReleaseContext(cryptProvider, 0);
        CertCloseStore(certStore, 0);
        free(encodedSubject);

        return "";
    }

    // add certificate to store
    if (!CertAddCertificateContextToStore(certStore, certContext, CERT_STORE_ADD_ALWAYS, nullptr))
    {
        CertFreeCertificateContext(certContext);
        CryptDestroyKey(key);
        CryptReleaseContext(cryptProvider, 0);
        CertCloseStore(certStore, 0);
        free(encodedSubject);

        return "";
    }

    CString result = GetCertificateThumbprintString(certContext);

    CertFreeCertificateContext(certContext);
    CryptDestroyKey(key);
    CryptReleaseContext(cryptProvider, 0);
    CertCloseStore(certStore, 0);
    free(encodedSubject);

    return result;
}

int main(int argc, char* argv[])
{
    if (argc < 2) {
        printf("Need arg.");
        return -1;
    }

    auto index = 1;
    if (strcmp(argv[1], "dbg") == 0) {
        while (!IsDebuggerPresent()) Sleep(100);

        index = 2;
    }

    CString strThumbprint = CreateCertificate(argv[index]);
    _tprintf(strThumbprint);

    return 0;
}

我发现了类似的问题here,这就是让我想到首先调用CertSetCertificateContextProperty的原因。但这不能解决问题。

我在这里想念什么?

编辑:当打开的证书存储在本地计算机上时,此相同的代码也有效。仅当证书存储位于远程计算机上时,才会出现此问题。

编辑2:根据RbMm的建议,我研究了将证书导出到PFX中的情况。对我来说,目前尚不清楚将证书导出到PFX中会如何改变,除了可能生成的PFX会做一些魔术,以允许在将密钥对插入到远程存储区时传递密钥对。

[在那次调查中,我发现this帮助我更改了代码以使用PFXExportCertStoreEx / PFXImportCertStore。您在下面看到的是我添加的内容(替换了CertSetCertificateContextProperty调用)。但是,应注意这也不起作用

。所以我又一头雾水。
// Create temporary store to shove the self-signed certificate into.
HCERTSTORE initialTempStore = CertOpenStore(CERT_STORE_PROV_MEMORY, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, NULL, CERT_SYSTEM_STORE_LOCAL_MACHINE, L"MY");
if (NULL == initialTempStore) {
    _tprintf(_T("Failed to open local Personal certificate store."));

    CertFreeCertificateContext(certContext);
    CryptDestroyKey(key);
    CryptReleaseContext(cryptProvider, 0);
    free(encodedSubject);

    return "";
}

// Add the certificate to the self-signed store.
if (!CertAddCertificateContextToStore(initialTempStore, certContext, CERT_STORE_ADD_REPLACE_EXISTING, nullptr)) {
    _tprintf(_T("Failed to add cert to local store."));

    CertCloseStore(initialTempStore, 0);
    CertFreeCertificateContext(certContext);
    CryptDestroyKey(key);
    CryptReleaseContext(cryptProvider, 0);
    free(encodedSubject);

    return "";
}

// Export the certificate store into a PFX that packages the certificates with the private keys.
CRYPT_DATA_BLOB pfx;
ZeroMemory(&pfx, sizeof(CRYPT_DATA_BLOB));
LPCTSTR password = L"hello5";
if (!PFXExportCertStoreEx(initialTempStore, &pfx, password, NULL, EXPORT_PRIVATE_KEYS)) {
    _tprintf(_T("Unable to export PFX.\n"));

    CertCloseStore(initialTempStore, 0);
    CertDeleteCertificateFromStore(certContext);
    CertFreeCertificateContext(certContext);
    CryptDestroyKey(key);
    CryptReleaseContext(cryptProvider, 0);
    free(encodedSubject);

    return "";
}

pfx.pbData = (BYTE*)malloc(pfx.cbData);
if (!PFXExportCertStoreEx(initialTempStore, &pfx, password, NULL, EXPORT_PRIVATE_KEYS)) {
    _tprintf(_T("Unable to export PFX.\n"));

    CertDeleteCertificateFromStore(certContext);
    CertFreeCertificateContext(certContext);
    CryptDestroyKey(key);
    CryptReleaseContext(cryptProvider, 0);
    free(encodedSubject);
    free(pfx.pbData);

    return "";
}

// Now we don't need anything we had before because the PFX contains it all.
CertFreeCertificateContext(certContext);
CertCloseStore(initialTempStore, 0);
CryptDestroyKey(key);
CryptReleaseContext(cryptProvider, 0);
free(encodedSubject);

// Import the cert into a temporary store marking the keys as exportable.
HCERTSTORE tempStoreWithKeys = PFXImportCertStore(&pfx, password, CRYPT_EXPORTABLE | CRYPT_MACHINE_KEYSET);
if (tempStoreWithKeys == NULL) {
    _tprintf(_T("Unable to import PFX.\n"));
    PrintError((LPTSTR)_T("PFXImportCertStore"));

    free(encodedSubject);
    free(pfx.pbData);

    return "";
}

// Search through the temporary store to find the cert we want.
PCCERT_CONTEXT certWithPrivateKey = CertEnumCertificatesInStore(tempStoreWithKeys, nullptr);
if (certWithPrivateKey == NULL) {
    _tprintf(_T("Unable to enumerate temporary store. Error %d\n"), GetLastError());

    free(encodedSubject);
    free(pfx.pbData);

    return "";
}

while (certWithPrivateKey) {
    DWORD requiredSize = CertNameToStr(X509_ASN_ENCODING, &certWithPrivateKey->pCertInfo->Issuer, CERT_X500_NAME_STR, NULL, NULL);
    LPTSTR decodedSubject = (LPTSTR)malloc(requiredSize * sizeof(TCHAR));
    if (NULL == decodedSubject) {
        _tprintf(_T("malloc() failed: %d "), GetLastError());

        free(pfx.pbData);

        return "";
    }

    if (!CertNameToStr(X509_ASN_ENCODING, &certWithPrivateKey->pCertInfo->Issuer, CERT_X500_NAME_STR, decodedSubject, requiredSize)) {
        _tprintf(_T("Invalid certificate subject name. Error %d\n"), GetLastError());

        CertFreeCertificateContext(certWithPrivateKey);
        CertCloseStore(tempStoreWithKeys, 0);
        free(decodedSubject);
        free(pfx.pbData);

        return "";
    }

    if (_tcsncmp(subjectName, decodedSubject, requiredSize) != 0) {
        free(decodedSubject);
        certWithPrivateKey = CertEnumCertificatesInStore(tempStoreWithKeys, certWithPrivateKey);
        continue;
    }

    free(decodedSubject);
    break;
}

if (!certWithPrivateKey) {
    _tprintf(_T("No matching cert found in store\n."));

    CertFreeCertificateContext(certWithPrivateKey);
    CertCloseStore(tempStoreWithKeys, 0);
    free(pfx.pbData);

    return "";
}

编辑3:在进一步讨论之后,我也尝试了以下方法。创建临时内存存储并将证书添加到其中之后,我对结果添加操作设置CERT_KEY_CONTEXT属性,如下所示:

PCCERT_CONTEXT newCertContext;
// Add the certificate to the self-signed store.
if (!CertAddCertificateContextToStore(initialTempStore, certContext, CERT_STORE_ADD_REPLACE_EXISTING, &newCertContext)) {
    _tprintf(_T("Failed to add cert to local store."));

    CertCloseStore(initialTempStore, 0);
    CertFreeCertificateContext(certContext);
    CryptDestroyKey(key);
    CryptReleaseContext(cryptProvider, 0);
    free(encodedSubject);

    return "";
}

CERT_KEY_CONTEXT keyContext;
keyContext.cbSize = sizeof(CERT_KEY_CONTEXT);
keyContext.dwKeySpec = AT_SIGNATURE;
keyContext.hCryptProv = cryptProvider;
if (!CertSetCertificateContextProperty(newCertContext, CERT_KEY_CONTEXT_PROP_ID, 0, &keyContext)) {
    _tprintf(_T("Unable to set key context property on certificate context. Error %d\n"), GetLastError());

    CertFreeCertificateContext(certContext);
    CertFreeCertificateContext(newCertContext);
    CryptDestroyKey(key);
    CryptReleaseContext(cryptProvider, 0);
    CertCloseStore(initialTempStore, 0);
    free(encodedSubject);

    return "";
}

这也不能解决问题。

我有一个场景,其中应用程序需要创建一个自签名证书,并将其添加到远程计算机上的证书存储中。我已经尝试了CryptAPI和CNG(尽管CNG仍然出现...

winapi visual-c++ mfc visual-studio-2005 visual-studio-2019
1个回答
0
投票

私钥从未存储在证书中。它存储在加密提供程序容器中。函数CertSetCertificateContextProperty将属性设置为证书上下文(不等于编码证书),这仅链接到加密提供程序容器。此链接对另一个组件没有意义,因为不存在这样的容器。和证书上下文仅存在于内存中或存储在证书存储中,在另一个组件中也不存在因此我们需要将证书+私钥

© www.soinside.com 2019 - 2024. All rights reserved.