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