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