1 /* $NetBSD: tls_dh.c,v 1.5 2023/12/23 20:30:45 christos Exp $ */
2
3 /*++
4 /* NAME
5 /* tls_dh
6 /* SUMMARY
7 /* Diffie-Hellman parameter support
8 /* SYNOPSIS
9 /* #define TLS_INTERNAL
10 /* #include <tls.h>
11 /*
12 /* void tls_set_dh_from_file(path)
13 /* const char *path;
14 /*
15 /* void tls_auto_groups(ctx, eecdh, ffdhe)
16 /* SSL_CTX *ctx;
17 /* char *eecdh;
18 /* char *ffdhe;
19 /*
20 /* void tls_tmp_dh(ctx, useauto)
21 /* SSL_CTX *ctx;
22 /* int useauto;
23 /* DESCRIPTION
24 /* This module maintains parameters for Diffie-Hellman key generation.
25 /*
26 /* tls_tmp_dh() returns the configured or compiled-in FFDHE
27 /* group parameters. The useauto argument enables OpenSSL-builtin group
28 /* selection in preference to our own compiled-in group. This may
29 /* interoperate better with overly strict peers that accept only
30 /* "standard" groups.
31 /*
32 /* tls_set_dh_from_file() overrides compiled-in DH parameters
33 /* with those specified in the named files. The file format
34 /* is as expected by the PEM_read_DHparams() routine.
35 /*
36 /* tls_auto_groups() enables negotiation of the most preferred key
37 /* exchange group among those specified by the "eecdh" and "ffdhe"
38 /* arguments. The "ffdhe" argument is only used with OpenSSL 3.0
39 /* and later, and applies to TLS 1.3 and up.
40 /* DIAGNOSTICS
41 /* In case of error, tls_set_dh_from_file() logs a warning and
42 /* ignores the request.
43 /* LICENSE
44 /* .ad
45 /* .fi
46 /* This software is free. You can do with it whatever you want.
47 /* The original author kindly requests that you acknowledge
48 /* the use of his software.
49 /* AUTHOR(S)
50 /* Originally written by:
51 /* Lutz Jaenicke
52 /* BTU Cottbus
53 /* Allgemeine Elektrotechnik
54 /* Universitaetsplatz 3-4
55 /* D-03044 Cottbus, Germany
56 /*
57 /* Updated by:
58 /* Wietse Venema
59 /* IBM T.J. Watson Research
60 /* P.O. Box 704
61 /* Yorktown Heights, NY 10598, USA
62 /*--*/
63
64 /* System library. */
65
66 #include <sys_defs.h>
67
68 #ifdef USE_TLS
69 #include <stdio.h>
70
71 /* Utility library. */
72
73 #include <msg.h>
74 #include <mymalloc.h>
75 #include <stringops.h>
76
77 /*
78 * Global library
79 */
80 #include <mail_params.h>
81
82 /* TLS library. */
83
84 #define TLS_INTERNAL
85 #include <tls.h>
86 #include <openssl/dh.h>
87 #ifndef OPENSSL_NO_ECDH
88 #include <openssl/ec.h>
89 #endif
90 #if OPENSSL_VERSION_PREREQ(3,0)
91 #include <openssl/decoder.h>
92 #endif
93
94 /* Application-specific. */
95
96 /*
97 * Compiled-in FFDHE (finite-field ephemeral Diffie-Hellman) parameters.
98 * Used when no parameters are explicitly loaded from a site-specific file.
99 *
100 * With OpenSSL 3.0 and later when no explicit parameter file is specified by
101 * the administrator (or the setting is "auto"), we delegate group selection
102 * to OpenSSL via SSL_CTX_set_dh_auto(3).
103 *
104 * Using an ASN.1 DER encoding avoids the need to explicitly manipulate the
105 * internal representation of DH parameter objects.
106 *
107 * The FFDHE group is now 2048-bit, as 1024 bits is increasingly considered to
108 * weak by clients. When greater security is required, use EECDH.
109 */
110
111 /*-
112 * Generated via:
113 * $ openssl dhparam -2 -outform DER 2048 2>/dev/null |
114 * hexdump -ve '/1 "0x%02x, "' | fmt -73
115 * TODO: generate at compile-time. But that is no good for the majority of
116 * sites that install pre-compiled binaries, and breaks reproducible builds.
117 * Instead, generate at installation time and use main.cf configuration.
118 */
119 static unsigned char builtin_der[] = {
120 0x30, 0x82, 0x01, 0x08, 0x02, 0x82, 0x01, 0x01, 0x00, 0xec, 0x02, 0x7b,
121 0x74, 0xc6, 0xd4, 0xb4, 0x89, 0x68, 0xfd, 0xbc, 0xe0, 0x82, 0xae, 0xd6,
122 0xf1, 0x4d, 0x93, 0xaa, 0x47, 0x07, 0x84, 0x3d, 0x86, 0xf8, 0x47, 0xf7,
123 0xdf, 0x08, 0x7b, 0xca, 0x04, 0xa4, 0x72, 0xec, 0x11, 0xe2, 0x38, 0x43,
124 0xb7, 0x94, 0xab, 0xaf, 0xe2, 0x85, 0x59, 0x43, 0x4e, 0x71, 0x85, 0xfe,
125 0x52, 0x0c, 0xe0, 0x1c, 0xb6, 0xc7, 0xb0, 0x1b, 0x06, 0xb3, 0x4d, 0x1b,
126 0x4f, 0xf6, 0x4b, 0x45, 0xbd, 0x1d, 0xb8, 0xe4, 0xa4, 0x48, 0x09, 0x28,
127 0x19, 0xd7, 0xce, 0xb1, 0xe5, 0x9a, 0xc4, 0x94, 0x55, 0xde, 0x4d, 0x86,
128 0x0f, 0x4c, 0x5e, 0x25, 0x51, 0x6c, 0x96, 0xca, 0xfa, 0xe3, 0x01, 0x69,
129 0x82, 0x6c, 0x8f, 0xf5, 0xe7, 0x0e, 0xb7, 0x8e, 0x52, 0xf1, 0xcf, 0x0b,
130 0x67, 0x10, 0xd0, 0xb3, 0x77, 0x79, 0xa4, 0xc1, 0xd0, 0x0f, 0x3f, 0xf5,
131 0x5c, 0x35, 0xf9, 0x46, 0xd2, 0xc7, 0xfb, 0x97, 0x6d, 0xd5, 0xbe, 0xe4,
132 0x8b, 0x5a, 0xf2, 0x88, 0xfa, 0x47, 0xdc, 0xc2, 0x4a, 0x4d, 0x69, 0xd3,
133 0x2a, 0xdf, 0x55, 0x6c, 0x5f, 0x71, 0x11, 0x1e, 0x87, 0x03, 0x68, 0xe1,
134 0xf4, 0x21, 0x06, 0x63, 0xd9, 0x65, 0xd4, 0x0c, 0x4d, 0xa7, 0x1f, 0x15,
135 0x53, 0x3a, 0x50, 0x1a, 0xf5, 0x9b, 0x50, 0x35, 0xe0, 0x16, 0xa1, 0xd7,
136 0xe6, 0xbf, 0xd7, 0xd9, 0xd9, 0x53, 0xe5, 0x8b, 0xf8, 0x7b, 0x45, 0x46,
137 0xb6, 0xac, 0x50, 0x16, 0x46, 0x42, 0xca, 0x76, 0x38, 0x4b, 0x8e, 0x83,
138 0xc6, 0x73, 0x13, 0x9c, 0x03, 0xd1, 0x7a, 0x3d, 0x8d, 0x99, 0x34, 0x10,
139 0x79, 0x67, 0x21, 0x23, 0xf9, 0x6f, 0x48, 0x9a, 0xa6, 0xde, 0xbf, 0x7f,
140 0x9c, 0x16, 0x53, 0xff, 0xf7, 0x20, 0x96, 0xeb, 0x34, 0xcb, 0x5b, 0x85,
141 0x2b, 0x7c, 0x98, 0x00, 0x23, 0x47, 0xce, 0xc2, 0x58, 0x12, 0x86, 0x2c,
142 0x57, 0x02, 0x01, 0x02,
143 };
144
145 #if OPENSSL_VERSION_PREREQ(3,0)
146
147 /* ------------------------------------- 3.0 API */
148
149 static EVP_PKEY *dhp = 0;
150
151 /* load_builtin - load compile-time FFDHE group */
152
load_builtin(void)153 static void load_builtin(void)
154 {
155 EVP_PKEY *tmp = 0;
156 OSSL_DECODER_CTX *d;
157 const unsigned char *endp = builtin_der;
158 size_t dlen = sizeof(builtin_der);
159
160 d = OSSL_DECODER_CTX_new_for_pkey(&tmp, "DER", NULL, "DH",
161 OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS,
162 NULL, NULL);
163 /* Check decode succeeds and consumes all data (final dlen == 0) */
164 if (d && OSSL_DECODER_from_data(d, &endp, &dlen) && tmp && !dlen) {
165 dhp = tmp;
166 } else {
167 EVP_PKEY_free(tmp);
168 msg_warn("error loading compiled-in DH parameters");
169 tls_print_errors();
170 }
171 OSSL_DECODER_CTX_free(d);
172 }
173
174 /* tls_set_dh_from_file - set Diffie-Hellman parameters from file */
175
tls_set_dh_from_file(const char * path)176 void tls_set_dh_from_file(const char *path)
177 {
178 FILE *fp;
179 EVP_PKEY *tmp = 0;
180 OSSL_DECODER_CTX *d;
181
182 /*
183 * This function is the first to set the DH parameters, but free any
184 * prior value just in case the call sequence changes some day.
185 */
186 if (dhp) {
187 EVP_PKEY_free(dhp);
188 dhp = 0;
189 }
190 if (strcmp(path, "auto") == 0)
191 return;
192
193 if ((fp = fopen(path, "r")) == 0) {
194 msg_warn("error opening DH parameter file \"%s\": %m"
195 " -- using compiled-in defaults", path);
196 return;
197 }
198 d = OSSL_DECODER_CTX_new_for_pkey(&tmp, "PEM", NULL, "DH",
199 OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS,
200 NULL, NULL);
201 if (!d || !OSSL_DECODER_from_fp(d, fp) || !tmp) {
202 msg_warn("error decoding DH parameters from file \"%s\""
203 " -- using compiled-in defaults", path);
204 tls_print_errors();
205 } else {
206 dhp = tmp;
207 }
208 OSSL_DECODER_CTX_free(d);
209 (void) fclose(fp);
210 }
211
212 /* tls_tmp_dh - configure FFDHE group */
213
tls_tmp_dh(SSL_CTX * ctx,int useauto)214 void tls_tmp_dh(SSL_CTX *ctx, int useauto)
215 {
216 if (!dhp && !useauto)
217 load_builtin();
218 if (!ctx)
219 return;
220 if (dhp) {
221 EVP_PKEY *tmp = EVP_PKEY_dup(dhp);
222
223 if (tmp && SSL_CTX_set0_tmp_dh_pkey(ctx, tmp) > 0)
224 return;
225 EVP_PKEY_free(tmp);
226 msg_warn("error configuring explicit DH parameters");
227 tls_print_errors();
228 } else {
229 if (SSL_CTX_set_dh_auto(ctx, 1) > 0)
230 return;
231 msg_warn("error configuring auto DH parameters");
232 tls_print_errors();
233 }
234 }
235
236 #else /* OPENSSL_VERSION_PREREQ(3,0) */
237
238 /* ------------------------------------- 1.1.1 API */
239
240 static DH *dhp = 0;
241
load_builtin(void)242 static void load_builtin(void)
243 {
244 DH *tmp = 0;
245 const unsigned char *endp = builtin_der;
246
247 if (d2i_DHparams(&tmp, &endp, sizeof(builtin_der))
248 && sizeof(builtin_der) == endp - builtin_der) {
249 dhp = tmp;
250 } else {
251 DH_free(tmp);
252 msg_warn("error loading compiled-in DH parameters");
253 tls_print_errors();
254 }
255 }
256
257 /* tls_set_dh_from_file - set Diffie-Hellman parameters from file */
258
tls_set_dh_from_file(const char * path)259 void tls_set_dh_from_file(const char *path)
260 {
261 FILE *fp;
262
263 /*
264 * This function is the first to set the DH parameters, but free any
265 * prior value just in case the call sequence changes some day.
266 */
267 if (dhp) {
268 DH_free(dhp);
269 dhp = 0;
270 }
271
272 /*
273 * Forwards compatibility, support "auto" by using the builtin group when
274 * OpenSSL is < 3.0 and does not support automatic FFDHE group selection.
275 */
276 if (strcmp(path, "auto") == 0)
277 return;
278
279 if ((fp = fopen(path, "r")) == 0) {
280 msg_warn("cannot load DH parameters from file %s: %m"
281 " -- using compiled-in defaults", path);
282 return;
283 }
284 if ((dhp = PEM_read_DHparams(fp, 0, 0, 0)) == 0) {
285 msg_warn("cannot load DH parameters from file %s"
286 " -- using compiled-in defaults", path);
287 tls_print_errors();
288 }
289 (void) fclose(fp);
290 }
291
292 /* tls_tmp_dh - configure FFDHE group */
293
tls_tmp_dh(SSL_CTX * ctx,int useauto)294 void tls_tmp_dh(SSL_CTX *ctx, int useauto)
295 {
296 if (!dhp)
297 load_builtin();
298 if (!ctx || !dhp || SSL_CTX_set_tmp_dh(ctx, dhp) > 0)
299 return;
300 msg_warn("error configuring explicit DH parameters");
301 tls_print_errors();
302 }
303
304 #endif /* OPENSSL_VERSION_PREREQ(3,0) */
305
306 /* ------------------------------------- Common API */
307
308 #define AG_STAT_OK (0)
309 #define AG_STAT_NO_GROUP (-1) /* no usable group, may retry */
310 #define AG_STAT_NO_RETRY (-2) /* other error, don't retry */
311
setup_auto_groups(SSL_CTX * ctx,const char * origin,const char * eecdh,const char * ffdhe)312 static int setup_auto_groups(SSL_CTX *ctx, const char *origin,
313 const char *eecdh,
314 const char *ffdhe)
315 {
316 #ifndef OPENSSL_NO_ECDH
317 SSL_CTX *tmpctx;
318 int *nids;
319 int space = 10;
320 int n = 0;
321 char *save;
322 char *groups;
323 char *group;
324
325 if ((tmpctx = SSL_CTX_new(TLS_method())) == 0) {
326 msg_warn("cannot allocate temp SSL_CTX");
327 tls_print_errors();
328 return (AG_STAT_NO_RETRY);
329 }
330 nids = mymalloc(space * sizeof(int));
331
332 #define SETUP_AG_RETURN(val) do { \
333 myfree(save); \
334 myfree(nids); \
335 SSL_CTX_free(tmpctx); \
336 return (val); \
337 } while (0)
338
339 groups = save = concatenate(eecdh, " ", ffdhe, NULL);
340 if ((group = mystrtok(&groups, CHARS_COMMA_SP)) == 0) {
341 msg_warn("no %s key exchange group - OpenSSL requires at least one",
342 origin);
343 SETUP_AG_RETURN(AG_STAT_NO_GROUP);
344 }
345 for (; group != 0; group = mystrtok(&groups, CHARS_COMMA_SP)) {
346 int nid = EC_curve_nist2nid(group);
347
348 if (nid == NID_undef)
349 nid = OBJ_sn2nid(group);
350 if (nid == NID_undef)
351 nid = OBJ_ln2nid(group);
352 if (nid == NID_undef) {
353 msg_warn("ignoring unknown key exchange group \"%s\"", group);
354 continue;
355 }
356
357 /*
358 * Validate the NID by trying it as the group for a throw-away SSL
359 * context. Silently skip unsupported code points. This way, we can
360 * list X25519 and X448 as soon as the nids are assigned, and before
361 * the supporting code is implemented. They'll be silently skipped
362 * when not yet supported.
363 */
364 if (SSL_CTX_set1_curves(tmpctx, &nid, 1) <= 0) {
365 continue;
366 }
367 if (++n > space) {
368 space *= 2;
369 nids = myrealloc(nids, space * sizeof(int));
370 }
371 nids[n - 1] = nid;
372 }
373
374 if (n == 0) {
375 /* The names may be case-sensitive */
376 msg_warn("none of the %s key exchange groups are supported", origin);
377 SETUP_AG_RETURN(AG_STAT_NO_GROUP);
378 }
379 if (SSL_CTX_set1_curves(ctx, nids, n) <= 0) {
380 msg_warn("failed to set up the %s key exchange groups", origin);
381 tls_print_errors();
382 SETUP_AG_RETURN(AG_STAT_NO_RETRY);
383 }
384 SETUP_AG_RETURN(AG_STAT_OK);
385 #endif
386 }
387
tls_auto_groups(SSL_CTX * ctx,const char * eecdh,const char * ffdhe)388 void tls_auto_groups(SSL_CTX *ctx, const char *eecdh, const char *ffdhe)
389 {
390 #ifndef OPENSSL_NO_ECDH
391 char *def_eecdh = DEF_TLS_EECDH_AUTO;
392
393 #if OPENSSL_VERSION_PREREQ(3, 0)
394 char *def_ffdhe = DEF_TLS_FFDHE_AUTO;
395
396 #else
397 char *def_ffdhe = "";
398
399 /* Has no effect prior to OpenSSL 3.0 */
400 ffdhe = def_ffdhe;
401 #endif
402 const char *origin;
403
404 /*
405 * Try the user-specified list first. If that fails (empty list or no
406 * known group name), try again with the Postfix defaults. We assume that
407 * group selection is mere performance tuning and not security critical.
408 * All the groups supported for negotiation should be strong enough.
409 */
410 for (origin = "configured"; /* void */ ; /* void */) {
411 switch (setup_auto_groups(ctx, origin, eecdh, ffdhe)) {
412 case AG_STAT_OK:
413 return;
414 case AG_STAT_NO_GROUP:
415 if (strcmp(eecdh, def_eecdh) != 0
416 || strcmp(ffdhe, def_ffdhe) != 0) {
417 msg_warn("using Postfix default key exchange groups instead");
418 origin = "Postfix default";
419 eecdh = def_eecdh;
420 ffdhe = def_ffdhe;
421 break;
422 }
423 /* FALLTHROUGH */
424 default:
425 msg_warn("using OpenSSL default key exchange groups instead");
426 return;
427 }
428 }
429 #endif
430 }
431
432 #ifdef TEST
433
main(int unused_argc,char ** unused_argv)434 int main(int unused_argc, char **unused_argv)
435 {
436 tls_tmp_dh(0, 0);
437 return (dhp == 0);
438 }
439
440 #endif
441
442 #endif
443