Coverage Report

Created: 2026-09-14 06:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/FreeRDP/libfreerdp/crypto/x509_utils.c
Line
Count
Source
1
/**
2
 * FreeRDP: A Remote Desktop Protocol Implementation
3
 * Cryptographic Abstraction Layer
4
 *
5
 * Copyright 2011-2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
6
 * Copyright 2023 Armin Novak <anovak@thincast.com>
7
 * Copyright 2023 Thincast Technologies GmbH
8
 *
9
 * Licensed under the Apache License, Version 2.0 (the "License");
10
 * you may not use this file except in compliance with the License.
11
 * You may obtain a copy of the License at
12
 *
13
 *   http://www.apache.org/licenses/LICENSE-2.0
14
 *
15
 * Unless required by applicable law or agreed to in writing, software
16
 * distributed under the License is distributed on an "AS IS" BASIS,
17
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
 * See the License for the specific language governing permissions and
19
 * limitations under the License.
20
 */
21
22
#include <ctype.h>
23
24
#include <openssl/objects.h>
25
#include <openssl/x509v3.h>
26
#include <openssl/pem.h>
27
#include <openssl/rsa.h>
28
#include <openssl/err.h>
29
30
#include <freerdp/config.h>
31
32
#include <winpr/crt.h>
33
#include <winpr/string.h>
34
#include <winpr/assert.h>
35
36
#include <freerdp/log.h>
37
38
#include "x509_utils.h"
39
40
#define TAG FREERDP_TAG("crypto")
41
42
BYTE* x509_utils_get_hash(const X509* xcert, const char* hash, size_t* length)
43
0
{
44
0
  UINT32 fp_len = EVP_MAX_MD_SIZE;
45
0
  BYTE* fp = nullptr;
46
0
  const EVP_MD* md = EVP_get_digestbyname(hash);
47
0
  if (!md)
48
0
  {
49
0
    WLog_ERR(TAG, "System does not support %s hash!", hash);
50
0
    return nullptr;
51
0
  }
52
0
  if (!xcert || !length)
53
0
  {
54
0
    WLog_ERR(TAG, "Invalid arguments: xcert=%p, length=%p",
55
0
             WINPR_CXX_COMPAT_CAST(const void*, xcert),
56
0
             WINPR_CXX_COMPAT_CAST(const void*, length));
57
0
    return nullptr;
58
0
  }
59
60
0
  fp = calloc(fp_len + 1, sizeof(BYTE));
61
0
  if (!fp)
62
0
  {
63
0
    WLog_ERR(TAG, "could not allocate %" PRIu32 " bytes", fp_len);
64
0
    return nullptr;
65
0
  }
66
67
0
  if (X509_digest(xcert, md, fp, &fp_len) != 1)
68
0
  {
69
0
    free(fp);
70
0
    WLog_ERR(TAG, "certificate does not have a %s hash!", hash);
71
0
    return nullptr;
72
0
  }
73
74
0
  *length = fp_len;
75
0
  return fp;
76
0
}
77
78
WINPR_ATTR_NODISCARD
79
static char* crypto_print_name(const X509_NAME* name)
80
0
{
81
0
  char* buffer = nullptr;
82
0
  BIO* outBIO = BIO_new(BIO_s_mem());
83
0
  if (!outBIO)
84
0
    return nullptr;
85
86
0
  if (X509_NAME_print_ex(outBIO, name, 0, XN_FLAG_ONELINE) > 0)
87
0
    buffer = x509_utils_bio_read(outBIO, nullptr);
88
89
0
  BIO_free_all(outBIO);
90
0
  return buffer;
91
0
}
92
93
char* x509_utils_get_subject(const X509* xcert)
94
0
{
95
0
  char* subject = nullptr;
96
0
  if (!xcert)
97
0
  {
98
0
    WLog_ERR(TAG, "Invalid certificate nullptr");
99
0
    return nullptr;
100
0
  }
101
0
  subject = crypto_print_name(X509_get_subject_name(xcert));
102
0
  if (!subject)
103
0
    WLog_WARN(TAG, "certificate does not have a subject!");
104
0
  return subject;
105
0
}
106
107
/* GENERAL_NAME type labels */
108
109
static const char* general_name_type_labels[] = { "OTHERNAME", "EMAIL    ", "DNS      ",
110
                                                "X400     ", "DIRNAME  ", "EDIPARTY ",
111
                                                "URI      ", "IPADD    ", "RID      " };
112
113
WINPR_ATTR_NODISCARD
114
static const char* general_name_type_label(int general_name_type)
115
0
{
116
0
  if ((0 <= general_name_type) &&
117
0
      ((size_t)general_name_type < ARRAYSIZE(general_name_type_labels)))
118
0
  {
119
0
    return general_name_type_labels[general_name_type];
120
0
  }
121
0
  else
122
0
  {
123
0
    static char buffer[80] = WINPR_C_ARRAY_INIT;
124
0
    (void)snprintf(buffer, sizeof(buffer), "Unknown general name type (%d)", general_name_type);
125
0
    return buffer;
126
0
  }
127
0
}
128
129
/*
130
131
map_subject_alt_name(x509,  general_name_type, mapper, data)
132
133
Call the function mapper with subjectAltNames found in the x509
134
certificate and data.  if generate_name_type is GEN_ALL,  the the
135
mapper is called for all the names,  else it's called only for names
136
of the given type.
137
138
139
We implement two extractors:
140
141
 -  a string extractor that can be used to get the subjectAltNames of
142
    the following types: GEN_URI,  GEN_DNS,  GEN_EMAIL
143
144
 - a ASN1_OBJECT filter/extractor that can be used to get the
145
   subjectAltNames of OTHERNAME type.
146
147
   Note: usually, it's a string, but some type of otherNames can be
148
   associated with different classes of objects. eg. a KPN may be a
149
   sequence of realm and principal name, instead of a single string
150
   object.
151
152
Not implemented yet: extractors for the types: GEN_X400, GEN_DIRNAME,
153
GEN_EDIPARTY, GEN_RID, GEN_IPADD (the later can contain nul-bytes).
154
155
156
mapper(name, data, index, count)
157
158
The mapper is passed:
159
 - the GENERAL_NAME selected,
160
 - the data,
161
 - the index of the general name in the subjectAltNames,
162
 - the total number of names in the subjectAltNames.
163
164
The last parameter let's the mapper allocate arrays to collect objects.
165
Note: if names are filtered,  not all the indices from 0 to count-1 are
166
passed to mapper,  only the indices selected.
167
168
When the mapper returns 0, map_subject_alt_name stops the iteration immediately.
169
170
*/
171
172
0
#define GEN_ALL (-1)
173
174
typedef int (*general_name_mapper_pr)(const X509* x509, GENERAL_NAME* name, void* data, int index,
175
                                      int count);
176
177
static void map_subject_alt_name(const X509* x509, int general_name_type,
178
                                 general_name_mapper_pr mapper, void* data)
179
0
{
180
0
  STACK_OF(GENERAL_NAME)* gens = X509_get_ext_d2i(x509, NID_subject_alt_name, nullptr, nullptr);
181
182
0
  if (!gens)
183
0
    return;
184
185
0
  const int num = sk_GENERAL_NAME_num(gens);
186
187
0
  for (int i = 0; (i < num); i++)
188
0
  {
189
0
    GENERAL_NAME* name = sk_GENERAL_NAME_value(gens, i);
190
191
0
    if (name)
192
0
    {
193
0
      if ((general_name_type == GEN_ALL) || (general_name_type == name->type))
194
0
      {
195
0
        if (!mapper(x509, name, data, i, num))
196
0
        {
197
0
          break;
198
0
        }
199
0
      }
200
0
    }
201
0
  }
202
203
0
  sk_GENERAL_NAME_pop_free(gens, GENERAL_NAME_free);
204
0
}
205
206
/*
207
extract_string  --  string extractor
208
209
- the strings array is allocated lazily, when we first have to store a
210
  string.
211
212
- allocated contains the size of the strings array, or -1 if
213
  allocation failed.
214
215
- count contains the actual count of strings in the strings array.
216
217
- maximum limits the number of strings we can store in the strings
218
  array: beyond, the extractor returns 0 to short-cut the search.
219
220
extract_string stores in the string list OPENSSL strings,
221
that must be freed with OPENSSL_free.
222
223
*/
224
225
typedef struct string_list
226
{
227
  char** strings;
228
  size_t* lengths;
229
  size_t allocated;
230
  size_t count;
231
  size_t maximum;
232
} string_list;
233
234
static string_list string_list_initialize(void)
235
0
{
236
0
  const string_list empty = {
237
0
    .strings = nullptr, .lengths = nullptr, .allocated = 0, .count = 0, .maximum = INT_MAX
238
0
  };
239
0
  return empty;
240
0
}
241
242
static BOOL string_list_allocate(string_list* list, size_t allocate_count)
243
0
{
244
0
  WINPR_ASSERT(list);
245
0
  if (!list->strings && (list->allocated == 0) && (allocate_count > 0))
246
0
  {
247
0
    list->strings = (char**)calloc(allocate_count, sizeof(char*));
248
0
    list->lengths = calloc(allocate_count, sizeof(size_t));
249
0
    list->count = 0;
250
0
    if (!list->strings || !list->lengths)
251
0
    {
252
0
      free((void*)list->strings);
253
0
      free(list->lengths);
254
0
      list->strings = nullptr;
255
0
      list->lengths = nullptr;
256
0
      return FALSE;
257
0
    }
258
0
    list->allocated = allocate_count;
259
0
  }
260
0
  return TRUE;
261
0
}
262
263
static void string_list_free(string_list* list)
264
0
{
265
  /* Note: we don't free the contents of the strings array: this */
266
  /* is handled by the caller,  either by returning this */
267
  /* content,  or freeing it itself. */
268
0
  free((void*)list->strings);
269
0
  free(list->lengths);
270
0
}
271
272
WINPR_ATTR_NODISCARD
273
static BOOL check_string_is_email(WINPR_ATTR_UNUSED const X509* x509, const unsigned char* ustr,
274
                                  size_t length)
275
0
{
276
0
  const size_t MAX_EMAIL_LENGTH = 256;
277
0
  const size_t MIN_EMAIL_LENGTH = 5;
278
279
0
  if (ustr == nullptr)
280
0
    return FALSE;
281
282
0
  const char* email = (const char*)ustr;
283
0
  const size_t len = strnlen(email, length);
284
0
  if ((len < MIN_EMAIL_LENGTH) || (len > MAX_EMAIL_LENGTH))
285
0
    return FALSE;
286
287
0
  size_t at_pos = 0;
288
0
  size_t at_count = 0;
289
290
0
  for (size_t i = 0; i < len; i++)
291
0
  {
292
0
    char cur = email[i];
293
0
    if (cur == '@')
294
0
    {
295
      /* @ must not be first or last */
296
0
      if (i == 0)
297
0
        return FALSE;
298
0
      if (i == len - 1)
299
0
        return FALSE;
300
0
      at_pos = i;
301
0
      at_count++;
302
0
    }
303
0
    if (isspace(cur))
304
0
      return FALSE;
305
0
  }
306
307
  /* only one @ allowed */
308
0
  if (at_count != 1)
309
0
  {
310
0
    return FALSE;
311
0
  }
312
313
  /* local part */
314
0
  if ((email[0] == '.') || (email[at_pos - 1] == '.'))
315
0
    return FALSE;
316
317
  /* .. forbidden */
318
0
  for (size_t i = 0; i < at_pos - 1; i++)
319
0
  {
320
0
    if ((email[i] == '.') && (email[i + 1] == '.'))
321
0
      return FALSE;
322
0
  }
323
324
  // Validate the domain part (after '@')
325
0
  const char* domain = &email[at_pos + 1];
326
0
  size_t domain_len = strnlen(domain, len);
327
328
0
  if (!winpr_str_is_valid_urlN(domain, domain_len))
329
0
    return FALSE;
330
331
  /* local part */
332
0
  for (size_t i = 0; i < at_pos; i++)
333
0
  {
334
0
    if (!isalnum(email[i]) && email[i] != '.' && email[i] != '-' && email[i] != '_' &&
335
0
        email[i] != '+')
336
0
    {
337
0
      return FALSE;
338
0
    }
339
0
  }
340
341
0
  return TRUE;
342
0
}
343
344
WINPR_ATTR_NODISCARD
345
static BOOL check_string_is_host_or_ip(WINPR_ATTR_UNUSED const X509* x509,
346
                                       const unsigned char* ustr, size_t length)
347
0
{
348
0
  const char* str = (const char*)ustr;
349
0
  if (strnlen(str, length) != length)
350
0
    return FALSE;
351
0
  return winpr_str_is_valid_urlN(str, length);
352
0
}
353
354
WINPR_ATTR_NODISCARD
355
static BOOL check_string_is_host_or_ip_or_email(WINPR_ATTR_UNUSED const X509* x509,
356
                                                const unsigned char* ustr, size_t length)
357
0
{
358
0
  if (check_string_is_host_or_ip(x509, ustr, length))
359
0
    return TRUE;
360
0
  return check_string_is_email(x509, ustr, length);
361
0
}
362
363
WINPR_ATTR_NODISCARD
364
static int
365
extract_string_generic(const X509* x509, GENERAL_NAME* name, void* data, int index, int count,
366
                       BOOL (*fkt)(const X509* x509, const unsigned char* str, size_t length))
367
0
{
368
0
  string_list* list = data;
369
0
  WINPR_ASSERT(list);
370
0
  WINPR_ASSERT(fkt);
371
372
0
  WINPR_ASSERT(name);
373
0
  WINPR_UNUSED(index);
374
375
0
  const ASN1_STRING* str = nullptr;
376
0
  switch (name->type)
377
0
  {
378
0
    case GEN_URI:
379
0
      str = name->d.uniformResourceIdentifier;
380
0
      break;
381
382
0
    case GEN_DNS:
383
0
      str = name->d.dNSName;
384
0
      break;
385
386
0
    case GEN_EMAIL:
387
0
      str = name->d.rfc822Name;
388
0
      break;
389
390
0
    default:
391
0
      return 1;
392
0
  }
393
394
0
  unsigned char* cstring = nullptr;
395
0
  const int rc = ASN1_STRING_to_UTF8(&cstring, str);
396
0
  if (rc < 0)
397
0
  {
398
0
    WLog_ERR(TAG, "ASN1_STRING_to_UTF8() failed for %s: %s",
399
0
             general_name_type_label(name->type), ERR_error_string(ERR_get_error(), nullptr));
400
0
    return 1;
401
0
  }
402
403
0
  if (!fkt(x509, cstring, WINPR_ASSERTING_INT_CAST(size_t, rc)))
404
0
  {
405
0
    WLog_ERR(TAG, "ASN1_STRING_to_UTF8() does not conform to expected format %s: %s",
406
0
             general_name_type_label(name->type), (const char*)str);
407
0
    OPENSSL_free(cstring);
408
0
    return -1;
409
0
  }
410
411
0
  if (!string_list_allocate(list, WINPR_ASSERTING_INT_CAST(WINPR_CIPHER_TYPE, count)) ||
412
0
      (list->allocated <= 0))
413
0
  {
414
0
    WLog_ERR(TAG, "ASN1_STRING_to_UTF8() allocation failed: %s",
415
0
             general_name_type_label(name->type));
416
0
    OPENSSL_free(cstring);
417
0
    return 0;
418
0
  }
419
420
0
  list->strings[list->count] = (char*)cstring;
421
0
  list->lengths[list->count] = WINPR_ASSERTING_INT_CAST(size_t, rc);
422
0
  list->count++;
423
424
0
  if (list->count >= list->maximum)
425
0
  {
426
0
    WLog_ERR(TAG, "ASN1_STRING_to_UTF8() limit exceeded: %s",
427
0
             general_name_type_label(name->type));
428
0
    return 0;
429
0
  }
430
431
0
  return 1;
432
0
}
433
434
WINPR_ATTR_NODISCARD
435
static int extract_string(const X509* x509, GENERAL_NAME* name, void* data, int index, int count)
436
0
{
437
0
  return extract_string_generic(x509, name, data, index, count, check_string_is_host_or_ip);
438
0
}
439
440
static int extract_email(const X509* x509, GENERAL_NAME* name, void* data, int index, int count)
441
0
{
442
0
  return extract_string_generic(x509, name, data, index, count, check_string_is_email);
443
0
}
444
445
/*
446
extract_othername_object --  object extractor.
447
448
- the objects array is allocated lazily, when we first have to store a
449
  string.
450
451
- allocated contains the size of the objects array, or -1 if
452
  allocation failed.
453
454
- count contains the actual count of objects in the objects array.
455
456
- maximum limits the number of objects we can store in the objects
457
  array: beyond, the extractor returns 0 to short-cut the search.
458
459
extract_othername_objects stores in the objects array ASN1_TYPE *
460
pointers directly obtained from the GENERAL_NAME.
461
*/
462
463
typedef struct object_list
464
{
465
  ASN1_OBJECT* type_id;
466
  char** strings;
467
  size_t* lengths;
468
469
  size_t allocated;
470
  size_t count;
471
  size_t maximum;
472
} object_list;
473
474
static object_list object_list_initialize(void)
475
0
{
476
0
  const object_list empty = { .type_id = nullptr,
477
0
                            .strings = nullptr,
478
0
                            .lengths = nullptr,
479
0
                            .allocated = 0,
480
0
                            .count = 0,
481
0
                            .maximum = INT_MAX };
482
0
  return empty;
483
0
}
484
485
WINPR_ATTR_NODISCARD
486
static BOOL object_list_allocate(object_list* list, size_t allocate_count)
487
0
{
488
0
  if (!list->strings && (list->allocated == 0) && (allocate_count > 0))
489
0
  {
490
0
    list->strings = (char**)calloc(allocate_count, sizeof(list->strings[0]));
491
0
    list->lengths = calloc(allocate_count, sizeof(size_t));
492
0
    list->count = 0;
493
0
    if (!list->strings || !list->lengths)
494
0
    {
495
0
      free((void*)list->strings);
496
0
      free(list->lengths);
497
0
      list->strings = nullptr;
498
0
      list->lengths = nullptr;
499
0
      return FALSE;
500
0
    }
501
0
    list->allocated = allocate_count;
502
0
  }
503
0
  return TRUE;
504
0
}
505
506
WINPR_ATTR_MALLOC(free, 1)
507
static char* object_string(const X509* x509, ASN1_TYPE* object, size_t* pLength)
508
0
{
509
0
  unsigned char* utf8String = nullptr;
510
511
0
  WINPR_ASSERT(object);
512
0
  WINPR_ASSERT(pLength);
513
514
0
  *pLength = 0;
515
516
  /* TODO: check that object.type is a string type. */
517
0
  const int length = ASN1_STRING_to_UTF8(&utf8String, object->value.asn1_string);
518
519
0
  if (length < 0)
520
0
    return nullptr;
521
522
0
  char* result = nullptr;
523
0
  if (check_string_is_host_or_ip_or_email(x509, utf8String,
524
0
                                          WINPR_ASSERTING_INT_CAST(size_t, length)))
525
0
  {
526
0
    result = strndup((char*)utf8String, WINPR_ASSERTING_INT_CAST(size_t, length));
527
0
    if (result)
528
0
      *pLength = WINPR_ASSERTING_INT_CAST(size_t, length);
529
0
  }
530
0
  else
531
0
    WLog_ERR(TAG, "Found invalid object_string entry in certificate: '%s'", utf8String);
532
0
  OPENSSL_free(utf8String);
533
0
  return result;
534
0
}
535
536
static void object_list_free(object_list* list)
537
0
{
538
0
  WINPR_ASSERT(list);
539
0
  free((void*)list->strings);
540
0
  free(list->lengths);
541
0
}
542
543
WINPR_ATTR_NODISCARD
544
static int extract_othername_object_as_string(const X509* x509, GENERAL_NAME* name, void* data,
545
                                              int index, int count)
546
0
{
547
0
  object_list* list = data;
548
0
  WINPR_UNUSED(index);
549
0
  WINPR_ASSERT(x509);
550
551
0
  if (count < 0)
552
0
    return -1;
553
554
0
  if (name->type != GEN_OTHERNAME)
555
0
  {
556
0
    return 1;
557
0
  }
558
559
0
  if (0 != OBJ_cmp(name->d.otherName->type_id, list->type_id))
560
0
  {
561
0
    return 1;
562
0
  }
563
564
0
  if (!object_list_allocate(list, WINPR_ASSERTING_INT_CAST(size_t, count)) ||
565
0
      (list->allocated <= 0))
566
0
  {
567
0
    return 0;
568
0
  }
569
570
0
  list->strings[list->count] =
571
0
      object_string(x509, name->d.otherName->value, &list->lengths[list->count]);
572
0
  if (list->strings[list->count])
573
0
  {
574
0
    list->count++;
575
0
  }
576
577
0
  if (list->count >= list->maximum)
578
0
  {
579
0
    return 0;
580
0
  }
581
582
0
  return 1;
583
0
}
584
585
char* x509_utils_get_email(const X509* x509)
586
0
{
587
0
  string_list list = string_list_initialize();
588
0
  list.maximum = 1;
589
0
  map_subject_alt_name(x509, GEN_EMAIL, extract_email, &list);
590
591
0
  if (list.count == 0)
592
0
  {
593
0
    string_list_free(&list);
594
0
    return nullptr;
595
0
  }
596
597
0
  char* result = strndup(list.strings[0], list.lengths[0]);
598
0
  OPENSSL_free(list.strings[0]);
599
0
  string_list_free(&list);
600
0
  return result;
601
0
}
602
603
char* x509_utils_get_upn(const X509* x509)
604
0
{
605
0
  object_list list = object_list_initialize();
606
607
0
  list.type_id = OBJ_nid2obj(NID_ms_upn);
608
0
  list.maximum = 1;
609
0
  map_subject_alt_name(x509, GEN_OTHERNAME, extract_othername_object_as_string, &list);
610
611
0
  if (list.count == 0)
612
0
  {
613
0
    object_list_free(&list);
614
0
    return nullptr;
615
0
  }
616
617
0
  char* result = list.strings[0];
618
0
  object_list_free(&list);
619
0
  return result;
620
0
}
621
622
char* x509_utils_get_date(const X509* x509, BOOL startDate)
623
0
{
624
0
  WINPR_ASSERT(x509);
625
626
0
  const ASN1_TIME* date = startDate ? X509_get0_notBefore(x509) : X509_get0_notAfter(x509);
627
0
  if (!date)
628
0
    return nullptr;
629
630
0
  BIO* bmem = BIO_new(BIO_s_mem());
631
0
  if (!bmem)
632
0
    return nullptr;
633
634
0
  char* str = nullptr;
635
0
  if (ASN1_TIME_print(bmem, date))
636
0
  {
637
0
    BUF_MEM* bptr = nullptr;
638
639
0
    BIO_get_mem_ptr(bmem, &bptr);
640
0
    str = strndup(bptr->data, bptr->length);
641
0
  }
642
0
  else
643
0
  { // Log error
644
0
  }
645
0
  BIO_free_all(bmem);
646
0
  return str;
647
0
}
648
649
void x509_utils_dns_names_free(size_t count, size_t* lengths, char** dns_names)
650
0
{
651
0
  free(lengths);
652
653
0
  if (dns_names)
654
0
  {
655
0
    for (size_t i = 0; i < count; i++)
656
0
    {
657
0
      if (dns_names[i])
658
0
      {
659
0
        OPENSSL_free(dns_names[i]);
660
0
      }
661
0
    }
662
663
0
    free((void*)dns_names);
664
0
  }
665
0
}
666
667
char** x509_utils_get_dns_names(const X509* xcert, size_t* count, size_t** lengths)
668
0
{
669
0
  string_list list = string_list_initialize();
670
0
  map_subject_alt_name(xcert, GEN_DNS, extract_string, &list);
671
0
  (*count) = list.count;
672
673
0
  if (list.count <= 0)
674
0
  {
675
0
    string_list_free(&list);
676
0
    return nullptr;
677
0
  }
678
679
  /* lengths are not useful,  since we converted the
680
     strings to utf-8,  there cannot be nul-bytes in them. */
681
0
  char** result = (char**)calloc(list.count, sizeof(*result));
682
0
  (*lengths) = calloc(list.count, sizeof(**lengths));
683
684
0
  if (!result || !(*lengths))
685
0
  {
686
0
    string_list_free(&list);
687
0
    free((void*)result);
688
0
    free(*lengths);
689
0
    (*lengths) = nullptr;
690
0
    (*count) = 0;
691
0
    return nullptr;
692
0
  }
693
694
0
  for (size_t i = 0; i < list.count; i++)
695
0
  {
696
0
    result[i] = list.strings[i];
697
0
    (*lengths)[i] = list.lengths[i];
698
0
  }
699
700
0
  string_list_free(&list);
701
0
  return result;
702
0
}
703
704
char* x509_utils_get_issuer(const X509* xcert)
705
0
{
706
0
  char* issuer = nullptr;
707
0
  if (!xcert)
708
0
  {
709
0
    WLog_ERR(TAG, "Invalid certificate nullptr");
710
0
    return nullptr;
711
0
  }
712
0
  issuer = crypto_print_name(X509_get_issuer_name(xcert));
713
0
  if (!issuer)
714
0
    WLog_WARN(TAG, "certificate does not have an issuer!");
715
0
  return issuer;
716
0
}
717
718
WINPR_ATTR_NODISCARD
719
static int asn1_object_cmp(const ASN1_OBJECT* const* a, const ASN1_OBJECT* const* b)
720
0
{
721
0
  if (!a || !b)
722
0
    return (a == b) ? 0 : (a ? 1 : -1);
723
724
0
  if (!*a || !*b)
725
0
    return (*a == *b) ? 0 : (*a ? 1 : -1);
726
727
0
  return OBJ_cmp(*a, *b);
728
0
}
729
730
BOOL x509_utils_check_eku(const X509* xcert, int nid)
731
0
{
732
0
  BOOL ret = FALSE;
733
0
  STACK_OF(ASN1_OBJECT)* oid_stack = nullptr;
734
0
  ASN1_OBJECT* oid = nullptr;
735
736
0
  if (!xcert)
737
0
    return FALSE;
738
739
0
  oid = OBJ_nid2obj(nid);
740
0
  if (!oid)
741
0
    return FALSE;
742
743
0
  oid_stack = X509_get_ext_d2i(xcert, NID_ext_key_usage, nullptr, nullptr);
744
0
  if (!oid_stack)
745
0
    return FALSE;
746
747
  /* sk_ASN1_OBJECT_set_cmp_func does some function pointer casting that produces a warning. We
748
   * can not fix upstream, so silence it here. */
749
0
  WINPR_PRAGMA_DIAG_PUSH
750
0
  WINPR_PRAGMA_DIAG_IGNORED_CAST_FUNCTION_TYPE
751
0
  sk_ASN1_OBJECT_set_cmp_func(oid_stack, asn1_object_cmp);
752
0
  WINPR_PRAGMA_DIAG_POP
753
0
  if (sk_ASN1_OBJECT_find(oid_stack, oid) >= 0)
754
0
    ret = TRUE;
755
756
0
  sk_ASN1_OBJECT_pop_free(oid_stack, ASN1_OBJECT_free);
757
0
  return ret;
758
0
}
759
760
void x509_utils_print_info(const X509* xcert)
761
0
{
762
0
  char* subject = x509_utils_get_subject(xcert);
763
0
  char* issuer = x509_utils_get_issuer(xcert);
764
0
  char* fp = (char*)x509_utils_get_hash(xcert, "sha256", nullptr);
765
766
0
  if (!fp)
767
0
  {
768
0
    WLog_ERR(TAG, "error computing fingerprint");
769
0
    goto out_free_issuer;
770
0
  }
771
772
0
  WLog_INFO(TAG, "Certificate details:");
773
0
  WLog_INFO(TAG, "\tSubject: %s", subject);
774
0
  WLog_INFO(TAG, "\tIssuer: %s", issuer);
775
0
  WLog_INFO(TAG, "\tThumbprint: %s", fp);
776
0
  WLog_INFO(TAG,
777
0
            "The above X.509 certificate could not be verified, possibly because you do not have "
778
0
            "the CA certificate in your certificate store, or the certificate has expired. "
779
0
            "Please look at the OpenSSL documentation on how to add a private CA to the store.");
780
0
  free(fp);
781
0
out_free_issuer:
782
0
  free(issuer);
783
0
  free(subject);
784
0
}
785
786
X509* x509_utils_from_pem(const char* data, size_t len, BOOL fromFile)
787
0
{
788
0
  BIO* bio = nullptr;
789
0
  if (fromFile)
790
0
    bio = BIO_new_file(data, "rb");
791
0
  else
792
0
  {
793
0
    if (len > INT_MAX)
794
0
      return nullptr;
795
796
0
    bio = BIO_new_mem_buf(data, (int)len);
797
0
  }
798
799
0
  if (!bio)
800
0
  {
801
0
    WLog_ERR(TAG, "BIO_new failed for certificate");
802
0
    return nullptr;
803
0
  }
804
805
0
  X509* x509 = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
806
0
  BIO_free_all(bio);
807
0
  if (!x509)
808
0
    WLog_ERR(TAG, "PEM_read_bio_X509 returned nullptr [input length %" PRIuz "]", len);
809
810
0
  return x509;
811
0
}
812
813
WINPR_ATTR_NODISCARD
814
static WINPR_MD_TYPE hash_nid_to_winpr(int hash_nid)
815
0
{
816
0
  switch (hash_nid)
817
0
  {
818
0
    case NID_md2:
819
0
      return WINPR_MD_MD2;
820
0
    case NID_md4:
821
0
      return WINPR_MD_MD4;
822
0
    case NID_md5:
823
0
      return WINPR_MD_MD5;
824
0
    case NID_sha1:
825
0
      return WINPR_MD_SHA1;
826
0
    case NID_sha224:
827
0
      return WINPR_MD_SHA224;
828
0
    case NID_sha256:
829
0
      return WINPR_MD_SHA256;
830
0
    case NID_sha384:
831
0
      return WINPR_MD_SHA384;
832
0
    case NID_sha512:
833
0
      return WINPR_MD_SHA512;
834
0
    case NID_ripemd160:
835
0
      return WINPR_MD_RIPEMD160;
836
0
#if (OPENSSL_VERSION_NUMBER >= 0x1010101fL) && !defined(LIBRESSL_VERSION_NUMBER)
837
0
    case NID_sha3_224:
838
0
      return WINPR_MD_SHA3_224;
839
0
    case NID_sha3_256:
840
0
      return WINPR_MD_SHA3_256;
841
0
    case NID_sha3_384:
842
0
      return WINPR_MD_SHA3_384;
843
0
    case NID_sha3_512:
844
0
      return WINPR_MD_SHA3_512;
845
0
    case NID_shake128:
846
0
      return WINPR_MD_SHAKE128;
847
0
    case NID_shake256:
848
0
      return WINPR_MD_SHAKE256;
849
0
#endif
850
0
    case NID_undef:
851
0
    default:
852
0
      return WINPR_MD_NONE;
853
0
  }
854
0
}
855
856
WINPR_ATTR_NODISCARD
857
static WINPR_MD_TYPE get_rsa_pss_digest(const X509_ALGOR* alg)
858
0
{
859
0
  WINPR_MD_TYPE ret = WINPR_MD_NONE;
860
0
  WINPR_MD_TYPE message_digest = WINPR_MD_NONE;
861
0
  WINPR_MD_TYPE mgf1_digest = WINPR_MD_NONE;
862
0
  int param_type = 0;
863
0
  const void* param_value = nullptr;
864
0
  const ASN1_STRING* sequence = nullptr;
865
0
  const unsigned char* inp = nullptr;
866
0
  RSA_PSS_PARAMS* params = nullptr;
867
0
  X509_ALGOR* mgf1_digest_alg = nullptr;
868
869
  /* The RSA-PSS digest is encoded in a complex structure, defined in
870
  https://www.rfc-editor.org/rfc/rfc4055.html. */
871
0
  X509_ALGOR_get0(nullptr, &param_type, &param_value, alg);
872
873
  /* param_type and param_value the parameter in ASN1_TYPE form, but split into two parameters. A
874
  SEQUENCE is has type V_ASN1_SEQUENCE, and the value is an ASN1_STRING with the encoded
875
  structure. */
876
0
  if (param_type != V_ASN1_SEQUENCE)
877
0
    goto end;
878
0
  sequence = param_value;
879
880
  /* Decode the structure. */
881
0
  inp = ASN1_STRING_get0_data(sequence);
882
0
  params = d2i_RSA_PSS_PARAMS(nullptr, &inp, ASN1_STRING_length(sequence));
883
0
  if (params == nullptr)
884
0
    goto end;
885
886
  /* RSA-PSS uses two hash algorithms, a message digest and also an MGF function which is, itself,
887
  parameterized by a hash function. Both fields default to SHA-1, so we must also check for the
888
  value being nullptr. */
889
0
  message_digest = WINPR_MD_SHA1;
890
0
  if (params->hashAlgorithm != nullptr)
891
0
  {
892
0
    const ASN1_OBJECT* obj = nullptr;
893
0
    X509_ALGOR_get0(&obj, nullptr, nullptr, params->hashAlgorithm);
894
0
    message_digest = hash_nid_to_winpr(OBJ_obj2nid(obj));
895
0
    if (message_digest == WINPR_MD_NONE)
896
0
      goto end;
897
0
  }
898
899
0
  mgf1_digest = WINPR_MD_SHA1;
900
0
  if (params->maskGenAlgorithm != nullptr)
901
0
  {
902
0
    const ASN1_OBJECT* obj = nullptr;
903
0
    int mgf_param_type = 0;
904
0
    const void* mgf_param_value = nullptr;
905
0
    const ASN1_STRING* mgf_param_sequence = nullptr;
906
    /* First, check this is MGF-1, the only one ever defined. */
907
0
    X509_ALGOR_get0(&obj, &mgf_param_type, &mgf_param_value, params->maskGenAlgorithm);
908
0
    if (OBJ_obj2nid(obj) != NID_mgf1)
909
0
      goto end;
910
911
    /* MGF-1 is, itself, parameterized by a hash function, encoded as an AlgorithmIdentifier. */
912
0
    if (mgf_param_type != V_ASN1_SEQUENCE)
913
0
      goto end;
914
0
    mgf_param_sequence = mgf_param_value;
915
0
    inp = ASN1_STRING_get0_data(mgf_param_sequence);
916
0
    mgf1_digest_alg = d2i_X509_ALGOR(nullptr, &inp, ASN1_STRING_length(mgf_param_sequence));
917
0
    if (mgf1_digest_alg == nullptr)
918
0
      goto end;
919
920
    /* Finally, extract the digest. */
921
0
    X509_ALGOR_get0(&obj, nullptr, nullptr, mgf1_digest_alg);
922
0
    mgf1_digest = hash_nid_to_winpr(OBJ_obj2nid(obj));
923
0
    if (mgf1_digest == WINPR_MD_NONE)
924
0
      goto end;
925
0
  }
926
927
  /* If the two digests do not match, it is ambiguous which to return. tls-server-end-point leaves
928
  it undefined, so return none.
929
  https://www.rfc-editor.org/rfc/rfc5929.html#section-4.1 */
930
0
  if (message_digest != mgf1_digest)
931
0
    goto end;
932
0
  ret = message_digest;
933
934
0
end:
935
0
  RSA_PSS_PARAMS_free(params);
936
0
  X509_ALGOR_free(mgf1_digest_alg);
937
0
  return ret;
938
0
}
939
940
WINPR_MD_TYPE x509_utils_get_signature_alg(const X509* xcert)
941
0
{
942
0
  WINPR_ASSERT(xcert);
943
944
0
  const int nid = X509_get_signature_nid(xcert);
945
946
0
  if (nid == NID_rsassaPss)
947
0
  {
948
0
    const X509_ALGOR* alg = nullptr;
949
0
    X509_get0_signature(nullptr, &alg, xcert);
950
0
    return get_rsa_pss_digest(alg);
951
0
  }
952
953
0
  int hash_nid = 0;
954
0
  if (OBJ_find_sigid_algs(nid, &hash_nid, nullptr) != 1)
955
0
    return WINPR_MD_NONE;
956
957
0
  return hash_nid_to_winpr(hash_nid);
958
0
}
959
960
char* x509_utils_get_common_name(const X509* xcert, size_t* plength)
961
0
{
962
0
  const X509_NAME* subject_name = X509_get_subject_name(xcert);
963
0
  if (subject_name == nullptr)
964
0
    return nullptr;
965
966
0
  const int index = X509_NAME_get_index_by_NID(subject_name, NID_commonName, -1);
967
0
  if (index < 0)
968
0
    return nullptr;
969
970
0
  const X509_NAME_ENTRY* entry = X509_NAME_get_entry(subject_name, index);
971
0
  if (entry == nullptr)
972
0
    return nullptr;
973
974
0
  const ASN1_STRING* entry_data = X509_NAME_ENTRY_get_data(entry);
975
0
  if (entry_data == nullptr)
976
0
    return nullptr;
977
978
0
  BYTE* common_name_raw = nullptr;
979
0
  const int length = ASN1_STRING_to_UTF8(&common_name_raw, entry_data);
980
0
  if (length < 0)
981
0
    return nullptr;
982
983
0
  char* common_name = nullptr;
984
0
  if (check_string_is_host_or_ip(xcert, common_name_raw,
985
0
                                 WINPR_ASSERTING_INT_CAST(size_t, length)))
986
0
  {
987
0
    if (plength)
988
0
      *plength = (size_t)length;
989
990
0
    common_name = strndup((char*)common_name_raw, (size_t)length);
991
0
  }
992
0
  OPENSSL_free(common_name_raw);
993
0
  return common_name;
994
0
}
995
996
WINPR_ATTR_NODISCARD
997
static int verify_cb(int ok, X509_STORE_CTX* csc)
998
0
{
999
0
  if (ok != 1)
1000
0
  {
1001
0
    WINPR_ASSERT(csc);
1002
0
    int err = X509_STORE_CTX_get_error(csc);
1003
0
    int derr = X509_STORE_CTX_get_error_depth(csc);
1004
0
    X509* where = X509_STORE_CTX_get_current_cert(csc);
1005
0
    const char* what = X509_verify_cert_error_string(err);
1006
0
    char* name = x509_utils_get_subject(where);
1007
1008
0
    WLog_WARN(TAG, "Certificate verification failure '%s (%d)' at stack position %d", what, err,
1009
0
              derr);
1010
0
    WLog_WARN(TAG, "%s", name);
1011
1012
0
    free(name);
1013
0
  }
1014
0
  return ok;
1015
0
}
1016
1017
BOOL x509_utils_verify(X509* xcert, STACK_OF(X509) * chain, const char* certificate_store_path)
1018
0
{
1019
0
  const int purposes[] = { X509_PURPOSE_SSL_SERVER };
1020
0
  BOOL status = FALSE;
1021
1022
0
  if (!xcert)
1023
0
    return FALSE;
1024
1025
0
  X509_STORE* cert_ctx = X509_STORE_new();
1026
1027
0
  if (cert_ctx == nullptr)
1028
0
    goto end;
1029
1030
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
1031
  OpenSSL_add_all_algorithms();
1032
#else
1033
0
  OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS | OPENSSL_INIT_ADD_ALL_DIGESTS |
1034
0
                          OPENSSL_INIT_LOAD_CONFIG,
1035
0
                      nullptr);
1036
0
#endif
1037
1038
0
  if (X509_STORE_set_default_paths(cert_ctx) != 1)
1039
0
    goto end;
1040
1041
0
  X509_LOOKUP* lookup = X509_STORE_add_lookup(cert_ctx, X509_LOOKUP_hash_dir());
1042
1043
0
  if (lookup == nullptr)
1044
0
    goto end;
1045
1046
0
  X509_LOOKUP_add_dir(lookup, nullptr, X509_FILETYPE_DEFAULT);
1047
1048
0
  if (certificate_store_path != nullptr)
1049
0
  {
1050
0
    X509_LOOKUP_add_dir(lookup, certificate_store_path, X509_FILETYPE_PEM);
1051
0
  }
1052
1053
0
  X509_STORE_set_flags(cert_ctx, 0);
1054
1055
0
  for (size_t i = 0; i < ARRAYSIZE(purposes); i++)
1056
0
  {
1057
0
    int err = -1;
1058
0
    int rc = -1;
1059
0
    int purpose = purposes[i];
1060
0
    X509_STORE_CTX* csc = X509_STORE_CTX_new();
1061
1062
0
    if (csc == nullptr)
1063
0
      goto skip;
1064
0
    if (!X509_STORE_CTX_init(csc, cert_ctx, xcert, chain))
1065
0
      goto skip;
1066
1067
0
    X509_STORE_CTX_set_purpose(csc, purpose);
1068
0
    X509_STORE_CTX_set_verify_cb(csc, verify_cb);
1069
1070
0
    rc = X509_verify_cert(csc);
1071
0
    err = X509_STORE_CTX_get_error(csc);
1072
0
  skip:
1073
0
    X509_STORE_CTX_free(csc);
1074
0
    if (rc == 1)
1075
0
    {
1076
0
      status = TRUE;
1077
0
      break;
1078
0
    }
1079
0
    else if (err != X509_V_ERR_INVALID_PURPOSE)
1080
0
      break;
1081
0
  }
1082
1083
0
  X509_STORE_free(cert_ctx);
1084
0
end:
1085
0
  return status;
1086
0
}
1087
1088
char* x509_utils_bio_read(BIO* bio, size_t* plen)
1089
0
{
1090
0
  char* buffer = nullptr;
1091
0
  WINPR_ASSERT(bio);
1092
1093
0
  if (plen)
1094
0
    *plen = 0;
1095
1096
0
  BIO_flush(bio);
1097
1098
0
  const UINT64 size = BIO_number_written(bio);
1099
0
  if (size > INT_MAX)
1100
0
    return nullptr;
1101
1102
0
  buffer = calloc(1, (size_t)size + 1ull);
1103
1104
0
  if (!buffer)
1105
0
    return nullptr;
1106
1107
0
  ERR_clear_error();
1108
0
  const int rc = BIO_read(bio, buffer, (int)size);
1109
0
  if (rc <= 0)
1110
0
    goto fail;
1111
1112
0
  if (plen)
1113
0
    *plen = size;
1114
0
  return buffer;
1115
1116
0
fail:
1117
0
  free(buffer);
1118
0
  return nullptr;
1119
0
}