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