From 22b2d17a6a86b8a61b4f76c05fb2c9c888670743 Mon Sep 17 00:00:00 2001
From: Wietse Z Venema 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.
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 +ML-DSA. 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 postfix-users 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 ML-DSA 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 ML-DSA. 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 ML-DSA +support, and is expected to work with OpenSSL 4.0, which should be free of +the underlying defect when initially released.
+Topics covered in this section:
diff --git a/postfix/html/postconf.5.html b/postfix/html/postconf.5.html index 771534b7e..70e3a587b 100644 --- a/postfix/html/postconf.5.html +++ b/postfix/html/postconf.5.html @@ -21883,6 +21883,25 @@ required in the TLS SNI extension). but here scoped to just TLS connections in which the client sends a matching SNI domain name. +Unpatched versions of OpenSSL versions 3.2 through 3.6, may not +correctly handle SNI certificate chains for private key algorithms +other than:
+ +The issue does not arise if you limit your SNI chains to just +these algorithms. Otherwise, inclusion of, e.g., ML-DSA +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.
+Example:
diff --git a/postfix/man/man5/postconf.5 b/postfix/man/man5/postconf.5 index 4d881b6ef..cf5256093 100644 --- a/postfix/man/man5/postconf.5 +++ b/postfix/man/man5/postconf.5 @@ -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 +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 diff --git a/postfix/proto/TLS_README.html b/postfix/proto/TLS_README.html index 879aae52e..fefe0e807 100644 --- a/postfix/proto/TLS_README.html +++ b/postfix/proto/TLS_README.html @@ -145,6 +145,8 @@ key configurationMiscellaneous server controls + Testing SNI support + 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. +
Testing SNI support
+ +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 +ML-DSA. 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 postfix-users 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 ML-DSA 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 ML-DSA. 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 ML-DSA +support, and is expected to work with OpenSSL 4.0, which should be free of +the underlying defect when initially released.
+SMTP Client specific settings
Topics covered in this section:
diff --git a/postfix/proto/postconf.proto b/postfix/proto/postconf.proto index 8f7567434..253fc6ebc 100644 --- a/postfix/proto/postconf.proto +++ b/postfix/proto/postconf.proto @@ -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. +Unpatched versions of OpenSSL versions 3.2 through 3.6, may not +correctly handle SNI certificate chains for private key algorithms +other than:
+ ++
+ +- RSA +
- ECDSA +
- DSA (obsolete and best avoided) +
- EdDSA (Ed25519 or Ed448). +
The issue does not arise if you limit your SNI chains to just +these algorithms. Otherwise, inclusion of, e.g., ML-DSA +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.
+Example:
diff --git a/postfix/proto/stop.double-history b/postfix/proto/stop.double-history index ab1eb020b..522a03470 100644 --- a/postfix/proto/stop.double-history +++ b/postfix/proto/stop.double-history @@ -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 + Dukhovni Files proto postconf proto proto TLS_README html diff --git a/postfix/proto/stop.double-proto-html b/postfix/proto/stop.double-proto-html index 25c2fad2e..90491409b 100644 --- a/postfix/proto/stop.double-proto-html +++ b/postfix/proto/stop.double-proto-html @@ -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 + 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 diff --git a/postfix/proto/stop.spell-proto-html b/postfix/proto/stop.spell-proto-html index caaa74a7f..cc4ab72b5 100644 --- a/postfix/proto/stop.spell-proto-html +++ b/postfix/proto/stop.spell-proto-html @@ -448,3 +448,42 @@ ccerts 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 diff --git a/postfix/src/global/Makefile.in b/postfix/src/global/Makefile.in index 273e7ab67..176b0c5d4 100644 --- a/postfix/src/global/Makefile.in +++ b/postfix/src/global/Makefile.in @@ -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) -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) @@ -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/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 diff --git a/postfix/src/global/dict_sqlite_test.c b/postfix/src/global/dict_sqlite_test.c index b546300f9..0eae17cd9 100644 --- a/postfix/src/global/dict_sqlite_test.c +++ b/postfix/src/global/dict_sqlite_test.c @@ -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. -/* +/* 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. @@ -18,7 +20,8 @@ /* .fi /* The Secure Mailer license must be distributed with this software. /* AUTHOR(S) -/* Wietse Venema porcupine.org +/* Wietse Venema +/* porcupine.org /*--*/ /* @@ -41,19 +44,11 @@ #include/* - * TODO(wietse) make this a proper VSTREAM interface or test helper API. + * Test libraries. */ +#include -/* 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. @@ -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 */ -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; @@ -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) - 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; @@ -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) - 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) @@ -135,112 +134,67 @@ typedef struct TEST_CASE { /* 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 diff --git a/postfix/src/global/hfrom_format_test.c b/postfix/src/global/hfrom_format_test.c index 9a6fe2e18..f5d1a3903 100644 --- a/postfix/src/global/hfrom_format_test.c +++ b/postfix/src/global/hfrom_format_test.c @@ -31,7 +31,7 @@ typedef struct PTEST_CASE { 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 */ }; @@ -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; - 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, @@ -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 */ - const char *want_warning; /* expected warning or empty */ + const char *want_log; /* expected warning or empty */ 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; - 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); diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index 2603ec1eb..79b686472 100644 --- a/postfix/src/global/mail_version.h +++ b/postfix/src/global/mail_version.h @@ -20,7 +20,7 @@ * 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 diff --git a/postfix/src/tls/tls.h b/postfix/src/tls/tls.h index 3326ffe36..971f1f2b1 100644 --- a/postfix/src/tls/tls.h +++ b/postfix/src/tls/tls.h @@ -336,7 +336,6 @@ extern int tls_log_mask(const char *, const char *); */ struct TLS_APPL_STATE { SSL_CTX *ssl_ctx; - SSL_CTX *sni_ctx; 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 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, diff --git a/postfix/src/tls/tls_client.c b/postfix/src/tls/tls_client.c index 1fad4d7e5..e7ec3c8e8 100644 --- a/postfix/src/tls/tls_client.c +++ b/postfix/src/tls/tls_client.c @@ -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. */ - 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. diff --git a/postfix/src/tls/tls_misc.c b/postfix/src/tls/tls_misc.c index c6403fc9e..af234a74e 100644 --- a/postfix/src/tls/tls_misc.c +++ b/postfix/src/tls/tls_misc.c @@ -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); } +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)) { @@ -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; } - 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; } @@ -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_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; @@ -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; - 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); } @@ -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->sni_ctx) - SSL_CTX_free(app_ctx->sni_ctx); if (app_ctx->cache_type) myfree(app_ctx->cache_type); myfree((void *) app_ctx); diff --git a/postfix/src/tls/tls_server.c b/postfix/src/tls/tls_server.c index 7dd3a0169..d38b3b331 100644 --- a/postfix/src/tls/tls_server.c +++ b/postfix/src/tls/tls_server.c @@ -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; - SSL_CTX *sni_ctx; 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); } - 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. @@ -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_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, @@ -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); - /* 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 @@ -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 */ - SSL_CTX_free(sni_ctx); 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); - 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 @@ -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 */ - SSL_CTX_free(sni_ctx); 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); - tls_tmp_dh(sni_ctx, 1); /* * 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); - 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 @@ -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); - 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); @@ -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); - - 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) { @@ -915,6 +869,9 @@ TLS_SESS_STATE *tls_server_start(const TLS_SERVER_START_PROPS *props) 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. -- 2.47.3