Coverage Report

Created: 2026-04-12 07:03

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