Home | History | Annotate | Line # | Download | only in tls
      1 /*	$NetBSD: tls_dh.c,v 1.7 2026/05/09 18:49:21 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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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