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