gen_pkcs3: add comment explaining rationale
[exim.git] / src / util / gen_pkcs3.c
1 /* Copyright (C) 2012 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   unsigned long e;
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);
75   if (!spaceless)
76     die("malloc(%zu) failed: %s", len, 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
85   b = NULL;
86   rc = BN_hex2bn(&b, spaceless);
87
88   if (rc != p - spaceless)
89     die("BN_hex2bn did not convert entire input; took %d of %z bytes",
90         rc, p - spaceless);
91
92   return b;
93 }
94
95
96 static void
97 our_dh_check(DH *dh)
98 {
99   int rc, errflags = 0;
100
101   rc = DH_check(dh, &errflags);
102   if (!rc) die_openssl_err("DH_check() could not be performed");;
103
104   /* We ignore DH_UNABLE_TO_CHECK_GENERATOR because some of the invocations
105    * deliberately provide generators other than 2 or 5. */
106
107   if (errflags & DH_CHECK_P_NOT_SAFE_PRIME)
108     die("DH_check(): p not a safe prime");
109   if (errflags & DH_NOT_SUITABLE_GENERATOR)
110     die("DH_check(): g not suitable as generator");
111 }
112
113
114 static void
115 emit_c_format_dh(FILE *stream, DH *dh)
116 {
117   BIO *bio;
118   long length;
119   char *data, *end, *p, *nl;
120
121   bio = BIO_new(BIO_s_mem());
122   PEM_write_bio_DHparams(bio, dh);
123   length = BIO_get_mem_data(bio, &data);
124   if (!length)
125     die("no data in memory BIO to format for printing");
126   if (length < 0)
127     die("grr, negative length memory not supported");
128   end = data + length;
129
130   for (p = data; p < end; /**/) {
131     nl = strchr(p, '\n');
132     if (!nl) {
133       fprintf(stream, "\"%s\\n\"\n/* missing final newline */\n", p);
134       break;
135     }
136     *nl = '\0';
137     fprintf(stream, "\"%s\\n\"\n", p);
138     p = nl + 1;
139   }
140 }
141
142
143 void __attribute__((__noreturn__))
144 usage(FILE *stream, int exitcode)
145 {
146   fprintf(stream, "Usage: %s [-CPcst] <dh_p> <dh_g>\n"
147 "Both dh_p and dh_g should be hex strings representing the numbers\n"
148 "They may contain whitespace.\n"
149 "\n"
150 " -C      show C string form of PEM result\n"
151 " -P      do not show PEM\n"
152 " -c      run OpenSSL DH_check() on the DH object\n"
153 " -s      show the parsed p and g\n"
154 " -t      show text form of certificate\n"
155
156       , __progname);
157   exit(exitcode);
158 }
159
160
161 int
162 main(int argc, char *argv[])
163 {
164   BIGNUM *p, *g;
165   DH *dh;
166   int ch;
167   bool perform_dh_check = false;
168   bool show_c_form = false;
169   bool show_numbers = false;
170   bool show_pem = true;
171   bool show_text = false;
172
173   while ((ch = getopt(argc, argv, "CPcsth")) != -1) {
174     switch (ch) {
175       case 'C':
176         show_c_form = true;
177         break;
178       case 'P':
179         show_pem = false;
180         break;
181       case 'c':
182         perform_dh_check = true;
183         break;
184       case 's':
185         show_numbers = true;
186         break;
187       case 't':
188         show_text = true;
189         break;
190
191       case 'h':
192         usage(stdout, 0);
193       case '?':
194         die("Unknown option or missing argument -%c", optopt);
195       default:
196         die("Unhandled option -%c", ch);
197     }
198   }
199
200   optind -= 1;
201   argc -= optind;
202   argv += optind;
203
204   if (argc != 3) {
205     fprintf(stderr, "argc: %d\n", argc);
206     usage(stderr, 1);
207   }
208
209   p = bn_from_text(argv[1]);
210   g = bn_from_text(argv[2]);
211
212   if (show_numbers) {
213     printf("p = ");
214     BN_print_fp(stdout, p);
215     printf("\ng = ");
216     BN_print_fp(stdout, g);
217     printf("\n");
218   }
219
220   dh = DH_new();
221   dh->p = p;
222   dh->g = g;
223
224   if (perform_dh_check)
225     our_dh_check(dh);
226
227   if (show_text)
228     DHparams_print_fp(stdout, dh);
229
230   if (show_pem) {
231     if (show_c_form)
232       emit_c_format_dh(stdout, dh);
233     else
234       PEM_write_DHparams(stdout, dh);
235   }
236
237   DH_free(dh); /* should free p & g too */
238   return 0;
239 }