Coverage Report

Created: 2026-10-28 06:54

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.5k
{
94
17.5k
  if (!scCert)
95
17.5k
    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
WINPR_ATTR_NODISCARD
128
static BOOL add_cert_to_list(SmartcardCertInfo*** certInfoList, size_t* count,
129
                             SmartcardCertInfo* certInfo)
130
0
{
131
0
  size_t curCount = *count;
132
0
  SmartcardCertInfo** curInfoList = *certInfoList;
133
134
  /* Check if the certificate is already in the list */
135
0
  for (size_t i = 0; i < curCount; ++i)
136
0
  {
137
0
    if (_wcscmp(curInfoList[i]->containerName, certInfo->containerName) == 0)
138
0
    {
139
0
      smartcardCertInfo_Free(certInfo);
140
0
      return TRUE;
141
0
    }
142
0
  }
143
144
0
  {
145
0
    SmartcardCertInfo** tmpInfoList = (SmartcardCertInfo**)realloc(
146
0
        (void*)curInfoList, sizeof(SmartcardCertInfo*) * (curCount + 1));
147
0
    if (!tmpInfoList)
148
0
    {
149
0
      WLog_ERR(TAG, "unable to reallocate certs");
150
0
      return FALSE;
151
0
    }
152
0
    curInfoList = tmpInfoList;
153
0
  }
154
155
0
  curInfoList[curCount++] = certInfo;
156
0
  *certInfoList = curInfoList;
157
0
  *count = curCount;
158
0
  return TRUE;
159
0
}
160
161
WINPR_ATTR_NODISCARD
162
static BOOL treat_sc_cert(SmartcardCertInfo* scCert)
163
0
{
164
0
  WINPR_ASSERT(scCert);
165
166
0
  scCert->upn = freerdp_certificate_get_upn(scCert->certificate);
167
0
  if (!scCert->upn)
168
0
  {
169
0
    WLog_DBG(TAG, "%s has no UPN, trying emailAddress", scCert->keyName);
170
0
    scCert->upn = freerdp_certificate_get_email(scCert->certificate);
171
0
  }
172
173
0
  if (scCert->upn)
174
0
  {
175
0
    size_t userLen = 0;
176
0
    const char* atPos = strchr(scCert->upn, '@');
177
178
0
    if (!atPos)
179
0
    {
180
0
      WLog_ERR(TAG, "invalid UPN, for key %s (no @)", scCert->keyName);
181
0
      return FALSE;
182
0
    }
183
184
0
    userLen = (size_t)(atPos - scCert->upn);
185
0
    scCert->userHint = malloc(userLen + 1);
186
0
    scCert->domainHint = _strdup(atPos + 1);
187
188
0
    if (!scCert->userHint || !scCert->domainHint)
189
0
    {
190
0
      WLog_ERR(TAG, "error allocating userHint or domainHint, for key %s", scCert->keyName);
191
0
      return FALSE;
192
0
    }
193
194
0
    memcpy(scCert->userHint, scCert->upn, userLen);
195
0
    scCert->userHint[userLen] = 0;
196
0
  }
197
198
0
  scCert->subject = freerdp_certificate_get_subject(scCert->certificate);
199
0
  scCert->issuer = freerdp_certificate_get_issuer(scCert->certificate);
200
0
  return TRUE;
201
0
}
202
203
WINPR_ATTR_NODISCARD
204
static BOOL set_info_certificate(SmartcardCertInfo* cert, BYTE* certBytes, DWORD cbCertBytes,
205
                                 const char* userFilter, const char* domainFilter)
206
0
{
207
0
  if (!winpr_Digest(WINPR_MD_SHA1, certBytes, cbCertBytes, cert->sha1Hash,
208
0
                    sizeof(cert->sha1Hash)))
209
0
  {
210
0
    WLog_ERR(TAG, "unable to compute certificate sha1 for key %s", cert->keyName);
211
0
    return FALSE;
212
0
  }
213
214
0
  cert->certificate = freerdp_certificate_new_from_der(certBytes, cbCertBytes);
215
0
  if (!cert->certificate)
216
0
  {
217
0
    WLog_ERR(TAG, "unable to parse X509 certificate for key %s", cert->keyName);
218
0
    return FALSE;
219
0
  }
220
221
0
  if (!freerdp_certificate_check_eku(cert->certificate, NID_ms_smartcard_login))
222
0
  {
223
0
    WLog_DBG(TAG, "discarding certificate without Smartcard Login EKU for key %s",
224
0
             cert->keyName);
225
0
    return FALSE;
226
0
  }
227
228
0
  if (!treat_sc_cert(cert))
229
0
  {
230
0
    WLog_DBG(TAG, "error treating cert");
231
0
    return FALSE;
232
0
  }
233
234
0
  if (userFilter && (!cert->upn || (strcmp(cert->upn, userFilter) != 0)))
235
0
  {
236
0
    if (cert->userHint && strcmp(cert->userHint, userFilter) != 0)
237
0
    {
238
0
      WLog_DBG(TAG, "discarding non matching cert by user %s@%s", cert->userHint,
239
0
               cert->domainHint);
240
0
      return FALSE;
241
0
    }
242
0
  }
243
244
0
  if (domainFilter && cert->domainHint)
245
0
  {
246
    /* Try exact domain match. */
247
0
    BOOL domainMatch = strcmp(cert->domainHint, domainFilter) == 0;
248
0
    if (!domainMatch)
249
0
    {
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
0
      const char* dot = strchr(cert->domainHint, '.');
255
0
      const char* ddot = strchr(domainFilter, '.');
256
0
      if (!ddot && dot)
257
0
      {
258
0
        const size_t filterLen = strlen(domainFilter);
259
0
        const size_t hlen = WINPR_ASSERTING_INT_CAST(size_t, (dot - cert->domainHint));
260
0
        if ((hlen == filterLen) && (strncmp(domainFilter, cert->domainHint, hlen) == 0))
261
0
          domainMatch = TRUE;
262
0
      }
263
0
    }
264
0
    if (!domainMatch)
265
0
    {
266
0
      WLog_DBG(TAG, "discarding non matching cert by domain(%s) %s@%s", domainFilter,
267
0
               cert->userHint, cert->domainHint);
268
0
      return FALSE;
269
0
    }
270
0
  }
271
272
0
  return TRUE;
273
0
}
274
275
#ifndef _WIN32
276
WINPR_ATTR_NODISCARD
277
static BOOL build_pkinit_args(NCRYPT_PROV_HANDLE provider, SmartcardCertInfo* scCert)
278
0
{
279
  /* pkinit args only under windows
280
   *    PKCS11:module_name=opensc-pkcs11.so
281
   */
282
0
  const char* pkModule = winpr_NCryptGetModulePath(provider);
283
0
  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
0
  const char* certId = nullptr;
290
0
  if (scCert->keyName)
291
0
  {
292
0
    const char* secondBackslash = strchr(scCert->keyName + 1, '\\');
293
0
    if (secondBackslash)
294
0
      certId = secondBackslash + 1;
295
0
  }
296
297
0
  if (certId && *certId)
298
0
    return (winpr_asprintf(&scCert->pkinitArgs, &size,
299
0
                           "PKCS11:module_name=%s:slotid=%" PRIu16 ":certid=%s", pkModule,
300
0
                           (UINT16)scCert->slotId, certId) > 0);
301
302
0
  return (winpr_asprintf(&scCert->pkinitArgs, &size, "PKCS11:module_name=%s:slotid=%" PRIu16,
303
0
                         pkModule, (UINT16)scCert->slotId) > 0);
304
0
}
305
#endif /* _WIN32 */
306
307
#ifdef _WIN32
308
WINPR_ATTR_NODISCARD
309
static 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
414
out:
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
427
WINPR_ATTR_NODISCARD
428
static 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
0
{
433
0
  BOOL ret = FALSE;
434
0
  NCryptKeyName* keyName = nullptr;
435
0
  PVOID enumState = nullptr;
436
0
  SmartcardCertInfo** cert_list = *pcerts;
437
0
  size_t count = *pcount;
438
439
0
  while (NCryptEnumKeys(provider, scope, &keyName, &enumState, NCRYPT_SILENT_FLAG) ==
440
0
         ERROR_SUCCESS)
441
0
  {
442
0
    NCRYPT_KEY_HANDLE phKey = 0;
443
0
    PBYTE certBytes = nullptr;
444
0
    DWORD dwFlags = NCRYPT_SILENT_FLAG;
445
0
    DWORD cbOutput = 0;
446
0
    SmartcardCertInfo* cert = nullptr;
447
0
    BOOL haveError = TRUE;
448
0
    SECURITY_STATUS status = 0;
449
450
0
    cert = calloc(1, sizeof(SmartcardCertInfo));
451
0
    if (!cert)
452
0
      goto out;
453
454
0
    cert->keyName = ConvertWCharToUtf8Alloc(keyName->pszName, nullptr);
455
0
    if (!cert->keyName)
456
0
      goto endofloop;
457
458
0
    WLog_DBG(TAG, "opening key %s", cert->keyName);
459
460
0
    status =
461
0
        NCryptOpenKey(provider, &phKey, keyName->pszName, keyName->dwLegacyKeySpec, dwFlags);
462
0
    if (status != ERROR_SUCCESS)
463
0
    {
464
0
      WLog_DBG(TAG,
465
0
               "unable to NCryptOpenKey(dwLegacyKeySpec=0x%08" PRIx32 " dwFlags=0x%08" PRIx32
466
0
               "), status=%s, skipping",
467
0
               keyName->dwLegacyKeySpec, keyName->dwFlags,
468
0
               winpr_NCryptSecurityStatusError(status));
469
0
      goto endofloop;
470
0
    }
471
472
0
    cert->csp = _wcsdup(csp);
473
0
    if (!cert->csp)
474
0
      goto endofloop;
475
476
0
#ifndef _WIN32
477
0
    status = NCryptGetProperty(phKey, NCRYPT_WINPR_SLOTID, (PBYTE)&cert->slotId, 4, &cbOutput,
478
0
                               dwFlags);
479
0
    if (status != ERROR_SUCCESS)
480
0
    {
481
0
      WLog_ERR(TAG, "unable to retrieve slotId for key %s, status=%s", cert->keyName,
482
0
               winpr_NCryptSecurityStatusError(status));
483
0
      goto endofloop;
484
0
    }
485
0
#endif /* _WIN32 */
486
487
    /* ====== retrieve key's reader ====== */
488
0
    cbOutput = 0;
489
0
    status = NCryptGetProperty(phKey, NCRYPT_READER_PROPERTY, nullptr, 0, &cbOutput, dwFlags);
490
0
    if (status != ERROR_SUCCESS)
491
0
    {
492
0
      WLog_DBG(TAG, "unable to retrieve reader's name length for key %s", cert->keyName);
493
0
      goto endofloop;
494
0
    }
495
496
0
    cert->reader = calloc(1, cbOutput + 2);
497
0
    if (!cert->reader)
498
0
    {
499
0
      WLog_ERR(TAG, "unable to allocate reader's name for key %s", cert->keyName);
500
0
      goto endofloop;
501
0
    }
502
503
0
    status = NCryptGetProperty(phKey, NCRYPT_READER_PROPERTY, (PBYTE)cert->reader, cbOutput + 2,
504
0
                               &cbOutput, dwFlags);
505
0
    if (status != ERROR_SUCCESS)
506
0
    {
507
0
      WLog_ERR(TAG, "unable to retrieve reader's name for key %s", cert->keyName);
508
0
      goto endofloop;
509
0
    }
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
0
    cbOutput = 0;
514
0
    status = NCryptGetProperty(phKey, NCRYPT_NAME_PROPERTY, nullptr, 0, &cbOutput, dwFlags);
515
0
    if (status == ERROR_SUCCESS)
516
0
    {
517
0
      cert->containerName = calloc(1, cbOutput + sizeof(WCHAR));
518
0
      if (!cert->containerName)
519
0
      {
520
0
        WLog_ERR(TAG, "unable to allocate key container name for key %s", cert->keyName);
521
0
        goto endofloop;
522
0
      }
523
524
0
      status = NCryptGetProperty(phKey, NCRYPT_NAME_PROPERTY, (BYTE*)cert->containerName,
525
0
                                 cbOutput, &cbOutput, dwFlags);
526
0
    }
527
528
0
    if (status != ERROR_SUCCESS)
529
0
    {
530
0
      WLog_ERR(TAG, "unable to retrieve key container name for key %s", cert->keyName);
531
0
      goto endofloop;
532
0
    }
533
534
    /* ========= retrieve the certificate ===============*/
535
0
    cbOutput = 0;
536
0
    status =
537
0
        NCryptGetProperty(phKey, NCRYPT_CERTIFICATE_PROPERTY, nullptr, 0, &cbOutput, dwFlags);
538
0
    if (status != ERROR_SUCCESS)
539
0
    {
540
      /* can happen that key don't have certificates */
541
0
      WLog_DBG(TAG, "unable to retrieve certificate property len, status=%s, skipping",
542
0
               winpr_NCryptSecurityStatusError(status));
543
0
      goto endofloop;
544
0
    }
545
546
0
    certBytes = calloc(1, cbOutput);
547
0
    if (!certBytes)
548
0
    {
549
0
      WLog_ERR(TAG, "unable to allocate %" PRIu32 " certBytes for key %s", cbOutput,
550
0
               cert->keyName);
551
0
      goto endofloop;
552
0
    }
553
554
0
    status = NCryptGetProperty(phKey, NCRYPT_CERTIFICATE_PROPERTY, certBytes, cbOutput,
555
0
                               &cbOutput, dwFlags);
556
0
    if (status != ERROR_SUCCESS)
557
0
    {
558
0
      WLog_ERR(TAG, "unable to retrieve certificate for key %s", cert->keyName);
559
0
      goto endofloop;
560
0
    }
561
562
0
    if (!set_info_certificate(cert, certBytes, cbOutput, userFilter, domainFilter))
563
0
      goto endofloop;
564
565
0
#ifndef _WIN32
566
0
    if (!build_pkinit_args(provider, cert))
567
0
    {
568
0
      WLog_ERR(TAG, "error build pkinit args");
569
0
      goto endofloop;
570
0
    }
571
0
#endif
572
0
    haveError = FALSE;
573
574
0
  endofloop:
575
0
    free(certBytes);
576
0
    NCryptFreeBuffer(keyName);
577
0
    if (phKey)
578
0
      NCryptFreeObject((NCRYPT_HANDLE)phKey);
579
580
0
    if (haveError)
581
0
      smartcardCertInfo_Free(cert);
582
0
    else
583
0
    {
584
0
      if (!add_cert_to_list(&cert_list, &count, cert))
585
0
        goto out;
586
0
    }
587
0
  }
588
589
0
  ret = TRUE;
590
0
out:
591
0
  if (count == 0)
592
0
  {
593
0
    char cspa[128] = WINPR_C_ARRAY_INIT;
594
595
0
    (void)ConvertWCharToUtf8(csp, cspa, sizeof(cspa));
596
0
    char scopea[128] = WINPR_C_ARRAY_INIT;
597
0
    (void)ConvertWCharToUtf8(scope, scopea, sizeof(scopea));
598
0
    WLog_WARN(TAG, "%s [%s] no certificates found", cspa, scopea);
599
0
  }
600
0
  *pcount = count;
601
0
  *pcerts = cert_list;
602
0
  NCryptFreeBuffer(enumState);
603
0
  return ret;
604
0
}
605
606
WINPR_ATTR_NODISCARD
607
static 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
0
{
612
0
  BOOL ret = FALSE;
613
0
  LPWSTR scope = nullptr;
614
0
  NCRYPT_PROV_HANDLE provider = 0;
615
0
  SECURITY_STATUS status = 0;
616
0
  size_t count = 0;
617
0
  SmartcardCertInfo** cert_list = nullptr;
618
0
  const char* Pkcs11Module = freerdp_settings_get_string(settings, FreeRDP_Pkcs11Module);
619
620
0
  WINPR_ASSERT(scCerts);
621
0
  WINPR_ASSERT(retCount);
622
623
0
  if (reader)
624
0
  {
625
0
    char* scopeStr = nullptr;
626
0
    size_t scopeStrLen = 0;
627
0
    winpr_asprintf(&scopeStr, &scopeStrLen, "\\\\.\\%s\\", reader);
628
0
    if (!scopeStr)
629
0
      goto out;
630
631
0
    scope = ConvertUtf8NToWCharAlloc(scopeStr, scopeStrLen, nullptr);
632
0
    free(scopeStr);
633
634
0
    if (!scope)
635
0
      goto out;
636
0
  }
637
638
0
  if (Pkcs11Module)
639
0
  {
640
    /* load a unique CSP by pkcs11 module path */
641
0
    LPCSTR paths[] = { Pkcs11Module, nullptr };
642
643
0
    if (!csp)
644
0
      csp = MS_SMART_CARD_KEY_STORAGE_PROVIDER;
645
646
0
    status = winpr_NCryptOpenStorageProviderEx(&provider, csp, 0, paths);
647
0
    if (status != ERROR_SUCCESS)
648
0
    {
649
0
      WLog_ERR(TAG, "unable to open provider given by pkcs11 module");
650
0
      goto out;
651
0
    }
652
653
0
    status = list_provider_keys(settings, provider, csp, scope, userFilter, domainFilter,
654
0
                                &cert_list, &count);
655
0
    NCryptFreeObject((NCRYPT_HANDLE)provider);
656
0
    if (!status)
657
0
    {
658
0
      WLog_ERR(TAG, "error listing keys from CSP loaded from %s", Pkcs11Module);
659
0
      goto out;
660
0
    }
661
0
  }
662
0
  else
663
0
  {
664
0
    NCryptProviderName* names = nullptr;
665
0
    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
0
    status = NCryptEnumStorageProviders(&nproviders, &names, NCRYPT_SILENT_FLAG);
704
0
    if (status != ERROR_SUCCESS)
705
0
    {
706
0
      WLog_ERR(TAG, "error listing providers");
707
0
      goto out;
708
0
    }
709
710
0
    for (DWORD i = 0; i < nproviders; i++)
711
0
    {
712
0
      char providerNameStr[256] = WINPR_C_ARRAY_INIT;
713
0
      const NCryptProviderName* name = &names[i];
714
715
0
      if (ConvertWCharToUtf8(name->pszName, providerNameStr, ARRAYSIZE(providerNameStr)) < 0)
716
0
      {
717
0
        (void)_snprintf(providerNameStr, sizeof(providerNameStr), "<unknown>");
718
0
        WLog_ERR(TAG, "unable to convert provider name to char*, will show it as '%s'",
719
0
                 providerNameStr);
720
0
      }
721
722
0
      WLog_DBG(TAG, "exploring CSP '%s'", providerNameStr);
723
0
      if (csp && _wcscmp(name->pszName, csp) != 0)
724
0
      {
725
0
        WLog_DBG(TAG, "CSP '%s' filtered out", providerNameStr);
726
0
        continue;
727
0
      }
728
729
0
      status = NCryptOpenStorageProvider(&provider, name->pszName, 0);
730
0
      if (status != ERROR_SUCCESS)
731
0
        continue;
732
733
0
      if (!list_provider_keys(settings, provider, name->pszName, scope, userFilter,
734
0
                              domainFilter, &cert_list, &count))
735
0
        WLog_INFO(TAG, "error when retrieving keys in CSP '%s'", providerNameStr);
736
737
0
      NCryptFreeObject((NCRYPT_HANDLE)provider);
738
0
    }
739
740
0
    NCryptFreeBuffer(names);
741
0
  }
742
743
0
  *scCerts = cert_list;
744
0
  *retCount = count;
745
0
  ret = TRUE;
746
747
0
out:
748
0
  if (!ret)
749
0
    smartcardCertList_Free(cert_list, count);
750
0
  free(scope);
751
0
  return ret;
752
0
}
753
754
WINPR_ATTR_MALLOC(free, 1)
755
static char* create_temporary_file(void)
756
0
{
757
0
  BYTE buffer[32] = WINPR_C_ARRAY_INIT;
758
0
  char* path = nullptr;
759
760
0
  if (winpr_RAND(buffer, sizeof(buffer)) < 0)
761
0
    return nullptr;
762
763
0
  char* hex = winpr_BinToHexString(buffer, sizeof(buffer), FALSE);
764
0
  if (hex)
765
0
    path = GetKnownSubPath(KNOWN_PATH_TEMP, hex);
766
0
  free(hex);
767
0
  return path;
768
0
}
769
770
WINPR_ATTR_MALLOC(smartcardCertInfo_Free, 1)
771
static SmartcardCertInfo* smartcardCertInfo_New(const char* privKeyPEM, const char* certPEM)
772
0
{
773
0
  size_t size = 0;
774
775
0
  WINPR_ASSERT(privKeyPEM);
776
0
  WINPR_ASSERT(certPEM);
777
778
0
  SmartcardCertInfo* cert = calloc(1, sizeof(SmartcardCertInfo));
779
0
  if (!cert)
780
0
    goto fail;
781
782
0
  {
783
0
    SmartcardKeyInfo* info = cert->key_info = calloc(1, sizeof(SmartcardKeyInfo));
784
0
    if (!info)
785
0
      goto fail;
786
787
0
    cert->certificate = freerdp_certificate_new_from_pem(certPEM);
788
0
    if (!cert->certificate)
789
0
    {
790
0
      WLog_ERR(TAG, "unable to read smartcard certificate");
791
0
      goto fail;
792
0
    }
793
794
0
    if (!treat_sc_cert(cert))
795
0
    {
796
0
      WLog_ERR(TAG, "unable to treat smartcard certificate");
797
0
      goto fail;
798
0
    }
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
0
    {
805
0
      size_t derLen = 0;
806
0
      BYTE* der = freerdp_certificate_get_der(cert->certificate, &derLen);
807
0
      const BOOL hashed =
808
0
          der && (derLen > 0) &&
809
0
          winpr_Digest(WINPR_MD_SHA1, der, derLen, cert->sha1Hash, sizeof(cert->sha1Hash));
810
0
      free(der);
811
0
      if (!hashed)
812
0
      {
813
0
        WLog_ERR(TAG, "unable to compute sha1 of the emulated smartcard certificate");
814
0
        goto fail;
815
0
      }
816
0
    }
817
818
0
    {
819
0
      char* str = nullptr;
820
0
      size_t len = 0;
821
0
      (void)winpr_asprintf(&str, &len, "%s Emulator", freerdp_getApplicationDetailsString());
822
0
      if (str)
823
0
        cert->reader = ConvertUtf8NToWCharAlloc(str, len, nullptr);
824
0
      free(str);
825
0
    }
826
0
    if (!cert->reader)
827
0
      goto fail;
828
829
0
    cert->containerName = ConvertUtf8ToWCharAlloc("Private Key 00", nullptr);
830
0
    if (!cert->containerName)
831
0
      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
0
    info->keyPath = create_temporary_file();
839
0
    WLog_DBG(TAG, "writing PKINIT key to %s", info->keyPath);
840
0
    if (!crypto_write_pem(info->keyPath, privKeyPEM, strlen(privKeyPEM)))
841
0
      goto fail;
842
843
0
    info->certPath = create_temporary_file();
844
0
    WLog_DBG(TAG, "writing PKINIT cert to %s", info->certPath);
845
0
    if (!crypto_write_pem(info->certPath, certPEM, strlen(certPEM)))
846
0
      goto fail;
847
848
0
    {
849
0
      const int res = winpr_asprintf(&cert->pkinitArgs, &size, "FILE:%s,%s", info->certPath,
850
0
                                     info->keyPath);
851
0
      if (res <= 0)
852
0
        goto fail;
853
0
    }
854
0
  }
855
856
0
  return cert;
857
0
fail:
858
0
  smartcardCertInfo_Free(cert);
859
0
  return nullptr;
860
0
}
861
862
WINPR_ATTR_NODISCARD
863
static BOOL smartcard_sw_enumerateCerts(const rdpSettings* settings, SmartcardCertInfo*** scCerts,
864
                                        size_t* retCount)
865
0
{
866
0
  BOOL rc = FALSE;
867
0
  SmartcardCertInfo** cert_list = nullptr;
868
869
0
  WINPR_ASSERT(settings);
870
0
  WINPR_ASSERT(scCerts);
871
0
  WINPR_ASSERT(retCount);
872
873
0
  const char* privKeyPEM = freerdp_settings_get_string(settings, FreeRDP_SmartcardPrivateKey);
874
0
  const char* certPEM = freerdp_settings_get_string(settings, FreeRDP_SmartcardCertificate);
875
0
  if (!privKeyPEM)
876
0
  {
877
0
    WLog_ERR(TAG, "Invalid smartcard private key PEM, aborting");
878
0
    goto out_error;
879
0
  }
880
0
  if (!certPEM)
881
0
  {
882
0
    WLog_ERR(TAG, "Invalid smartcard certificate PEM, aborting");
883
0
    goto out_error;
884
0
  }
885
886
0
  cert_list = (SmartcardCertInfo**)calloc(1, sizeof(SmartcardCertInfo*));
887
0
  if (!cert_list)
888
0
    goto out_error;
889
890
0
  {
891
0
    SmartcardCertInfo* cert = smartcardCertInfo_New(privKeyPEM, certPEM);
892
0
    if (!cert)
893
0
      goto out_error;
894
0
    cert_list[0] = cert;
895
0
  }
896
897
0
  rc = TRUE;
898
0
  *scCerts = cert_list;
899
0
  *retCount = 1;
900
901
0
out_error:
902
0
  if (!rc)
903
0
    smartcardCertList_Free(cert_list, 1);
904
0
  return rc;
905
0
}
906
907
BOOL smartcard_enumerateCerts(const rdpSettings* settings, SmartcardCertInfo*** scCerts,
908
                              size_t* retCount, BOOL gateway)
909
0
{
910
0
  BOOL ret = 0;
911
0
  LPWSTR csp = nullptr;
912
0
  const char* ReaderName = freerdp_settings_get_string(settings, FreeRDP_ReaderName);
913
0
  const char* CspName = freerdp_settings_get_string(settings, FreeRDP_CspName);
914
0
  const char* Username = nullptr;
915
0
  const char* Domain = nullptr;
916
917
0
  if (gateway)
918
0
  {
919
0
    Username = freerdp_settings_get_string(settings, FreeRDP_GatewayUsername);
920
0
    Domain = freerdp_settings_get_string(settings, FreeRDP_GatewayDomain);
921
0
  }
922
0
  else
923
0
  {
924
0
    Username = freerdp_settings_get_string(settings, FreeRDP_Username);
925
0
    Domain = freerdp_settings_get_string(settings, FreeRDP_Domain);
926
0
  }
927
928
0
  WINPR_ASSERT(settings);
929
0
  WINPR_ASSERT(scCerts);
930
0
  WINPR_ASSERT(retCount);
931
932
0
  if (Domain && !strlen(Domain))
933
0
    Domain = nullptr;
934
935
0
  if (freerdp_settings_get_bool(settings, FreeRDP_SmartcardEmulation))
936
0
    return smartcard_sw_enumerateCerts(settings, scCerts, retCount);
937
938
0
  if (CspName && (!(csp = ConvertUtf8ToWCharAlloc(CspName, nullptr))))
939
0
  {
940
0
    WLog_ERR(TAG, "error while converting CSP to WCHAR");
941
0
    return FALSE;
942
0
  }
943
944
0
  ret =
945
0
      smartcard_hw_enumerateCerts(settings, csp, ReaderName, Username, Domain, scCerts, retCount);
946
0
  free(csp);
947
0
  return ret;
948
0
}
949
950
WINPR_ATTR_NODISCARD
951
static BOOL set_settings_from_smartcard(rdpSettings* settings, FreeRDP_Settings_Keys_String id,
952
                                        const char* value)
953
0
{
954
0
  WINPR_ASSERT(settings);
955
956
0
  if (!freerdp_settings_get_string(settings, id) && value)
957
0
    if (!freerdp_settings_set_string(settings, id, value))
958
0
      return FALSE;
959
960
0
  return TRUE;
961
0
}
962
963
BOOL smartcard_getCert(const rdpContext* context, SmartcardCertInfo** cert, BOOL gateway)
964
0
{
965
0
  WINPR_ASSERT(context);
966
967
0
  const freerdp* instance = context->instance;
968
0
  rdpSettings* settings = context->settings;
969
0
  SmartcardCertInfo** cert_list = nullptr;
970
0
  size_t count = 0;
971
972
0
  WINPR_ASSERT(instance);
973
0
  WINPR_ASSERT(settings);
974
975
0
  if (!smartcard_enumerateCerts(settings, &cert_list, &count, gateway))
976
0
    return FALSE;
977
978
0
  if (count < 1)
979
0
  {
980
0
    WLog_ERR(TAG, "no suitable smartcard certificates were found");
981
0
    return FALSE;
982
0
  }
983
984
0
  if (count > UINT32_MAX)
985
0
  {
986
0
    WLog_ERR(TAG, "smartcard certificate count %" PRIuz " exceeds UINT32_MAX", count);
987
0
    return FALSE;
988
0
  }
989
990
0
  if (count > 1)
991
0
  {
992
0
    DWORD index = 0;
993
994
0
    if (!instance->ChooseSmartcard ||
995
0
        !instance->ChooseSmartcard(context->instance, cert_list, (UINT32)count, &index,
996
0
                                   gateway))
997
0
    {
998
0
      WLog_ERR(TAG, "more than one suitable smartcard certificate was found");
999
0
      smartcardCertList_Free(cert_list, count);
1000
0
      return FALSE;
1001
0
    }
1002
0
    *cert = cert_list[index];
1003
1004
0
    for (DWORD i = 0; i < index; i++)
1005
0
      smartcardCertInfo_Free(cert_list[i]);
1006
0
    for (DWORD i = index + 1; i < count; i++)
1007
0
      smartcardCertInfo_Free(cert_list[i]);
1008
0
  }
1009
0
  else
1010
0
    *cert = cert_list[0];
1011
1012
0
  FreeRDP_Settings_Keys_String username_setting =
1013
0
      gateway ? FreeRDP_GatewayUsername : FreeRDP_Username;
1014
0
  FreeRDP_Settings_Keys_String domain_setting = gateway ? FreeRDP_GatewayDomain : FreeRDP_Domain;
1015
1016
0
  free((void*)cert_list);
1017
1018
0
  if (!set_settings_from_smartcard(settings, username_setting, (*cert)->userHint) ||
1019
0
      !set_settings_from_smartcard(settings, domain_setting, (*cert)->domainHint))
1020
0
  {
1021
0
    WLog_ERR(TAG, "unable to set settings from smartcard!");
1022
0
    smartcardCertInfo_Free(*cert);
1023
0
    return FALSE;
1024
0
  }
1025
1026
0
  return TRUE;
1027
0
}