У меня есть сценарий, в котором приложению необходимо создать самозаверяющий сертификат и добавить его в хранилище сертификатов на удаленном компьютере. Я пробовал и CryptAPI, и CNG (хотя CNG по-прежнему использует CryptAPI для создания самозаверяющего сертификата и добавления его в удаленное хранилище сертификатов), но поведение, которое я вижу, имеет место в обеих.
Среда:
Две машины в одном домене. Один из них - Windows Server 2016 Standard, а другой - Windows Server 2019 Datacenter. Один и тот же пользователь домена с правами администратора используется для входа на обе машины. Запустите приложение на компьютере 2016 года, указав имя другого хоста.
Код использует MFC / ATL для CString, включает wincrypt.h и ссылки на crypt32.lib. Проверено как на наборе инструментов VS2019, так и на наборе инструментов VS2005.
Что я вижу:
Самоподписанный сертификат создается и добавляется в удаленное хранилище. Если я просматриваю сертификат через MM C, это означает, что к нему прикреплен закрытый ключ. Однако, когда я пытаюсь нажать «Управление личными ключами ...», появляется сообщение об ошибке:
«Ключи для сертификата не найдены!»
Аналогично экспорт сертификата Мастер говорит:
«Примечание: связанный закрытый ключ не может быть найден. Только сертификат может быть экспортирован.»
и «Да, экспортировать закрытый ключ» опция неактивна.
Моя теория заключается в том, что закрытый ключ неправильно подключен к контексту сертификата (или не передается на удаленный компьютер, когда я добавляю ключ в хранилище сертификатов).
Вот как выглядит мой код:
#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;
}
Я обнаружил похожую проблему здесь , что и дало мне идею вызвать CertSetCertificateContextProperty. Но это не решает проблему.
Что мне здесь не хватает?
Редактировать: Этот же код работает, когда открытое хранилище сертификатов находится на локальном компьютере. Эта проблема возникает, только когда хранилище сертификатов находится на удаленной машине.
Редактировать 2: По предложению RbMm я исследовал экспорт сертификата в PFX. Мне было неясно, как экспорт сертификатов в PFX изменит что-либо, за исключением того, что, возможно, сгенерированный PFX сделает несколько магий c, чтобы разрешить передачу пары ключей, когда я вставлю ее в удаленное хранилище.
В этом исследовании я обнаружил этот , который помог мне изменить мой код для использования 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 "";
}
Это также не решает проблему.