Commit | Line | Data |
---|---|---|
317e40ac | 1 | /* Copyright (C) 2012,2016 Phil Pennock. |
a799883d PP |
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 | ||
c3eacdb0 PP |
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 | ||
a799883d PP |
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) | |
317e40ac | 89 | die("BN_hex2bn did not convert entire input; took %d of %zu bytes", |
a799883d PP |
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'; | |
317e40ac | 137 | fprintf(stream, "\"%s\\n\"%s\n", p, (nl == end - 1 ? ";" : "")); |
a799883d PP |
138 | p = nl + 1; |
139 | } | |
140 | } | |
141 | ||
142 | ||
143 | void __attribute__((__noreturn__)) | |
144 | usage(FILE *stream, int exitcode) | |
145 | { | |
317e40ac | 146 | fprintf(stream, "Usage: %s [-CPcst] <dh_p> <dh_g> [<dh_q>]\n" |
a799883d | 147 | "Both dh_p and dh_g should be hex strings representing the numbers\n" |
317e40ac | 148 | "The same applies to the optional dh_q (prime-order subgroup).\n" |
a799883d | 149 | "They may contain whitespace.\n" |
317e40ac | 150 | "Older values, dh_g is often just '2', not a long string.\n" |
a799883d PP |
151 | "\n" |
152 | " -C show C string form of PEM result\n" | |
153 | " -P do not show PEM\n" | |
154 | " -c run OpenSSL DH_check() on the DH object\n" | |
155 | " -s show the parsed p and g\n" | |
156 | " -t show text form of certificate\n" | |
157 | ||
158 | , __progname); | |
159 | exit(exitcode); | |
160 | } | |
161 | ||
162 | ||
163 | int | |
164 | main(int argc, char *argv[]) | |
165 | { | |
317e40ac | 166 | BIGNUM *p, *g, *q; |
a799883d PP |
167 | DH *dh; |
168 | int ch; | |
169 | bool perform_dh_check = false; | |
170 | bool show_c_form = false; | |
171 | bool show_numbers = false; | |
172 | bool show_pem = true; | |
173 | bool show_text = false; | |
317e40ac | 174 | bool given_q = false; |
a799883d PP |
175 | |
176 | while ((ch = getopt(argc, argv, "CPcsth")) != -1) { | |
177 | switch (ch) { | |
178 | case 'C': | |
179 | show_c_form = true; | |
180 | break; | |
181 | case 'P': | |
182 | show_pem = false; | |
183 | break; | |
184 | case 'c': | |
185 | perform_dh_check = true; | |
186 | break; | |
187 | case 's': | |
188 | show_numbers = true; | |
189 | break; | |
190 | case 't': | |
191 | show_text = true; | |
192 | break; | |
193 | ||
194 | case 'h': | |
195 | usage(stdout, 0); | |
196 | case '?': | |
197 | die("Unknown option or missing argument -%c", optopt); | |
198 | default: | |
199 | die("Unhandled option -%c", ch); | |
200 | } | |
201 | } | |
202 | ||
203 | optind -= 1; | |
204 | argc -= optind; | |
205 | argv += optind; | |
206 | ||
317e40ac | 207 | if ((argc < 3) || (argc > 4)) { |
a799883d PP |
208 | fprintf(stderr, "argc: %d\n", argc); |
209 | usage(stderr, 1); | |
210 | } | |
211 | ||
317e40ac PP |
212 | // If we use DH_set0_pqg instead of setting dh fields directly; the q value |
213 | // is optional and may be NULL. | |
214 | // Just blank them all. | |
215 | p = g = q = NULL; | |
216 | ||
a799883d PP |
217 | p = bn_from_text(argv[1]); |
218 | g = bn_from_text(argv[2]); | |
317e40ac PP |
219 | if (argc >= 4) { |
220 | q = bn_from_text(argv[3]); | |
221 | given_q = true; | |
222 | } | |
a799883d PP |
223 | |
224 | if (show_numbers) { | |
225 | printf("p = "); | |
226 | BN_print_fp(stdout, p); | |
227 | printf("\ng = "); | |
228 | BN_print_fp(stdout, g); | |
317e40ac PP |
229 | if (given_q) { |
230 | printf("\nq = "); | |
231 | BN_print_fp(stdout, q); | |
232 | } | |
a799883d PP |
233 | printf("\n"); |
234 | } | |
235 | ||
236 | dh = DH_new(); | |
317e40ac PP |
237 | // The documented method for setting q appeared in OpenSSL 1.1.0. |
238 | #if OPENSSL_VERSION_NUMBER >= 0x1010000f | |
239 | // NULL okay for q; yes, the optional value is in the middle. | |
240 | if (DH_set0_pqg(dh, p, q, g) != 1) { | |
241 | die_openssl_err("initialising DH pqg values failed"); | |
242 | } | |
243 | #else | |
a799883d PP |
244 | dh->p = p; |
245 | dh->g = g; | |
317e40ac PP |
246 | if (given_q) { |
247 | dh->q = q; | |
248 | } | |
249 | #endif | |
a799883d PP |
250 | |
251 | if (perform_dh_check) | |
252 | our_dh_check(dh); | |
253 | ||
254 | if (show_text) | |
255 | DHparams_print_fp(stdout, dh); | |
256 | ||
257 | if (show_pem) { | |
258 | if (show_c_form) | |
259 | emit_c_format_dh(stdout, dh); | |
260 | else | |
261 | PEM_write_DHparams(stdout, dh); | |
262 | } | |
263 | ||
317e40ac | 264 | DH_free(dh); /* should free p,g (& q if non-NULL) too */ |
a799883d PP |
265 | return 0; |
266 | } |