diff --git a/postfix/HISTORY b/postfix/HISTORY index cb6cd436a..65846c70b 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -19111,3 +19111,36 @@ Apologies for any names omitted. cleanup/cleanup_map11.c, cleanup/cleanup_map1n.c, cleanup/cleanup_masquerade.c, cleanup/cleanup_message.c, cleanup/cleanup_milter.c. + +20131116 + + Feature: MySQL client support for option_file, option_group, + tls_cert_file, tls_key_file, tls_CAfile, tls_CApath, + tls_verify_cert. See mysql_table(5). Code by Gareth Palmer. + Files: proto/mysql_table, global/dict_mysql.c. + + Cleanup: DANE support. Keep the attributes of TA certificates + obtained via "IN TLSA 2 0 X" RRs, while continuing to only + use the key from "IN TLSA 2 1 X" RRs. This means in the + "2 0 X" case that we re-sign the TA certificate in place, + rather than synthesize a vanilla cert around just the key. + Viktor Dukhovni. File: tls/tls_dane.c. + + Bugfix: posttls-finger parsing of destination and optional + match values. Viktor Dukhovni. File: + posttls-finger/posttls-finger.c. + + Cleanup: When wrap_signed is false (OpenSSL 1.0.2 some day), + we don't have to sign trust anchors, and don't generate a + key to do so. Thus don't attempt to re-sign trust-anchor + certificates (IN TLSA 2 0 X) in this case. Viktor Dukhovni. + File: tls/tls_dane.c. + + Feature: configurable DANE digest algorithm priority. Use + only the most-preferred, shared, digest algorithm for any + give (usage, selector) combination. Viktor Dukhovni. + mantools/postlink, proto/postconf.proto, global/mail_params.h, + tls/tls_dane.c, tls/tls_misc.c. + + Bugfix: FreeBSD nroff workaround messed up. File: + mantools/postlink. diff --git a/postfix/WISHLIST b/postfix/WISHLIST index aa77857b8..92f2cb53f 100644 --- a/postfix/WISHLIST +++ b/postfix/WISHLIST @@ -4,6 +4,9 @@ Wish list: Make been_here flag BH_FLAG_FOLD configurable for masochists. + Allow LMDB-based caches to be shared. This requires a new + flag that says a database is concurrency-safe. + Preserve case in smtpd_resolve_addr() and add a structure member for the case-folded address. diff --git a/postfix/html/mysql_table.5.html b/postfix/html/mysql_table.5.html index 52d4c46da..c40b1723c 100644 --- a/postfix/html/mysql_table.5.html +++ b/postfix/html/mysql_table.5.html @@ -256,15 +256,63 @@ MYSQL_TABLE(5) MYSQL_TABLE(5) limit is exceeded. Setting the limit to 1 ensures that lookups do not return multiple values. + option_file + Read options from the given file instead of the + default my.cnf location. + + This parameter is available with Postfix 2.11 and + later. + + option_group + Read options from the given group. + + This parameter is available with Postfix 2.11 and + later. + + tls_cert_file + File containing client's X509 certificate. + + This parameter is available with Postfix 2.11 and + later. + + tls_key_file + File containing the private key corresponding to + tls_cert_file. + + This parameter is available with Postfix 2.11 and + later. + + tls_CAfile + File containing certificates for all of the X509 + Certificate Authorities the client will recognize. + Takes precedence over tls_CApath. + + This parameter is available with Postfix 2.11 and + later. + + tls_CApath + Directory containing X509 Certificate Authority + certificates in separate individual files. + + This parameter is available with Postfix 2.11 and + later. + + tls_verify_cert (default: no) + Verify that the server's name matches the common + name in the certficate. + + This parameter is available with Postfix 2.11 and + later. + OBSOLETE QUERY INTERFACE - This section describes an interface that is deprecated as - of Postfix 2.2. It is replaced by the more general query - interface described above. If the query parameter is - defined, the legacy parameters described here ignored. - Please migrate to the new interface as the legacy inter- + This section describes an interface that is deprecated as + of Postfix 2.2. It is replaced by the more general query + interface described above. If the query parameter is + defined, the legacy parameters described here ignored. + Please migrate to the new interface as the legacy inter- face may be removed in a future release. - The following parameters can be used to fill in a SELECT + The following parameters can be used to fill in a SELECT template statement of the form: SELECT [select_field] @@ -272,7 +320,7 @@ MYSQL_TABLE(5) MYSQL_TABLE(5) WHERE [where_field] = '%s' [additional_conditions] - The specifier %s is replaced by the search string, and is + The specifier %s is replaced by the search string, and is escaped so if it contains single quotes or other odd char- acters, it will not cause a parse error, or worse, a secu- rity problem. @@ -304,7 +352,7 @@ MYSQL_TABLE(5) MYSQL_TABLE(5) MYSQL_README, Postfix MYSQL client guide LICENSE - The Secure Mailer license must be distributed with this + The Secure Mailer license must be distributed with this software. HISTORY diff --git a/postfix/html/pcre_table.5.html b/postfix/html/pcre_table.5.html index eac6faa92..7a51ce1fa 100644 --- a/postfix/html/pcre_table.5.html +++ b/postfix/html/pcre_table.5.html @@ -10,9 +10,9 @@ PCRE_TABLE(5) PCRE_TABLE(5) pcre_table - format of Postfix PCRE tables SYNOPSIS - postmap -q "string" pcre:/etc/postfix/filename + postmap -q "string" pcre:/etc/postfix/filename - postmap -q - pcre:/etc/postfix/filename <inputfile + postmap -q - pcre:/etc/postfix/filename <inputfile DESCRIPTION The Postfix mail system uses optional tables for address @@ -39,15 +39,15 @@ PCRE_TABLE(5) PCRE_TABLE(5) TABLE FORMAT The general form of a PCRE table is: - /pattern/flags result + /pattern/flags result When pattern matches the input string, use the cor- responding result value. - !/pattern/flags result + !/pattern/flags result When pattern does not match the input string, use the corresponding result value. - if /pattern/flags + if /pattern/flags endif Match the input string against the patterns between if and endif, if and only if that same input string @@ -58,7 +58,7 @@ PCRE_TABLE(5) PCRE_TABLE(5) This feature is available in Postfix 2.1 and later. - if !/pattern/flags + if !/pattern/flags endif Match the input string against the patterns between if and endif, if and only if that same input string @@ -115,7 +115,7 @@ PCRE_TABLE(5) PCRE_TABLE(5) a whitespace character as part of the pattern, escape it with backslash. - Note: do not use #comment after patterns. + Note: do not use #comment after patterns. A (default: off) Toggles the PCRE_ANCHORED flag. When this flag is diff --git a/postfix/html/postconf.5.html b/postfix/html/postconf.5.html index 28a3b3e6d..c1523c001 100644 --- a/postfix/html/postconf.5.html +++ b/postfix/html/postconf.5.html @@ -11311,7 +11311,7 @@ to Postfix 2.9.6 or later.

Lookup the associated DANE TLSA RRset even when a hostname is not an alias and its address records lie in an unsigned zone. This is unlikely to ever yield DNSSEC validated results, since child -zones of unsigned zones are also unsigned in the absense of DLV or +zones of unsigned zones are also unsigned in the absence of DLV or locally configured non-root trust-anchors. We anticipate that such mechanisms will not be used for just the "_tcp" subdomain of a host. Suppressing the TLSA RRset lookup reduces latency and avoids potential @@ -16211,6 +16211,55 @@ bytes (equivalent to 256 bits) is sufficient to generate a 128bit

This feature is available in Postfix 2.2 and later.

+ + +
tls_dane_digests +(default: sha512 sha256)
+ +

RFC 6698 TLSA resource-record "matching type" digest algorithms +in descending preference order. All the specified algorithms must +be supported by the underlying OpenSSL library, otherwise the Postfix +SMTP client will not support DANE TLSA security.

+ +

When for a particular combination of "certificate usage" and +"selector" the TLSA RRset contains a well-formed record with a +matching type of "0", i.e. a full value of the associated certificate +or public key, the Postfix SMTP client will ignore all other matching +types for the same certificate usage and selector. In this case +the first algorithm listed in tls_dane_digests will be used to +compute a digest of the full value, which will then be used to match +certificates or public keys in the server's certificate chain.

+ +

Otherwise, when for a particular combination of "certificate +usage" and "selector" the TLSA RRset contains a records with more +than one non-zero matching type, i.e. multiple digest algorithms, +only records with the highest preference digest are used after +discarding any records with an incorrect digest length as unusable.

+ +

This strategy ensures that the strongest digest supported by +both the Postfix SMTP client and the remote server is used, and +weaker digests are ignored. This supports non-disruptive deprecation +of outdated digest algorithms.

+ +

The strategy requires that when a TLSA RRset provides association +data for multiple certificates or public keys, all RRs with the same +"certificate usage" and "selector" be published with the same set +of digests. In particular, during key rotation, when a certificate +or public key is being replaced with another (and both are published +during the transition) both the old and the new certificate MUST be +specified with the same set of digests. One can change the list of +digest algorithms later, once old keys are retired. At any given +time change either the list of digests without changing the list of +certificates or public keys or the list of certificates or public +keys without changing the list of digests.

+ +

It is expected that this algorithm agility mechanism will be +published in a standards track RFC for SMTP with DANE, and perhaps +in an eventual update to RFC 6698.

+ +

This feature is available in Postfix 2.11.

+ +
tls_dane_trust_anchor_digest_enable diff --git a/postfix/html/regexp_table.5.html b/postfix/html/regexp_table.5.html index 8a2bb3b20..8a5b9f9d9 100644 --- a/postfix/html/regexp_table.5.html +++ b/postfix/html/regexp_table.5.html @@ -10,9 +10,9 @@ REGEXP_TABLE(5) REGEXP_TABLE(5) regexp_table - format of Postfix regular expression tables SYNOPSIS - postmap -q "string" regexp:/etc/postfix/filename + postmap -q "string" regexp:/etc/postfix/filename - postmap -q - regexp:/etc/postfix/filename <inputfile + postmap -q - regexp:/etc/postfix/filename <inputfile DESCRIPTION The Postfix mail system uses optional tables for address @@ -39,15 +39,15 @@ REGEXP_TABLE(5) REGEXP_TABLE(5) TABLE FORMAT The general form of a Postfix regular expression table is: - /pattern/flags result + /pattern/flags result When pattern matches the input string, use the cor- responding result value. - !/pattern/flags result + !/pattern/flags result When pattern does not match the input string, use the corresponding result value. - if /pattern/flags + if /pattern/flags endif Match the input string against the patterns between if and endif, if and only if that same input string @@ -58,7 +58,7 @@ REGEXP_TABLE(5) REGEXP_TABLE(5) This feature is available in Postfix 2.1 and later. - if !/pattern/flags + if !/pattern/flags endif Match the input string against the patterns between if and endif, if and only if that same input string diff --git a/postfix/html/smtp-sink.1.html b/postfix/html/smtp-sink.1.html index 96f49a0e3..18768e945 100644 --- a/postfix/html/smtp-sink.1.html +++ b/postfix/html/smtp-sink.1.html @@ -12,7 +12,7 @@ SMTP-SINK(1) SMTP-SINK(1) SYNOPSIS smtp-sink [options] [inet:][host]:port backlog - smtp-sink [options] unix:pathname backlog + smtp-sink [options] unix:pathname backlog DESCRIPTION smtp-sink listens on the named host (or address) and port. @@ -47,19 +47,19 @@ SMTP-SINK(1) SMTP-SINK(1) -a Do not announce SASL authentication support. - -A delay + -A delay Wait delay seconds after responding to DATA, then abort prematurely with a 550 reply status. Do not read further input from the client; this is an attempt to block the client before it sends ".". Specify a zero delay value to abort immediately. - -b soft-bounce-reply + -b soft-bounce-reply Use soft-bounce-reply for soft reject responses. The default reply is "450 4.3.0 Error: command failed". - -B hard-bounce-reply + -B hard-bounce-reply Use hard-bounce-reply for hard reject responses. The default reply is "500 5.3.0 Error: command failed". @@ -70,7 +70,7 @@ SMTP-SINK(1) SMTP-SINK(1) -C Disable XCLIENT support. - -d dump-template + -d dump-template Dump each mail transaction to a single-message file whose name is created by expanding the dump-tem- plate via strftime(3) and appending a pseudo-random @@ -83,7 +83,7 @@ SMTP-SINK(1) SMTP-SINK(1) Note: this option keeps one capture file open for every mail transaction in progress. - -D dump-template + -D dump-template Append mail transactions to a multi-message dump file whose name is created by expanding the dump- template via strftime(3). If the template contains @@ -98,7 +98,7 @@ SMTP-SINK(1) SMTP-SINK(1) -E Do not announce ENHANCEDSTATUSCODES support. - -f command,command,... + -f command,command,... Reject the specified commands with a hard (5xx) error code. This option implies -p. @@ -110,24 +110,24 @@ SMTP-SINK(1) SMTP-SINK(1) -F Disable XFORWARD support. - -h hostname + -h hostname Use hostname in the SMTP greeting, in the HELO response, and in the EHLO response. The default hostname is "smtp-sink". -L Enable LMTP instead of SMTP. - -m count (default: 256) + -m count (default: 256) An upper bound on the maximal number of simultane- ous connections that smtp-sink will handle. This prevents the process from running out of file descriptors. Excess connections will stay queued in the TCP/IP stack. - -M count + -M count Terminate after receiving count messages. - -n count + -n count Terminate after count sessions. -p Do not announce support for ESMTP command pipelin- @@ -136,7 +136,7 @@ SMTP-SINK(1) SMTP-SINK(1) -P Change the server greeting so that it appears to come through a CISCO PIX system. Implies -e. - -q command,command,... + -q command,command,... Disconnect (without replying) after receiving one of the specified commands. @@ -146,7 +146,7 @@ SMTP-SINK(1) SMTP-SINK(1) and use quotes to protect white space from the shell. Command names are case-insensitive. - -Q command,command,... + -Q command,command,... Send a 421 reply and disconnect after receiving one of the specified commands. @@ -156,7 +156,7 @@ SMTP-SINK(1) SMTP-SINK(1) and use quotes to protect white space from the shell. Command names are case-insensitive. - -r command,command,... + -r command,command,... Reject the specified commands with a soft (4xx) error code. This option implies -p. @@ -166,12 +166,12 @@ SMTP-SINK(1) SMTP-SINK(1) and use quotes to protect white space from the shell. Command names are case-insensitive. - -R root-directory + -R root-directory Change the process root directory to the specified location. This option requires super-user privi- leges. See also the -u option. - -s command,command,... + -s command,command,... Log the named commands to syslogd. Examples of commands are CONNECT, HELO, EHLO, LHLO, @@ -190,16 +190,16 @@ SMTP-SINK(1) SMTP-SINK(1) tab), \ddd (up to three octal digits) and \\ (the backslash character). - -t timeout (default: 100) + -t timeout (default: 100) Limit the time for receiving a command or sending a response. The time limit is specified in seconds. - -T windowsize + -T windowsize Override the default TCP window size. To work around broken TCP window scaling implementations, specify a value > 0 and < 65536. - -u username + -u username Switch to the specified user privileges after open- ing the network socket and optionally changing the process root directory. This option is required @@ -208,11 +208,11 @@ SMTP-SINK(1) SMTP-SINK(1) -v Show the SMTP conversations. - -w delay + -w delay Wait delay seconds before responding to a DATA com- mand. - -W command:delay[:odds] + -W command:delay[:odds] Wait delay seconds before responding to command. If odds is also specified (a number between 1-99 inclusive), wait for a random multiple of delay. @@ -226,7 +226,7 @@ SMTP-SINK(1) SMTP-SINK(1) interface) TCP port port. Both host and port may be specified in numeric or symbolic form. - unix:pathname + unix:pathname Listen on the UNIX-domain socket at pathname. backlog @@ -251,45 +251,45 @@ SMTP-SINK(1) SMTP-SINK(1) The format of the smtp-sink generated headers is as fol- lows: - X-Client-Addr: text + X-Client-Addr: text The client IP address without enclosing []. An IPv6 address is prefixed with "ipv6:". This record is always present. - X-Client-Proto: text + X-Client-Proto: text The client protocol: SMTP, ESMTP or LMTP. This record is always present. - X-Helo-Args: text + X-Helo-Args: text The arguments of the last HELO or EHLO command before this mail delivery transaction. This record is present only if the client sent a recognizable HELO or EHLO command before the DATA command. - X-Mail-Args: text + X-Mail-Args: text The arguments of the MAIL command that started this mail delivery transaction. This record is present exactly once. - X-Rcpt-Args: text + X-Rcpt-Args: text The arguments of an RCPT command within this mail delivery transaction. There is one record for each RCPT command, and they are in the order as sent by the client. - Received: text + Received: text A message header for compatibility with mail pro- cessing software. This three-line header marks the end of the headers provided by smtp-sink, and is formatted as follows: - from helo ([addr]) + from helo ([addr]) The HELO or EHLO command argument and client IP address. If the client did not send HELO or EHLO, the client IP address is used instead. - by host (smtp-sink) with proto id random; + by host (smtp-sink) with proto id random; The hostname specified with the -h option, the client protocol (see X-Client-Proto above), and the pseudo-random portion of the diff --git a/postfix/man/man5/mysql_table.5 b/postfix/man/man5/mysql_table.5 index d79aa2330..dd3ec9b4c 100644 --- a/postfix/man/man5/mysql_table.5 +++ b/postfix/man/man5/mysql_table.5 @@ -256,6 +256,39 @@ A setting of zero disables the limit. Lookups fail with a temporary error if the limit is exceeded. Setting the limit to 1 ensures that lookups do not return multiple values. +.IP "\fBoption_file\fR" +Read options from the given file instead of the default my.cnf +location. +.sp +This parameter is available with Postfix 2.11 and later. +.IP "\fBoption_group\fR" +Read options from the given group. +.sp +This parameter is available with Postfix 2.11 and later. +.IP "\fBtls_cert_file\fR" +File containing client's X509 certificate. +.sp +This parameter is available with Postfix 2.11 and later. +.IP "\fBtls_key_file\fR" +File containing the private key corresponding to \fBtls_cert_file\fR. +.sp +This parameter is available with Postfix 2.11 and later. +.IP "\fBtls_CAfile\fR" +File containing certificates for all of the X509 Certificate +Authorities the client will recognize. Takes precedence over +\fBtls_CApath\fR. +.sp +This parameter is available with Postfix 2.11 and later. +.IP "\fBtls_CApath\fR" +Directory containing X509 Certificate Authority certificates +in separate individual files. +.sp +This parameter is available with Postfix 2.11 and later. +.IP "\fBtls_verify_cert (default: no)\fR" +Verify that the server's name matches the common name in the +certficate. +.sp +This parameter is available with Postfix 2.11 and later. .SH "OBSOLETE QUERY INTERFACE" .na .nf diff --git a/postfix/man/man5/postconf.5 b/postfix/man/man5/postconf.5 index 58b9886bc..4f9068d19 100644 --- a/postfix/man/man5/postconf.5 +++ b/postfix/man/man5/postconf.5 @@ -4173,7 +4173,7 @@ Example: .ft C /etc/postfix/main.cf: postscreen_access_list = permit_mynetworks, - cidr:/etc/postfix/postscreen_access.cidr + cidr:/etc/postfix/postscreen_access.cidr postscreen_blacklist_action = enforce .fi .ad @@ -4393,7 +4393,7 @@ Example: .na .ft C /etc/postfix/dnsbl_reply: - secret.zen.spamhaus.org zen.spamhaus.org + secret.zen.spamhaus.org zen.spamhaus.org .fi .ad .ft R @@ -6962,7 +6962,7 @@ As in the example above, we show two matching fingerprints: .na .ft C /etc/postfix/tls_policy: - example.com fingerprint + example.com fingerprint match=3D:95:34:51:24:66:33:B9:D2:40:99:C0:C1:17:0B:D1 match=EC:3B:2D:B0:5B:B1:FB:6D:20:A3:9D:72:F6:8D:12:35 .fi @@ -7079,7 +7079,7 @@ This feature is available in Postfix 2.5 and later. Lookup the associated DANE TLSA RRset even when a hostname is not an alias and its address records lie in an unsigned zone. This is unlikely to ever yield DNSSEC validated results, since child -zones of unsigned zones are also unsigned in the absense of DLV or +zones of unsigned zones are also unsigned in the absence of DLV or locally configured non-root trust-anchors. We anticipate that such mechanisms will not be used for just the "_tcp" subdomain of a host. Suppressing the TLSA RRset lookup reduces latency and avoids potential @@ -7506,8 +7506,8 @@ Example: [mail.example.org]:587 secure match=nexthop # Postfix 2.5 and later [thumb.example.org] fingerprint - match=EC:3B:2D:B0:5B:B1:FB:6D:20:A3:9D:72:F6:8D:12:35 - match=3D:95:34:51:24:66:33:B9:D2:40:99:C0:C1:17:0B:D1 + match=EC:3B:2D:B0:5B:B1:FB:6D:20:A3:9D:72:F6:8D:12:35 + match=3D:95:34:51:24:66:33:B9:D2:40:99:C0:C1:17:0B:D1 .fi .ad .ft R @@ -11006,6 +11006,49 @@ bytes (equivalent to 256 bits) is sufficient to generate a 128bit (or 168bit) session key. .PP This feature is available in Postfix 2.2 and later. +.SH tls_dane_digests (default: sha512 sha256) +RFC 6698 TLSA resource-record "matching type" digest algorithms +in descending preference order. All the specified algorithms must +be supported by the underlying OpenSSL library, otherwise the Postfix +SMTP client will not support DANE TLSA security. +.PP +When for a particular combination of "certificate usage" and +"selector" the TLSA RRset contains a well-formed record with a +matching type of "0", i.e. a full value of the associated certificate +or public key, the Postfix SMTP client will ignore all other matching +types for the same certificate usage and selector. In this case +the first algorithm listed in tls_dane_digests will be used to +compute a digest of the full value, which will then be used to match +certificates or public keys in the server's certificate chain. +.PP +Otherwise, when for a particular combination of "certificate +usage" and "selector" the TLSA RRset contains a records with more +than one non-zero matching type, i.e. multiple digest algorithms, +only records with the highest preference digest are used after +discarding any records with an incorrect digest length as unusable. +.PP +This strategy ensures that the strongest digest supported by +both the Postfix SMTP client and the remote server is used, and +weaker digests are ignored. This supports non-disruptive deprecation +of outdated digest algorithms. +.PP +The strategy requires that when a TLSA RRset provides association +data for multiple certificates or public keys, all RRs with the same +"certificate usage" and "selector" be published with the same set +of digests. In particular, during key rotation, when a certificate +or public key is being replaced with another (and both are published +during the transition) both the old and the new certificate MUST be +specified with the same set of digests. One can change the list of +digest algorithms later, once old keys are retired. At any given +time change either the list of digests without changing the list of +certificates or public keys or the list of certificates or public +keys without changing the list of digests. +.PP +It is expected that this algorithm agility mechanism will be +published in a standards track RFC for SMTP with DANE, and perhaps +in an eventual update to RFC 6698. +.PP +This feature is available in Postfix 2.11. .SH tls_dane_trust_anchor_digest_enable (default: trust-anchor-assertion) RFC 6698 trust-anchor digest support in the Postfix TLS library. Specify zero or more of the following options, separated by comma or diff --git a/postfix/mantools/man2html b/postfix/mantools/man2html index 2b8e3bc49..f9896f07b 100755 --- a/postfix/mantools/man2html +++ b/postfix/mantools/man2html @@ -47,7 +47,7 @@ sed ' s;'$ESC'\[4m;;g s;'$ESC'\[24m;;g # Undo gratuitous whitespace changes. - s;\( *\)\(\);\2\1;g + #s;\( *\)\(\);\2\1;g # End nroff ANSI escape sequence workarounds. s;\( *\);\1;g s;\( *\);\1;g diff --git a/postfix/mantools/postlink b/postfix/mantools/postlink index 233a9828f..e827d314b 100755 --- a/postfix/mantools/postlink +++ b/postfix/mantools/postlink @@ -716,6 +716,7 @@ while (<>) { s;\btls_append_default_CA\b;$&;g; s;\btls_legacy_public_key_fingerprints\b;$&;g; s;\btls_dane_trust_anchor_digest_enable\b;$&;g; + s;\btls_dane_digests\b;$&;g; s;\btls_wildcard_matches_multiple_labels\b;$&;g; s;\bfrozen_delivered_to\b;$&;g; diff --git a/postfix/proto/mysql_table b/postfix/proto/mysql_table index 4e84316f6..d422e7d46 100644 --- a/postfix/proto/mysql_table +++ b/postfix/proto/mysql_table @@ -244,6 +244,39 @@ # temporary error if the limit is exceeded. Setting the # limit to 1 ensures that lookups do not return multiple # values. +# .IP "\fBoption_file\fR" +# Read options from the given file instead of the default my.cnf +# location. +# .sp +# This parameter is available with Postfix 2.11 and later. +# .IP "\fBoption_group\fR" +# Read options from the given group. +# .sp +# This parameter is available with Postfix 2.11 and later. +# .IP "\fBtls_cert_file\fR" +# File containing client's X509 certificate. +# .sp +# This parameter is available with Postfix 2.11 and later. +# .IP "\fBtls_key_file\fR" +# File containing the private key corresponding to \fBtls_cert_file\fR. +# .sp +# This parameter is available with Postfix 2.11 and later. +# .IP "\fBtls_CAfile\fR" +# File containing certificates for all of the X509 Certificate +# Authorities the client will recognize. Takes precedence over +# \fBtls_CApath\fR. +# .sp +# This parameter is available with Postfix 2.11 and later. +# .IP "\fBtls_CApath\fR" +# Directory containing X509 Certificate Authority certificates +# in separate individual files. +# .sp +# This parameter is available with Postfix 2.11 and later. +# .IP "\fBtls_verify_cert (default: no)\fR" +# Verify that the server's name matches the common name in the +# certficate. +# .sp +# This parameter is available with Postfix 2.11 and later. # OBSOLETE QUERY INTERFACE # .ad # .fi diff --git a/postfix/proto/postconf.proto b/postfix/proto/postconf.proto index 1a72ee4e5..89be6ccbf 100644 --- a/postfix/proto/postconf.proto +++ b/postfix/proto/postconf.proto @@ -15438,7 +15438,7 @@ configuration parameter. See there for details.

Lookup the associated DANE TLSA RRset even when a hostname is not an alias and its address records lie in an unsigned zone. This is unlikely to ever yield DNSSEC validated results, since child -zones of unsigned zones are also unsigned in the absense of DLV or +zones of unsigned zones are also unsigned in the absence of DLV or locally configured non-root trust-anchors. We anticipate that such mechanisms will not be used for just the "_tcp" subdomain of a host. Suppressing the TLSA RRset lookup reduces latency and avoids potential @@ -15446,3 +15446,48 @@ interoperability problems with nameservers for unsigned zones that are not prepared to handle the new TLSA RRset.

This feature is available in Postfix 2.11.

+ +%PARAM tls_dane_digests sha512 sha256 + +

RFC 6698 TLSA resource-record "matching type" digest algorithms +in descending preference order. All the specified algorithms must +be supported by the underlying OpenSSL library, otherwise the Postfix +SMTP client will not support DANE TLSA security.

+ +

When for a particular combination of "certificate usage" and +"selector" the TLSA RRset contains a well-formed record with a +matching type of "0", i.e. a full value of the associated certificate +or public key, the Postfix SMTP client will ignore all other matching +types for the same certificate usage and selector. In this case +the first algorithm listed in tls_dane_digests will be used to +compute a digest of the full value, which will then be used to match +certificates or public keys in the server's certificate chain.

+ +

Otherwise, when for a particular combination of "certificate +usage" and "selector" the TLSA RRset contains a records with more +than one non-zero matching type, i.e. multiple digest algorithms, +only records with the highest preference digest are used after +discarding any records with an incorrect digest length as unusable.

+ +

This strategy ensures that the strongest digest supported by +both the Postfix SMTP client and the remote server is used, and +weaker digests are ignored. This supports non-disruptive deprecation +of outdated digest algorithms.

+ +

The strategy requires that when a TLSA RRset provides association +data for multiple certificates or public keys, all RRs with the same +"certificate usage" and "selector" be published with the same set +of digests. In particular, during key rotation, when a certificate +or public key is being replaced with another (and both are published +during the transition) both the old and the new certificate MUST be +specified with the same set of digests. One can change the list of +digest algorithms later, once old keys are retired. At any given +time change either the list of digests without changing the list of +certificates or public keys or the list of certificates or public +keys without changing the list of digests.

+ +

It is expected that this algorithm agility mechanism will be +published in a standards track RFC for SMTP with DANE, and perhaps +in an eventual update to RFC 6698.

+ +

This feature is available in Postfix 2.11.

diff --git a/postfix/src/global/dict_mysql.c b/postfix/src/global/dict_mysql.c index a3e231a4e..e899a1489 100644 --- a/postfix/src/global/dict_mysql.c +++ b/postfix/src/global/dict_mysql.c @@ -91,6 +91,25 @@ /* releases. /* .IP hosts /* List of hosts to connect to. +/* .IP option_file +/* Read options from the given file instead of the default my.cnf +/* location. +/* .IP option_group +/* Read options from the given group. +/* .IP tls_cert_file +/* File containing client's X509 certificate. +/* .IP tls_key_file +/* File containing the private key corresponding to \fItls_cert_file\fR. +/* .IP tls_CAfile +/* File containing certificates for all of the X509 Certificate +/* Authorities the client will recognize. Takes precedence over +/* \fItls_CApath\fR. +/* .IP tls_CApath +/* Directory containing X509 Certificate Authority certificates +/* in separate individual files. +/* .IP tls_verify_cert +/* Verify that the server's name matches the common name of the +/* certficate. /* .PP /* For example, if you want the map to reference databases of /* the name "your_db" and execute a query like this: select @@ -217,6 +236,8 @@ typedef struct { CFG_PARSER *parser; char *query; char *result_format; + char *option_file; + char *option_group; void *ctx; int expansion_limit; char *username; @@ -226,6 +247,14 @@ typedef struct { PLMYSQL *pldb; #if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000 HOST *active_host; + char *tls_cert_file; + char *tls_key_file; + char *tls_CAfile; + char *tls_CApath; + char *tls_ciphers; +#if MYSQL_VERSION_ID >= 50023 + int tls_verify_cert; +#endif #endif } DICT_MYSQL; @@ -242,12 +271,11 @@ typedef struct { /* internal function declarations */ static PLMYSQL *plmysql_init(ARGV *); -static MYSQL_RES *plmysql_query(DICT_MYSQL *, const char *, VSTRING *, char *, - char *, char *); +static MYSQL_RES *plmysql_query(DICT_MYSQL *, const char *, VSTRING *); static void plmysql_dealloc(PLMYSQL *); static void plmysql_close_host(HOST *); static void plmysql_down_host(HOST *); -static void plmysql_connect_single(HOST *, char *, char *, char *); +static void plmysql_connect_single(DICT_MYSQL *, HOST *); static const char *dict_mysql_lookup(DICT *, const char *); DICT *dict_mysql_open(const char *, int, int); static void dict_mysql_close(DICT *); @@ -349,10 +377,7 @@ static const char *dict_mysql_lookup(DICT *dict, const char *name) return (0); /* do the query - set dict->error & cleanup if there's an error */ - if ((query_res = plmysql_query(dict_mysql, name, query, - dict_mysql->dbname, - dict_mysql->username, - dict_mysql->password)) == 0) { + if ((query_res = plmysql_query(dict_mysql, name, query)) == 0) { dict->error = DICT_ERR_RETRY; return (0); } @@ -428,10 +453,10 @@ static HOST *dict_mysql_find_host(PLMYSQL *PLDB, unsigned stat, unsigned type) /* dict_mysql_get_active - get an active connection */ -static HOST *dict_mysql_get_active(PLMYSQL *PLDB, char *dbname, - char *username, char *password) +static HOST *dict_mysql_get_active(DICT_MYSQL *dict_mysql) { const char *myname = "dict_mysql_get_active"; + PLMYSQL *PLDB = dict_mysql->pldb; HOST *host; int count = RETRY_CONN_MAX; @@ -457,7 +482,7 @@ static HOST *dict_mysql_get_active(PLMYSQL *PLDB, char *dbname, if (msg_verbose) msg_info("%s: attempting to connect to host %s", myname, host->hostname); - plmysql_connect_single(host, dbname, username, password); + plmysql_connect_single(dict_mysql, host); if (host->stat == STATACTIVE) return host; } @@ -485,17 +510,12 @@ static void dict_mysql_event(int unused_event, char *context) static MYSQL_RES *plmysql_query(DICT_MYSQL *dict_mysql, const char *name, - VSTRING *query, - char *dbname, - char *username, - char *password) + VSTRING *query) { - PLMYSQL *PLDB = dict_mysql->pldb; HOST *host; MYSQL_RES *res = 0; - while ((host = dict_mysql_get_active(PLDB, dbname, username, password)) != NULL) { - + while ((host = dict_mysql_get_active(dict_mysql)) != NULL) { #if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000 /* @@ -534,15 +554,32 @@ static MYSQL_RES *plmysql_query(DICT_MYSQL *dict_mysql, * used to reconnect to a single database when one is down or none is * connected yet. Log all errors and set the stat field of host accordingly */ -static void plmysql_connect_single(HOST *host, char *dbname, char *username, char *password) +static void plmysql_connect_single(DICT_MYSQL *dict_mysql, HOST *host) { if ((host->db = mysql_init(NULL)) == NULL) msg_fatal("dict_mysql: insufficient memory"); + if (dict_mysql->option_file) + mysql_options(host->db, MYSQL_READ_DEFAULT_FILE, dict_mysql->option_file); + if (dict_mysql->option_group) + mysql_options(host->db, MYSQL_READ_DEFAULT_GROUP, dict_mysql->option_group); +#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000 + if (dict_mysql->tls_key_file || dict_mysql->tls_cert_file || + dict_mysql->tls_CAfile || dict_mysql->tls_CApath || dict_mysql->tls_ciphers) + mysql_ssl_set(host->db, + dict_mysql->tls_key_file, dict_mysql->tls_cert_file, + dict_mysql->tls_CAfile, dict_mysql->tls_CApath, + dict_mysql->tls_ciphers); +#if MYSQL_VERSION_ID >= 50023 + if (dict_mysql->tls_verify_cert != -1) + mysql_options(host->db, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, + &dict_mysql->tls_verify_cert); +#endif +#endif if (mysql_real_connect(host->db, (host->type == TYPEINET ? host->name : 0), - username, - password, - dbname, + dict_mysql->username, + dict_mysql->password, + dict_mysql->dbname, host->port, (host->type == TYPEUNIX ? host->name : 0), 0)) { @@ -582,7 +619,7 @@ static void plmysql_down_host(HOST *host) static void mysql_parse_config(DICT_MYSQL *dict_mysql, const char *mysqlcf) { - const char *myname = "mysqlname_parse"; + const char *myname = "mysql_parse_config"; CFG_PARSER *p = dict_mysql->parser; VSTRING *buf; char *hosts; @@ -591,6 +628,18 @@ static void mysql_parse_config(DICT_MYSQL *dict_mysql, const char *mysqlcf) dict_mysql->password = cfg_get_str(p, "password", "", 0, 0); dict_mysql->dbname = cfg_get_str(p, "dbname", "", 1, 0); dict_mysql->result_format = cfg_get_str(p, "result_format", "%s", 1, 0); + dict_mysql->option_file = cfg_get_str(p, "option_file", NULL, 0, 0); + dict_mysql->option_group = cfg_get_str(p, "option_group", NULL, 0, 0); +#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000 + dict_mysql->tls_key_file = cfg_get_str(p, "tls_key_file", NULL, 0, 0); + dict_mysql->tls_cert_file = cfg_get_str(p, "tls_cert_file", NULL, 0, 0); + dict_mysql->tls_CAfile = cfg_get_str(p, "tls_CAfile", NULL, 0, 0); + dict_mysql->tls_CApath = cfg_get_str(p, "tls_CApath", NULL, 0, 0); + dict_mysql->tls_ciphers = cfg_get_str(p, "tls_ciphers", NULL, 0, 0); +#if MYSQL_VERSION_ID >= 50023 + dict_mysql->tls_verify_cert = cfg_get_bool(p, "tls_verify_cert", -1); +#endif +#endif /* * XXX: The default should be non-zero for safety, but that is not @@ -759,6 +808,22 @@ static void dict_mysql_close(DICT *dict) myfree(dict_mysql->dbname); myfree(dict_mysql->query); myfree(dict_mysql->result_format); + if (dict_mysql->option_file) + myfree(dict_mysql->option_file); + if (dict_mysql->option_group) + myfree(dict_mysql->option_group); +#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 40000 + if (dict_mysql->tls_key_file) + myfree(dict_mysql->tls_key_file); + if (dict_mysql->tls_cert_file) + myfree(dict_mysql->tls_cert_file); + if (dict_mysql->tls_CAfile) + myfree(dict_mysql->tls_CAfile); + if (dict_mysql->tls_CApath) + myfree(dict_mysql->tls_CApath); + if (dict_mysql->tls_ciphers) + myfree(dict_mysql->tls_ciphers); +#endif if (dict_mysql->hosts) argv_free(dict_mysql->hosts); if (dict_mysql->ctx) diff --git a/postfix/src/global/mail_params.h b/postfix/src/global/mail_params.h index 063977b4f..d181947a0 100644 --- a/postfix/src/global/mail_params.h +++ b/postfix/src/global/mail_params.h @@ -3103,6 +3103,13 @@ extern char *var_tls_ssl_options; #define DEF_TLS_BC_PKEY_FPRINT 0 extern bool var_tls_bc_pkey_fprint; + /* + * Ordered list of DANE digest algorithms. + */ +#define VAR_TLS_DANE_DIGESTS "tls_dane_digests" +#define DEF_TLS_DANE_DIGESTS "sha512 sha256" +extern char *var_tls_dane_digests; + /* * External interface for enabling trust-anchor digests, which are risky * when the corresponding certificate is missing from the peer chain (this diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index 13cd38b5d..65a352579 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 "20131114" +#define MAIL_RELEASE_DATE "20131117" #define MAIL_VERSION_NUMBER "2.11" #ifdef SNAPSHOT diff --git a/postfix/src/posttls-finger/posttls-finger.c b/postfix/src/posttls-finger/posttls-finger.c index 407dfb3db..2971c30b3 100644 --- a/postfix/src/posttls-finger/posttls-finger.c +++ b/postfix/src/posttls-finger/posttls-finger.c @@ -1745,11 +1745,8 @@ static void parse_match(STATE *state, int argc, char *argv[]) { #ifdef USE_TLS - argc -= optind; - argv += optind; - switch (state->level) { - case TLS_LEV_SECURE: + case TLS_LEV_SECURE: state->match = argv_alloc(2); while (*argv) argv_split_append(state->match, *argv++, ""); @@ -1826,14 +1823,17 @@ int main(int argc, char *argv[]) mail_conf_suck(); parse_options(&state, argc, argv); mail_params_init(); - - parse_match(&state, argc, argv); parse_tas(&state); + argc -= optind; + argv += optind; + /* The first non-option argument is the destination. */ - if (argc < optind) + if (!argc) usage(); - state.dest = mystrdup(argv[optind]); + + state.dest = mystrdup(argv[0]); + parse_match(&state, --argc, ++argv); /* Don't talk to remote systems as root */ if (!geteuid()) diff --git a/postfix/src/tls/tls_dane.c b/postfix/src/tls/tls_dane.c index 8e499832b..6e6e1fb9c 100644 --- a/postfix/src/tls/tls_dane.c +++ b/postfix/src/tls/tls_dane.c @@ -235,17 +235,29 @@ static int wrap_signed = 0; #endif static const EVP_MD *signmd; +static const char *signalg; static EVP_PKEY *danekey; static ASN1_OBJECT *serverAuth; -static const char *sha256 = "sha256"; -static const EVP_MD *sha256md; -static int sha256len; +/* + * https://www.iana.org/assignments/dane-parameters/dane-parameters.xhtml + */ +typedef struct digest_info { + const char *alg; /* OpenSSL name */ + const EVP_MD *md; /* OpenSSL EVP handle */ + int len; /* digest octet length */ + int pref; /* tls_dane_digests index or -1 */ + uint8_t dane_id; /* IANA id */ +} digest_info; -static const char *sha512 = "sha512"; -static const EVP_MD *sha512md; -static int sha512len; +#define MAXDIGESTS 256 /* RFC limit */ +digest_info digest_table[] = { + {"full", 0, 0, 0, DNS_TLSA_MATCHING_TYPE_NO_HASH_USED}, + {"sha256", 0, 0, -1, DNS_TLSA_MATCHING_TYPE_SHA256}, + {"sha512", 0, 0, -1, DNS_TLSA_MATCHING_TYPE_SHA512}, + {0, 0, 0, 0, 0} +}; static int digest_mask; @@ -271,6 +283,46 @@ void tls_dane_verbose(int on) dane_verbose = on; } +/* digest_info_cmp - qsort() comparator for digest_table */ + +static int digest_info_cmp(const void *a, const void *b) +{ + register const digest_info *ai = (const digest_info *) a; + register const digest_info *bi = (const digest_info *) b; + + /* + * Negative preferences sort last. Otherwise, sort in ascending order. + */ + if (ai->pref == bi->pref) + return (0); + if (ai->pref < 0 || bi->pref < 0) + return bi->pref - ai->pref; + return ai->pref - bi->pref; +} + +/* dane_digest_info - locate digest_table entry for given IANA id */ + +static digest_info *dane_digest_info(uint8_t dane_id) +{ + digest_info *di; + + for (di = digest_table; di->alg; ++di) + if (di->dane_id == dane_id) + return (di); + return (0); +} + +/* dane_digest_pref - digest preference by IANA id */ + +static int dane_digest_pref(uint8_t dane_id) +{ + digest_info *di = dane_digest_info(dane_id); + + if (di && di->pref >= 0) + return (di->pref); + return (MAXDIGESTS + dane_id); +} + /* gencakey - generate interal DANE root CA key */ static EVP_PKEY *gencakey(void) @@ -278,8 +330,6 @@ static EVP_PKEY *gencakey(void) EVP_PKEY *key = 0; #ifdef WRAP_SIGNED - int len; - unsigned char *p; EC_KEY *eckey; EC_GROUP *group; @@ -312,16 +362,44 @@ static void dane_init(void) TLS_DANE_TAA, TLS_DANE_ENABLE_TAA, 0, }; + int digest_pref = 0; + char *cp; + char *save; + char *tok; + digest_info *di; digest_mask = name_mask_opt(VAR_TLS_DANE_TA_DGST, ta_dgsts, var_tls_dane_ta_dgst, NAME_MASK_ANY_CASE | NAME_MASK_FATAL); - if ((sha256md = EVP_get_digestbyname(sha256)) != 0) - sha256len = EVP_MD_size(sha256md); - if ((sha512md = EVP_get_digestbyname(sha512)) != 0) - sha512len = EVP_MD_size(sha512md); - signmd = sha256md ? sha256md : EVP_sha1(); + save = cp = mystrdup(var_tls_dane_digests); + while ((tok = mystrtok(&cp, "\t\n\r ,")) != 0) { + for (di = digest_table; di->alg; ++di) + if (strcasecmp(tok, di->alg) == 0) + break; + if (di->alg != 0 + && (di->md = EVP_get_digestbyname(di->alg)) != 0 + && (di->len = EVP_MD_size(di->md)) > 0 + && di->len <= EVP_MAX_MD_SIZE) { + + /* + * The most preferred digest will be used for cert signing and + * digesting for comparison. + */ + if ((di->pref = ++digest_pref) == 1) { + signalg = di->alg; + signmd = di->md; + } + } else { + msg_warn("Unsupported DANE digest algorithm: %s", tok); + continue; + } + } + myfree(save); + + if (digest_pref > 0) + qsort(digest_table, digest_pref, sizeof(digest_table[0]), + digest_info_cmp); /* Don't report old news */ ERR_clear_error(); @@ -344,7 +422,7 @@ int tls_dane_avail(void) dane_init(); #ifdef DANE_TLSA_SUPPORT - return (sha256md && sha512md && serverAuth); + return (signalg && serverAuth); #else return (0); #endif @@ -574,9 +652,27 @@ static void dane_add(TLS_DANE *dane, int certusage, int selector, static int tlsa_rr_cmp(DNS_RR *a, DNS_RR *b) { - if (a->data_len == b->data_len) - return (memcmp(a->data, b->data, a->data_len)); - return ((a->data_len > b->data_len) ? 1 : -1); + int cmp; + + /* + * Sort in ascending order, by usage, selector, matching type preference + * and payload. The usage, selector and matching type are the first + * three unsigned octets of the RR data. + */ + if (a->data_len > 2 && b->data_len > 2) { + uint8_t *ai = (uint8_t *) a->data; + uint8_t *bi = (uint8_t *) b->data; + +#define signedcmp(x, y) (((int)(y)) - ((int)(x))) + + if ((cmp = signedcmp(ai[0], bi[0])) != 0 + || (cmp = signedcmp(ai[1], bi[1])) != 0 + || (cmp = dane_digest_pref(ai[2]) - dane_digest_pref(bi[2])) != 0) + return (cmp); + } + if ((cmp = a->data_len - b->data_len) != 0) + return (cmp); + return (memcmp(a->data, b->data, a->data_len)); } /* parse_tlsa_rrs - parse a validated TLSA RRset */ @@ -588,18 +684,24 @@ static void parse_tlsa_rrs(TLS_DANE *dane, DNS_RR *rr) uint8_t mtype; int mlen; const unsigned char *p; + uint32_t prev_usage_selector; /* Previous (usage<<8|selector) */ + uint32_t prev_mtype; /* Previous valid mtype for above */ + +#define NO_PREV 0xffffffff /* Not any usage|selector or + * mtype */ + prev_usage_selector = NO_PREV; if (rr == 0) msg_panic("null TLSA rr"); for ( /* nop */ ; rr; rr = rr->next) { const char *mdalg = 0; - int mdlen; char *digest; int same = (strcasecmp(rr->rname, rr->qname) == 0); uint8_t *ip = (uint8_t *) rr->data; X509 *x = 0; /* OpenSSL tries to re-use *x if x!=0 */ EVP_PKEY *k = 0; /* OpenSSL tries to re-use *k if k!=0 */ + digest_info *di; #define rcname(rr) (same ? "" : rr->qname) #define rarrow(rr) (same ? "" : " -> ") @@ -638,26 +740,34 @@ static void parse_tlsa_rrs(TLS_DANE *dane, DNS_RR *rr) break; } - switch (mtype = *ip++) { - default: - msg_warn("unsupported matching type %u in RR: " - "%s%s%s IN TLSA %u %u %u ...", mtype, rcname(rr), - rarrow(rr), rr->rname, usage, selector, mtype); + /* + * Skip all but the most preferred matching type for any given + * (usage, selector) combination. + */ + mtype = *ip++; + if (prev_usage_selector != (usage << 8 | selector)) + prev_mtype = NO_PREV; + else if (prev_mtype != NO_PREV && prev_mtype != mtype) continue; - case DNS_TLSA_MATCHING_TYPE_SHA256: - if (!mdalg) { - mdalg = sha256; - mdlen = sha256len; + + switch (mtype) { + default: + if ((di = dane_digest_info(mtype)) == 0) { + msg_warn("unsupported matching type %u in RR: " + "%s%s%s IN TLSA %u %u %u ...", mtype, rcname(rr), + rarrow(rr), rr->rname, usage, selector, mtype); + continue; } - /* FALLTHROUGH */ - case DNS_TLSA_MATCHING_TYPE_SHA512: - if (!mdalg) { - mdalg = sha512; - mdlen = sha512len; + if (di->pref < 0) { + msg_warn("digest algorithm %s locally disabled, in RR: " + "%s%s%s IN TLSA %u %u %u ...", di->alg, + rcname(rr), rarrow(rr), rr->rname, + usage, selector, mtype); + continue; } - if (mlen != mdlen) { + if (mlen != di->len) { msg_warn("malformed %s digest, length %u, in RR: " - "%s%s%s IN TLSA %u %u %u ...", mdalg, mlen, + "%s%s%s IN TLSA %u %u %u ...", di->alg, mlen, rcname(rr), rarrow(rr), rr->rname, usage, selector, mtype); continue; @@ -667,7 +777,7 @@ static void parse_tlsa_rrs(TLS_DANE *dane, DNS_RR *rr) if (!(digest_mask & TLS_DANE_ENABLE_CC)) { msg_warn("%s trust-anchor %s digests disabled, in RR: " "%s%s%s IN TLSA %u %u %u ...", TLS_DANE_CC, - mdalg, rcname(rr), rarrow(rr), rr->rname, + di->alg, rcname(rr), rarrow(rr), rr->rname, usage, selector, mtype); continue; } @@ -676,14 +786,14 @@ static void parse_tlsa_rrs(TLS_DANE *dane, DNS_RR *rr) if (!(digest_mask & TLS_DANE_ENABLE_TAA)) { msg_warn("%s trust-anchor %s digests disabled, in RR: " "%s%s%s IN TLSA %u %u %u ...", TLS_DANE_TAA, - mdalg, rcname(rr), rarrow(rr), rr->rname, + di->alg, rcname(rr), rarrow(rr), rr->rname, usage, selector, mtype); continue; } break; } - dane_add(dane, usage, selector, mdalg, - digest = tls_digest_encode((unsigned char *) ip, mdlen)); + digest = tls_digest_encode((unsigned char *) ip, di->len); + dane_add(dane, usage, selector, di->alg, digest); break; case DNS_TLSA_MATCHING_TYPE_NO_HASH_USED: @@ -726,6 +836,7 @@ static void parse_tlsa_rrs(TLS_DANE *dane, DNS_RR *rr) } X509_free(x); break; + case DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO: if (!d2i_PUBKEY(&k, &p, mlen)) { msg_warn("malformed %s in RR: " @@ -747,12 +858,19 @@ static void parse_tlsa_rrs(TLS_DANE *dane, DNS_RR *rr) /* * The cert or key was valid, just digest the raw object, and - * encode the digest value. We choose SHA256. + * encode the digest value. */ - dane_add(dane, usage, selector, mdalg = sha256, - digest = tls_data_fprint((char *) ip, mlen, sha256)); + digest = tls_data_fprint((char *) ip, mlen, signalg); + dane_add(dane, usage, selector, mdalg = signalg, digest); break; } + + /* + * Save state + */ + prev_usage_selector = (usage << 8 | selector); + prev_mtype = mtype; + if (msg_verbose || dane_verbose) { switch (mtype) { default: @@ -803,7 +921,13 @@ static void *dane_lookup(const char *tlsa_fqdn, void *unused_ctx) dane->expires = 1 + event_time() + rrs->ttl; if (rrs->dnssec_valid) { - /* Sort for deterministic digest in session cache lookup key */ + + /* + * Sort for deterministic digest in session cache lookup key. In + * addition we must arrange for more preferred matching types + * (full value or digest) to precede less preferred ones for the + * same usage and selector. + */ rrs = dns_rr_sort(rrs, tlsa_rr_cmp); parse_tlsa_rrs(dane, rrs); } else @@ -905,7 +1029,7 @@ int tls_dane_load_trustfile(TLS_DANE *dane, const char *tafile) msg_warn("trust-anchor files not supported"); return (0); } - mdalg = sha256md ? sha256 : "sha1"; + mdalg = signalg ? signalg : "sha1"; /* * On each call, PEM_read() wraps a stdio file in a BIO_NOCLOSE bio, @@ -1001,12 +1125,20 @@ int tls_dane_match(TLS_SESS_STATE *TLScontext, int usage, char **dgst; ARGV *certs; + /* + * Note, set_trust() needs to know whether the match was for a pkey + * digest or a certificate digest. We return MATCHED_PKEY or + * MATCHED_CERT accordingly. + */ +#define MATCHED_CERT 1 +#define MATCHED_PKEY 2 + 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; + matched = MATCHED_PKEY; if (TLScontext->log_mask & (TLS_LOG_VERBOSE | TLS_LOG_CERTMATCH) && matched) msg_info("%s: depth=%d matched %s public-key %s digest=%s", @@ -1034,7 +1166,7 @@ int tls_dane_match(TLS_SESS_STATE *TLScontext, int usage, for (dgst = certs->argv; !matched && *dgst; ++dgst) if (strcasecmp(cert_dgst, *dgst) == 0) - matched = 1; + matched = MATCHED_CERT; if (TLScontext->log_mask & (TLS_LOG_VERBOSE | TLS_LOG_CERTMATCH) && matched) msg_info("%s: depth=%d matched %s certificate %s digest %s", @@ -1139,15 +1271,10 @@ static int add_skid(X509 *cert, AUTHORITY_KEYID *akid) return (ret); } -/* set_issuer - set issuer DN to match akid if specified */ +/* akid_issuer_name - get akid issuer directory name */ -static int set_issuer_name(X509 *cert, AUTHORITY_KEYID *akid) +static X509_NAME *akid_issuer_name(AUTHORITY_KEYID *akid) { - - /* - * If subject's akid specifies an authority key identifer issuer name, we - * must use that. - */ if (akid && akid->issuer) { int i; general_name_stack_t *gens = akid->issuer; @@ -1156,9 +1283,24 @@ static int set_issuer_name(X509 *cert, AUTHORITY_KEYID *akid) GENERAL_NAME *gn = sk_GENERAL_NAME_value(gens, i); if (gn->type == GEN_DIRNAME) - return (X509_set_issuer_name(cert, gn->d.dirn)); + return (gn->d.dirn); } } + return (0); +} + +/* set_issuer - set issuer DN to match akid if specified */ + +static int set_issuer_name(X509 *cert, AUTHORITY_KEYID *akid) +{ + X509_NAME *name = akid_issuer_name(akid); + + /* + * If subject's akid specifies an authority key identifer issuer name, we + * must use that. + */ + if (name) + return (X509_set_issuer_name(cert, name)); return (X509_set_issuer_name(cert, X509_get_subject_name(cert))); } @@ -1179,23 +1321,26 @@ static void grow_chain(x509_stack_t **skptr, X509 *cert, ASN1_OBJECT *trust) /* wrap_key - wrap TA "key" as issuer of "subject" */ -static int wrap_key(TLS_SESS_STATE *TLScontext, EVP_PKEY *key, X509 *subject, - int depth) +static int wrap_key(TLS_SESS_STATE *TLScontext, int depth, + EVP_PKEY *key, X509 *subject) { int ret = 1; + int selfsigned = 0; X509 *cert = 0; AUTHORITY_KEYID *akid; X509_NAME *name = X509_get_issuer_name(subject); + X509_NAME *akid_name; /* * Record the depth of the intermediate wrapper certificate, logged in - * the verify callback, unlike the parent root CA. + * the verify callback. */ - if (!key) - TLScontext->tadepth = depth; - else if (TLScontext->log_mask & (TLS_LOG_VERBOSE | TLS_LOG_CERTMATCH)) - msg_info("%s: depth=%d chain is trust-anchor signed", - TLScontext->namaddr, depth); + if (TLScontext->tadepth < 0) { + TLScontext->tadepth = depth + 1; + if (TLScontext->log_mask & (TLS_LOG_VERBOSE | TLS_LOG_CERTMATCH)) + msg_info("%s: depth=%d chain is trust-anchor signed", + TLScontext->namaddr, depth); + } /* * If key is NULL generate a self-signed root CA, with key "danekey", @@ -1205,6 +1350,9 @@ static int wrap_key(TLS_SESS_STATE *TLScontext, EVP_PKEY *key, X509 *subject, return (0); akid = X509_get_ext_d2i(subject, NID_authority_key_identifier, 0, 0); + if ((akid_name = akid_issuer_name(akid)) == 0 + || X509_NAME_cmp(name, akid_name) == 0) + selfsigned = 1; ERR_clear_error(); @@ -1217,11 +1365,12 @@ static int wrap_key(TLS_SESS_STATE *TLScontext, EVP_PKEY *key, X509 *subject, || !X509_gmtime_adj(X509_get_notAfter(cert), 30 * 86400L) || !X509_set_pubkey(cert, key ? key : danekey) || !add_ext(0, cert, NID_basic_constraints, "CA:TRUE") - || (key && !add_akid(cert, akid)) + || (key && !selfsigned && !add_akid(cert, akid)) || !add_skid(cert, akid) || (wrap_signed && (!X509_sign(cert, danekey, signmd) - || (key && !wrap_key(TLScontext, 0, cert, depth + 1))))) { + || (key && !selfsigned + && !wrap_key(TLScontext, depth + 1, 0, cert))))) { msg_warn("error generating DANE wrapper certificate"); tls_print_errors(); ret = 0; @@ -1229,7 +1378,7 @@ static int wrap_key(TLS_SESS_STATE *TLScontext, EVP_PKEY *key, X509 *subject, if (akid) AUTHORITY_KEYID_free(akid); if (ret) { - if (key && wrap_signed) + if (key && !selfsigned && wrap_signed) grow_chain(&TLScontext->untrusted, cert, 0); else grow_chain(&TLScontext->trusted, cert, serverAuth); @@ -1239,6 +1388,56 @@ static int wrap_key(TLS_SESS_STATE *TLScontext, EVP_PKEY *key, X509 *subject, return (ret); } +/* wrap_cert - wrap "tacert" as issuer of "subject" */ + +static int wrap_cert(TLS_SESS_STATE *TLScontext, int depth, + X509 *tacert, X509 *subject) +{ + int ret = 1; + X509 *cert; + int len; + unsigned char *asn1; + unsigned char *buf; + + TLScontext->tadepth = depth; + if (TLScontext->log_mask & (TLS_LOG_VERBOSE | TLS_LOG_CERTMATCH)) + msg_info("%s: depth=%d trust-anchor certificate", + TLScontext->namaddr, depth); + + /* + * If the TA certificate is self-issued, use it directly. + */ + if (!wrap_signed + || X509_check_issued(tacert, tacert) == X509_V_OK) { + grow_chain(&TLScontext->trusted, tacert, serverAuth); + return (ret); + } + /* Deep-copy tacert by converting to ASN.1 and back */ + len = i2d_X509(tacert, NULL); + asn1 = buf = (unsigned char *) mymalloc(len); + i2d_X509(tacert, &buf); + if (buf - asn1 != len) + msg_panic("i2d_X509 failed to encode TA certificate"); + + buf = asn1; + cert = d2i_X509(0, (unsigned const char **) &buf, len); + if (!cert || (buf - asn1) != len) + msg_panic("d2i_X509 failed to decode TA certificate"); + myfree((char *) asn1); + + grow_chain(&TLScontext->untrusted, cert, 0); + + /* Sign and wrap TA cert with internal "danekey" */ + if (!X509_sign(cert, danekey, signmd) + || !wrap_key(TLScontext, depth + 1, danekey, cert)) { + msg_warn("error generating DANE wrapper certificate"); + tls_print_errors(); + ret = 0; + } + X509_free(cert); + return (ret); +} + /* ta_signed - is certificate signed by a TLSA cert or pkey */ static int ta_signed(TLS_SESS_STATE *TLScontext, X509 *cert, int depth) @@ -1262,7 +1461,7 @@ static int ta_signed(TLS_SESS_STATE *TLScontext, X509 *cert, int depth) continue; /* Check signature, since some other TA may work if not this. */ if (X509_verify(cert, pk) > 0) - done = wrap_key(TLScontext, pk, cert, depth); + done = wrap_cert(TLScontext, depth + 1, x->cert, cert); EVP_PKEY_free(pk); } } @@ -1286,7 +1485,7 @@ static int ta_signed(TLS_SESS_STATE *TLScontext, X509 *cert, int depth) */ for (k = dane->pkeys; !done && k; k = k->next) if (X509_verify(cert, k->pkey) > 0) - done = wrap_key(TLScontext, k->pkey, cert, depth); + done = wrap_key(TLScontext, depth, k->pkey, cert); return (done); } @@ -1297,6 +1496,7 @@ static void set_trust(TLS_SESS_STATE *TLScontext, X509_STORE_CTX *ctx) { int n; int i; + int match; int depth = 0; EVP_PKEY *takey; X509 *ca; @@ -1331,10 +1531,21 @@ static void set_trust(TLS_SESS_STATE *TLScontext, X509_STORE_CTX *ctx) ca = sk_X509_delete(in, i); /* Is it a trust anchor? */ - if (tls_dane_match(TLScontext, TLS_DANE_TA, ca, depth + 1)) { - if ((takey = X509_get_pubkey(ca)) != 0 - && wrap_key(TLScontext, takey, cert, depth)) + match = tls_dane_match(TLScontext, TLS_DANE_TA, ca, depth + 1); + if (match) { + switch (match) { + case MATCHED_CERT: + wrap_cert(TLScontext, depth, ca, cert); + break; + case MATCHED_PKEY: + if ((takey = X509_get_pubkey(ca)) == 0) + msg_panic("trust-anchor certificate has null pkey"); + wrap_key(TLScontext, depth, takey, cert); EVP_PKEY_free(takey); + break; + default: + msg_panic("unexpected tls_dane_match result: %d", match); + } cert = 0; break; } diff --git a/postfix/src/tls/tls_misc.c b/postfix/src/tls/tls_misc.c index 492c6f50c..4bd8b9b03 100644 --- a/postfix/src/tls/tls_misc.c +++ b/postfix/src/tls/tls_misc.c @@ -15,6 +15,7 @@ /* char *var_tls_eecdh_strong; /* char *var_tls_eecdh_ultra; /* char *var_tls_dane_ta_dgst; +/* char *var_tls_dane_digests; /* int var_tls_daemon_rand_bytes; /* bool var_tls_append_def_CA; /* bool var_tls_preempt_clist; @@ -220,6 +221,7 @@ char *var_tls_null_clist; int var_tls_daemon_rand_bytes; char *var_tls_eecdh_strong; char *var_tls_eecdh_ultra; +char *var_tls_dane_digests; char *var_tls_dane_ta_dgst; bool var_tls_append_def_CA; char *var_tls_bug_tweaks; @@ -227,6 +229,7 @@ char *var_tls_ssl_options; bool var_tls_bc_pkey_fprint; bool var_tls_multi_wildcard; char *var_tls_mgr_service; +char *tls_dane_digests; #ifdef VAR_TLS_PREEMPT_CLIST bool var_tls_preempt_clist; @@ -594,6 +597,7 @@ void tls_param_init(void) VAR_TLS_EECDH_ULTRA, DEF_TLS_EECDH_ULTRA, &var_tls_eecdh_ultra, 1, 0, VAR_TLS_BUG_TWEAKS, DEF_TLS_BUG_TWEAKS, &var_tls_bug_tweaks, 0, 0, VAR_TLS_SSL_OPTIONS, DEF_TLS_SSL_OPTIONS, &var_tls_ssl_options, 0, 0, + VAR_TLS_DANE_DIGESTS, DEF_TLS_DANE_DIGESTS, &var_tls_dane_digests, 1, 0, VAR_TLS_DANE_TA_DGST, DEF_TLS_DANE_TA_DGST, &var_tls_dane_ta_dgst, 0, 0, VAR_TLS_MGR_SERVICE, DEF_TLS_MGR_SERVICE, &var_tls_mgr_service, 1, 0, 0, diff --git a/postfix/src/util/dict_test.c b/postfix/src/util/dict_test.c index 409b634b9..55910f756 100644 --- a/postfix/src/util/dict_test.c +++ b/postfix/src/util/dict_test.c @@ -1,8 +1,6 @@ /* - * Proof-of-concept test program. Create, update or read a database. When - * the input is a name=value pair, the database is updated, otherwise the - * program assumes that the input specifies a lookup key and prints the - * corresponding value. + * Proof-of-concept test program. Create, update or read a database. Type + * '?' for a list of commands. */ /* System library. */