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.
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.
* 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
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:
<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
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>
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>
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
<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
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>
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>
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
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
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
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)
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/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 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.
/* .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>
/*
- * 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.
/* 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;
* 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)
- 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) {
- 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 */
-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;
* 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)
- 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)
- msg_fatal("vstream_fdclose: %m");
+ ptest_fatal(t, "vstream_fdclose: %m");
}
+#endif /* HAS_SQLITE */
+
/*
* 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 *exp_warning; /* substring match or null */
-} TEST_CASE;
+ const char *want_log; /* substring match or null */
+} PTEST_CASE;
#define PASS (0)
#define FAIL (1)
/* 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;
- 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));
- create_and_populate_db(dbpath, tp->commands);
+ create_and_populate_db(t, dbpath, tp->commands);
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)
- msg_fatal("unlink %s: %m", dbpath);
+ ptest_error(t, "unlink %s: %m", dbpath);
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.
*/
-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.
*/
- {.label = "no_dynamic_payload",
+ {.testname = "no_dynamic_payload",
.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'",
},
- {.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",
- .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\"",
- .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.
*/
- {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>
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 */
};
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);
- 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,
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 */
};
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);
- 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);
* 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
*/
struct TLS_APPL_STATE {
SSL_CTX *ssl_ctx;
- SSL_CTX *sni_ctx;
int log_mask;
char *cache_type;
};
*/
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 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,
* 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.
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 */
-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);
- const char *cp = sni;
- const char *pem;
/* 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)) {
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;
}
/* 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;
/* 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;
- 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_arg(ssl_ctx, (void *) sni_ctx);
- }
return (app_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);
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;
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);
- 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);
- SSL_CTX_set_verify_depth(sni_ctx, props->verifydepth + 1);
/*
* The session cache is implemented by the tlsmgr(8) server.
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,
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.
*/
- 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(sni_ctx, tls_info_callback);
- }
/*
* Load the CA public key certificates for both the server cert and for
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);
}
* 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
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);
}
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
* 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
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);
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(sni_ctx, trust_server_ccerts, NULL);
- }
/*
* 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) {
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.