1 /* $NetBSD: tls_dh.c,v 1.6 2025/02/25 19:15:50 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 <been_here.h>
81 #include <mail_params.h>
82
83 /* TLS library. */
84
85 #define TLS_INTERNAL
86 #include <tls.h>
87 #include <openssl/dh.h>
88 #ifndef OPENSSL_NO_ECDH
89 #include <openssl/ec.h>
90 #endif
91 #if OPENSSL_VERSION_PREREQ(3,0)
92 #include <openssl/decoder.h>
93 #endif
94
95 /* Application-specific. */
96
97 /*
98 * Compiled-in FFDHE (finite-field ephemeral Diffie-Hellman) parameters.
99 * Used when no parameters are explicitly loaded from a site-specific file.
100 *
101 * With OpenSSL 3.0 and later when no explicit parameter file is specified by
102 * the administrator (or the setting is "auto"), we delegate group selection
103 * to OpenSSL via SSL_CTX_set_dh_auto(3).
104 *
105 * Using an ASN.1 DER encoding avoids the need to explicitly manipulate the
106 * internal representation of DH parameter objects.
107 *
108 * The FFDHE group is now 2048-bit, as 1024 bits is increasingly considered to
109 * weak by clients. When greater security is required, use EECDH.
110 */
111
112 /*-
113 * Generated via:
114 * $ openssl dhparam -2 -outform DER 2048 2>/dev/null |
115 * hexdump -ve '/1 "0x%02x, "' | fmt -73
116 * TODO: generate at compile-time. But that is no good for the majority of
117 * sites that install pre-compiled binaries, and breaks reproducible builds.
118 * Instead, generate at installation time and use main.cf configuration.
119 */
120 static unsigned char builtin_der[] = {
121 0x30, 0x82, 0x01, 0x08, 0x02, 0x82, 0x01, 0x01, 0x00, 0xec, 0x02, 0x7b,
122 0x74, 0xc6, 0xd4, 0xb4, 0x89, 0x68, 0xfd, 0xbc, 0xe0, 0x82, 0xae, 0xd6,
123 0xf1, 0x4d, 0x93, 0xaa, 0x47, 0x07, 0x84, 0x3d, 0x86, 0xf8, 0x47, 0xf7,
124 0xdf, 0x08, 0x7b, 0xca, 0x04, 0xa4, 0x72, 0xec, 0x11, 0xe2, 0x38, 0x43,
125 0xb7, 0x94, 0xab, 0xaf, 0xe2, 0x85, 0x59, 0x43, 0x4e, 0x71, 0x85, 0xfe,
126 0x52, 0x0c, 0xe0, 0x1c, 0xb6, 0xc7, 0xb0, 0x1b, 0x06, 0xb3, 0x4d, 0x1b,
127 0x4f, 0xf6, 0x4b, 0x45, 0xbd, 0x1d, 0xb8, 0xe4, 0xa4, 0x48, 0x09, 0x28,
128 0x19, 0xd7, 0xce, 0xb1, 0xe5, 0x9a, 0xc4, 0x94, 0x55, 0xde, 0x4d, 0x86,
129 0x0f, 0x4c, 0x5e, 0x25, 0x51, 0x6c, 0x96, 0xca, 0xfa, 0xe3, 0x01, 0x69,
130 0x82, 0x6c, 0x8f, 0xf5, 0xe7, 0x0e, 0xb7, 0x8e, 0x52, 0xf1, 0xcf, 0x0b,
131 0x67, 0x10, 0xd0, 0xb3, 0x77, 0x79, 0xa4, 0xc1, 0xd0, 0x0f, 0x3f, 0xf5,
132 0x5c, 0x35, 0xf9, 0x46, 0xd2, 0xc7, 0xfb, 0x97, 0x6d, 0xd5, 0xbe, 0xe4,
133 0x8b, 0x5a, 0xf2, 0x88, 0xfa, 0x47, 0xdc, 0xc2, 0x4a, 0x4d, 0x69, 0xd3,
134 0x2a, 0xdf, 0x55, 0x6c, 0x5f, 0x71, 0x11, 0x1e, 0x87, 0x03, 0x68, 0xe1,
135 0xf4, 0x21, 0x06, 0x63, 0xd9, 0x65, 0xd4, 0x0c, 0x4d, 0xa7, 0x1f, 0x15,
136 0x53, 0x3a, 0x50, 0x1a, 0xf5, 0x9b, 0x50, 0x35, 0xe0, 0x16, 0xa1, 0xd7,
137 0xe6, 0xbf, 0xd7, 0xd9, 0xd9, 0x53, 0xe5, 0x8b, 0xf8, 0x7b, 0x45, 0x46,
138 0xb6, 0xac, 0x50, 0x16, 0x46, 0x42, 0xca, 0x76, 0x38, 0x4b, 0x8e, 0x83,
139 0xc6, 0x73, 0x13, 0x9c, 0x03, 0xd1, 0x7a, 0x3d, 0x8d, 0x99, 0x34, 0x10,
140 0x79, 0x67, 0x21, 0x23, 0xf9, 0x6f, 0x48, 0x9a, 0xa6, 0xde, 0xbf, 0x7f,
141 0x9c, 0x16, 0x53, 0xff, 0xf7, 0x20, 0x96, 0xeb, 0x34, 0xcb, 0x5b, 0x85,
142 0x2b, 0x7c, 0x98, 0x00, 0x23, 0x47, 0xce, 0xc2, 0x58, 0x12, 0x86, 0x2c,
143 0x57, 0x02, 0x01, 0x02,
144 };
145
146 #if OPENSSL_VERSION_PREREQ(3,0)
147
148 /* ------------------------------------- 3.0 API */
149
150 static EVP_PKEY *dhp = 0;
151
152 /* load_builtin - load compile-time FFDHE group */
153
load_builtin(void)154 static void load_builtin(void)
155 {
156 EVP_PKEY *tmp = 0;
157 OSSL_DECODER_CTX *d;
158 const unsigned char *endp = builtin_der;
159 size_t dlen = sizeof(builtin_der);
160
161 d = OSSL_DECODER_CTX_new_for_pkey(&tmp, "DER", NULL, "DH",
162 OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS,
163 NULL, NULL);
164 /* Check decode succeeds and consumes all data (final dlen == 0) */
165 if (d && OSSL_DECODER_from_data(d, &endp, &dlen) && tmp && !dlen) {
166 dhp = tmp;
167 } else {
168 EVP_PKEY_free(tmp);
169 msg_warn("error loading compiled-in DH parameters");
170 tls_print_errors();
171 }
172 OSSL_DECODER_CTX_free(d);
173 }
174
175 /* tls_set_dh_from_file - set Diffie-Hellman parameters from file */
176
tls_set_dh_from_file(const char * path)177 void tls_set_dh_from_file(const char *path)
178 {
179 FILE *fp;
180 EVP_PKEY *tmp = 0;
181 OSSL_DECODER_CTX *d;
182
183 /*
184 * This function is the first to set the DH parameters, but free any
185 * prior value just in case the call sequence changes some day.
186 */
187 if (dhp) {
188 EVP_PKEY_free(dhp);
189 dhp = 0;
190 }
191 if (strcmp(path, "auto") == 0)
192 return;
193
194 if ((fp = fopen(path, "r")) == 0) {
195 msg_warn("error opening DH parameter file \"%s\": %m"
196 " -- using compiled-in defaults", path);
197 return;
198 }
199 d = OSSL_DECODER_CTX_new_for_pkey(&tmp, "PEM", NULL, "DH",
200 OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS,
201 NULL, NULL);
202 if (!d || !OSSL_DECODER_from_fp(d, fp) || !tmp) {
203 msg_warn("error decoding DH parameters from file \"%s\""
204 " -- using compiled-in defaults", path);
205 tls_print_errors();
206 } else {
207 dhp = tmp;
208 }
209 OSSL_DECODER_CTX_free(d);
210 (void) fclose(fp);
211 }
212
213 /* tls_tmp_dh - configure FFDHE group */
214
tls_tmp_dh(SSL_CTX * ctx,int useauto)215 void tls_tmp_dh(SSL_CTX *ctx, int useauto)
216 {
217 if (!dhp && !useauto)
218 load_builtin();
219 if (!ctx)
220 return;
221 if (dhp) {
222 EVP_PKEY *tmp = EVP_PKEY_dup(dhp);
223
224 if (tmp && SSL_CTX_set0_tmp_dh_pkey(ctx, tmp) > 0)
225 return;
226 EVP_PKEY_free(tmp);
227 msg_warn("error configuring explicit DH parameters");
228 tls_print_errors();
229 } else {
230 if (SSL_CTX_set_dh_auto(ctx, 1) > 0)
231 return;
232 msg_warn("error configuring auto DH parameters");
233 tls_print_errors();
234 }
235 }
236
237 #else /* OPENSSL_VERSION_PREREQ(3,0) */
238
239 /* ------------------------------------- 1.1.1 API */
240
241 static DH *dhp = 0;
242
load_builtin(void)243 static void load_builtin(void)
244 {
245 DH *tmp = 0;
246 const unsigned char *endp = builtin_der;
247
248 if (d2i_DHparams(&tmp, &endp, sizeof(builtin_der))
249 && sizeof(builtin_der) == endp - builtin_der) {
250 dhp = tmp;
251 } else {
252 DH_free(tmp);
253 msg_warn("error loading compiled-in DH parameters");
254 tls_print_errors();
255 }
256 }
257
258 /* tls_set_dh_from_file - set Diffie-Hellman parameters from file */
259
tls_set_dh_from_file(const char * path)260 void tls_set_dh_from_file(const char *path)
261 {
262 FILE *fp;
263
264 /*
265 * This function is the first to set the DH parameters, but free any
266 * prior value just in case the call sequence changes some day.
267 */
268 if (dhp) {
269 DH_free(dhp);
270 dhp = 0;
271 }
272
273 /*
274 * Forwards compatibility, support "auto" by using the builtin group when
275 * OpenSSL is < 3.0 and does not support automatic FFDHE group selection.
276 */
277 if (strcmp(path, "auto") == 0)
278 return;
279
280 if ((fp = fopen(path, "r")) == 0) {
281 msg_warn("cannot load DH parameters from file %s: %m"
282 " -- using compiled-in defaults", path);
283 return;
284 }
285 if ((dhp = PEM_read_DHparams(fp, 0, 0, 0)) == 0) {
286 msg_warn("cannot load DH parameters from file %s"
287 " -- using compiled-in defaults", path);
288 tls_print_errors();
289 }
290 (void) fclose(fp);
291 }
292
293 /* tls_tmp_dh - configure FFDHE group */
294
tls_tmp_dh(SSL_CTX * ctx,int useauto)295 void tls_tmp_dh(SSL_CTX *ctx, int useauto)
296 {
297 if (!dhp)
298 load_builtin();
299 if (!ctx || !dhp || SSL_CTX_set_tmp_dh(ctx, dhp) > 0)
300 return;
301 msg_warn("error configuring explicit DH parameters");
302 tls_print_errors();
303 }
304
305 #endif /* OPENSSL_VERSION_PREREQ(3,0) */
306
307 /* ------------------------------------- Common API */
308
309 #define AG_STAT_OK (0)
310 #define AG_STAT_NO_GROUP (-1) /* no usable group, may retry */
311 #define AG_STAT_NO_RETRY (-2) /* other error, don't retry */
312
setup_auto_groups(SSL_CTX * ctx,const char * origin,const char * eecdh,const char * ffdhe)313 static int setup_auto_groups(SSL_CTX *ctx, const char *origin,
314 const char *eecdh,
315 const char *ffdhe)
316 {
317 #ifndef OPENSSL_NO_ECDH
318 SSL_CTX *tmpctx;
319 BH_TABLE *seen;
320 char *save;
321 char *groups;
322 char *group;
323 static VSTRING *names;
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 if (!names)
331 names = vstring_alloc(sizeof DEF_TLS_EECDH_AUTO +
332 sizeof DEF_TLS_FFDHE_AUTO);
333 VSTRING_RESET(names);
334
335 /*
336 * OpenSSL does not tolerate duplicate groups in the requested list.
337 * Deduplicate case-insensitively, just in case OpenSSL some day supports
338 * case-insensitive group lookup. Deduplicate only verified extant
339 * groups we're going to ask OpenSSL to use.
340 *
341 * OpenSSL 3.3 supports "?<name>" as a syntax for optionally ignoring
342 * unsupported groups, so we could skip checking against the throw-away
343 * CTX when linked against 3.3 or higher, but the cost savings don't
344 * justify the #ifdef overhead for now.
345 */
346 seen = been_here_init(0, BH_FLAG_FOLD);
347
348 #define GROUPS_SEP CHARS_COMMA_SP ":"
349 #define SETUP_AG_RETURN(val) do { \
350 been_here_free(seen); \
351 myfree(save); \
352 SSL_CTX_free(tmpctx); \
353 return (val); \
354 } while (0)
355
356 groups = save = concatenate(eecdh, " ", ffdhe, NULL);
357 if ((group = mystrtok(&groups, GROUPS_SEP)) == 0) {
358 msg_warn("no %s key exchange group - OpenSSL requires at least one",
359 origin);
360 SETUP_AG_RETURN(AG_STAT_NO_GROUP);
361 }
362 for (; group != 0; group = mystrtok(&groups, GROUPS_SEP)) {
363
364 /*
365 * Validate the group name by trying it as the group for a throw-away
366 * SSL context. This way, we can ask for new groups that may not yet
367 * be supported by the underlying OpenSSL runtime. Unsupported
368 * groups are silently ignored.
369 */
370 ERR_set_mark();
371 if (SSL_CTX_set1_curves_list(tmpctx, group) > 0 &&
372 !been_here_fixed(seen, group)) {
373 if (VSTRING_LEN(names) > 0)
374 VSTRING_ADDCH(names, ':');
375 vstring_strcat(names, group);
376 }
377 ERR_pop_to_mark();
378 }
379
380 if (VSTRING_LEN(names) == 0) {
381 /* The names may be case-sensitive */
382 msg_warn("none of the %s key exchange groups are supported", origin);
383 SETUP_AG_RETURN(AG_STAT_NO_GROUP);
384 }
385 VSTRING_TERMINATE(names);
386
387 if (SSL_CTX_set1_curves_list(ctx, vstring_str(names)) <= 0) {
388 msg_warn("failed to set up the %s key exchange groups", origin);
389 tls_print_errors();
390 SETUP_AG_RETURN(AG_STAT_NO_RETRY);
391 }
392 SETUP_AG_RETURN(AG_STAT_OK);
393 #endif
394 }
395
tls_auto_groups(SSL_CTX * ctx,const char * eecdh,const char * ffdhe)396 void tls_auto_groups(SSL_CTX *ctx, const char *eecdh, const char *ffdhe)
397 {
398 char *def_eecdh = DEF_TLS_EECDH_AUTO;
399
400 #if OPENSSL_VERSION_PREREQ(3, 0)
401 char *def_ffdhe = DEF_TLS_FFDHE_AUTO;
402
403 #else
404 char *def_ffdhe = "";
405
406 /* Has no effect prior to OpenSSL 3.0 */
407 ffdhe = def_ffdhe;
408 #endif
409 const char *origin;
410
411 /* Use OpenSSL defaults */
412 if (!*eecdh && !*ffdhe)
413 return;
414
415 /*
416 * Try the user-specified list first. If that fails (empty list or no
417 * known group name), try again with the Postfix defaults. We assume that
418 * group selection is mere performance tuning and not security critical.
419 * All the groups supported for negotiation should be strong enough.
420 */
421 for (origin = "configured"; /* void */ ; /* void */ ) {
422 switch (setup_auto_groups(ctx, origin, eecdh, ffdhe)) {
423 case AG_STAT_OK:
424 return;
425 case AG_STAT_NO_GROUP:
426 if (strcmp(eecdh, def_eecdh) != 0
427 || strcmp(ffdhe, def_ffdhe) != 0) {
428 msg_warn("using Postfix default key exchange groups instead");
429 origin = "Postfix default";
430 eecdh = def_eecdh;
431 ffdhe = def_ffdhe;
432 break;
433 }
434 /* FALLTHROUGH */
435 default:
436 msg_warn("using OpenSSL default key exchange groups instead");
437 return;
438 }
439 }
440 }
441
442 #ifdef TEST
443
main(int unused_argc,char ** unused_argv)444 int main(int unused_argc, char **unused_argv)
445 {
446 tls_tmp_dh(0, 0);
447 return (dhp == 0);
448 }
449
450 #endif
451
452 #endif
453