我有一个场景,其中应用程序需要创建一个自签名证书,并将其添加到远程计算机上的证书存储中。我已经尝试了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仍然出现...
私钥从未存储在证书中。它存储在加密提供程序容器中。函数CertSetCertificateContextProperty
将属性设置为证书上下文(不等于编码证书),这仅链接到加密提供程序容器。此链接对另一个组件没有意义,因为不存在这样的容器。和证书上下文仅存在于内存中或存储在证书存储中,在另一个组件中也不存在因此我们需要将证书+私钥