21#include <winpr/error.h>
22#include <winpr/ncrypt.h>
23#include <winpr/string.h>
24#include <winpr/wlog.h>
25#include <winpr/crypto.h>
26#include <winpr/path.h>
28#include <freerdp/log.h>
29#include <freerdp/freerdp.h>
30#include <winpr/print.h>
32#include <freerdp/utils/smartcardlogon.h>
33#include <freerdp/crypto/crypto.h>
34#include <freerdp/utils/helpers.h>
36#include <openssl/obj_mac.h>
38#define TAG FREERDP_TAG("smartcardlogon")
40struct SmartcardKeyInfo_st
46static void delete_file(
char* path)
53 FILE* fp = winpr_fopen(path,
"r+");
56 const char buffer[8192] = WINPR_C_ARRAY_INIT;
58 int rs = _fseeki64(fp, 0, SEEK_END);
61 if (_fseeki64(fp, 0, SEEK_SET) == 0)
63 for (INT64 x = 0; x < size; x +=
sizeof(buffer))
65 const size_t dnmemb = (size_t)(size - x);
66 const size_t nmemb = MIN(
sizeof(buffer), dnmemb);
67 const size_t count = fwrite(buffer, nmemb, 1, fp);
77 winpr_DeleteFile(path);
81static void smartcardKeyInfo_Free(SmartcardKeyInfo* key_info)
86 delete_file(key_info->certPath);
87 delete_file(key_info->keyPath);
99 freerdp_certificate_free(scCert->certificate);
100 free(scCert->pkinitArgs);
101 free(scCert->keyName);
102 free(scCert->containerName);
104 free(scCert->userHint);
105 free(scCert->domainHint);
106 free(scCert->subject);
107 free(scCert->issuer);
108 smartcardKeyInfo_Free(scCert->key_info);
118 for (
size_t i = 0; i < count; i++)
121 smartcardCertInfo_Free(cert);
124 free((
void*)pscCert);
131 size_t curCount = *count;
135 for (
size_t i = 0; i < curCount; ++i)
137 if (_wcscmp(curInfoList[i]->containerName, certInfo->containerName) == 0)
139 smartcardCertInfo_Free(certInfo);
149 WLog_ERR(TAG,
"unable to reallocate certs");
152 curInfoList = tmpInfoList;
155 curInfoList[curCount++] = certInfo;
156 *certInfoList = curInfoList;
164 WINPR_ASSERT(scCert);
166 scCert->upn = freerdp_certificate_get_upn(scCert->certificate);
169 WLog_DBG(TAG,
"%s has no UPN, trying emailAddress", scCert->keyName);
170 scCert->upn = freerdp_certificate_get_email(scCert->certificate);
176 const char* atPos = strchr(scCert->upn,
'@');
180 WLog_ERR(TAG,
"invalid UPN, for key %s (no @)", scCert->keyName);
184 userLen = (size_t)(atPos - scCert->upn);
185 scCert->userHint = malloc(userLen + 1);
186 scCert->domainHint = _strdup(atPos + 1);
188 if (!scCert->userHint || !scCert->domainHint)
190 WLog_ERR(TAG,
"error allocating userHint or domainHint, for key %s", scCert->keyName);
194 memcpy(scCert->userHint, scCert->upn, userLen);
195 scCert->userHint[userLen] = 0;
198 scCert->subject = freerdp_certificate_get_subject(scCert->certificate);
199 scCert->issuer = freerdp_certificate_get_issuer(scCert->certificate);
204static BOOL set_info_certificate(
SmartcardCertInfo* cert, BYTE* certBytes, DWORD cbCertBytes,
205 const char* userFilter,
const char* domainFilter)
207 if (!winpr_Digest(WINPR_MD_SHA1, certBytes, cbCertBytes, cert->sha1Hash,
208 sizeof(cert->sha1Hash)))
210 WLog_ERR(TAG,
"unable to compute certificate sha1 for key %s", cert->keyName);
214 cert->certificate = freerdp_certificate_new_from_der(certBytes, cbCertBytes);
215 if (!cert->certificate)
217 WLog_ERR(TAG,
"unable to parse X509 certificate for key %s", cert->keyName);
221 if (!freerdp_certificate_check_eku(cert->certificate, NID_ms_smartcard_login))
223 WLog_DBG(TAG,
"discarding certificate without Smartcard Login EKU for key %s",
228 if (!treat_sc_cert(cert))
230 WLog_DBG(TAG,
"error treating cert");
234 if (userFilter && (!cert->upn || (strcmp(cert->upn, userFilter) != 0)))
236 if (cert->userHint && strcmp(cert->userHint, userFilter) != 0)
238 WLog_DBG(TAG,
"discarding non matching cert by user %s@%s", cert->userHint,
244 if (domainFilter && cert->domainHint)
247 BOOL domainMatch = strcmp(cert->domainHint, domainFilter) == 0;
254 const char* dot = strchr(cert->domainHint,
'.');
255 const char* ddot = strchr(domainFilter,
'.');
258 const size_t filterLen = strlen(domainFilter);
259 const size_t hlen = WINPR_ASSERTING_INT_CAST(
size_t, (dot - cert->domainHint));
260 if ((hlen == filterLen) && (strncmp(domainFilter, cert->domainHint, hlen) == 0))
266 WLog_DBG(TAG,
"discarding non matching cert by domain(%s) %s@%s", domainFilter,
267 cert->userHint, cert->domainHint);
277static BOOL build_pkinit_args(NCRYPT_PROV_HANDLE provider,
SmartcardCertInfo* scCert)
282 const char* pkModule = winpr_NCryptGetModulePath(provider);
289 const char* certId =
nullptr;
292 const char* secondBackslash = strchr(scCert->keyName + 1,
'\\');
294 certId = secondBackslash + 1;
297 if (certId && *certId)
298 return (winpr_asprintf(&scCert->pkinitArgs, &size,
299 "PKCS11:module_name=%s:slotid=%" PRIu16
":certid=%s", pkModule,
300 (UINT16)scCert->slotId, certId) > 0);
302 return (winpr_asprintf(&scCert->pkinitArgs, &size,
"PKCS11:module_name=%s:slotid=%" PRIu16,
303 pkModule, (UINT16)scCert->slotId) > 0);
309static BOOL list_capi_provider_keys(
const rdpSettings* settings, LPCWSTR csp, LPCWSTR scope,
310 const char* userFilter,
const char* domainFilter,
315 HCRYPTPROV hProvider = 0;
317 BYTE* certBytes =
nullptr;
318 CHAR* readerName =
nullptr;
320 if (!CryptAcquireContextW(&hProvider, scope, csp, PROV_RSA_FULL, CRYPT_SILENT))
322 WLog_DBG(TAG,
"Unable to acquire context: %d", GetLastError());
330 cert->csp = _wcsdup(csp);
336 if (!CryptGetProvParam(hProvider, PP_SMARTCARD_READER,
nullptr, &dwDataLen, 0))
338 WLog_DBG(TAG,
"Unable to get provider param: %d", GetLastError());
342 readerName = malloc(dwDataLen);
346 if (!CryptGetProvParam(hProvider, PP_SMARTCARD_READER, readerName, &dwDataLen, 0))
348 WLog_DBG(TAG,
"Unable to get reader name: %d", GetLastError());
352 cert->reader = ConvertUtf8ToWCharAlloc(readerName,
nullptr);
358 if (!CryptGetProvParam(hProvider, PP_CONTAINER,
nullptr, &dwDataLen, 0))
360 WLog_DBG(TAG,
"Unable to get provider param: %d", GetLastError());
364 cert->keyName = malloc(dwDataLen);
368 if (!CryptGetProvParam(hProvider, PP_CONTAINER, cert->keyName, &dwDataLen, 0))
370 WLog_DBG(TAG,
"Unable to get container name: %d", GetLastError());
374 cert->containerName = ConvertUtf8ToWCharAlloc(cert->keyName,
nullptr);
375 if (!cert->containerName)
379 if (!CryptGetUserKey(hProvider, AT_KEYEXCHANGE, &hKey))
381 WLog_DBG(TAG,
"Unable to get user key for %s: %d", cert->keyName, GetLastError());
386 if (!CryptGetKeyParam(hKey, KP_CERTIFICATE,
nullptr, &dwDataLen, 0))
388 WLog_DBG(TAG,
"Unable to get key param for key %s: %d", cert->keyName, GetLastError());
392 certBytes = malloc(dwDataLen);
395 WLog_ERR(TAG,
"unable to allocate %" PRIu32
" certBytes for key %s", dwDataLen,
400 if (!CryptGetKeyParam(hKey, KP_CERTIFICATE, certBytes, &dwDataLen, 0))
402 WLog_ERR(TAG,
"unable to retrieve certificate for key %s", cert->keyName);
406 if (!set_info_certificate(cert, certBytes, dwDataLen, userFilter, domainFilter))
409 if (!add_cert_to_list(pcerts, pcount, cert))
418 CryptDestroyKey(hKey);
420 CryptReleaseContext(hProvider, 0);
422 smartcardCertInfo_Free(cert);
428static BOOL list_provider_keys(WINPR_ATTR_UNUSED
const rdpSettings* settings,
429 NCRYPT_PROV_HANDLE provider, LPCWSTR csp, LPCWSTR scope,
430 const char* userFilter,
const char* domainFilter,
435 PVOID enumState =
nullptr;
437 size_t count = *pcount;
439 while (NCryptEnumKeys(provider, scope, &keyName, &enumState, NCRYPT_SILENT_FLAG) ==
442 NCRYPT_KEY_HANDLE phKey = 0;
443 PBYTE certBytes =
nullptr;
444 DWORD dwFlags = NCRYPT_SILENT_FLAG;
447 BOOL haveError = TRUE;
448 SECURITY_STATUS status = 0;
454 cert->keyName = ConvertWCharToUtf8Alloc(keyName->pszName,
nullptr);
458 WLog_DBG(TAG,
"opening key %s", cert->keyName);
461 NCryptOpenKey(provider, &phKey, keyName->pszName, keyName->dwLegacyKeySpec, dwFlags);
462 if (status != ERROR_SUCCESS)
465 "unable to NCryptOpenKey(dwLegacyKeySpec=0x%08" PRIx32
" dwFlags=0x%08" PRIx32
466 "), status=%s, skipping",
467 keyName->dwLegacyKeySpec, keyName->dwFlags,
468 winpr_NCryptSecurityStatusError(status));
472 cert->csp = _wcsdup(csp);
477 status = NCryptGetProperty(phKey, NCRYPT_WINPR_SLOTID, (PBYTE)&cert->slotId, 4, &cbOutput,
479 if (status != ERROR_SUCCESS)
481 WLog_ERR(TAG,
"unable to retrieve slotId for key %s, status=%s", cert->keyName,
482 winpr_NCryptSecurityStatusError(status));
489 status = NCryptGetProperty(phKey, NCRYPT_READER_PROPERTY,
nullptr, 0, &cbOutput, dwFlags);
490 if (status != ERROR_SUCCESS)
492 WLog_DBG(TAG,
"unable to retrieve reader's name length for key %s", cert->keyName);
496 cert->reader = calloc(1, cbOutput + 2);
499 WLog_ERR(TAG,
"unable to allocate reader's name for key %s", cert->keyName);
503 status = NCryptGetProperty(phKey, NCRYPT_READER_PROPERTY, (PBYTE)cert->reader, cbOutput + 2,
505 if (status != ERROR_SUCCESS)
507 WLog_ERR(TAG,
"unable to retrieve reader's name for key %s", cert->keyName);
514 status = NCryptGetProperty(phKey, NCRYPT_NAME_PROPERTY,
nullptr, 0, &cbOutput, dwFlags);
515 if (status == ERROR_SUCCESS)
517 cert->containerName = calloc(1, cbOutput +
sizeof(WCHAR));
518 if (!cert->containerName)
520 WLog_ERR(TAG,
"unable to allocate key container name for key %s", cert->keyName);
524 status = NCryptGetProperty(phKey, NCRYPT_NAME_PROPERTY, (BYTE*)cert->containerName,
525 cbOutput, &cbOutput, dwFlags);
528 if (status != ERROR_SUCCESS)
530 WLog_ERR(TAG,
"unable to retrieve key container name for key %s", cert->keyName);
537 NCryptGetProperty(phKey, NCRYPT_CERTIFICATE_PROPERTY,
nullptr, 0, &cbOutput, dwFlags);
538 if (status != ERROR_SUCCESS)
541 WLog_DBG(TAG,
"unable to retrieve certificate property len, status=%s, skipping",
542 winpr_NCryptSecurityStatusError(status));
546 certBytes = calloc(1, cbOutput);
549 WLog_ERR(TAG,
"unable to allocate %" PRIu32
" certBytes for key %s", cbOutput,
554 status = NCryptGetProperty(phKey, NCRYPT_CERTIFICATE_PROPERTY, certBytes, cbOutput,
556 if (status != ERROR_SUCCESS)
558 WLog_ERR(TAG,
"unable to retrieve certificate for key %s", cert->keyName);
562 if (!set_info_certificate(cert, certBytes, cbOutput, userFilter, domainFilter))
566 if (!build_pkinit_args(provider, cert))
568 WLog_ERR(TAG,
"error build pkinit args");
576 NCryptFreeBuffer(keyName);
578 NCryptFreeObject((NCRYPT_HANDLE)phKey);
581 smartcardCertInfo_Free(cert);
584 if (!add_cert_to_list(&cert_list, &count, cert))
593 char cspa[128] = WINPR_C_ARRAY_INIT;
595 (void)ConvertWCharToUtf8(csp, cspa,
sizeof(cspa));
596 char scopea[128] = WINPR_C_ARRAY_INIT;
597 (void)ConvertWCharToUtf8(scope, scopea,
sizeof(scopea));
598 WLog_WARN(TAG,
"%s [%s] no certificates found", cspa, scopea);
602 NCryptFreeBuffer(enumState);
607static BOOL smartcard_hw_enumerateCerts(
const rdpSettings* settings, LPCWSTR csp,
608 const char* reader,
const char* userFilter,
613 LPWSTR scope =
nullptr;
614 NCRYPT_PROV_HANDLE provider = 0;
615 SECURITY_STATUS status = 0;
620 WINPR_ASSERT(scCerts);
621 WINPR_ASSERT(retCount);
625 char* scopeStr =
nullptr;
626 size_t scopeStrLen = 0;
627 winpr_asprintf(&scopeStr, &scopeStrLen,
"\\\\.\\%s\\", reader);
631 scope = ConvertUtf8NToWCharAlloc(scopeStr, scopeStrLen,
nullptr);
641 LPCSTR paths[] = { Pkcs11Module,
nullptr };
644 csp = MS_SMART_CARD_KEY_STORAGE_PROVIDER;
646 status = winpr_NCryptOpenStorageProviderEx(&provider, csp, 0, paths);
647 if (status != ERROR_SUCCESS)
649 WLog_ERR(TAG,
"unable to open provider given by pkcs11 module");
653 status = list_provider_keys(settings, provider, csp, scope, userFilter, domainFilter,
655 NCryptFreeObject((NCRYPT_HANDLE)provider);
658 WLog_ERR(TAG,
"error listing keys from CSP loaded from %s", Pkcs11Module);
665 DWORD nproviders = 0;
669 DWORD provType, cbProvName = 0;
670 for (DWORD i = 0; CryptEnumProvidersW(i,
nullptr, 0, &provType,
nullptr, &cbProvName); ++i)
672 char providerNameStr[256] = WINPR_C_ARRAY_INIT;
673 LPWSTR szProvName = malloc(cbProvName *
sizeof(WCHAR));
674 if (!CryptEnumProvidersW(i,
nullptr, 0, &provType, szProvName, &cbProvName))
680 if (ConvertWCharToUtf8(szProvName, providerNameStr, ARRAYSIZE(providerNameStr)) < 0)
682 (void)_snprintf(providerNameStr,
sizeof(providerNameStr),
"<unknown>");
683 WLog_ERR(TAG,
"unable to convert provider name to char*, will show it as '%s'",
687 WLog_DBG(TAG,
"exploring CSP '%s'", providerNameStr);
688 if (provType != PROV_RSA_FULL || (csp && _wcscmp(szProvName, csp) != 0))
690 WLog_DBG(TAG,
"CSP '%s' filtered out", providerNameStr);
694 if (!list_capi_provider_keys(settings, szProvName, scope, userFilter, domainFilter,
696 WLog_INFO(TAG,
"error when retrieving keys in CSP '%s'", providerNameStr);
703 status = NCryptEnumStorageProviders(&nproviders, &names, NCRYPT_SILENT_FLAG);
704 if (status != ERROR_SUCCESS)
706 WLog_ERR(TAG,
"error listing providers");
710 for (DWORD i = 0; i < nproviders; i++)
712 char providerNameStr[256] = WINPR_C_ARRAY_INIT;
715 if (ConvertWCharToUtf8(name->pszName, providerNameStr, ARRAYSIZE(providerNameStr)) < 0)
717 (void)_snprintf(providerNameStr,
sizeof(providerNameStr),
"<unknown>");
718 WLog_ERR(TAG,
"unable to convert provider name to char*, will show it as '%s'",
722 WLog_DBG(TAG,
"exploring CSP '%s'", providerNameStr);
723 if (csp && _wcscmp(name->pszName, csp) != 0)
725 WLog_DBG(TAG,
"CSP '%s' filtered out", providerNameStr);
729 status = NCryptOpenStorageProvider(&provider, name->pszName, 0);
730 if (status != ERROR_SUCCESS)
733 if (!list_provider_keys(settings, provider, name->pszName, scope, userFilter,
734 domainFilter, &cert_list, &count))
735 WLog_INFO(TAG,
"error when retrieving keys in CSP '%s'", providerNameStr);
737 NCryptFreeObject((NCRYPT_HANDLE)provider);
740 NCryptFreeBuffer(names);
743 *scCerts = cert_list;
749 smartcardCertList_Free(cert_list, count);
754WINPR_ATTR_MALLOC(free, 1)
755static
char* create_temporary_file(
void)
757 BYTE buffer[32] = WINPR_C_ARRAY_INIT;
758 char* path =
nullptr;
760 if (winpr_RAND(buffer,
sizeof(buffer)) < 0)
763 char* hex = winpr_BinToHexString(buffer,
sizeof(buffer), FALSE);
765 path = GetKnownSubPath(KNOWN_PATH_TEMP, hex);
770WINPR_ATTR_MALLOC(smartcardCertInfo_Free, 1)
771static
SmartcardCertInfo* smartcardCertInfo_New(const
char* privKeyPEM, const
char* certPEM)
775 WINPR_ASSERT(privKeyPEM);
776 WINPR_ASSERT(certPEM);
783 SmartcardKeyInfo* info = cert->key_info = calloc(1,
sizeof(SmartcardKeyInfo));
787 cert->certificate = freerdp_certificate_new_from_pem(certPEM);
788 if (!cert->certificate)
790 WLog_ERR(TAG,
"unable to read smartcard certificate");
794 if (!treat_sc_cert(cert))
796 WLog_ERR(TAG,
"unable to treat smartcard certificate");
806 BYTE* der = freerdp_certificate_get_der(cert->certificate, &derLen);
808 der && (derLen > 0) &&
809 winpr_Digest(WINPR_MD_SHA1, der, derLen, cert->sha1Hash,
sizeof(cert->sha1Hash));
813 WLog_ERR(TAG,
"unable to compute sha1 of the emulated smartcard certificate");
821 (void)winpr_asprintf(&str, &len,
"%s Emulator", freerdp_getApplicationDetailsString());
823 cert->reader = ConvertUtf8NToWCharAlloc(str, len,
nullptr);
829 cert->containerName = ConvertUtf8ToWCharAlloc(
"Private Key 00",
nullptr);
830 if (!cert->containerName)
838 info->keyPath = create_temporary_file();
839 WLog_DBG(TAG,
"writing PKINIT key to %s", info->keyPath);
840 if (!crypto_write_pem(info->keyPath, privKeyPEM, strlen(privKeyPEM)))
843 info->certPath = create_temporary_file();
844 WLog_DBG(TAG,
"writing PKINIT cert to %s", info->certPath);
845 if (!crypto_write_pem(info->certPath, certPEM, strlen(certPEM)))
849 const int res = winpr_asprintf(&cert->pkinitArgs, &size,
"FILE:%s,%s", info->certPath,
858 smartcardCertInfo_Free(cert);
863static BOOL smartcard_sw_enumerateCerts(
const rdpSettings* settings,
SmartcardCertInfo*** scCerts,
869 WINPR_ASSERT(settings);
870 WINPR_ASSERT(scCerts);
871 WINPR_ASSERT(retCount);
877 WLog_ERR(TAG,
"Invalid smartcard private key PEM, aborting");
882 WLog_ERR(TAG,
"Invalid smartcard certificate PEM, aborting");
898 *scCerts = cert_list;
903 smartcardCertList_Free(cert_list, 1);
907BOOL smartcard_enumerateCerts(
const rdpSettings* settings,
SmartcardCertInfo*** scCerts,
908 size_t* retCount, BOOL gateway)
911 LPWSTR csp =
nullptr;
914 const char* Username =
nullptr;
915 const char* Domain =
nullptr;
928 WINPR_ASSERT(settings);
929 WINPR_ASSERT(scCerts);
930 WINPR_ASSERT(retCount);
932 if (Domain && !strlen(Domain))
936 return smartcard_sw_enumerateCerts(settings, scCerts, retCount);
938 if (CspName && (!(csp = ConvertUtf8ToWCharAlloc(CspName,
nullptr))))
940 WLog_ERR(TAG,
"error while converting CSP to WCHAR");
945 smartcard_hw_enumerateCerts(settings, csp, ReaderName, Username, Domain, scCerts, retCount);
951static BOOL set_settings_from_smartcard(rdpSettings* settings, FreeRDP_Settings_Keys_String
id,
954 WINPR_ASSERT(settings);
963BOOL smartcard_getCert(
const rdpContext* context,
SmartcardCertInfo** cert, BOOL gateway)
965 WINPR_ASSERT(context);
967 const freerdp* instance = context->instance;
968 rdpSettings* settings = context->settings;
972 WINPR_ASSERT(instance);
973 WINPR_ASSERT(settings);
975 if (!smartcard_enumerateCerts(settings, &cert_list, &count, gateway))
980 WLog_ERR(TAG,
"no suitable smartcard certificates were found");
984 if (count > UINT32_MAX)
986 WLog_ERR(TAG,
"smartcard certificate count %" PRIuz
" exceeds UINT32_MAX", count);
994 if (!instance->ChooseSmartcard ||
995 !instance->ChooseSmartcard(context->instance, cert_list, (UINT32)count, &index,
998 WLog_ERR(TAG,
"more than one suitable smartcard certificate was found");
999 smartcardCertList_Free(cert_list, count);
1002 *cert = cert_list[index];
1004 for (DWORD i = 0; i < index; i++)
1005 smartcardCertInfo_Free(cert_list[i]);
1006 for (DWORD i = index + 1; i < count; i++)
1007 smartcardCertInfo_Free(cert_list[i]);
1010 *cert = cert_list[0];
1012 FreeRDP_Settings_Keys_String username_setting =
1013 gateway ? FreeRDP_GatewayUsername : FreeRDP_Username;
1014 FreeRDP_Settings_Keys_String domain_setting = gateway ? FreeRDP_GatewayDomain : FreeRDP_Domain;
1016 free((
void*)cert_list);
1018 if (!set_settings_from_smartcard(settings, username_setting, (*cert)->userHint) ||
1019 !set_settings_from_smartcard(settings, domain_setting, (*cert)->domainHint))
1021 WLog_ERR(TAG,
"unable to set settings from smartcard!");
1022 smartcardCertInfo_Free(*cert);
WINPR_ATTR_NODISCARD FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_set_string(rdpSettings *settings, FreeRDP_Settings_Keys_String id, const char *val)
Sets a string settings value. The param is copied.
WINPR_ATTR_NODISCARD FREERDP_API BOOL freerdp_settings_get_bool(const rdpSettings *settings, FreeRDP_Settings_Keys_Bool id)
Returns a boolean settings value.
a provider name descriptor