FreeRDP
Loading...
Searching...
No Matches
makecert.c
1
20#include <errno.h>
21
22#include <winpr/assert.h>
23#include <winpr/crt.h>
24#include <winpr/path.h>
25#include <winpr/file.h>
26#include <winpr/cmdline.h>
27#include <winpr/sysinfo.h>
28#include <winpr/crypto.h>
29#include <winpr/print.h>
30
31#ifdef WITH_OPENSSL
32#include <openssl/crypto.h>
33#include <openssl/conf.h>
34#include <openssl/pem.h>
35#include <openssl/err.h>
36#include <openssl/rsa.h>
37#include <openssl/pkcs12.h>
38#include <openssl/x509v3.h>
39#include <openssl/bn.h>
40#endif
41
42#include <winpr/tools/makecert.h>
43
44#if !defined(_WIN32)
45#include <sys/stat.h>
46#endif
47
48struct S_MAKECERT_CONTEXT
49{
50 int argc;
51 char** argv;
52
53#ifdef WITH_OPENSSL
54 X509* x509;
55 EVP_PKEY* pkey;
56 PKCS12* pkcs12;
57#endif
58
59 BOOL live;
60 BOOL silent;
61
62 BOOL crtFormat;
63 BOOL pemFormat;
64 BOOL pfxFormat;
65
66 char* password;
67
68 char* output_file;
69 char* output_path;
70 char* default_name;
71 char* common_name;
72
73 int duration_years;
74 int duration_months;
75};
76
77WINPR_ATTR_NODISCARD
78static BOOL utils_set_umask(void)
79{
80#if !defined(_WIN32)
81 (void)umask(S_IRWXG | S_IRWXO);
82#endif
83 return TRUE;
84}
85
86WINPR_ATTR_MALLOC(winpr_zfree, 1)
87static char* makecert_read_str(BIO* bio, size_t* pOffset)
88{
89 int status = -1;
90 size_t offset = 0;
91 size_t length = 0;
92 char* x509_str = nullptr;
93
94 while (offset >= length)
95 {
96 size_t readBytes = 0;
97 char* new_str = nullptr;
98 size_t new_len = length + 2048ull;
99
100 if (new_len > INT_MAX)
101 {
102 status = -1;
103 break;
104 }
105
106 new_str = (char*)realloc(x509_str, new_len);
107
108 if (!new_str)
109 {
110 status = -1;
111 break;
112 }
113
114 length = new_len;
115 x509_str = new_str;
116 ERR_clear_error();
117#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER)
118 status = BIO_read_ex(bio, &x509_str[offset], length - offset, &readBytes);
119#else
120 status = BIO_read(bio, &x509_str[offset], length - offset);
121 readBytes = status;
122#endif
123 if (status <= 0)
124 break;
125
126 offset += readBytes;
127 }
128
129 if (status < 0)
130 {
131 free(x509_str);
132 if (pOffset)
133 *pOffset = 0;
134 return nullptr;
135 }
136
137 x509_str[offset] = '\0';
138 if (pOffset)
139 *pOffset = offset + 1;
140 return x509_str;
141}
142
143static int makecert_print_command_line_help(COMMAND_LINE_ARGUMENT_A* args, int argc, char** argv)
144{
145 char* str = nullptr;
146 const COMMAND_LINE_ARGUMENT_A* arg = nullptr;
147
148 if (!argv || (argc < 1))
149 return -1;
150
151 printf("Usage: %s [options] [output file]\n", argv[0]);
152 printf("\n");
153 arg = args;
154
155 do
156 {
157 if (arg->Flags & COMMAND_LINE_VALUE_FLAG)
158 {
159 printf(" %s", "-");
160 printf("%-20s", arg->Name);
161 printf("\t%s\n", arg->Text);
162 }
163 else if ((arg->Flags & COMMAND_LINE_VALUE_REQUIRED) ||
164 (arg->Flags & COMMAND_LINE_VALUE_OPTIONAL))
165 {
166 printf(" %s", "-");
167
168 if (arg->Format)
169 {
170 size_t length = strlen(arg->Name) + strlen(arg->Format) + 2;
171 str = malloc(length + 1);
172
173 if (!str)
174 return -1;
175
176 (void)sprintf_s(str, length + 1, "%s %s", arg->Name, arg->Format);
177 (void)printf("%-20s", str);
178 free(str);
179 }
180 else
181 {
182 printf("%-20s", arg->Name);
183 }
184
185 printf("\t%s\n", arg->Text);
186 }
187 } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr);
188
189 return 1;
190}
191
192#ifdef WITH_OPENSSL
193WINPR_ATTR_NODISCARD
194static int x509_add_ext(X509* cert, int nid, char* value)
195{
196 X509V3_CTX ctx;
197 X509_EXTENSION* ext = nullptr;
198
199 if (!cert || !value)
200 return 0;
201
202 X509V3_set_ctx_nodb(&ctx) X509V3_set_ctx(&ctx, cert, cert, nullptr, nullptr, 0);
203 ext = X509V3_EXT_conf_nid(nullptr, &ctx, nid, value);
204
205 if (!ext)
206 return 0;
207
208 X509_add_ext(cert, ext, -1);
209 X509_EXTENSION_free(ext);
210 return 1;
211}
212#endif
213
214WINPR_ATTR_NODISCARD
215static const char* x509_name_parse(const char* name, const char* txt, size_t* length)
216{
217 if (!name || !txt || !length)
218 return nullptr;
219
220 const char* entry = nullptr;
221 char* fmt = nullptr;
222 size_t fmtlen = 0;
223 (void)winpr_asprintf(&fmt, &fmtlen, "%s=", txt);
224 if (!fmt)
225 goto fail;
226
227 const char* p = strstr(name, fmt);
228
229 if (!p)
230 goto fail;
231
232 entry = &p[fmtlen];
233 const char* sep1 = strchr(entry, ';');
234 const char* sep2 = strchr(entry, ',');
235 const char* sep3 = strchr(entry, ' ');
236 if (!sep1 && !sep2 && !sep3)
237 *length = strlen(entry);
238 else if (sep1)
239 *length = WINPR_ASSERTING_INT_CAST(size_t, sep1 - entry);
240 else if (sep2)
241 *length = WINPR_ASSERTING_INT_CAST(size_t, sep2 - entry);
242 else
243 *length = WINPR_ASSERTING_INT_CAST(size_t, sep3 - entry);
244
245fail:
246 free(fmt);
247 return entry;
248}
249
250WINPR_ATTR_MALLOC(free, 1)
251static char* get_name(COMPUTER_NAME_FORMAT type)
252{
253 DWORD nSize = 0;
254
255 if (GetComputerNameExA(type, nullptr, &nSize))
256 return nullptr;
257
258 if (GetLastError() != ERROR_MORE_DATA)
259 return nullptr;
260
261 char* computerName = calloc(1, nSize);
262
263 if (!computerName)
264 return nullptr;
265
266 if (!GetComputerNameExA(type, computerName, &nSize))
267 {
268 free(computerName);
269 return nullptr;
270 }
271
272 return computerName;
273}
274
275WINPR_ATTR_MALLOC(free, 1)
276static char* x509_get_default_name(void)
277{
278 char* computerName = get_name(ComputerNamePhysicalDnsFullyQualified);
279 if (!computerName)
280 computerName = get_name(ComputerNamePhysicalNetBIOS);
281 return computerName;
282}
283
284WINPR_ATTR_NODISCARD
285static int command_line_pre_filter(void* pvctx, int index, int argc, LPSTR* argv)
286{
287 MAKECERT_CONTEXT* context = pvctx;
288 if (!context || !argv || (index < 0) || (argc < 0))
289 return -1;
290
291 if (index == (argc - 1))
292 {
293 if (argv[index][0] != '-')
294 {
295 context->output_file = _strdup(argv[index]);
296
297 if (!context->output_file)
298 return -1;
299
300 return 1;
301 }
302 }
303
304 return 0;
305}
306
307WINPR_ATTR_NODISCARD
308static int makecert_context_parse_arguments(MAKECERT_CONTEXT* context,
309 COMMAND_LINE_ARGUMENT_A* args, int argc, char** argv)
310{
311 int status = 0;
312 DWORD flags = 0;
313 const COMMAND_LINE_ARGUMENT_A* arg = nullptr;
314
315 if (!context || !argv || (argc < 0))
316 return -1;
317
322 CommandLineClearArgumentsA(args);
323 flags = COMMAND_LINE_SEPARATOR_SPACE | COMMAND_LINE_SIGIL_DASH;
324 status = CommandLineParseArgumentsA(argc, argv, args, flags, context, command_line_pre_filter,
325 nullptr);
326
327 if (status & COMMAND_LINE_STATUS_PRINT_HELP)
328 {
329 makecert_print_command_line_help(args, argc, argv);
330 return 0;
331 }
332
333 arg = args;
334 errno = 0;
335
336 do
337 {
338 if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
339 continue;
340
341 CommandLineSwitchStart(arg)
342 /* Basic Options */
343 CommandLineSwitchCase(arg, "silent")
344 {
345 context->silent = TRUE;
346 }
347 CommandLineSwitchCase(arg, "live")
348 {
349 context->live = TRUE;
350 }
351 CommandLineSwitchCase(arg, "format")
352 {
353 if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
354 continue;
355
356 if (strcmp(arg->Value, "crt") == 0)
357 {
358 context->crtFormat = TRUE;
359 context->pemFormat = FALSE;
360 context->pfxFormat = FALSE;
361 }
362 else if (strcmp(arg->Value, "pem") == 0)
363 {
364 context->crtFormat = FALSE;
365 context->pemFormat = TRUE;
366 context->pfxFormat = FALSE;
367 }
368 else if (strcmp(arg->Value, "pfx") == 0)
369 {
370 context->crtFormat = FALSE;
371 context->pemFormat = FALSE;
372 context->pfxFormat = TRUE;
373 }
374 else
375 return -1;
376 }
377 CommandLineSwitchCase(arg, "path")
378 {
379 if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
380 continue;
381
382 context->output_path = _strdup(arg->Value);
383
384 if (!context->output_path)
385 return -1;
386 }
387 CommandLineSwitchCase(arg, "p")
388 {
389 if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
390 continue;
391
392 context->password = _strdup(arg->Value);
393
394 if (!context->password)
395 return -1;
396 }
397 CommandLineSwitchCase(arg, "n")
398 {
399 if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
400 continue;
401
402 context->common_name = _strdup(arg->Value);
403
404 if (!context->common_name)
405 return -1;
406 }
407 CommandLineSwitchCase(arg, "y")
408 {
409 long val = 0;
410
411 if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
412 continue;
413
414 val = strtol(arg->Value, nullptr, 0);
415
416 if ((errno != 0) || (val < 0) || (val > INT32_MAX))
417 return -1;
418
419 context->duration_years = (int)val;
420 }
421 CommandLineSwitchCase(arg, "m")
422 {
423 long val = 0;
424
425 if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
426 continue;
427
428 val = strtol(arg->Value, nullptr, 0);
429
430 if ((errno != 0) || (val < 0))
431 return -1;
432
433 context->duration_months = (int)val;
434 }
435 CommandLineSwitchDefault(arg)
436 {
437 }
438 CommandLineSwitchEnd(arg)
439 } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr);
440
441 return 1;
442}
443
444int makecert_context_set_output_file_name(MAKECERT_CONTEXT* context, const char* name)
445{
446 if (!context)
447 return -1;
448
449 free(context->output_file);
450 context->output_file = nullptr;
451
452 if (name)
453 context->output_file = _strdup(name);
454
455 if (!context->output_file)
456 return -1;
457
458 return 1;
459}
460
461int makecert_context_output_certificate_file(MAKECERT_CONTEXT* context, const char* path)
462{
463#ifdef WITH_OPENSSL
464 FILE* fp = nullptr;
465 int status = 0;
466 size_t offset = 0;
467 char* fullpath = nullptr;
468 char* ext = nullptr;
469 int ret = -1;
470 BIO* bio = nullptr;
471 char* x509_str = nullptr;
472
473 if (!context)
474 return -1;
475
476 if (!context->output_file)
477 {
478 context->output_file = _strdup(context->default_name);
479
480 if (!context->output_file)
481 return -1;
482 }
483
484 /*
485 * Output Certificate File
486 */
487 size_t length = strlen(context->output_file);
488 char* filename = malloc(length + 8);
489
490 if (!filename)
491 return -1;
492
493 if (context->crtFormat)
494 ext = "crt";
495 else if (context->pemFormat)
496 ext = "pem";
497 else if (context->pfxFormat)
498 ext = "pfx";
499 else
500 goto out_fail;
501
502 (void)sprintf_s(filename, length + 8, "%s.%s", context->output_file, ext);
503
504 if (path)
505 fullpath = GetCombinedPath(path, filename);
506 else
507 fullpath = _strdup(filename);
508
509 if (!fullpath)
510 goto out_fail;
511
512 fp = winpr_fopen(fullpath, "w+");
513
514 if (fp)
515 {
516 if (context->pfxFormat)
517 {
518 if (!context->password)
519 {
520 BYTE random[32] = WINPR_C_ARRAY_INIT;
521 if (winpr_RAND(random, sizeof(random)) < 0)
522 goto out_fail;
523
524 context->password = winpr_BinToHexString(random, sizeof(random), FALSE);
525
526 if (!context->password)
527 goto out_fail;
528
529 printf("Using random export password \"%s\"\n", context->password);
530 }
531
532#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
533 OpenSSL_add_all_algorithms();
534 OpenSSL_add_all_ciphers();
535 OpenSSL_add_all_digests();
536#else
537 if (OPENSSL_init_crypto(OPENSSL_INIT_ADD_ALL_CIPHERS | OPENSSL_INIT_ADD_ALL_DIGESTS |
538 OPENSSL_INIT_LOAD_CONFIG,
539 nullptr) != 1)
540 goto out_fail;
541#endif
542 context->pkcs12 = PKCS12_create(context->password, context->default_name, context->pkey,
543 context->x509, nullptr, 0, 0, 0, 0, 0);
544
545 if (!context->pkcs12)
546 goto out_fail;
547
548 bio = BIO_new(BIO_s_mem());
549
550 if (!bio)
551 goto out_fail;
552
553 status = i2d_PKCS12_bio(bio, context->pkcs12);
554
555 if (status != 1)
556 goto out_fail;
557
558 x509_str = makecert_read_str(bio, &offset);
559
560 if (!x509_str)
561 goto out_fail;
562
563 length = offset;
564
565 if (fwrite((void*)x509_str, length, 1, fp) != 1)
566 goto out_fail;
567 }
568 else
569 {
570 bio = BIO_new(BIO_s_mem());
571
572 if (!bio)
573 goto out_fail;
574
575 if (!PEM_write_bio_X509(bio, context->x509))
576 goto out_fail;
577
578 x509_str = makecert_read_str(bio, &offset);
579
580 if (!x509_str)
581 goto out_fail;
582
583 length = offset;
584
585 if (fwrite(x509_str, length, 1, fp) != 1)
586 goto out_fail;
587
588 free(x509_str);
589 x509_str = nullptr;
590 BIO_free_all(bio);
591 bio = nullptr;
592
593 if (context->pemFormat)
594 {
595 bio = BIO_new(BIO_s_mem());
596
597 if (!bio)
598 goto out_fail;
599
600 status = PEM_write_bio_PrivateKey(bio, context->pkey, nullptr, nullptr, 0, nullptr,
601 nullptr);
602
603 if (status < 0)
604 goto out_fail;
605
606 x509_str = makecert_read_str(bio, &offset);
607 if (!x509_str)
608 goto out_fail;
609
610 length = offset;
611
612 if (fwrite(x509_str, length, 1, fp) != 1)
613 goto out_fail;
614 }
615 }
616 }
617
618 ret = 1;
619out_fail:
620 BIO_free_all(bio);
621
622 if (fp)
623 (void)fclose(fp);
624
625 free(x509_str);
626 free(filename);
627 free(fullpath);
628 return ret;
629#else
630 WLog_ERR(TAG, "%s only supported with OpenSSL", __func__);
631 return -1;
632#endif
633}
634
635int makecert_context_output_private_key_file(MAKECERT_CONTEXT* context, const char* path)
636{
637#ifdef WITH_OPENSSL
638 FILE* fp = nullptr;
639 size_t length = 0;
640 size_t offset = 0;
641 char* filename = nullptr;
642 char* fullpath = nullptr;
643 int ret = -1;
644 BIO* bio = nullptr;
645 char* x509_str = nullptr;
646
647 if (!context->crtFormat)
648 return 1;
649
650 if (!context->output_file)
651 {
652 context->output_file = _strdup(context->default_name);
653
654 if (!context->output_file)
655 return -1;
656 }
657
661 length = strlen(context->output_file);
662 filename = malloc(length + 8);
663
664 if (!filename)
665 return -1;
666
667 (void)sprintf_s(filename, length + 8, "%s.key", context->output_file);
668
669 if (path)
670 fullpath = GetCombinedPath(path, filename);
671 else
672 fullpath = _strdup(filename);
673
674 if (!fullpath)
675 goto out_fail;
676
677 fp = winpr_fopen(fullpath, "w+");
678
679 if (!fp)
680 goto out_fail;
681
682 bio = BIO_new(BIO_s_mem());
683
684 if (!bio)
685 goto out_fail;
686
687 if (!PEM_write_bio_PrivateKey(bio, context->pkey, nullptr, nullptr, 0, nullptr, nullptr))
688 goto out_fail;
689
690 x509_str = makecert_read_str(bio, &offset);
691
692 if (!x509_str)
693 goto out_fail;
694
695 length = offset;
696
697 if (fwrite((void*)x509_str, length, 1, fp) != 1)
698 goto out_fail;
699
700 ret = 1;
701out_fail:
702
703 if (fp)
704 (void)fclose(fp);
705
706 BIO_free_all(bio);
707 free(x509_str);
708 free(filename);
709 free(fullpath);
710 return ret;
711#else
712 WLog_ERR(TAG, "%s only supported with OpenSSL", __func__);
713 return -1;
714#endif
715}
716
717#ifdef WITH_OPENSSL
718WINPR_ATTR_NODISCARD
719static BOOL makecert_create_rsa(EVP_PKEY** ppkey, size_t key_length)
720{
721 BOOL rc = FALSE;
722
723 WINPR_ASSERT(ppkey);
724
725#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
726 RSA* rsa = nullptr;
727#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(LIBRESSL_VERSION_NUMBER)
728 rsa = RSA_generate_key(key_length, RSA_F4, nullptr, nullptr);
729#else
730 {
731 BIGNUM* bn = BN_secure_new();
732
733 if (!bn)
734 return FALSE;
735
736 rsa = RSA_new();
737
738 if (!rsa)
739 {
740 BN_clear_free(bn);
741 return FALSE;
742 }
743
744 BN_set_word(bn, RSA_F4);
745 const int res = RSA_generate_key_ex(rsa, key_length, bn, nullptr);
746 BN_clear_free(bn);
747
748 if (res != 1)
749 return FALSE;
750 }
751#endif
752
753 if (!EVP_PKEY_assign_RSA(*ppkey, rsa))
754 {
755 RSA_free(rsa);
756 return FALSE;
757 }
758 rc = TRUE;
759#else
760 EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_from_name(nullptr, "RSA", nullptr);
761 if (!pctx)
762 return FALSE;
763
764 if (EVP_PKEY_keygen_init(pctx) != 1)
765 goto fail;
766
767 {
768 WINPR_ASSERT(key_length <= UINT_MAX);
769 unsigned int keylen = (unsigned int)key_length;
770 const OSSL_PARAM params[] = { OSSL_PARAM_construct_uint("bits", &keylen),
771 OSSL_PARAM_construct_end() };
772 if (EVP_PKEY_CTX_set_params(pctx, params) != 1)
773 goto fail;
774 }
775
776 if (EVP_PKEY_generate(pctx, ppkey) != 1)
777 goto fail;
778
779 rc = TRUE;
780fail:
781 EVP_PKEY_CTX_free(pctx);
782#endif
783 return rc;
784}
785#endif
786
787static int add_entry(X509_NAME* name, const char* value, const char* txt)
788{
789 WINPR_ASSERT(name);
790 WINPR_ASSERT(value);
791 WINPR_ASSERT(txt);
792
793 size_t length = 0;
794 const char* entry = x509_name_parse(value, txt, &length);
795 if (entry)
796 {
797 if (length > INT32_MAX)
798 return -1;
799 const int rc =
800 X509_NAME_add_entry_by_txt(name, txt, MBSTRING_UTF8, (const unsigned char*)entry,
801 WINPR_ASSERTING_INT_CAST(int, length), -1, 0);
802 if (rc != 1)
803 return -1;
804 return 1;
805 }
806 return 0;
807}
808
809int makecert_context_process(MAKECERT_CONTEXT* context, int argc, char** argv)
810{
811 COMMAND_LINE_ARGUMENT_A args[] = {
812 /* Custom Options */
813
814 { "rdp", COMMAND_LINE_VALUE_FLAG, nullptr, nullptr, nullptr, -1, nullptr,
815 "Unsupported - Generate certificate with required options for RDP usage." },
816 { "silent", COMMAND_LINE_VALUE_FLAG, nullptr, nullptr, nullptr, -1, nullptr,
817 "Silently generate certificate without verbose output." },
818 { "live", COMMAND_LINE_VALUE_FLAG, nullptr, nullptr, nullptr, -1, nullptr,
819 "Generate certificate live in memory when used as a library." },
820 { "format", COMMAND_LINE_VALUE_REQUIRED, "<crt|pem|pfx>", nullptr, nullptr, -1, nullptr,
821 "Specify certificate file format" },
822 { "path", COMMAND_LINE_VALUE_REQUIRED, "<path>", nullptr, nullptr, -1, nullptr,
823 "Specify certificate file output path" },
824 { "p", COMMAND_LINE_VALUE_REQUIRED, "<password>", nullptr, nullptr, -1, nullptr,
825 "Specify certificate export password" },
826
827 /* Basic Options */
828
829 { "n", COMMAND_LINE_VALUE_REQUIRED, "<name>", nullptr, nullptr, -1, nullptr,
830 "Specifies the subject's certificate name. This name must conform to the X.500 standard. "
831 "The simplest method is to specify the name in double quotes, preceded by CN=; for "
832 "example, "
833 "-n \"CN=myName\"." },
834 { "pe", COMMAND_LINE_VALUE_FLAG, nullptr, nullptr, nullptr, -1, nullptr,
835 "Unsupported - Marks the generated private key as exportable. This allows the private "
836 "key to "
837 "be included in the certificate." },
838 { "sk", COMMAND_LINE_VALUE_REQUIRED, "<keyname>", nullptr, nullptr, -1, nullptr,
839 "Unsupported - Specifies the subject's key container location, which contains the "
840 "private "
841 "key. "
842 "If a key container does not exist, it will be created." },
843 { "sr", COMMAND_LINE_VALUE_REQUIRED, "<location>", nullptr, nullptr, -1, nullptr,
844 "Unsupported - Specifies the subject's certificate store location. location can be "
845 "either "
846 "currentuser (the default) or localmachine." },
847 { "ss", COMMAND_LINE_VALUE_REQUIRED, "<store>", nullptr, nullptr, -1, nullptr,
848 "Unsupported - Specifies the subject's certificate store name that stores the output "
849 "certificate." },
850 { "#", COMMAND_LINE_VALUE_REQUIRED, "<number>", nullptr, nullptr, -1, nullptr,
851 "Specifies a serial number from 1 to 2,147,483,647. The default is a unique value "
852 "generated "
853 "by Makecert.exe." },
854 { "$", COMMAND_LINE_VALUE_REQUIRED, "<authority>", nullptr, nullptr, -1, nullptr,
855 "Unsupported - Specifies the signing authority of the certificate, which must be set to "
856 "either commercial "
857 "(for certificates used by commercial software publishers) or individual (for "
858 "certificates "
859 "used by individual software publishers)." },
860
861 /* Extended Options */
862
863 { "a", COMMAND_LINE_VALUE_REQUIRED, "<algorithm>", nullptr, nullptr, -1, nullptr,
864 "Specifies the signature algorithm. algorithm must be md5, sha1, sha256 (the default), "
865 "sha384, or sha512." },
866 { "b", COMMAND_LINE_VALUE_REQUIRED, "<mm/dd/yyyy>", nullptr, nullptr, -1, nullptr,
867 "Unsupported - Specifies the start of the validity period. Defaults to the current "
868 "date." },
869 { "crl", COMMAND_LINE_VALUE_FLAG, nullptr, nullptr, nullptr, -1, nullptr,
870 "Unsupported - Generates a certificate relocation list (CRL) instead of a certificate." },
871 { "cy", COMMAND_LINE_VALUE_REQUIRED, "<certType>", nullptr, nullptr, -1, nullptr,
872 "Unsupported - Specifies the certificate type. Valid values are end for end-entity and "
873 "authority for certification authority." },
874 { "e", COMMAND_LINE_VALUE_REQUIRED, "<mm/dd/yyyy>", nullptr, nullptr, -1, nullptr,
875 "Unsupported - Specifies the end of the validity period. Defaults to 12/31/2039 11:59:59 "
876 "GMT." },
877 { "eku", COMMAND_LINE_VALUE_REQUIRED, "<oid[,oid…]>", nullptr, nullptr, -1, nullptr,
878 "Unsupported - Inserts a list of comma-separated, enhanced key usage object identifiers "
879 "(OIDs) into the certificate." },
880 { "h", COMMAND_LINE_VALUE_REQUIRED, "<number>", nullptr, nullptr, -1, nullptr,
881 "Unsupported - Specifies the maximum height of the tree below this certificate." },
882 { "ic", COMMAND_LINE_VALUE_REQUIRED, "<file>", nullptr, nullptr, -1, nullptr,
883 "Unsupported - Specifies the issuer's certificate file." },
884 { "ik", COMMAND_LINE_VALUE_REQUIRED, "<keyName>", nullptr, nullptr, -1, nullptr,
885 "Unsupported - Specifies the issuer's key container name." },
886 { "iky", COMMAND_LINE_VALUE_REQUIRED, "<keyType>", nullptr, nullptr, -1, nullptr,
887 "Unsupported - Specifies the issuer's key type, which must be one of the following: "
888 "signature (which indicates that the key is used for a digital signature), "
889 "exchange (which indicates that the key is used for key encryption and key exchange), "
890 "or an integer that represents a provider type. "
891 "By default, you can pass 1 for an exchange key or 2 for a signature key." },
892 { "in", COMMAND_LINE_VALUE_REQUIRED, "<name>", nullptr, nullptr, -1, nullptr,
893 "Unsupported - Specifies the issuer's certificate common name." },
894 { "ip", COMMAND_LINE_VALUE_REQUIRED, "<provider>", nullptr, nullptr, -1, nullptr,
895 "Unsupported - Specifies the issuer's CryptoAPI provider name. For information about the "
896 "CryptoAPI provider name, see the –sp option." },
897 { "ir", COMMAND_LINE_VALUE_REQUIRED, "<location>", nullptr, nullptr, -1, nullptr,
898 "Unsupported - Specifies the location of the issuer's certificate store. location can be "
899 "either currentuser (the default) or localmachine." },
900 { "is", COMMAND_LINE_VALUE_REQUIRED, "<store>", nullptr, nullptr, -1, nullptr,
901 "Unsupported - Specifies the issuer's certificate store name." },
902 { "iv", COMMAND_LINE_VALUE_REQUIRED, "<pvkFile>", nullptr, nullptr, -1, nullptr,
903 "Unsupported - Specifies the issuer's .pvk private key file." },
904 { "iy", COMMAND_LINE_VALUE_REQUIRED, "<type>", nullptr, nullptr, -1, nullptr,
905 "Unsupported - Specifies the issuer's CryptoAPI provider type. For information about the "
906 "CryptoAPI provider type, see the –sy option." },
907 { "l", COMMAND_LINE_VALUE_REQUIRED, "<link>", nullptr, nullptr, -1, nullptr,
908 "Unsupported - Links to policy information (for example, to a URL)." },
909 { "len", COMMAND_LINE_VALUE_REQUIRED, "<number>", nullptr, nullptr, -1, nullptr,
910 "Specifies the generated key length, in bits." },
911 { "m", COMMAND_LINE_VALUE_REQUIRED, "<number>", nullptr, nullptr, -1, nullptr,
912 "Specifies the duration, in months, of the certificate validity period." },
913 { "y", COMMAND_LINE_VALUE_REQUIRED, "<number>", nullptr, nullptr, -1, nullptr,
914 "Specifies the duration, in years, of the certificate validity period." },
915 { "nscp", COMMAND_LINE_VALUE_FLAG, nullptr, nullptr, nullptr, -1, nullptr,
916 "Unsupported - Includes the Netscape client-authorization extension." },
917 { "r", COMMAND_LINE_VALUE_FLAG, nullptr, nullptr, nullptr, -1, nullptr,
918 "Unsupported - Creates a self-signed certificate." },
919 { "sc", COMMAND_LINE_VALUE_REQUIRED, "<file>", nullptr, nullptr, -1, nullptr,
920 "Unsupported - Specifies the subject's certificate file." },
921 { "sky", COMMAND_LINE_VALUE_REQUIRED, "<keyType>", nullptr, nullptr, -1, nullptr,
922 "Unsupported - Specifies the subject's key type, which must be one of the following: "
923 "signature (which indicates that the key is used for a digital signature), "
924 "exchange (which indicates that the key is used for key encryption and key exchange), "
925 "or an integer that represents a provider type. "
926 "By default, you can pass 1 for an exchange key or 2 for a signature key." },
927 { "sp", COMMAND_LINE_VALUE_REQUIRED, "<provider>", nullptr, nullptr, -1, nullptr,
928 "Unsupported - Specifies the subject's CryptoAPI provider name, which must be defined in "
929 "the "
930 "registry subkeys of "
931 "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography\\Defaults\\Provider. If both –sp "
932 "and "
933 "–sy are present, "
934 "the type of the CryptoAPI provider must correspond to the Type value of the provider's "
935 "subkey." },
936 { "sv", COMMAND_LINE_VALUE_REQUIRED, "<pvkFile>", nullptr, nullptr, -1, nullptr,
937 "Unsupported - Specifies the subject's .pvk private key file. The file is created if "
938 "none "
939 "exists." },
940 { "sy", COMMAND_LINE_VALUE_REQUIRED, "<type>", nullptr, nullptr, -1, nullptr,
941 "Unsupported - Specifies the subject's CryptoAPI provider type, which must be defined in "
942 "the "
943 "registry subkeys of "
944 "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography\\Defaults\\Provider Types. If "
945 "both "
946 "–sy and –sp are present, "
947 "the name of the CryptoAPI provider must correspond to the Name value of the provider "
948 "type "
949 "subkey." },
950 { "tbs", COMMAND_LINE_VALUE_REQUIRED, "<file>", nullptr, nullptr, -1, nullptr,
951 "Unsupported - Specifies the certificate or CRL file to be signed." },
952
953 /* Help */
954
955 { "?", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_HELP, nullptr, nullptr, nullptr, -1,
956 "help", "print help" },
957 { "!", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_HELP, nullptr, nullptr, nullptr, -1,
958 "help-ext", "print extended help" },
959 { nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr }
960 };
961#ifdef WITH_OPENSSL
962 long serial = 0;
963 X509_NAME* name = nullptr;
964 const EVP_MD* md = nullptr;
965 const COMMAND_LINE_ARGUMENT_A* arg = nullptr;
966 int ret = makecert_context_parse_arguments(context, args, argc, argv);
967
968 if (ret < 1)
969 {
970 return ret;
971 }
972
973 if (!context->default_name && !context->common_name)
974 {
975 context->default_name = x509_get_default_name();
976
977 if (!context->default_name)
978 return -1;
979 }
980 else
981 {
982 context->default_name = _strdup(context->common_name);
983
984 if (!context->default_name)
985 return -1;
986 }
987
988 if (!context->common_name)
989 {
990 context->common_name = _strdup(context->default_name);
991
992 if (!context->common_name)
993 return -1;
994 }
995
996 if (!context->pkey)
997 context->pkey = EVP_PKEY_new();
998
999 if (!context->pkey)
1000 return -1;
1001
1002 if (!context->x509)
1003 context->x509 = X509_new();
1004
1005 if (!context->x509)
1006 return -1;
1007
1008 size_t key_length = 2048;
1009 arg = CommandLineFindArgumentA(args, "len");
1010
1011 if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
1012 {
1013 unsigned long val = strtoul(arg->Value, nullptr, 0);
1014
1015 if ((errno != 0) || (val > INT_MAX))
1016 return -1;
1017 key_length = val;
1018 }
1019
1020 if (!makecert_create_rsa(&context->pkey, key_length))
1021 return -1;
1022
1023 if (X509_set_version(context->x509, 2) != 1)
1024 return -1;
1025
1026 arg = CommandLineFindArgumentA(args, "#");
1027
1028 if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
1029 {
1030 serial = strtol(arg->Value, nullptr, 0);
1031
1032 if (errno != 0)
1033 return -1;
1034 }
1035 else
1036 serial = (long)GetTickCount64();
1037
1038 if (ASN1_INTEGER_set(X509_get_serialNumber(context->x509), serial) != 1)
1039 return -1;
1040
1041 {
1042 ASN1_TIME* before = nullptr;
1043 ASN1_TIME* after = nullptr;
1044#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(LIBRESSL_VERSION_NUMBER)
1045 before = X509_get_notBefore(context->x509);
1046 after = X509_get_notAfter(context->x509);
1047#else
1048 before = X509_getm_notBefore(context->x509);
1049 after = X509_getm_notAfter(context->x509);
1050#endif
1051 X509_gmtime_adj(before, 0);
1052
1053 long duration = context->duration_months * 31l + context->duration_years * 365l;
1054 duration *= 60l * 60l * 24l;
1055 X509_gmtime_adj(after, duration);
1056 }
1057 if (X509_set_pubkey(context->x509, context->pkey) != 1)
1058 return -1;
1059
1060 name = X509_get_subject_name(context->x509);
1061 arg = CommandLineFindArgumentA(args, "n");
1062
1063 if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
1064 {
1065 BOOL haveCN = FALSE;
1066
1067 const char* records[] = { "CN", "ST", "OU", "L", "O", "C" };
1068
1069 for (size_t x = 0; x < ARRAYSIZE(records); x++)
1070 {
1071 const char* record = records[x];
1072 const int rc = add_entry(name, arg->Value, record);
1073 if (rc < 0)
1074 return rc;
1075 if (x == 0)
1076 haveCN = rc > 0;
1077 }
1078
1079 if (!haveCN)
1080 {
1081 const int rc = add_entry(name, context->common_name, "CN");
1082 if (rc < 0)
1083 return rc;
1084 }
1085 }
1086 else
1087 {
1088 const int rc = add_entry(name, context->common_name, "CN");
1089 if (rc < 0)
1090 return rc;
1091 }
1092
1093 if (X509_set_issuer_name(context->x509, name) != 1)
1094 return -1;
1095
1096 if (x509_add_ext(context->x509, NID_ext_key_usage, "serverAuth") != 1)
1097 return -1;
1098
1099 arg = CommandLineFindArgumentA(args, "a");
1100 md = EVP_sha256();
1101
1102 if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
1103 {
1104 md = EVP_get_digestbyname(arg->Value);
1105 if (!md)
1106 return -1;
1107 }
1108
1109 if (!X509_sign(context->x509, context->pkey, md))
1110 return -1;
1111
1116 if (!context->silent)
1117 {
1118 BIO* bio = BIO_new(BIO_s_mem());
1119
1120 if (!bio)
1121 return -1;
1122
1123 const int status = X509_print(bio, context->x509);
1124
1125 if (status < 0)
1126 {
1127 BIO_free_all(bio);
1128 return -1;
1129 }
1130
1131 char* x509_str = makecert_read_str(bio, nullptr);
1132 if (!x509_str)
1133 {
1134 BIO_free_all(bio);
1135 return -1;
1136 }
1137
1138 printf("%s", x509_str);
1139 free(x509_str);
1140 BIO_free_all(bio);
1141 }
1142
1147 if (!context->live)
1148 {
1149 if (!winpr_PathFileExists(context->output_path))
1150 {
1151 if (!winpr_PathMakePath(context->output_path, nullptr))
1152 return -1;
1153 }
1154
1155 if (makecert_context_output_certificate_file(context, context->output_path) != 1)
1156 return -1;
1157
1158 if (context->crtFormat)
1159 {
1160 if (makecert_context_output_private_key_file(context, context->output_path) < 0)
1161 return -1;
1162 }
1163 }
1164
1165 return 0;
1166#else
1167 WLog_ERR(TAG, "%s only supported with OpenSSL", __func__);
1168 return -1;
1169#endif
1170}
1171
1172MAKECERT_CONTEXT* makecert_context_new(void)
1173{
1174 if (!utils_set_umask())
1175 return nullptr;
1176
1177 MAKECERT_CONTEXT* context = (MAKECERT_CONTEXT*)calloc(1, sizeof(MAKECERT_CONTEXT));
1178
1179 if (context)
1180 {
1181 context->crtFormat = TRUE;
1182 context->duration_years = 1;
1183 }
1184
1185 return context;
1186}
1187
1188void makecert_context_free(MAKECERT_CONTEXT* context)
1189{
1190 if (context)
1191 {
1192 winpr_zfree(context->password);
1193 free(context->default_name);
1194 free(context->common_name);
1195 free(context->output_file);
1196 free(context->output_path);
1197#ifdef WITH_OPENSSL
1198 X509_free(context->x509);
1199 EVP_PKEY_free(context->pkey);
1200#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(LIBRESSL_VERSION_NUMBER)
1201 CRYPTO_cleanup_all_ex_data();
1202#endif
1203#endif
1204 free(context);
1205 }
1206}