xref: /netbsd-src/external/ibm-public/postfix/dist/src/tls/tls_dh.c (revision c48c605c14fd8622b523d1d6a3f0c0bad133ea89)
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