Coverage Report

Created: 2026-09-14 06:32

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
0
{
74
0
  int status = -1;
75
0
  size_t offset = 0;
76
0
  size_t length = 0;
77
0
  char* x509_str = nullptr;
78
79
0
  while (offset >= length)
80
0
  {
81
0
    size_t readBytes = 0;
82
0
    char* new_str = nullptr;
83
0
    size_t new_len = length + 2048ull;
84
85
0
    if (new_len > INT_MAX)
86
0
    {
87
0
      status = -1;
88
0
      break;
89
0
    }
90
91
0
    new_str = (char*)realloc(x509_str, new_len);
92
93
0
    if (!new_str)
94
0
    {
95
0
      status = -1;
96
0
      break;
97
0
    }
98
99
0
    length = new_len;
100
0
    x509_str = new_str;
101
0
    ERR_clear_error();
102
0
#if OPENSSL_VERSION_NUMBER >= 0x10101000L && !defined(LIBRESSL_VERSION_NUMBER)
103
0
    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
0
    if (status <= 0)
109
0
      break;
110
111
0
    offset += readBytes;
112
0
  }
113
114
0
  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
0
  x509_str[offset] = '\0';
123
0
  if (pOffset)
124
0
    *pOffset = offset + 1;
125
0
  return x509_str;
126
0
}
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
0
{
180
0
  X509V3_CTX ctx;
181
0
  X509_EXTENSION* ext = nullptr;
182
183
0
  if (!cert || !value)
184
0
    return 0;
185
186
0
  X509V3_set_ctx_nodb(&ctx) X509V3_set_ctx(&ctx, cert, cert, nullptr, nullptr, 0);
187
0
  ext = X509V3_EXT_conf_nid(nullptr, &ctx, nid, value);
188
189
0
  if (!ext)
190
0
    return 0;
191
192
0
  X509_add_ext(cert, ext, -1);
193
0
  X509_EXTENSION_free(ext);
194
0
  return 1;
195
0
}
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
0
{
224
0
  DWORD nSize = 0;
225
226
0
  if (GetComputerNameExA(type, nullptr, &nSize))
227
0
    return nullptr;
228
229
0
  if (GetLastError() != ERROR_MORE_DATA)
230
0
    return nullptr;
231
232
0
  char* computerName = calloc(1, nSize);
233
234
0
  if (!computerName)
235
0
    return nullptr;
236
237
0
  if (!GetComputerNameExA(type, computerName, &nSize))
238
0
  {
239
0
    free(computerName);
240
0
    return nullptr;
241
0
  }
242
243
0
  return computerName;
244
0
}
245
246
static char* x509_get_default_name(void)
247
0
{
248
0
  char* computerName = get_name(ComputerNamePhysicalDnsFullyQualified);
249
0
  if (!computerName)
250
0
    computerName = get_name(ComputerNamePhysicalNetBIOS);
251
0
  return computerName;
252
0
}
253
254
static int command_line_pre_filter(void* pvctx, int index, int argc, LPSTR* argv)
255
0
{
256
0
  MAKECERT_CONTEXT* context = pvctx;
257
0
  if (!context || !argv || (index < 0) || (argc < 0))
258
0
    return -1;
259
260
0
  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
0
  return 0;
274
0
}
275
276
static int makecert_context_parse_arguments(MAKECERT_CONTEXT* context,
277
                                            COMMAND_LINE_ARGUMENT_A* args, int argc, char** argv)
278
0
{
279
0
  int status = 0;
280
0
  DWORD flags = 0;
281
0
  const COMMAND_LINE_ARGUMENT_A* arg = nullptr;
282
283
0
  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
0
  CommandLineClearArgumentsA(args);
291
0
  flags = COMMAND_LINE_SEPARATOR_SPACE | COMMAND_LINE_SIGIL_DASH;
292
0
  status = CommandLineParseArgumentsA(argc, argv, args, flags, context, command_line_pre_filter,
293
0
                                      nullptr);
294
295
0
  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
0
  arg = args;
302
0
  errno = 0;
303
304
0
  do
305
0
  {
306
0
    if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
307
0
      continue;
308
309
0
    CommandLineSwitchStart(arg)
310
        /* Basic Options */
311
0
        CommandLineSwitchCase(arg, "silent")
312
0
    {
313
0
      context->silent = TRUE;
314
0
    }
315
0
    CommandLineSwitchCase(arg, "live")
316
0
    {
317
0
      context->live = TRUE;
318
0
    }
319
0
    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
0
    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
0
    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
0
    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
0
    CommandLineSwitchCase(arg, "y")
376
0
    {
377
0
      long val = 0;
378
379
0
      if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
380
0
        continue;
381
382
0
      val = strtol(arg->Value, nullptr, 0);
383
384
0
      if ((errno != 0) || (val < 0) || (val > INT32_MAX))
385
0
        return -1;
386
387
0
      context->duration_years = (int)val;
388
0
    }
389
0
    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
0
    CommandLineSwitchDefault(arg)
404
0
    {
405
0
    }
406
0
    CommandLineSwitchEnd(arg)
407
0
  } while ((arg = CommandLineFindNextArgumentA(arg)) != nullptr);
408
409
0
  return 1;
410
0
}
411
412
int makecert_context_set_output_file_name(MAKECERT_CONTEXT* context, const char* name)
413
0
{
414
0
  if (!context)
415
0
    return -1;
416
417
0
  free(context->output_file);
418
0
  context->output_file = nullptr;
419
420
0
  if (name)
421
0
    context->output_file = _strdup(name);
422
423
0
  if (!context->output_file)
424
0
    return -1;
425
426
0
  return 1;
427
0
}
428
429
int makecert_context_output_certificate_file(MAKECERT_CONTEXT* context, const char* path)
430
0
{
431
0
#ifdef WITH_OPENSSL
432
0
  FILE* fp = nullptr;
433
0
  int status = 0;
434
0
  size_t length = 0;
435
0
  size_t offset = 0;
436
0
  char* filename = nullptr;
437
0
  char* fullpath = nullptr;
438
0
  char* ext = nullptr;
439
0
  int ret = -1;
440
0
  BIO* bio = nullptr;
441
0
  char* x509_str = nullptr;
442
443
0
  if (!context)
444
0
    return -1;
445
446
0
  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
0
  length = strlen(context->output_file);
458
0
  filename = malloc(length + 8);
459
460
0
  if (!filename)
461
0
    return -1;
462
463
0
  if (context->crtFormat)
464
0
    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
0
  (void)sprintf_s(filename, length + 8, "%s.%s", context->output_file, ext);
473
474
0
  if (path)
475
0
    fullpath = GetCombinedPath(path, filename);
476
0
  else
477
0
    fullpath = _strdup(filename);
478
479
0
  if (!fullpath)
480
0
    goto out_fail;
481
482
0
  fp = winpr_fopen(fullpath, "w+");
483
484
0
  if (fp)
485
0
  {
486
0
    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
0
    else
534
0
    {
535
0
      bio = BIO_new(BIO_s_mem());
536
537
0
      if (!bio)
538
0
        goto out_fail;
539
540
0
      if (!PEM_write_bio_X509(bio, context->x509))
541
0
        goto out_fail;
542
543
0
      x509_str = makecert_read_str(bio, &offset);
544
545
0
      if (!x509_str)
546
0
        goto out_fail;
547
548
0
      length = offset;
549
550
0
      if (fwrite(x509_str, length, 1, fp) != 1)
551
0
        goto out_fail;
552
553
0
      free(x509_str);
554
0
      x509_str = nullptr;
555
0
      BIO_free_all(bio);
556
0
      bio = nullptr;
557
558
0
      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
0
    }
581
0
  }
582
583
0
  ret = 1;
584
0
out_fail:
585
0
  BIO_free_all(bio);
586
587
0
  if (fp)
588
0
    (void)fclose(fp);
589
590
0
  free(x509_str);
591
0
  free(filename);
592
0
  free(fullpath);
593
0
  return ret;
594
#else
595
  WLog_ERR(TAG, "%s only supported with OpenSSL", __func__);
596
  return -1;
597
#endif
598
0
}
599
600
int makecert_context_output_private_key_file(MAKECERT_CONTEXT* context, const char* path)
601
0
{
602
0
#ifdef WITH_OPENSSL
603
0
  FILE* fp = nullptr;
604
0
  size_t length = 0;
605
0
  size_t offset = 0;
606
0
  char* filename = nullptr;
607
0
  char* fullpath = nullptr;
608
0
  int ret = -1;
609
0
  BIO* bio = nullptr;
610
0
  char* x509_str = nullptr;
611
612
0
  if (!context->crtFormat)
613
0
    return 1;
614
615
0
  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
0
  length = strlen(context->output_file);
627
0
  filename = malloc(length + 8);
628
629
0
  if (!filename)
630
0
    return -1;
631
632
0
  (void)sprintf_s(filename, length + 8, "%s.key", context->output_file);
633
634
0
  if (path)
635
0
    fullpath = GetCombinedPath(path, filename);
636
0
  else
637
0
    fullpath = _strdup(filename);
638
639
0
  if (!fullpath)
640
0
    goto out_fail;
641
642
0
  fp = winpr_fopen(fullpath, "w+");
643
644
0
  if (!fp)
645
0
    goto out_fail;
646
647
0
  bio = BIO_new(BIO_s_mem());
648
649
0
  if (!bio)
650
0
    goto out_fail;
651
652
0
  if (!PEM_write_bio_PrivateKey(bio, context->pkey, nullptr, nullptr, 0, nullptr, nullptr))
653
0
    goto out_fail;
654
655
0
  x509_str = makecert_read_str(bio, &offset);
656
657
0
  if (!x509_str)
658
0
    goto out_fail;
659
660
0
  length = offset;
661
662
0
  if (fwrite((void*)x509_str, length, 1, fp) != 1)
663
0
    goto out_fail;
664
665
0
  ret = 1;
666
0
out_fail:
667
668
0
  if (fp)
669
0
    (void)fclose(fp);
670
671
0
  BIO_free_all(bio);
672
0
  free(x509_str);
673
0
  free(filename);
674
0
  free(fullpath);
675
0
  return ret;
676
#else
677
  WLog_ERR(TAG, "%s only supported with OpenSSL", __func__);
678
  return -1;
679
#endif
680
0
}
681
682
#ifdef WITH_OPENSSL
683
static BOOL makecert_create_rsa(EVP_PKEY** ppkey, size_t key_length)
684
0
{
685
0
  BOOL rc = FALSE;
686
687
0
  WINPR_ASSERT(ppkey);
688
689
0
#if !defined(OPENSSL_VERSION_MAJOR) || (OPENSSL_VERSION_MAJOR < 3)
690
0
  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
0
  {
695
0
    BIGNUM* bn = BN_secure_new();
696
697
0
    if (!bn)
698
0
      return FALSE;
699
700
0
    rsa = RSA_new();
701
702
0
    if (!rsa)
703
0
    {
704
0
      BN_clear_free(bn);
705
0
      return FALSE;
706
0
    }
707
708
0
    BN_set_word(bn, RSA_F4);
709
0
    const int res = RSA_generate_key_ex(rsa, key_length, bn, nullptr);
710
0
    BN_clear_free(bn);
711
712
0
    if (res != 1)
713
0
      return FALSE;
714
0
  }
715
0
#endif
716
717
0
  if (!EVP_PKEY_assign_RSA(*ppkey, rsa))
718
0
  {
719
0
    RSA_free(rsa);
720
0
    return FALSE;
721
0
  }
722
0
  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
0
  return rc;
748
0
}
749
#endif
750
751
int makecert_context_process(MAKECERT_CONTEXT* context, int argc, char** argv)
752
0
{
753
0
  COMMAND_LINE_ARGUMENT_A args[] = {
754
    /* Custom Options */
755
756
0
    { "rdp", COMMAND_LINE_VALUE_FLAG, nullptr, nullptr, nullptr, -1, nullptr,
757
0
      "Unsupported - Generate certificate with required options for RDP usage." },
758
0
    { "silent", COMMAND_LINE_VALUE_FLAG, nullptr, nullptr, nullptr, -1, nullptr,
759
0
      "Silently generate certificate without verbose output." },
760
0
    { "live", COMMAND_LINE_VALUE_FLAG, nullptr, nullptr, nullptr, -1, nullptr,
761
0
      "Generate certificate live in memory when used as a library." },
762
0
    { "format", COMMAND_LINE_VALUE_REQUIRED, "<crt|pem|pfx>", nullptr, nullptr, -1, nullptr,
763
0
      "Specify certificate file format" },
764
0
    { "path", COMMAND_LINE_VALUE_REQUIRED, "<path>", nullptr, nullptr, -1, nullptr,
765
0
      "Specify certificate file output path" },
766
0
    { "p", COMMAND_LINE_VALUE_REQUIRED, "<password>", nullptr, nullptr, -1, nullptr,
767
0
      "Specify certificate export password" },
768
769
    /* Basic Options */
770
771
0
    { "n", COMMAND_LINE_VALUE_REQUIRED, "<name>", nullptr, nullptr, -1, nullptr,
772
0
      "Specifies the subject's certificate name. This name must conform to the X.500 standard. "
773
0
      "The simplest method is to specify the name in double quotes, preceded by CN=; for "
774
0
      "example, "
775
0
      "-n \"CN=myName\"." },
776
0
    { "pe", COMMAND_LINE_VALUE_FLAG, nullptr, nullptr, nullptr, -1, nullptr,
777
0
      "Unsupported - Marks the generated private key as exportable. This allows the private "
778
0
      "key to "
779
0
      "be included in the certificate." },
780
0
    { "sk", COMMAND_LINE_VALUE_REQUIRED, "<keyname>", nullptr, nullptr, -1, nullptr,
781
0
      "Unsupported - Specifies the subject's key container location, which contains the "
782
0
      "private "
783
0
      "key. "
784
0
      "If a key container does not exist, it will be created." },
785
0
    { "sr", COMMAND_LINE_VALUE_REQUIRED, "<location>", nullptr, nullptr, -1, nullptr,
786
0
      "Unsupported - Specifies the subject's certificate store location. location can be "
787
0
      "either "
788
0
      "currentuser (the default) or localmachine." },
789
0
    { "ss", COMMAND_LINE_VALUE_REQUIRED, "<store>", nullptr, nullptr, -1, nullptr,
790
0
      "Unsupported - Specifies the subject's certificate store name that stores the output "
791
0
      "certificate." },
792
0
    { "#", COMMAND_LINE_VALUE_REQUIRED, "<number>", nullptr, nullptr, -1, nullptr,
793
0
      "Specifies a serial number from 1 to 2,147,483,647. The default is a unique value "
794
0
      "generated "
795
0
      "by Makecert.exe." },
796
0
    { "$", COMMAND_LINE_VALUE_REQUIRED, "<authority>", nullptr, nullptr, -1, nullptr,
797
0
      "Unsupported - Specifies the signing authority of the certificate, which must be set to "
798
0
      "either commercial "
799
0
      "(for certificates used by commercial software publishers) or individual (for "
800
0
      "certificates "
801
0
      "used by individual software publishers)." },
802
803
    /* Extended Options */
804
805
0
    { "a", COMMAND_LINE_VALUE_REQUIRED, "<algorithm>", nullptr, nullptr, -1, nullptr,
806
0
      "Specifies the signature algorithm. algorithm must be md5, sha1, sha256 (the default), "
807
0
      "sha384, or sha512." },
808
0
    { "b", COMMAND_LINE_VALUE_REQUIRED, "<mm/dd/yyyy>", nullptr, nullptr, -1, nullptr,
809
0
      "Unsupported - Specifies the start of the validity period. Defaults to the current "
810
0
      "date." },
811
0
    { "crl", COMMAND_LINE_VALUE_FLAG, nullptr, nullptr, nullptr, -1, nullptr,
812
0
      "Unsupported - Generates a certificate relocation list (CRL) instead of a certificate." },
813
0
    { "cy", COMMAND_LINE_VALUE_REQUIRED, "<certType>", nullptr, nullptr, -1, nullptr,
814
0
      "Unsupported - Specifies the certificate type. Valid values are end for end-entity and "
815
0
      "authority for certification authority." },
816
0
    { "e", COMMAND_LINE_VALUE_REQUIRED, "<mm/dd/yyyy>", nullptr, nullptr, -1, nullptr,
817
0
      "Unsupported - Specifies the end of the validity period. Defaults to 12/31/2039 11:59:59 "
818
0
      "GMT." },
819
0
    { "eku", COMMAND_LINE_VALUE_REQUIRED, "<oid[,oid…]>", nullptr, nullptr, -1, nullptr,
820
0
      "Unsupported - Inserts a list of comma-separated, enhanced key usage object identifiers "
821
0
      "(OIDs) into the certificate." },
822
0
    { "h", COMMAND_LINE_VALUE_REQUIRED, "<number>", nullptr, nullptr, -1, nullptr,
823
0
      "Unsupported - Specifies the maximum height of the tree below this certificate." },
824
0
    { "ic", COMMAND_LINE_VALUE_REQUIRED, "<file>", nullptr, nullptr, -1, nullptr,
825
0
      "Unsupported - Specifies the issuer's certificate file." },
826
0
    { "ik", COMMAND_LINE_VALUE_REQUIRED, "<keyName>", nullptr, nullptr, -1, nullptr,
827
0
      "Unsupported - Specifies the issuer's key container name." },
828
0
    { "iky", COMMAND_LINE_VALUE_REQUIRED, "<keyType>", nullptr, nullptr, -1, nullptr,
829
0
      "Unsupported - Specifies the issuer's key type, which must be one of the following: "
830
0
      "signature (which indicates that the key is used for a digital signature), "
831
0
      "exchange (which indicates that the key is used for key encryption and key exchange), "
832
0
      "or an integer that represents a provider type. "
833
0
      "By default, you can pass 1 for an exchange key or 2 for a signature key." },
834
0
    { "in", COMMAND_LINE_VALUE_REQUIRED, "<name>", nullptr, nullptr, -1, nullptr,
835
0
      "Unsupported - Specifies the issuer's certificate common name." },
836
0
    { "ip", COMMAND_LINE_VALUE_REQUIRED, "<provider>", nullptr, nullptr, -1, nullptr,
837
0
      "Unsupported - Specifies the issuer's CryptoAPI provider name. For information about the "
838
0
      "CryptoAPI provider name, see the –sp option." },
839
0
    { "ir", COMMAND_LINE_VALUE_REQUIRED, "<location>", nullptr, nullptr, -1, nullptr,
840
0
      "Unsupported - Specifies the location of the issuer's certificate store. location can be "
841
0
      "either currentuser (the default) or localmachine." },
842
0
    { "is", COMMAND_LINE_VALUE_REQUIRED, "<store>", nullptr, nullptr, -1, nullptr,
843
0
      "Unsupported - Specifies the issuer's certificate store name." },
844
0
    { "iv", COMMAND_LINE_VALUE_REQUIRED, "<pvkFile>", nullptr, nullptr, -1, nullptr,
845
0
      "Unsupported - Specifies the issuer's .pvk private key file." },
846
0
    { "iy", COMMAND_LINE_VALUE_REQUIRED, "<type>", nullptr, nullptr, -1, nullptr,
847
0
      "Unsupported - Specifies the issuer's CryptoAPI provider type. For information about the "
848
0
      "CryptoAPI provider type, see the –sy option." },
849
0
    { "l", COMMAND_LINE_VALUE_REQUIRED, "<link>", nullptr, nullptr, -1, nullptr,
850
0
      "Unsupported - Links to policy information (for example, to a URL)." },
851
0
    { "len", COMMAND_LINE_VALUE_REQUIRED, "<number>", nullptr, nullptr, -1, nullptr,
852
0
      "Specifies the generated key length, in bits." },
853
0
    { "m", COMMAND_LINE_VALUE_REQUIRED, "<number>", nullptr, nullptr, -1, nullptr,
854
0
      "Specifies the duration, in months, of the certificate validity period." },
855
0
    { "y", COMMAND_LINE_VALUE_REQUIRED, "<number>", nullptr, nullptr, -1, nullptr,
856
0
      "Specifies the duration, in years, of the certificate validity period." },
857
0
    { "nscp", COMMAND_LINE_VALUE_FLAG, nullptr, nullptr, nullptr, -1, nullptr,
858
0
      "Unsupported - Includes the Netscape client-authorization extension." },
859
0
    { "r", COMMAND_LINE_VALUE_FLAG, nullptr, nullptr, nullptr, -1, nullptr,
860
0
      "Unsupported - Creates a self-signed certificate." },
861
0
    { "sc", COMMAND_LINE_VALUE_REQUIRED, "<file>", nullptr, nullptr, -1, nullptr,
862
0
      "Unsupported - Specifies the subject's certificate file." },
863
0
    { "sky", COMMAND_LINE_VALUE_REQUIRED, "<keyType>", nullptr, nullptr, -1, nullptr,
864
0
      "Unsupported - Specifies the subject's key type, which must be one of the following: "
865
0
      "signature (which indicates that the key is used for a digital signature), "
866
0
      "exchange (which indicates that the key is used for key encryption and key exchange), "
867
0
      "or an integer that represents a provider type. "
868
0
      "By default, you can pass 1 for an exchange key or 2 for a signature key." },
869
0
    { "sp", COMMAND_LINE_VALUE_REQUIRED, "<provider>", nullptr, nullptr, -1, nullptr,
870
0
      "Unsupported - Specifies the subject's CryptoAPI provider name, which must be defined in "
871
0
      "the "
872
0
      "registry subkeys of "
873
0
      "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography\\Defaults\\Provider. If both –sp "
874
0
      "and "
875
0
      "–sy are present, "
876
0
      "the type of the CryptoAPI provider must correspond to the Type value of the provider's "
877
0
      "subkey." },
878
0
    { "sv", COMMAND_LINE_VALUE_REQUIRED, "<pvkFile>", nullptr, nullptr, -1, nullptr,
879
0
      "Unsupported - Specifies the subject's .pvk private key file. The file is created if "
880
0
      "none "
881
0
      "exists." },
882
0
    { "sy", COMMAND_LINE_VALUE_REQUIRED, "<type>", nullptr, nullptr, -1, nullptr,
883
0
      "Unsupported - Specifies the subject's CryptoAPI provider type, which must be defined in "
884
0
      "the "
885
0
      "registry subkeys of "
886
0
      "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography\\Defaults\\Provider Types. If "
887
0
      "both "
888
0
      "–sy and –sp are present, "
889
0
      "the name of the CryptoAPI provider must correspond to the Name value of the provider "
890
0
      "type "
891
0
      "subkey." },
892
0
    { "tbs", COMMAND_LINE_VALUE_REQUIRED, "<file>", nullptr, nullptr, -1, nullptr,
893
0
      "Unsupported - Specifies the certificate or CRL file to be signed." },
894
895
    /* Help */
896
897
0
    { "?", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_HELP, nullptr, nullptr, nullptr, -1,
898
0
      "help", "print help" },
899
0
    { "!", COMMAND_LINE_VALUE_FLAG | COMMAND_LINE_PRINT_HELP, nullptr, nullptr, nullptr, -1,
900
0
      "help-ext", "print extended help" },
901
0
    { nullptr, 0, nullptr, nullptr, nullptr, -1, nullptr, nullptr }
902
0
  };
903
0
#ifdef WITH_OPENSSL
904
0
  size_t length = 0;
905
0
  char* entry = nullptr;
906
0
  int key_length = 0;
907
0
  long serial = 0;
908
0
  X509_NAME* name = nullptr;
909
0
  const EVP_MD* md = nullptr;
910
0
  const COMMAND_LINE_ARGUMENT_A* arg = nullptr;
911
0
  int ret = 0;
912
0
  ret = makecert_context_parse_arguments(context, args, argc, argv);
913
914
0
  if (ret < 1)
915
0
  {
916
0
    return ret;
917
0
  }
918
919
0
  if (!context->default_name && !context->common_name)
920
0
  {
921
0
    context->default_name = x509_get_default_name();
922
923
0
    if (!context->default_name)
924
0
      return -1;
925
0
  }
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
0
  if (!context->common_name)
935
0
  {
936
0
    context->common_name = _strdup(context->default_name);
937
938
0
    if (!context->common_name)
939
0
      return -1;
940
0
  }
941
942
0
  if (!context->pkey)
943
0
    context->pkey = EVP_PKEY_new();
944
945
0
  if (!context->pkey)
946
0
    return -1;
947
948
0
  if (!context->x509)
949
0
    context->x509 = X509_new();
950
951
0
  if (!context->x509)
952
0
    return -1;
953
954
0
  key_length = 2048;
955
0
  arg = CommandLineFindArgumentA(args, "len");
956
957
0
  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
0
  if (!makecert_create_rsa(&context->pkey, WINPR_ASSERTING_INT_CAST(size_t, key_length)))
967
0
    return -1;
968
969
0
  X509_set_version(context->x509, 2);
970
0
  arg = CommandLineFindArgumentA(args, "#");
971
972
0
  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
0
  else
980
0
    serial = (long)GetTickCount64();
981
982
0
  ASN1_INTEGER_set(X509_get_serialNumber(context->x509), serial);
983
0
  {
984
0
    ASN1_TIME* before = nullptr;
985
0
    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
0
    before = X509_getm_notBefore(context->x509);
991
0
    after = X509_getm_notAfter(context->x509);
992
0
#endif
993
0
    X509_gmtime_adj(before, 0);
994
995
0
    long duration = context->duration_months * 31l + context->duration_years * 365l;
996
0
    duration *= 60l * 60l * 24l;
997
0
    X509_gmtime_adj(after, duration);
998
0
  }
999
0
  X509_set_pubkey(context->x509, context->pkey);
1000
0
  name = X509_get_subject_name(context->x509);
1001
0
  arg = CommandLineFindArgumentA(args, "n");
1002
1003
0
  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
0
  else
1041
0
  {
1042
0
    entry = context->common_name;
1043
0
    length = strlen(entry);
1044
0
    X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_UTF8, (const unsigned char*)entry,
1045
0
                               (int)length, -1, 0);
1046
0
  }
1047
1048
0
  X509_set_issuer_name(context->x509, name);
1049
0
  x509_add_ext(context->x509, NID_ext_key_usage, "serverAuth");
1050
0
  arg = CommandLineFindArgumentA(args, "a");
1051
0
  md = EVP_sha256();
1052
1053
0
  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
0
  if (!X509_sign(context->x509, context->pkey, md))
1061
0
    return -1;
1062
1063
  /**
1064
   * Print certificate
1065
   */
1066
1067
0
  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
0
  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
0
  return 0;
1120
#else
1121
  WLog_ERR(TAG, "%s only supported with OpenSSL", __func__);
1122
  return -1;
1123
#endif
1124
0
}
1125
1126
MAKECERT_CONTEXT* makecert_context_new(void)
1127
0
{
1128
0
  MAKECERT_CONTEXT* context = (MAKECERT_CONTEXT*)calloc(1, sizeof(MAKECERT_CONTEXT));
1129
1130
0
  if (context)
1131
0
  {
1132
0
    context->crtFormat = TRUE;
1133
0
    context->duration_years = 1;
1134
0
  }
1135
1136
0
  return context;
1137
0
}
1138
1139
void makecert_context_free(MAKECERT_CONTEXT* context)
1140
0
{
1141
0
  if (context)
1142
0
  {
1143
0
    free(context->password);
1144
0
    free(context->default_name);
1145
0
    free(context->common_name);
1146
0
    free(context->output_file);
1147
0
    free(context->output_path);
1148
0
#ifdef WITH_OPENSSL
1149
0
    X509_free(context->x509);
1150
0
    EVP_PKEY_free(context->pkey);
1151
#if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(LIBRESSL_VERSION_NUMBER)
1152
    CRYPTO_cleanup_all_ex_data();
1153
#endif
1154
0
#endif
1155
0
    free(context);
1156
0
  }
1157
0
}