Coverage Report

Created: 2026-08-31 06:25

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
0
  sk_ASN1_OBJECT_set_cmp_func(oid_stack, asn1_object_cmp);
748
0
  if (sk_ASN1_OBJECT_find(oid_stack, oid) >= 0)
749
0
    ret = TRUE;
750
751
0
  sk_ASN1_OBJECT_pop_free(oid_stack, ASN1_OBJECT_free);
752
0
  return ret;
753
0
}
754
755
void x509_utils_print_info(const X509* xcert)
756
0
{
757
0
  char* subject = x509_utils_get_subject(xcert);
758
0
  char* issuer = x509_utils_get_issuer(xcert);
759
0
  char* fp = (char*)x509_utils_get_hash(xcert, "sha256", nullptr);
760
761
0
  if (!fp)
762
0
  {
763
0
    WLog_ERR(TAG, "error computing fingerprint");
764
0
    goto out_free_issuer;
765
0
  }
766
767
0
  WLog_INFO(TAG, "Certificate details:");
768
0
  WLog_INFO(TAG, "\tSubject: %s", subject);
769
0
  WLog_INFO(TAG, "\tIssuer: %s", issuer);
770
0
  WLog_INFO(TAG, "\tThumbprint: %s", fp);
771
0
  WLog_INFO(TAG,
772
0
            "The above X.509 certificate could not be verified, possibly because you do not have "
773
0
            "the CA certificate in your certificate store, or the certificate has expired. "
774
0
            "Please look at the OpenSSL documentation on how to add a private CA to the store.");
775
0
  free(fp);
776
0
out_free_issuer:
777
0
  free(issuer);
778
0
  free(subject);
779
0
}
780
781
X509* x509_utils_from_pem(const char* data, size_t len, BOOL fromFile)
782
23
{
783
23
  BIO* bio = nullptr;
784
23
  if (fromFile)
785
23
    bio = BIO_new_file(data, "rb");
786
0
  else
787
0
  {
788
0
    if (len > INT_MAX)
789
0
      return nullptr;
790
791
0
    bio = BIO_new_mem_buf(data, (int)len);
792
0
  }
793
794
23
  if (!bio)
795
0
  {
796
0
    WLog_ERR(TAG, "BIO_new failed for certificate");
797
0
    return nullptr;
798
0
  }
799
800
23
  X509* x509 = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
801
23
  BIO_free_all(bio);
802
23
  if (!x509)
803
23
    WLog_ERR(TAG, "PEM_read_bio_X509 returned nullptr [input length %" PRIuz "]", len);
804
805
23
  return x509;
806
23
}
807
808
WINPR_ATTR_NODISCARD
809
static WINPR_MD_TYPE hash_nid_to_winpr(int hash_nid)
810
0
{
811
0
  switch (hash_nid)
812
0
  {
813
0
    case NID_md2:
814
0
      return WINPR_MD_MD2;
815
0
    case NID_md4:
816
0
      return WINPR_MD_MD4;
817
0
    case NID_md5:
818
0
      return WINPR_MD_MD5;
819
0
    case NID_sha1:
820
0
      return WINPR_MD_SHA1;
821
0
    case NID_sha224:
822
0
      return WINPR_MD_SHA224;
823
0
    case NID_sha256:
824
0
      return WINPR_MD_SHA256;
825
0
    case NID_sha384:
826
0
      return WINPR_MD_SHA384;
827
0
    case NID_sha512:
828
0
      return WINPR_MD_SHA512;
829
0
    case NID_ripemd160:
830
0
      return WINPR_MD_RIPEMD160;
831
0
#if (OPENSSL_VERSION_NUMBER >= 0x1010101fL) && !defined(LIBRESSL_VERSION_NUMBER)
832
0
    case NID_sha3_224:
833
0
      return WINPR_MD_SHA3_224;
834
0
    case NID_sha3_256:
835
0
      return WINPR_MD_SHA3_256;
836
0
    case NID_sha3_384:
837
0
      return WINPR_MD_SHA3_384;
838
0
    case NID_sha3_512:
839
0
      return WINPR_MD_SHA3_512;
840
0
    case NID_shake128:
841
0
      return WINPR_MD_SHAKE128;
842
0
    case NID_shake256:
843
0
      return WINPR_MD_SHAKE256;
844
0
#endif
845
0
    case NID_undef:
846
0
    default:
847
0
      return WINPR_MD_NONE;
848
0
  }
849
0
}
850
851
WINPR_ATTR_NODISCARD
852
static WINPR_MD_TYPE get_rsa_pss_digest(const X509_ALGOR* alg)
853
0
{
854
0
  WINPR_MD_TYPE ret = WINPR_MD_NONE;
855
0
  WINPR_MD_TYPE message_digest = WINPR_MD_NONE;
856
0
  WINPR_MD_TYPE mgf1_digest = WINPR_MD_NONE;
857
0
  int param_type = 0;
858
0
  const void* param_value = nullptr;
859
0
  const ASN1_STRING* sequence = nullptr;
860
0
  const unsigned char* inp = nullptr;
861
0
  RSA_PSS_PARAMS* params = nullptr;
862
0
  X509_ALGOR* mgf1_digest_alg = nullptr;
863
864
  /* The RSA-PSS digest is encoded in a complex structure, defined in
865
  https://www.rfc-editor.org/rfc/rfc4055.html. */
866
0
  X509_ALGOR_get0(nullptr, &param_type, &param_value, alg);
867
868
  /* param_type and param_value the parameter in ASN1_TYPE form, but split into two parameters. A
869
  SEQUENCE is has type V_ASN1_SEQUENCE, and the value is an ASN1_STRING with the encoded
870
  structure. */
871
0
  if (param_type != V_ASN1_SEQUENCE)
872
0
    goto end;
873
0
  sequence = param_value;
874
875
  /* Decode the structure. */
876
0
  inp = ASN1_STRING_get0_data(sequence);
877
0
  params = d2i_RSA_PSS_PARAMS(nullptr, &inp, ASN1_STRING_length(sequence));
878
0
  if (params == nullptr)
879
0
    goto end;
880
881
  /* RSA-PSS uses two hash algorithms, a message digest and also an MGF function which is, itself,
882
  parameterized by a hash function. Both fields default to SHA-1, so we must also check for the
883
  value being nullptr. */
884
0
  message_digest = WINPR_MD_SHA1;
885
0
  if (params->hashAlgorithm != nullptr)
886
0
  {
887
0
    const ASN1_OBJECT* obj = nullptr;
888
0
    X509_ALGOR_get0(&obj, nullptr, nullptr, params->hashAlgorithm);
889
0
    message_digest = hash_nid_to_winpr(OBJ_obj2nid(obj));
890
0
    if (message_digest == WINPR_MD_NONE)
891
0
      goto end;
892
0
  }
893
894
0
  mgf1_digest = WINPR_MD_SHA1;
895
0
  if (params->maskGenAlgorithm != nullptr)
896
0
  {
897
0
    const ASN1_OBJECT* obj = nullptr;
898
0
    int mgf_param_type = 0;
899
0
    const void* mgf_param_value = nullptr;
900
0
    const ASN1_STRING* mgf_param_sequence = nullptr;
901
    /* First, check this is MGF-1, the only one ever defined. */
902
0
    X509_ALGOR_get0(&obj, &mgf_param_type, &mgf_param_value, params->maskGenAlgorithm);
903
0
    if (OBJ_obj2nid(obj) != NID_mgf1)
904
0
      goto end;
905
906
    /* MGF-1 is, itself, parameterized by a hash function, encoded as an AlgorithmIdentifier. */
907
0
    if (mgf_param_type != V_ASN1_SEQUENCE)
908
0
      goto end;
909
0
    mgf_param_sequence = mgf_param_value;
910
0
    inp = ASN1_STRING_get0_data(mgf_param_sequence);
911
0
    mgf1_digest_alg = d2i_X509_ALGOR(nullptr, &inp, ASN1_STRING_length(mgf_param_sequence));
912
0
    if (mgf1_digest_alg == nullptr)
913
0
      goto end;
914
915
    /* Finally, extract the digest. */
916
0
    X509_ALGOR_get0(&obj, nullptr, nullptr, mgf1_digest_alg);
917
0
    mgf1_digest = hash_nid_to_winpr(OBJ_obj2nid(obj));
918
0
    if (mgf1_digest == WINPR_MD_NONE)
919
0
      goto end;
920
0
  }
921
922
  /* If the two digests do not match, it is ambiguous which to return. tls-server-end-point leaves
923
  it undefined, so return none.
924
  https://www.rfc-editor.org/rfc/rfc5929.html#section-4.1 */
925
0
  if (message_digest != mgf1_digest)
926
0
    goto end;
927
0
  ret = message_digest;
928
929
0
end:
930
0
  RSA_PSS_PARAMS_free(params);
931
0
  X509_ALGOR_free(mgf1_digest_alg);
932
0
  return ret;
933
0
}
934
935
WINPR_MD_TYPE x509_utils_get_signature_alg(const X509* xcert)
936
0
{
937
0
  WINPR_ASSERT(xcert);
938
939
0
  const int nid = X509_get_signature_nid(xcert);
940
941
0
  if (nid == NID_rsassaPss)
942
0
  {
943
0
    const X509_ALGOR* alg = nullptr;
944
0
    X509_get0_signature(nullptr, &alg, xcert);
945
0
    return get_rsa_pss_digest(alg);
946
0
  }
947
948
0
  int hash_nid = 0;
949
0
  if (OBJ_find_sigid_algs(nid, &hash_nid, nullptr) != 1)
950
0
    return WINPR_MD_NONE;
951
952
0
  return hash_nid_to_winpr(hash_nid);
953
0
}
954
955
char* x509_utils_get_common_name(const X509* xcert, size_t* plength)
956
0
{
957
0
  const X509_NAME* subject_name = X509_get_subject_name(xcert);
958
0
  if (subject_name == nullptr)
959
0
    return nullptr;
960
961
0
  const int index = X509_NAME_get_index_by_NID(subject_name, NID_commonName, -1);
962
0
  if (index < 0)
963
0
    return nullptr;
964
965
0
  const X509_NAME_ENTRY* entry = X509_NAME_get_entry(subject_name, index);
966
0
  if (entry == nullptr)
967
0
    return nullptr;
968
969
0
  const ASN1_STRING* entry_data = X509_NAME_ENTRY_get_data(entry);
970
0
  if (entry_data == nullptr)
971
0
    return nullptr;
972
973
0
  BYTE* common_name_raw = nullptr;
974
0
  const int length = ASN1_STRING_to_UTF8(&common_name_raw, entry_data);
975
0
  if (length < 0)
976
0
    return nullptr;
977
978
0
  char* common_name = nullptr;
979
0
  if (check_string_is_host_or_ip(xcert, common_name_raw,
980
0
                                 WINPR_ASSERTING_INT_CAST(size_t, length)))
981
0
  {
982
0
    if (plength)
983
0
      *plength = (size_t)length;
984
985
0
    common_name = strndup((char*)common_name_raw, (size_t)length);
986
0
  }
987
0
  OPENSSL_free(common_name_raw);
988
0
  return common_name;
989
0
}
990
991
WINPR_ATTR_NODISCARD
992
static int verify_cb(int ok, X509_STORE_CTX* csc)
993
0
{
994
0
  if (ok != 1)
995
0
  {
996
0
    WINPR_ASSERT(csc);
997
0
    int err = X509_STORE_CTX_get_error(csc);
998
0
    int derr = X509_STORE_CTX_get_error_depth(csc);
999
0
    X509* where = X509_STORE_CTX_get_current_cert(csc);
1000
0
    const char* what = X509_verify_cert_error_string(err);
1001
0
    char* name = x509_utils_get_subject(where);
1002
1003
0
    WLog_WARN(TAG, "Certificate verification failure '%s (%d)' at stack position %d", what, err,
1004
0
              derr);
1005
0
    WLog_WARN(TAG, "%s", name);
1006
1007
0
    free(name);
1008
0
  }
1009
0
  return ok;
1010
0
}
1011
1012
BOOL x509_utils_verify(X509* xcert, STACK_OF(X509) * chain, const char* certificate_store_path)
1013
0
{
1014
0
  const int purposes[] = { X509_PURPOSE_SSL_SERVER };
1015
0
  BOOL status = FALSE;
1016
1017
0
  if (!xcert)
1018
0
    return FALSE;
1019
1020
0
  X509_STORE* cert_ctx = X509_STORE_new();
1021
1022
0
  if (cert_ctx == nullptr)
1023
0
    goto end;
1024
1025
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
1026
  OpenSSL_add_all_algorithms();
1027
#else
1028
0
  OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS | OPENSSL_INIT_ADD_ALL_DIGESTS |
1029
0
                          OPENSSL_INIT_LOAD_CONFIG,
1030
0
                      nullptr);
1031
0
#endif
1032
1033
0
  if (X509_STORE_set_default_paths(cert_ctx) != 1)
1034
0
    goto end;
1035
1036
0
  X509_LOOKUP* lookup = X509_STORE_add_lookup(cert_ctx, X509_LOOKUP_hash_dir());
1037
1038
0
  if (lookup == nullptr)
1039
0
    goto end;
1040
1041
0
  X509_LOOKUP_add_dir(lookup, nullptr, X509_FILETYPE_DEFAULT);
1042
1043
0
  if (certificate_store_path != nullptr)
1044
0
  {
1045
0
    X509_LOOKUP_add_dir(lookup, certificate_store_path, X509_FILETYPE_PEM);
1046
0
  }
1047
1048
0
  X509_STORE_set_flags(cert_ctx, 0);
1049
1050
0
  for (size_t i = 0; i < ARRAYSIZE(purposes); i++)
1051
0
  {
1052
0
    int err = -1;
1053
0
    int rc = -1;
1054
0
    int purpose = purposes[i];
1055
0
    X509_STORE_CTX* csc = X509_STORE_CTX_new();
1056
1057
0
    if (csc == nullptr)
1058
0
      goto skip;
1059
0
    if (!X509_STORE_CTX_init(csc, cert_ctx, xcert, chain))
1060
0
      goto skip;
1061
1062
0
    X509_STORE_CTX_set_purpose(csc, purpose);
1063
0
    X509_STORE_CTX_set_verify_cb(csc, verify_cb);
1064
1065
0
    rc = X509_verify_cert(csc);
1066
0
    err = X509_STORE_CTX_get_error(csc);
1067
0
  skip:
1068
0
    X509_STORE_CTX_free(csc);
1069
0
    if (rc == 1)
1070
0
    {
1071
0
      status = TRUE;
1072
0
      break;
1073
0
    }
1074
0
    else if (err != X509_V_ERR_INVALID_PURPOSE)
1075
0
      break;
1076
0
  }
1077
1078
0
  X509_STORE_free(cert_ctx);
1079
0
end:
1080
0
  return status;
1081
0
}
1082
1083
char* x509_utils_bio_read(BIO* bio, size_t* plen)
1084
0
{
1085
0
  char* buffer = nullptr;
1086
0
  WINPR_ASSERT(bio);
1087
1088
0
  if (plen)
1089
0
    *plen = 0;
1090
1091
0
  BIO_flush(bio);
1092
1093
0
  const UINT64 size = BIO_number_written(bio);
1094
0
  if (size > INT_MAX)
1095
0
    return nullptr;
1096
1097
0
  buffer = calloc(1, (size_t)size + 1ull);
1098
1099
0
  if (!buffer)
1100
0
    return nullptr;
1101
1102
0
  ERR_clear_error();
1103
0
  const int rc = BIO_read(bio, buffer, (int)size);
1104
0
  if (rc <= 0)
1105
0
    goto fail;
1106
1107
0
  if (plen)
1108
0
    *plen = size;
1109
0
  return buffer;
1110
1111
0
fail:
1112
0
  free(buffer);
1113
0
  return nullptr;
1114
0
}