]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-3.12-20260406 master
authorWietse Z Venema <wietse@porcupine.org>
Mon, 6 Apr 2026 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <ietf-dane@dukhovni.org>
Mon, 6 Apr 2026 19:36:41 +0000 (05:36 +1000)
18 files changed:
postfix/HISTORY
postfix/README_FILES/TLS_README
postfix/html/TLS_README.html
postfix/html/postconf.5.html
postfix/man/man5/postconf.5
postfix/proto/TLS_README.html
postfix/proto/postconf.proto
postfix/proto/stop.double-history
postfix/proto/stop.double-proto-html
postfix/proto/stop.spell-proto-html
postfix/src/global/Makefile.in
postfix/src/global/dict_sqlite_test.c
postfix/src/global/hfrom_format_test.c
postfix/src/global/mail_version.h
postfix/src/tls/tls.h
postfix/src/tls/tls_client.c
postfix/src/tls/tls_misc.c
postfix/src/tls/tls_server.c

index 7562757c681dceb64a3f1a29c1d6ff2a8ce01e35..d99f00d0e89282574e7390df52991ccab22a800f 100644 (file)
@@ -30743,11 +30743,12 @@ Apologies for any names omitted.
        Cleanup: migrate the MySQL client from find_inet(3) to
        find_inet_service(3). Files: src/global/dict_mysql.c.
 
        Cleanup: migrate the MySQL client from find_inet(3) to
        find_inet_service(3). Files: src/global/dict_mysql.c.
 
-       Feature: new NAME_MASK_NULL flag. When is present, the
-       str_name_mask_opt() function will output a string "0" when
-       the input mask is empty. Files: util/name_mask.[hc].
+       Feature: new NAME_MASK_NULL flag. When this flag is set,
+       the str_name_mask_opt() function will output a string "0"
+       when the input mask is empty. Otherwise, the result is an
+       empty string. Files: util/name_mask.[hc].
 
 
-       Cleanup: to make dict_open() easier to test, with support
+       Cleanup: make dict_open() easier to test, with support
        for graceful degradation after dictionary type or name
        syntax errors. File: util/dict_open.c.
 
        for graceful degradation after dictionary type or name
        syntax errors. File: util/dict_open.c.
 
@@ -30804,6 +30805,27 @@ Apologies for any names omitted.
        smtpd/Makefile.in, smtpd/smtpd_check.c, util/dict_open.c,
        util/dynamicmaps.c, util/Makefile.in.
 
        smtpd/Makefile.in, smtpd/smtpd_check.c, util/dict_open.c,
        util/dynamicmaps.c, util/Makefile.in.
 
+20260404
+
+       Refactored SNI support and eliminated some duplicate code
+       and data. Viktor Dukhovni. Files: tls/tls_client.c,
+       tls/tls.h, tls/tls_misc.c, tls/tls_server.c.
+
+20260405
+
+       Documentation: in TLS_README, step-by-step instructions to
+       test Postfix SMTP server SNI support, and how to avoid a
+       null pointer bug in unpatched OpenSSL versions 3.2..3.6;
+       this text is linked to from the postconf(5) manpage. Viktor
+       Dukhovni. Files: proto/postconf.proto, proto/TLS_README.html.
+
+       Testing: In the SQLite test, call ptest_skip() if Postfix
+       is built without SQLite (after converting the test to use
+       Ptest). Files: global/Makefile.in, global/dict_sqlite_test.c.
+
+       Testing: expect_ptest_error() should be expect_ptest_log_event().
+       File: global/hfrom_format_test.c.
+
 TODO
 
        Reorganize PTEST_LIB, PMOCK_LIB, TESTLIB, TESTLIBS, etc.
 TODO
 
        Reorganize PTEST_LIB, PMOCK_LIB, TESTLIB, TESTLIBS, etc.
index 9a75f24a975c6926b5b2b34835a030ae30e0b521..59791412fb6ee5543d563c0c39576939a359c932 100644 (file)
@@ -71,6 +71,7 @@ Topics covered in this section:
   * Server access control
   * Server-side cipher controls
   * Miscellaneous server controls
   * Server access control
   * Server-side cipher controls
   * Miscellaneous server controls
+  * Testing SNI support
 
 S\bSe\ber\brv\bve\ber\br-\b-s\bsi\bid\bde\be c\bce\ber\brt\bti\bif\bfi\bic\bca\bat\bte\be a\ban\bnd\bd p\bpr\bri\biv\bva\bat\bte\be k\bke\bey\by c\bco\bon\bnf\bfi\big\bgu\bur\bra\bat\bti\bio\bon\bn
 
 
 S\bSe\ber\brv\bve\ber\br-\b-s\bsi\bid\bde\be c\bce\ber\brt\bti\bif\bfi\bic\bca\bat\bte\be a\ban\bnd\bd p\bpr\bri\biv\bva\bat\bte\be k\bke\bey\by c\bco\bon\bnf\bfi\big\bgu\bur\bra\bat\bti\bio\bon\bn
 
@@ -747,6 +748,251 @@ release may be enabled by other means in a later release, and the mask bit will
 then be ignored. Therefore, use of the hexadecimal mask is only a temporary
 measure until a new Postfix or OpenSSL release provides a better solution.
 
 then be ignored. Therefore, use of the hexadecimal mask is only a temporary
 measure until a new Postfix or OpenSSL release provides a better solution.
 
+T\bTe\bes\bst\bti\bin\bng\bg S\bSN\bNI\bI s\bsu\bup\bpp\bpo\bor\brt\bt
+
+The Postfix SMTP server supports dynamic selection of private keys and
+certificate chains based on the content of the server-name-indication (SNI) TLS
+extension sent by the SMTP client. See the documentation of the
+tls_server_sni_maps configuration parameter for basic syntax details.
+
+Below you'll see how to test SNI support, and, in particular, how to determine
+whether your OpenSSL runtime has been patched to correctly handle SNI with
+recently added public key algorithms, such as the post-quantum M\bML\bL-\b-D\bDS\bSA\bA. Use of
+post-quantum certificates via SNI will become relevant once certification
+authorities start to issue post-quantum certificates. Until then, with self-
+signed certificates, it rarely matters what names happen to appear in a
+certificate that can't be chained to a trusted CA.
+
+First let's check that SNI support works with RSA keys. The below commands will
+generate a key and self-signed certificate:
+
+        # cfdir=$(postconf -dxh config_directory)
+        # cd "$cfdir"
+        # rchain="rsa-test.pem"
+        # fqdn=sni-test.localhost
+        # openssl genpkey -out "$rchain" -algorithm rsa \
+            -pkeyopt rsa_keygen_bits:2048
+        # openssl req -new -x509 -key "$rchain" -subj "/CN=$fqdn" \
+            -addext "basicConstraints = critical,CA:false" \
+            -addext "extendedKeyUsage = serverAuth" \
+            -addext "subjectAltName = DNS:$fqdn" >> "$rchain"
+
+Next, we check that the private key matches the public key in the certificate:
+
+        # cfdir=$(postconf -dxh config_directory)
+        # cd "$cfdir"
+        # rchain=rsa-test.pem
+        # kh=$(openssl pkey -in "$rchain" -pubout -outform DER |
+            openssl dgst -sha256 | sed 's/.*= *//')
+        # xh=$(openssl x509 -in "$rchain" -noout -pubkey |
+            openssl pkey -pubin -outform DER |
+            openssl dgst -sha256 | sed 's/.*= *//')
+        # if [[ "X$kh" == "X$xh" ]] then
+            echo "Equal private and certificate pubkey hashes: $kh"
+          else
+            echo "key does not match certificate in ${rchain}" >&2
+          fi
+
+Next, we construct a test SNI table (as a matter of good habit, making sure it
+is not world-readable), and check that queries return the same public key hash
+as before:
+
+        # cfdir=$(postconf -dxh config_directory)
+        # cd "$cfdir"
+        # rchain=rsa-test.pem
+        # fqdn=sni-test.localhost
+        # dbtype=$(postconf -xh default_database_type)
+        # snitab=sni-test-table
+        # (
+            umask 077
+            printf "%s\t%s\n" "$fqdn" "$cfdir/$rchain" > "$snitab"
+          )
+        # postmap -F "${dbtype}:${snitab}"
+        # postmap -Fq "$fqdn" "${dbtype}:${snitab}" |
+            openssl pkey -pubout -outform DER |
+            openssl dgst -sha256 | sed 's/.*= */pubkey hash: /'
+        # postmap -Fq "$fqdn" "${dbtype}:${snitab}" |
+            openssl x509 -noout -pubkey |
+            openssl pkey -pubin -outform DER |
+            openssl dgst -sha256 | sed 's/.*= */pubkey hash: /'
+
+Next, we add a test SNI-enabled SMTP service on an unused port and loopback IP
+address:
+
+        # cfdir=$(postconf -dxh config_directory)
+        # cd "$cfdir"
+        # rchain=rsa-test.pem
+        # fqdn=sni-test.localhost
+        # dbtype=$(postconf -xh default_database_type)
+        # snitab=sni-test-table
+        # port=$(perl -MIO::Socket -le '
+            # Just pick a "random" unused port if you do not have Perl
+            print IO::Socket::INET->new(
+                Listen => 1,
+                ReuseAddr => 1,
+                LocalHost => "127.0.0.1",
+                LocalPort => 0)->sockport()
+                ')
+        # postconf -M "$(
+            printf '%s/inet = %s inet n - n - - smtpd' \
+                "127.0.0.1:$port" "127.0.0.1:$port"
+            printf '\t-o { tls_server_sni_maps = %s:%s/%s }' \
+                "$dbtype" "$cfdir" "$snitab"
+            printf '\t-o { syslog_name = postfix/sni-test }'
+            )"
+        # postfix reload; sleep 1
+        #
+        # kh=$(openssl pkey -in "$rchain" -pubout -outform DER |
+            openssl dgst -sha256 | sed 's/.*= *//')
+        # ( sleep 1; printf "QUIT\r\n") |
+            openssl s_client -brief -starttls smtp \
+                -connect "127.0.0.1:$port" \
+                -verify 1 -verify_return_error \
+                -dane_tlsa_domain "$fqdn" \
+                -dane_tlsa_rrdata "3 1 1 $kh" &&
+            printf "OK: Got expected SNI chain\n" ||
+            printf "ERROR: Unexpected SNI certificate\n" >&2
+        # postconf -MX "127.0.0.1:$port/inet"
+        # postfix reload
+
+The above basic RSA tests are expected to work, if the instructions are
+followed accurately. If there's an unexpected failure, and the reasons are
+unclear, see the debugging tutorial, for how seek help from the p\bpo\bos\bst\btf\bfi\bix\bx-\b-u\bus\bse\ber\brs\bs
+list.
+
+With the basic RSA test out of the way, let's try combining RSA and ECDSA keys:
+
+        # cfdir=$(postconf -dxh config_directory)
+        # cd "$cfdir"
+        # rchain="rsa-test.pem"
+        # echain="ec-test.pem"
+        # rsasig="rsa_pss_rsae_sha256:rsa_pss_rsae_sha384:rsa_pss_rsae_sha512"
+        # rsasig="$rsasig:rsa_pss_pss_sha256:rsa_pss_pss_sha384:
+    rsa_pss_pss_sha512"
+        # rsasig="$rsasig:rsa_pkcs1_sha256:rsa_pkcs1_sha384:rsa_pkcs1_sha512"
+        # ecsig="ecdsa_secp256r1_sha256:ecdsa_secp384r1_sha384:
+    ecdsa_secp521r1_sha512"
+        # fqdn=sni-test.localhost
+        # dbtype=$(postconf -xh default_database_type)
+        # snitab=sni-test-table
+        # openssl genpkey -algorithm rsa -pkeyopt rsa_keygen_bits:2048 \
+            -out "$rchain"
+        # openssl req -new -x509 -key "$rchain" -subj "/CN=$fqdn" \
+            -addext "basicConstraints = critical,CA:false" \
+            -addext "extendedKeyUsage = serverAuth" \
+            -addext "subjectAltName = DNS:$fqdn" >> "$rchain"
+        # openssl genpkey -algorithm ec -pkeyopt ec_paramgen_curve:prime256v1 \
+            -out "$echain"
+        # openssl req -new -x509 -key "$echain" -subj "/CN=$fqdn" \
+            -addext "basicConstraints = critical,CA:false" \
+            -addext "extendedKeyUsage = serverAuth" \
+            -addext "subjectAltName = DNS:$fqdn" >> "$echain"
+        # rh=$(openssl pkey -in "$rchain" -pubout -outform DER |
+            openssl dgst -sha256 | sed 's/.*= *//')
+        # eh=$(openssl pkey -in "$echain" -pubout -outform DER |
+            openssl dgst -sha256 | sed 's/.*= *//')
+        # (
+            umask 077
+            printf "%s\t%s, %s\n" \
+                "$fqdn" "$cfdir/$rchain" "$cfdir/$echain" > "$snitab"
+          )
+        # postmap -F "${dbtype}:${snitab}"
+        # port=$(perl -MIO::Socket -le '
+            print IO::Socket::INET->new(
+                Listen => 1,
+                ReuseAddr => 1,
+                LocalHost => "127.0.0.1",
+                LocalPort => 0)->sockport()
+                ')
+        # postconf -M "$(
+            printf '%s/inet = %s inet n - n - - smtpd' \
+                "127.0.0.1:$port" "127.0.0.1:$port"
+            printf '\t-o { tls_server_sni_maps = %s:%s/%s }' \
+                "$dbtype" "$cfdir" "$snitab"
+            printf '\t-o { syslog_name = postfix/sni-test }'
+            )"
+        # postfix reload; sleep 1
+        # ( sleep 1; printf "QUIT\r\n") |
+            openssl s_client -brief -starttls smtp \
+                -connect "127.0.0.1:$port" \
+                -sigalgs "$rsasig" -cipher aRSA \
+                -verify 1 -verify_return_error \
+                -dane_tlsa_domain "$fqdn" \
+                -dane_tlsa_rrdata "3 1 1 $rh" &&
+            printf "OK: Got expected RSA SNI chain\n" ||
+            printf "ERROR: Unexpected RSA server certificate\n" >&2
+        # ( sleep 1; printf "QUIT\r\n") |
+            openssl s_client -brief -starttls smtp \
+                -connect "127.0.0.1:$port" \
+                -sigalgs "$ecsig" -cipher aECDSA \
+                -verify 1 -verify_return_error \
+                -dane_tlsa_domain "$fqdn" \
+                -dane_tlsa_rrdata "3 1 1 $eh" &&
+            printf "OK: Got expected EC SNI chain\n" ||
+            printf "ERROR: Unexpected EC SNI certificate\n" >&2
+        # postconf -MX "127.0.0.1:$port/inet"
+        # postfix reload
+
+With a dual algorithm RSA + ECDSA chain also successful (for most users just
+single-algorithm RSA is the better choice), it is time to check whether the
+OpenSSL runtime supports SNI also for post-quantum algorithm keys. This
+requires OpenSSL 3.5 or later, earlier versions don't support the M\bML\bL-\b-D\bDS\bSA\bA
+algorithm needed for this test:
+
+        # cfdir=$(postconf -dxh config_directory)
+        # cd "$cfdir"
+        # mchain="test-mldsa-chain.pem"
+        # mlsig="mldsa44"
+        # fqdn=sni-test.localhost
+        # dbtype=$(postconf -xh default_database_type)
+        # snitab=sni-test-table
+        # openssl genpkey -algorithm ml-dsa-44 -out "$mchain"
+        # openssl req -new -x509 -key "$mchain" -subj "/CN=$fqdn" \
+            -addext "basicConstraints = critical,CA:false" \
+            -addext "extendedKeyUsage = serverAuth" \
+            -addext "subjectAltName = DNS:$fqdn" >> "$mchain"
+        # mh=$(openssl pkey -in "$mchain" -pubout -outform DER |
+            openssl dgst -sha256 | sed 's/.*= *//')
+        # (
+            umask 077
+            printf "%s\t%s\n" "$fqdn" "$cfdir/$mchain" > "$snitab"
+          )
+        # postmap -F "${dbtype}:${snitab}"
+        # port=$(perl -MIO::Socket -le '
+            print IO::Socket::INET->new(
+                Listen => 1,
+                ReuseAddr => 1,
+                LocalHost => "127.0.0.1",
+                LocalPort => 0)->sockport()
+                ')
+        # postconf -M "$(
+            printf '%s/inet = %s inet n - n - - smtpd' \
+                "127.0.0.1:$port" "127.0.0.1:$port"
+            printf '\t-o { tls_server_sni_maps = %s:%s/%s }' \
+                "$dbtype" "$cfdir" "$snitab"
+            printf '\t-o { syslog_name = postfix/sni-test }'
+            )"
+        # postfix reload; sleep 1
+        # ( sleep 1; printf "QUIT\r\n") |
+            openssl s_client -brief -starttls smtp \
+                -connect "127.0.0.1:$port" \
+                -sigalgs "$mlsig" -tls1_3 \
+                -verify 1 -verify_return_error \
+                -dane_tlsa_domain "$fqdn" \
+                -dane_tlsa_rrdata "3 1 1 $mh" &&
+            printf "OK: Got expected ML-DSA SNI chain\n" ||
+            printf "ERROR: Unexpected ML-DSA server certificate\n" >&2
+        # postconf -MX "127.0.0.1:$port/inet"
+        # postfix reload
+
+If all went well, your OpenSSL runtime working SNI support for also for newer
+public key algorithms, such as M\bML\bL-\b-D\bDS\bSA\bA. If the test failed, and the Postfix logs
+show that the test Postfix SMTP server failed with a segfault, your OpenSSL
+runtime has not yet been patched. The test is not viable with OpenSSL 3.4 or
+earlier, which don't include M\bML\bL-\b-D\bDS\bSA\bA support, and is expected to work with
+OpenSSL 4.0, which should be free of the underlying defect when initially
+released.
+
 S\bSM\bMT\bTP\bP C\bCl\bli\bie\ben\bnt\bt s\bsp\bpe\bec\bci\bif\bfi\bic\bc s\bse\bet\btt\bti\bin\bng\bgs\bs
 
 Topics covered in this section:
 S\bSM\bMT\bTP\bP C\bCl\bli\bie\ben\bnt\bt s\bsp\bpe\bec\bci\bif\bfi\bic\bc s\bse\bet\btt\bti\bin\bng\bgs\bs
 
 Topics covered in this section:
index 4dc06dcb1db7be67ecbbd306f54b6feca671daa3..e5f8654dc700c30042ca91fab0bc4bfa77de66d1 100644 (file)
@@ -145,6 +145,8 @@ key configuration </a>
 
 <li><a href="#server_misc"> Miscellaneous server controls</a>
 
 
 <li><a href="#server_misc"> Miscellaneous server controls</a>
 
+<li><a href="#testing_sni"> Testing SNI support</a>
+
 </ul>
 
 <h3><a name="server_cert_key">Server-side certificate and private
 </ul>
 
 <h3><a name="server_cert_key">Server-side certificate and private
@@ -1038,6 +1040,277 @@ ignored.  Therefore, use of the hexadecimal mask is only a temporary
 measure until a new Postfix or OpenSSL release provides a better
 solution.  </p>
 
 measure until a new Postfix or OpenSSL release provides a better
 solution.  </p>
 
+<h3><a name="testing_sni"> Testing SNI support</a> </h3>
+
+<p> The Postfix SMTP server supports dynamic selection of private keys and
+certificate chains based on the content of the server-name-indication (SNI)
+TLS extension sent by the SMTP client.  See the documentation of the <a
+href="postconf.5.html#tls_server_sni_maps">tls_server_sni_maps</a>
+configuration parameter for basic syntax details. </p>
+
+<p> Below you'll see how to test SNI support, and, in particular, how to
+determine whether your OpenSSL runtime has been patched to correctly handle
+SNI with recently added public key algorithms, such as the post-quantum
+<b>ML-DSA</b>.  Use of post-quantum certificates via SNI will become
+relevant once certification authorities start to issue post-quantum
+certificates.  Until then, with self-signed certificates, it rarely matters
+what names happen to appear in a certificate that can't be chained to a
+trusted CA.  </p>
+
+<p> First let's check that SNI support works with RSA keys.  The below
+commands will generate a key and self-signed certificate: </p>
+
+<blockquote>
+<pre>
+    # cfdir=$(postconf -dxh <a href="postconf.5.html#config_directory">config_directory</a>)
+    # cd "$cfdir"
+    # rchain="rsa-test.pem"
+    # fqdn=sni-test.localhost
+    # openssl genpkey -out "$rchain" -algorithm rsa \
+        -pkeyopt rsa_keygen_bits:2048
+    # openssl req -new -x509 -key "$rchain" -subj "/CN=$fqdn" \
+        -addext "basicConstraints = critical,CA:false" \
+        -addext "extendedKeyUsage = serverAuth" \
+        -addext "subjectAltName = DNS:$fqdn" >> "$rchain"
+</pre>
+</blockquote>
+
+<p> Next, we check that the private key matches the public key in the
+certificate: </p>
+
+<blockquote>
+<pre>
+    # cfdir=$(postconf -dxh <a href="postconf.5.html#config_directory">config_directory</a>)
+    # cd "$cfdir"
+    # rchain=rsa-test.pem
+    # kh=$(openssl pkey -in "$rchain" -pubout -outform DER |
+        openssl dgst -sha256 | sed 's/.*= *//')
+    # xh=$(openssl x509 -in "$rchain" -noout -pubkey |
+        openssl pkey -pubin -outform DER |
+        openssl dgst -sha256 | sed 's/.*= *//')
+    # if [[ "X$kh" == "X$xh" ]] then
+        echo "Equal private and certificate pubkey hashes: $kh"
+      else
+        echo "key does not match certificate in ${rchain}" >&2
+      fi
+</pre>
+</blockquote>
+
+<p> Next, we construct a test SNI table (as a matter of good habit, making
+sure it is not world-readable), and check that queries return the same public
+key hash as before: </p>
+
+<blockquote>
+<pre>
+    # cfdir=$(postconf -dxh <a href="postconf.5.html#config_directory">config_directory</a>)
+    # cd "$cfdir"
+    # rchain=rsa-test.pem
+    # fqdn=sni-test.localhost
+    # dbtype=$(postconf -xh <a href="postconf.5.html#default_database_type">default_database_type</a>)
+    # snitab=sni-test-table
+    # (
+        umask 077
+        printf "%s\t%s\n" "$fqdn" "$cfdir/$rchain" > "$snitab"
+      )
+    # postmap -F "${dbtype}:${snitab}"
+    # postmap -Fq "$fqdn" "${dbtype}:${snitab}" |
+        openssl pkey -pubout -outform DER |
+        openssl dgst -sha256 | sed 's/.*= */pubkey <a href="DATABASE_README.html#types">hash</a>: /'
+    # postmap -Fq "$fqdn" "${dbtype}:${snitab}" |
+        openssl x509 -noout -pubkey |
+        openssl pkey -pubin -outform DER |
+        openssl dgst -sha256 | sed 's/.*= */pubkey <a href="DATABASE_README.html#types">hash</a>: /'
+</pre>
+</blockquote>
+
+<p> Next, we add a test SNI-enabled SMTP service on an unused port and
+loopback IP address: </p>
+
+<blockquote>
+<pre>
+    # cfdir=$(postconf -dxh <a href="postconf.5.html#config_directory">config_directory</a>)
+    # cd "$cfdir"
+    # rchain=rsa-test.pem
+    # fqdn=sni-test.localhost
+    # dbtype=$(postconf -xh <a href="postconf.5.html#default_database_type">default_database_type</a>)
+    # snitab=sni-test-table
+    # port=$(perl -MIO::Socket -le '
+        # Just pick a "random" unused port if you do not have Perl
+        print IO::Socket::INET->new(
+            Listen => 1,
+            ReuseAddr => 1,
+            LocalHost => "127.0.0.1",
+            LocalPort => 0)->sockport()
+            ')
+    # postconf -M "$(
+        printf '%s/inet = %s inet n - n - - smtpd' \
+            "127.0.0.1:$port" "127.0.0.1:$port"
+        printf '\t-o { <a href="postconf.5.html#tls_server_sni_maps">tls_server_sni_maps</a> = %s:%s/%s }' \
+            "$dbtype" "$cfdir" "$snitab"
+        printf '\t-o { <a href="postconf.5.html#syslog_name">syslog_name</a> = postfix/sni-test }'
+        )"
+    # postfix reload; sleep 1
+    #
+    # kh=$(openssl pkey -in "$rchain" -pubout -outform DER |
+        openssl dgst -sha256 | sed 's/.*= *//')
+    # ( sleep 1; printf "QUIT\r\n") |
+        openssl s_client -brief -starttls smtp \
+            -connect "127.0.0.1:$port" \
+            -verify 1 -verify_return_error \
+            -dane_tlsa_domain "$fqdn" \
+            -dane_tlsa_rrdata "3 1 1 $kh" &&
+        printf "OK: Got expected SNI chain\n" ||
+        printf "ERROR: Unexpected SNI certificate\n" >&2
+    # postconf -MX "127.0.0.1:$port/inet"
+    # postfix reload
+</pre>
+</blockquote>
+
+<p> The above basic RSA tests are expected to work, if the instructions
+are followed accurately.  If there's an unexpected failure, and the reasons
+are unclear, see the <a href="DEBUG_README.html#mail">debugging
+tutorial</a>, for how seek help from the <b>postfix-users</b> list. </p>
+
+<p> With the basic RSA test out of the way, let's try combining RSA and
+ECDSA keys: </p>
+
+<blockquote>
+<pre>
+    # cfdir=$(postconf -dxh <a href="postconf.5.html#config_directory">config_directory</a>)
+    # cd "$cfdir"
+    # rchain="rsa-test.pem"
+    # echain="ec-test.pem"
+    # rsasig="rsa_pss_rsae_sha256:rsa_pss_rsae_sha384:rsa_pss_rsae_sha512"
+    # rsasig="$rsasig:rsa_pss_pss_sha256:rsa_pss_pss_sha384:rsa_pss_pss_sha512"
+    # rsasig="$rsasig:rsa_pkcs1_sha256:rsa_pkcs1_sha384:rsa_pkcs1_sha512"
+    # ecsig="ecdsa_secp256r1_sha256:ecdsa_secp384r1_sha384:ecdsa_secp521r1_sha512"
+    # fqdn=sni-test.localhost
+    # dbtype=$(postconf -xh <a href="postconf.5.html#default_database_type">default_database_type</a>)
+    # snitab=sni-test-table
+    # openssl genpkey -algorithm rsa -pkeyopt rsa_keygen_bits:2048 \
+        -out "$rchain"
+    # openssl req -new -x509 -key "$rchain" -subj "/CN=$fqdn" \
+        -addext "basicConstraints = critical,CA:false" \
+        -addext "extendedKeyUsage = serverAuth" \
+        -addext "subjectAltName = DNS:$fqdn" >> "$rchain"
+    # openssl genpkey -algorithm ec -pkeyopt ec_paramgen_curve:prime256v1 \
+        -out "$echain"
+    # openssl req -new -x509 -key "$echain" -subj "/CN=$fqdn" \
+        -addext "basicConstraints = critical,CA:false" \
+        -addext "extendedKeyUsage = serverAuth" \
+        -addext "subjectAltName = DNS:$fqdn" >> "$echain"
+    # rh=$(openssl pkey -in "$rchain" -pubout -outform DER |
+        openssl dgst -sha256 | sed 's/.*= *//')
+    # eh=$(openssl pkey -in "$echain" -pubout -outform DER |
+        openssl dgst -sha256 | sed 's/.*= *//')
+    # (
+        umask 077
+        printf "%s\t%s, %s\n" \
+            "$fqdn" "$cfdir/$rchain" "$cfdir/$echain" > "$snitab"
+      )
+    # postmap -F "${dbtype}:${snitab}"
+    # port=$(perl -MIO::Socket -le '
+        print IO::Socket::INET->new(
+            Listen => 1,
+            ReuseAddr => 1,
+            LocalHost => "127.0.0.1",
+            LocalPort => 0)->sockport()
+            ')
+    # postconf -M "$(
+        printf '%s/inet = %s inet n - n - - smtpd' \
+            "127.0.0.1:$port" "127.0.0.1:$port"
+        printf '\t-o { <a href="postconf.5.html#tls_server_sni_maps">tls_server_sni_maps</a> = %s:%s/%s }' \
+            "$dbtype" "$cfdir" "$snitab"
+        printf '\t-o { <a href="postconf.5.html#syslog_name">syslog_name</a> = postfix/sni-test }'
+        )"
+    # postfix reload; sleep 1
+    # ( sleep 1; printf "QUIT\r\n") |
+        openssl s_client -brief -starttls smtp \
+            -connect "127.0.0.1:$port" \
+            -sigalgs "$rsasig" -cipher aRSA \
+            -verify 1 -verify_return_error \
+            -dane_tlsa_domain "$fqdn" \
+            -dane_tlsa_rrdata "3 1 1 $rh" &&
+        printf "OK: Got expected RSA SNI chain\n" ||
+        printf "ERROR: Unexpected RSA server certificate\n" >&2
+    # ( sleep 1; printf "QUIT\r\n") |
+        openssl s_client -brief -starttls smtp \
+            -connect "127.0.0.1:$port" \
+            -sigalgs "$ecsig" -cipher aECDSA \
+            -verify 1 -verify_return_error \
+            -dane_tlsa_domain "$fqdn" \
+            -dane_tlsa_rrdata "3 1 1 $eh" &&
+        printf "OK: Got expected EC SNI chain\n" ||
+        printf "ERROR: Unexpected EC SNI certificate\n" >&2
+    # postconf -MX "127.0.0.1:$port/inet"
+    # postfix reload
+</pre>
+</blockquote>
+
+<p> With a dual algorithm RSA + ECDSA chain also successful (for most users
+just single-algorithm RSA is the better choice), it is time to check
+whether the OpenSSL runtime supports SNI also for post-quantum algorithm
+keys.  This requires OpenSSL 3.5 or later, earlier versions don't support
+the <b>ML-DSA</b> algorithm needed for this test: </p>
+
+<blockquote>
+<pre>
+    # cfdir=$(postconf -dxh <a href="postconf.5.html#config_directory">config_directory</a>)
+    # cd "$cfdir"
+    # mchain="test-mldsa-chain.pem"
+    # mlsig="mldsa44"
+    # fqdn=sni-test.localhost
+    # dbtype=$(postconf -xh <a href="postconf.5.html#default_database_type">default_database_type</a>)
+    # snitab=sni-test-table
+    # openssl genpkey -algorithm ml-dsa-44 -out "$mchain"
+    # openssl req -new -x509 -key "$mchain" -subj "/CN=$fqdn" \
+        -addext "basicConstraints = critical,CA:false" \
+        -addext "extendedKeyUsage = serverAuth" \
+        -addext "subjectAltName = DNS:$fqdn" >> "$mchain"
+    # mh=$(openssl pkey -in "$mchain" -pubout -outform DER |
+        openssl dgst -sha256 | sed 's/.*= *//')
+    # (
+        umask 077
+        printf "%s\t%s\n" "$fqdn" "$cfdir/$mchain" > "$snitab"
+      )
+    # postmap -F "${dbtype}:${snitab}"
+    # port=$(perl -MIO::Socket -le '
+        print IO::Socket::INET->new(
+            Listen => 1,
+            ReuseAddr => 1,
+            LocalHost => "127.0.0.1",
+            LocalPort => 0)->sockport()
+            ')
+    # postconf -M "$(
+        printf '%s/inet = %s inet n - n - - smtpd' \
+            "127.0.0.1:$port" "127.0.0.1:$port"
+        printf '\t-o { <a href="postconf.5.html#tls_server_sni_maps">tls_server_sni_maps</a> = %s:%s/%s }' \
+            "$dbtype" "$cfdir" "$snitab"
+        printf '\t-o { <a href="postconf.5.html#syslog_name">syslog_name</a> = postfix/sni-test }'
+        )"
+    # postfix reload; sleep 1
+    # ( sleep 1; printf "QUIT\r\n") |
+        openssl s_client -brief -starttls smtp \
+            -connect "127.0.0.1:$port" \
+            -sigalgs "$mlsig" -tls1_3 \
+            -verify 1 -verify_return_error \
+            -dane_tlsa_domain "$fqdn" \
+            -dane_tlsa_rrdata "3 1 1 $mh" &&
+        printf "OK: Got expected ML-DSA SNI chain\n" ||
+        printf "ERROR: Unexpected ML-DSA server certificate\n" >&2
+    # postconf -MX "127.0.0.1:$port/inet"
+    # postfix reload
+</pre>
+</blockquote>
+
+<p> If all went well, your OpenSSL runtime working SNI support for also for
+newer public key algorithms, such as <b>ML-DSA</b>.  If the test failed, and
+the Postfix logs show that the test Postfix SMTP server failed with a
+segfault, your OpenSSL runtime has not yet been patched.  The test is not
+viable with OpenSSL 3.4 or earlier, which don't include <b>ML-DSA</b>
+support, and is expected to work with OpenSSL 4.0, which should be free of
+the underlying defect when initially released.  </p>
+
 <h2> <a name="client_tls">SMTP Client specific settings</a> </h2>
 
 <p> Topics covered in this section: </p>
 <h2> <a name="client_tls">SMTP Client specific settings</a> </h2>
 
 <p> Topics covered in this section: </p>
index 771534b7e42e616a2b463457f02d7570e214ec40..70e3a587be4e2e54ab0d1177e0568208ec10b65c 100644 (file)
@@ -21883,6 +21883,25 @@ required in the TLS SNI extension). </p>
 but here scoped to just TLS connections in which the client sends
 a matching SNI domain name. </p>
 
 but here scoped to just TLS connections in which the client sends
 a matching SNI domain name. </p>
 
+<p> Unpatched versions of OpenSSL versions 3.2 through 3.6, may not
+correctly handle SNI certificate chains for private key algorithms
+other than: </p>
+
+<ul>
+<li> <b>RSA</b>
+<li> <b>ECDSA</b>
+<li> <b>DSA</b> (obsolete and best avoided)
+<li> <b>EdDSA</b> (Ed25519 or Ed448).
+</ul>
+
+<p>The issue does not arise if you limit your SNI chains to just
+these algorithms. Otherwise, inclusion of, e.g., <b>ML-DSA</b>
+keys in SNI chains may lead to a null-pointer-exception (NPE).  The
+most recent update of your system's OpenSSL runtime libraries may
+already include a fix for this issue. See the <a
+href="TLS_README.html#testing_sni">testing SNI</a> section of the
+Postfix TLS documentation for further guidance.  </p>
+
 <p> Example: </p>
 <blockquote>
 <pre>
 <p> Example: </p>
 <blockquote>
 <pre>
index 4d881b6ef72afbbea88a03fd886356d527bc3138..cf52560931949ebedbfc6010e1d02d018919b08f 100644 (file)
@@ -15522,6 +15522,25 @@ smtp_tls_chain_files parameter (see there for additional details),
 but here scoped to just TLS connections in which the client sends
 a matching SNI domain name.
 .PP
 but here scoped to just TLS connections in which the client sends
 a matching SNI domain name.
 .PP
+Unpatched versions of OpenSSL versions 3.2 through 3.6, may not
+correctly handle SNI certificate chains for private key algorithms
+other than:
+.IP \(bu
+\fBRSA\fR
+.IP \(bu
+\fBECDSA\fR
+.IP \(bu
+\fBDSA\fR (obsolete and best avoided)
+.IP \(bu
+\fBEdDSA\fR (Ed25519 or Ed448).
+.br
+The issue does not arise if you limit your SNI chains to just
+these algorithms. Otherwise, inclusion of, e.g., \fBML\-DSA\fR
+keys in SNI chains may lead to a null\-pointer\-exception (NPE).  The
+most recent update of your system's OpenSSL runtime libraries may
+already include a fix for this issue. See the testing SNI section of the
+Postfix TLS documentation for further guidance.
+.PP
 Example:
 .sp
 .in +4
 Example:
 .sp
 .in +4
index 879aae52e9dd1ed1f4ecb5120c990e282d77c0c4..fefe0e807434c1b76a6afa5495a08600b8d3c443 100644 (file)
@@ -145,6 +145,8 @@ key configuration </a>
 
 <li><a href="#server_misc"> Miscellaneous server controls</a>
 
 
 <li><a href="#server_misc"> Miscellaneous server controls</a>
 
+<li><a href="#testing_sni"> Testing SNI support</a>
+
 </ul>
 
 <h3><a name="server_cert_key">Server-side certificate and private
 </ul>
 
 <h3><a name="server_cert_key">Server-side certificate and private
@@ -1038,6 +1040,277 @@ ignored.  Therefore, use of the hexadecimal mask is only a temporary
 measure until a new Postfix or OpenSSL release provides a better
 solution.  </p>
 
 measure until a new Postfix or OpenSSL release provides a better
 solution.  </p>
 
+<h3><a name="testing_sni"> Testing SNI support</a> </h3>
+
+<p> The Postfix SMTP server supports dynamic selection of private keys and
+certificate chains based on the content of the server-name-indication (SNI)
+TLS extension sent by the SMTP client.  See the documentation of the <a
+href="postconf.5.html#tls_server_sni_maps">tls_server_sni_maps</a>
+configuration parameter for basic syntax details. </p>
+
+<p> Below you'll see how to test SNI support, and, in particular, how to
+determine whether your OpenSSL runtime has been patched to correctly handle
+SNI with recently added public key algorithms, such as the post-quantum
+<b>ML-DSA</b>.  Use of post-quantum certificates via SNI will become
+relevant once certification authorities start to issue post-quantum
+certificates.  Until then, with self-signed certificates, it rarely matters
+what names happen to appear in a certificate that can't be chained to a
+trusted CA.  </p>
+
+<p> First let's check that SNI support works with RSA keys.  The below
+commands will generate a key and self-signed certificate: </p>
+
+<blockquote>
+<pre>
+    # cfdir=$(postconf -dxh config_directory)
+    # cd "$cfdir"
+    # rchain="rsa-test.pem"
+    # fqdn=sni-test.localhost
+    # openssl genpkey -out "$rchain" -algorithm rsa \
+        -pkeyopt rsa_keygen_bits:2048
+    # openssl req -new -x509 -key "$rchain" -subj "/CN=$fqdn" \
+        -addext "basicConstraints = critical,CA:false" \
+        -addext "extendedKeyUsage = serverAuth" \
+        -addext "subjectAltName = DNS:$fqdn" >> "$rchain"
+</pre>
+</blockquote>
+
+<p> Next, we check that the private key matches the public key in the
+certificate: </p>
+
+<blockquote>
+<pre>
+    # cfdir=$(postconf -dxh config_directory)
+    # cd "$cfdir"
+    # rchain=rsa-test.pem
+    # kh=$(openssl pkey -in "$rchain" -pubout -outform DER |
+        openssl dgst -sha256 | sed 's/.*= *//')
+    # xh=$(openssl x509 -in "$rchain" -noout -pubkey |
+        openssl pkey -pubin -outform DER |
+        openssl dgst -sha256 | sed 's/.*= *//')
+    # if [[ "X$kh" == "X$xh" ]] then
+        echo "Equal private and certificate pubkey hashes: $kh"
+      else
+        echo "key does not match certificate in ${rchain}" >&2
+      fi
+</pre>
+</blockquote>
+
+<p> Next, we construct a test SNI table (as a matter of good habit, making
+sure it is not world-readable), and check that queries return the same public
+key hash as before: </p>
+
+<blockquote>
+<pre>
+    # cfdir=$(postconf -dxh config_directory)
+    # cd "$cfdir"
+    # rchain=rsa-test.pem
+    # fqdn=sni-test.localhost
+    # dbtype=$(postconf -xh default_database_type)
+    # snitab=sni-test-table
+    # (
+        umask 077
+        printf "%s\t%s\n" "$fqdn" "$cfdir/$rchain" > "$snitab"
+      )
+    # postmap -F "${dbtype}:${snitab}"
+    # postmap -Fq "$fqdn" "${dbtype}:${snitab}" |
+        openssl pkey -pubout -outform DER |
+        openssl dgst -sha256 | sed 's/.*= */pubkey hash: /'
+    # postmap -Fq "$fqdn" "${dbtype}:${snitab}" |
+        openssl x509 -noout -pubkey |
+        openssl pkey -pubin -outform DER |
+        openssl dgst -sha256 | sed 's/.*= */pubkey hash: /'
+</pre>
+</blockquote>
+
+<p> Next, we add a test SNI-enabled SMTP service on an unused port and
+loopback IP address: </p>
+
+<blockquote>
+<pre>
+    # cfdir=$(postconf -dxh config_directory)
+    # cd "$cfdir"
+    # rchain=rsa-test.pem
+    # fqdn=sni-test.localhost
+    # dbtype=$(postconf -xh default_database_type)
+    # snitab=sni-test-table
+    # port=$(perl -MIO::Socket -le '
+        # Just pick a "random" unused port if you do not have Perl
+        print IO::Socket::INET->new(
+            Listen => 1,
+            ReuseAddr => 1,
+            LocalHost => "127.0.0.1",
+            LocalPort => 0)->sockport()
+            ')
+    # postconf -M "$(
+        printf '%s/inet = %s inet n - n - - smtpd' \
+            "127.0.0.1:$port" "127.0.0.1:$port"
+        printf '\t-o { tls_server_sni_maps = %s:%s/%s }' \
+            "$dbtype" "$cfdir" "$snitab"
+        printf '\t-o { syslog_name = postfix/sni-test }'
+        )"
+    # postfix reload; sleep 1
+    #
+    # kh=$(openssl pkey -in "$rchain" -pubout -outform DER |
+        openssl dgst -sha256 | sed 's/.*= *//')
+    # ( sleep 1; printf "QUIT\r\n") |
+        openssl s_client -brief -starttls smtp \
+            -connect "127.0.0.1:$port" \
+            -verify 1 -verify_return_error \
+            -dane_tlsa_domain "$fqdn" \
+            -dane_tlsa_rrdata "3 1 1 $kh" &&
+        printf "OK: Got expected SNI chain\n" ||
+        printf "ERROR: Unexpected SNI certificate\n" >&2
+    # postconf -MX "127.0.0.1:$port/inet"
+    # postfix reload
+</pre>
+</blockquote>
+
+<p> The above basic RSA tests are expected to work, if the instructions
+are followed accurately.  If there's an unexpected failure, and the reasons
+are unclear, see the <a href="DEBUG_README.html#mail">debugging
+tutorial</a>, for how seek help from the <b>postfix-users</b> list. </p>
+
+<p> With the basic RSA test out of the way, let's try combining RSA and
+ECDSA keys: </p>
+
+<blockquote>
+<pre>
+    # cfdir=$(postconf -dxh config_directory)
+    # cd "$cfdir"
+    # rchain="rsa-test.pem"
+    # echain="ec-test.pem"
+    # rsasig="rsa_pss_rsae_sha256:rsa_pss_rsae_sha384:rsa_pss_rsae_sha512"
+    # rsasig="$rsasig:rsa_pss_pss_sha256:rsa_pss_pss_sha384:rsa_pss_pss_sha512"
+    # rsasig="$rsasig:rsa_pkcs1_sha256:rsa_pkcs1_sha384:rsa_pkcs1_sha512"
+    # ecsig="ecdsa_secp256r1_sha256:ecdsa_secp384r1_sha384:ecdsa_secp521r1_sha512"
+    # fqdn=sni-test.localhost
+    # dbtype=$(postconf -xh default_database_type)
+    # snitab=sni-test-table
+    # openssl genpkey -algorithm rsa -pkeyopt rsa_keygen_bits:2048 \
+        -out "$rchain"
+    # openssl req -new -x509 -key "$rchain" -subj "/CN=$fqdn" \
+        -addext "basicConstraints = critical,CA:false" \
+        -addext "extendedKeyUsage = serverAuth" \
+        -addext "subjectAltName = DNS:$fqdn" >> "$rchain"
+    # openssl genpkey -algorithm ec -pkeyopt ec_paramgen_curve:prime256v1 \
+        -out "$echain"
+    # openssl req -new -x509 -key "$echain" -subj "/CN=$fqdn" \
+        -addext "basicConstraints = critical,CA:false" \
+        -addext "extendedKeyUsage = serverAuth" \
+        -addext "subjectAltName = DNS:$fqdn" >> "$echain"
+    # rh=$(openssl pkey -in "$rchain" -pubout -outform DER |
+        openssl dgst -sha256 | sed 's/.*= *//')
+    # eh=$(openssl pkey -in "$echain" -pubout -outform DER |
+        openssl dgst -sha256 | sed 's/.*= *//')
+    # (
+        umask 077
+        printf "%s\t%s, %s\n" \
+            "$fqdn" "$cfdir/$rchain" "$cfdir/$echain" > "$snitab"
+      )
+    # postmap -F "${dbtype}:${snitab}"
+    # port=$(perl -MIO::Socket -le '
+        print IO::Socket::INET->new(
+            Listen => 1,
+            ReuseAddr => 1,
+            LocalHost => "127.0.0.1",
+            LocalPort => 0)->sockport()
+            ')
+    # postconf -M "$(
+        printf '%s/inet = %s inet n - n - - smtpd' \
+            "127.0.0.1:$port" "127.0.0.1:$port"
+        printf '\t-o { tls_server_sni_maps = %s:%s/%s }' \
+            "$dbtype" "$cfdir" "$snitab"
+        printf '\t-o { syslog_name = postfix/sni-test }'
+        )"
+    # postfix reload; sleep 1
+    # ( sleep 1; printf "QUIT\r\n") |
+        openssl s_client -brief -starttls smtp \
+            -connect "127.0.0.1:$port" \
+            -sigalgs "$rsasig" -cipher aRSA \
+            -verify 1 -verify_return_error \
+            -dane_tlsa_domain "$fqdn" \
+            -dane_tlsa_rrdata "3 1 1 $rh" &&
+        printf "OK: Got expected RSA SNI chain\n" ||
+        printf "ERROR: Unexpected RSA server certificate\n" >&2
+    # ( sleep 1; printf "QUIT\r\n") |
+        openssl s_client -brief -starttls smtp \
+            -connect "127.0.0.1:$port" \
+            -sigalgs "$ecsig" -cipher aECDSA \
+            -verify 1 -verify_return_error \
+            -dane_tlsa_domain "$fqdn" \
+            -dane_tlsa_rrdata "3 1 1 $eh" &&
+        printf "OK: Got expected EC SNI chain\n" ||
+        printf "ERROR: Unexpected EC SNI certificate\n" >&2
+    # postconf -MX "127.0.0.1:$port/inet"
+    # postfix reload
+</pre>
+</blockquote>
+
+<p> With a dual algorithm RSA + ECDSA chain also successful (for most users
+just single-algorithm RSA is the better choice), it is time to check
+whether the OpenSSL runtime supports SNI also for post-quantum algorithm
+keys.  This requires OpenSSL 3.5 or later, earlier versions don't support
+the <b>ML-DSA</b> algorithm needed for this test: </p>
+
+<blockquote>
+<pre>
+    # cfdir=$(postconf -dxh config_directory)
+    # cd "$cfdir"
+    # mchain="test-mldsa-chain.pem"
+    # mlsig="mldsa44"
+    # fqdn=sni-test.localhost
+    # dbtype=$(postconf -xh default_database_type)
+    # snitab=sni-test-table
+    # openssl genpkey -algorithm ml-dsa-44 -out "$mchain"
+    # openssl req -new -x509 -key "$mchain" -subj "/CN=$fqdn" \
+        -addext "basicConstraints = critical,CA:false" \
+        -addext "extendedKeyUsage = serverAuth" \
+        -addext "subjectAltName = DNS:$fqdn" >> "$mchain"
+    # mh=$(openssl pkey -in "$mchain" -pubout -outform DER |
+        openssl dgst -sha256 | sed 's/.*= *//')
+    # (
+        umask 077
+        printf "%s\t%s\n" "$fqdn" "$cfdir/$mchain" > "$snitab"
+      )
+    # postmap -F "${dbtype}:${snitab}"
+    # port=$(perl -MIO::Socket -le '
+        print IO::Socket::INET->new(
+            Listen => 1,
+            ReuseAddr => 1,
+            LocalHost => "127.0.0.1",
+            LocalPort => 0)->sockport()
+            ')
+    # postconf -M "$(
+        printf '%s/inet = %s inet n - n - - smtpd' \
+            "127.0.0.1:$port" "127.0.0.1:$port"
+        printf '\t-o { tls_server_sni_maps = %s:%s/%s }' \
+            "$dbtype" "$cfdir" "$snitab"
+        printf '\t-o { syslog_name = postfix/sni-test }'
+        )"
+    # postfix reload; sleep 1
+    # ( sleep 1; printf "QUIT\r\n") |
+        openssl s_client -brief -starttls smtp \
+            -connect "127.0.0.1:$port" \
+            -sigalgs "$mlsig" -tls1_3 \
+            -verify 1 -verify_return_error \
+            -dane_tlsa_domain "$fqdn" \
+            -dane_tlsa_rrdata "3 1 1 $mh" &&
+        printf "OK: Got expected ML-DSA SNI chain\n" ||
+        printf "ERROR: Unexpected ML-DSA server certificate\n" >&2
+    # postconf -MX "127.0.0.1:$port/inet"
+    # postfix reload
+</pre>
+</blockquote>
+
+<p> If all went well, your OpenSSL runtime working SNI support for also for
+newer public key algorithms, such as <b>ML-DSA</b>.  If the test failed, and
+the Postfix logs show that the test Postfix SMTP server failed with a
+segfault, your OpenSSL runtime has not yet been patched.  The test is not
+viable with OpenSSL 3.4 or earlier, which don't include <b>ML-DSA</b>
+support, and is expected to work with OpenSSL 4.0, which should be free of
+the underlying defect when initially released.  </p>
+
 <h2> <a name="client_tls">SMTP Client specific settings</a> </h2>
 
 <p> Topics covered in this section: </p>
 <h2> <a name="client_tls">SMTP Client specific settings</a> </h2>
 
 <p> Topics covered in this section: </p>
index 8f7567434d4881135f4e5caff3e5e02a08fd2a20..253fc6ebc469c5ae8a365e92a5d47fc253a2b921 100644 (file)
@@ -18639,6 +18639,25 @@ smtp_tls_chain_files parameter (see there for additional details),
 but here scoped to just TLS connections in which the client sends
 a matching SNI domain name. </p>
 
 but here scoped to just TLS connections in which the client sends
 a matching SNI domain name. </p>
 
+<p> Unpatched versions of OpenSSL versions 3.2 through 3.6, may not
+correctly handle SNI certificate chains for private key algorithms
+other than: </p>
+
+<ul>
+<li> <b>RSA</b>
+<li> <b>ECDSA</b>
+<li> <b>DSA</b> (obsolete and best avoided)
+<li> <b>EdDSA</b> (Ed25519 or Ed448).
+</ul>
+
+<p>The issue does not arise if you limit your SNI chains to just
+these algorithms. Otherwise, inclusion of, e.g., <b>ML-DSA</b>
+keys in SNI chains may lead to a null-pointer-exception (NPE).  The
+most recent update of your system's OpenSSL runtime libraries may
+already include a fix for this issue. See the <a
+href="TLS_README.html#testing_sni">testing SNI</a> section of the
+Postfix TLS documentation for further guidance.  </p>
+
 <p> Example: </p>
 <blockquote>
 <pre>
 <p> Example: </p>
 <blockquote>
 <pre>
index ab1eb020b57ba72f35cd14c9647324713199f981..522a03470bdb0ba1cf7578713247f3d97dc1618f 100644 (file)
@@ -230,3 +230,4 @@ proto  proto stop proto stop double cc
  tls tls h tls tls_misc c tls tls_verify c 
  differ Files postalias postalias c local alias c 
  local local hc local Makefile 
  tls tls h tls tls_misc c tls tls_verify c 
  differ Files postalias postalias c local alias c 
  local local hc local Makefile 
+ Dukhovni Files proto postconf proto proto TLS_README html 
index 25c2fad2ef11df505107b4c9bb37be0788ffbfc1..90491409b71671e197bc5297ac4b64e455eafad6 100644 (file)
@@ -390,3 +390,5 @@ mynetworks mynetworks lmdb etc postfix network_table
  void ptest_defer PTEST_CTX t void defer_fn void void defer_ctx 
 130 type_name unionmap static one fail fail 
 187 for cpp tp want_log cpp tp want_log MAX_WANT_LOG cpp cpp 
  void ptest_defer PTEST_CTX t void defer_fn void void defer_ctx 
 130 type_name unionmap static one fail fail 
 187 for cpp tp want_log cpp tp want_log MAX_WANT_LOG cpp cpp 
+ rsasig rsasig rsa_pss_pss_sha256 rsa_pss_pss_sha384 rsa_pss_pss_sha512 
+ rsasig rsasig rsa_pkcs1_sha256 rsa_pkcs1_sha384 rsa_pkcs1_sha512 
index caaa74a7fd05bdc3c70ab22c72c3d7be20a42723..cc4ab72b5c399a6abe2583d8ec883b57692057b0 100644 (file)
@@ -448,3 +448,42 @@ ccerts
 clientAuth
 notfound
 serverAuth
 clientAuth
 notfound
 serverAuth
+EdDSA
+LocalHost
+LocalPort
+MIO
+NPE
+ReuseAddr
+Unpatched
+aECDSA
+addext
+basicConstraints
+cfdir
+dbtype
+dxh
+ec
+echain
+ecsig
+extendedKeyUsage
+genpkey
+keygen
+kh
+mchain
+mh
+mldsa
+mlsig
+paramgen
+pkcs
+pkeyopt
+pss
+rchain
+rh
+rrdata
+rsae
+rsasig
+sed
+sigalgs
+snitab
+sockport
+subjectAltName
+xh
index 273e7ab6739064770c5d93c1fa0cdb636af6737e..176b0c5d41834c5916abb379558445af8a616066 100644 (file)
@@ -403,8 +403,8 @@ ascii_header_text: ascii_header_text.c $(LIB) $(LIBS)
 sendopts_test: sendopts_test.o $(LIB) $(LIBS)
        $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
 
 sendopts_test: sendopts_test.o $(LIB) $(LIBS)
        $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
 
-dict_sqlite_test: dict_sqlite_test.o dict_sqlite.o $(LIB) $(LIBS)
-       $(CC) $(CFLAGS) -o $@ $@.o dict_sqlite.o $(LIB) $(LIBS) \
+dict_sqlite_test: dict_sqlite_test.o dict_sqlite.o $(PTEST_LIB) $(LIB) $(LIBS)
+       $(CC) $(CFLAGS) -o $@ $@.o dict_sqlite.o $(PTEST_LIB) $(LIB) $(LIBS) \
            $(SYSLIBS) $(AUXLIBS_SQLITE)
 
 pol_stats_test: pol_stats_test.o pol_stats.o $(LIB) $(LIBS)
            $(SYSLIBS) $(AUXLIBS_SQLITE)
 
 pol_stats_test: pol_stats_test.o pol_stats.o $(LIB) $(LIBS)
@@ -1442,8 +1442,14 @@ dict_sqlite_test.o: ../../include/argv.h
 dict_sqlite_test.o: ../../include/check_arg.h
 dict_sqlite_test.o: ../../include/dict.h
 dict_sqlite_test.o: ../../include/msg.h
 dict_sqlite_test.o: ../../include/check_arg.h
 dict_sqlite_test.o: ../../include/dict.h
 dict_sqlite_test.o: ../../include/msg.h
+dict_sqlite_test.o: ../../include/msg_jmp.h
+dict_sqlite_test.o: ../../include/msg_output.h
 dict_sqlite_test.o: ../../include/msg_vstream.h
 dict_sqlite_test.o: ../../include/myflock.h
 dict_sqlite_test.o: ../../include/msg_vstream.h
 dict_sqlite_test.o: ../../include/myflock.h
+dict_sqlite_test.o: ../../include/myrand.h
+dict_sqlite_test.o: ../../include/pmock_expect.h
+dict_sqlite_test.o: ../../include/ptest.h
+dict_sqlite_test.o: ../../include/ptest_main.h
 dict_sqlite_test.o: ../../include/stringops.h
 dict_sqlite_test.o: ../../include/sys_defs.h
 dict_sqlite_test.o: ../../include/vbuf.h
 dict_sqlite_test.o: ../../include/stringops.h
 dict_sqlite_test.o: ../../include/sys_defs.h
 dict_sqlite_test.o: ../../include/vbuf.h
index b546300f9c35bbf1f87c8fee047b9db83cd11d4f..0eae17cd92c854c2f702e236d0b12175873e523d 100644 (file)
@@ -9,7 +9,9 @@
 /*     dict_sqlite_test runs and logs each configured test, reports if
 /*     a test is a PASS or FAIL, and returns an exit status of zero if
 /*     all tests are a PASS.
 /*     dict_sqlite_test runs and logs each configured test, reports if
 /*     a test is a PASS or FAIL, and returns an exit status of zero if
 /*     all tests are a PASS.
-/*
+/* HERMETICITY
+/* .ad
+/* .fi
 /*     Each test creates a temporary test database and a corresponding
 /*     Postfix sqlite client configuration file, both having unique
 /*     names. Otherwise, each test is hermetic.
 /*     Each test creates a temporary test database and a corresponding
 /*     Postfix sqlite client configuration file, both having unique
 /*     names. Otherwise, each test is hermetic.
@@ -18,7 +20,8 @@
 /* .fi
 /*     The Secure Mailer license must be distributed with this software.
 /* AUTHOR(S)
 /* .fi
 /*     The Secure Mailer license must be distributed with this software.
 /* AUTHOR(S)
-/*     Wietse Venema porcupine.org
+/*     Wietse Venema
+/*     porcupine.org
 /*--*/
 
  /*
 /*--*/
 
  /*
 #include <dict_sqlite.h>
 
  /*
 #include <dict_sqlite.h>
 
  /*
-  * TODO(wietse) make this a proper VSTREAM interface or test helper API.
+  * Test libraries.
   */
   */
+#include <ptest.h>
 
 
-/* vstream_swap - capture output for testing */
-
-static void vstream_swap(VSTREAM *one, VSTREAM *two)
-{
-    VSTREAM save;
-
-    save = *one;
-    *one = *two;
-    *two = save;
-}
+#ifdef HAS_SQLITE
 
  /*
   * Override the printable.c module because it may break some tests.
 
  /*
   * Override the printable.c module because it may break some tests.
@@ -74,7 +69,8 @@ char   *printable_except(char *string, int replacement, const char *except)
 
 /* create_and_populate_db - create an empty database and optionally populate */
 
 
 /* create_and_populate_db - create an empty database and optionally populate */
 
-static void create_and_populate_db(char *dbpath, const char *commands)
+static void create_and_populate_db(PTEST_CTX *t, char *dbpath,
+                                          const char *commands)
 {
     int     fd;
 
 {
     int     fd;
 
@@ -83,22 +79,23 @@ static void create_and_populate_db(char *dbpath, const char *commands)
      * adversary cannot rename or remove the file.
      */
     if ((fd = mkstemp(dbpath)) < 0)
      * adversary cannot rename or remove the file.
      */
     if ((fd = mkstemp(dbpath)) < 0)
-       msg_fatal("mkstemp(\"%s\"): %m", dbpath);
+       ptest_fatal(t, "mkstemp(\"%s\"): %m", dbpath);
     if (close(fd) < 0)
     if (close(fd) < 0)
-       msg_fatal("close %s: %m", dbpath);
+       ptest_fatal(t, "close %s: %m", dbpath);
 
     /*
      * TODO(wietse) Open the database file, prepare and execute commands to
      * populate the database, and close the database.
      */
     if (commands) {
 
     /*
      * TODO(wietse) Open the database file, prepare and execute commands to
      * populate the database, and close the database.
      */
     if (commands) {
-       msg_fatal("commands are not yet supported");
+       ptest_fatal(t, "commands are not yet supported");
     }
 }
 
 /* create_and_populate_cf - create sqlite_table(5) configuration file */
 
     }
 }
 
 /* create_and_populate_cf - create sqlite_table(5) configuration file */
 
-static void create_and_populate_cf(char *cfpath, const char *dbpath,
+static void create_and_populate_cf(PTEST_CTX *t, char *cfpath,
+                                          const char *dbpath,
                                           const char *cftext)
 {
     int     fd;
                                           const char *cftext)
 {
     int     fd;
@@ -109,24 +106,26 @@ static void create_and_populate_cf(char *cfpath, const char *dbpath,
      * Assume that an adversary cannot rename or remove the file.
      */
     if ((fd = mkstemp(cfpath)) < 0)
      * Assume that an adversary cannot rename or remove the file.
      */
     if ((fd = mkstemp(cfpath)) < 0)
-       msg_fatal("mkstemp(\"%s\"): %m", cfpath);
+       ptest_fatal(t, "mkstemp(\"%s\"): %m", cfpath);
     if ((fp = vstream_fdopen(fd, O_WRONLY)) == 0)
     if ((fp = vstream_fdopen(fd, O_WRONLY)) == 0)
-       msg_fatal("vstream_fdopen: %m");
+       ptest_fatal(t, "vstream_fdopen: %m");
     (void) vstream_fprintf(fp, "%s\ndbpath = %s\n", cftext, dbpath);
     if (vstream_fclose(fp) != 0)
     (void) vstream_fprintf(fp, "%s\ndbpath = %s\n", cftext, dbpath);
     if (vstream_fclose(fp) != 0)
-       msg_fatal("vstream_fdclose: %m");
+       ptest_fatal(t, "vstream_fdclose: %m");
 }
 
 }
 
+#endif                                 /* HAS_SQLITE */
+
  /*
   * Test structure. Some tests may come their own.
   */
  /*
   * Test structure. Some tests may come their own.
   */
-typedef struct TEST_CASE {
-    const char *label;
-    int     (*action) (const struct TEST_CASE *);
+typedef struct PTEST_CASE {
+    const char *testname;
+    void    (*action) (PTEST_CTX *, const struct PTEST_CASE *);
     const char *commands;              /* commands or null */
     const char *settings;              /* sqlite_table(5) */
     const char *commands;              /* commands or null */
     const char *settings;              /* sqlite_table(5) */
-    const char *exp_warning;           /* substring match or null */
-} TEST_CASE;
+    const char *want_log;              /* substring match or null */
+} PTEST_CASE;
 
 #define PASS    (0)
 #define FAIL    (1)
 
 #define PASS    (0)
 #define FAIL    (1)
@@ -135,112 +134,67 @@ typedef struct TEST_CASE {
 
 /* test_flag_non_recommended_query - flag non-recommended query payloads */
 
 
 /* test_flag_non_recommended_query - flag non-recommended query payloads */
 
-static int test_flag_non_recommended_query(const TEST_CASE *tp)
+static void test_flag_non_recommended_query(PTEST_CTX *t, const PTEST_CASE *tp)
 {
 {
-    static VSTRING *msg_buf;
-    VSTREAM *memory_stream;
+#ifdef HAS_SQLITE
     const char template[] = PATH_TEMPLATE;
     char    dbpath[sizeof(template)];
     char    cfpath[sizeof(template)];
     DICT   *dict;
 
     const char template[] = PATH_TEMPLATE;
     char    dbpath[sizeof(template)];
     char    cfpath[sizeof(template)];
     DICT   *dict;
 
-    if (msg_buf == 0)
-       msg_buf = vstring_alloc(100);
-
-    /* Prepare scaffolding database and configuration files. */
+    /* Prepare scaffolding database and configuration file. */
     memcpy(dbpath, template, sizeof(dbpath));
     memcpy(dbpath, template, sizeof(dbpath));
-    create_and_populate_db(dbpath, tp->commands);
+    create_and_populate_db(t, dbpath, tp->commands);
     memcpy(cfpath, template, sizeof(cfpath));
     memcpy(cfpath, template, sizeof(cfpath));
-    create_and_populate_cf(cfpath, dbpath, tp->settings);
-
-    /* Run the test with custom STDERR stream. */
-    VSTRING_RESET(msg_buf);
-    VSTRING_TERMINATE(msg_buf);
-    if ((memory_stream = vstream_memopen(msg_buf, O_WRONLY)) == 0)
-       msg_fatal("open memory stream: %m");
-    vstream_swap(VSTREAM_ERR, memory_stream);
-    if ((dict = dict_sqlite_open(cfpath, O_RDONLY, DICT_FLAG_UTF8_REQUEST)) != 0)
-       dict_close(dict);
-    vstream_swap(memory_stream, VSTREAM_ERR);
-    if (vstream_fclose(memory_stream))
-       msg_fatal("close memory stream: %m");
+    create_and_populate_cf(t, cfpath, dbpath, tp->settings);
+
+    if (tp->want_log)
+       expect_ptest_log_event(t, tp->want_log);
+    dict = dict_sqlite_open(cfpath, O_RDONLY, DICT_FLAG_UTF8_REQUEST);
+    dict_close(dict);
 
     /* Cleanup scaffolding database and configuration files. */
     if (unlink(dbpath) < 0)
 
     /* Cleanup scaffolding database and configuration files. */
     if (unlink(dbpath) < 0)
-       msg_fatal("unlink %s: %m", dbpath);
+       ptest_error(t, "unlink %s: %m", dbpath);
     if (unlink(cfpath) < 0)
     if (unlink(cfpath) < 0)
-       msg_fatal("unlink %s: %m", cfpath);
-
-    /* Verify the results. */
-    if (tp->exp_warning == 0 && VSTRING_LEN(msg_buf) > 0) {
-       msg_warn("got warning ``%s'', want ``null''", vstring_str(msg_buf));
-       return (FAIL);
-    }
-    if (tp->exp_warning != 0
-       && strstr(vstring_str(msg_buf), tp->exp_warning) == 0) {
-       msg_warn("got warning ``%s'', want ``%s''",
-                vstring_str(msg_buf), tp->exp_warning);
-       return (FAIL);
-    }
-    return (PASS);
+       ptest_error(t, "unlink %s: %m", cfpath);
+#else
+    ptest_skip(t);
+#endif
 }
 
  /*
   * The list of test cases.
   */
 }
 
  /*
   * The list of test cases.
   */
-static const TEST_CASE test_cases[] = {
+static const PTEST_CASE ptestcases[] = {
 
     /*
      * Tests to flag non-recommended query forms. These create an empty test
      * database, and open it with the dict_sqlite client without querying it.
      */
 
     /*
      * Tests to flag non-recommended query forms. These create an empty test
      * database, and open it with the dict_sqlite client without querying it.
      */
-    {.label = "no_dynamic_payload",
+    {.testname = "no_dynamic_payload",
        .action = test_flag_non_recommended_query,
        .settings = "query = select a from b where c = 5",
     },
        .action = test_flag_non_recommended_query,
        .settings = "query = select a from b where c = 5",
     },
-    {.label = "dynamic_payload_inside_recommended_quotes",
+    {.testname = "dynamic_payload_inside_recommended_quotes",
        .action = test_flag_non_recommended_query,
        .settings = "query = select a from b where c = 'xx%syy'",
     },
        .action = test_flag_non_recommended_query,
        .settings = "query = select a from b where c = 'xx%syy'",
     },
-    {.label = "dynamic_payload_without_quotes",
+    {.testname = "dynamic_payload_without_quotes",
        .action = test_flag_non_recommended_query,
        .settings = "query = select s from b where c = xx%syy",
        .action = test_flag_non_recommended_query,
        .settings = "query = select s from b where c = xx%syy",
-       .exp_warning = "contains >%s< without the recommended '' quotes",
+       .want_log = "contains >%s< without the recommended '' quotes",
     },
     },
-    {.label = "payload_inside_double_quotes",
+    {.testname = "payload_inside_double_quotes",
        .action = test_flag_non_recommended_query,
        .settings = "query = select s from b where c = \"xx%syy\"",
        .action = test_flag_non_recommended_query,
        .settings = "query = select s from b where c = \"xx%syy\"",
-       .exp_warning = "contains >%s< without the recommended '' quotes",
+       .want_log = "contains >%s< without the recommended '' quotes",
     },
 
     /*
      * TODO: Tests that actually populate a test database, and that query it
      * with the dict_sqlite client.
      */
     },
 
     /*
      * TODO: Tests that actually populate a test database, and that query it
      * with the dict_sqlite client.
      */
-    {0},
 };
 
 };
 
-int     main(int argc, char **argv)
-{
-    const TEST_CASE *tp;
-    int     pass = 0;
-    int     fail = 0;
-
-    msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR);
-
-    for (tp = test_cases; tp->label != 0; tp++) {
-       int     test_failed;
-
-       msg_info("RUN  %s", tp->label);
-       test_failed = tp->action(tp);
-       if (test_failed) {
-           msg_info("FAIL %s", tp->label);
-           fail++;
-       } else {
-           msg_info("PASS %s", tp->label);
-           pass++;
-       }
-    }
-    msg_info("PASS=%d FAIL=%d", pass, fail);
-    exit(fail != 0);
-}
+#include <ptest_main.h>
index 9a6fe2e18ec9c01e78cd9647c0eb1503bf5d0696..f5d1a390354763d83bccef7ece86b9155bde9005 100644 (file)
@@ -31,7 +31,7 @@ typedef struct PTEST_CASE {
 struct name_test_case {
     const char *label;                 /* identifies test case */
     const char *config;                        /* configuration under test */
 struct name_test_case {
     const char *label;                 /* identifies test case */
     const char *config;                        /* configuration under test */
-    const char *want_warning;          /* expected warning or empty */
+    const char *want_log;              /* expected warning or empty */
     const int want_code;               /* expected code */
 };
 
     const int want_code;               /* expected code */
 };
 
@@ -67,10 +67,10 @@ static void test_hfrom_format_parse(PTEST_CTX *t, const PTEST_CASE *tp)
        PTEST_RUN(t, np->label, {
            int     got_code;
 
        PTEST_RUN(t, np->label, {
            int     got_code;
 
-           if (*np->want_warning)
-               expect_ptest_error(t, np->want_warning);
+           if (*np->want_log)
+               expect_ptest_log_event(t, np->want_log);
            got_code = hfrom_format_parse(np->label, np->config);
            got_code = hfrom_format_parse(np->label, np->config);
-           if (*np->want_warning == 0) {
+           if (*np->want_log == 0) {
                if (got_code != np->want_code) {
                    ptest_error(t, "got code \"%d\", want \"%d\"(%s)",
                               got_code, np->want_code,
                if (got_code != np->want_code) {
                    ptest_error(t, "got code \"%d\", want \"%d\"(%s)",
                               got_code, np->want_code,
@@ -84,7 +84,7 @@ static void test_hfrom_format_parse(PTEST_CTX *t, const PTEST_CASE *tp)
 struct code_test_case {
     const char *label;                 /* identifies test case */
     int     code;                      /* code under test */
 struct code_test_case {
     const char *label;                 /* identifies test case */
     int     code;                      /* code under test */
-    const char *want_warning;          /* expected warning or empty */
+    const char *want_log;              /* expected warning or empty */
     const char *want_name;             /* expected namme */
 };
 
     const char *want_name;             /* expected namme */
 };
 
@@ -115,10 +115,10 @@ static void test_str_hfrom_format_code(PTEST_CTX *t, const PTEST_CASE *tp)
        PTEST_RUN(t, cp->label, {
            const char *got_name;
 
        PTEST_RUN(t, cp->label, {
            const char *got_name;
 
-           if (*cp->want_warning)
-               expect_ptest_error(t, cp->want_warning);
+           if (*cp->want_log)
+               expect_ptest_log_event(t, cp->want_log);
            got_name = str_hfrom_format_code(cp->code);
            got_name = str_hfrom_format_code(cp->code);
-           if (*cp->want_warning == 0) {
+           if (*cp->want_log == 0) {
                if (strcmp(got_name, cp->want_name) != 0) {
                    ptest_error(t, "got name: \"%s\", want: \"%s\"",
                               got_name, cp->want_name);
                if (strcmp(got_name, cp->want_name) != 0) {
                    ptest_error(t, "got name: \"%s\", want: \"%s\"",
                               got_name, cp->want_name);
index 2603ec1eb46396fac372c8542f8a823896123699..79b686472c29b0799ac6d7fb3b30df4658e86cc5 100644 (file)
@@ -20,7 +20,7 @@
   * Patches change both the patchlevel and the release date. Snapshots have no
   * patchlevel; they change the release date only.
   */
   * Patches change both the patchlevel and the release date. Snapshots have no
   * patchlevel; they change the release date only.
   */
-#define MAIL_RELEASE_DATE      "20260403"
+#define MAIL_RELEASE_DATE      "20260406"
 #define MAIL_VERSION_NUMBER    "3.12"
 
 #ifdef SNAPSHOT
 #define MAIL_VERSION_NUMBER    "3.12"
 
 #ifdef SNAPSHOT
index 3326ffe366f6590a5c365b475b597f3ef8cafd88..971f1f2b1ea12209da214675f86b9f05a99e089d 100644 (file)
@@ -336,7 +336,6 @@ extern int tls_log_mask(const char *, const char *);
   */
 struct TLS_APPL_STATE {
     SSL_CTX *ssl_ctx;
   */
 struct TLS_APPL_STATE {
     SSL_CTX *ssl_ctx;
-    SSL_CTX *sni_ctx;
     int     log_mask;
     char   *cache_type;
 };
     int     log_mask;
     char   *cache_type;
 };
@@ -714,13 +713,14 @@ extern int tls_set_my_certificate_key_info(SSL_CTX *, /* All */ const char *,
   */
 extern int TLScontext_index;
 
   */
 extern int TLScontext_index;
 
-extern TLS_APPL_STATE *tls_alloc_app_context(SSL_CTX *, SSL_CTX *, int);
+extern TLS_APPL_STATE *tls_alloc_app_context(SSL_CTX *, int);
 extern TLS_SESS_STATE *tls_alloc_sess_context(int, const char *);
 extern void tls_free_context(TLS_SESS_STATE *);
 extern void tls_check_version(void);
 extern long tls_bug_bits(void);
 extern void tls_print_errors(void);
 extern void tls_info_callback(const SSL *, int, int);
 extern TLS_SESS_STATE *tls_alloc_sess_context(int, const char *);
 extern void tls_free_context(TLS_SESS_STATE *);
 extern void tls_check_version(void);
 extern long tls_bug_bits(void);
 extern void tls_print_errors(void);
 extern void tls_info_callback(const SSL *, int, int);
+extern int tls_cert_cb(SSL *, void *);
 
 #if OPENSSL_VERSION_PREREQ(3,0)
 extern long tls_bio_dump_cb(BIO *, int, const char *, size_t, int, long,
 
 #if OPENSSL_VERSION_PREREQ(3,0)
 extern long tls_bio_dump_cb(BIO *, int, const char *, size_t, int, long,
index 1fad4d7e575def06571766e8524388d4a0b1467d..e7ec3c8e8059d1c0f4b918254d2857ca309480e5 100644 (file)
@@ -918,7 +918,7 @@ TLS_APPL_STATE *tls_client_init(const TLS_CLIENT_INIT_PROPS *props)
      * Allocate an application context, and populate with mandatory protocol
      * and cipher data.
      */
      * Allocate an application context, and populate with mandatory protocol
      * and cipher data.
      */
-    app_ctx = tls_alloc_app_context(client_ctx, 0, log_mask);
+    app_ctx = tls_alloc_app_context(client_ctx, log_mask);
 
     /*
      * The external session cache is implemented by the tlsmgr(8) process.
 
     /*
      * The external session cache is implemented by the tlsmgr(8) process.
index c6403fc9ee20cb412005ea447d6d50a7f42f7cbb..af234a74e54bef0f0bade53bd5ced78dfdde3e10 100644 (file)
@@ -845,18 +845,50 @@ void    tls_pre_jail_init(TLS_ROLE role)
        maps_create(VAR_TLS_SERVER_SNI_MAPS, var_tls_server_sni_maps, flags);
 }
 
        maps_create(VAR_TLS_SERVER_SNI_MAPS, var_tls_server_sni_maps, flags);
 }
 
+int tls_cert_cb(SSL *ssl, void *arg)
+{
+    TLS_SESS_STATE *TLScontext = arg;
+    const char *cp = TLScontext->peer_sni;
+    const char *pem;
+
+    if (!cp || !tls_server_sni_maps)
+       return 1;
+
+    do {
+       /* Don't silently skip maps opened with the wrong flags. */
+       pem = maps_file_find(tls_server_sni_maps, cp, 0);
+    } while (!pem
+            && !tls_server_sni_maps->error
+            && (cp = strchr(cp + 1, '.')) != 0);
+
+    if (!pem) {
+       if (tls_server_sni_maps->error) {
+           msg_warn("%s: %s map lookup problem",
+                    tls_server_sni_maps->title, TLScontext->peer_sni);
+           return 0;
+       }
+       msg_info("TLS SNI %s from %s not matched, using default chain",
+                TLScontext->peer_sni, TLScontext->namaddr);
+        return 1;
+    }
+
+    SSL_certs_clear(ssl);
+    if (tls_load_pem_chain(ssl, pem, TLScontext->peer_sni) == 0)
+        return 1;
+
+    /* errors already logged */
+    return 0;
+}
+
 /* server_sni_callback - process client's SNI extension */
 
 /* server_sni_callback - process client's SNI extension */
 
-static int server_sni_callback(SSL *ssl, int *alert, void *arg)
+static int server_sni_callback(SSL *ssl, int *alert, void *unused)
 {
 {
-    SSL_CTX *sni_ctx = (SSL_CTX *) arg;
     TLS_SESS_STATE *TLScontext = SSL_get_ex_data(ssl, TLScontext_index);
     const char *sni = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
     TLS_SESS_STATE *TLScontext = SSL_get_ex_data(ssl, TLScontext_index);
     const char *sni = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
-    const char *cp = sni;
-    const char *pem;
 
     /* SNI is silently ignored when we don't care or is NULL or empty */
 
     /* SNI is silently ignored when we don't care or is NULL or empty */
-    if (!sni_ctx || !tls_server_sni_maps || !sni || !*sni)
+    if (!tls_server_sni_maps || !sni || !*sni)
        return SSL_TLSEXT_ERR_NOACK;
 
     if (!valid_hostname(sni, DONT_GRIPE)) {
        return SSL_TLSEXT_ERR_NOACK;
 
     if (!valid_hostname(sni, DONT_GRIPE)) {
@@ -885,37 +917,7 @@ static int server_sni_callback(SSL *ssl, int *alert, void *arg)
                 TLScontext->namaddr, TLScontext->peer_sni, sni);
        return SSL_TLSEXT_ERR_NOACK;
     }
                 TLScontext->namaddr, TLScontext->peer_sni, sni);
        return SSL_TLSEXT_ERR_NOACK;
     }
-    do {
-       /* Don't silently skip maps opened with the wrong flags. */
-       pem = maps_file_find(tls_server_sni_maps, cp, 0);
-    } while (!pem
-            && !tls_server_sni_maps->error
-            && (cp = strchr(cp + 1, '.')) != 0);
-
-    if (!pem) {
-       if (tls_server_sni_maps->error) {
-           msg_warn("%s: %s map lookup problem",
-                    tls_server_sni_maps->title, sni);
-           *alert = SSL_AD_INTERNAL_ERROR;
-           return SSL_TLSEXT_ERR_ALERT_FATAL;
-       }
-       msg_info("TLS SNI %s from %s not matched, using default chain",
-                sni, TLScontext->namaddr);
 
 
-       /*
-        * XXX: We could lie and pretend to accept the name, but since we've
-        * previously not implemented the callback (with OpenSSL then
-        * declining the extension), and nothing bad happened, declining it
-        * explicitly should be safe.
-        */
-       return SSL_TLSEXT_ERR_NOACK;
-    }
-    SSL_set_SSL_CTX(ssl, sni_ctx);
-    if (tls_load_pem_chain(ssl, pem, sni) != 0) {
-       /* errors already logged */
-       *alert = SSL_AD_INTERNAL_ERROR;
-       return SSL_TLSEXT_ERR_ALERT_FATAL;
-    }
     TLScontext->peer_sni = mystrdup(sni);
     return SSL_TLSEXT_ERR_OK;
 }
     TLScontext->peer_sni = mystrdup(sni);
     return SSL_TLSEXT_ERR_OK;
 }
@@ -1296,8 +1298,7 @@ void    tls_log_summary(TLS_ROLE role, TLS_USAGE usage, TLS_SESS_STATE *ctx)
 
 /* tls_alloc_app_context - allocate TLS application context */
 
 
 /* tls_alloc_app_context - allocate TLS application context */
 
-TLS_APPL_STATE *tls_alloc_app_context(SSL_CTX *ssl_ctx, SSL_CTX *sni_ctx,
-                                             int log_mask)
+TLS_APPL_STATE *tls_alloc_app_context(SSL_CTX *ssl_ctx, int log_mask)
 {
     TLS_APPL_STATE *app_ctx;
 
 {
     TLS_APPL_STATE *app_ctx;
 
@@ -1306,16 +1307,13 @@ TLS_APPL_STATE *tls_alloc_app_context(SSL_CTX *ssl_ctx, SSL_CTX *sni_ctx,
     /* See portability note below with other memset() call. */
     memset((void *) app_ctx, 0, sizeof(*app_ctx));
     app_ctx->ssl_ctx = ssl_ctx;
     /* See portability note below with other memset() call. */
     memset((void *) app_ctx, 0, sizeof(*app_ctx));
     app_ctx->ssl_ctx = ssl_ctx;
-    app_ctx->sni_ctx = sni_ctx;
     app_ctx->log_mask = log_mask;
 
     /* See also: cache purging code in tls_set_ciphers(). */
     app_ctx->cache_type = 0;
 
     app_ctx->log_mask = log_mask;
 
     /* See also: cache purging code in tls_set_ciphers(). */
     app_ctx->cache_type = 0;
 
-    if (tls_server_sni_maps) {
+    if (tls_server_sni_maps)
        SSL_CTX_set_tlsext_servername_callback(ssl_ctx, server_sni_callback);
        SSL_CTX_set_tlsext_servername_callback(ssl_ctx, server_sni_callback);
-       SSL_CTX_set_tlsext_servername_arg(ssl_ctx, (void *) sni_ctx);
-    }
     return (app_ctx);
 }
 
     return (app_ctx);
 }
 
@@ -1325,8 +1323,6 @@ void    tls_free_app_context(TLS_APPL_STATE *app_ctx)
 {
     if (app_ctx->ssl_ctx)
        SSL_CTX_free(app_ctx->ssl_ctx);
 {
     if (app_ctx->ssl_ctx)
        SSL_CTX_free(app_ctx->ssl_ctx);
-    if (app_ctx->sni_ctx)
-       SSL_CTX_free(app_ctx->sni_ctx);
     if (app_ctx->cache_type)
        myfree(app_ctx->cache_type);
     myfree((void *) app_ctx);
     if (app_ctx->cache_type)
        myfree(app_ctx->cache_type);
     myfree((void *) app_ctx);
index 7dd3a0169652e27f04b4b2edc78cfd81b5d3efaf..d38b3b3315df4e2c968bce64b820ffa9950ae96d 100644 (file)
@@ -425,7 +425,6 @@ static int trust_server_ccerts(X509_STORE_CTX *ctx, void *unused)
 TLS_APPL_STATE *tls_server_init(const TLS_SERVER_INIT_PROPS *props)
 {
     SSL_CTX *server_ctx;
 TLS_APPL_STATE *tls_server_init(const TLS_SERVER_INIT_PROPS *props)
 {
     SSL_CTX *server_ctx;
-    SSL_CTX *sni_ctx;
     X509_STORE *cert_store;
     long    off = 0;
     int     verify_flags = SSL_VERIFY_NONE;
     X509_STORE *cert_store;
     long    off = 0;
     int     verify_flags = SSL_VERIFY_NONE;
@@ -524,24 +523,15 @@ TLS_APPL_STATE *tls_server_init(const TLS_SERVER_INIT_PROPS *props)
        tls_print_errors();
        return (0);
     }
        tls_print_errors();
        return (0);
     }
-    sni_ctx = SSL_CTX_new(TLS_server_method());
-    if (sni_ctx == 0) {
-       SSL_CTX_free(server_ctx);
-       msg_warn("cannot allocate server SNI SSL_CTX: disabling TLS support");
-       tls_print_errors();
-       return (0);
-    }
 #ifdef SSL_SECOP_PEER
     /* Backwards compatible security as a base for opportunistic TLS. */
     SSL_CTX_set_security_level(server_ctx, 0);
 #ifdef SSL_SECOP_PEER
     /* Backwards compatible security as a base for opportunistic TLS. */
     SSL_CTX_set_security_level(server_ctx, 0);
-    SSL_CTX_set_security_level(sni_ctx, 0);
 #endif
 
     /*
      * See the verify callback in tls_verify.c
      */
     SSL_CTX_set_verify_depth(server_ctx, props->verifydepth + 1);
 #endif
 
     /*
      * See the verify callback in tls_verify.c
      */
     SSL_CTX_set_verify_depth(server_ctx, props->verifydepth + 1);
-    SSL_CTX_set_verify_depth(sni_ctx, props->verifydepth + 1);
 
     /*
      * The session cache is implemented by the tlsmgr(8) server.
 
     /*
      * The session cache is implemented by the tlsmgr(8) server.
@@ -630,8 +620,6 @@ TLS_APPL_STATE *tls_server_init(const TLS_SERVER_INIT_PROPS *props)
        SSL_CTX_set_options(server_ctx, TLS_SSL_OP_PROTOMASK(protomask));
     SSL_CTX_set_min_proto_version(server_ctx, min_proto);
     SSL_CTX_set_max_proto_version(server_ctx, max_proto);
        SSL_CTX_set_options(server_ctx, TLS_SSL_OP_PROTOMASK(protomask));
     SSL_CTX_set_min_proto_version(server_ctx, min_proto);
     SSL_CTX_set_max_proto_version(server_ctx, max_proto);
-    SSL_CTX_set_min_proto_version(sni_ctx, min_proto);
-    SSL_CTX_set_max_proto_version(sni_ctx, max_proto);
 
     /*
      * Some sites may want to give the client less rope. On the other hand,
 
     /*
      * Some sites may want to give the client less rope. On the other hand,
@@ -642,17 +630,11 @@ TLS_APPL_STATE *tls_server_init(const TLS_SERVER_INIT_PROPS *props)
     if (var_tls_preempt_clist)
        SSL_CTX_set_options(server_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
 
     if (var_tls_preempt_clist)
        SSL_CTX_set_options(server_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
 
-    /* Done with server_ctx options, clone to sni_ctx */
-    SSL_CTX_clear_options(sni_ctx, ~0);
-    SSL_CTX_set_options(sni_ctx, SSL_CTX_get_options(server_ctx));
-
     /*
      * Set the call-back routine to debug handshake progress.
      */
     /*
      * Set the call-back routine to debug handshake progress.
      */
-    if (log_mask & TLS_LOG_DEBUG) {
+    if (log_mask & TLS_LOG_DEBUG)
        SSL_CTX_set_info_callback(server_ctx, tls_info_callback);
        SSL_CTX_set_info_callback(server_ctx, tls_info_callback);
-       SSL_CTX_set_info_callback(sni_ctx, tls_info_callback);
-    }
 
     /*
      * Load the CA public key certificates for both the server cert and for
 
     /*
      * Load the CA public key certificates for both the server cert and for
@@ -669,7 +651,6 @@ TLS_APPL_STATE *tls_server_init(const TLS_SERVER_INIT_PROPS *props)
                                    props->CAfile, props->CApath) < 0) {
        /* tls_set_ca_certificate_info() already logs a warning. */
        SSL_CTX_free(server_ctx);               /* 200411 */
                                    props->CAfile, props->CApath) < 0) {
        /* tls_set_ca_certificate_info() already logs a warning. */
        SSL_CTX_free(server_ctx);               /* 200411 */
-       SSL_CTX_free(sni_ctx);
        return (0);
     }
 
        return (0);
     }
 
@@ -678,15 +659,6 @@ TLS_APPL_STATE *tls_server_init(const TLS_SERVER_INIT_PROPS *props)
      * for the client, they're good enough for us.
      */
     tls_enable_server_rpk(server_ctx, NULL);
      * for the client, they're good enough for us.
      */
     tls_enable_server_rpk(server_ctx, NULL);
-    tls_enable_server_rpk(sni_ctx, NULL);
-
-    /*
-     * Upref and share the cert store.  Sadly we can't yet use
-     * SSL_CTX_set1_cert_store(3) which was added in OpenSSL 1.1.0.
-     */
-    cert_store = SSL_CTX_get_cert_store(server_ctx);
-    X509_STORE_up_ref(cert_store);
-    SSL_CTX_set_cert_store(sni_ctx, cert_store);
 
     /*
      * Load the server public key certificate and private key from file and
 
     /*
      * Load the server public key certificate and private key from file and
@@ -710,7 +682,6 @@ TLS_APPL_STATE *tls_server_init(const TLS_SERVER_INIT_PROPS *props)
                                        props->eckey_file) < 0) {
        /* tls_set_my_certificate_key_info() already logs a warning. */
        SSL_CTX_free(server_ctx);               /* 200411 */
                                        props->eckey_file) < 0) {
        /* tls_set_my_certificate_key_info() already logs a warning. */
        SSL_CTX_free(server_ctx);               /* 200411 */
-       SSL_CTX_free(sni_ctx);
        return (0);
     }
 
        return (0);
     }
 
@@ -725,7 +696,6 @@ TLS_APPL_STATE *tls_server_init(const TLS_SERVER_INIT_PROPS *props)
     if (*props->dh1024_param_file != 0)
        tls_set_dh_from_file(props->dh1024_param_file);
     tls_tmp_dh(server_ctx, 1);
     if (*props->dh1024_param_file != 0)
        tls_set_dh_from_file(props->dh1024_param_file);
     tls_tmp_dh(server_ctx, 1);
-    tls_tmp_dh(sni_ctx, 1);
 
     /*
      * Enable EECDH if available, errors are not fatal, we just keep going
 
     /*
      * Enable EECDH if available, errors are not fatal, we just keep going
@@ -734,7 +704,6 @@ TLS_APPL_STATE *tls_server_init(const TLS_SERVER_INIT_PROPS *props)
      * unified "groups" list.
      */
     tls_auto_groups(server_ctx, var_tls_eecdh_auto, var_tls_ffdhe_auto);
      * unified "groups" list.
      */
     tls_auto_groups(server_ctx, var_tls_eecdh_auto, var_tls_ffdhe_auto);
-    tls_auto_groups(sni_ctx, var_tls_eecdh_auto, var_tls_ffdhe_auto);
 
     /*
      * If we want to check client certificates, we have to indicate it in
 
     /*
      * If we want to check client certificates, we have to indicate it in
@@ -760,8 +729,6 @@ TLS_APPL_STATE *tls_server_init(const TLS_SERVER_INIT_PROPS *props)
        verify_flags = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE;
     SSL_CTX_set_verify(server_ctx, verify_flags,
                       tls_verify_certificate_callback);
        verify_flags = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE;
     SSL_CTX_set_verify(server_ctx, verify_flags,
                       tls_verify_certificate_callback);
-    SSL_CTX_set_verify(sni_ctx, verify_flags,
-                      tls_verify_certificate_callback);
     if (props->ask_ccert && *props->CAfile) {
        STACK_OF(X509_NAME) *calist = SSL_load_client_CA_file(props->CAfile);
 
     if (props->ask_ccert && *props->CAfile) {
        STACK_OF(X509_NAME) *calist = SSL_load_client_CA_file(props->CAfile);
 
@@ -772,29 +739,16 @@ TLS_APPL_STATE *tls_server_init(const TLS_SERVER_INIT_PROPS *props)
            tls_print_errors();
        }
        SSL_CTX_set_client_CA_list(server_ctx, calist);
            tls_print_errors();
        }
        SSL_CTX_set_client_CA_list(server_ctx, calist);
-
-       if (calist != 0 && sk_X509_NAME_num(calist) > 0) {
-           calist = SSL_dup_CA_list(calist);
-
-           if (calist == 0) {
-               msg_warn("error duplicating client CA names for SNI");
-               tls_print_errors();
-           } else {
-               SSL_CTX_set_client_CA_list(sni_ctx, calist);
-           }
-       }
     }
 
     }
 
-    if (props->ask_ccert && var_tls_srvr_ccerts) {
+    if (props->ask_ccert && var_tls_srvr_ccerts)
        SSL_CTX_set_cert_verify_callback(server_ctx, trust_server_ccerts, NULL);
        SSL_CTX_set_cert_verify_callback(server_ctx, trust_server_ccerts, NULL);
-       SSL_CTX_set_cert_verify_callback(sni_ctx, trust_server_ccerts, NULL);
-    }
 
     /*
      * Initialize our own TLS server handle, before diving into the details
      * of TLS session cache management.
      */
 
     /*
      * Initialize our own TLS server handle, before diving into the details
      * of TLS session cache management.
      */
-    app_ctx = tls_alloc_app_context(server_ctx, sni_ctx, log_mask);
+    app_ctx = tls_alloc_app_context(server_ctx, log_mask);
 
     if (cachable || ticketable || props->set_sessid) {
 
 
     if (cachable || ticketable || props->set_sessid) {
 
@@ -915,6 +869,9 @@ TLS_SESS_STATE *tls_server_start(const TLS_SERVER_START_PROPS *props)
        return (0);
     }
 
        return (0);
     }
 
+    /* Configure the SNI-based certificate selection callback */
+    SSL_set_cert_cb(TLScontext->con, tls_cert_cb, TLScontext);
+
     /*
      * When encryption is mandatory use the 80-bit plus OpenSSL security
      * level.
     /*
      * When encryption is mandatory use the 80-bit plus OpenSSL security
      * level.