Coverage Report

Created: 2026-10-15 06:27

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_hostnameN(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_hostnameN(str, length);
352
0
}
353
354
WINPR_ATTR_NODISCARD
355
static BOOL check_string_is_dns_name(const X509* x509, const unsigned char* ustr, size_t length)
356
0
{
357
  /* RFC 6125 wildcard DNS names (e.g. *.example.com): validate the part after
358
   * the leading label; tls_match_hostname applies the single-label rule.
359
   * Like OpenSSL, require at least two non-empty labels after the wildcard,
360
   * so *.com, *..com and *.com. are rejected. */
361
0
  if ((length > 2) && (ustr[0] == '*') && (ustr[1] == '.'))
362
0
  {
363
0
    const unsigned char* suffix = &ustr[2];
364
0
    const size_t suffixLength = length - 2;
365
0
    size_t dots = 0;
366
367
0
    if ((suffix[0] == '.') || (suffix[suffixLength - 1] == '.'))
368
0
      return FALSE;
369
370
0
    for (size_t x = 1; x < suffixLength; x++)
371
0
    {
372
0
      if (suffix[x] == '.')
373
0
      {
374
0
        if (suffix[x - 1] == '.')
375
0
          return FALSE;
376
0
        dots++;
377
0
      }
378
0
    }
379
380
0
    if (dots == 0)
381
0
      return FALSE;
382
0
    return check_string_is_host_or_ip(x509, suffix, suffixLength);
383
0
  }
384
0
  return check_string_is_host_or_ip(x509, ustr, length);
385
0
}
386
387
WINPR_ATTR_NODISCARD
388
static BOOL check_string_is_host_or_ip_or_email(WINPR_ATTR_UNUSED const X509* x509,
389
                                                const unsigned char* ustr, size_t length)
390
0
{
391
0
  if (check_string_is_host_or_ip(x509, ustr, length))
392
0
    return TRUE;
393
0
  return check_string_is_email(x509, ustr, length);
394
0
}
395
396
WINPR_ATTR_NODISCARD
397
static int
398
extract_string_generic(const X509* x509, GENERAL_NAME* name, void* data, int index, int count,
399
                       BOOL (*fkt)(const X509* x509, const unsigned char* str, size_t length))
400
0
{
401
0
  string_list* list = data;
402
0
  WINPR_ASSERT(list);
403
0
  WINPR_ASSERT(fkt);
404
405
0
  WINPR_ASSERT(name);
406
0
  WINPR_UNUSED(index);
407
408
0
  const ASN1_STRING* str = nullptr;
409
0
  switch (name->type)
410
0
  {
411
0
    case GEN_URI:
412
0
      str = name->d.uniformResourceIdentifier;
413
0
      break;
414
415
0
    case GEN_DNS:
416
0
      str = name->d.dNSName;
417
0
      break;
418
419
0
    case GEN_EMAIL:
420
0
      str = name->d.rfc822Name;
421
0
      break;
422
423
0
    default:
424
0
      return 1;
425
0
  }
426
427
0
  unsigned char* cstring = nullptr;
428
0
  const int rc = ASN1_STRING_to_UTF8(&cstring, str);
429
0
  if (rc < 0)
430
0
  {
431
0
    WLog_ERR(TAG, "ASN1_STRING_to_UTF8() failed for %s: %s",
432
0
             general_name_type_label(name->type), ERR_error_string(ERR_get_error(), nullptr));
433
0
    return 1;
434
0
  }
435
436
0
  if (!fkt(x509, cstring, WINPR_ASSERTING_INT_CAST(size_t, rc)))
437
0
  {
438
0
    WLog_ERR(TAG, "ASN1_STRING_to_UTF8() does not conform to expected format %s: %s",
439
0
             general_name_type_label(name->type), (const char*)str);
440
0
    OPENSSL_free(cstring);
441
0
    return -1;
442
0
  }
443
444
0
  if (!string_list_allocate(list, WINPR_ASSERTING_INT_CAST(WINPR_CIPHER_TYPE, count)) ||
445
0
      (list->allocated <= 0))
446
0
  {
447
0
    WLog_ERR(TAG, "ASN1_STRING_to_UTF8() allocation failed: %s",
448
0
             general_name_type_label(name->type));
449
0
    OPENSSL_free(cstring);
450
0
    return 0;
451
0
  }
452
453
0
  list->strings[list->count] = (char*)cstring;
454
0
  list->lengths[list->count] = WINPR_ASSERTING_INT_CAST(size_t, rc);
455
0
  list->count++;
456
457
0
  if (list->count >= list->maximum)
458
0
  {
459
0
    WLog_ERR(TAG, "ASN1_STRING_to_UTF8() limit exceeded: %s",
460
0
             general_name_type_label(name->type));
461
0
    return 0;
462
0
  }
463
464
0
  return 1;
465
0
}
466
467
WINPR_ATTR_NODISCARD
468
static int extract_string(const X509* x509, GENERAL_NAME* name, void* data, int index, int count)
469
0
{
470
0
  return extract_string_generic(x509, name, data, index, count, check_string_is_dns_name);
471
0
}
472
473
static int extract_email(const X509* x509, GENERAL_NAME* name, void* data, int index, int count)
474
0
{
475
0
  return extract_string_generic(x509, name, data, index, count, check_string_is_email);
476
0
}
477
478
/*
479
extract_othername_object --  object extractor.
480
481
- the objects array is allocated lazily, when we first have to store a
482
  string.
483
484
- allocated contains the size of the objects array, or -1 if
485
  allocation failed.
486
487
- count contains the actual count of objects in the objects array.
488
489
- maximum limits the number of objects we can store in the objects
490
  array: beyond, the extractor returns 0 to short-cut the search.
491
492
extract_othername_objects stores in the objects array ASN1_TYPE *
493
pointers directly obtained from the GENERAL_NAME.
494
*/
495
496
typedef struct object_list
497
{
498
  ASN1_OBJECT* type_id;
499
  char** strings;
500
  size_t* lengths;
501
502
  size_t allocated;
503
  size_t count;
504
  size_t maximum;
505
} object_list;
506
507
static object_list object_list_initialize(void)
508
0
{
509
0
  const object_list empty = { .type_id = nullptr,
510
0
                            .strings = nullptr,
511
0
                            .lengths = nullptr,
512
0
                            .allocated = 0,
513
0
                            .count = 0,
514
0
                            .maximum = INT_MAX };
515
0
  return empty;
516
0
}
517
518
WINPR_ATTR_NODISCARD
519
static BOOL object_list_allocate(object_list* list, size_t allocate_count)
520
0
{
521
0
  if (!list->strings && (list->allocated == 0) && (allocate_count > 0))
522
0
  {
523
0
    list->strings = (char**)calloc(allocate_count, sizeof(list->strings[0]));
524
0
    list->lengths = calloc(allocate_count, sizeof(size_t));
525
0
    list->count = 0;
526
0
    if (!list->strings || !list->lengths)
527
0
    {
528
0
      free((void*)list->strings);
529
0
      free(list->lengths);
530
0
      list->strings = nullptr;
531
0
      list->lengths = nullptr;
532
0
      return FALSE;
533
0
    }
534
0
    list->allocated = allocate_count;
535
0
  }
536
0
  return TRUE;
537
0
}
538
539
WINPR_ATTR_MALLOC(free, 1)
540
static char* object_string(const X509* x509, ASN1_TYPE* object, size_t* pLength)
541
0
{
542
0
  unsigned char* utf8String = nullptr;
543
544
0
  WINPR_ASSERT(object);
545
0
  WINPR_ASSERT(pLength);
546
547
0
  *pLength = 0;
548
549
  /* TODO: check that object.type is a string type. */
550
0
  const int length = ASN1_STRING_to_UTF8(&utf8String, object->value.asn1_string);
551
552
0
  if (length < 0)
553
0
    return nullptr;
554
555
0
  char* result = nullptr;
556
0
  if (check_string_is_host_or_ip_or_email(x509, utf8String,
557
0
                                          WINPR_ASSERTING_INT_CAST(size_t, length)))
558
0
  {
559
0
    result = strndup((char*)utf8String, WINPR_ASSERTING_INT_CAST(size_t, length));
560
0
    if (result)
561
0
      *pLength = WINPR_ASSERTING_INT_CAST(size_t, length);
562
0
  }
563
0
  else
564
0
    WLog_ERR(TAG, "Found invalid object_string entry in certificate: '%s'", utf8String);
565
0
  OPENSSL_free(utf8String);
566
0
  return result;
567
0
}
568
569
static void object_list_free(object_list* list)
570
0
{
571
0
  WINPR_ASSERT(list);
572
0
  free((void*)list->strings);
573
0
  free(list->lengths);
574
0
}
575
576
WINPR_ATTR_NODISCARD
577
static int extract_othername_object_as_string(const X509* x509, GENERAL_NAME* name, void* data,
578
                                              int index, int count)
579
0
{
580
0
  object_list* list = data;
581
0
  WINPR_UNUSED(index);
582
0
  WINPR_ASSERT(x509);
583
584
0
  if (count < 0)
585
0
    return -1;
586
587
0
  if (name->type != GEN_OTHERNAME)
588
0
  {
589
0
    return 1;
590
0
  }
591
592
0
  if (0 != OBJ_cmp(name->d.otherName->type_id, list->type_id))
593
0
  {
594
0
    return 1;
595
0
  }
596
597
0
  if (!object_list_allocate(list, WINPR_ASSERTING_INT_CAST(size_t, count)) ||
598
0
      (list->allocated <= 0))
599
0
  {
600
0
    return 0;
601
0
  }
602
603
0
  list->strings[list->count] =
604
0
      object_string(x509, name->d.otherName->value, &list->lengths[list->count]);
605
0
  if (list->strings[list->count])
606
0
  {
607
0
    list->count++;
608
0
  }
609
610
0
  if (list->count >= list->maximum)
611
0
  {
612
0
    return 0;
613
0
  }
614
615
0
  return 1;
616
0
}
617
618
char* x509_utils_get_email(const X509* x509)
619
0
{
620
0
  string_list list = string_list_initialize();
621
0
  list.maximum = 1;
622
0
  map_subject_alt_name(x509, GEN_EMAIL, extract_email, &list);
623
624
0
  if (list.count == 0)
625
0
  {
626
0
    string_list_free(&list);
627
0
    return nullptr;
628
0
  }
629
630
0
  char* result = strndup(list.strings[0], list.lengths[0]);
631
0
  OPENSSL_free(list.strings[0]);
632
0
  string_list_free(&list);
633
0
  return result;
634
0
}
635
636
char* x509_utils_get_upn(const X509* x509)
637
0
{
638
0
  object_list list = object_list_initialize();
639
640
0
  list.type_id = OBJ_nid2obj(NID_ms_upn);
641
0
  list.maximum = 1;
642
0
  map_subject_alt_name(x509, GEN_OTHERNAME, extract_othername_object_as_string, &list);
643
644
0
  if (list.count == 0)
645
0
  {
646
0
    object_list_free(&list);
647
0
    return nullptr;
648
0
  }
649
650
0
  char* result = list.strings[0];
651
0
  object_list_free(&list);
652
0
  return result;
653
0
}
654
655
char* x509_utils_get_date(const X509* x509, BOOL startDate)
656
0
{
657
0
  WINPR_ASSERT(x509);
658
659
0
  const ASN1_TIME* date = startDate ? X509_get0_notBefore(x509) : X509_get0_notAfter(x509);
660
0
  if (!date)
661
0
    return nullptr;
662
663
0
  BIO* bmem = BIO_new(BIO_s_mem());
664
0
  if (!bmem)
665
0
    return nullptr;
666
667
0
  char* str = nullptr;
668
0
  if (ASN1_TIME_print(bmem, date))
669
0
  {
670
0
    BUF_MEM* bptr = nullptr;
671
672
0
    BIO_get_mem_ptr(bmem, &bptr);
673
0
    str = strndup(bptr->data, bptr->length);
674
0
  }
675
0
  else
676
0
  { // Log error
677
0
  }
678
0
  BIO_free_all(bmem);
679
0
  return str;
680
0
}
681
682
void x509_utils_dns_names_free(size_t count, size_t* lengths, char** dns_names)
683
0
{
684
0
  free(lengths);
685
686
0
  if (dns_names)
687
0
  {
688
0
    for (size_t i = 0; i < count; i++)
689
0
    {
690
0
      if (dns_names[i])
691
0
      {
692
0
        OPENSSL_free(dns_names[i]);
693
0
      }
694
0
    }
695
696
0
    free((void*)dns_names);
697
0
  }
698
0
}
699
700
WINPR_ATTR_NODISCARD
701
static char** x509_utils_get_names(const X509* xcert, int general_name_type, size_t* count,
702
                                   size_t** lengths)
703
0
{
704
0
  string_list list = string_list_initialize();
705
0
  map_subject_alt_name(xcert, general_name_type, extract_string, &list);
706
0
  (*count) = list.count;
707
708
0
  if (list.count <= 0)
709
0
  {
710
0
    string_list_free(&list);
711
0
    return nullptr;
712
0
  }
713
714
  /* lengths are not useful,  since we converted the
715
     strings to utf-8,  there cannot be nul-bytes in them. */
716
0
  char** result = (char**)calloc(list.count, sizeof(*result));
717
0
  (*lengths) = calloc(list.count, sizeof(**lengths));
718
719
0
  if (!result || !(*lengths))
720
0
  {
721
0
    string_list_free(&list);
722
0
    free((void*)result);
723
0
    free(*lengths);
724
0
    (*lengths) = nullptr;
725
0
    (*count) = 0;
726
0
    return nullptr;
727
0
  }
728
729
0
  for (size_t i = 0; i < list.count; i++)
730
0
  {
731
0
    result[i] = list.strings[i];
732
0
    (*lengths)[i] = list.lengths[i];
733
0
  }
734
735
0
  string_list_free(&list);
736
0
  return result;
737
0
}
738
739
char** x509_utils_get_dns_names(const X509* xcert, size_t* count, size_t** pplengths)
740
0
{
741
0
  return x509_utils_get_names(xcert, GEN_DNS, count, pplengths);
742
0
}
743
744
char** x509_utils_get_ip_names(const X509* xcert, size_t* count, size_t** pplengths)
745
0
{
746
0
  return x509_utils_get_names(xcert, GEN_IPADD, count, pplengths);
747
0
}
748
749
char* x509_utils_get_issuer(const X509* xcert)
750
0
{
751
0
  char* issuer = nullptr;
752
0
  if (!xcert)
753
0
  {
754
0
    WLog_ERR(TAG, "Invalid certificate nullptr");
755
0
    return nullptr;
756
0
  }
757
0
  issuer = crypto_print_name(X509_get_issuer_name(xcert));
758
0
  if (!issuer)
759
0
    WLog_WARN(TAG, "certificate does not have an issuer!");
760
0
  return issuer;
761
0
}
762
763
WINPR_ATTR_NODISCARD
764
static int asn1_object_cmp(const ASN1_OBJECT* const* a, const ASN1_OBJECT* const* b)
765
0
{
766
0
  if (!a || !b)
767
0
    return (a == b) ? 0 : (a ? 1 : -1);
768
769
0
  if (!*a || !*b)
770
0
    return (*a == *b) ? 0 : (*a ? 1 : -1);
771
772
0
  return OBJ_cmp(*a, *b);
773
0
}
774
775
BOOL x509_utils_check_eku(const X509* xcert, int nid)
776
0
{
777
0
  BOOL ret = FALSE;
778
0
  STACK_OF(ASN1_OBJECT)* oid_stack = nullptr;
779
0
  ASN1_OBJECT* oid = nullptr;
780
781
0
  if (!xcert)
782
0
    return FALSE;
783
784
0
  oid = OBJ_nid2obj(nid);
785
0
  if (!oid)
786
0
    return FALSE;
787
788
0
  oid_stack = X509_get_ext_d2i(xcert, NID_ext_key_usage, nullptr, nullptr);
789
0
  if (!oid_stack)
790
0
    return FALSE;
791
792
  /* sk_ASN1_OBJECT_set_cmp_func does some function pointer casting that produces a warning. We
793
   * can not fix upstream, so silence it here. */
794
0
  WINPR_PRAGMA_DIAG_PUSH
795
0
  WINPR_PRAGMA_DIAG_IGNORED_CAST_FUNCTION_TYPE
796
0
  sk_ASN1_OBJECT_set_cmp_func(oid_stack, asn1_object_cmp);
797
0
  WINPR_PRAGMA_DIAG_POP
798
0
  if (sk_ASN1_OBJECT_find(oid_stack, oid) >= 0)
799
0
    ret = TRUE;
800
801
0
  sk_ASN1_OBJECT_pop_free(oid_stack, ASN1_OBJECT_free);
802
0
  return ret;
803
0
}
804
805
void x509_utils_print_info(const X509* xcert)
806
0
{
807
0
  char* subject = x509_utils_get_subject(xcert);
808
0
  char* issuer = x509_utils_get_issuer(xcert);
809
0
  char* fp = (char*)x509_utils_get_hash(xcert, "sha256", nullptr);
810
811
0
  if (!fp)
812
0
  {
813
0
    WLog_ERR(TAG, "error computing fingerprint");
814
0
    goto out_free_issuer;
815
0
  }
816
817
0
  WLog_INFO(TAG, "Certificate details:");
818
0
  WLog_INFO(TAG, "\tSubject: %s", subject);
819
0
  WLog_INFO(TAG, "\tIssuer: %s", issuer);
820
0
  WLog_INFO(TAG, "\tThumbprint: %s", fp);
821
0
  WLog_INFO(TAG,
822
0
            "The above X.509 certificate could not be verified, possibly because you do not have "
823
0
            "the CA certificate in your certificate store, or the certificate has expired. "
824
0
            "Please look at the OpenSSL documentation on how to add a private CA to the store.");
825
0
  free(fp);
826
0
out_free_issuer:
827
0
  free(issuer);
828
0
  free(subject);
829
0
}
830
831
X509* x509_utils_from_pem(const char* data, size_t len, BOOL fromFile)
832
0
{
833
0
  BIO* bio = nullptr;
834
0
  if (fromFile)
835
0
    bio = BIO_new_file(data, "rb");
836
0
  else
837
0
  {
838
0
    if (len > INT_MAX)
839
0
      return nullptr;
840
841
0
    bio = BIO_new_mem_buf(data, (int)len);
842
0
  }
843
844
0
  if (!bio)
845
0
  {
846
0
    WLog_ERR(TAG, "BIO_new failed for certificate");
847
0
    return nullptr;
848
0
  }
849
850
0
  X509* x509 = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
851
0
  BIO_free_all(bio);
852
0
  if (!x509)
853
0
    WLog_ERR(TAG, "PEM_read_bio_X509 returned nullptr [input length %" PRIuz "]", len);
854
855
0
  return x509;
856
0
}
857
858
WINPR_ATTR_NODISCARD
859
static WINPR_MD_TYPE hash_nid_to_winpr(int hash_nid)
860
0
{
861
0
  switch (hash_nid)
862
0
  {
863
0
    case NID_md2:
864
0
      return WINPR_MD_MD2;
865
0
    case NID_md4:
866
0
      return WINPR_MD_MD4;
867
0
    case NID_md5:
868
0
      return WINPR_MD_MD5;
869
0
    case NID_sha1:
870
0
      return WINPR_MD_SHA1;
871
0
    case NID_sha224:
872
0
      return WINPR_MD_SHA224;
873
0
    case NID_sha256:
874
0
      return WINPR_MD_SHA256;
875
0
    case NID_sha384:
876
0
      return WINPR_MD_SHA384;
877
0
    case NID_sha512:
878
0
      return WINPR_MD_SHA512;
879
0
    case NID_ripemd160:
880
0
      return WINPR_MD_RIPEMD160;
881
0
#if (OPENSSL_VERSION_NUMBER >= 0x1010101fL) && !defined(LIBRESSL_VERSION_NUMBER)
882
0
    case NID_sha3_224:
883
0
      return WINPR_MD_SHA3_224;
884
0
    case NID_sha3_256:
885
0
      return WINPR_MD_SHA3_256;
886
0
    case NID_sha3_384:
887
0
      return WINPR_MD_SHA3_384;
888
0
    case NID_sha3_512:
889
0
      return WINPR_MD_SHA3_512;
890
0
    case NID_shake128:
891
0
      return WINPR_MD_SHAKE128;
892
0
    case NID_shake256:
893
0
      return WINPR_MD_SHAKE256;
894
0
#endif
895
0
    case NID_undef:
896
0
    default:
897
0
      return WINPR_MD_NONE;
898
0
  }
899
0
}
900
901
WINPR_ATTR_NODISCARD
902
static WINPR_MD_TYPE get_rsa_pss_digest(const X509_ALGOR* alg)
903
0
{
904
0
  WINPR_MD_TYPE ret = WINPR_MD_NONE;
905
0
  WINPR_MD_TYPE message_digest = WINPR_MD_NONE;
906
0
  WINPR_MD_TYPE mgf1_digest = WINPR_MD_NONE;
907
0
  int param_type = 0;
908
0
  const void* param_value = nullptr;
909
0
  const ASN1_STRING* sequence = nullptr;
910
0
  const unsigned char* inp = nullptr;
911
0
  RSA_PSS_PARAMS* params = nullptr;
912
0
  X509_ALGOR* mgf1_digest_alg = nullptr;
913
914
  /* The RSA-PSS digest is encoded in a complex structure, defined in
915
  https://www.rfc-editor.org/rfc/rfc4055.html. */
916
0
  X509_ALGOR_get0(nullptr, &param_type, &param_value, alg);
917
918
  /* param_type and param_value the parameter in ASN1_TYPE form, but split into two parameters. A
919
  SEQUENCE is has type V_ASN1_SEQUENCE, and the value is an ASN1_STRING with the encoded
920
  structure. */
921
0
  if (param_type != V_ASN1_SEQUENCE)
922
0
    goto end;
923
0
  sequence = param_value;
924
925
  /* Decode the structure. */
926
0
  inp = ASN1_STRING_get0_data(sequence);
927
0
  params = d2i_RSA_PSS_PARAMS(nullptr, &inp, ASN1_STRING_length(sequence));
928
0
  if (params == nullptr)
929
0
    goto end;
930
931
  /* RSA-PSS uses two hash algorithms, a message digest and also an MGF function which is, itself,
932
  parameterized by a hash function. Both fields default to SHA-1, so we must also check for the
933
  value being nullptr. */
934
0
  message_digest = WINPR_MD_SHA1;
935
0
  if (params->hashAlgorithm != nullptr)
936
0
  {
937
0
    const ASN1_OBJECT* obj = nullptr;
938
0
    X509_ALGOR_get0(&obj, nullptr, nullptr, params->hashAlgorithm);
939
0
    message_digest = hash_nid_to_winpr(OBJ_obj2nid(obj));
940
0
    if (message_digest == WINPR_MD_NONE)
941
0
      goto end;
942
0
  }
943
944
0
  mgf1_digest = WINPR_MD_SHA1;
945
0
  if (params->maskGenAlgorithm != nullptr)
946
0
  {
947
0
    const ASN1_OBJECT* obj = nullptr;
948
0
    int mgf_param_type = 0;
949
0
    const void* mgf_param_value = nullptr;
950
0
    const ASN1_STRING* mgf_param_sequence = nullptr;
951
    /* First, check this is MGF-1, the only one ever defined. */
952
0
    X509_ALGOR_get0(&obj, &mgf_param_type, &mgf_param_value, params->maskGenAlgorithm);
953
0
    if (OBJ_obj2nid(obj) != NID_mgf1)
954
0
      goto end;
955
956
    /* MGF-1 is, itself, parameterized by a hash function, encoded as an AlgorithmIdentifier. */
957
0
    if (mgf_param_type != V_ASN1_SEQUENCE)
958
0
      goto end;
959
0
    mgf_param_sequence = mgf_param_value;
960
0
    inp = ASN1_STRING_get0_data(mgf_param_sequence);
961
0
    mgf1_digest_alg = d2i_X509_ALGOR(nullptr, &inp, ASN1_STRING_length(mgf_param_sequence));
962
0
    if (mgf1_digest_alg == nullptr)
963
0
      goto end;
964
965
    /* Finally, extract the digest. */
966
0
    X509_ALGOR_get0(&obj, nullptr, nullptr, mgf1_digest_alg);
967
0
    mgf1_digest = hash_nid_to_winpr(OBJ_obj2nid(obj));
968
0
    if (mgf1_digest == WINPR_MD_NONE)
969
0
      goto end;
970
0
  }
971
972
  /* If the two digests do not match, it is ambiguous which to return. tls-server-end-point leaves
973
  it undefined, so return none.
974
  https://www.rfc-editor.org/rfc/rfc5929.html#section-4.1 */
975
0
  if (message_digest != mgf1_digest)
976
0
    goto end;
977
0
  ret = message_digest;
978
979
0
end:
980
0
  RSA_PSS_PARAMS_free(params);
981
0
  X509_ALGOR_free(mgf1_digest_alg);
982
0
  return ret;
983
0
}
984
985
WINPR_MD_TYPE x509_utils_get_signature_alg(const X509* xcert)
986
0
{
987
0
  WINPR_ASSERT(xcert);
988
989
0
  const int nid = X509_get_signature_nid(xcert);
990
991
0
  if (nid == NID_rsassaPss)
992
0
  {
993
0
    const X509_ALGOR* alg = nullptr;
994
0
    X509_get0_signature(nullptr, &alg, xcert);
995
0
    return get_rsa_pss_digest(alg);
996
0
  }
997
998
0
  int hash_nid = 0;
999
0
  if (OBJ_find_sigid_algs(nid, &hash_nid, nullptr) != 1)
1000
0
    return WINPR_MD_NONE;
1001
1002
0
  return hash_nid_to_winpr(hash_nid);
1003
0
}
1004
1005
char* x509_utils_get_common_name(const X509* xcert, size_t* plength)
1006
0
{
1007
0
  const X509_NAME* subject_name = X509_get_subject_name(xcert);
1008
0
  if (subject_name == nullptr)
1009
0
    return nullptr;
1010
1011
0
  const int index = X509_NAME_get_index_by_NID(subject_name, NID_commonName, -1);
1012
0
  if (index < 0)
1013
0
    return nullptr;
1014
1015
0
  const X509_NAME_ENTRY* entry = X509_NAME_get_entry(subject_name, index);
1016
0
  if (entry == nullptr)
1017
0
    return nullptr;
1018
1019
0
  const ASN1_STRING* entry_data = X509_NAME_ENTRY_get_data(entry);
1020
0
  if (entry_data == nullptr)
1021
0
    return nullptr;
1022
1023
0
  BYTE* common_name_raw = nullptr;
1024
0
  const int length = ASN1_STRING_to_UTF8(&common_name_raw, entry_data);
1025
0
  if (length < 0)
1026
0
    return nullptr;
1027
1028
0
  char* common_name = nullptr;
1029
0
  if (check_string_is_dns_name(xcert, common_name_raw, WINPR_ASSERTING_INT_CAST(size_t, length)))
1030
0
  {
1031
0
    if (plength)
1032
0
      *plength = (size_t)length;
1033
1034
0
    common_name = strndup((char*)common_name_raw, (size_t)length);
1035
0
  }
1036
0
  OPENSSL_free(common_name_raw);
1037
0
  return common_name;
1038
0
}
1039
1040
WINPR_ATTR_NODISCARD
1041
static int verify_cb(int ok, X509_STORE_CTX* csc)
1042
0
{
1043
0
  if (ok != 1)
1044
0
  {
1045
0
    WINPR_ASSERT(csc);
1046
0
    int err = X509_STORE_CTX_get_error(csc);
1047
0
    int derr = X509_STORE_CTX_get_error_depth(csc);
1048
0
    X509* where = X509_STORE_CTX_get_current_cert(csc);
1049
0
    const char* what = X509_verify_cert_error_string(err);
1050
0
    char* name = x509_utils_get_subject(where);
1051
1052
0
    WLog_WARN(TAG, "Certificate verification failure '%s (%d)' at stack position %d", what, err,
1053
0
              derr);
1054
0
    WLog_WARN(TAG, "%s", name);
1055
1056
0
    free(name);
1057
0
  }
1058
0
  return ok;
1059
0
}
1060
1061
BOOL x509_utils_verify(X509* xcert, STACK_OF(X509) * chain, const char* certificate_store_path)
1062
0
{
1063
0
  const int purposes[] = { X509_PURPOSE_SSL_SERVER };
1064
0
  BOOL status = FALSE;
1065
1066
0
  if (!xcert)
1067
0
    return FALSE;
1068
1069
0
  X509_STORE* cert_ctx = X509_STORE_new();
1070
1071
0
  if (cert_ctx == nullptr)
1072
0
    goto end;
1073
1074
#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
1075
  OpenSSL_add_all_algorithms();
1076
#else
1077
0
  OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS | OPENSSL_INIT_ADD_ALL_DIGESTS |
1078
0
                          OPENSSL_INIT_LOAD_CONFIG,
1079
0
                      nullptr);
1080
0
#endif
1081
1082
0
  if (X509_STORE_set_default_paths(cert_ctx) != 1)
1083
0
    goto end;
1084
1085
0
  X509_LOOKUP* lookup = X509_STORE_add_lookup(cert_ctx, X509_LOOKUP_hash_dir());
1086
1087
0
  if (lookup == nullptr)
1088
0
    goto end;
1089
1090
0
  X509_LOOKUP_add_dir(lookup, nullptr, X509_FILETYPE_DEFAULT);
1091
1092
0
  if (certificate_store_path != nullptr)
1093
0
  {
1094
0
    X509_LOOKUP_add_dir(lookup, certificate_store_path, X509_FILETYPE_PEM);
1095
0
  }
1096
1097
0
  X509_STORE_set_flags(cert_ctx, 0);
1098
1099
0
  for (size_t i = 0; i < ARRAYSIZE(purposes); i++)
1100
0
  {
1101
0
    int err = -1;
1102
0
    int rc = -1;
1103
0
    int purpose = purposes[i];
1104
0
    X509_STORE_CTX* csc = X509_STORE_CTX_new();
1105
1106
0
    if (csc == nullptr)
1107
0
      goto skip;
1108
0
    if (!X509_STORE_CTX_init(csc, cert_ctx, xcert, chain))
1109
0
      goto skip;
1110
1111
0
    X509_STORE_CTX_set_purpose(csc, purpose);
1112
0
    X509_STORE_CTX_set_verify_cb(csc, verify_cb);
1113
1114
0
    rc = X509_verify_cert(csc);
1115
0
    err = X509_STORE_CTX_get_error(csc);
1116
0
  skip:
1117
0
    X509_STORE_CTX_free(csc);
1118
0
    if (rc == 1)
1119
0
    {
1120
0
      status = TRUE;
1121
0
      break;
1122
0
    }
1123
0
    else if (err != X509_V_ERR_INVALID_PURPOSE)
1124
0
      break;
1125
0
  }
1126
1127
0
  X509_STORE_free(cert_ctx);
1128
0
end:
1129
0
  return status;
1130
0
}
1131
1132
char* x509_utils_bio_read(BIO* bio, size_t* plen)
1133
0
{
1134
0
  char* buffer = nullptr;
1135
0
  WINPR_ASSERT(bio);
1136
1137
0
  if (plen)
1138
0
    *plen = 0;
1139
1140
0
  BIO_flush(bio);
1141
1142
0
  const UINT64 size = BIO_number_written(bio);
1143
0
  if (size > INT_MAX)
1144
0
    return nullptr;
1145
1146
0
  buffer = calloc(1, (size_t)size + 1ull);
1147
1148
0
  if (!buffer)
1149
0
    return nullptr;
1150
1151
0
  ERR_clear_error();
1152
0
  const int rc = BIO_read(bio, buffer, (int)size);
1153
0
  if (rc <= 0)
1154
0
    goto fail;
1155
1156
0
  if (plen)
1157
0
    *plen = size;
1158
0
  return buffer;
1159
1160
0
fail:
1161
0
  free(buffer);
1162
0
  return nullptr;
1163
0
}
1164
1165
void x509_utils_ip_names_free(size_t count, size_t* lengths, char** dns_names)
1166
0
{
1167
0
  x509_utils_dns_names_free(count, lengths, dns_names);
1168
0
}