FreeRDP
Loading...
Searching...
No Matches
smartcardlogon.c
1
19#include <string.h>
20
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>
27
28#include <freerdp/log.h>
29#include <freerdp/freerdp.h>
30#include <winpr/print.h>
31
32#include <freerdp/utils/smartcardlogon.h>
33#include <freerdp/crypto/crypto.h>
34#include <freerdp/utils/helpers.h>
35
36#include <openssl/obj_mac.h>
37
38#define TAG FREERDP_TAG("smartcardlogon")
39
40struct SmartcardKeyInfo_st
41{
42 char* certPath;
43 char* keyPath;
44};
45
46static void delete_file(char* path)
47{
48 if (!path)
49 return;
50
51 /* Overwrite data in files before deletion */
52 {
53 FILE* fp = winpr_fopen(path, "r+");
54 if (fp)
55 {
56 const char buffer[8192] = WINPR_C_ARRAY_INIT;
57 INT64 size = 0;
58 int rs = _fseeki64(fp, 0, SEEK_END);
59 if (rs == 0)
60 size = _ftelli64(fp);
61 if (_fseeki64(fp, 0, SEEK_SET) == 0)
62 {
63 for (INT64 x = 0; x < size; x += sizeof(buffer))
64 {
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);
68 if (count != 1)
69 break;
70 }
71 }
72
73 (void)fclose(fp);
74 }
75 }
76
77 winpr_DeleteFile(path);
78 free(path);
79}
80
81static void smartcardKeyInfo_Free(SmartcardKeyInfo* key_info)
82{
83 if (!key_info)
84 return;
85
86 delete_file(key_info->certPath);
87 delete_file(key_info->keyPath);
88
89 free(key_info);
90}
91
92void smartcardCertInfo_Free(SmartcardCertInfo* scCert)
93{
94 if (!scCert)
95 return;
96
97 free(scCert->csp);
98 free(scCert->reader);
99 freerdp_certificate_free(scCert->certificate);
100 free(scCert->pkinitArgs);
101 free(scCert->keyName);
102 free(scCert->containerName);
103 free(scCert->upn);
104 free(scCert->userHint);
105 free(scCert->domainHint);
106 free(scCert->subject);
107 free(scCert->issuer);
108 smartcardKeyInfo_Free(scCert->key_info);
109
110 free(scCert);
111}
112
113void smartcardCertList_Free(SmartcardCertInfo** pscCert, size_t count)
114{
115 if (!pscCert)
116 return;
117
118 for (size_t i = 0; i < count; i++)
119 {
120 SmartcardCertInfo* cert = pscCert[i];
121 smartcardCertInfo_Free(cert);
122 }
123
124 free((void*)pscCert);
125}
126
127WINPR_ATTR_NODISCARD
128static BOOL add_cert_to_list(SmartcardCertInfo*** certInfoList, size_t* count,
129 SmartcardCertInfo* certInfo)
130{
131 size_t curCount = *count;
132 SmartcardCertInfo** curInfoList = *certInfoList;
133
134 /* Check if the certificate is already in the list */
135 for (size_t i = 0; i < curCount; ++i)
136 {
137 if (_wcscmp(curInfoList[i]->containerName, certInfo->containerName) == 0)
138 {
139 smartcardCertInfo_Free(certInfo);
140 return TRUE;
141 }
142 }
143
144 {
145 SmartcardCertInfo** tmpInfoList = (SmartcardCertInfo**)realloc(
146 (void*)curInfoList, sizeof(SmartcardCertInfo*) * (curCount + 1));
147 if (!tmpInfoList)
148 {
149 WLog_ERR(TAG, "unable to reallocate certs");
150 return FALSE;
151 }
152 curInfoList = tmpInfoList;
153 }
154
155 curInfoList[curCount++] = certInfo;
156 *certInfoList = curInfoList;
157 *count = curCount;
158 return TRUE;
159}
160
161WINPR_ATTR_NODISCARD
162static BOOL treat_sc_cert(SmartcardCertInfo* scCert)
163{
164 WINPR_ASSERT(scCert);
165
166 scCert->upn = freerdp_certificate_get_upn(scCert->certificate);
167 if (!scCert->upn)
168 {
169 WLog_DBG(TAG, "%s has no UPN, trying emailAddress", scCert->keyName);
170 scCert->upn = freerdp_certificate_get_email(scCert->certificate);
171 }
172
173 if (scCert->upn)
174 {
175 size_t userLen = 0;
176 const char* atPos = strchr(scCert->upn, '@');
177
178 if (!atPos)
179 {
180 WLog_ERR(TAG, "invalid UPN, for key %s (no @)", scCert->keyName);
181 return FALSE;
182 }
183
184 userLen = (size_t)(atPos - scCert->upn);
185 scCert->userHint = malloc(userLen + 1);
186 scCert->domainHint = _strdup(atPos + 1);
187
188 if (!scCert->userHint || !scCert->domainHint)
189 {
190 WLog_ERR(TAG, "error allocating userHint or domainHint, for key %s", scCert->keyName);
191 return FALSE;
192 }
193
194 memcpy(scCert->userHint, scCert->upn, userLen);
195 scCert->userHint[userLen] = 0;
196 }
197
198 scCert->subject = freerdp_certificate_get_subject(scCert->certificate);
199 scCert->issuer = freerdp_certificate_get_issuer(scCert->certificate);
200 return TRUE;
201}
202
203WINPR_ATTR_NODISCARD
204static BOOL set_info_certificate(SmartcardCertInfo* cert, BYTE* certBytes, DWORD cbCertBytes,
205 const char* userFilter, const char* domainFilter)
206{
207 if (!winpr_Digest(WINPR_MD_SHA1, certBytes, cbCertBytes, cert->sha1Hash,
208 sizeof(cert->sha1Hash)))
209 {
210 WLog_ERR(TAG, "unable to compute certificate sha1 for key %s", cert->keyName);
211 return FALSE;
212 }
213
214 cert->certificate = freerdp_certificate_new_from_der(certBytes, cbCertBytes);
215 if (!cert->certificate)
216 {
217 WLog_ERR(TAG, "unable to parse X509 certificate for key %s", cert->keyName);
218 return FALSE;
219 }
220
221 if (!freerdp_certificate_check_eku(cert->certificate, NID_ms_smartcard_login))
222 {
223 WLog_DBG(TAG, "discarding certificate without Smartcard Login EKU for key %s",
224 cert->keyName);
225 return FALSE;
226 }
227
228 if (!treat_sc_cert(cert))
229 {
230 WLog_DBG(TAG, "error treating cert");
231 return FALSE;
232 }
233
234 if (userFilter && (!cert->upn || (strcmp(cert->upn, userFilter) != 0)))
235 {
236 if (cert->userHint && strcmp(cert->userHint, userFilter) != 0)
237 {
238 WLog_DBG(TAG, "discarding non matching cert by user %s@%s", cert->userHint,
239 cert->domainHint);
240 return FALSE;
241 }
242 }
243
244 if (domainFilter && cert->domainHint)
245 {
246 /* Try exact domain match. */
247 BOOL domainMatch = strcmp(cert->domainHint, domainFilter) == 0;
248 if (!domainMatch)
249 {
250 /* If our filter is a windows domain name and the hint in the cert a FQDN
251 * then check if the first segment matches.
252 * e.g. internal == internal.mydomain.com should match
253 */
254 const char* dot = strchr(cert->domainHint, '.');
255 const char* ddot = strchr(domainFilter, '.');
256 if (!ddot && dot)
257 {
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))
261 domainMatch = TRUE;
262 }
263 }
264 if (!domainMatch)
265 {
266 WLog_DBG(TAG, "discarding non matching cert by domain(%s) %s@%s", domainFilter,
267 cert->userHint, cert->domainHint);
268 return FALSE;
269 }
270 }
271
272 return TRUE;
273}
274
275#ifndef _WIN32
276WINPR_ATTR_NODISCARD
277static BOOL build_pkinit_args(NCRYPT_PROV_HANDLE provider, SmartcardCertInfo* scCert)
278{
279 /* pkinit args only under windows
280 * PKCS11:module_name=opensc-pkcs11.so
281 */
282 const char* pkModule = winpr_NCryptGetModulePath(provider);
283 size_t size = 0;
284
285 /* Extract the PKCS#11 CKA_ID from keyName (format: <slotIdHex><certIdHex>)
286 * and pass it as certid= so that MIT krb5's PKINIT module can select the
287 * correct certificate when multiple certificates are present on the token.
288 */
289 const char* certId = nullptr;
290 if (scCert->keyName)
291 {
292 const char* secondBackslash = strchr(scCert->keyName + 1, '\\');
293 if (secondBackslash)
294 certId = secondBackslash + 1;
295 }
296
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);
301
302 return (winpr_asprintf(&scCert->pkinitArgs, &size, "PKCS11:module_name=%s:slotid=%" PRIu16,
303 pkModule, (UINT16)scCert->slotId) > 0);
304}
305#endif /* _WIN32 */
306
307#ifdef _WIN32
308WINPR_ATTR_NODISCARD
309static BOOL list_capi_provider_keys(const rdpSettings* settings, LPCWSTR csp, LPCWSTR scope,
310 const char* userFilter, const char* domainFilter,
311 SmartcardCertInfo*** pcerts, size_t* pcount)
312{
313 BOOL ret = FALSE;
314 HCRYPTKEY hKey = 0;
315 HCRYPTPROV hProvider = 0;
316 SmartcardCertInfo* cert = nullptr;
317 BYTE* certBytes = nullptr;
318 CHAR* readerName = nullptr;
319
320 if (!CryptAcquireContextW(&hProvider, scope, csp, PROV_RSA_FULL, CRYPT_SILENT))
321 {
322 WLog_DBG(TAG, "Unable to acquire context: %d", GetLastError());
323 goto out;
324 }
325
326 cert = calloc(1, sizeof(SmartcardCertInfo));
327 if (!cert)
328 goto out;
329
330 cert->csp = _wcsdup(csp);
331 if (!cert->csp)
332 goto out;
333
334 /* ====== retrieve key's reader ====== */
335 DWORD dwDataLen = 0;
336 if (!CryptGetProvParam(hProvider, PP_SMARTCARD_READER, nullptr, &dwDataLen, 0))
337 {
338 WLog_DBG(TAG, "Unable to get provider param: %d", GetLastError());
339 goto out;
340 }
341
342 readerName = malloc(dwDataLen);
343 if (!readerName)
344 goto out;
345
346 if (!CryptGetProvParam(hProvider, PP_SMARTCARD_READER, readerName, &dwDataLen, 0))
347 {
348 WLog_DBG(TAG, "Unable to get reader name: %d", GetLastError());
349 goto out;
350 }
351
352 cert->reader = ConvertUtf8ToWCharAlloc(readerName, nullptr);
353 if (!cert->reader)
354 goto out;
355
356 /* ====== retrieve key container name ====== */
357 dwDataLen = 0;
358 if (!CryptGetProvParam(hProvider, PP_CONTAINER, nullptr, &dwDataLen, 0))
359 {
360 WLog_DBG(TAG, "Unable to get provider param: %d", GetLastError());
361 goto out;
362 }
363
364 cert->keyName = malloc(dwDataLen);
365 if (!cert->keyName)
366 goto out;
367
368 if (!CryptGetProvParam(hProvider, PP_CONTAINER, cert->keyName, &dwDataLen, 0))
369 {
370 WLog_DBG(TAG, "Unable to get container name: %d", GetLastError());
371 goto out;
372 }
373
374 cert->containerName = ConvertUtf8ToWCharAlloc(cert->keyName, nullptr);
375 if (!cert->containerName)
376 goto out;
377
378 /* ========= retrieve the certificate ===============*/
379 if (!CryptGetUserKey(hProvider, AT_KEYEXCHANGE, &hKey))
380 {
381 WLog_DBG(TAG, "Unable to get user key for %s: %d", cert->keyName, GetLastError());
382 goto out;
383 }
384
385 dwDataLen = 0;
386 if (!CryptGetKeyParam(hKey, KP_CERTIFICATE, nullptr, &dwDataLen, 0))
387 {
388 WLog_DBG(TAG, "Unable to get key param for key %s: %d", cert->keyName, GetLastError());
389 goto out;
390 }
391
392 certBytes = malloc(dwDataLen);
393 if (!certBytes)
394 {
395 WLog_ERR(TAG, "unable to allocate %" PRIu32 " certBytes for key %s", dwDataLen,
396 cert->keyName);
397 goto out;
398 }
399
400 if (!CryptGetKeyParam(hKey, KP_CERTIFICATE, certBytes, &dwDataLen, 0))
401 {
402 WLog_ERR(TAG, "unable to retrieve certificate for key %s", cert->keyName);
403 goto out;
404 }
405
406 if (!set_info_certificate(cert, certBytes, dwDataLen, userFilter, domainFilter))
407 goto out;
408
409 if (!add_cert_to_list(pcerts, pcount, cert))
410 goto out;
411
412 ret = TRUE;
413
414out:
415 free(readerName);
416 free(certBytes);
417 if (hKey)
418 CryptDestroyKey(hKey);
419 if (hProvider)
420 CryptReleaseContext(hProvider, 0);
421 if (!ret)
422 smartcardCertInfo_Free(cert);
423 return ret;
424}
425#endif /* _WIN32 */
426
427WINPR_ATTR_NODISCARD
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,
431 SmartcardCertInfo*** pcerts, size_t* pcount)
432{
433 BOOL ret = FALSE;
434 NCryptKeyName* keyName = nullptr;
435 PVOID enumState = nullptr;
436 SmartcardCertInfo** cert_list = *pcerts;
437 size_t count = *pcount;
438
439 while (NCryptEnumKeys(provider, scope, &keyName, &enumState, NCRYPT_SILENT_FLAG) ==
440 ERROR_SUCCESS)
441 {
442 NCRYPT_KEY_HANDLE phKey = 0;
443 PBYTE certBytes = nullptr;
444 DWORD dwFlags = NCRYPT_SILENT_FLAG;
445 DWORD cbOutput = 0;
446 SmartcardCertInfo* cert = nullptr;
447 BOOL haveError = TRUE;
448 SECURITY_STATUS status = 0;
449
450 cert = calloc(1, sizeof(SmartcardCertInfo));
451 if (!cert)
452 goto out;
453
454 cert->keyName = ConvertWCharToUtf8Alloc(keyName->pszName, nullptr);
455 if (!cert->keyName)
456 goto endofloop;
457
458 WLog_DBG(TAG, "opening key %s", cert->keyName);
459
460 status =
461 NCryptOpenKey(provider, &phKey, keyName->pszName, keyName->dwLegacyKeySpec, dwFlags);
462 if (status != ERROR_SUCCESS)
463 {
464 WLog_DBG(TAG,
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));
469 goto endofloop;
470 }
471
472 cert->csp = _wcsdup(csp);
473 if (!cert->csp)
474 goto endofloop;
475
476#ifndef _WIN32
477 status = NCryptGetProperty(phKey, NCRYPT_WINPR_SLOTID, (PBYTE)&cert->slotId, 4, &cbOutput,
478 dwFlags);
479 if (status != ERROR_SUCCESS)
480 {
481 WLog_ERR(TAG, "unable to retrieve slotId for key %s, status=%s", cert->keyName,
482 winpr_NCryptSecurityStatusError(status));
483 goto endofloop;
484 }
485#endif /* _WIN32 */
486
487 /* ====== retrieve key's reader ====== */
488 cbOutput = 0;
489 status = NCryptGetProperty(phKey, NCRYPT_READER_PROPERTY, nullptr, 0, &cbOutput, dwFlags);
490 if (status != ERROR_SUCCESS)
491 {
492 WLog_DBG(TAG, "unable to retrieve reader's name length for key %s", cert->keyName);
493 goto endofloop;
494 }
495
496 cert->reader = calloc(1, cbOutput + 2);
497 if (!cert->reader)
498 {
499 WLog_ERR(TAG, "unable to allocate reader's name for key %s", cert->keyName);
500 goto endofloop;
501 }
502
503 status = NCryptGetProperty(phKey, NCRYPT_READER_PROPERTY, (PBYTE)cert->reader, cbOutput + 2,
504 &cbOutput, dwFlags);
505 if (status != ERROR_SUCCESS)
506 {
507 WLog_ERR(TAG, "unable to retrieve reader's name for key %s", cert->keyName);
508 goto endofloop;
509 }
510
511 /* ====== retrieve key container name ====== */
512 /* When using PKCS11, this will try to return what Windows would use for the key's name */
513 cbOutput = 0;
514 status = NCryptGetProperty(phKey, NCRYPT_NAME_PROPERTY, nullptr, 0, &cbOutput, dwFlags);
515 if (status == ERROR_SUCCESS)
516 {
517 cert->containerName = calloc(1, cbOutput + sizeof(WCHAR));
518 if (!cert->containerName)
519 {
520 WLog_ERR(TAG, "unable to allocate key container name for key %s", cert->keyName);
521 goto endofloop;
522 }
523
524 status = NCryptGetProperty(phKey, NCRYPT_NAME_PROPERTY, (BYTE*)cert->containerName,
525 cbOutput, &cbOutput, dwFlags);
526 }
527
528 if (status != ERROR_SUCCESS)
529 {
530 WLog_ERR(TAG, "unable to retrieve key container name for key %s", cert->keyName);
531 goto endofloop;
532 }
533
534 /* ========= retrieve the certificate ===============*/
535 cbOutput = 0;
536 status =
537 NCryptGetProperty(phKey, NCRYPT_CERTIFICATE_PROPERTY, nullptr, 0, &cbOutput, dwFlags);
538 if (status != ERROR_SUCCESS)
539 {
540 /* can happen that key don't have certificates */
541 WLog_DBG(TAG, "unable to retrieve certificate property len, status=%s, skipping",
542 winpr_NCryptSecurityStatusError(status));
543 goto endofloop;
544 }
545
546 certBytes = calloc(1, cbOutput);
547 if (!certBytes)
548 {
549 WLog_ERR(TAG, "unable to allocate %" PRIu32 " certBytes for key %s", cbOutput,
550 cert->keyName);
551 goto endofloop;
552 }
553
554 status = NCryptGetProperty(phKey, NCRYPT_CERTIFICATE_PROPERTY, certBytes, cbOutput,
555 &cbOutput, dwFlags);
556 if (status != ERROR_SUCCESS)
557 {
558 WLog_ERR(TAG, "unable to retrieve certificate for key %s", cert->keyName);
559 goto endofloop;
560 }
561
562 if (!set_info_certificate(cert, certBytes, cbOutput, userFilter, domainFilter))
563 goto endofloop;
564
565#ifndef _WIN32
566 if (!build_pkinit_args(provider, cert))
567 {
568 WLog_ERR(TAG, "error build pkinit args");
569 goto endofloop;
570 }
571#endif
572 haveError = FALSE;
573
574 endofloop:
575 free(certBytes);
576 NCryptFreeBuffer(keyName);
577 if (phKey)
578 NCryptFreeObject((NCRYPT_HANDLE)phKey);
579
580 if (haveError)
581 smartcardCertInfo_Free(cert);
582 else
583 {
584 if (!add_cert_to_list(&cert_list, &count, cert))
585 goto out;
586 }
587 }
588
589 ret = TRUE;
590out:
591 if (count == 0)
592 {
593 char cspa[128] = WINPR_C_ARRAY_INIT;
594
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);
599 }
600 *pcount = count;
601 *pcerts = cert_list;
602 NCryptFreeBuffer(enumState);
603 return ret;
604}
605
606WINPR_ATTR_NODISCARD
607static BOOL smartcard_hw_enumerateCerts(const rdpSettings* settings, LPCWSTR csp,
608 const char* reader, const char* userFilter,
609 const char* domainFilter, SmartcardCertInfo*** scCerts,
610 size_t* retCount)
611{
612 BOOL ret = FALSE;
613 LPWSTR scope = nullptr;
614 NCRYPT_PROV_HANDLE provider = 0;
615 SECURITY_STATUS status = 0;
616 size_t count = 0;
617 SmartcardCertInfo** cert_list = nullptr;
618 const char* Pkcs11Module = freerdp_settings_get_string(settings, FreeRDP_Pkcs11Module);
619
620 WINPR_ASSERT(scCerts);
621 WINPR_ASSERT(retCount);
622
623 if (reader)
624 {
625 char* scopeStr = nullptr;
626 size_t scopeStrLen = 0;
627 winpr_asprintf(&scopeStr, &scopeStrLen, "\\\\.\\%s\\", reader);
628 if (!scopeStr)
629 goto out;
630
631 scope = ConvertUtf8NToWCharAlloc(scopeStr, scopeStrLen, nullptr);
632 free(scopeStr);
633
634 if (!scope)
635 goto out;
636 }
637
638 if (Pkcs11Module)
639 {
640 /* load a unique CSP by pkcs11 module path */
641 LPCSTR paths[] = { Pkcs11Module, nullptr };
642
643 if (!csp)
644 csp = MS_SMART_CARD_KEY_STORAGE_PROVIDER;
645
646 status = winpr_NCryptOpenStorageProviderEx(&provider, csp, 0, paths);
647 if (status != ERROR_SUCCESS)
648 {
649 WLog_ERR(TAG, "unable to open provider given by pkcs11 module");
650 goto out;
651 }
652
653 status = list_provider_keys(settings, provider, csp, scope, userFilter, domainFilter,
654 &cert_list, &count);
655 NCryptFreeObject((NCRYPT_HANDLE)provider);
656 if (!status)
657 {
658 WLog_ERR(TAG, "error listing keys from CSP loaded from %s", Pkcs11Module);
659 goto out;
660 }
661 }
662 else
663 {
664 NCryptProviderName* names = nullptr;
665 DWORD nproviders = 0;
666
667#ifdef _WIN32
668 /* On Windows, mstsc first enumerates the legacy CAPI providers for usable certificates. */
669 DWORD provType, cbProvName = 0;
670 for (DWORD i = 0; CryptEnumProvidersW(i, nullptr, 0, &provType, nullptr, &cbProvName); ++i)
671 {
672 char providerNameStr[256] = WINPR_C_ARRAY_INIT;
673 LPWSTR szProvName = malloc(cbProvName * sizeof(WCHAR));
674 if (!CryptEnumProvidersW(i, nullptr, 0, &provType, szProvName, &cbProvName))
675 {
676 free(szProvName);
677 break;
678 }
679
680 if (ConvertWCharToUtf8(szProvName, providerNameStr, ARRAYSIZE(providerNameStr)) < 0)
681 {
682 (void)_snprintf(providerNameStr, sizeof(providerNameStr), "<unknown>");
683 WLog_ERR(TAG, "unable to convert provider name to char*, will show it as '%s'",
684 providerNameStr);
685 }
686
687 WLog_DBG(TAG, "exploring CSP '%s'", providerNameStr);
688 if (provType != PROV_RSA_FULL || (csp && _wcscmp(szProvName, csp) != 0))
689 {
690 WLog_DBG(TAG, "CSP '%s' filtered out", providerNameStr);
691 goto end_of_loop;
692 }
693
694 if (!list_capi_provider_keys(settings, szProvName, scope, userFilter, domainFilter,
695 &cert_list, &count))
696 WLog_INFO(TAG, "error when retrieving keys in CSP '%s'", providerNameStr);
697
698 end_of_loop:
699 free(szProvName);
700 }
701#endif
702
703 status = NCryptEnumStorageProviders(&nproviders, &names, NCRYPT_SILENT_FLAG);
704 if (status != ERROR_SUCCESS)
705 {
706 WLog_ERR(TAG, "error listing providers");
707 goto out;
708 }
709
710 for (DWORD i = 0; i < nproviders; i++)
711 {
712 char providerNameStr[256] = WINPR_C_ARRAY_INIT;
713 const NCryptProviderName* name = &names[i];
714
715 if (ConvertWCharToUtf8(name->pszName, providerNameStr, ARRAYSIZE(providerNameStr)) < 0)
716 {
717 (void)_snprintf(providerNameStr, sizeof(providerNameStr), "<unknown>");
718 WLog_ERR(TAG, "unable to convert provider name to char*, will show it as '%s'",
719 providerNameStr);
720 }
721
722 WLog_DBG(TAG, "exploring CSP '%s'", providerNameStr);
723 if (csp && _wcscmp(name->pszName, csp) != 0)
724 {
725 WLog_DBG(TAG, "CSP '%s' filtered out", providerNameStr);
726 continue;
727 }
728
729 status = NCryptOpenStorageProvider(&provider, name->pszName, 0);
730 if (status != ERROR_SUCCESS)
731 continue;
732
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);
736
737 NCryptFreeObject((NCRYPT_HANDLE)provider);
738 }
739
740 NCryptFreeBuffer(names);
741 }
742
743 *scCerts = cert_list;
744 *retCount = count;
745 ret = TRUE;
746
747out:
748 if (!ret)
749 smartcardCertList_Free(cert_list, count);
750 free(scope);
751 return ret;
752}
753
754WINPR_ATTR_MALLOC(free, 1)
755static char* create_temporary_file(void)
756{
757 BYTE buffer[32] = WINPR_C_ARRAY_INIT;
758 char* path = nullptr;
759
760 if (winpr_RAND(buffer, sizeof(buffer)) < 0)
761 return nullptr;
762
763 char* hex = winpr_BinToHexString(buffer, sizeof(buffer), FALSE);
764 if (hex)
765 path = GetKnownSubPath(KNOWN_PATH_TEMP, hex);
766 free(hex);
767 return path;
768}
769
770WINPR_ATTR_MALLOC(smartcardCertInfo_Free, 1)
771static SmartcardCertInfo* smartcardCertInfo_New(const char* privKeyPEM, const char* certPEM)
772{
773 size_t size = 0;
774
775 WINPR_ASSERT(privKeyPEM);
776 WINPR_ASSERT(certPEM);
777
778 SmartcardCertInfo* cert = calloc(1, sizeof(SmartcardCertInfo));
779 if (!cert)
780 goto fail;
781
782 {
783 SmartcardKeyInfo* info = cert->key_info = calloc(1, sizeof(SmartcardKeyInfo));
784 if (!info)
785 goto fail;
786
787 cert->certificate = freerdp_certificate_new_from_pem(certPEM);
788 if (!cert->certificate)
789 {
790 WLog_ERR(TAG, "unable to read smartcard certificate");
791 goto fail;
792 }
793
794 if (!treat_sc_cert(cert))
795 {
796 WLog_ERR(TAG, "unable to treat smartcard certificate");
797 goto fail;
798 }
799
800 /* The hardware path fills sha1Hash in set_info_certificate(); this software path has to
801 * as well. identity_set_from_smartcard_hash() marshals the hash into a CERT_CREDENTIAL_INFO
802 * and LSA resolves the certificate by it, so a zeroed hash matches nothing and CredSSP
803 * fails without pointing at the emulated card. Compute it from the DER. */
804 {
805 size_t derLen = 0;
806 BYTE* der = freerdp_certificate_get_der(cert->certificate, &derLen);
807 const BOOL hashed =
808 der && (derLen > 0) &&
809 winpr_Digest(WINPR_MD_SHA1, der, derLen, cert->sha1Hash, sizeof(cert->sha1Hash));
810 free(der);
811 if (!hashed)
812 {
813 WLog_ERR(TAG, "unable to compute sha1 of the emulated smartcard certificate");
814 goto fail;
815 }
816 }
817
818 {
819 char* str = nullptr;
820 size_t len = 0;
821 (void)winpr_asprintf(&str, &len, "%s Emulator", freerdp_getApplicationDetailsString());
822 if (str)
823 cert->reader = ConvertUtf8NToWCharAlloc(str, len, nullptr);
824 free(str);
825 }
826 if (!cert->reader)
827 goto fail;
828
829 cert->containerName = ConvertUtf8ToWCharAlloc("Private Key 00", nullptr);
830 if (!cert->containerName)
831 goto fail;
832
833 /* compute PKINIT args FILE:<cert file>,<key file>
834 *
835 * We need files for PKINIT to read, so write the certificate to some
836 * temporary location and use that.
837 */
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)))
841 goto fail;
842
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)))
846 goto fail;
847
848 {
849 const int res = winpr_asprintf(&cert->pkinitArgs, &size, "FILE:%s,%s", info->certPath,
850 info->keyPath);
851 if (res <= 0)
852 goto fail;
853 }
854 }
855
856 return cert;
857fail:
858 smartcardCertInfo_Free(cert);
859 return nullptr;
860}
861
862WINPR_ATTR_NODISCARD
863static BOOL smartcard_sw_enumerateCerts(const rdpSettings* settings, SmartcardCertInfo*** scCerts,
864 size_t* retCount)
865{
866 BOOL rc = FALSE;
867 SmartcardCertInfo** cert_list = nullptr;
868
869 WINPR_ASSERT(settings);
870 WINPR_ASSERT(scCerts);
871 WINPR_ASSERT(retCount);
872
873 const char* privKeyPEM = freerdp_settings_get_string(settings, FreeRDP_SmartcardPrivateKey);
874 const char* certPEM = freerdp_settings_get_string(settings, FreeRDP_SmartcardCertificate);
875 if (!privKeyPEM)
876 {
877 WLog_ERR(TAG, "Invalid smartcard private key PEM, aborting");
878 goto out_error;
879 }
880 if (!certPEM)
881 {
882 WLog_ERR(TAG, "Invalid smartcard certificate PEM, aborting");
883 goto out_error;
884 }
885
886 cert_list = (SmartcardCertInfo**)calloc(1, sizeof(SmartcardCertInfo*));
887 if (!cert_list)
888 goto out_error;
889
890 {
891 SmartcardCertInfo* cert = smartcardCertInfo_New(privKeyPEM, certPEM);
892 if (!cert)
893 goto out_error;
894 cert_list[0] = cert;
895 }
896
897 rc = TRUE;
898 *scCerts = cert_list;
899 *retCount = 1;
900
901out_error:
902 if (!rc)
903 smartcardCertList_Free(cert_list, 1);
904 return rc;
905}
906
907BOOL smartcard_enumerateCerts(const rdpSettings* settings, SmartcardCertInfo*** scCerts,
908 size_t* retCount, BOOL gateway)
909{
910 BOOL ret = 0;
911 LPWSTR csp = nullptr;
912 const char* ReaderName = freerdp_settings_get_string(settings, FreeRDP_ReaderName);
913 const char* CspName = freerdp_settings_get_string(settings, FreeRDP_CspName);
914 const char* Username = nullptr;
915 const char* Domain = nullptr;
916
917 if (gateway)
918 {
919 Username = freerdp_settings_get_string(settings, FreeRDP_GatewayUsername);
920 Domain = freerdp_settings_get_string(settings, FreeRDP_GatewayDomain);
921 }
922 else
923 {
924 Username = freerdp_settings_get_string(settings, FreeRDP_Username);
925 Domain = freerdp_settings_get_string(settings, FreeRDP_Domain);
926 }
927
928 WINPR_ASSERT(settings);
929 WINPR_ASSERT(scCerts);
930 WINPR_ASSERT(retCount);
931
932 if (Domain && !strlen(Domain))
933 Domain = nullptr;
934
935 if (freerdp_settings_get_bool(settings, FreeRDP_SmartcardEmulation))
936 return smartcard_sw_enumerateCerts(settings, scCerts, retCount);
937
938 if (CspName && (!(csp = ConvertUtf8ToWCharAlloc(CspName, nullptr))))
939 {
940 WLog_ERR(TAG, "error while converting CSP to WCHAR");
941 return FALSE;
942 }
943
944 ret =
945 smartcard_hw_enumerateCerts(settings, csp, ReaderName, Username, Domain, scCerts, retCount);
946 free(csp);
947 return ret;
948}
949
950WINPR_ATTR_NODISCARD
951static BOOL set_settings_from_smartcard(rdpSettings* settings, FreeRDP_Settings_Keys_String id,
952 const char* value)
953{
954 WINPR_ASSERT(settings);
955
956 if (!freerdp_settings_get_string(settings, id) && value)
957 if (!freerdp_settings_set_string(settings, id, value))
958 return FALSE;
959
960 return TRUE;
961}
962
963BOOL smartcard_getCert(const rdpContext* context, SmartcardCertInfo** cert, BOOL gateway)
964{
965 WINPR_ASSERT(context);
966
967 const freerdp* instance = context->instance;
968 rdpSettings* settings = context->settings;
969 SmartcardCertInfo** cert_list = nullptr;
970 size_t count = 0;
971
972 WINPR_ASSERT(instance);
973 WINPR_ASSERT(settings);
974
975 if (!smartcard_enumerateCerts(settings, &cert_list, &count, gateway))
976 return FALSE;
977
978 if (count < 1)
979 {
980 WLog_ERR(TAG, "no suitable smartcard certificates were found");
981 return FALSE;
982 }
983
984 if (count > UINT32_MAX)
985 {
986 WLog_ERR(TAG, "smartcard certificate count %" PRIuz " exceeds UINT32_MAX", count);
987 return FALSE;
988 }
989
990 if (count > 1)
991 {
992 DWORD index = 0;
993
994 if (!instance->ChooseSmartcard ||
995 !instance->ChooseSmartcard(context->instance, cert_list, (UINT32)count, &index,
996 gateway))
997 {
998 WLog_ERR(TAG, "more than one suitable smartcard certificate was found");
999 smartcardCertList_Free(cert_list, count);
1000 return FALSE;
1001 }
1002 *cert = cert_list[index];
1003
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]);
1008 }
1009 else
1010 *cert = cert_list[0];
1011
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;
1015
1016 free((void*)cert_list);
1017
1018 if (!set_settings_from_smartcard(settings, username_setting, (*cert)->userHint) ||
1019 !set_settings_from_smartcard(settings, domain_setting, (*cert)->domainHint))
1020 {
1021 WLog_ERR(TAG, "unable to set settings from smartcard!");
1022 smartcardCertInfo_Free(*cert);
1023 return FALSE;
1024 }
1025
1026 return TRUE;
1027}
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 key name descriptor
a provider name descriptor