From b15cd2bc199ccf7764871bfa268ada4700187fe5 Mon Sep 17 00:00:00 2001 From: Wietse Venema Date: Fri, 5 Apr 2013 22:58:59 -0400 Subject: [PATCH] postfix-2.11-20130405-nonprod --- postfix/.indent.pro | 2 + postfix/HISTORY | 16 ++ postfix/README_FILES/TLS_README | 33 ++- postfix/WISHLIST | 26 ++ postfix/html/TLS_README.html | 48 +++- postfix/html/lmtp.8.html | 162 ++++++----- postfix/html/postconf.5.html | 60 ++++ postfix/html/smtp.8.html | 162 ++++++----- postfix/makedefs | 2 +- postfix/man/man5/postconf.5 | 52 ++++ postfix/man/man8/smtp.8 | 5 + postfix/mantools/postlink | 2 + postfix/proto/TLS_README.html | 48 +++- postfix/proto/postconf.proto | 54 +++- postfix/proto/stop | 3 + postfix/src/global/mail_params.h | 6 + postfix/src/global/mail_version.h | 2 +- postfix/src/smtp/Makefile.in | 1 + postfix/src/smtp/lmtp_params.c | 1 + postfix/src/smtp/smtp.c | 6 + postfix/src/smtp/smtp.h | 17 +- postfix/src/smtp/smtp_params.c | 1 + postfix/src/smtp/smtp_proto.c | 22 +- postfix/src/smtp/smtp_session.c | 34 +-- postfix/src/smtp/smtp_state.c | 4 + postfix/src/smtp/smtp_tls_sess.c | 406 ++++++++++++++++++-------- postfix/src/tls/Makefile.in | 19 +- postfix/src/tls/tls.h | 100 ++++++- postfix/src/tls/tls_client.c | 42 +-- postfix/src/tls/tls_dane.c | 458 ++++++++++++++++++++++++++++++ postfix/src/tls/tls_fprint.c | 64 ++++- postfix/src/tls/tls_misc.c | 5 +- postfix/src/tls/tls_verify.c | 264 ++++++++++++++++- postfix/src/util/argv.c | 21 ++ postfix/src/util/argv.h | 1 + 35 files changed, 1789 insertions(+), 360 deletions(-) create mode 100644 postfix/src/tls/tls_dane.c diff --git a/postfix/.indent.pro b/postfix/.indent.pro index 8979235c9..5774f67f2 100644 --- a/postfix/.indent.pro +++ b/postfix/.indent.pro @@ -294,6 +294,7 @@ -TTLS_APPL_STATE -TTLS_CLIENT_INIT_PROPS -TTLS_CLIENT_START_PROPS +-TTLS_DANE -TTLS_PRNG_SEED_INFO -TTLS_PRNG_SRC -TTLS_SCACHE @@ -301,6 +302,7 @@ -TTLS_SERVER_INIT_PROPS -TTLS_SERVER_START_PROPS -TTLS_SESS_STATE +-TTLS_TLSA -TTLS_VINFO -TTLScontext_t -TTOK822 diff --git a/postfix/HISTORY b/postfix/HISTORY index b2e474cd6..d40c98664 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -18410,3 +18410,19 @@ Apologies for any names omitted. Documentation: in smtpd.c, the comment that justifies the 454 reply for "TLS unavailable" cited the wrong RFC. + +20130405 + + Feature: support for trust anchors, i.e. CA certificates + or public keys that will be used instead of conventional + root certificates, and revised fingerprint support. This + can be used by itself, and this provides support for an + upcoming DANE implementation. Victor Duchovni. Files: + mantools/postlink, proto/TLS_README.html, proto/postconf.proto, + src/global/mail_params.h, src/smtp/lmtp_params.c, + src/smtp/smtp.c, src/smtp/smtp.h, src/smtp/smtp_params.c, + src/smtp/smtp_proto.c, src/smtp/smtp_session.c, + src/smtp/smtp_state.c, src/smtp/smtp_tls_sess.c, + src/tls/Makefile.in, src/tls/tls.h, src/tls/tls_client.c, + src/tls/tls_dane.c, src/tls/tls_fprint.c, src/tls/tls_misc.c, + src/tls/tls_verify.c, src/util/argv.c, src/util/argv.h. diff --git a/postfix/README_FILES/TLS_README b/postfix/README_FILES/TLS_README index 893b2c95b..377c061ae 100644 --- a/postfix/README_FILES/TLS_README +++ b/postfix/README_FILES/TLS_README @@ -908,6 +908,17 @@ extension are used to verify the remote SMTP server name. If no DNS names are specified, the certificate CommonName is checked. If you want mandatory encryption without server certificate verification, see above. +With Postfix >= 2.11 the "smtp_tls_trust_anchor_file" parameter or more +typically the corresponding per-destination "tafile" attribute optionally +modifies trust chain verification. If the parameter is not empty the root CAs +in CAfile and CApath are no longer trusted. Rather, the Postfix SMTP client +will only trust certificate-chains signed by one of the trust-anchors contained +in the chosen files. The specified trust-anchor certificates and public keys +are not subject to expiration, and need not be (self-signed) root CAs. They +may, if desired, be intermediate certificates. Therefore, these certificates +also may be found "in the middle" of the trust chain presented by the remote +SMTP server, and any untrusted issuing parent certificates will be ignored. + Despite the potential for eliminating "man-in-the-middle" and other attacks, mandatory certificate trust chain and subject name verification is not viable as a default Internet mail delivery policy. Most MX hosts do not support TLS at @@ -958,6 +969,17 @@ extension are used to verify the remote SMTP server name. If no DNS names are specified, the CommonName is checked. If you want mandatory encryption without server certificate verification, see above. +With Postfix >= 2.11 the "smtp_tls_trust_anchor_file" parameter or more +typically the corresponding per-destination "tafile" attribute optionally +modifies trust chain verification. If the parameter is not empty the root CAs +in CAfile and CApath are no longer trusted. Rather, the Postfix SMTP client +will only trust certificate-chains signed by one of the trust-anchors contained +in the chosen files. The specified trust-anchor certificates and public keys +are not subject to expiration, and need not be (self-signed) root CAs. They +may, if desired, be intermediate certificates. Therefore, these certificates +also may be found "in the middle" of the trust chain presented by the remote +SMTP server, and any untrusted issuing parent certificates will be ignored. + Despite the potential for eliminating "man-in-the-middle" and other attacks, mandatory secure server certificate verification is not viable as a default Internet mail delivery policy. Most MX hosts do not support TLS at all, and a @@ -1360,14 +1382,21 @@ vveerriiffyy validated (not expired or revoked, and signed by a trusted certificate authority), and if the server certificate name matches the optional "match" attribute (or the main.cf smtp_tls_verify_cert_match parameter value when - no optional "match" attribute is specified). + no optional "match" attribute is specified). With Postfix >= 2.11 the + "tafile" attribute optionally modifies trust chain verification in the same + manner as the "smtp_tls_trust_anchor_file" parameter. The "tafile" + attribute may be specified multiple times to load multiple trust-anchor + files. sseeccuurree Secure certificate verification. Mail is delivered only if the TLS handshake succeeds, if the remote SMTP server certificate can be validated (not expired or revoked, and signed by a trusted certificate authority), and if the server certificate name matches the optional "match" attribute (or the main.cf smtp_tls_secure_cert_match parameter value when no optional - "match" attribute is specified). + "match" attribute is specified). With Postfix >= 2.11 the "tafile" + attribute optionally modifies trust chain verification in the same manner + as the "smtp_tls_trust_anchor_file" parameter. The "tafile" attribute may + be specified multiple times to load multiple trust-anchor files. Notes: * The "match" attribute is especially useful to verify TLS certificates for diff --git a/postfix/WISHLIST b/postfix/WISHLIST index b61e913e7..7170996e4 100644 --- a/postfix/WISHLIST +++ b/postfix/WISHLIST @@ -11,6 +11,32 @@ Wish list: Begin code revision, after DANE support stabilizes. This should be one pass that changes only names and no code. + Run new source files through ccformat. If I do it now, + almost every block of code or comments is changed. Having + different formatting styles in the same project is PROBLEMATIC. + There is a reason why ccformat is included with source code. + + Embed all statement-like macros in do { ... } while (0). + This is especially necessary with macros that contain an + "if" statement, or that contain multiple statements. + + Spell-check, double-word check, and HTML validator check. + + Use make(1) and cc(1) to convert the C++ like templates + into debuggable source code, such that each statement has + its own distinct line number (what a revolutionary idea). + + All source code must specify its original author and + license statement. Some code modules specify Lutz Jaenicke + as the original author and fall under his liberal license. + Code that is added to such a module has the same license + (or at least something that is not more restrictive). Code + modules without input from Lutz Jaenicke must state its + original author and license (preferably no more restrictive + than Postfix's own license). Currently, too many files list + Wietse as the original author, and Lutz Jaenicke's license, + which is wrong. + Generally, macro and function names should make a program more clear, not merely reduce the number of programmer keystrokes; similar considerations hold for variable names diff --git a/postfix/html/TLS_README.html b/postfix/html/TLS_README.html index aaeb6296f..a8a1998ac 100644 --- a/postfix/html/TLS_README.html +++ b/postfix/html/TLS_README.html @@ -1239,6 +1239,19 @@ DNS names are specified, the certificate CommonName is checked. If you want mandatory encryption without server certificate verification, see above.

+

With Postfix ≥ 2.11 the "smtp_tls_trust_anchor_file" parameter +or more typically the corresponding per-destination "tafile" attribute +optionally modifies trust chain verification. If the parameter is +not empty the root CAs in CAfile and CApath are no longer trusted. +Rather, the Postfix SMTP client will only trust certificate-chains +signed by one of the trust-anchors contained in the chosen files. +The specified trust-anchor certificates and public keys are not +subject to expiration, and need not be (self-signed) root CAs. They +may, if desired, be intermediate certificates. Therefore, these +certificates also may be found "in the middle" of the trust chain +presented by the remote SMTP server, and any untrusted issuing +parent certificates will be ignored.

+

Despite the potential for eliminating "man-in-the-middle" and other attacks, mandatory certificate trust chain and subject name verification is not viable as a default Internet mail delivery policy. Most MX hosts @@ -1300,6 +1313,19 @@ specified, the CommonName is checked. If you want mandatory encryption without server certificate verification, see above.

+

With Postfix ≥ 2.11 the "smtp_tls_trust_anchor_file" parameter +or more typically the corresponding per-destination "tafile" attribute +optionally modifies trust chain verification. If the parameter is +not empty the root CAs in CAfile and CApath are no longer trusted. +Rather, the Postfix SMTP client will only trust certificate-chains +signed by one of the trust-anchors contained in the chosen files. +The specified trust-anchor certificates and public keys are not +subject to expiration, and need not be (self-signed) root CAs. They +may, if desired, be intermediate certificates. Therefore, these +certificates also may be found "in the middle" of the trust chain +presented by the remote SMTP server, and any untrusted issuing +parent certificates will be ignored.

+

Despite the potential for eliminating "man-in-the-middle" and other attacks, mandatory secure server certificate verification is not viable as a default Internet mail delivery policy. Most MX hosts @@ -1806,12 +1832,16 @@ digits.

verify
Mandatory server certificate verification. Mail is delivered only if the -TLS handshake -succeeds, if the remote SMTP server certificate can be validated (not -expired or revoked, and signed by a trusted certificate authority), and -if the server certificate name matches the optional "match" attribute (or -the main.cf smtp_tls_verify_cert_match parameter value when no optional -"match" attribute is specified).
+TLS handshake succeeds, if the remote SMTP server certificate can +be validated (not expired or revoked, and signed by a trusted +certificate authority), and if the server certificate name matches +the optional "match" attribute (or the main.cf smtp_tls_verify_cert_match +parameter value when no optional "match" attribute is specified). +With Postfix ≥ 2.11 the "tafile" attribute optionally modifies +trust chain verification in the same manner as the +"smtp_tls_trust_anchor_file" parameter. The "tafile" attribute +may be specified multiple times to load multiple trust-anchor +files.
secure
Secure certificate verification. Mail is delivered only if the TLS handshake succeeds, @@ -1819,7 +1849,11 @@ if the remote SMTP server certificate can be validated (not expired or revoked, and signed by a trusted certificate authority), and if the server certificate name matches the optional "match" attribute (or the main.cf smtp_tls_secure_cert_match parameter value when no optional -"match" attribute is specified).
+"match" attribute is specified). With Postfix ≥ 2.11 the "tafile" +attribute optionally modifies trust chain verification in the same manner +as the "smtp_tls_trust_anchor_file" parameter. The "tafile" attribute +may be specified multiple times to load multiple trust-anchor +files. diff --git a/postfix/html/lmtp.8.html b/postfix/html/lmtp.8.html index 20bed174b..7db52e027 100644 --- a/postfix/html/lmtp.8.html +++ b/postfix/html/lmtp.8.html @@ -587,29 +587,35 @@ SMTP(8) SMTP(8) List or bit-mask of OpenSSL bug work-arounds to disable. + Available in Postfix version 2.11 and later: + + smtp_tls_trust_anchor_file (empty) + Zero or more PEM-format files with trust-anchor + certificates and/or public keys. + OBSOLETE STARTTLS CONTROLS - The following configuration parameters exist for compati- + The following configuration parameters exist for compati- bility with Postfix versions before 2.3. Support for these will be removed in a future release. smtp_use_tls (no) - Opportunistic mode: use TLS when a remote SMTP - server announces STARTTLS support, otherwise send + Opportunistic mode: use TLS when a remote SMTP + server announces STARTTLS support, otherwise send the mail in the clear. smtp_enforce_tls (no) - Enforcement mode: require that remote SMTP servers - use TLS encryption, and never send mail in the + Enforcement mode: require that remote SMTP servers + use TLS encryption, and never send mail in the clear. smtp_tls_enforce_peername (yes) - With mandatory TLS encryption, require that the + With mandatory TLS encryption, require that the remote SMTP server hostname matches the information in the remote SMTP server certificate. smtp_tls_per_site (empty) Optional lookup tables with the Postfix SMTP client - TLS usage policy by next-hop destination and by + TLS usage policy by next-hop destination and by remote SMTP server hostname. smtp_tls_cipherlist (empty) @@ -619,80 +625,80 @@ SMTP(8) SMTP(8) RESOURCE AND RATE CONTROLS smtp_destination_concurrency_limit ($default_destina- tion_concurrency_limit) - The maximal number of parallel deliveries to the - same destination via the smtp message delivery + The maximal number of parallel deliveries to the + same destination via the smtp message delivery transport. smtp_destination_recipient_limit ($default_destina- tion_recipient_limit) - The maximal number of recipients per message for + The maximal number of recipients per message for the smtp message delivery transport. smtp_connect_timeout (30s) The Postfix SMTP client time limit for completing a - TCP connection, or zero (use the operating system + TCP connection, or zero (use the operating system built-in time limit). smtp_helo_timeout (300s) - The Postfix SMTP client time limit for sending the + The Postfix SMTP client time limit for sending the HELO or EHLO command, and for receiving the initial remote SMTP server response. lmtp_lhlo_timeout (300s) - The Postfix LMTP client time limit for sending the - LHLO command, and for receiving the initial remote + The Postfix LMTP client time limit for sending the + LHLO command, and for receiving the initial remote LMTP server response. smtp_xforward_timeout (300s) - The Postfix SMTP client time limit for sending the + The Postfix SMTP client time limit for sending the XFORWARD command, and for receiving the remote SMTP server response. smtp_mail_timeout (300s) - The Postfix SMTP client time limit for sending the - MAIL FROM command, and for receiving the remote + The Postfix SMTP client time limit for sending the + MAIL FROM command, and for receiving the remote SMTP server response. smtp_rcpt_timeout (300s) - The Postfix SMTP client time limit for sending the - SMTP RCPT TO command, and for receiving the remote + The Postfix SMTP client time limit for sending the + SMTP RCPT TO command, and for receiving the remote SMTP server response. smtp_data_init_timeout (120s) - The Postfix SMTP client time limit for sending the - SMTP DATA command, and for receiving the remote + The Postfix SMTP client time limit for sending the + SMTP DATA command, and for receiving the remote SMTP server response. smtp_data_xfer_timeout (180s) - The Postfix SMTP client time limit for sending the + The Postfix SMTP client time limit for sending the SMTP message content. smtp_data_done_timeout (600s) - The Postfix SMTP client time limit for sending the - SMTP ".", and for receiving the remote SMTP server + The Postfix SMTP client time limit for sending the + SMTP ".", and for receiving the remote SMTP server response. smtp_quit_timeout (300s) - The Postfix SMTP client time limit for sending the - QUIT command, and for receiving the remote SMTP + The Postfix SMTP client time limit for sending the + QUIT command, and for receiving the remote SMTP server response. Available in Postfix version 2.1 and later: smtp_mx_address_limit (5) The maximal number of MX (mail exchanger) IP - addresses that can result from Postfix SMTP client + addresses that can result from Postfix SMTP client mail exchanger lookups, or zero (no limit). smtp_mx_session_limit (2) - The maximal number of SMTP sessions per delivery - request before the Postfix SMTP client gives up or - delivers to a fall-back relay host, or zero (no + The maximal number of SMTP sessions per delivery + request before the Postfix SMTP client gives up or + delivers to a fall-back relay host, or zero (no limit). smtp_rset_timeout (20s) - The Postfix SMTP client time limit for sending the - RSET command, and for receiving the remote SMTP + The Postfix SMTP client time limit for sending the + RSET command, and for receiving the remote SMTP server response. Available in Postfix version 2.2 and earlier: @@ -704,11 +710,11 @@ SMTP(8) SMTP(8) Available in Postfix version 2.2 and later: smtp_connection_cache_destinations (empty) - Permanently enable SMTP connection caching for the + Permanently enable SMTP connection caching for the specified destinations. smtp_connection_cache_on_demand (yes) - Temporarily enable SMTP connection caching while a + Temporarily enable SMTP connection caching while a destination has a high volume of mail in the active queue. @@ -718,72 +724,72 @@ SMTP(8) SMTP(8) smtp_connection_cache_time_limit (2s) When SMTP connection caching is enabled, the amount - of time that an unused SMTP client socket is kept + of time that an unused SMTP client socket is kept open before it is closed. Available in Postfix version 2.3 and later: connection_cache_protocol_timeout (5s) - Time limit for connection cache connect, send or + Time limit for connection cache connect, send or receive operations. Available in Postfix version 2.9 and later: smtp_per_record_deadline (no) Change the behavior of the smtp_*_timeout time lim- - its, from a time limit per read or write system + its, from a time limit per read or write system call, to a time limit to send or receive a complete - record (an SMTP command line, SMTP response line, - SMTP message content line, or TLS protocol mes- + record (an SMTP command line, SMTP response line, + SMTP message content line, or TLS protocol mes- sage). TROUBLE SHOOTING CONTROLS debug_peer_level (2) - The increment in verbose logging level when a - remote client or server matches a pattern in the + The increment in verbose logging level when a + remote client or server matches a pattern in the debug_peer_list parameter. debug_peer_list (empty) - Optional list of remote client or server hostname - or network address patterns that cause the verbose - logging level to increase by the amount specified + Optional list of remote client or server hostname + or network address patterns that cause the verbose + logging level to increase by the amount specified in $debug_peer_level. error_notice_recipient (postmaster) - The recipient of postmaster notifications about - mail delivery problems that are caused by policy, + The recipient of postmaster notifications about + mail delivery problems that are caused by policy, resource, software or protocol errors. internal_mail_filter_classes (empty) - What categories of Postfix-generated mail are sub- - ject to before-queue content inspection by + What categories of Postfix-generated mail are sub- + ject to before-queue content inspection by non_smtpd_milters, header_checks and body_checks. notify_classes (resource, software) - The list of error classes that are reported to the + The list of error classes that are reported to the postmaster. MISCELLANEOUS CONTROLS best_mx_transport (empty) - Where the Postfix SMTP client should deliver mail + Where the Postfix SMTP client should deliver mail when it detects a "mail loops back to myself" error condition. config_directory (see 'postconf -d' output) - The default location of the Postfix main.cf and + The default location of the Postfix main.cf and master.cf configuration files. daemon_timeout (18000s) - How much time a Postfix daemon process may take to - handle a request before it is terminated by a + How much time a Postfix daemon process may take to + handle a request before it is terminated by a built-in watchdog timer. delay_logging_resolution_limit (2) - The maximal number of digits after the decimal + The maximal number of digits after the decimal point when logging sub-second delay values. disable_dns_lookups (no) - Disable DNS lookups in the Postfix SMTP and LMTP + Disable DNS lookups in the Postfix SMTP and LMTP clients. inet_interfaces (all) @@ -791,7 +797,7 @@ SMTP(8) SMTP(8) tem receives mail on. inet_protocols (all) - The Internet protocols Postfix will attempt to use + The Internet protocols Postfix will attempt to use when making or accepting connections. ipc_timeout (3600s) @@ -801,85 +807,85 @@ SMTP(8) SMTP(8) lmtp_assume_final (no) When a remote LMTP server announces no DSN support, assume that the server performs final delivery, and - send "delivered" delivery status notifications + send "delivered" delivery status notifications instead of "relayed". lmtp_tcp_port (24) - The default TCP port that the Postfix LMTP client + The default TCP port that the Postfix LMTP client connects to. max_idle (100s) - The maximum amount of time that an idle Postfix - daemon process waits for an incoming connection + The maximum amount of time that an idle Postfix + daemon process waits for an incoming connection before terminating voluntarily. max_use (100) - The maximal number of incoming connections that a - Postfix daemon process will service before termi- + The maximal number of incoming connections that a + Postfix daemon process will service before termi- nating voluntarily. process_id (read-only) - The process ID of a Postfix command or daemon + The process ID of a Postfix command or daemon process. process_name (read-only) - The process name of a Postfix command or daemon + The process name of a Postfix command or daemon process. proxy_interfaces (empty) The network interface addresses that this mail sys- - tem receives mail on by way of a proxy or network + tem receives mail on by way of a proxy or network address translation unit. smtp_address_preference (any) The address type ("ipv6", "ipv4" or "any") that the Postfix SMTP client will try first, when a destina- - tion has IPv6 and IPv4 addresses with equal MX + tion has IPv6 and IPv4 addresses with equal MX preference. smtp_bind_address (empty) - An optional numerical network address that the - Postfix SMTP client should bind to when making an + An optional numerical network address that the + Postfix SMTP client should bind to when making an IPv4 connection. smtp_bind_address6 (empty) - An optional numerical network address that the - Postfix SMTP client should bind to when making an + An optional numerical network address that the + Postfix SMTP client should bind to when making an IPv6 connection. smtp_helo_name ($myhostname) - The hostname to send in the SMTP EHLO or HELO com- + The hostname to send in the SMTP EHLO or HELO com- mand. lmtp_lhlo_name ($myhostname) The hostname to send in the LMTP LHLO command. smtp_host_lookup (dns) - What mechanisms the Postfix SMTP client uses to + What mechanisms the Postfix SMTP client uses to look up a host's IP address. smtp_randomize_addresses (yes) - Randomize the order of equal-preference MX host + Randomize the order of equal-preference MX host addresses. syslog_facility (mail) The syslog facility of Postfix logging. syslog_name (see 'postconf -d' output) - The mail system name that is prepended to the - process name in syslog records, so that "smtpd" + The mail system name that is prepended to the + process name in syslog records, so that "smtpd" becomes, for example, "postfix/smtpd". Available with Postfix 2.2 and earlier: fallback_relay (empty) - Optional list of relay hosts for SMTP destinations + Optional list of relay hosts for SMTP destinations that can't be found or that are unreachable. Available with Postfix 2.3 and later: smtp_fallback_relay ($fallback_relay) - Optional list of relay hosts for SMTP destinations + Optional list of relay hosts for SMTP destinations that can't be found or that are unreachable. SEE ALSO @@ -900,7 +906,7 @@ SMTP(8) SMTP(8) TLS_README, Postfix STARTTLS howto LICENSE - The Secure Mailer license must be distributed with this + The Secure Mailer license must be distributed with this software. AUTHOR(S) diff --git a/postfix/html/postconf.5.html b/postfix/html/postconf.5.html index 1c7b27b8e..096e97bcd 100644 --- a/postfix/html/postconf.5.html +++ b/postfix/html/postconf.5.html @@ -4963,6 +4963,17 @@ configuration parameter. See there for details.

This feature is available in Postfix 2.3 and later.

+ + +
lmtp_tls_trust_anchor_file +(default: empty)
+ +

The LMTP-specific version of the smtp_tls_trust_anchor_file +configuration parameter. See there for details.

+ +

This feature is available in Postfix 2.11 and later.

+ +
lmtp_tls_verify_cert_match @@ -11918,6 +11929,55 @@ are not possible.

This feature is available in Postfix 2.2 and later.

+ + +
smtp_tls_trust_anchor_file +(default: empty)
+ +

Zero or more PEM-format files with trust-anchor certificates +and/or public keys. If the parameter is not empty the root CAs in +CAfile and CApath are no longer trusted. Rather, the Postfix SMTP +client will only trust certificate-chains signed by one of the +trust-anchors contained in the chosen files. The specified +trust-anchor certificates and public keys are not subject to +expiration, and need not be (self-signed) root CAs. They may, if +desired, be intermediate certificates. Therefore, these certificates +also may be found "in the middle" of the trust chain presented by +the remote SMTP server, and any untrusted issuing parent certificates +will be ignored. Specify a list of pathnames separated by comma +or whitespace.

+ +

This feature is implemented for completeness, to allow installations +with a small set of SMTP peers to set global policy in main.cf, +that at most sites would be set via smtp_tls_policy_maps. In almost +all cases it is better to use it on a per-destination basis via the +"tafile" policy attribute of the "verify" and "secure" levels leaving +the global main.cf setting empty.

+ +

When used on a per-destination basis, each "tafile" PEM file +must be accessible to the Postfix SMTP client in the chroot jail +if applicable. The files should not contain any sensitive data, +and must be readable by the non-privileged $mail_owner user. This +allows destinations to be bound to a set of specific CAs or public +keys without trusting the same CAs for all destinations.

+ +

The underlying mechanism is in support of RFC 6698 (DANE TLSA), +which defines mechanisms for a client to securely determine server +TLS certificates via DNS.

+ +

If you want your trust anchors to be public keys, with OpenSSL +you can extract a single PEM public key from a PEM X.509 file +containing a single certificate, as follows:

+ +
+
+$ openssl x509 -in cert.pem -out ta-key.pem -noout -pubkey
+
+
+ +

This feature is available in Postfix 2.11 and later.

+ +
smtp_tls_verify_cert_match diff --git a/postfix/html/smtp.8.html b/postfix/html/smtp.8.html index 20bed174b..7db52e027 100644 --- a/postfix/html/smtp.8.html +++ b/postfix/html/smtp.8.html @@ -587,29 +587,35 @@ SMTP(8) SMTP(8) List or bit-mask of OpenSSL bug work-arounds to disable. + Available in Postfix version 2.11 and later: + + smtp_tls_trust_anchor_file (empty) + Zero or more PEM-format files with trust-anchor + certificates and/or public keys. + OBSOLETE STARTTLS CONTROLS - The following configuration parameters exist for compati- + The following configuration parameters exist for compati- bility with Postfix versions before 2.3. Support for these will be removed in a future release. smtp_use_tls (no) - Opportunistic mode: use TLS when a remote SMTP - server announces STARTTLS support, otherwise send + Opportunistic mode: use TLS when a remote SMTP + server announces STARTTLS support, otherwise send the mail in the clear. smtp_enforce_tls (no) - Enforcement mode: require that remote SMTP servers - use TLS encryption, and never send mail in the + Enforcement mode: require that remote SMTP servers + use TLS encryption, and never send mail in the clear. smtp_tls_enforce_peername (yes) - With mandatory TLS encryption, require that the + With mandatory TLS encryption, require that the remote SMTP server hostname matches the information in the remote SMTP server certificate. smtp_tls_per_site (empty) Optional lookup tables with the Postfix SMTP client - TLS usage policy by next-hop destination and by + TLS usage policy by next-hop destination and by remote SMTP server hostname. smtp_tls_cipherlist (empty) @@ -619,80 +625,80 @@ SMTP(8) SMTP(8) RESOURCE AND RATE CONTROLS smtp_destination_concurrency_limit ($default_destina- tion_concurrency_limit) - The maximal number of parallel deliveries to the - same destination via the smtp message delivery + The maximal number of parallel deliveries to the + same destination via the smtp message delivery transport. smtp_destination_recipient_limit ($default_destina- tion_recipient_limit) - The maximal number of recipients per message for + The maximal number of recipients per message for the smtp message delivery transport. smtp_connect_timeout (30s) The Postfix SMTP client time limit for completing a - TCP connection, or zero (use the operating system + TCP connection, or zero (use the operating system built-in time limit). smtp_helo_timeout (300s) - The Postfix SMTP client time limit for sending the + The Postfix SMTP client time limit for sending the HELO or EHLO command, and for receiving the initial remote SMTP server response. lmtp_lhlo_timeout (300s) - The Postfix LMTP client time limit for sending the - LHLO command, and for receiving the initial remote + The Postfix LMTP client time limit for sending the + LHLO command, and for receiving the initial remote LMTP server response. smtp_xforward_timeout (300s) - The Postfix SMTP client time limit for sending the + The Postfix SMTP client time limit for sending the XFORWARD command, and for receiving the remote SMTP server response. smtp_mail_timeout (300s) - The Postfix SMTP client time limit for sending the - MAIL FROM command, and for receiving the remote + The Postfix SMTP client time limit for sending the + MAIL FROM command, and for receiving the remote SMTP server response. smtp_rcpt_timeout (300s) - The Postfix SMTP client time limit for sending the - SMTP RCPT TO command, and for receiving the remote + The Postfix SMTP client time limit for sending the + SMTP RCPT TO command, and for receiving the remote SMTP server response. smtp_data_init_timeout (120s) - The Postfix SMTP client time limit for sending the - SMTP DATA command, and for receiving the remote + The Postfix SMTP client time limit for sending the + SMTP DATA command, and for receiving the remote SMTP server response. smtp_data_xfer_timeout (180s) - The Postfix SMTP client time limit for sending the + The Postfix SMTP client time limit for sending the SMTP message content. smtp_data_done_timeout (600s) - The Postfix SMTP client time limit for sending the - SMTP ".", and for receiving the remote SMTP server + The Postfix SMTP client time limit for sending the + SMTP ".", and for receiving the remote SMTP server response. smtp_quit_timeout (300s) - The Postfix SMTP client time limit for sending the - QUIT command, and for receiving the remote SMTP + The Postfix SMTP client time limit for sending the + QUIT command, and for receiving the remote SMTP server response. Available in Postfix version 2.1 and later: smtp_mx_address_limit (5) The maximal number of MX (mail exchanger) IP - addresses that can result from Postfix SMTP client + addresses that can result from Postfix SMTP client mail exchanger lookups, or zero (no limit). smtp_mx_session_limit (2) - The maximal number of SMTP sessions per delivery - request before the Postfix SMTP client gives up or - delivers to a fall-back relay host, or zero (no + The maximal number of SMTP sessions per delivery + request before the Postfix SMTP client gives up or + delivers to a fall-back relay host, or zero (no limit). smtp_rset_timeout (20s) - The Postfix SMTP client time limit for sending the - RSET command, and for receiving the remote SMTP + The Postfix SMTP client time limit for sending the + RSET command, and for receiving the remote SMTP server response. Available in Postfix version 2.2 and earlier: @@ -704,11 +710,11 @@ SMTP(8) SMTP(8) Available in Postfix version 2.2 and later: smtp_connection_cache_destinations (empty) - Permanently enable SMTP connection caching for the + Permanently enable SMTP connection caching for the specified destinations. smtp_connection_cache_on_demand (yes) - Temporarily enable SMTP connection caching while a + Temporarily enable SMTP connection caching while a destination has a high volume of mail in the active queue. @@ -718,72 +724,72 @@ SMTP(8) SMTP(8) smtp_connection_cache_time_limit (2s) When SMTP connection caching is enabled, the amount - of time that an unused SMTP client socket is kept + of time that an unused SMTP client socket is kept open before it is closed. Available in Postfix version 2.3 and later: connection_cache_protocol_timeout (5s) - Time limit for connection cache connect, send or + Time limit for connection cache connect, send or receive operations. Available in Postfix version 2.9 and later: smtp_per_record_deadline (no) Change the behavior of the smtp_*_timeout time lim- - its, from a time limit per read or write system + its, from a time limit per read or write system call, to a time limit to send or receive a complete - record (an SMTP command line, SMTP response line, - SMTP message content line, or TLS protocol mes- + record (an SMTP command line, SMTP response line, + SMTP message content line, or TLS protocol mes- sage). TROUBLE SHOOTING CONTROLS debug_peer_level (2) - The increment in verbose logging level when a - remote client or server matches a pattern in the + The increment in verbose logging level when a + remote client or server matches a pattern in the debug_peer_list parameter. debug_peer_list (empty) - Optional list of remote client or server hostname - or network address patterns that cause the verbose - logging level to increase by the amount specified + Optional list of remote client or server hostname + or network address patterns that cause the verbose + logging level to increase by the amount specified in $debug_peer_level. error_notice_recipient (postmaster) - The recipient of postmaster notifications about - mail delivery problems that are caused by policy, + The recipient of postmaster notifications about + mail delivery problems that are caused by policy, resource, software or protocol errors. internal_mail_filter_classes (empty) - What categories of Postfix-generated mail are sub- - ject to before-queue content inspection by + What categories of Postfix-generated mail are sub- + ject to before-queue content inspection by non_smtpd_milters, header_checks and body_checks. notify_classes (resource, software) - The list of error classes that are reported to the + The list of error classes that are reported to the postmaster. MISCELLANEOUS CONTROLS best_mx_transport (empty) - Where the Postfix SMTP client should deliver mail + Where the Postfix SMTP client should deliver mail when it detects a "mail loops back to myself" error condition. config_directory (see 'postconf -d' output) - The default location of the Postfix main.cf and + The default location of the Postfix main.cf and master.cf configuration files. daemon_timeout (18000s) - How much time a Postfix daemon process may take to - handle a request before it is terminated by a + How much time a Postfix daemon process may take to + handle a request before it is terminated by a built-in watchdog timer. delay_logging_resolution_limit (2) - The maximal number of digits after the decimal + The maximal number of digits after the decimal point when logging sub-second delay values. disable_dns_lookups (no) - Disable DNS lookups in the Postfix SMTP and LMTP + Disable DNS lookups in the Postfix SMTP and LMTP clients. inet_interfaces (all) @@ -791,7 +797,7 @@ SMTP(8) SMTP(8) tem receives mail on. inet_protocols (all) - The Internet protocols Postfix will attempt to use + The Internet protocols Postfix will attempt to use when making or accepting connections. ipc_timeout (3600s) @@ -801,85 +807,85 @@ SMTP(8) SMTP(8) lmtp_assume_final (no) When a remote LMTP server announces no DSN support, assume that the server performs final delivery, and - send "delivered" delivery status notifications + send "delivered" delivery status notifications instead of "relayed". lmtp_tcp_port (24) - The default TCP port that the Postfix LMTP client + The default TCP port that the Postfix LMTP client connects to. max_idle (100s) - The maximum amount of time that an idle Postfix - daemon process waits for an incoming connection + The maximum amount of time that an idle Postfix + daemon process waits for an incoming connection before terminating voluntarily. max_use (100) - The maximal number of incoming connections that a - Postfix daemon process will service before termi- + The maximal number of incoming connections that a + Postfix daemon process will service before termi- nating voluntarily. process_id (read-only) - The process ID of a Postfix command or daemon + The process ID of a Postfix command or daemon process. process_name (read-only) - The process name of a Postfix command or daemon + The process name of a Postfix command or daemon process. proxy_interfaces (empty) The network interface addresses that this mail sys- - tem receives mail on by way of a proxy or network + tem receives mail on by way of a proxy or network address translation unit. smtp_address_preference (any) The address type ("ipv6", "ipv4" or "any") that the Postfix SMTP client will try first, when a destina- - tion has IPv6 and IPv4 addresses with equal MX + tion has IPv6 and IPv4 addresses with equal MX preference. smtp_bind_address (empty) - An optional numerical network address that the - Postfix SMTP client should bind to when making an + An optional numerical network address that the + Postfix SMTP client should bind to when making an IPv4 connection. smtp_bind_address6 (empty) - An optional numerical network address that the - Postfix SMTP client should bind to when making an + An optional numerical network address that the + Postfix SMTP client should bind to when making an IPv6 connection. smtp_helo_name ($myhostname) - The hostname to send in the SMTP EHLO or HELO com- + The hostname to send in the SMTP EHLO or HELO com- mand. lmtp_lhlo_name ($myhostname) The hostname to send in the LMTP LHLO command. smtp_host_lookup (dns) - What mechanisms the Postfix SMTP client uses to + What mechanisms the Postfix SMTP client uses to look up a host's IP address. smtp_randomize_addresses (yes) - Randomize the order of equal-preference MX host + Randomize the order of equal-preference MX host addresses. syslog_facility (mail) The syslog facility of Postfix logging. syslog_name (see 'postconf -d' output) - The mail system name that is prepended to the - process name in syslog records, so that "smtpd" + The mail system name that is prepended to the + process name in syslog records, so that "smtpd" becomes, for example, "postfix/smtpd". Available with Postfix 2.2 and earlier: fallback_relay (empty) - Optional list of relay hosts for SMTP destinations + Optional list of relay hosts for SMTP destinations that can't be found or that are unreachable. Available with Postfix 2.3 and later: smtp_fallback_relay ($fallback_relay) - Optional list of relay hosts for SMTP destinations + Optional list of relay hosts for SMTP destinations that can't be found or that are unreachable. SEE ALSO @@ -900,7 +906,7 @@ SMTP(8) SMTP(8) TLS_README, Postfix STARTTLS howto LICENSE - The Secure Mailer license must be distributed with this + The Secure Mailer license must be distributed with this software. AUTHOR(S) diff --git a/postfix/makedefs b/postfix/makedefs index 2ceff2e4c..0ac57f423 100644 --- a/postfix/makedefs +++ b/postfix/makedefs @@ -626,7 +626,7 @@ CCARGS="$CCARGS -DSNAPSHOT" # Non-production: needs thorough testing, or major changes are still # needed before the code stabilizes. -#CCARGS="$CCARGS -DNONPROD" +CCARGS="$CCARGS -DNONPROD" sed 's/ / /g' <) { s;\blmtp_tls_security_level\b;$&;g; s;\blmtp_tls_fingerprint_cert_match\b;$&;g; s;\blmtp_tls_verify_cert_match\b;$&;g; + s;\blmtp_tls_trust_anchor_file\b;$&;g; s;\blmtp_tls_per_site\b;$&;g; s;\blmtp_tls_cert_file\b;$&;g; s;\blmtp_tls_key_file\b;$&;g; @@ -638,6 +639,7 @@ while (<>) { s;\bsmtp_tls_fingerprint_cert_match\b;$&;g; s;\bsmtp_tls_verify_cert_match\b;$&;g; s;\bsmtp_tls_secure_cert_match\b;$&;g; + s;\bsmtp_tls_trust_anchor_file\b;$&;g; s;\bsmtp_tls_scert_verifydepth\b;$&;g; s;\bsmtp_tls_security_level\b;$&;g; s;\bsmtp_tls_session_cache_database\b;$&;g; diff --git a/postfix/proto/TLS_README.html b/postfix/proto/TLS_README.html index 833e445d6..08ee5d474 100644 --- a/postfix/proto/TLS_README.html +++ b/postfix/proto/TLS_README.html @@ -1239,6 +1239,19 @@ DNS names are specified, the certificate CommonName is checked. If you want mandatory encryption without server certificate verification, see above.

+

With Postfix ≥ 2.11 the "smtp_tls_trust_anchor_file" parameter +or more typically the corresponding per-destination "tafile" attribute +optionally modifies trust chain verification. If the parameter is +not empty the root CAs in CAfile and CApath are no longer trusted. +Rather, the Postfix SMTP client will only trust certificate-chains +signed by one of the trust-anchors contained in the chosen files. +The specified trust-anchor certificates and public keys are not +subject to expiration, and need not be (self-signed) root CAs. They +may, if desired, be intermediate certificates. Therefore, these +certificates also may be found "in the middle" of the trust chain +presented by the remote SMTP server, and any untrusted issuing +parent certificates will be ignored.

+

Despite the potential for eliminating "man-in-the-middle" and other attacks, mandatory certificate trust chain and subject name verification is not viable as a default Internet mail delivery policy. Most MX hosts @@ -1300,6 +1313,19 @@ specified, the CommonName is checked. If you want mandatory encryption without server certificate verification, see above.

+

With Postfix ≥ 2.11 the "smtp_tls_trust_anchor_file" parameter +or more typically the corresponding per-destination "tafile" attribute +optionally modifies trust chain verification. If the parameter is +not empty the root CAs in CAfile and CApath are no longer trusted. +Rather, the Postfix SMTP client will only trust certificate-chains +signed by one of the trust-anchors contained in the chosen files. +The specified trust-anchor certificates and public keys are not +subject to expiration, and need not be (self-signed) root CAs. They +may, if desired, be intermediate certificates. Therefore, these +certificates also may be found "in the middle" of the trust chain +presented by the remote SMTP server, and any untrusted issuing +parent certificates will be ignored.

+

Despite the potential for eliminating "man-in-the-middle" and other attacks, mandatory secure server certificate verification is not viable as a default Internet mail delivery policy. Most MX hosts @@ -1806,12 +1832,16 @@ digits.

verify
Mandatory server certificate verification. Mail is delivered only if the -TLS handshake -succeeds, if the remote SMTP server certificate can be validated (not -expired or revoked, and signed by a trusted certificate authority), and -if the server certificate name matches the optional "match" attribute (or -the main.cf smtp_tls_verify_cert_match parameter value when no optional -"match" attribute is specified).
+TLS handshake succeeds, if the remote SMTP server certificate can +be validated (not expired or revoked, and signed by a trusted +certificate authority), and if the server certificate name matches +the optional "match" attribute (or the main.cf smtp_tls_verify_cert_match +parameter value when no optional "match" attribute is specified). +With Postfix ≥ 2.11 the "tafile" attribute optionally modifies +trust chain verification in the same manner as the +"smtp_tls_trust_anchor_file" parameter. The "tafile" attribute +may be specified multiple times to load multiple trust-anchor +files.
secure
Secure certificate verification. Mail is delivered only if the TLS handshake succeeds, @@ -1819,7 +1849,11 @@ if the remote SMTP server certificate can be validated (not expired or revoked, and signed by a trusted certificate authority), and if the server certificate name matches the optional "match" attribute (or the main.cf smtp_tls_secure_cert_match parameter value when no optional -"match" attribute is specified).
+"match" attribute is specified). With Postfix ≥ 2.11 the "tafile" +attribute optionally modifies trust chain verification in the same manner +as the "smtp_tls_trust_anchor_file" parameter. The "tafile" attribute +may be specified multiple times to load multiple trust-anchor +files. diff --git a/postfix/proto/postconf.proto b/postfix/proto/postconf.proto index c42856f1b..cb9ad8ef1 100644 --- a/postfix/proto/postconf.proto +++ b/postfix/proto/postconf.proto @@ -14919,7 +14919,7 @@ limited to 13 over the lifetime of a daemon process.

This feature is available in Postfix 2.9 and later.

-%PARAM smtpd_log_access_permit_actions empty +%PARAM smtpd_log_access_permit_actions

Enable logging of the named "permit" actions in SMTP server access lists (by default, the SMTP server logs "reject" actions but @@ -15042,3 +15042,55 @@ RES_USE_DNSSEC and RES_USE_EDNS0 resolver options.

configuration parameter. See there for details.

This feature is available in Postfix 2.11 and later.

+ +%PARAM smtp_tls_trust_anchor_file + +

Zero or more PEM-format files with trust-anchor certificates +and/or public keys. If the parameter is not empty the root CAs in +CAfile and CApath are no longer trusted. Rather, the Postfix SMTP +client will only trust certificate-chains signed by one of the +trust-anchors contained in the chosen files. The specified +trust-anchor certificates and public keys are not subject to +expiration, and need not be (self-signed) root CAs. They may, if +desired, be intermediate certificates. Therefore, these certificates +also may be found "in the middle" of the trust chain presented by +the remote SMTP server, and any untrusted issuing parent certificates +will be ignored. Specify a list of pathnames separated by comma +or whitespace.

+ +

This feature is implemented for completeness, to allow installations +with a small set of SMTP peers to set global policy in main.cf, +that at most sites would be set via smtp_tls_policy_maps. In almost +all cases it is better to use it on a per-destination basis via the +"tafile" policy attribute of the "verify" and "secure" levels leaving +the global main.cf setting empty.

+ +

When used on a per-destination basis, each "tafile" PEM file +must be accessible to the Postfix SMTP client in the chroot jail +if applicable. The files should not contain any sensitive data, +and must be readable by the non-privileged $mail_owner user. This +allows destinations to be bound to a set of specific CAs or public +keys without trusting the same CAs for all destinations.

+ +

The underlying mechanism is in support of RFC 6698 (DANE TLSA), +which defines mechanisms for a client to securely determine server +TLS certificates via DNS.

+ +

If you want your trust anchors to be public keys, with OpenSSL +you can extract a single PEM public key from a PEM X.509 file +containing a single certificate, as follows:

+ +
+
+$ openssl x509 -in cert.pem -out ta-key.pem -noout -pubkey
+
+
+ +

This feature is available in Postfix 2.11 and later.

+ +%PARAM lmtp_tls_trust_anchor_file + +

The LMTP-specific version of the smtp_tls_trust_anchor_file +configuration parameter. See there for details.

+ +

This feature is available in Postfix 2.11 and later.

diff --git a/postfix/proto/stop b/postfix/proto/stop index 396e2ff02..2d1867267 100644 --- a/postfix/proto/stop +++ b/postfix/proto/stop @@ -1256,3 +1256,6 @@ uncached unzipping windowsize xpostconf +TLSA +tafile +VPN diff --git a/postfix/src/global/mail_params.h b/postfix/src/global/mail_params.h index 1e368f8a5..6b1aef850 100644 --- a/postfix/src/global/mail_params.h +++ b/postfix/src/global/mail_params.h @@ -1440,6 +1440,12 @@ extern char *var_smtp_tls_mand_excl; #define DEF_LMTP_TLS_FPT_DGST "md5" extern char *var_smtp_tls_fpt_dgst; +#define VAR_SMTP_TLS_TAFILE "smtp_tls_trust_anchor_file" +#define DEF_SMTP_TLS_TAFILE "" +#define VAR_LMTP_TLS_TAFILE "lmtp_tls_trust_anchor_file" +#define DEF_LMTP_TLS_TAFILE "" +extern char *var_smtp_tls_tafile; + #define VAR_SMTP_TLS_LOGLEVEL "smtp_tls_loglevel" #define DEF_SMTP_TLS_LOGLEVEL "0" #define VAR_LMTP_TLS_LOGLEVEL "lmtp_tls_loglevel" diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index a869f257d..434e516e8 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 "20130403" +#define MAIL_RELEASE_DATE "20130405" #define MAIL_VERSION_NUMBER "2.11" #ifdef SNAPSHOT diff --git a/postfix/src/smtp/Makefile.in b/postfix/src/smtp/Makefile.in index 98ba32cbf..f30cdfa6d 100644 --- a/postfix/src/smtp/Makefile.in +++ b/postfix/src/smtp/Makefile.in @@ -593,6 +593,7 @@ smtp_state.o: smtp_sasl.h smtp_state.o: smtp_state.c smtp_tls_sess.o: ../../include/argv.h smtp_tls_sess.o: ../../include/attr.h +smtp_tls_sess.o: ../../include/ctable.h smtp_tls_sess.o: ../../include/deliver_request.h smtp_tls_sess.o: ../../include/dict.h smtp_tls_sess.o: ../../include/dsn.h diff --git a/postfix/src/smtp/lmtp_params.c b/postfix/src/smtp/lmtp_params.c index cb645d567..40b7b70bf 100644 --- a/postfix/src/smtp/lmtp_params.c +++ b/postfix/src/smtp/lmtp_params.c @@ -22,6 +22,7 @@ VAR_LMTP_TLS_SEC_CMATCH, DEF_LMTP_TLS_SEC_CMATCH, &var_smtp_tls_sec_cmatch, 1, 0, VAR_LMTP_TLS_FPT_CMATCH, DEF_LMTP_TLS_FPT_CMATCH, &var_smtp_tls_fpt_cmatch, 0, 0, VAR_LMTP_TLS_FPT_DGST, DEF_LMTP_TLS_FPT_DGST, &var_smtp_tls_fpt_dgst, 1, 0, + VAR_LMTP_TLS_TAFILE, DEF_LMTP_TLS_TAFILE, &var_smtp_tls_tafile, 0, 0, VAR_LMTP_TLS_PROTO, DEF_LMTP_TLS_PROTO, &var_smtp_tls_proto, 0, 0, VAR_LMTP_TLS_CIPH, DEF_LMTP_TLS_CIPH, &var_smtp_tls_ciph, 1, 0, VAR_LMTP_TLS_ECCERT_FILE, DEF_LMTP_TLS_ECCERT_FILE, &var_smtp_tls_eccert_file, 0, 0, diff --git a/postfix/src/smtp/smtp.c b/postfix/src/smtp/smtp.c index 841910b0b..42e24070c 100644 --- a/postfix/src/smtp/smtp.c +++ b/postfix/src/smtp/smtp.c @@ -441,6 +441,11 @@ /* Available in Postfix version 2.8 and later: /* .IP "\fBtls_disable_workarounds (see 'postconf -d' output)\fR" /* List or bit-mask of OpenSSL bug work-arounds to disable. +/* .PP +/* Available in Postfix version 2.11 and later: +/* .IP "\fBsmtp_tls_trust_anchor_file (empty)\fR" +/* Zero or more PEM-format files with trust-anchor certificates +/* and/or public keys. /* OBSOLETE STARTTLS CONTROLS /* .ad /* .fi @@ -825,6 +830,7 @@ int var_smtp_tls_scert_vd; char *var_smtp_tls_vfy_cmatch; char *var_smtp_tls_fpt_cmatch; char *var_smtp_tls_fpt_dgst; +char *var_smtp_tls_tafile; char *var_smtp_tls_proto; char *var_smtp_tls_ciph; char *var_smtp_tls_eccert_file; diff --git a/postfix/src/smtp/smtp.h b/postfix/src/smtp/smtp.h index 7ccd29bfb..1dbbf516a 100644 --- a/postfix/src/smtp/smtp.h +++ b/postfix/src/smtp/smtp.h @@ -202,13 +202,16 @@ extern HBC_CHECKS *smtp_body_checks; /* limited body checks */ * smtp_session.c */ #ifdef USE_TLS -typedef struct SMTP_TLS_SESS { +typedef struct SMTP_TLS_POLICY { + int refs; /* Reference count */ int level; /* TLS enforcement level */ char *protocols; /* Acceptable SSL protocols */ char *grade; /* Cipher grade: "export", ... */ VSTRING *exclusions; /* Excluded SSL ciphers */ ARGV *matchargv; /* Cert match patterns */ -} SMTP_TLS_SESS; + DSN_BUF *why; /* Lookup error status */ + TLS_DANE *dane; /* DANE TLSA digests */ +} SMTP_TLS_POLICY; #endif @@ -254,7 +257,7 @@ typedef struct SMTP_SESSION { TLS_SESS_STATE *tls_context; /* TLS library session state */ char *tls_nexthop; /* Nexthop domain for cert checks */ int tls_retry_plain; /* Try plain when TLS handshake fails */ - SMTP_TLS_SESS *tls; /* SMTP session TLS policy */ + SMTP_TLS_POLICY *tls; /* SMTP session TLS policy */ #endif SMTP_STATE *state; /* back link */ @@ -274,10 +277,10 @@ extern SMTP_SESSION *smtp_session_activate(int, VSTRING *, VSTRING *); * smtp_tls_sess.c */ extern void smtp_tls_list_init(void); -extern SMTP_TLS_SESS *smtp_tls_sess_alloc(DSN_BUF *, const char *, const char *, - unsigned, int); -extern SMTP_TLS_SESS *smtp_tls_sess_free(SMTP_TLS_SESS *); - +extern SMTP_TLS_POLICY *smtp_tls_policy(DSN_BUF *, const char *, const char *, + unsigned, int); +extern void smtp_tls_policy_free(SMTP_TLS_POLICY *); +extern void smtp_tls_policy_flush(void); #endif /* diff --git a/postfix/src/smtp/smtp_params.c b/postfix/src/smtp/smtp_params.c index cd25fdc4f..13f8723fa 100644 --- a/postfix/src/smtp/smtp_params.c +++ b/postfix/src/smtp/smtp_params.c @@ -23,6 +23,7 @@ VAR_SMTP_TLS_SEC_CMATCH, DEF_SMTP_TLS_SEC_CMATCH, &var_smtp_tls_sec_cmatch, 1, 0, VAR_SMTP_TLS_FPT_CMATCH, DEF_SMTP_TLS_FPT_CMATCH, &var_smtp_tls_fpt_cmatch, 0, 0, VAR_SMTP_TLS_FPT_DGST, DEF_SMTP_TLS_FPT_DGST, &var_smtp_tls_fpt_dgst, 1, 0, + VAR_SMTP_TLS_TAFILE, DEF_SMTP_TLS_TAFILE, &var_smtp_tls_tafile, 0, 0, VAR_SMTP_TLS_PROTO, DEF_SMTP_TLS_PROTO, &var_smtp_tls_proto, 0, 0, VAR_SMTP_TLS_CIPH, DEF_SMTP_TLS_CIPH, &var_smtp_tls_ciph, 1, 0, VAR_SMTP_TLS_ECCERT_FILE, DEF_SMTP_TLS_ECCERT_FILE, &var_smtp_tls_eccert_file, 0, 0, diff --git a/postfix/src/smtp/smtp_proto.c b/postfix/src/smtp/smtp_proto.c index 929c74a67..ec3559d37 100644 --- a/postfix/src/smtp/smtp_proto.c +++ b/postfix/src/smtp/smtp_proto.c @@ -808,7 +808,8 @@ static int smtp_start_tls(SMTP_STATE *state) cipher_exclusions = vstring_str(session->tls->exclusions), matchargv = session->tls->matchargv, - mdalg = var_smtp_tls_fpt_dgst); + mdalg = var_smtp_tls_fpt_dgst, + dane = session->tls->dane); vstring_free(serverid); if (session->tls_context == 0) { @@ -848,8 +849,23 @@ static int smtp_start_tls(SMTP_STATE *state) * server, so no need to disable I/O, ... we can even be polite and send * "QUIT". * - * See src/tls/tls_level.c. Levels above encrypt require matching. Levels >= - * verify require CA trust. + * See src/tls/tls_level.c. Levels above encrypt require matching. + * Levels >= verify require CA trust. + * + * The DANE level is a hybrid of verify and fingerprint, if we have + * trust-anchors, we must do name checking, so we treat like verify. + * We also do fingerprint verification against any end-entity certs, + * so it'll work for that too. + * + * If we only have end-entity certs, then it is just like fingerprint. + * + * If we have no usable certs at all, but TLSA records were found, + * we do "encrypt". Contrary to draft-ietf-dane-srv, we can't + * do standard PKIX as a fallback, it will almost always fail, + * with no human present to "click ok". + * + * The DANE security level is for now still disabled in tls_level.c + * When it is enabled, we're ready to enforce its constraints. */ if (session->tls->level >= TLS_LEV_VERIFY) if (!TLS_CERT_IS_TRUSTED(session->tls_context)) diff --git a/postfix/src/smtp/smtp_session.c b/postfix/src/smtp/smtp_session.c index 62937926a..443b4d216 100644 --- a/postfix/src/smtp/smtp_session.c +++ b/postfix/src/smtp/smtp_session.c @@ -203,9 +203,8 @@ SMTP_SESSION *smtp_session_alloc(DSN_BUF *why, const char *dest, /* * When the destination argument of smtp_tls_sess_alloc() is null, a * trivial TLS policy (level = "none") is returned unconditionally and - * the other arguments are not used. Soon the DSN_BUF "why" argument - * will be optional when the caller is not interested in the error - * status. + * the other arguments are not used. The DSN_BUF "why" argument is + * optional when the caller is not interested in the error status. */ #define NO_DSN_BUF (DSN_BUF *) 0 #define NO_DEST (char *) 0 @@ -218,12 +217,12 @@ SMTP_SESSION *smtp_session_alloc(DSN_BUF *why, const char *dest, session->tls_retry_plain = 0; session->tls_nexthop = 0; if (flags & SMTP_MISC_FLAG_NO_TLS) - session->tls = smtp_tls_sess_alloc(NO_DSN_BUF, NO_DEST, NO_HOST, - NO_PORT, NO_FLAGS); + session->tls = smtp_tls_policy(NO_DSN_BUF, NO_DEST, NO_HOST, + NO_PORT, NO_FLAGS); else { if (why == 0) msg_panic("%s: null error buffer", myname); - session->tls = smtp_tls_sess_alloc(why, dest, host, port, valid); + session->tls = smtp_tls_policy(why, dest, host, port, valid); } if (!session->tls) { smtp_session_free(session); @@ -275,7 +274,7 @@ void smtp_session_free(SMTP_SESSION *session) var_smtp_starttls_tmout, 0, session->tls_context); } if (session->tls) - (void) smtp_tls_sess_free(session->tls); + smtp_tls_policy_free(session->tls); #endif if (session->stream) vstream_fclose(session->stream); @@ -310,20 +309,15 @@ int smtp_sess_tls_check(const char *dest, const char *host, unsigned port, int valid) { #ifdef USE_TLS - static DSN_BUF *why; - SMTP_TLS_SESS *tls; + int needed = 1; + SMTP_TLS_POLICY *tls; - if (!why) - why = dsb_create(); - - tls = smtp_tls_sess_alloc(why, dest, host, ntohs(port), valid); - dsb_reset(why); - - if (tls && tls->level >= TLS_LEV_NONE && tls->level <= TLS_LEV_MAY) - return (0); - if (tls) - smtp_tls_sess_free(tls); - return (1); + /* Say "no" only when we're sure. Otherwise, "yes". */ + if ((tls = smtp_tls_policy(0, dest, host, ntohs(port), valid)) != 0) { + needed = tls->level >= TLS_LEV_NONE && tls->level <= TLS_LEV_MAY; + smtp_tls_policy_free(tls); + } + return (needed); #else return (0); #endif diff --git a/postfix/src/smtp/smtp_state.c b/postfix/src/smtp/smtp_state.c index 9699a053b..995af3496 100644 --- a/postfix/src/smtp/smtp_state.c +++ b/postfix/src/smtp/smtp_state.c @@ -84,6 +84,10 @@ SMTP_STATE *smtp_state_alloc(void) void smtp_state_free(SMTP_STATE *state) { +#ifdef USE_TLS + /* The TLS policy cache lifetime is one delivery. */ + smtp_tls_policy_flush(); +#endif if (state->dest_label) vstring_free(state->dest_label); if (state->dest_prop) diff --git a/postfix/src/smtp/smtp_tls_sess.c b/postfix/src/smtp/smtp_tls_sess.c index 74467da1d..bd514cc9f 100644 --- a/postfix/src/smtp/smtp_tls_sess.c +++ b/postfix/src/smtp/smtp_tls_sess.c @@ -1,38 +1,49 @@ /*++ /* NAME -/* smtp_tls_sess 3 +/* smtp_tls_policy 3 /* SUMMARY -/* SMTP_TLS_SESS structure management +/* SMTP_TLS_POLICY structure management /* SYNOPSIS /* #include "smtp.h" /* /* void smtp_tls_list_init() /* -/* SMTP_TLS_SESS *smtp_tls_sess_alloc(why, dest, host, port, valid) +/* SMTP_TLS_POLICY *smtp_tls_policy(why, dest, host, port, valid) /* DSN_BUF *why; /* char *dest; /* char *host; /* unsigned port; /* int valid; /* -/* SMTP_TLS_SESS *smtp_tls_sess_free(tls) -/* SMTP_TLS_SESS *tls; +/* void smtp_tls_policy_free(tls) +/* SMTP_TLS_POLICY *tls; +/* +/* void smtp_tls_policy_flush() /* DESCRIPTION /* smtp_tls_list_init() initializes lookup tables used by the TLS /* policy engine. /* -/* smtp_tls_sess_alloc() allocates memory for an SMTP_TLS_SESS structure -/* and initializes it based on the given information. Any required -/* table and DNS lookups can fail. When this happens, "why" is updated -/* with the error reason and a null pointer is returned. NOTE: the -/* port is in network byte order. If "dest" is null, no policy checks are -/* made, rather a trivial policy with tls disabled is returned (the -/* remaining arguments are unused in this case and may be null). +/* smtp_tls_policy() returns the SMTP_TLS_POLICY structure for +/* the destination, host, port and DNSSEC validation status. Any of +/* the required table and DNS lookups can fail. When this happens, and +/* "why" is non-null, it is updated with the error reason and a null +/* policy is returned. NOTE: the port is in network byte order. If +/* "dest" is null, no policy checks are made, rather a trivial policy +/* with TLS disabled is returned. The caller must free the policy via +/* smtp_tls_policy_free(). +/* +/* smtp_tls_policy_free() frees the SMTP_TLS_POLICY object. The +/* objects are reference counted, so storage is deallocated when +/* the reference count drops to zero. Since the objects are also +/* cached, this typically happens when the cached is flushed, provided +/* all other references have been released. /* -/* smtp_tls_sess_free() destroys an SMTP_TLS_SESS structure and its -/* members. A null pointer is returned for convenience. +/* smtp_tls_policy_flush() frees the cache contents and cache object. /* /* Arguments: +/* .IP why +/* A pointer to a DSN_BUF which holds error status information when +/* the TLS policy lookup fails. /* .IP dest /* The unmodified next-hop or fall-back destination including /* the optional [] and including the optional port or service. @@ -47,6 +58,8 @@ /* The remote port, network byte order. /* .IP valid /* The DNSSEC validation status of the host name. +/* .IP tls +/* An SMTP_TLS_POLICY object. /* LICENSE /* .ad /* .fi @@ -73,6 +86,8 @@ #ifdef USE_TLS +#include /* ntohs() for Solaris or BSD */ +#include /* ntohs() for Linux or BSD */ #include #include @@ -87,6 +102,7 @@ #include #include #include +#include /* Global library. */ @@ -97,6 +113,10 @@ #include "smtp.h" +#define CACHE_SIZE 20 +static CTABLE *policy_cache; + +static int global_tls_level(void); static MAPS *tls_policy; /* lookup table(s) */ static MAPS *tls_per_site; /* lookup table(s) */ @@ -129,11 +149,14 @@ static const char *policy_name(int tls_level) return name; } +#define MARK_INVALID(why, levelp) do { \ + dsb_simple((why), "4.7.5", "client TLS configuration problem"); \ + *(levelp) = TLS_LEV_INVALID; } while (0) + /* tls_site_lookup - look up per-site TLS security level */ -static void tls_site_lookup(SMTP_TLS_SESS *tls, int *site_level, - const char *site_name, const char *site_class, - DSN_BUF *why) +static void tls_site_lookup(SMTP_TLS_POLICY *tls, int *site_level, + const char *site_name, const char *site_class) { const char *lookup; @@ -162,14 +185,13 @@ static void tls_site_lookup(SMTP_TLS_SESS *tls, int *site_level, } else { msg_warn("%s: unknown TLS policy '%s' for %s %s", tls_per_site->title, lookup, site_class, site_name); - dsb_simple(why, "4.7.5", "client TLS configuration problem"); - *site_level = TLS_LEV_INVALID; + MARK_INVALID(tls->why, site_level); return; } } else if (tls_per_site->error) { msg_warn("%s: %s \"%s\": per-site table lookup error", tls_per_site->title, site_class, site_name); - dsb_simple(why, "4.3.0", "Temporary lookup error"); + dsb_simple(tls->why, "4.3.0", "Temporary lookup error"); *site_level = TLS_LEV_INVALID; return; } @@ -178,9 +200,9 @@ static void tls_site_lookup(SMTP_TLS_SESS *tls, int *site_level, /* tls_policy_lookup_one - look up destination TLS policy */ -static void tls_policy_lookup_one(SMTP_TLS_SESS *tls, int *site_level, - const char *site_name, - const char *site_class, DSN_BUF *why) +static void tls_policy_lookup_one(SMTP_TLS_POLICY *tls, int *site_level, + const char *site_name, + const char *site_class) { const char *lookup; char *policy; @@ -194,6 +216,9 @@ static void tls_policy_lookup_one(SMTP_TLS_SESS *tls, int *site_level, #undef FREE_RETURN #define FREE_RETURN do { myfree(saved_policy); return; } while (0) +#define INVALID_RETURN(why, levelp) do { \ + MARK_INVALID((why), (levelp)); FREE_RETURN; } while (0) + #define WHERE \ vstring_str(vstring_sprintf(cbuf, "%s, %s \"%s\"", \ tls_policy->title, site_class, site_name)) @@ -204,8 +229,7 @@ static void tls_policy_lookup_one(SMTP_TLS_SESS *tls, int *site_level, if ((lookup = maps_find(tls_policy, site_name, 0)) == 0) { if (tls_policy->error) { msg_warn("%s: policy table lookup error", WHERE); - dsb_simple(why, "4.3.0", "Temporary lookup error"); - *site_level = TLS_LEV_INVALID; + MARK_INVALID(tls->why, site_level); } return; } @@ -213,16 +237,13 @@ static void tls_policy_lookup_one(SMTP_TLS_SESS *tls, int *site_level, if ((tok = mystrtok(&policy, "\t\n\r ,")) == 0) { msg_warn("%s: invalid empty policy", WHERE); - dsb_simple(why, "4.7.5", "client TLS configuration problem"); - *site_level = TLS_LEV_INVALID; - FREE_RETURN; + INVALID_RETURN(tls->why, site_level); } *site_level = tls_level_lookup(tok); if (*site_level == TLS_LEV_INVALID) { /* tls_level_lookup() logs no warning. */ msg_warn("%s: invalid security level \"%s\"", WHERE, tok); - dsb_simple(why, "4.7.5", "client TLS configuration problem"); - FREE_RETURN; + INVALID_RETURN(tls->why, site_level); } /* @@ -235,6 +256,13 @@ static void tls_policy_lookup_one(SMTP_TLS_SESS *tls, int *site_level, FREE_RETURN; } + /* + * The fingerprint, verify and secure levels require or + * support explicit TA or EE certificate digest match lists. + */ + if (*site_level >= TLS_LEV_FPRINT) + tls->dane = tls_dane_alloc(TLS_DANE_FLAG_MIXED); + /* * Errors in attributes may have security consequences, don't ignore * errors that can degrade security. @@ -243,24 +271,18 @@ static void tls_policy_lookup_one(SMTP_TLS_SESS *tls, int *site_level, if ((err = split_nameval(tok, &name, &val)) != 0) { msg_warn("%s: malformed attribute/value pair \"%s\": %s", WHERE, tok, err); - dsb_simple(why, "4.7.5", "client TLS configuration problem"); - *site_level = TLS_LEV_INVALID; - FREE_RETURN; + INVALID_RETURN(tls->why, site_level); } /* Only one instance per policy. */ if (!strcasecmp(name, "ciphers")) { if (*val == 0) { msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); - dsb_simple(why, "4.7.5", "client TLS configuration problem"); - *site_level = TLS_LEV_INVALID; - FREE_RETURN; + INVALID_RETURN(tls->why, site_level); } if (tls->grade) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); - dsb_simple(why, "4.7.5", "client TLS configuration problem"); - *site_level = TLS_LEV_INVALID; - FREE_RETURN; + INVALID_RETURN(tls->why, site_level); } tls->grade = mystrdup(val); continue; @@ -270,34 +292,35 @@ static void tls_policy_lookup_one(SMTP_TLS_SESS *tls, int *site_level, if (tls->protocols) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); - dsb_simple(why, "4.7.5", "client TLS configuration problem"); - *site_level = TLS_LEV_INVALID; - FREE_RETURN; + INVALID_RETURN(tls->why, site_level); } tls->protocols = mystrdup(val); continue; } /* Multiple instances per policy. */ if (!strcasecmp(name, "match")) { - char *delim = *site_level == TLS_LEV_FPRINT ? "|" : ":"; - if (*site_level <= TLS_LEV_ENCRYPT) { msg_warn("%s: attribute \"%s\" invalid at security level " "\"%s\"", WHERE, name, policy_name(*site_level)); - dsb_simple(why, "4.7.5", "client TLS configuration problem"); - *site_level = TLS_LEV_INVALID; - FREE_RETURN; + INVALID_RETURN(tls->why, site_level); } if (*val == 0) { msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); - dsb_simple(why, "4.7.5", "client TLS configuration problem"); - *site_level = TLS_LEV_INVALID; - FREE_RETURN; + INVALID_RETURN(tls->why, site_level); + } + switch (*site_level) { + case TLS_LEV_FPRINT: + tls_dane_split(tls->dane, TLS_DANE_EE, TLS_DANE_PKEY, + var_smtp_tls_fpt_dgst, val, "|"); + break; + case TLS_LEV_VERIFY: + case TLS_LEV_SECURE: + if (tls->matchargv == 0) + tls->matchargv = argv_split(val, ":"); + else + argv_split_append(tls->matchargv, val, ":"); + break; } - if (tls->matchargv == 0) - tls->matchargv = argv_split(val, delim); - else - argv_split_append(tls->matchargv, val, delim); continue; } /* Only one instance per policy. */ @@ -305,17 +328,31 @@ static void tls_policy_lookup_one(SMTP_TLS_SESS *tls, int *site_level, if (tls->exclusions) { msg_warn("%s: attribute \"%s\" is specified multiple times", WHERE, name); - dsb_simple(why, "4.7.5", "client TLS configuration problem"); - *site_level = TLS_LEV_INVALID; - FREE_RETURN; + INVALID_RETURN(tls->why, site_level); } tls->exclusions = vstring_strcpy(vstring_alloc(10), val); continue; } + /* Multiple instances per policy. */ + if (!strcasecmp(name, "tafile")) { + /* Only makes sense if we're using CA-based trust */ + if (*site_level <= TLS_LEV_ENCRYPT) { + msg_warn("%s: attribute \"%s\" invalid at security level" + " \"%s\"", WHERE, name, policy_name(*site_level)); + INVALID_RETURN(tls->why, site_level); + } + if (*val == 0) { + msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); + INVALID_RETURN(tls->why, site_level); + } + if (!tls_dane_load_trustfile(tls->dane, val)) { + INVALID_RETURN(tls->why, site_level); + } + continue; + } + msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name); - dsb_simple(why, "4.7.5", "client TLS configuration problem"); - *site_level = TLS_LEV_INVALID; - FREE_RETURN; + INVALID_RETURN(tls->why, site_level); } FREE_RETURN; @@ -323,9 +360,9 @@ static void tls_policy_lookup_one(SMTP_TLS_SESS *tls, int *site_level, /* tls_policy_lookup - look up destination TLS policy */ -static void tls_policy_lookup(SMTP_TLS_SESS *tls, int *site_level, +static void tls_policy_lookup(SMTP_TLS_POLICY *tls, int *site_level, const char *site_name, - const char *site_class, DSN_BUF *why) + const char *site_class) { /* @@ -336,18 +373,36 @@ static void tls_policy_lookup(SMTP_TLS_SESS *tls, int *site_level, * sub-domains of the recipient domain. */ if (!valid_hostname(site_name, DONT_GRIPE)) { - tls_policy_lookup_one(tls, site_level, site_name, site_class, why); + tls_policy_lookup_one(tls, site_level, site_name, site_class); return; } do { - tls_policy_lookup_one(tls, site_level, site_name, site_class, why); + tls_policy_lookup_one(tls, site_level, site_name, site_class); } while (*site_level == TLS_LEV_NOTFOUND && (site_name = strchr(site_name + 1, '.')) != 0); } +/* load_tas - load one or more ta files */ + +static int load_tas(TLS_DANE *dane, const char *files) +{ + int ret = 0; + char *save = mystrdup(files); + char *buf = save; + char *file; + + do { + if ((file = mystrtok(&buf, "\t\n\r ,")) != 0) + ret = tls_dane_load_trustfile(dane, file); + } while (file && ret); + + myfree(save); + return (ret); +} + /* set_cipher_grade - Set cipher grade and exclusions */ -static void set_cipher_grade(SMTP_TLS_SESS *tls) +static void set_cipher_grade(SMTP_TLS_POLICY *tls) { const char *mand_exclude = ""; const char *also_exclude = ""; @@ -403,60 +458,35 @@ static void set_cipher_grade(SMTP_TLS_SESS *tls) ADD_EXCLUDE(tls->exclusions, also_exclude); } -/* smtp_tls_sess_alloc - session TLS policy parameters */ +/* tls_policy_init - initialize policy in an embryonic cache entry */ -SMTP_TLS_SESS *smtp_tls_sess_alloc(DSN_BUF *why, const char *dest, - const char *host, unsigned port, int valid) +static void tls_policy_init(SMTP_TLS_POLICY *tls, const char *dest, + const char *host, unsigned port, int valid) { - const char *myname = "smtp_tls_sess_alloc"; - int global_level; + const char *myname = "tls_policy_init"; int site_level; - SMTP_TLS_SESS *tls = (SMTP_TLS_SESS *) mymalloc(sizeof(*tls)); - - tls->level = TLS_LEV_NONE; - tls->protocols = 0; - tls->grade = 0; - tls->exclusions = 0; - tls->matchargv = 0; - - if (!dest) - return (tls); - /* - * Compute the global TLS policy. This is the default policy level when - * no per-site policy exists. It also is used to override a wild-card - * per-site policy. - */ - if (*var_smtp_tls_level) { - /* Require that var_smtp_tls_level is sanitized upon startup. */ - global_level = tls_level_lookup(var_smtp_tls_level); - if (global_level == TLS_LEV_INVALID) - msg_panic("%s: invalid TLS security level: \"%s\"", - myname, var_smtp_tls_level); - } else if (var_smtp_enforce_tls) { - global_level = var_smtp_tls_enforce_peername ? - TLS_LEV_VERIFY : TLS_LEV_ENCRYPT; - } else { - global_level = var_smtp_use_tls ? - TLS_LEV_MAY : TLS_LEV_NONE; + /* Caller requested trivial policy */ + if (!dest) { + tls->level = TLS_LEV_NONE; + return; } - if (msg_verbose) - msg_info("%s TLS level: %s", "global", policy_name(global_level)); /* * Compute the per-site TLS enforcement level. For compatibility with the * original TLS patch, this algorithm is gives equal precedence to host * and next-hop policies. */ + tls->level = global_tls_level(); site_level = TLS_LEV_NOTFOUND; if (tls_policy) { - tls_policy_lookup(tls, &site_level, dest, "next-hop destination", why); + tls_policy_lookup(tls, &site_level, dest, "next-hop destination"); } else if (tls_per_site) { - tls_site_lookup(tls, &site_level, dest, "next-hop destination", why); + tls_site_lookup(tls, &site_level, dest, "next-hop destination"); if (site_level != TLS_LEV_INVALID && strcasecmp(dest, host) != 0) - tls_site_lookup(tls, &site_level, host, "server hostname", why); + tls_site_lookup(tls, &site_level, host, "server hostname"); /* * Override a wild-card per-site policy with a more specific global @@ -474,18 +504,16 @@ SMTP_TLS_SESS *smtp_tls_sess_alloc(DSN_BUF *why, const char *dest, * (non-wildcard) per-site policies consistently override global * policies. */ - if (site_level == TLS_LEV_MAY && global_level > TLS_LEV_MAY) - site_level = global_level; + if (site_level == TLS_LEV_MAY && tls->level > TLS_LEV_MAY) + site_level = tls->level; } switch (site_level) { - case TLS_LEV_INVALID: - return (smtp_tls_sess_free(tls)); - case TLS_LEV_NOTFOUND: - tls->level = global_level; - break; default: tls->level = site_level; + case TLS_LEV_NOTFOUND: break; + case TLS_LEV_INVALID: + return; } /* @@ -514,36 +542,58 @@ SMTP_TLS_SESS *smtp_tls_sess_alloc(DSN_BUF *why, const char *dest, case TLS_LEV_DANE: break; case TLS_LEV_FPRINT: - if (tls->matchargv == 0) - tls->matchargv = - argv_split(var_smtp_tls_fpt_cmatch, "\t\n\r, |"); + if (tls->dane == 0) + tls->dane = tls_dane_alloc(TLS_DANE_FLAG_MIXED); + if (!TLS_DANE_HASEE(tls->dane)) { + tls_dane_split(tls->dane, TLS_DANE_EE, TLS_DANE_PKEY, + var_smtp_tls_fpt_dgst, var_smtp_tls_fpt_cmatch, + "\t\n\r, "); + if (!TLS_DANE_HASEE(tls->dane)) { + msg_warn("nexthop domain %s: configured at fingerprint " + "security level, but with no fingerprints to match.", + dest); + MARK_INVALID(tls->why, &tls->level); + return; + } + } + tls_dane_final(tls->dane); break; case TLS_LEV_VERIFY: - if (tls->matchargv == 0) - tls->matchargv = - argv_split(var_smtp_tls_vfy_cmatch, "\t\n\r, :"); - break; case TLS_LEV_SECURE: if (tls->matchargv == 0) tls->matchargv = - argv_split(var_smtp_tls_sec_cmatch, "\t\n\r, :"); + argv_split(tls->level == TLS_LEV_VERIFY ? + var_smtp_tls_vfy_cmatch : var_smtp_tls_sec_cmatch, + "\t\n\r, :"); + if (*var_smtp_tls_tafile) { + if (tls->dane == 0) + tls->dane = tls_dane_alloc(TLS_DANE_FLAG_MIXED); + if (!TLS_DANE_HASTA(tls->dane)) { + if (load_tas(tls->dane, var_smtp_tls_tafile)) + tls_dane_final(tls->dane); + else { + MARK_INVALID(tls->why, &tls->level); + return; + } + } + } break; default: - msg_panic("unexpected TLS security level: %d", - tls->level); + msg_panic("unexpected TLS security level: %d", tls->level); } - if (msg_verbose && (tls_policy || tls_per_site)) + if (msg_verbose && tls->level != global_tls_level()) msg_info("%s TLS level: %s", "effective", policy_name(tls->level)); - - return (tls); } -/* smtp_sess_tls_free - free and return null pointer of same type */ +/* tls_policy_free - free no longer cached policy */ -SMTP_TLS_SESS *smtp_tls_sess_free(SMTP_TLS_SESS *tls) +void smtp_tls_policy_free(SMTP_TLS_POLICY *tls) { + if (--tls->refs > 0) + return; + if (tls->protocols) myfree(tls->protocols); if (tls->grade) @@ -552,9 +602,117 @@ SMTP_TLS_SESS *smtp_tls_sess_free(SMTP_TLS_SESS *tls) vstring_free(tls->exclusions); if (tls->matchargv) argv_free(tls->matchargv); + if (tls->dane) + tls_dane_free(tls->dane); + dsb_free(tls->why); myfree((char *) tls); +} + +/* policy_create - create embryonic SMTP TLS policy, ctable glue. */ + +static void *policy_create(const char *key, void *unused_context) +{ + SMTP_TLS_POLICY *tls = (SMTP_TLS_POLICY *)mymalloc(sizeof(*tls)); + + tls->refs = 1; + + /* First use will update level to a real level or TLS_LEV_INVALID */ + tls->level = TLS_LEV_NOTFOUND; + tls->protocols = 0; + tls->grade = 0; + tls->exclusions = 0; + tls->matchargv = 0; + tls->why = dsb_create(); + tls->dane = 0; + + return ((void *) tls); +} + +/* policy_delete - delete SMTP TLS policy, ctable glue. */ + +static void policy_delete(void *item, void *unused_context) +{ + smtp_tls_policy_free((SMTP_TLS_POLICY *)item); +} + +/* smtp_tls_policy - cached lookup of TLS policy */ + +SMTP_TLS_POLICY *smtp_tls_policy(DSN_BUF *why, const char *dest, + const char *host, unsigned port, int valid) +{ + SMTP_TLS_POLICY *tls; + VSTRING *key; + + if (policy_cache == 0) + policy_cache = + ctable_create(CACHE_SIZE, policy_create, policy_delete, 0); + + if (dest) { + key = vstring_alloc(strlen(dest) + strlen(host) + 10); + vstring_sprintf(key, "%s %s:%u %d", dest, host, ntohs(port), !!valid); + tls = (SMTP_TLS_POLICY *)ctable_locate(policy_cache, vstring_str(key)); + vstring_free(key); + } else { + tls = (SMTP_TLS_POLICY *)ctable_locate(policy_cache, ""); + } + + /* One-time initialization */ + if (tls->level == TLS_LEV_NOTFOUND) + tls_policy_init(tls, dest, host, port, valid); + + if (tls->level != TLS_LEV_INVALID) { + ++tls->refs; + return (tls); + } + + if (why) + dsb_update(why, + STR(tls->why->status), STR(tls->why->action), + STR(tls->why->mtype), STR(tls->why->mname), + STR(tls->why->dtype), STR(tls->why->dtext), + "%s", STR(tls->why->reason)); return (0); } +/* smtp_tls_policy_flush - flush TLS policy cache */ + +void smtp_tls_policy_flush(void) +{ + if (policy_cache == 0) + return; + ctable_free(policy_cache); + policy_cache = 0; +} + +/* global_tls_level - parse and cache var_smtp_tls_level */ + +static int global_tls_level(void) +{ + static int l = TLS_LEV_NOTFOUND; + + if (l != TLS_LEV_NOTFOUND) + return l; + + /* + * Compute the global TLS policy. This is the default policy level when + * no per-site policy exists. It also is used to override a wild-card + * per-site policy. + * + * We require that the global level is valid on startup. + */ + if (*var_smtp_tls_level) { + if ((l = tls_level_lookup(var_smtp_tls_level)) == TLS_LEV_INVALID) + msg_fatal("invalid tls security level: \"%s\"", var_smtp_tls_level); + } else if (var_smtp_enforce_tls) + l = var_smtp_tls_enforce_peername ? TLS_LEV_VERIFY : TLS_LEV_ENCRYPT; + else + l = var_smtp_use_tls ? TLS_LEV_MAY : TLS_LEV_NONE; + + if (msg_verbose) + msg_info("%s TLS level: %s", "global", policy_name(l)); + + return l; +} + #endif diff --git a/postfix/src/tls/Makefile.in b/postfix/src/tls/Makefile.in index f1565f3bb..92bf67818 100644 --- a/postfix/src/tls/Makefile.in +++ b/postfix/src/tls/Makefile.in @@ -1,13 +1,13 @@ SHELL = /bin/sh SRCS = tls_prng_dev.c tls_prng_egd.c tls_prng_file.c tls_fprint.c \ tls_prng_exch.c tls_stream.c tls_bio_ops.c tls_misc.c tls_dh.c \ - tls_rsa.c tls_verify.c tls_certkey.c tls_session.c \ + tls_rsa.c tls_verify.c tls_dane.c tls_certkey.c tls_session.c \ tls_client.c tls_server.c tls_scache.c tls_mgr.c tls_seed.c \ tls_level.c \ tls_proxy_clnt.c tls_proxy_print.c tls_proxy_scan.c OBJS = tls_prng_dev.o tls_prng_egd.o tls_prng_file.o tls_fprint.o \ tls_prng_exch.o tls_stream.o tls_bio_ops.o tls_misc.o tls_dh.o \ - tls_rsa.o tls_verify.o tls_certkey.o tls_session.o \ + tls_rsa.o tls_verify.o tls_dane.o tls_certkey.o tls_session.o \ tls_client.o tls_server.o tls_scache.o tls_mgr.o tls_seed.o \ tls_level.o \ tls_proxy_clnt.o tls_proxy_print.o tls_proxy_scan.o @@ -128,6 +128,21 @@ tls_client.o: ../../include/vstring.h tls_client.o: tls.h tls_client.o: tls_client.c tls_client.o: tls_mgr.h +tls_dane.o: ../../include/argv.h +tls_dane.o: ../../include/dns.h +tls_dane.o: ../../include/msg.h +tls_dane.o: ../../include/myaddrinfo.h +tls_dane.o: ../../include/mymalloc.h +tls_dane.o: ../../include/name_code.h +tls_dane.o: ../../include/name_mask.h +tls_dane.o: ../../include/sock_addr.h +tls_dane.o: ../../include/stringops.h +tls_dane.o: ../../include/sys_defs.h +tls_dane.o: ../../include/vbuf.h +tls_dane.o: ../../include/vstream.h +tls_dane.o: ../../include/vstring.h +tls_dane.o: tls.h +tls_dane.o: tls_dane.c tls_dh.o: ../../include/argv.h tls_dh.o: ../../include/mail_params.h tls_dh.o: ../../include/msg.h diff --git a/postfix/src/tls/tls.h b/postfix/src/tls/tls.h index a89f456ac..dff4cf566 100644 --- a/postfix/src/tls/tls.h +++ b/postfix/src/tls/tls.h @@ -71,6 +71,96 @@ extern const NAME_CODE tls_level_table[]; #define TLS_MGR_SCACHE_SMTP "smtp" #define TLS_MGR_SCACHE_LMTP "lmtp" + /* + * RFC 6698 DANE + */ +#define TLS_DANE_TA 0 /* Match trust-anchor digests */ +#define TLS_DANE_EE 1 /* Match end-entity digests */ + +#define TLS_DANE_CERT 0 /* Match the certificate digest */ +#define TLS_DANE_PKEY 1 /* Match the public key digest */ + +#define TLS_DANE_FLAG_MIXED (1<<0) /* Combined pkeys and certs */ +#define TLS_DANE_FLAG_FINAL (1<<1) /* No further changes */ + + /* + * Linked list of either t = X509 certs or t = EVP_PKEY public keys + */ +#define TLS_DANE_L(t) TLS_DANE_##t##_L + +#define DECL_TLS_DANE_L(t) \ + typedef struct TLS_DANE_L(t) { \ + t *item; \ + struct TLS_DANE_L(t) *next; \ + } TLS_DANE_L(t) + +#define TLS_DANE_Z(t) ((TLS_DANE_L(t) *)0) +#define TLS_DANE_H(t) t##_head +#define TLS_DANE_HEAD(d, t) ((d) ? (d)->TLS_DANE_H(t) : TLS_DANE_Z(t)) +#define TLS_DANE_MEMB(t) TLS_DANE_L(t) *TLS_DANE_H(t) + +DECL_TLS_DANE_L(X509); +DECL_TLS_DANE_L(EVP_PKEY); + + /* + * Certificate and public key digests (typically from TLSA RRs), + * grouped by algorithm. + */ +typedef struct TLS_TLSA { + char *mdalg; /* Algorithm for this digest list */ + ARGV *certs; /* Complete certificate digests */ + ARGV *pkeys; /* SubjectPublicKeyInfo digests */ + struct TLS_TLSA *next; /* Chain to next algorithm */ +} TLS_TLSA; + + /* + * When TLS_DANE_FLAG_MIXED is set, the pkeys digest list is not allocated + * separately, and aliases the certs digest list for each algorithm. + */ +typedef struct TLS_DANE { + TLS_TLSA *ta; /* Trust-anchor cert/pubkey digests */ + TLS_TLSA *ee; /* End-entity cert/pubkey digests */ + TLS_DANE_MEMB(X509); /* Full trust-anchor certificates */ + TLS_DANE_MEMB(EVP_PKEY); /* Full trust-anchor public keys */ + int flags; /* Conflate cert and pkey digests */ +} TLS_DANE; + +#define TLS_DANE_HASTA(d) ((d) ? (d)->ta : 0) +#define TLS_DANE_HASEE(d) ((d) ? (d)->ee : 0) + +#define TLS_DANE_LINSERT(t) dane_##t##_list_insert +#define TLS_DANE_LFREE(t) dane_##t##_list_free +#define IMPL_TLS_DANE_L(t) \ + static void TLS_DANE_LINSERT(t)(TLS_DANE *d, t *i) \ + { \ + TLS_DANE_L(t) *new = (TLS_DANE_L(t) *) mymalloc(sizeof(*new)); \ + CRYPTO_add(&i->references, 1, CRYPTO_LOCK_##t); \ + new->item = i; \ + new->next = d->TLS_DANE_H(t); \ + d->TLS_DANE_H(t) = new; \ + } \ + static void TLS_DANE_LFREE(t)(TLS_DANE *d) \ + { \ + TLS_DANE_L(t) *head; \ + TLS_DANE_L(t) *next; \ + for (head = TLS_DANE_HEAD(d, t); head; head = next) { \ + next = head->next; \ + t##_free(head->item); \ + } \ + } + + /* + * tls_dane.c + */ +extern int tls_dane_avail(void); +extern void tls_dane_verbose(int); +extern TLS_DANE *tls_dane_alloc(int); +extern void tls_dane_split(TLS_DANE *, int, int, const char *, const char *, + const char *); +extern TLS_DANE *tls_dane_final(TLS_DANE *); +extern void tls_dane_free(TLS_DANE *); +extern int tls_dane_load_trustfile(TLS_DANE *, const char *); + /* * TLS session context, also used by the VSTREAM call-back routines for SMTP * input/output, and by OpenSSL call-back routines for key verification. @@ -101,7 +191,11 @@ typedef struct { const char *mdalg; /* default message digest algorithm */ /* Built-in vs external SSL_accept/read/write/shutdown support. */ VSTREAM *stream; /* Blocking-mode SMTP session */ + /* RFC 6698 DANE trust input and verification state */ + const TLS_DANE *dane; /* DANE TLSA digests */ + int trustdepth; /* Chain depth of trusted cert */ int errordepth; /* Chain depth of error cert */ + int chaindepth; /* Chain depth of top cert */ int errorcode; /* First error at error depth */ X509 *errorcert; /* Error certificate closest to leaf */ } TLS_SESS_STATE; @@ -256,6 +350,7 @@ typedef struct { const char *cipher_exclusions; /* Ciphers to exclude */ const ARGV *matchargv; /* Cert match patterns */ const char *mdalg; /* default message digest algorithm */ + const TLS_DANE *dane; /* RFC 6698 verification */ } TLS_CLIENT_START_PROPS; extern TLS_APPL_STATE *tls_client_init(const TLS_CLIENT_INIT_PROPS *); @@ -272,11 +367,11 @@ extern TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *); ((props)->a12), ((props)->a13), (props))) #define TLS_CLIENT_START(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, \ - a10, a11, a12, a13, a14) \ + a10, a11, a12, a13, a14, a15) \ tls_client_start((((props)->a1), ((props)->a2), ((props)->a3), \ ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \ ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \ - ((props)->a12), ((props)->a13), ((props)->a14), (props))) + ((props)->a12), ((props)->a13), ((props)->a14), ((props)->a15), (props))) /* * tls_server.c @@ -402,6 +497,7 @@ extern RSA *tls_tmp_rsa_cb(SSL *, int, int); extern char *tls_peer_CN(X509 *, const TLS_SESS_STATE *); extern char *tls_issuer_CN(X509 *, const TLS_SESS_STATE *); extern const char *tls_dns_name(const GENERAL_NAME *, const TLS_SESS_STATE *); +extern int tls_cert_match(TLS_SESS_STATE *, int, X509 *, int); extern int tls_verify_certificate_callback(int, X509_STORE_CTX *); extern void tls_log_verify_error(TLS_SESS_STATE *); diff --git a/postfix/src/tls/tls_client.c b/postfix/src/tls/tls_client.c index 2282c4489..9af4b7f3f 100644 --- a/postfix/src/tls/tls_client.c +++ b/postfix/src/tls/tls_client.c @@ -595,7 +595,8 @@ static void verify_extract_name(TLS_SESS_STATE *TLScontext, X509 *peercert, if (SSL_get_verify_result(TLScontext->con) == X509_V_OK) TLScontext->peer_status |= TLS_CERT_FLAG_TRUSTED; - if (TLS_CERT_IS_TRUSTED(TLScontext) && props->tls_level >= TLS_LEV_VERIFY) + if (TLS_CERT_IS_TRUSTED(TLScontext) + && (props->tls_level >= TLS_LEV_VERIFY || TLS_DANE_HASTA(props->dane))) verify_peername = 1; /* Force cert processing so we can log the data? */ @@ -720,26 +721,13 @@ static void verify_extract_name(TLS_SESS_STATE *TLScontext, X509 *peercert, static void verify_extract_print(TLS_SESS_STATE *TLScontext, X509 *peercert, const TLS_CLIENT_START_PROPS *props) { - char **cpp; - - /* Non-null by contract */ TLScontext->peer_fingerprint = tls_fingerprint(peercert, props->mdalg); TLScontext->peer_pkey_fprint = tls_pkey_fprint(peercert, props->mdalg); - /* - * Compare the fingerprint against each acceptable value, ignoring - * upper/lower case differences. - */ - if (props->tls_level == TLS_LEV_FPRINT) { - for (cpp = props->matchargv->argv; *cpp; ++cpp) { - if (strcasecmp(TLScontext->peer_fingerprint, *cpp) == 0 - || strcasecmp(TLScontext->peer_pkey_fprint, *cpp) == 0) { - TLScontext->peer_status |= - TLS_CERT_FLAG_TRUSTED | TLS_CERT_FLAG_MATCHED; - break; - } - } - } + if (TLS_DANE_HASEE(props->dane) + && tls_cert_match(TLScontext, TLS_DANE_EE, peercert, 0)) + TLScontext->peer_status |= + TLS_CERT_FLAG_TRUSTED | TLS_CERT_FLAG_MATCHED; } /* @@ -764,7 +752,7 @@ TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *props) * When certificate verification is required, log trust chain validation * errors even when disabled by default for opportunistic sessions. */ - if (props->tls_level >= TLS_LEV_VERIFY) + if (props->tls_level >= TLS_LEV_VERIFY || TLS_DANE_HASTA(props->dane)) log_mask |= TLS_LOG_UNTRUSTED; if (log_mask & TLS_LOG_VERBOSE) @@ -814,6 +802,15 @@ TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *props) * not attempt to re-use sessions whose ciphers are too weak. We salt the * session lookup key with the cipher list, so that sessions found in the * cache are always acceptable. + * + * With DANE, (more generally any TLScontext where we specified explicit + * trust-anchor or end-entity certificates) the verification status of + * the SSL session depends on the specified list. Since we verify the + * certificate only during the initial handshake, we must segregate + * sessions with different TA lists. Note, that TA re-verification + * is not possible with cached sessions, since these don't hold the complete + * peer trust chain. Therefore, we compute a digest of the sorted TA + * parameters and append it to the serverid. */ myserverid = tls_serverid_digest(props, protomask, cipher_list); @@ -832,6 +829,9 @@ TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *props) TLScontext->stream = props->stream; TLScontext->mdalg = props->mdalg; + /* Alias DANE digest info from props */ + TLScontext->dane = props->dane; + if ((TLScontext->con = SSL_new(app_ctx->ssl_ctx)) == NULL) { msg_warn("Could not allocate 'TLScontext->con' with SSL_new()"); tls_print_errors(); @@ -973,9 +973,11 @@ TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *props) /* * Peer name or fingerprint verification as requested. * Unconditionally set peer_CN, issuer_CN and peer_fingerprint. + * Check fingerprint first, and avoid logging verified as untrusted + * in the call to verify_extract_name(). */ - verify_extract_name(TLScontext, peercert, props); verify_extract_print(TLScontext, peercert, props); + verify_extract_name(TLScontext, peercert, props); if (TLScontext->log_mask & (TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE | TLS_LOG_PEERCERT)) diff --git a/postfix/src/tls/tls_dane.c b/postfix/src/tls/tls_dane.c new file mode 100644 index 000000000..2694259fb --- /dev/null +++ b/postfix/src/tls/tls_dane.c @@ -0,0 +1,458 @@ +/*++ +/* NAME +/* tls_dane 3 +/* SUMMARY +/* Support for RFC 6698 (DANE) certificate matching +/* SYNOPSIS +/* #include +/* +/* int tls_dane_avail() +/* +/* void tls_dane_verbose(on) +/* int on; +/* +/* TLS_DANE *tls_dane_alloc(flags) +/* int flags; +/* +/* void tls_dane_free(dane) +/* TLS_DANE *dane; +/* +/* void tls_dane_split(dane, certusage, selector, mdalg, digest, delim) +/* TLS_DANE *dane; +/* int certusage; +/* int selector; +/* const char *mdalg; +/* const char *digest; +/* const char *delim; +/* +/* int tls_dane_load_trustfile(dane, tafile) +/* TLS_DANE *dane; +/* const char *tafile; +/* +/* TLS_DANE *tls_dane_final(dane) +/* TLS_DANE *dane; +/* DESCRIPTION +/* tls_dane_avail() returns true if the features required to support DANE +/* are present in OpenSSL's libcrypto and in libresolv. Since OpenSSL's +/* libcrypto is not initialized until we call tls_client_init(), calls +/* to tls_dane_avail() must be deferred until this initialization is +/* completed successufully. +/* +/* tls_dane_verbose() turns on verbose logging of TLSA record lookups. +/* +/* tls_dane_alloc() returns a pointer to a newly allocated TLS_DANE +/* structure with null ta and ee digest sublists. If "flags" includes +/* TLS_DANE_FLAG_MIXED, the cert and pkey digests are stored together on +/* the pkeys list with the certs list empty, otherwise they are stored +/* separately. +/* +/* tls_dane_free() frees the structure allocated by tls_dane_alloc(). +/* +/* tls_dane_split() splits "digest" using the characters in "delim" as +/* delimiters and stores the results with the requested "certusage" +/* and "selector". This is an incremental interface, that builds a +/* TLS_DANE structure outside the cache by manually adding entries. +/* Once all the entries have been added, the caller must call +/* tls_dane_final() to complete its construction. +/* +/* tls_dane_load_trustfile() imports trust-anchor certificates and +/* public keys from a file (rather than DNS TLSA records). +/* +/* tls_dane_final() completes the construction of a TLS_DANE structure, +/* obtained via tls_dane_alloc() and populated via tls_dane_split() or +/* tls_dane_load_trustfile(). After tls_dane_final() is called, the +/* structure must not be modified. The return value is the input +/* argument. +/* +/* Arguments: +/* .IP dane +/* Pointer to a TLS_DANE structure that lists the valid trust-anchor +/* and end-entity full-certificate and/or public-key digests. +/* .IP host +/* DNSSEC validated (cname resolved) FQDN of target service. +/* .IP proto +/* Almost certainly "tcp". +/* .IP port +/* The TCP port in network byte order. +/* .IP flags +/* Only one flag is part of the public interface at this time: +/* .RS +/* .IP TLS_DANE_FLAG_MIXED +/* Don't distinguish between certificate and public-key digests. +/* A single digest list for both certificates and keys with be +/* stored for each algorithm in the "pkeys" field, the "certs" +/* field will be null. +/* .RE +/* .IP certusage +/* Trust anchor (TLS_DANE_TA) or end-entity (TLS_DANE_EE) digests? +/* .IP selector +/* Full certificate (TLS_DANE_CERT) or pubkey (TLS_DANE_PKEY) digests? +/* .IP mdalg +/* Name of a message digest algorithm suitable for computing secure +/* (1st pre-image resistant) message digests of certificates. For now, +/* md5, sha1, or member of SHA-2 family if supported by OpenSSL. +/* .IP digest +/* The digest (or list of digests concatenated with characters from +/* "delim") to be added to the TLS_DANE record. +/* .IP delim +/* The set of delimiter characters used above. +/* LICENSE +/* .ad +/* .fi +/* This software is free. You can do with it whatever you want. +/* The original author kindly requests that you acknowledge +/* the use of his software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Viktor Dukhovni +/*--*/ + +/* System library. */ + +#include +#include +#include + +#ifdef USE_TLS +#include + +/* Utility library. */ + +#include +#include +#include +#include + +#define STR(x) vstring_str(x) + +/* DNS library. */ + +#include + +/* TLS library. */ + +#define TLS_INTERNAL +#include + +/* Application-specific. */ + +static const char *sha256 = "sha256"; +static const char *sha512 = "sha512"; +static int sha256len; +static int sha512len; +static int dane_verbose; +static TLS_TLSA **dane_locate(TLS_TLSA **, const char *); + +IMPL_TLS_DANE_L(X509) +IMPL_TLS_DANE_L(EVP_PKEY) + +/* tls_dane_verbose - enable/disable verbose logging */ + +void tls_dane_verbose(int on) +{ + dane_verbose = on; +} + +/* tls_dane_avail - check for availability of dane required digests */ + +int tls_dane_avail(void) +{ + static int avail = -1; + const EVP_MD *sha256md; + const EVP_MD *sha512md; + + if (avail >= 0) + return avail; + + sha256md = EVP_get_digestbyname(sha256); + sha512md = EVP_get_digestbyname(sha512); + + if (sha256md == 0 || sha512md == 0 + || RES_USE_DNSSEC == 0 || RES_USE_EDNS0 == 0) + return (avail = 0); + + sha256len = EVP_MD_size(sha256md); + sha512len = EVP_MD_size(sha512md); + + return (avail = 1); +} + +/* tls_dane_alloc - allocate a TLS_DANE structure */ + +TLS_DANE *tls_dane_alloc(int flags) +{ + TLS_DANE *dane = (TLS_DANE *)mymalloc(sizeof(*dane)); + + dane->ta = 0; + dane->ee = 0; + dane->TLS_DANE_H(X509) = 0; + dane->TLS_DANE_H(EVP_PKEY) = 0; + dane->flags = flags; + return (dane); +} + +static void tlsa_free(TLS_TLSA *tlsa) +{ + + myfree(tlsa->mdalg); + if (tlsa->certs) + argv_free(tlsa->certs); + if (tlsa->pkeys) + argv_free(tlsa->pkeys); + myfree((char *)tlsa); +} + +/* tls_dane_free - free a TLS_DANE structure */ + +void tls_dane_free(TLS_DANE *dane) +{ + TLS_TLSA *tlsa; + TLS_TLSA *next; + + /* De-allocate TA and EE lists */ + for (tlsa = dane->ta; tlsa; tlsa = next) { + next = tlsa->next; + tlsa_free(tlsa); + } + for (tlsa = dane->ee; tlsa; tlsa = next) { + next = tlsa->next; + tlsa_free(tlsa); + } + TLS_DANE_LFREE(X509)(dane); + TLS_DANE_LFREE(EVP_PKEY)(dane); + myfree((char *)dane); +} + +/* dane_free - ctable style */ + +static void dane_free(void *dane, void *unused_context) +{ + tls_dane_free((TLS_DANE *)dane); +} + +/* tlsa_sort - sort digests for a single certusage */ + +static void tlsa_sort(TLS_TLSA *tlsa) +{ + for (; tlsa; tlsa = tlsa->next) { + if (tlsa->pkeys) + argv_sort(tlsa->pkeys); + if (tlsa->certs) + argv_sort(tlsa->certs); + } +} + +/* tls_dane_final - finish by sorting into canonical order */ + +TLS_DANE *tls_dane_final(TLS_DANE *dane) +{ + /* + * We only sort the trust anchors, see tls_serverid_digest(). + */ + if (dane->ta) + tlsa_sort(dane->ta); + dane->flags |= TLS_DANE_FLAG_FINAL; + return (dane); +} + +/* dane_locate - list head address of TLSA sublist for given algorithm */ + +static TLS_TLSA **dane_locate(TLS_TLSA **tlsap, const char *mdalg) +{ + TLS_TLSA *new; + + /* + * Correct computation of the session cache serverid requires a TLSA + * digest list that is sorted by algorithm name. Below we maintain + * the sort order (by algorithm name canonicalized to lowercase). + */ + for (; *tlsap; tlsap = &(*tlsap)->next) { + int cmp = strcasecmp(mdalg, (*tlsap)->mdalg); + + if (cmp == 0) + return (tlsap); + if (cmp < 0) + break; + } + + new = (TLS_TLSA *)mymalloc(sizeof(*new)); + new->mdalg = lowercase(mystrdup(mdalg)); + new->certs = 0; + new->pkeys = 0; + new->next = *tlsap; + *tlsap = new; + + return (tlsap); +} + +/* tls_dane_split - split and append digests */ + +void tls_dane_split(TLS_DANE *dane, int certusage, int selector, + const char *mdalg, const char *digest, const char *delim) +{ + TLS_TLSA **tlsap; + TLS_TLSA *tlsa; + ARGV **argvp; + + if (dane->flags & TLS_DANE_FLAG_FINAL) + msg_panic("updating frozen TLS_DANE object"); + + tlsap = (certusage == TLS_DANE_EE) ? &dane->ee : &dane->ta; + tlsa = *(tlsap = dane_locate(tlsap, mdalg)); + argvp = ((dane->flags & TLS_DANE_FLAG_MIXED) || selector == TLS_DANE_PKEY) ? + &tlsa->pkeys : &tlsa->certs; + + /* Delimited append, may append nothing */ + if (*argvp == 0) + *argvp = argv_split(digest, delim); + else + argv_split_append(*argvp, digest, delim); + + if ((*argvp)->argc == 0) { + argv_free(*argvp); + *argvp = 0; + + /* Remove empty elements from the list */ + if (tlsa->pkeys == 0 && tlsa->certs == 0) { + *tlsap = tlsa->next; + tlsa_free(tlsa); + } + } +} + +/* dane_add - add a digest entry */ + +static void dane_add(TLS_DANE *dane, int certusage, int selector, + const char *mdalg, char *digest) +{ + TLS_TLSA **tlsap; + TLS_TLSA *tlsa; + ARGV **argvp; + + if (dane->flags & TLS_DANE_FLAG_FINAL) + msg_panic("updating frozen TLS_DANE object"); + + switch (certusage) { + case DNS_TLSA_USAGE_CA_CONSTRAINT: + case DNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION: + certusage = TLS_DANE_TA; /* Collapse 0/2 -> 2 */ + break; + case DNS_TLSA_USAGE_SERVICE_CERTIFICATE_CONSTRAINT: + case DNS_TLSA_USAGE_DOMAIN_ISSUED_CERTIFICATE: + certusage = TLS_DANE_EE; /* Collapse 1/3 -> 3 */ + break; + } + + switch (selector) { + case DNS_TLSA_SELECTOR_FULL_CERTIFICATE: + selector = TLS_DANE_CERT; + break; + case DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO: + selector = TLS_DANE_PKEY; + break; + } + + tlsap = (certusage == TLS_DANE_EE) ? &dane->ee : &dane->ta; + tlsa = *(tlsap = dane_locate(tlsap, mdalg)); + argvp = ((dane->flags & TLS_DANE_FLAG_MIXED) || selector == TLS_DANE_PKEY) ? + &tlsa->pkeys : &tlsa->certs; + + if (*argvp == 0) + *argvp = argv_alloc(1); + argv_add(*argvp, digest, ARGV_END); +} + +/* tls_dane_load_trustfile - load trust anchor certs or keys from file */ + +int tls_dane_load_trustfile(TLS_DANE *dane, const char *tafile) +{ + FILE *fp; + char *name = 0; + char *header = 0; + long error = 0; + long len; + int ret = 0; + unsigned char *data = 0; + + /* nop */ + if (tafile == 0 || *tafile == 0) + return (1); + + if ((fp = fopen(tafile, "r")) == NULL) { + msg_warn("error opening trust anchor file: %s: %m", tafile); + return (0); + } + + /* Don't report old news */ + ERR_clear_error(); + + while (PEM_read(fp, &name, &header, &data, &len)) { + const unsigned char *p = data; + int selector = 0; + + if (strcmp(name, PEM_STRING_X509) == 0 + || strcmp(name, PEM_STRING_X509_OLD) == 0) { + X509 *cert = d2i_X509(0, &p, len); + + if (!(ret = (cert && (p - data) == len))) { + msg_warn("error reading: %s: malformed trust-anchor %s", + "certificate", tafile); + tls_print_errors(); + break; + } + TLS_DANE_LINSERT(X509)(dane, cert); + X509_free(cert); + selector = DNS_TLSA_SELECTOR_FULL_CERTIFICATE; + } else if (strcmp(name, PEM_STRING_PUBLIC) == 0) { + EVP_PKEY *pkey = d2i_PUBKEY(0, &p, len); + + if (!(ret = (pkey && (p - data) == len))) { + msg_warn("error reading: %s: malformed trust-anchor %s", + "public key", tafile); + tls_print_errors(); + break; + } + TLS_DANE_LINSERT(EVP_PKEY)(dane, pkey); + EVP_PKEY_free(pkey); + selector = DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO; + } + + if (ret) { + int usage = DNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION; + char *digest = tls_fprint((char *)data, len, sha256); + + dane_add(dane, usage, selector, sha256, digest); + myfree(digest); + } + if (name) + OPENSSL_free(name); + if (header) + OPENSSL_free(header); + if (data) + OPENSSL_free(data); + name = header = (char *) (data = 0); + } + + if (fclose(fp) == EOF) { + msg_warn("error reading trust anchor file: %s: %m", tafile); + return (0); + } + + switch(ERR_GET_REASON(ERR_peek_last_error())) { + case PEM_R_NO_START_LINE: + ERR_clear_error(); + break; + default: /* Something to report */ + tls_print_errors(); + case 0: /* All errors reported */ + ret = 0; + break; + } + return (ret); +} + +#endif diff --git a/postfix/src/tls/tls_fprint.c b/postfix/src/tls/tls_fprint.c index 182b71b6e..2fddbe05c 100644 --- a/postfix/src/tls/tls_fprint.c +++ b/postfix/src/tls/tls_fprint.c @@ -76,8 +76,9 @@ /* .IP len /* The length of the input data. /* .IP props -/* The client start properties for the session, which include the -/* initial serverid from the SMTP client. +/* The client start properties for the session, which contains the +/* initial serverid from the SMTP client and the DANE verification +/* parameters. /* .IP protomask /* The mask of protocol exclusions. /* .IP ciphers @@ -129,6 +130,34 @@ static const char hexcodes[] = "0123456789ABCDEF"; #define digestptr(p) digestpl((p), sizeof(*(p))) #define digeststr(s) digestpl((s), strlen(s)+1) +#define digestdane(dane, memb) do { \ + if ((dane)->memb != 0) \ + chknonzero(tlsa_digest(mdctx, (dane)->memb, #memb)); \ + } while (0) + +#define digesttlsa(tlsa, memb) do { \ + if ((tlsa)->memb) { \ + digeststr(#memb); \ + for (dgst = (tlsa)->memb->argv; *dgst; ++dgst) \ + digeststr(*dgst); \ + } \ + } while (0) + +/* tlsa_digest - digest a pre-sorted by caller TLSA match list */ + +static int tlsa_digest(EVP_MD_CTX *mdctx, TLS_TLSA *tlsa, const char *usage) +{ + char **dgst; + int ok = 1; + + for (digeststr(usage); tlsa; tlsa = tlsa->next) { + digeststr(tlsa->mdalg); + digesttlsa(tlsa, pkeys); + digesttlsa(tlsa, certs); + } + return (ok); +} + /* tls_serverid_digest - suffix props->serverid with parameter digest */ char *tls_serverid_digest(const TLS_CLIENT_START_PROPS *props, long protomask, @@ -166,6 +195,37 @@ char *tls_serverid_digest(const TLS_CLIENT_START_PROPS *props, long protomask, digestptr(&sslversion); digestptr(&protomask); digeststr(ciphers); + + /* + * All we get from the session cache is a single bit telling us whether + * the certificate is trusted or not, but we need to know whether the + * trust is CA-based (in that case we must do name checks) or whether it + * is a direct end-point match. We mustn't confuse the two, so it is best + * to process only TA trust in the verify callback and check the EE trust + * after. This works since re-used sessions always have access to the leaf + * certificate, while only the original session has the leaf and the full + * trust chain. + * + * Only the trust anchor matchlist is hashed into the session key. + * The end entity certs are not used to determine whether a certificate + * is trusted or not, rather these are rechecked against the leaf cert + * outside the verification callback, each time a session is created or + * reused. + * + * Therefore, the security context of the session does not depend on the + * EE matching data, which is checked separately each time. So we exclude + * the EE part of the DANE structure from the serverid digest. + * + * If this changes, also update tls_dane_final() in tls_dane.c. + */ + if (props->dane) { + int mixed = (props->dane->flags & TLS_DANE_FLAG_MIXED); + digestptr(&mixed); + digestdane(props->dane, ta); +#if 0 + digestdane(props->dane, ee); /* See above */ +#endif + } chknonzero(EVP_DigestFinal_ex(mdctx, md_buf, &md_len)); EVP_MD_CTX_destroy(mdctx); if (!ok) diff --git a/postfix/src/tls/tls_misc.c b/postfix/src/tls/tls_misc.c index 8143da296..ef7e0f817 100644 --- a/postfix/src/tls/tls_misc.c +++ b/postfix/src/tls/tls_misc.c @@ -746,7 +746,10 @@ TLS_SESS_STATE *tls_alloc_sess_context(int log_mask, const char *namaddr) TLScontext->cipher_name = 0; TLScontext->log_mask = log_mask; TLScontext->namaddr = lowercase(mystrdup(namaddr)); - TLScontext->mdalg = 0; /* Alias for props->mdalg */ + TLScontext->mdalg = 0; /* Alias for props->mdalg */ + TLScontext->dane = 0; /* Alias for client props->dane */ + TLScontext->trustdepth = -1; + TLScontext->chaindepth = -1; TLScontext->errordepth = -1; TLScontext->errorcode = X509_V_OK; TLScontext->errorcert = 0; diff --git a/postfix/src/tls/tls_verify.c b/postfix/src/tls/tls_verify.c index f3daf2a5c..44b9b62c4 100644 --- a/postfix/src/tls/tls_verify.c +++ b/postfix/src/tls/tls_verify.c @@ -7,6 +7,12 @@ /* #define TLS_INTERNAL /* #include /* +/* int tls_cert_match(TLSContext, usage, cert, depth) +/* TLS_SESS_STATE *TLScontext; +/* int usage; +/* X509 *cert; +/* int depth; +/* /* int tls_verify_certificate_callback(ok, ctx) /* int ok; /* X509_STORE_CTX *ctx; @@ -26,6 +32,12 @@ /* const GENERAL_NAME *gn; /* TLS_SESS_STATE *TLScontext; /* DESCRIPTION +/* tls_cert_match() matches the full and/or public key digest of +/* "cert" against each candidate digest in TLScontext->dane. If usage +/* is TLS_DANE_EE, the match is against end-entity digests, otherwise +/* it is against trust-anchor digests. Returns true if a match is found, +/* false otherwise. +/* /* tls_verify_certificate_callback() is called several times (directly /* or indirectly) from crypto/x509/x509_vfy.c. It collects errors /* and trust information at each element of the trust chain. @@ -67,6 +79,12 @@ /* .IP gn /* An OpenSSL GENERAL_NAME structure holding a DNS subjectAltName /* to be decoded and checked for validity. +/* .IP usage +/* Trust anchor (TLS_DANE_TA) or end-entity (TLS_DANE_EE) digests? +/* .IP cert +/* Certificate from peer trust chain (CA or leaf server). +/* .IP depth +/* The certificate depth for logging. /* .IP peercert /* Server or client X.509 certificate. /* .IP TLScontext @@ -126,7 +144,8 @@ static void update_error_state(TLS_SESS_STATE *TLScontext, int depth, X509 *errorcert, int errorcode) { /* No news is good news */ - if (TLScontext->errordepth >= 0 && TLScontext->errordepth <= depth) + if ((TLScontext->trustdepth >= 0 && TLScontext->trustdepth < depth) || + (TLScontext->errordepth >= 0 && TLScontext->errordepth <= depth)) return; /* @@ -141,6 +160,169 @@ static void update_error_state(TLS_SESS_STATE *TLScontext, int depth, CRYPTO_add(&errorcert->references, 1, CRYPTO_LOCK_X509); TLScontext->errorcert = errorcert; TLScontext->errorcode = errorcode; + + /* + * Maintain an invariant, at most one of errordepth and trustdepth + * is non-negative at any given time. + */ + TLScontext->errordepth = depth; + TLScontext->trustdepth = -1; +} + +/* update_trust_state - safely stash away trust state */ + +static void update_trust_state(TLS_SESS_STATE *TLScontext, int depth) +{ + /* No news is bad news */ + if ((TLScontext->trustdepth >= 0 && TLScontext->trustdepth <= depth) + || (TLScontext->errordepth >= 0 && TLScontext->errordepth <= depth)) + return; + + /* + * Maintain an invariant, at most one of errordepth and trustdepth + * is non-negative at any given time. + */ + TLScontext->trustdepth = depth; + TLScontext->errordepth = -1; +} + +/* tls_cert_match - match cert against given list of TA or EE digests */ + +int tls_cert_match(TLS_SESS_STATE *TLScontext, int usage, X509 *cert, int depth) +{ + const TLS_DANE *dane = TLScontext->dane; + TLS_TLSA *tlsa = (usage == TLS_DANE_EE) ? dane->ee : dane->ta; + const char *namaddr = TLScontext->namaddr; + const char *ustr = (usage == TLS_DANE_EE) ? "end entity" : "trust anchor"; + int mixed = (dane->flags & TLS_DANE_FLAG_MIXED); + int matched; + + for (matched = 0; tlsa && !matched; tlsa = tlsa->next) { + char **dgst; + ARGV *certs; + + if (tlsa->pkeys) { + char *pkey_dgst = tls_pkey_fprint(cert, tlsa->mdalg); + for (dgst = tlsa->pkeys->argv; !matched && *dgst; ++dgst) + if (strcasecmp(pkey_dgst, *dgst) == 0) + matched = 1; + if (TLScontext->log_mask & (TLS_LOG_VERBOSE|TLS_LOG_CERTMATCH)) + msg_info("%s: depth=%d matched=%d %s public-key %s digest=%s", + namaddr, depth, matched, ustr, tlsa->mdalg, pkey_dgst); + myfree(pkey_dgst); + } + + certs = mixed ? tlsa->pkeys : tlsa->certs; + if (certs != 0 && !matched) { + char *cert_dgst = tls_fingerprint(cert, tlsa->mdalg); + for (dgst = certs->argv; !matched && *dgst; ++dgst) + if (strcasecmp(cert_dgst, *dgst) == 0) + matched = 1; + if (TLScontext->log_mask & (TLS_LOG_VERBOSE|TLS_LOG_CERTMATCH)) + msg_info("%s: depth=%d matched=%d %s certificate %s digest %s", + namaddr, depth, matched, ustr, tlsa->mdalg, cert_dgst); + myfree(cert_dgst); + } + } + + return (matched); +} + +/* ta_match - match cert against out-of-band TA keys or digests */ + +static int ta_match(TLS_SESS_STATE *TLScontext, X509_STORE_CTX *ctx, + X509 *cert, int depth, int expired) +{ + const TLS_DANE *dane = TLScontext->dane; + int matched = tls_cert_match(TLScontext, TLS_DANE_TA, cert, depth); + + /* + * If we are the TA, the first trusted certificate is one level below! + * As a degenerate case a self-signed TA at depth 0 is also treated as + * a TA validated trust chain, (even if the certificate is expired). + * + * Note: OpenSSL will flag an error when the chain contains just one + * certificate that is not self-issued. + */ + if (matched) { + if (--depth < 0) + depth = 0; + update_trust_state(TLScontext, depth); + return (1); + } + + /* + * If expired, no need to check for a trust-anchor signature. The TA + * itself is matched by its digest, so we're at best looking at some + * other expired certificate issued by the TA, which we don't accept. + */ + if (expired) + return (0); + + /* + * Compute the index of the topmost chain certificate; it may need to be + * verified via one of our out-of-band trust-anchors. Since we're here, + * the chain contains at least one certificate. + * + * Optimization: if the top is self-issued, we don't need to try to check + * whether it is signed by any ancestor TAs. If it is trusted, it will be + * matched by its fingerprint. + */ + if (TLScontext->trustdepth < 0 && TLScontext->chaindepth < 0) { + STACK_OF(X509) *chain = X509_STORE_CTX_get_chain(ctx); + int i = sk_X509_num(chain) - 1; + X509 *top = sk_X509_value(chain, i); + + if (X509_check_issued(top, top) == X509_V_OK) + TLScontext->chaindepth = i + 1; + else + TLScontext->chaindepth = i; + } + + /* + * Last resort, check whether signed by out-of-band TA public key. + * + * Only the top certificate of the server chain needs this logic, since + * any certs below are signed by their parent, which we checked against + * the TA list more cheaply. Do this at most once (by incrementing the + * depth when we're done). + */ + if (depth == TLScontext->chaindepth) { + TLS_DANE_L(EVP_PKEY) *k; + TLS_DANE_L(X509) *x; + + /* + * First check whether issued and signed by a TA cert, this is cheaper + * than the bare-public key checks below, since we can determine + * whether the candidate TA certificate issued the certificate + * to be checked first (name comparisons), before we bother with + * signature checks (public key operations). + */ + for (x = TLS_DANE_HEAD(dane, X509); !matched && x; x = x->next) { + if (X509_check_issued(x->item, cert) == X509_V_OK) { + EVP_PKEY *pk = X509_get_pubkey(x->item); + matched = pk && X509_verify(cert, pk) > 0; + EVP_PKEY_free(pk); + } + } + + /* + * With bare TA public keys, we can't check whether the trust chain + * is issued by the key, but we can determine whether it is signed + * by the key, so we go with that. Ideally, the corresponding + * certificate was presented in the chain, and we matched it by + * its public key digest one level up. This code is here to handle + * adverse conditions imposed by sloppy administrators of receiving + * systems with poorly constructed chains. + */ + for (k = TLS_DANE_HEAD(dane, EVP_PKEY); !matched && k; k = k->next) + matched = X509_verify(cert, k->item) > 0; + + if (matched) + update_trust_state(TLScontext, depth); + ++TLScontext->chaindepth; + } + return (matched); } /* tls_verify_certificate_callback - verify peer certificate info */ @@ -193,18 +375,92 @@ int tls_verify_certificate_callback(int ok, X509_STORE_CTX *ctx) if (max_depth >= 0 && depth > max_depth) { update_error_state(TLScontext, depth, cert, X509_V_ERR_CERT_CHAIN_TOO_LONG); - ok = 0; + return (1); + } + + /* + * Per RFC 5280 and its upstream ITU documents, a trust anchor is just + * a public key, no more no less, and thus certificates bearing the + * trust-anchor public key are just public keys in X.509v3 garb. Any + * meaning attached to their expiration, ... is simply local policy. + * + * We don't punish server administrators for including an expired optional + * TA certificate in their chain. Had they left it out, and provided us + * instead with only the TA public-key via a "2 1 0" TLSA record, there'd + * be no TA certificate from which to learn the expiration dates. + * + * Therefore, in the interests of consistent behavior, we only enforce + * expiration dates BELOW the TA signature. When we find an expired + * certificate, we only check whether it is a TA, and not whether it is + * signed by a TA. + * + * Other than allowing TA certificate expiration, the only errors we + * allow are failure to chain to a trusted root. Our TA set includes + * out-of-band data not available to the X509_STORE_CTX. + * + * More than one of the allowed errors may be reported at a given depth, + * trap all instances, but run the matching code at most once. If the + * current cert is ok, we have a trusted ancestor, and we're not verbose, + * don't bother with matching. + */ + if (cert != 0 + && (ok == 0 + || TLScontext->trustdepth < 0 + || (TLScontext->log_mask & (TLS_LOG_VERBOSE | TLS_LOG_CERTMATCH))) + && TLS_DANE_HASTA(TLScontext->dane) + && (TLScontext->trustdepth == -1 || depth <= TLScontext->trustdepth) + && (TLScontext->errordepth == -1 || depth < TLScontext->errordepth)) { + int expired = 0; /* or not yet valid */ + + switch (ok ? X509_V_OK : err) { + case X509_V_ERR_CERT_NOT_YET_VALID: + case X509_V_ERR_CERT_HAS_EXPIRED: + expired = 1; + /* FALLTHROUGH */ + case X509_V_OK: + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: + case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: + case X509_V_ERR_CERT_UNTRUSTED: + if ((!expired && depth == TLScontext->trustdepth) + || ta_match(TLScontext, ctx, cert, depth, expired)) + ok = 1; + break; + } } if (ok == 0) update_error_state(TLScontext, depth, cert, err); /* - * The final depth zero call sets the verification status. + * Perhaps the chain is verified, or perhaps we'll get called again, + * either way the best we know is that if trust depth is below error + * depth we win and otherwise we lose. Set the error state accordingly. + * + * If we are given explicit TA match list, we must match one of them + * at a non-negative depth below any errors, otherwise we just need + * no errors. */ if (depth == 0) { - ok = TLScontext->errordepth < 0 ? 1 : 0; + ok = 0; + if (TLScontext->trustdepth < 0 && TLS_DANE_HASTA(TLScontext->dane)) { + /* Required Policy or DANE certs not present */ + if (TLScontext->errordepth < 0) { + /* + * For lack of a better choice log the trust problem against + * the leaf cert when PKI says yes, but local policy or DANE + * says no. Logging a root cert as untrusted would far more + * likely confuse users! + */ + update_error_state(TLScontext, depth, cert, + X509_V_ERR_CERT_UNTRUSTED); + } + } else if (TLScontext->errordepth < 0) { + /* No PKI trust errors, or only above a good policy or DANE CA. */ + ok = 1; + } X509_STORE_CTX_set_error(ctx, ok ? X509_V_OK : TLScontext->errorcode); } + if (TLScontext->log_mask & TLS_LOG_VERBOSE) { if (cert) X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf)); diff --git a/postfix/src/util/argv.c b/postfix/src/util/argv.c index 7a0ce1f15..35bb56db5 100644 --- a/postfix/src/util/argv.c +++ b/postfix/src/util/argv.c @@ -9,6 +9,9 @@ /* ARGV *argv_alloc(len) /* ssize_t len; /* +/* ARGV *argv_sort(argvp) +/* ARGV *argvp; +/* /* ARGV *argv_free(argvp) /* ARGV *argvp; /* @@ -56,6 +59,9 @@ /* length. The result is ready for use by argv_add(). The array /* is null terminated. /* +/* argv_sort() sorts the elements of argvp in place returning +/* the original array. +/* /* argv_add() copies zero or more strings and adds them to the /* specified string array. The array is null terminated. /* Terminate the argument list with a null pointer. The manifest @@ -148,6 +154,21 @@ ARGV *argv_alloc(ssize_t len) return (argvp); } +static int argv_cmp(const void *e1, const void *e2) +{ + const char *s1 = *(const char **)e1; + const char *s2 = *(const char **)e2; + return strcmp(s1, s2); +} + +/* argv_sort - sort array in place */ + +ARGV *argv_sort(ARGV *argvp) +{ + qsort(argvp->argv, argvp->argc, sizeof(argvp->argv[0]), argv_cmp); + return (argvp); +} + /* argv_extend - extend array */ static void argv_extend(ARGV *argvp) diff --git a/postfix/src/util/argv.h b/postfix/src/util/argv.h index 7c56f581a..fe0322422 100644 --- a/postfix/src/util/argv.h +++ b/postfix/src/util/argv.h @@ -21,6 +21,7 @@ typedef struct ARGV { } ARGV; extern ARGV *argv_alloc(ssize_t); +extern ARGV *argv_sort(ARGV *); extern void argv_add(ARGV *,...); extern void argv_addn(ARGV *,...); extern void argv_terminate(ARGV *); -- 2.47.3