Coverage Report

Created: 2026-08-31 06:25

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