Coverage Report

Created: 2026-07-16 07:09

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