SPDX: Mass-update to GPL-2.0-or-later
[exim.git] / src / util / gen_pkcs3.c
1 /* Copyright (C) 2012,2016 Phil Pennock.
2  * Copyright (c) The Exim Maintainers 2021
3  * This is distributed as part of Exim and licensed under the GPL.
4  * See the file "NOTICE" for more details.
5  * SPDX-License-Identifier: GPL-2.0-or-later
6  */
7
8 /* Build with:
9  * c99 $(pkg-config --cflags openssl) gen_pkcs3.c $(pkg-config --libs openssl)
10  */
11
12 /*
13  * Rationale:
14  * The Diffie-Hellman primes which are embedded into Exim as named primes for
15  * the tls_dhparam option are in the std-crypto.c file.  The source for those
16  * comes from various RFCs, where they are given in hexadecimal form.
17  *
18  * This tool provides convenient conversion, to reduce the risk of human
19  * error in transcription.
20  */
21
22 #include <ctype.h>
23 #include <errno.h>
24 #include <stdbool.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29
30 #include <openssl/bio.h>
31 #include <openssl/bn.h>
32 #include <openssl/dh.h>
33 #include <openssl/err.h>
34 #include <openssl/pem.h>
35
36 extern const char *__progname;
37
38
39 void __attribute__((__noreturn__)) __attribute__((__format__(printf, 1, 2)))
40 die(const char *fmt, ...)
41 {
42   va_list ap;
43
44   fflush(NULL);
45   fprintf(stderr, "%s: ", __progname);
46   va_start(ap, fmt);
47   vfprintf(stderr, fmt, ap);
48   va_end(ap);
49   fprintf(stderr, "\n");
50   fflush(stderr);
51   exit(1);
52 }
53
54
55 void __attribute__((__noreturn__))
56 die_openssl_err(const char *msg)
57 {
58   char err_string[250];
59
60   ERR_error_string_n(ERR_get_error(), err_string, sizeof(err_string));
61   die("%s: %s", msg, err_string);
62 }
63
64
65 static BIGNUM *
66 bn_from_text(const char *text)
67 {
68   BIGNUM *b;
69   char *p, *spaceless;
70   const char *q, *end;
71   size_t len;
72   int rc;
73
74   len = strlen(text);
75   spaceless = malloc(len + 1);
76   if (!spaceless)
77     die("malloc(%zu) failed: %s", len + 1, strerror(errno));
78
79   for (p = spaceless, q = text, end = text + len;
80        q < end;
81        ++q) {
82     if (!isspace(*q))
83       *p++ = *q;
84   }
85   len = p - spaceless;
86   *p++ = '\0';
87
88   b = NULL;
89   rc = BN_hex2bn(&b, spaceless);
90
91   if (rc != (int)len)
92     die("BN_hex2bn did not convert entire input; took %d of %zu bytes",
93         rc, len);
94
95   return b;
96 }
97
98
99 static void
100 our_dh_check(DH *dh)
101 {
102   int rc, errflags = 0;
103
104   rc = DH_check(dh, &errflags);
105   if (!rc) die_openssl_err("DH_check() could not be performed");;
106
107   /* We ignore DH_UNABLE_TO_CHECK_GENERATOR because some of the invocations
108    * deliberately provide generators other than 2 or 5. */
109
110   if (errflags & DH_CHECK_P_NOT_SAFE_PRIME)
111     die("DH_check(): p not a safe prime");
112   if (errflags & DH_NOT_SUITABLE_GENERATOR)
113     die("DH_check(): g not suitable as generator");
114 }
115
116
117 static void
118 emit_c_format_dh(FILE *stream, DH *dh)
119 {
120   BIO *bio;
121   long length;
122   char *data, *end, *p, *nl;
123
124   bio = BIO_new(BIO_s_mem());
125   PEM_write_bio_DHparams(bio, dh);
126   length = BIO_get_mem_data(bio, &data);
127   if (!length)
128     die("no data in memory BIO to format for printing");
129   if (length < 0)
130     die("grr, negative length memory not supported");
131   end = data + length;
132
133   for (p = data; p < end; /**/) {
134     nl = strchr(p, '\n');
135     if (!nl) {
136       fprintf(stream, "\"%s\\n\"\n/* missing final newline */\n", p);
137       break;
138     }
139     *nl = '\0';
140     fprintf(stream, "\"%s\\n\"%s\n", p, (nl == end - 1 ? ";" : ""));
141     p = nl + 1;
142   }
143 }
144
145
146 void __attribute__((__noreturn__))
147 usage(FILE *stream, int exitcode)
148 {
149   fprintf(stream, "Usage: %s [-CPcst] <dh_p> <dh_g> [<dh_q>]\n"
150 "Both dh_p and dh_g should be hex strings representing the numbers\n"
151 "The same applies to the optional dh_q (prime-order subgroup).\n"
152 "They may contain whitespace.\n"
153 "Older values, dh_g is often just '2', not a long string.\n"
154 "\n"
155 " -C      show C string form of PEM result\n"
156 " -P      do not show PEM\n"
157 " -c      run OpenSSL DH_check() on the DH object\n"
158 " -s      show the parsed p and g\n"
159 " -t      show text form of certificate\n"
160
161       , __progname);
162   exit(exitcode);
163 }
164
165
166 int
167 main(int argc, char *argv[])
168 {
169   BIGNUM *p, *g, *q;
170   DH *dh;
171   int ch;
172   bool perform_dh_check = false;
173   bool show_c_form = false;
174   bool show_numbers = false;
175   bool show_pem = true;
176   bool show_text = false;
177   bool given_q = false;
178
179   while ((ch = getopt(argc, argv, "CPcsth")) != -1) {
180     switch (ch) {
181       case 'C':
182         show_c_form = true;
183         break;
184       case 'P':
185         show_pem = false;
186         break;
187       case 'c':
188         perform_dh_check = true;
189         break;
190       case 's':
191         show_numbers = true;
192         break;
193       case 't':
194         show_text = true;
195         break;
196
197       case 'h':
198         usage(stdout, 0);
199       case '?':
200         die("Unknown option or missing argument -%c", optopt);
201       default:
202         die("Unhandled option -%c", ch);
203     }
204   }
205
206   optind -= 1;
207   argc -= optind;
208   argv += optind;
209
210   if ((argc < 3) || (argc > 4)) {
211     fprintf(stderr, "argc: %d\n", argc);
212     usage(stderr, 1);
213   }
214
215   // If we use DH_set0_pqg instead of setting dh fields directly; the q value
216   // is optional and may be NULL.
217   // Just blank them all.
218   p = g = q = NULL;
219
220   p = bn_from_text(argv[1]);
221   g = bn_from_text(argv[2]);
222   if (argc >= 4) {
223     q = bn_from_text(argv[3]);
224     given_q = true;
225   }
226
227   if (show_numbers) {
228     printf("p = ");
229     BN_print_fp(stdout, p);
230     printf("\ng = ");
231     BN_print_fp(stdout, g);
232     if (given_q) {
233       printf("\nq = ");
234       BN_print_fp(stdout, q);
235     }
236     printf("\n");
237   }
238
239   dh = DH_new();
240   // The documented method for setting q appeared in OpenSSL 1.1.0.
241 #if OPENSSL_VERSION_NUMBER >= 0x1010000f
242   // NULL okay for q; yes, the optional value is in the middle.
243   if (DH_set0_pqg(dh, p, q, g) != 1) {
244     die_openssl_err("initialising DH pqg values failed");
245   }
246 #else
247   dh->p = p;
248   dh->g = g;
249   if (given_q) {
250     dh->q = q;
251   }
252 #endif
253
254   if (perform_dh_check)
255     our_dh_check(dh);
256
257   if (show_text)
258     DHparams_print_fp(stdout, dh);
259
260   if (show_pem) {
261     if (show_c_form)
262       emit_c_format_dh(stdout, dh);
263     else
264       PEM_write_DHparams(stdout, dh);
265   }
266
267   DH_free(dh); /* should free p,g (& q if non-NULL) too */
268   return 0;
269 }