Coverage Report

Created: 2026-10-15 06:26

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