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