]*sion_ids\b;$&;g;
+ s;\bsmtpd_tls_enable_rpk\b;$&;g;
s;\bsmtpd_tls_wrappermode\b;$&;g;
s;\bsmtpd_use_tls\b;$&;g;
s;\bsmtpd_reject_footer\b;$&;g;
@@ -1120,6 +1123,7 @@ while (<>) {
s;\btlsproxy_tls_protocols\b;$&;g;
s;\btlsproxy_tls_req_ccert\b;$&;g;
s;\btlsproxy_tls_security_level\b;$&;g;
+ s;\btlsproxy_tls_enable_rpk\b;$&;g;
s;\btlsproxy_use_tls\b;$&;g;
s;\btlsproxy_client_CAfile\b;$&;g;
diff --git a/postfix/proto/TLS_README.html b/postfix/proto/TLS_README.html
index a3905662a..d6fe51b52 100644
--- a/postfix/proto/TLS_README.html
+++ b/postfix/proto/TLS_README.html
@@ -2266,82 +2266,124 @@ describe the corresponding table syntax:
additional attributes are supported at this level.
may Opportunistic TLS.
-The optional "ciphers", "exclude" and "protocols" attributes
-(available for opportunistic TLS with Postfix ≥ 2.6) override the
-"smtp_tls_ciphers", "smtp_tls_exclude_ciphers" and "smtp_tls_protocols"
-configuration parameters. At this level and higher, the optional
-"servername" attribute (available with Postfix ≥ 3.4) overrides the
-global "smtp_tls_servername" parameter, enabling per-destination
-configuration of the SNI extension sent to the remote SMTP server.
+The optional "ciphers", "exclude", and "protocols" attributes (available
+for opportunistic TLS with Postfix ≥ 2.6) and "connection_reuse"
+attribute (Postfix ≥ 3.4) override the "smtp_tls_ciphers",
+"smtp_tls_exclude_ciphers", "smtp_tls_protocols", and
+"smtp_tls_connection_reuse" configuration parameters. At this level and
+higher, the optional "servername" attribute (available with Postfix ≥
+3.4) overrides the global "smtp_tls_servername" parameter, enabling
+per-destination configuration of the SNI extension sent to the remote
+SMTP server. The optional "enable_rpk" attribute (Postfix ≥ 3.9)
+overrides the main.cf smtp_tls_enable_rpk parameter. When opportunistic
+TLS handshakes fail, Postfix retries the connection with TLS disabled.
+This allows mail delivery to sites with non-interoperable TLS
+implementations.
encrypt Mandatory encryption.
-Mail is delivered only if the remote SMTP server offers STARTTLS
-and the TLS handshake succeeds. At this level and higher, the optional
+Mail is delivered only if the remote SMTP server offers STARTTLS and the
+TLS handshake succeeds. At this level and higher, the optional
"protocols" attribute overrides the main.cf smtp_tls_mandatory_protocols
-parameter, the optional "ciphers" attribute overrides the
-main.cf smtp_tls_mandatory_ciphers parameter, and the optional
-"exclude" attribute (Postfix ≥ 2.6) overrides the main.cf
-smtp_tls_mandatory_exclude_ciphers parameter.
+parameter, the optional "ciphers" attribute overrides the main.cf
+smtp_tls_mandatory_ciphers parameter, the optional "exclude" attribute
+(Postfix ≥ 2.6) overrides the main.cf
+smtp_tls_mandatory_exclude_ciphers parameter, and the optional
+"connection_reuse" attribute (Postfix ≥ 3.4) overrides the main.cf
+smtp_tls_connection_reuse parameter. The optional "enable_rpk" attribute
+(Postfix ≥ 3.9) overrides the main.cf smtp_tls_enable_rpk parameter.
+
dane Opportunistic DANE TLS.
The TLS policy for the destination is obtained via TLSA records in
-DNSSEC. If no TLSA records are found, the effective security level
-used is may. If TLSA records are
-found, but none are usable, the effective security level is encrypt. When usable TLSA records
-are obtained for the remote SMTP server, SSLv2+3 are automatically
-disabled (see smtp_tls_mandatory_protocols), and the server certificate
-must match the TLSA records. RFC 7672 (DANE) TLS authentication
-and DNSSEC support is available with Postfix 2.11 and later.
+DNSSEC. If no TLSA records are found, the effective security level used
+is may. If TLSA records are found, but
+none are usable, the effective security level is encrypt. When usable TLSA records are
+obtained for the remote SMTP server, the server certificate must match
+the TLSA records (and the SNI name is unconditionally set to the TLSA
+base domain). RFC 7672 (DANE) TLS authentication and DNSSEC
+support is available with Postfix 2.11 and later. The optional
+"connection_reuse" attribute (Postfix ≥ 3.4) overrides the main.cf
+smtp_tls_connection_reuse parameter. When the effective security level
+used is may, the optional "ciphers",
+"exclude", and "protocols" attributes (Postfix ≥ 2.6) override the
+"smtp_tls_ciphers", "smtp_tls_exclude_ciphers", and "smtp_tls_protocols"
+configuration parameters. When the effective security level used is encrypt, the optional "ciphers",
+"exclude", and "protocols" attributes (Postfix ≥ 2.6) override the
+"smtp_tls_mandatory_ciphers", "smtp_tls_mandatory_exclude_ciphers", and
+"smtp_tls_mandatory_protocols" configuration parameters.
dane-only Mandatory DANE TLS.
The TLS policy for the destination is obtained via TLSA records in
-DNSSEC. If no TLSA records are found, or none are usable, no
-connection is made to the server. When usable TLSA records are
-obtained for the remote SMTP server, SSLv2+3 are automatically disabled
-(see smtp_tls_mandatory_protocols), and the server certificate must
-match the TLSA records. RFC 7672 (DANE) TLS authentication and
-DNSSEC support is available with Postfix 2.11 and later.
+DNSSEC. If no TLSA records are found, or none are usable, no connection
+is made to the server. When usable TLSA records are obtained for the
+remote SMTP server, the server certificate must match the TLSA records.
+RFC 7672 (DANE) TLS authentication and DNSSEC support is available with
+Postfix 2.11 and later. The optional "ciphers", "exclude", and
+"protocols" attributes (Postfix ≥ 2.6) override the
+"smtp_tls_mandatory_ciphers", "smtp_tls_mandatory_exclude_ciphers", and
+"smtp_tls_mandatory_protocols" configuration parameters. The optional
+"connection_reuse" attribute (Postfix ≥ 3.4) overrides the main.cf
+smtp_tls_connection_reuse parameter.
fingerprint Certificate
-fingerprint verification. Available with Postfix 2.5 and
-later. At this security level, there are no trusted Certification
-Authorities. The certificate trust chain, expiration date, ... are
-not checked. Instead, the optional match attribute, or else
-the main.cf smtp_tls_fingerprint_cert_match parameter, lists
-the server certificate fingerprints or public key fingerprints
-(Postfix 2.9 and later). The
-digest algorithm used to calculate fingerprints is selected by the
-smtp_tls_fingerprint_digest parameter. Multiple fingerprints can
-be combined with a "|" delimiter in a single match attribute, or multiple
-match attributes can be employed. The ":" character is not used as a
-delimiter as it occurs between each pair of fingerprint (hexadecimal)
-digits.
+fingerprint verification. Available with Postfix 2.5 and later. At
+this security level, there are no trusted Certification Authorities. The
+certificate trust chain, expiration date, ... are not checked. Instead,
+the optional "match" attribute, or else the main.cf
+smtp_tls_fingerprint_cert_match parameter, lists the certificate
+fingerprints or the public key fingerprints (Postfix 2.9 and later) of
+acceptable server certificates. The digest algorithm used to calculate
+the fingerprint is selected by the smtp_tls_fingerprint_digest
+parameter. Multiple fingerprints can be combined with a "|" delimiter in
+a single match attribute, or multiple match attributes can be employed.
+The ":" character is not used as a delimiter as it occurs between each
+pair of fingerprint (hexadecimal) digits. The optional "ciphers",
+"exclude", and "protocols" attributes (Postfix ≥ 2.6) override the
+"smtp_tls_mandatory_ciphers", "smtp_tls_mandatory_exclude_ciphers", and
+"smtp_tls_mandatory_protocols" configuration parameters. The optional
+"connection_reuse" attribute (Postfix ≥ 3.4) overrides the main.cf
+smtp_tls_connection_reuse parameter. The optional "enable_rpk"
+attribute (Postfix ≥ 3.9) overrides the main.cf smtp_tls_enable_rpk
+parameter.
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
-Certification 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.
+server certificate verification. Mail is delivered only if the TLS
+handshake succeeds, the remote SMTP server certificate chain can be
+validated, and a DNS name in the certificate matches the specified match
+criteria. At this security level, DNS MX lookups are presumed to be
+secure enough, and the name verified in the server certificate is
+potentially obtained via unauthenticated DNS MX lookups. The server
+certificate name must match either the optional "match" attribute, or
+else the main.cf smtp_tls_verify_cert_match parameter value. 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. The optional "connection_reuse"
+attribute (Postfix ≥ 3.4) overrides the main.cf
+smtp_tls_connection_reuse parameter.
secure Secure certificate
-verification. Mail is delivered only if the TLS handshake succeeds,
-and DNS forgery resistant remote SMTP certificate verification succeeds
-(not expired or revoked, and signed by a trusted Certification 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). 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.
+verification.
+Mail is delivered only if the TLS handshake succeeds, the remote SMTP
+server certificate chain can be validated, and a DNS name in the
+certificate matches the specified match criteria. At this security
+level, DNS MX lookups, though potentially used to determine the
+candidate next-hop gateway IP addresses, are not presumed to be
+secure enough for TLS peername verification. Instead, the default name
+verified in the server certificate is obtained directly from the
+next-hop, or is explicitly specified via the optional "match" attribute
+which overrides the main.cf smtp_tls_secure_cert_match parameter. The
+optional "ciphers", "exclude", and "protocols" attributes (Postfix ≥
+2.6) override the "smtp_tls_mandatory_ciphers",
+"smtp_tls_mandatory_exclude_ciphers", and "smtp_tls_mandatory_protocols"
+configuration parameters. 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. The
+optional "connection_reuse" attribute (Postfix ≥ 3.4) overrides the
+main.cf smtp_tls_connection_reuse parameter.
diff --git a/postfix/proto/postconf.proto b/postfix/proto/postconf.proto
index 348ca6831..1b60a9765 100644
--- a/postfix/proto/postconf.proto
+++ b/postfix/proto/postconf.proto
@@ -11448,28 +11448,35 @@ security are:
Opportunistic TLS. Since sending in the clear is acceptable,
demanding stronger than default TLS security merely reduces
interoperability. The optional "ciphers", "exclude", and "protocols"
-attributes (available for opportunistic TLS with Postfix ≥ 2.6)
-and "connection_reuse" attribute (Postfix ≥ 3.4) override the
+attributes (available for opportunistic TLS with Postfix ≥ 2.6) and
+"connection_reuse" attribute (Postfix ≥ 3.4) override the
"smtp_tls_ciphers", "smtp_tls_exclude_ciphers", "smtp_tls_protocols",
-and
-"smtp_tls_connection_reuse" configuration parameters. In the policy table,
-multiple ciphers, protocols or excluded ciphers must be separated by colons,
-as attribute values may not contain whitespace or commas. When opportunistic
-TLS handshakes fail, Postfix retries the connection with TLS disabled.
-This allows mail delivery to sites with non-interoperable TLS
-implementations.
+and "smtp_tls_connection_reuse" configuration parameters. In the policy
+table, multiple ciphers, protocols or excluded ciphers must be separated
+by colons, as attribute values may not contain whitespace or commas. At
+this level and higher, the optional "servername" attribute (available
+with Postfix ≥ 3.4) overrides the global "smtp_tls_servername"
+parameter, enabling per-destination configuration of the SNI extension
+sent to the remote SMTP server. The optional "enable_rpk" attribute
+(Postfix ≥ 3.9) overrides the main.cf smtp_tls_enable_rpk parameter.
+When opportunistic TLS handshakes fail, Postfix retries the connection
+with TLS disabled. This allows mail delivery to sites with
+non-interoperable TLS implementations.
encrypt
-Mandatory TLS encryption. At this level
-and higher, the optional "protocols" attribute overrides the main.cf
+Mandatory TLS encryption. Mail is delivered only if the remote SMTP
+server offers STARTTLS and the TLS handshake succeeds. At this level and
+higher, the optional "protocols" attribute overrides the main.cf
smtp_tls_mandatory_protocols parameter, the optional "ciphers" attribute
-overrides the main.cf smtp_tls_mandatory_ciphers parameter, the
-optional "exclude" attribute (Postfix ≥ 2.6) overrides the main.cf
+overrides the main.cf smtp_tls_mandatory_ciphers parameter, the optional
+"exclude" attribute (Postfix ≥ 2.6) overrides the main.cf
smtp_tls_mandatory_exclude_ciphers parameter, and the optional
-"connection_reuse" attribute (Postfix ≥ 3.4) overrides the
-main.cf smtp_tls_connection_reuse parameter. In the policy table,
-multiple ciphers, protocols or excluded ciphers must be separated by colons,
-as attribute values may not contain whitespace or commas.
+"connection_reuse" attribute (Postfix ≥ 3.4) overrides the main.cf
+smtp_tls_connection_reuse parameter. In the policy table, multiple
+ciphers, protocols or excluded ciphers must be separated by colons, as
+attribute values may not contain whitespace or commas. The optional
+"enable_rpk" attribute (Postfix ≥ 3.9) overrides the main.cf
+smtp_tls_enable_rpk parameter.
dane
Opportunistic DANE TLS. The TLS policy for the destination is
@@ -11514,10 +11521,10 @@ configuration parameters. The optional "connection_reuse" attribute
verification. Available with Postfix 2.5 and later. At this security
level, there are no trusted Certification Authorities. The certificate
trust chain, expiration date, ... are not checked. Instead,
-the optional "match" attribute, or else the main.cf
+the optional policy table "match" attribute, or else the main.cf
smtp_tls_fingerprint_cert_match parameter, lists the certificate
-fingerprints or the public key fingerprint (Postfix 2.9 and later)
-of the valid server certificate. The digest
+fingerprints or the public key fingerprints (Postfix 2.9 and later)
+of acceptable server certificates. The digest
algorithm used to calculate the fingerprint is selected by the
smtp_tls_fingerprint_digest parameter. Multiple fingerprints can
be combined with a "|" delimiter in a single match attribute, or multiple
@@ -11528,45 +11535,58 @@ digits. The optional "ciphers", "exclude", and "protocols" attributes
"smtp_tls_mandatory_exclude_ciphers", and "smtp_tls_mandatory_protocols"
configuration parameters. The optional "connection_reuse" attribute
(Postfix ≥ 3.4) overrides the main.cf smtp_tls_connection_reuse
-parameter.
+parameter. The optional "enable_rpk" attribute (Postfix ≥ 3.9)
+overrides the main.cf smtp_tls_enable_rpk parameter.
verify
-Mandatory TLS verification. At this security
-level, DNS MX lookups are trusted to be secure enough, and the name
-verified in the server certificate is usually obtained indirectly via
-unauthenticated DNS MX lookups. The optional "match" attribute overrides
-the main.cf smtp_tls_verify_cert_match parameter. In the policy table,
-multiple match patterns and strategies must be separated by colons.
-In practice explicit control over matching is more common with the
-"secure" policy, described below. The optional "ciphers", "exclude",
-and "protocols" attributes (Postfix ≥ 2.6) override the
-"smtp_tls_mandatory_ciphers", "smtp_tls_mandatory_exclude_ciphers", and
-"smtp_tls_mandatory_protocols" configuration parameters. The optional
-"connection_reuse" attribute (Postfix ≥ 3.4) overrides the main.cf
-smtp_tls_connection_reuse parameter.
+Mandatory TLS verification. Mail is delivered only if the TLS
+handshake succeeds, the remote SMTP server certificate chain can be
+validated, and a DNS name in the certificate matches the specified match
+criteria. At this security level, DNS MX lookups are presumed to be
+secure enough, and the name verified in the server certificate is
+potentially obtained via unauthenticated DNS MX lookups. The optional
+"match" attribute overrides the main.cf smtp_tls_verify_cert_match
+parameter. In the policy table, multiple match patterns and strategies
+must be separated by colons. In practice explicit control over matching
+is more common with the "secure" policy, described below. The optional
+"ciphers", "exclude", and "protocols" attributes (Postfix ≥ 2.6)
+override the "smtp_tls_mandatory_ciphers",
+"smtp_tls_mandatory_exclude_ciphers", and "smtp_tls_mandatory_protocols"
+configuration parameters. With Postfix ≥ 2.11 the optional "tafile"
+policy table attribute 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. The optional "connection_reuse" attribute (Postfix ≥ 3.4)
+overrides the main.cf smtp_tls_connection_reuse parameter.
secure
-Secure-channel TLS. At this security level, DNS
-MX lookups, though potentially used to determine the candidate next-hop
-gateway IP addresses, are not trusted to be secure enough for TLS
-peername verification. Instead, the default name verified in the server
-certificate is obtained directly from the next-hop, or is explicitly
-specified via the optional "match" attribute which overrides the
-main.cf smtp_tls_secure_cert_match parameter. In the policy table,
-multiple match patterns and strategies must be separated by colons.
-The match attribute is most useful when multiple domains are supported by
-a common server: the policy entries for additional domains specify matching
-rules for the primary domain certificate. While transport table overrides
-that route the secondary domains to the primary nexthop also allow secure
-verification, they risk delivery to the wrong destination when domains
-change hands or are re-assigned to new gateways. With the "match"
-attribute approach, routing is not perturbed, and mail is deferred if
-verification of a new MX host fails. The optional "ciphers", "exclude",
-and "protocols" attributes (Postfix ≥ 2.6) override the
-"smtp_tls_mandatory_ciphers", "smtp_tls_mandatory_exclude_ciphers", and
-"smtp_tls_mandatory_protocols" configuration parameters. The optional
-"connection_reuse" attribute (Postfix ≥ 3.4) overrides the main.cf
-smtp_tls_connection_reuse parameter.
+Secure certificate verification. Mail is delivered only if the TLS
+handshake succeeds, the remote SMTP server certificate chain can be
+validated, and a DNS name in the certificate matches the specified match
+criteria. At this security level, DNS MX lookups, though potentially
+used to determine the candidate next-hop gateway IP addresses, are
+not presumed to be secure enough for TLS peername verification.
+Instead, the default name verified in the server certificate is obtained
+directly from the next-hop, or is explicitly specified via the optional
+"match" attribute which overrides the main.cf smtp_tls_secure_cert_match
+parameter. In the policy table, multiple match patterns and strategies
+must be separated by colons. The match attribute is most useful when
+multiple domains are supported by a common server: the policy entries
+for additional domains specify matching rules for the primary domain
+certificate. While transport table overrides that route the secondary
+domains to the primary nexthop also allow secure verification, they risk
+delivery to the wrong destination when domains change hands or are
+re-assigned to new gateways. With the "match" attribute approach,
+routing is not perturbed, and mail is deferred if verification of a new
+MX host fails. The optional "ciphers", "exclude", and "protocols"
+attributes (Postfix ≥ 2.6) override the "smtp_tls_mandatory_ciphers",
+"smtp_tls_mandatory_exclude_ciphers", and "smtp_tls_mandatory_protocols"
+configuration parameters. 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. The
+optional "connection_reuse" attribute (Postfix ≥ 3.4) overrides the
+main.cf smtp_tls_connection_reuse parameter.
@@ -18624,6 +18644,126 @@ configuration parameter. See there for details.
This feature is available in Postfix 3.7 and later.
+%PARAM smtpd_tls_enable_rpk no
+
+ Request that remote SMTP clients send an RFC7250 raw public key
+instead of an X.509 certificate, when asking for or requiring client
+authentication. This feature is ignored when there is no raw public
+key support in the local TLS implementation.
+
+ The Postfix SMTP server will log a warning when "smtpd_tls_enable_rpk
+= yes", but the remote SMTP client sends a certificate, the
+certificate's public key fingerprint does not match a check_ccert_access
+table, while the certificate fingerprint does match a check_ccert_access
+table. The remote SMTP client would lose access when it starts
+sending a raw public key instead of a certificate, after its TLS
+implementation is updated with raw public key support.
+
+ The Postfix SMTP server always sends a raw public key instead
+of a certificate, if solicited by the remote SMTP client and the
+local TLS implementation supports raw public keys. If the client
+sends a server name indication with an SNI TLS extension, and
+tls_server_sni_maps is configured, the server will extract a raw
+public key from the indicated certificate.
+
+ Sample commands to compute certificate and public key SHA256 digests:
+
+
+# SHA256 digest of the first certificate in "cert.pem"
+$ openssl x509 -in cert.pem -outform DER | openssl dgst -sha256 -c
+
+
+
+# SHA256 digest of the SPKI of the first certificate in "cert.pem"
+$ openssl x509 -in cert.pem -pubkey -noout |
+ openssl pkey -pubin -outform DER | openssl dgst -sha256 -c
+
+
+
+# SHA256 digest of the SPKI of the first private key in "pkey.pem"
+$ openssl pkey -in pkey.pem -pubout -outform DER |
+ openssl dgst -sha256 -c
+
+
+ This feature is available in Postfix 3.9 and later.
+
+%PARAM tlsproxy_tls_enable_rpk $smtpd_tls_enable_rpk
+
+ Request that remote SMTP clients send an RFC7250 raw public key
+instead of an X.509 certificate, when asking or requiring client
+authentication. See $smtpd_tls_enable_rpk for details.
+
+ This feature is available in Postfix 3.9 and later.
+
+%PARAM smtp_tls_enable_rpk no
+
+ Request that remote SMTP servers send an RFC7250 raw public key
+instead of an X.509 certificate. This feature and the enable_rpk
+policy attribute are ignored when there is no raw public key support
+in the local TLS implementation.
+
+
+
+-
At the "may", "encrypt" and "fingerprint" security levels,
+with parameter setting "smtp_tls_enable_rpk = yes" or with "enable_rpk
+= yes" in a policy entry, the Postfix SMTP client will indicate in
+the TLS handshake that it prefers to receive a raw server public
+key, but it will still accept a server public key certificate.
+
+ -
At the "fingerprint" security level, with parameter setting
+"smtp_tls_enable_rpk = yes" or with "enable_rpk = yes" in a policy entry,
+server authentication based on certificate fingerprints becomes more
+fragile. Even if the server private key and certificate remain
+unchanged, the remote SMTP server will fail fingerprint authentication
+(won't match the configured list of fingerprints) when it starts sending
+a raw public key instead of a certificate, after its TLS implementation
+is updated with raw public key support. Therefore, DO NOT
+enable raw public keys to remote destinations authenticated by server
+certificate fingerprints. You should enable raw public keys
+only for servers matched via their public key fingerprint.
+
+ -
At the "dane" security level, the Postfix SMTP client
+always ignores the parameter setting smtp_tls_enable_rpk or the
+enable_rpk policy attribute. When all valid TLSA records specify
+only server public keys (no certificates) and the local TLS
+implementation supports raw public keys, the client will indicate
+in the TLS handshake that it prefers to receive a raw public key,
+but it will still accept a public key certificate.
+
+
+
+The Postfix SMTP client is always willing to send raw public keys
+to servers that solicit them when a client certificate is configured
+and the local TLS implementation supports raw public keys.
+
+ Sample commands to compute certificate and public key SHA256 digests:
+
+
+# SHA256 digest of the first certificate in "cert.pem"
+$ openssl x509 -in cert.pem -outform DER | openssl dgst -sha256 -c
+
+
+
+# SHA256 digest of the SPKI of the first certificate in "cert.pem"
+$ openssl x509 -in cert.pem -pubkey -noout |
+ openssl pkey -pubin -outform DER | openssl dgst -sha256 -c
+
+
+
+# SHA256 digest of the SPKI of the first private key in "pkey.pem"
+$ openssl pkey -in pkey.pem -pubout -outform DER |
+ openssl dgst -sha256 -c
+
+
+ This feature is available in Postfix 3.9 and later.
+
+%PARAM lmtp_tls_enable_rpk yes
+
+ The LMTP-specific version of the smtp_tls_enable_rpk
+configuration parameter. See there for details.
+
+ This feature is available in Postfix 3.9 and later.
+
%PARAM use_srv_lookup
Enables discovery for the specified service(s) using DNS SRV
diff --git a/postfix/proto/stop b/postfix/proto/stop
index 72cecf446..ae0f37b8b 100644
--- a/postfix/proto/stop
+++ b/postfix/proto/stop
@@ -1580,3 +1580,8 @@ charset
latin
utf
mb
+SPKI
+certificate's
+pubout
+rpk
+sni
diff --git a/postfix/proto/stop.double-history b/postfix/proto/stop.double-history
index bfa589c3d..40178180d 100644
--- a/postfix/proto/stop.double-history
+++ b/postfix/proto/stop.double-history
@@ -59,3 +59,6 @@ proto proto aliases proto virtual proto ADDRESS_REWRITING_README html
before the server greeting File smtpd smtpd c
global mail_params h smtpd smtpd c proto postconf proto
Typo fix by Trent W Buck Files proto postconf proto proto stop
+ smtp smtp c smtp smtp h smtp smtp_params c smtp smtp_proto c
+ smtp smtp_tls_policy c smtpd smtpd c smtpd smtpd_check c
+ tls tls h tls tls_client c tls tls_dane c tls tls_fprint c
diff --git a/postfix/proto/stop.spell-cc b/postfix/proto/stop.spell-cc
index 2bd163eb2..5af0b0e81 100644
--- a/postfix/proto/stop.spell-cc
+++ b/postfix/proto/stop.spell-cc
@@ -1803,3 +1803,9 @@ ipproto
cw
uncreate
MFLAGS
+CRED
+RPK
+RPKs
+SPKI
+peerpkey
+rpk
diff --git a/postfix/src/global/mail_params.h b/postfix/src/global/mail_params.h
index c8101c530..028b6a2c6 100644
--- a/postfix/src/global/mail_params.h
+++ b/postfix/src/global/mail_params.h
@@ -1321,6 +1321,10 @@ extern bool var_smtpd_tls_ask_ccert;
#define DEF_SMTPD_TLS_RCERT 0
extern bool var_smtpd_tls_req_ccert;
+#define VAR_SMTPD_TLS_ENABLE_RPK "smtpd_tls_enable_rpk"
+#define DEF_SMTPD_TLS_ENABLE_RPK 0
+extern bool var_smtpd_tls_enable_rpk;
+
#define VAR_SMTPD_TLS_CCERT_VD "smtpd_tls_ccert_verifydepth"
#define DEF_SMTPD_TLS_CCERT_VD 9
extern int var_smtpd_tls_ccert_vd;
@@ -1555,6 +1559,12 @@ extern char *var_smtp_tls_mand_excl;
"{md5} : {sha256}}"
extern char *var_smtp_tls_fpt_dgst;
+#define VAR_SMTP_TLS_ENABLE_RPK "smtp_tls_enable_rpk"
+#define DEF_SMTP_TLS_ENABLE_RPK 0
+#define VAR_LMTP_TLS_ENABLE_RPK "lmtp_tls_enable_rpk"
+#define DEF_LMTP_TLS_ENABLE_RPK 0
+extern bool var_smtp_tls_enable_rpk;
+
#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"
@@ -3982,6 +3992,10 @@ extern bool var_tlsp_tls_ask_ccert;
#define DEF_TLSP_TLS_RCERT "$" VAR_SMTPD_TLS_RCERT
extern bool var_tlsp_tls_req_ccert;
+#define VAR_TLSP_TLS_ENABLE_RPK "tlsproxy_tls_enable_rpk"
+#define DEF_TLSP_TLS_ENABLE_RPK "$" VAR_SMTPD_TLS_ENABLE_RPK
+extern bool var_tlsp_tls_enable_rpk;
+
#define VAR_TLSP_TLS_CCERT_VD "tlsproxy_tls_ccert_verifydepth"
#define DEF_TLSP_TLS_CCERT_VD "$" VAR_SMTPD_TLS_CCERT_VD
extern int var_tlsp_tls_ccert_vd;
diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h
index 25c36229f..de4dce043 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 "20230723"
+#define MAIL_RELEASE_DATE "20230807"
#define MAIL_VERSION_NUMBER "3.9"
#ifdef SNAPSHOT
diff --git a/postfix/src/posttls-finger/posttls-finger.c b/postfix/src/posttls-finger/posttls-finger.c
index b9a46999d..29c17e396 100644
--- a/postfix/src/posttls-finger/posttls-finger.c
+++ b/postfix/src/posttls-finger/posttls-finger.c
@@ -103,7 +103,7 @@
/* in the DNS). In Postfix versions prior to 3.6, the default value
/* was "md5".
/* .IP "\fB-f\fR"
-/* Lookup the associated DANE TLSA RRset even when a hostname is not an
+/* Look up the associated DANE TLSA RRset even when a hostname is not an
/* alias and its address records lie in an unsigned zone. See
/* smtp_tls_force_insecure_host_tlsa_lookup for details.
/* .IP "\fB-F \fICAfile.pem\fR (default: none)"
@@ -264,6 +264,15 @@
/* the SMTP-in-SSL protocol, rather than the STARTTLS protocol.
/* The destination \fIdomain\fR:\fIport\fR must of course provide such
/* a service.
+/* .IP "\fB-x\fR"
+/* Prefer RFC7250 non-X.509 raw public key (RPK) server credentials. By
+/* default only X.509 certificates are accepted. This is analogous to
+/* setting \fBsmtp_tls_enable_rpk = yes\fR in the smtp(8) client. At the
+/* fingerprint security level, when raw public keys are enabled, only
+/* public key (and not certificate) fingerprints will be compared against
+/* the specified list of \fImatch\fR arguments. Certificate fingerprints
+/* are fragile when raw public keys are solicited, the server may at some
+/* point in time start returning only the public key.
/* .IP "\fB-X\fR"
/* Enable \fBtlsproxy\fR(8) mode. This is an unsupported mode,
/* for program development only.
@@ -441,6 +450,9 @@ typedef struct OPTIONS {
ARGV *tas;
char *host_lookup;
char *addr_pref;
+#ifdef USE_TLS
+ int enable_rpk;
+#endif
} OPTIONS;
/*
@@ -696,7 +708,7 @@ static void print_stack(STATE *state, x509_stack_t *sk, int trustout)
BIO_printf(state->tls_bio, " cert digest=%s\n", digest);
myfree(digest);
- digest = tls_pkey_fprint(cert, state->mdalg);
+ digest = tls_pkey_fprint(X509_get0_pubkey(cert), state->mdalg);
BIO_printf(state->tls_bio, " pkey digest=%s\n", digest);
myfree(digest);
@@ -809,6 +821,7 @@ static int starttls(STATE *state)
mdalg = state->mdalg);
TLS_PROXY_CLIENT_START_PROPS(&start_props,
timeout = smtp_tmout,
+ enable_rpk = state->options.enable_rpk,
tls_level = state->level,
nexthop = state->nexthop,
host = state->hostname,
@@ -886,13 +899,19 @@ static int starttls(STATE *state)
state->tls_context = tls_proxy_context_receive(state->stream);
if (state->tls_context) {
if (state->log_mask &
- (TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE | TLS_LOG_PEERCERT))
- msg_info("%s: subject_CN=%s, issuer_CN=%s, "
- "fingerprint=%s, pkey_fingerprint=%s",
- state->namaddrport, state->tls_context->peer_CN,
- state->tls_context->issuer_CN,
- state->tls_context->peer_cert_fprint,
- state->tls_context->peer_pkey_fprint);
+ (TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE | TLS_LOG_PEERCERT)) {
+ if (state->tls_context->stoc_rpk)
+ msg_info("%s: pkey_fingerprint=%s", state->namaddrport,
+ state->tls_context->peer_pkey_fprint);
+ else
+ msg_info("%s: subject_CN=%s, issuer_CN=%s, "
+ "fingerprint=%s, pkey_fingerprint=%s",
+ state->namaddrport,
+ state->tls_context->peer_CN,
+ state->tls_context->issuer_CN,
+ state->tls_context->peer_cert_fprint,
+ state->tls_context->peer_pkey_fprint);
+ }
tls_log_summary(TLS_ROLE_CLIENT, TLS_USAGE_NEW,
state->tls_context);
} else {
@@ -906,6 +925,7 @@ static int starttls(STATE *state)
stream = stream,
fd = -1,
timeout = smtp_tmout,
+ enable_rpk = state->options.enable_rpk,
tls_level = state->level,
nexthop = state->nexthop,
host = state->hostname,
@@ -938,7 +958,7 @@ static int starttls(STATE *state)
if (state->pass == 1) {
ehlo(state);
- if (!TLS_CERT_IS_PRESENT(state->tls_context))
+ if (!TLS_CRED_IS_PRESENT(state->tls_context))
msg_info("Server is anonymous");
else if (state->tlsproxy_mode == 0) {
if (state->print_trust)
@@ -1829,14 +1849,14 @@ static void usage(void)
#ifdef USE_TLS
fprintf(stderr, "usage: %s %s \\\n\t%s \\\n\t%s \\\n\t%s \\\n\t%s"
" destination [match ...]\n", var_procname,
- "[-acCfSvw] [-t conn_tmout] [-T cmd_tmout] [-L logopts]",
+ "[-acCfRSvwx] [-t conn_tmout] [-T cmd_tmout] [-L logopts]",
"[-h host_lookup] [-l level] [-d mdalg] [-g grade] [-p protocols]",
"[-A tafile] [-F CAfile.pem] [-P CApath/] [-s servername]",
"[ [-H chainfiles] | [-k certfile [-K keyfile]] ]",
"[-m count] [-r delay] [-o name=value]");
#else
- fprintf(stderr, "usage: %s [-acStTv] [-h host_lookup] [-o name=value] destination\n",
- var_procname);
+ fprintf(stderr, "usage: %s [-acRStTv] [-h host_lookup] [-o name=value]"
+ " destination\n", var_procname);
#endif
exit(1);
}
@@ -1906,7 +1926,7 @@ static void parse_options(STATE *state, int argc, char *argv[])
#define OPTS "a:ch:o:RSt:T:v"
#ifdef USE_TLS
-#define TLSOPTS "A:Cd:fF:g:H:k:K:l:L:m:M:p:P:r:s:wX"
+#define TLSOPTS "A:Cd:fF:g:H:k:K:l:L:m:M:p:P:r:s:wxX"
state->mdalg = 0;
state->CApath = mystrdup("");
@@ -1917,6 +1937,7 @@ static void parse_options(STATE *state, int argc, char *argv[])
state->sni = mystrdup("");
state->options.tas = argv_alloc(1);
state->options.logopts = 0;
+ state->options.enable_rpk = 0;
state->level = TLS_LEV_DANE;
state->mxinsec_level = TLS_LEV_DANE;
state->tlsproxy_mode = 0;
@@ -2050,6 +2071,9 @@ static void parse_options(STATE *state, int argc, char *argv[])
case 'w':
state->wrapper_mode = 1;
break;
+ case 'x':
+ state->options.enable_rpk = 1;
+ break;
case 'X':
state->tlsproxy_mode = 1;
break;
@@ -2132,8 +2156,8 @@ static void parse_match(STATE *state, int argc, char *argv[])
case TLS_LEV_FPRINT:
state->dane = tls_dane_alloc();
while (*argv)
- tls_dane_add_fpt_digests((TLS_DANE *) state->dane, *argv++, "",
- smtp_mode);
+ tls_dane_add_fpt_digests(state->dane, state->options.enable_rpk,
+ *argv++, "", smtp_mode);
break;
case TLS_LEV_DANE:
case TLS_LEV_DANE_ONLY:
diff --git a/postfix/src/smtp/lmtp_params.c b/postfix/src/smtp/lmtp_params.c
index bca7cd494..ed567e6ec 100644
--- a/postfix/src/smtp/lmtp_params.c
+++ b/postfix/src/smtp/lmtp_params.c
@@ -120,6 +120,7 @@
VAR_LMTP_TLS_NOTEOFFER, DEF_LMTP_TLS_NOTEOFFER, &var_smtp_tls_note_starttls_offer,
VAR_LMTP_TLS_BLK_EARLY_MAIL_REPLY, DEF_LMTP_TLS_BLK_EARLY_MAIL_REPLY, &var_smtp_tls_blk_early_mail_reply,
VAR_LMTP_TLS_FORCE_TLSA, DEF_LMTP_TLS_FORCE_TLSA, &var_smtp_tls_force_tlsa,
+ VAR_LMTP_TLS_ENABLE_RPK, DEF_LMTP_TLS_ENABLE_RPK, &var_smtp_tls_enable_rpk,
#endif
VAR_LMTP_TLS_WRAPPER, DEF_LMTP_TLS_WRAPPER, &var_smtp_tls_wrappermode,
VAR_LMTP_SENDER_AUTH, DEF_LMTP_SENDER_AUTH, &var_smtp_sender_auth,
diff --git a/postfix/src/smtp/smtp.c b/postfix/src/smtp/smtp.c
index ba014919e..2f93a79e7 100644
--- a/postfix/src/smtp/smtp.c
+++ b/postfix/src/smtp/smtp.c
@@ -613,6 +613,11 @@
/* .IP "\fBtls_config_name (empty)\fR"
/* The application name passed by Postfix to OpenSSL library
/* initialization functions.
+/* .PP
+/* Available in Postfix version 3.9 and later:
+/* .IP "\fBsmtp_tls_enable_rpk (no)\fR"
+/* Request that remote SMTP servers send an RFC7250 raw public key
+/* instead of an X.509 certificate.
/* OBSOLETE STARTTLS CONTROLS
/* .ad
/* .fi
@@ -1090,6 +1095,7 @@ char *var_smtp_tls_sni;
bool var_smtp_tls_blk_early_mail_reply;
bool var_smtp_tls_force_tlsa;
char *var_smtp_tls_insecure_mx_policy;
+bool var_smtp_tls_enable_rpk;
#endif
diff --git a/postfix/src/smtp/smtp.h b/postfix/src/smtp/smtp.h
index 35d97da2a..29e8efbd7 100644
--- a/postfix/src/smtp/smtp.h
+++ b/postfix/src/smtp/smtp.h
@@ -107,6 +107,7 @@ typedef struct SMTP_TLS_POLICY {
TLS_DANE *dane; /* DANE TLSA digests */
char *sni; /* Optional SNI name when not DANE */
int conn_reuse; /* enable connection reuse */
+ int enable_rpk; /* Enable server->client RPK */
} SMTP_TLS_POLICY;
/*
@@ -142,6 +143,7 @@ extern void smtp_tls_policy_cache_flush(void);
_tls_policy_init_tmp->dane = 0; \
_tls_policy_init_tmp->sni = 0; \
_tls_policy_init_tmp->conn_reuse = 0; \
+ _tls_policy_init_tmp->enable_rpk = 0; \
} while (0)
#endif
diff --git a/postfix/src/smtp/smtp_params.c b/postfix/src/smtp/smtp_params.c
index 22f4709c1..b4fafbeb4 100644
--- a/postfix/src/smtp/smtp_params.c
+++ b/postfix/src/smtp/smtp_params.c
@@ -124,6 +124,7 @@
VAR_SMTP_TLS_NOTEOFFER, DEF_SMTP_TLS_NOTEOFFER, &var_smtp_tls_note_starttls_offer,
VAR_SMTP_TLS_BLK_EARLY_MAIL_REPLY, DEF_SMTP_TLS_BLK_EARLY_MAIL_REPLY, &var_smtp_tls_blk_early_mail_reply,
VAR_SMTP_TLS_FORCE_TLSA, DEF_SMTP_TLS_FORCE_TLSA, &var_smtp_tls_force_tlsa,
+ VAR_SMTP_TLS_ENABLE_RPK, DEF_SMTP_TLS_ENABLE_RPK, &var_smtp_tls_enable_rpk,
#endif
VAR_SMTP_TLS_WRAPPER, DEF_SMTP_TLS_WRAPPER, &var_smtp_tls_wrappermode,
VAR_SMTP_SENDER_AUTH, DEF_SMTP_SENDER_AUTH, &var_smtp_sender_auth,
diff --git a/postfix/src/smtp/smtp_proto.c b/postfix/src/smtp/smtp_proto.c
index 097d51842..e022bc2cf 100644
--- a/postfix/src/smtp/smtp_proto.c
+++ b/postfix/src/smtp/smtp_proto.c
@@ -929,6 +929,7 @@ static int smtp_start_tls(SMTP_STATE *state)
TLS_PROXY_CLIENT_START_PROPS(&start_props,
timeout = var_smtp_starttls_tmout,
tls_level = state->tls->level,
+ enable_rpk = state->tls->enable_rpk,
nexthop = session->tls_nexthop,
host = STR(iter->host),
namaddr = session->namaddrport,
@@ -1051,6 +1052,7 @@ static int smtp_start_tls(SMTP_STATE *state)
fd = -1,
timeout = var_smtp_starttls_tmout,
tls_level = state->tls->level,
+ enable_rpk = state->tls->enable_rpk,
nexthop = session->tls_nexthop,
host = STR(iter->host),
namaddr = session->namaddrport,
diff --git a/postfix/src/smtp/smtp_tls_policy.c b/postfix/src/smtp/smtp_tls_policy.c
index 92a231df5..f407d6579 100644
--- a/postfix/src/smtp/smtp_tls_policy.c
+++ b/postfix/src/smtp/smtp_tls_policy.c
@@ -334,9 +334,10 @@ static void tls_policy_lookup_one(SMTP_TLS_POLICY *tls, int *site_level,
INVALID_RETURN(tls->why, site_level);
break;
case TLS_LEV_FPRINT:
- if (!tls->dane)
- tls->dane = tls_dane_alloc();
- tls_dane_add_fpt_digests(tls->dane, val, "|", smtp_mode);
+ if (tls->matchargv == 0)
+ tls->matchargv = argv_split(val, "|");
+ else
+ argv_split_append(tls->matchargv, val, "|");
break;
case TLS_LEV_VERIFY:
case TLS_LEV_SECURE:
@@ -390,6 +391,19 @@ static void tls_policy_lookup_one(SMTP_TLS_POLICY *tls, int *site_level,
}
continue;
}
+ if (!strcasecmp(name, "enable_rpk")) {
+ /* Ultimately ignored at some security levels */
+ if (strcasecmp(val, "yes") == 0) {
+ tls->enable_rpk = 1;
+ } else if (strcasecmp(val, "no") == 0) {
+ tls->enable_rpk = 0;
+ } else {
+ msg_warn("%s: attribute \"%s\" has bad value: \"%s\"",
+ WHERE, name, val);
+ INVALID_RETURN(tls->why, site_level);
+ }
+ continue;
+ }
msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name);
INVALID_RETURN(tls->why, site_level);
}
@@ -518,6 +532,7 @@ static void *policy_create(const char *unused_key, void *context)
smtp_tls_policy_init(tls, dsb_create());
tls->conn_reuse = var_smtp_tls_conn_reuse;
+ tls->enable_rpk = var_smtp_tls_enable_rpk;
/*
* Compute the per-site TLS enforcement level. For compatibility with the
@@ -602,6 +617,13 @@ static void *policy_create(const char *unused_key, void *context)
*/
set_cipher_grade(tls);
+/*
+ * Even when soliciting raw public keys, synthesize TLSA RRs that also match
+ * certificates. Though this is fragile, it maintains compatibility with
+ * servers that never return RPKs.
+ */
+#define DONT_SUPPRESS_CERT_MATCH 0
+
/*
* Use main.cf cert_match setting if not set in per-destination table.
*/
@@ -617,16 +639,26 @@ static void *policy_create(const char *unused_key, void *context)
case TLS_LEV_FPRINT:
if (tls->dane == 0)
tls->dane = tls_dane_alloc();
- if (tls->dane->tlsa == 0) {
- tls_dane_add_fpt_digests(tls->dane, var_smtp_tls_fpt_cmatch,
- CHARS_COMMA_SP, smtp_mode);
- if (tls->dane->tlsa == 0) {
- msg_warn("nexthop domain %s: configured at fingerprint "
- "security level, but with no fingerprints to match.",
- dest);
- MARK_INVALID(tls->why, &tls->level);
- return ((void *) tls);
+ /* Process the specified fingerprint match patterns */
+ if (tls->matchargv) {
+ int i;
+
+ for (i = 0; i < tls->matchargv->argc; ++i) {
+ tls_dane_add_fpt_digests(tls->dane, DONT_SUPPRESS_CERT_MATCH,
+ tls->matchargv->argv[i], "",
+ smtp_mode);
}
+ } else {
+ tls_dane_add_fpt_digests(tls->dane, DONT_SUPPRESS_CERT_MATCH,
+ var_smtp_tls_fpt_cmatch, CHARS_COMMA_SP,
+ smtp_mode);
+ }
+ if (tls->dane->tlsa == 0) {
+ msg_warn("nexthop domain %s: configured at fingerprint "
+ "security level, but with no fingerprints to match.",
+ dest);
+ MARK_INVALID(tls->why, &tls->level);
+ return ((void *) tls);
}
break;
case TLS_LEV_VERIFY:
diff --git a/postfix/src/smtpd/smtpd.c b/postfix/src/smtpd/smtpd.c
index 06dd86d99..f6ade16e3 100644
--- a/postfix/src/smtpd/smtpd.c
+++ b/postfix/src/smtpd/smtpd.c
@@ -537,6 +537,12 @@
/* .IP "\fBtls_config_name (empty)\fR"
/* The application name passed by Postfix to OpenSSL library
/* initialization functions.
+/* .PP
+/* Available in Postfix version 3.9 and later:
+/* .IP "\fBsmtpd_tls_enable_rpk (no)\fR"
+/* Request that remote SMTP clients send an RFC7250 raw public key
+/* instead of an X.509 certificate, when asking for or requiring client
+/* authentication.
/* OBSOLETE STARTTLS CONTROLS
/* .ad
/* .fi
@@ -1479,6 +1485,7 @@ char *var_smtpd_tls_eecdh;
char *var_smtpd_tls_eccert_file;
char *var_smtpd_tls_eckey_file;
char *var_smtpd_tls_chain_files;
+int var_smtpd_tls_enable_rpk;
#endif
@@ -3494,6 +3501,11 @@ static void common_pre_message_handling(SMTPD_STATE *state,
"verified OK" : "not verified");
vstring_free(issuer_CN);
vstring_free(peer_CN);
+ } else if (TLS_RPK_IS_PRESENT(state->tls_context)) {
+ out_fprintf(out_stream, REC_TYPE_NORM,
+ "\t(Client RPK %s digest %s)",
+ var_smtpd_tls_fpt_dgst,
+ state->tls_context->peer_pkey_fprint);
} else if (var_smtpd_tls_ask_ccert)
out_fprintf(out_stream, REC_TYPE_NORM,
"\t(Client did not present a certificate)");
@@ -5143,6 +5155,7 @@ static void smtpd_start_tls(SMTPD_STATE *state)
stream = state->client,
fd = -1,
timeout = var_smtpd_starttls_tmout,
+ enable_rpk = var_smtpd_tls_enable_rpk,
requirecert = requirecert,
serverid = state->service,
namaddr = state->namaddr,
@@ -6292,7 +6305,6 @@ static void pre_jail_init(char *unused_name, char **unused_argv)
no_server_cert_ok = 0;
cert_file = var_smtpd_tls_cert_file;
}
-
have_server_cert = *cert_file != 0;
have_server_cert |= *var_smtpd_tls_eccert_file != 0;
have_server_cert |= *var_smtpd_tls_dcert_file != 0;
@@ -6541,6 +6553,7 @@ int main(int argc, char **argv)
#ifdef USE_TLS
VAR_SMTPD_TLS_ACERT, DEF_SMTPD_TLS_ACERT, &var_smtpd_tls_ask_ccert,
VAR_SMTPD_TLS_RCERT, DEF_SMTPD_TLS_RCERT, &var_smtpd_tls_req_ccert,
+ VAR_SMTPD_TLS_ENABLE_RPK, DEF_SMTPD_TLS_ENABLE_RPK, &var_smtpd_tls_enable_rpk,
VAR_SMTPD_TLS_RECHEAD, DEF_SMTPD_TLS_RECHEAD, &var_smtpd_tls_received_header,
VAR_SMTPD_TLS_SET_SESSID, DEF_SMTPD_TLS_SET_SESSID, &var_smtpd_tls_set_sessid,
#endif
diff --git a/postfix/src/smtpd/smtpd_check.c b/postfix/src/smtpd/smtpd_check.c
index b5ce4a94f..fd63f6813 100644
--- a/postfix/src/smtpd/smtpd_check.c
+++ b/postfix/src/smtpd/smtpd_check.c
@@ -1588,6 +1588,7 @@ static int permit_auth_destination(SMTPD_STATE *state, char *recipient);
static int permit_tls_clientcerts(SMTPD_STATE *state, int permit_all_certs)
{
#ifdef USE_TLS
+ const char *myname = "permit_tls_clientcerts";
const char *found = 0;
if (!state->tls_context)
@@ -1602,9 +1603,9 @@ static int permit_tls_clientcerts(SMTPD_STATE *state, int permit_all_certs)
/*
* When directly checking the fingerprint, it is OK if the issuing CA is
- * not trusted.
+ * not trusted. Raw public keys are also acceptable.
*/
- if (TLS_CERT_IS_PRESENT(state->tls_context)) {
+ if (TLS_CRED_IS_PRESENT(state->tls_context)) {
int i;
char *prints[2];
@@ -1613,12 +1614,24 @@ static int permit_tls_clientcerts(SMTPD_STATE *state, int permit_all_certs)
VAR_SMTPD_TLS_FPT_DGST "=md5 to compute certificate "
"fingerprints");
- prints[0] = state->tls_context->peer_cert_fprint;
- prints[1] = state->tls_context->peer_pkey_fprint;
+ prints[0] = state->tls_context->peer_pkey_fprint;
+ prints[1] = state->tls_context->peer_cert_fprint;
/* After lookup error, leave relay_ccerts->error at non-zero value. */
for (i = 0; i < 2; ++i) {
+ /* With RFC7250 RPK, no certificate may be available */
+ if (!*prints[i])
+ continue;
found = maps_find(relay_ccerts, prints[i], DICT_FLAG_NONE);
+ if (var_smtpd_tls_enable_rpk && i > 0 && found) {
+ msg_warn("%s: %s: %s: Fragile access policy: %s=yes, but"
+ " public key fingerprint \"%s\" not matched, while"
+ " certificate fingerprint \"%s\" matched",
+ myname, state->namaddr, relay_ccerts->title,
+ VAR_SMTPD_TLS_ENABLE_RPK,
+ state->tls_context->peer_cert_fprint,
+ state->tls_context->peer_pkey_fprint);
+ }
if (found != 0) {
if (msg_verbose)
msg_info("Relaying allowed for certified client: %s", found);
@@ -3165,6 +3178,9 @@ static int check_ccert_access(SMTPD_STATE *state, const char *acl_spec,
#ifdef USE_TLS
const char *myname = "check_ccert_access";
+ int cert_result = SMTPD_CHECK_DUNNO;
+ int pkey_result = SMTPD_CHECK_DUNNO;
+ int *respt;
int found;
const MAP_SEARCH *acl;
const char default_search[] = {
@@ -3191,9 +3207,9 @@ static int check_ccert_access(SMTPD_STATE *state, const char *acl_spec,
/*
* When directly checking the fingerprint, it is OK if the issuing CA is
- * not trusted.
+ * not trusted. Raw public keys are also acceptable.
*/
- if (TLS_CERT_IS_PRESENT(state->tls_context)) {
+ if (TLS_CRED_IS_PRESENT(state->tls_context)) {
const char *action;
const char *match_this;
const char *known_action;
@@ -3202,17 +3218,19 @@ static int check_ccert_access(SMTPD_STATE *state, const char *acl_spec,
switch (*action) {
case SMTPD_ACL_SEARCH_CODE_CERT_FPRINT:
match_this = state->tls_context->peer_cert_fprint;
- if (warn_compat_break_smtpd_tls_fpt_dgst)
+ if (*match_this && warn_compat_break_smtpd_tls_fpt_dgst)
msg_info("using backwards-compatible default setting "
VAR_SMTPD_TLS_FPT_DGST "=md5 to compute "
"certificate fingerprints");
+ respt = &cert_result;
break;
case SMTPD_ACL_SEARCH_CODE_PKEY_FPRINT:
match_this = state->tls_context->peer_pkey_fprint;
- if (warn_compat_break_smtpd_tls_fpt_dgst)
+ if (*match_this && warn_compat_break_smtpd_tls_fpt_dgst)
msg_info("using backwards-compatible default setting "
VAR_SMTPD_TLS_FPT_DGST "=md5 to compute "
- "certificate fingerprints");
+ "public key fingerprints");
+ respt = &pkey_result;
break;
default:
known_action = str_name_code(search_actions, *action);
@@ -3225,6 +3243,9 @@ static int check_ccert_access(SMTPD_STATE *state, const char *acl_spec,
451, "4.3.5",
"Server configuration error"));
}
+ /* With RFC7250 RPK, no certificate may be available */
+ if (!*match_this)
+ continue;
if (msg_verbose)
msg_info("%s: look up %s %s",
myname, str_name_code(search_actions, *action),
@@ -3237,11 +3258,16 @@ static int check_ccert_access(SMTPD_STATE *state, const char *acl_spec,
* "reject" event. XXX Should log the thing that is rejected
* (fingerprint etc.) or would that give away too much?
*/
- result = check_access(state, acl->map_type_name, match_this,
+ *respt = check_access(state, acl->map_type_name, match_this,
DICT_FLAG_NONE, &found,
state->tls_context->peer_CN,
SMTPD_NAME_CCERT, def_acl);
- if (result != SMTPD_CHECK_DUNNO)
+ if (*respt == SMTPD_CHECK_DUNNO)
+ continue;
+ if (result == SMTPD_CHECK_DUNNO)
+ result = *respt;
+ if (!var_smtpd_tls_enable_rpk
+ || *action == SMTPD_ACL_SEARCH_CODE_PKEY_FPRINT)
break;
}
} else if (!var_smtpd_tls_ask_ccert) {
@@ -3251,6 +3277,17 @@ static int check_ccert_access(SMTPD_STATE *state, const char *acl_spec,
if (msg_verbose)
msg_info("%s: no client certificate", myname);
}
+ if (var_smtpd_tls_enable_rpk
+ && cert_result != SMTPD_CHECK_DUNNO
+ && cert_result != pkey_result) {
+ msg_warn("%s: %s: %s: Fragile access policy: %s=yes, but"
+ " the action for certificate fingerprint \"%s\" !="
+ " the action for public key fingerprint \"%s\"",
+ myname, state->namaddr, acl->map_type_name,
+ VAR_SMTPD_TLS_ENABLE_RPK,
+ state->tls_context->peer_cert_fprint,
+ state->tls_context->peer_pkey_fprint);
+ }
#endif
return (result);
}
@@ -4015,6 +4052,7 @@ static int check_policy_service(SMTPD_STATE *state, const char *server,
ENCODE_CN(subject, subject_buf, state->tls_context->peer_CN);
ENCODE_CN(issuer, issuer_buf, state->tls_context->issuer_CN);
+#define NONEMPTY(x) ((x) != 0 && (*x) != 0)
/*
* XXX: Too noisy to warn for each policy lookup, especially because we
* don't even know whether the policy server will use the fingerprint. So
@@ -4024,12 +4062,12 @@ static int check_policy_service(SMTPD_STATE *state, const char *server,
if (!warned
&& warn_compat_break_smtpd_tls_fpt_dgst
&& state->tls_context
- && state->tls_context->peer_cert_fprint
- && *state->tls_context->peer_cert_fprint) {
+ && (NONEMPTY(state->tls_context->peer_cert_fprint)
+ || NONEMPTY(state->tls_context->peer_pkey_fprint))) {
warned = 1;
msg_info("using backwards-compatible default setting "
VAR_SMTPD_TLS_FPT_DGST "=md5 to compute certificate "
- "fingerprints");
+ "and public key fingerprints");
}
#endif
@@ -6386,7 +6424,7 @@ int main(int argc, char **argv)
state.tls_context->peer_cert_fprint =
state.tls_context->peer_pkey_fprint = 0;
}
- state.tls_context->peer_status |= TLS_CERT_FLAG_PRESENT;
+ state.tls_context->peer_status |= TLS_CRED_FLAG_CERT;
UPDATE_STRING(state.tls_context->peer_cert_fprint,
args->argv[1]);
state.tls_context->peer_pkey_fprint =
diff --git a/postfix/src/tls/tls.h b/postfix/src/tls/tls.h
index 73eebaeda..3ec41ba36 100644
--- a/postfix/src/tls/tls.h
+++ b/postfix/src/tls/tls.h
@@ -78,6 +78,7 @@ extern const char *str_tls_level(int);
#include /* OPENSSL_VERSION_NUMBER */
#include
#include
+#include /* TLS extensions */
/* Appease indent(1) */
#define x509_stack_t STACK_OF(X509)
@@ -203,8 +204,8 @@ extern void tls_dane_flush(void);
extern TLS_DANE *tls_dane_alloc(void);
extern void tls_tlsa_free(TLS_TLSA *);
extern void tls_dane_free(TLS_DANE *);
-extern void tls_dane_add_fpt_digests(TLS_DANE *, const char *, const char *,
- int);
+extern void tls_dane_add_fpt_digests(TLS_DANE *, int, const char *,
+ const char *, int);
extern TLS_DANE *tls_dane_resolve(unsigned, const char *, DNS_RR *, int);
extern int tls_dane_load_trustfile(TLS_DANE *, const char *);
@@ -232,6 +233,8 @@ typedef struct {
const char *kex_name; /* shared key-exchange algorithm */
const char *kex_curve; /* shared key-exchange ECDHE curve */
int kex_bits; /* shared FFDHE key exchange bits */
+ int ctos_rpk; /* Did the client send an RPK? */
+ int stoc_rpk; /* Did the server send an RPK? */
const char *clnt_sig_name; /* client's signature key algorithm */
const char *clnt_sig_curve; /* client's ECDSA curve name */
int clnt_sig_bits; /* client's RSA signature key bits */
@@ -264,13 +267,17 @@ typedef struct {
* Peer status bits. TLS_CERT_FLAG_MATCHED implies TLS_CERT_FLAG_TRUSTED
* only in the case of a hostname match.
*/
-#define TLS_CERT_FLAG_PRESENT (1<<0)
+#define TLS_CRED_FLAG_CERT (1<<0)
#define TLS_CERT_FLAG_ALTNAME (1<<1)
#define TLS_CERT_FLAG_TRUSTED (1<<2)
#define TLS_CERT_FLAG_MATCHED (1<<3)
#define TLS_CERT_FLAG_SECURED (1<<4)
+#define TLS_CRED_FLAG_RPK (1<<5)
+#define TLS_CRED_FLAG_ANY (TLS_CRED_FLAG_CERT|TLS_CRED_FLAG_RPK)
-#define TLS_CERT_IS_PRESENT(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_PRESENT))
+#define TLS_CRED_IS_PRESENT(c) ((c) && ((c)->peer_status&TLS_CRED_FLAG_ANY))
+#define TLS_CERT_IS_PRESENT(c) ((c) && ((c)->peer_status&TLS_CRED_FLAG_CERT))
+#define TLS_RPK_IS_PRESENT(c) ((c) && ((c)->peer_status&TLS_CRED_FLAG_RPK))
#define TLS_CERT_IS_ALTNAME(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_ALTNAME))
#define TLS_CERT_IS_TRUSTED(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_TRUSTED))
#define TLS_CERT_IS_MATCHED(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_MATCHED))
@@ -472,6 +479,7 @@ typedef struct {
VSTREAM *stream;
int fd; /* Event-driven file descriptor */
int timeout;
+ int enable_rpk; /* Solicit server raw public keys */
int tls_level; /* Security level */
const char *nexthop; /* destination domain */
const char *host; /* MX hostname */
@@ -508,12 +516,12 @@ extern TLS_SESS_STATE *tls_client_post_connect(TLS_SESS_STATE *,
a6, a7, a8, a9, a10, a11, a12, a13, a14))
#define TLS_CLIENT_START(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, \
- a10, a11, a12, a13, a14, a15, a16, a17) \
+ a10, a11, a12, a13, a14, a15, a16, a17, a18) \
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)->a15), \
- ((props)->a16), ((props)->a17), (props)))
+ ((props)->a16), ((props)->a17), ((props)->a18), (props)))
/*
* tls_server.c
@@ -546,6 +554,7 @@ typedef struct {
VSTREAM *stream; /* Client stream */
int fd; /* Event-driven file descriptor */
int timeout; /* TLS handshake timeout */
+ int enable_rpk; /* Solicit client raw public keys */
int requirecert; /* Insist on client cert? */
const char *serverid; /* Server instance (salt cache key) */
const char *namaddr; /* Client nam[addr] for logging */
@@ -570,10 +579,12 @@ extern TLS_SESS_STATE *tls_server_post_accept(TLS_SESS_STATE *);
((props)->a16), ((props)->a17), ((props)->a18), ((props)->a19), \
((props)->a20), (props)))
-#define TLS_SERVER_START(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) \
+#define TLS_SERVER_START(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, \
+ a10, a11) \
tls_server_start((((props)->a1), ((props)->a2), ((props)->a3), \
((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \
- ((props)->a8), ((props)->a9), ((props)->a10), (props)))
+ ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \
+ (props)))
/*
* tls_session.c
@@ -660,7 +671,7 @@ extern TLS_TLSA *tlsa_prepend(TLS_TLSA *, uint8_t, uint8_t, uint8_t,
extern const EVP_MD *tls_digest_byname(const char *, EVP_MD_CTX **);
extern char *tls_digest_encode(const unsigned char *, int);
extern char *tls_cert_fprint(X509 *, const char *);
-extern char *tls_pkey_fprint(X509 *, const char *);
+extern char *tls_pkey_fprint(EVP_PKEY *, const char *);
extern char *tls_serverid_digest(TLS_SESS_STATE *,
const TLS_CLIENT_START_PROPS *, const char *);
@@ -696,6 +707,8 @@ extern long tls_bio_dump_cb(BIO *, int, const char *, int, long, long);
#endif
extern const EVP_MD *tls_validate_digest(const char *);
+extern void tls_enable_client_rpk(SSL_CTX *, SSL *);
+extern void tls_enable_server_rpk(SSL_CTX *, SSL *);
/*
* tls_seed.c
diff --git a/postfix/src/tls/tls_client.c b/postfix/src/tls/tls_client.c
index e0dfe15e9..3eda859cc 100644
--- a/postfix/src/tls/tls_client.c
+++ b/postfix/src/tls/tls_client.c
@@ -86,8 +86,9 @@
/* available as:
/* .IP TLScontext->peer_status
/* A bitmask field that records the status of the peer certificate
-/* verification. This consists of one or more of TLS_CERT_FLAG_PRESENT,
-/* TLS_CERT_FLAG_TRUSTED, TLS_CERT_FLAG_MATCHED and TLS_CERT_FLAG_SECURED.
+/* verification. This consists of one or more of TLS_CRED_FLAG_CERT,
+/* TLS_CRED_FLAG_RPK, TLS_CERT_FLAG_TRUSTED, TLS_CERT_FLAG_MATCHED and
+/* TLS_CERT_FLAG_SECURED.
/* .IP TLScontext->peer_CN
/* Extracted CommonName of the peer, or zero-length string if the
/* information could not be extracted.
@@ -303,15 +304,11 @@ static void uncache_session(SSL_CTX *ctx, TLS_SESS_STATE *TLScontext)
tls_mgr_delete(TLScontext->cache_type, TLScontext->serverid);
}
-/* verify_extract_name - verify peer name and extract peer information */
+/* verify_x509 - process X.509 certificate verification status */
-static void verify_extract_name(TLS_SESS_STATE *TLScontext, X509 *peercert,
- const TLS_CLIENT_START_PROPS *props)
+static void verify_x509(TLS_SESS_STATE *TLScontext, X509 *peercert,
+ const TLS_CLIENT_START_PROPS *props)
{
- int verbose;
-
- verbose = TLScontext->log_mask &
- (TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE | TLS_LOG_PEERCERT);
/*
* On exit both peer_CN and issuer_CN should be set.
@@ -345,7 +342,8 @@ static void verify_extract_name(TLS_SESS_STATE *TLScontext, X509 *peercert,
TLScontext->peer_status |= TLS_CERT_FLAG_SECURED;
TLScontext->peer_status |= TLS_CERT_FLAG_MATCHED;
- if (verbose) {
+ if (TLScontext->log_mask &
+ (TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE | TLS_LOG_PEERCERT)) {
const char *peername = SSL_get0_peername(TLScontext->con);
if (peername)
@@ -356,6 +354,50 @@ static void verify_extract_name(TLS_SESS_STATE *TLScontext, X509 *peercert,
}
}
+ /*
+ * Give them a clue. Problems with trust chain verification are logged
+ * when the session is first negotiated, before the session is stored
+ * into the cache. We don't want mystery failures, so log the fact the
+ * real problem is to be found in the past.
+ */
+ if (!TLS_CERT_IS_MATCHED(TLScontext)
+ && (TLScontext->log_mask & TLS_LOG_UNTRUSTED)) {
+ if (TLScontext->session_reused == 0)
+ tls_log_verify_error(TLScontext);
+ else
+ msg_info("%s: re-using session with untrusted peer credential, "
+ "look for details earlier in the log", props->namaddr);
+ }
+}
+
+/* verify_rpk - process RFC7250 raw public key verification status */
+
+static void verify_rpk(TLS_SESS_STATE *TLScontext, EVP_PKEY *peerpkey,
+ const TLS_CLIENT_START_PROPS *props)
+{
+ /* Was the raw public key (type of cert) matched? */
+ if (SSL_get_verify_result(TLScontext->con) == X509_V_OK) {
+ TLScontext->peer_status |= TLS_CERT_FLAG_TRUSTED;
+ if (TLScontext->must_fail) {
+ msg_panic("%s: raw public key valid despite trust init failure",
+ TLScontext->namaddr);
+ } else if (TLS_MUST_MATCH(TLScontext->level)) {
+
+ /*
+ * Fully secured only if not insecure like half-dane. We use
+ * TLS_CERT_FLAG_MATCHED to satisfy policy, but
+ * TLS_CERT_FLAG_SECURED to log the effective security.
+ */
+ if (!TLS_NEVER_SECURED(TLScontext->level))
+ TLScontext->peer_status |= TLS_CERT_FLAG_SECURED;
+ TLScontext->peer_status |= TLS_CERT_FLAG_MATCHED;
+
+ if (TLScontext->log_mask &
+ (TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE | TLS_LOG_PEERCERT))
+ tls_dane_log(TLScontext);
+ }
+ }
+
/*
* Give them a clue. Problems with trust chain verification are logged
* when the session is first negotiated, before the session is stored
@@ -792,6 +834,30 @@ TLS_APPL_STATE *tls_client_init(const TLS_CLIENT_INIT_PROPS *props)
return (0);
}
+ /*
+ * Enable support for client->server raw public keys, provided we actually
+ * have keys to send. They'll only be used if the server also enables
+ * client RPKs.
+ *
+ * XXX: When the server requests client auth, the TLS 1.2 protocol does not
+ * provide an unambiguous mechanism for the client to not send an RPK (as
+ * it can with client X.509 certs or TLS 1.3). This is why we don't just
+ * enable client RPK also with no keys in hand.
+ *
+ * A very unlikely scenario is that the server allows clients to not send
+ * keys, but only accepts keys for a set of algorithms we don't have. Then
+ * we still can't send a key, but have agreed to RPK. OpenSSL will attempt
+ * to send an empty RPK even with TLS 1.2 (and will accept such a message),
+ * but other implementations may be more strict.
+ *
+ * We could limit client RPK support to connections that support only TLS
+ * 1.3 and up, but that's practical only decades in the future, and the
+ * risk scenario is contrived and very unlikely.
+ */
+ if (SSL_CTX_get0_certificate(client_ctx) != NULL &&
+ SSL_CTX_get0_privatekey(client_ctx) != NULL)
+ tls_enable_client_rpk(client_ctx, NULL);
+
/*
* With OpenSSL 1.0.2 and later the client EECDH curve list becomes
* configurable with the preferred curve negotiated via the supported
@@ -1007,6 +1073,24 @@ TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *props)
return (0);
}
+ /*
+ * Possibly enable RFC7250 raw public keys in non-DANE/non-PKI levels
+ * when the fingerprint mask includes only public keys. For "may" and
+ * "encrypt" this is a heuristic, since we don't use the fingerprints
+ * beyond reporting them in verbose logging. If you always want certs
+ * with "may" and "encrypt" you'll have to tolerate them with
+ * "fingerprint", or use a separate transport.
+ */
+ switch (props->tls_level) {
+ case TLS_LEV_MAY:
+ case TLS_LEV_ENCRYPT:
+ case TLS_LEV_FPRINT:
+ if (props->enable_rpk)
+ tls_enable_server_rpk(NULL, TLScontext->con);
+ default:
+ break;
+ }
+
/*
* Try to convey the configured TLSA records for this connection to the
* OpenSSL library. If none are "usable", we'll fall back to "encrypt"
@@ -1175,6 +1259,7 @@ TLS_SESS_STATE *tls_client_post_connect(TLS_SESS_STATE *TLScontext,
{
const SSL_CIPHER *cipher;
X509 *peercert;
+ EVP_PKEY *peerpkey = 0;
/* Turn off packet dump if only dumping the handshake */
if ((TLScontext->log_mask & TLS_LOG_ALLPKTS) == 0)
@@ -1192,31 +1277,61 @@ TLS_SESS_STATE *tls_client_post_connect(TLS_SESS_STATE *TLScontext,
* Do peername verification if requested and extract useful information
* from the certificate for later use.
*/
- if ((peercert = TLS_PEEK_PEER_CERT(TLScontext->con)) != 0) {
- TLScontext->peer_status |= TLS_CERT_FLAG_PRESENT;
+ peercert = TLS_PEEK_PEER_CERT(TLScontext->con);
+ if (peercert != 0) {
+ peerpkey = X509_get0_pubkey(peercert);
+ }
+#if OPENSSL_VERSION_PREREQ(3,2)
+ else {
+ peerpkey = SSL_get0_peer_rpk(TLScontext->con);
+ }
+#endif
+
+ if (peercert != 0) {
+ TLScontext->peer_status |= TLS_CRED_FLAG_CERT;
/*
* Peer name or fingerprint verification as requested.
* Unconditionally set peer_CN, issuer_CN and peer_cert_fprint. Check
* fingerprint first, and avoid logging verified as untrusted in the
- * call to verify_extract_name().
+ * call to verify_x509().
*/
- TLScontext->peer_cert_fprint = tls_cert_fprint(peercert, props->mdalg);
- TLScontext->peer_pkey_fprint = tls_pkey_fprint(peercert, props->mdalg);
- verify_extract_name(TLScontext, peercert, props);
+ TLScontext->peer_cert_fprint =
+ tls_cert_fprint(peercert, props->mdalg);
+ TLScontext->peer_pkey_fprint =
+ tls_pkey_fprint(peerpkey, props->mdalg);
+ verify_x509(TLScontext, peercert, props);
if (TLScontext->log_mask &
(TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE | TLS_LOG_PEERCERT))
- msg_info("%s: subject_CN=%s, issuer_CN=%s, "
- "fingerprint=%s, pkey_fingerprint=%s", props->namaddr,
+ msg_info("%s: subject_CN=%s, issuer=%s%s%s%s%s",
+ TLScontext->namaddr,
TLScontext->peer_CN, TLScontext->issuer_CN,
- TLScontext->peer_cert_fprint,
- TLScontext->peer_pkey_fprint);
+ *TLScontext->peer_cert_fprint ?
+ ", cert fingerprint=" : "",
+ *TLScontext->peer_cert_fprint ?
+ TLScontext->peer_cert_fprint : "",
+ *TLScontext->peer_pkey_fprint ?
+ ", pkey fingerprint=" : "",
+ *TLScontext->peer_pkey_fprint ?
+ TLScontext->peer_pkey_fprint : "");
} else {
TLScontext->issuer_CN = mystrdup("");
TLScontext->peer_CN = mystrdup("");
TLScontext->peer_cert_fprint = mystrdup("");
- TLScontext->peer_pkey_fprint = mystrdup("");
+
+ if (!peerpkey) {
+ TLScontext->peer_pkey_fprint = mystrdup("");
+ } else {
+ TLScontext->peer_status |= TLS_CRED_FLAG_RPK;
+ TLScontext->peer_pkey_fprint =
+ tls_pkey_fprint(peerpkey, props->mdalg);
+ if (TLScontext->log_mask &
+ (TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE | TLS_LOG_PEERCERT))
+ msg_info("%s: raw public key fingerprint=%s", props->namaddr,
+ TLScontext->peer_pkey_fprint);
+ verify_rpk(TLScontext, peerpkey, props);
+ }
}
/*
diff --git a/postfix/src/tls/tls_dane.c b/postfix/src/tls/tls_dane.c
index a2b9b80d3..ac7f05f94 100644
--- a/postfix/src/tls/tls_dane.c
+++ b/postfix/src/tls/tls_dane.c
@@ -22,8 +22,9 @@
/* void tls_dane_free(dane)
/* TLS_DANE *dane;
/*
-/* void tls_dane_add_fpt_digests(dane, digest, delim, smtp_mode)
+/* void tls_dane_add_fpt_digests(dane, pkey_only, digest, delim, smtp_mode)
/* TLS_DANE *dane;
+/* int pkey_only;
/* const char *digest;
/* const char *delim;
/* int smtp_mode;
@@ -130,6 +131,9 @@
/* SSL context to be configured with the chosen digest algorithms.
/* .IP fpt_alg
/* The OpenSSL EVP digest algorithm handle for the fingerprint digest.
+/* .IP pkey_only
+/* When true, generate "fingerprint" TLSA records for just the public
+/* keys. Otherwise, for both certificates and public keys.
/* .IP tlsa
/* TLSA record linked list head, initially NULL.
/* .IP usage
@@ -415,8 +419,9 @@ static void dane_free(void *dane, void *unused_context)
/* tls_dane_add_fpt_digests - map fingerprint list to DANE TLSA RRset */
-void tls_dane_add_fpt_digests(TLS_DANE *dane, const char *digest,
- const char *delim, int smtp_mode)
+void tls_dane_add_fpt_digests(TLS_DANE *dane, int pkey_only,
+ const char *digest, const char *delim,
+ int smtp_mode)
{
ARGV *values = argv_split(digest, delim);
ssize_t i;
@@ -455,31 +460,41 @@ void tls_dane_add_fpt_digests(TLS_DANE *dane, const char *digest,
continue;
}
+#define USTR_LEN(raw) (unsigned char *) STR(raw), VSTRING_LEN(raw)
+
/*
* At the "fingerprint" security level certificate digests and public
- * key digests are interchangeable. Each leaf certificate is matched
- * via either the public key digest or full certificate digest. The
- * DER encoding of a certificate is not a valid public key, and
- * conversely, the DER encoding of a public key is not a valid
- * certificate. An attacker would need a 2nd-preimage that is
+ * key digests are by default interchangeable. Each leaf certificate
+ * is matched via either the public key digest or full certificate
+ * digest. The DER encoding of a certificate is not a valid public
+ * key, and conversely, the DER encoding of a public key is not a
+ * valid certificate. An attacker would need a 2nd-preimage that is
* feasible across types (given cert digest == some pkey digest) and
* yet presumably difficult within a type (e.g. given cert digest ==
* some other cert digest). No such attacks are known at this time,
* and it is expected that if any are found they would work within as
* well as across the cert/pkey data types.
+ *
+ * That said, when `pkey_only` is true, we match only public keys.
*
* The private-use matching type "255" is mapped to the configured
* fingerprint digest, which may (harmlessly) coincide with one of
* the standard DANE digest algorithms. The private code point is
* however unconditionally enabled.
*/
+ if (!pkey_only) {
+ dane->tlsa = tlsa_prepend(dane->tlsa, 3, 0, 255, USTR_LEN(raw));
+ if (log_mask & (TLS_LOG_VERBOSE | TLS_LOG_DANE))
+ tlsa_info("fingerprint", "digest as private-use TLSA record",
+ 3, 0, 255, USTR_LEN(raw));
+ }
+
+ /* The public key match is unconditional */
+ dane->tlsa = tlsa_prepend(dane->tlsa, 3, 1, 255, USTR_LEN(raw));
if (log_mask & (TLS_LOG_VERBOSE | TLS_LOG_DANE))
tlsa_info("fingerprint", "digest as private-use TLSA record",
- 3, 0, 255, (unsigned char *) STR(raw), VSTRING_LEN(raw));
- dane->tlsa = tlsa_prepend(dane->tlsa, 3, 0, 255,
- (unsigned char *) STR(raw), VSTRING_LEN(raw));
- dane->tlsa = tlsa_prepend(dane->tlsa, 3, 1, 255,
- (unsigned char *) STR(raw), VSTRING_LEN(raw));
+ 3, 1, 255, USTR_LEN(raw));
+
vstring_free(raw);
}
argv_free(values);
@@ -798,12 +813,21 @@ int tls_dane_enable(TLS_SESS_STATE *TLScontext)
SSL *ssl = TLScontext->con;
int usable = 0;
int ret;
+ int rpk_compat = 1;
for (tp = dane->tlsa; tp != 0; tp = tp->next) {
ret = SSL_dane_tlsa_add(ssl, tp->usage, tp->selector,
tp->mtype, tp->data, tp->length);
if (ret > 0) {
++usable;
+ /*
+ * Disable use of RFC7250 raw public keys if any TLSA record
+ * depends on X.509 certificates. Only DANE-EE(3) SPKI(1) records
+ * can get by with just a public key.
+ */
+ if (tp->usage != DNS_TLSA_USAGE_DOMAIN_ISSUED_CERTIFICATE
+ || tp->selector != DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO)
+ rpk_compat = 0;
continue;
}
if (ret == 0) {
@@ -818,6 +842,9 @@ int tls_dane_enable(TLS_SESS_STATE *TLScontext)
tls_print_errors();
return (-1);
}
+ if (rpk_compat)
+ tls_enable_server_rpk(NULL, ssl);
+
return (usable);
}
@@ -964,8 +991,9 @@ void tls_dane_log(TLS_SESS_STATE *TLScontext)
{
static VSTRING *top;
static VSTRING *bot;
+ X509 *mcert = 0;
EVP_PKEY *mspki = 0;
- int depth = SSL_get0_dane_authority(TLScontext->con, NULL, &mspki);
+ int depth = SSL_get0_dane_authority(TLScontext->con, &mcert, &mspki);
uint8_t u, s, m;
unsigned const char *data;
size_t dlen;
@@ -994,22 +1022,27 @@ void tls_dane_log(TLS_SESS_STATE *TLScontext)
hex_encode(top, (char *) data, dlen);
}
- switch (TLScontext->level) {
- case TLS_LEV_FPRINT:
+ if (TLScontext->level == TLS_LEV_FPRINT) {
msg_info("%s: Matched fingerprint: %s%s%s", TLScontext->namaddr,
STR(top), dlen > MAX_DUMP_BYTES ? "..." : "",
dlen > MAX_DUMP_BYTES ? STR(bot) : "");
return;
-
- default:
- msg_info("%s: Matched DANE %s at depth %d: %u %u %u %s%s%s",
- TLScontext->namaddr, mspki ?
- "TA public key verified certificate" : depth ?
- "TA certificate" : "EE certificate", depth, u, s, m,
+ }
+#if OPENSSL_VERSION_PREREQ(3,2)
+ if (SSL_get0_peer_rpk(TLScontext->con) != NULL) {
+ msg_info("%s: Matched DANE raw public key: %u %u %u %s%s%s",
+ TLScontext->namaddr, u, s, m,
STR(top), dlen > MAX_DUMP_BYTES ? "..." : "",
dlen > MAX_DUMP_BYTES ? STR(bot) : "");
return;
}
+#endif
+ msg_info("%s: Matched DANE %s at depth %d: %u %u %u %s%s%s",
+ TLScontext->namaddr, mspki ?
+ "TA public key verified certificate" : depth ?
+ "TA certificate" : "EE certificate", depth, u, s, m,
+ STR(top), dlen > MAX_DUMP_BYTES ? "..." : "",
+ dlen > MAX_DUMP_BYTES ? STR(bot) : "");
}
#ifdef TEST
diff --git a/postfix/src/tls/tls_fprint.c b/postfix/src/tls/tls_fprint.c
index 39b5a5219..dc3f99e8e 100644
--- a/postfix/src/tls/tls_fprint.c
+++ b/postfix/src/tls/tls_fprint.c
@@ -24,7 +24,7 @@
/* const char *mdalg;
/*
/* char *tls_pkey_fprint(peercert, mdalg)
-/* X509 *peercert;
+/* EVP_PKEY *peerpkey;
/* const char *mdalg;
/* DESCRIPTION
/* tls_digest_byname() constructs, and optionally returns, an EVP_MD_CTX
@@ -48,8 +48,6 @@
/*
/* tls_pkey_fprint() returns a public-key fingerprint; in all
/* other respects the function behaves as tls_cert_fprint().
-/* The var_tls_bc_pkey_fprint variable enables an incorrect
-/* algorithm that was used in Postfix versions 2.9.[0-5].
/* The return value is dynamically allocated with mymalloc(),
/* and the caller must eventually free it with myfree().
/*
@@ -274,6 +272,9 @@ char *tls_serverid_digest(TLS_SESS_STATE *TLScontext,
CHECK_OK_AND_DIGEST_CHARS(mdctx, props->protocols);
CHECK_OK_AND_DIGEST_CHARS(mdctx, ciphers);
+ /* Just in case we make this destination-policy specific */
+ CHECK_OK_AND_DIGEST_OBJECT(mdctx, &props->enable_rpk);
+
/*
* Ensure separation of caches for sessions where DANE trust
* configuration succeeded from those where it did not. The latter
@@ -398,38 +399,24 @@ char *tls_cert_fprint(X509 *peercert, const char *mdalg)
return (result);
}
-/* tls_pkey_fprint - extract public key fingerprint from certificate */
+/* tls_pkey_fprint - extract public key fingerprint */
-char *tls_pkey_fprint(X509 *peercert, const char *mdalg)
+char *tls_pkey_fprint(EVP_PKEY *peerpkey, const char *mdalg)
{
- if (var_tls_bc_pkey_fprint) {
- const char *myname = "tls_pkey_fprint";
- ASN1_BIT_STRING *key;
- char *result;
-
- key = X509_get0_pubkey_bitstr(peercert);
- if (key == 0)
- msg_fatal("%s: error extracting legacy public-key fingerprint: %m",
- myname);
-
- result = tls_data_fprint(key->data, key->length, mdalg);
- return (result);
- } else {
- int len;
- unsigned char *buf;
- unsigned char *buf2;
- char *result;
-
- len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(peercert), NULL);
- buf2 = buf = mymalloc(len);
- i2d_X509_PUBKEY(X509_get_X509_PUBKEY(peercert), &buf2);
- if (buf2 - buf != len)
- msg_panic("i2d_X509_PUBKEY invalid result length");
-
- result = tls_data_fprint(buf, len, mdalg);
- myfree(buf);
- return (result);
- }
+ int len;
+ unsigned char *buf;
+ unsigned char *buf2;
+ char *result;
+
+ len = i2d_PUBKEY(peerpkey, NULL);
+ buf2 = buf = mymalloc(len);
+ i2d_PUBKEY(peerpkey, &buf2);
+ if (buf2 - buf != len)
+ msg_panic("i2d_PUBKEY invalid result length");
+
+ result = tls_data_fprint(buf, len, mdalg);
+ myfree(buf);
+ return (result);
}
#endif
diff --git a/postfix/src/tls/tls_misc.c b/postfix/src/tls/tls_misc.c
index c7712b8d0..cf8f6fa70 100644
--- a/postfix/src/tls/tls_misc.c
+++ b/postfix/src/tls/tls_misc.c
@@ -118,6 +118,14 @@
/*
/* const EVP_MD *tls_validate_digest(dgst)
/* const char *dgst;
+/*
+/* void tls_enable_client_rpk(ctx, ssl)
+/* SSL_CTX *ctx;
+/* SSL *ssl;
+/*
+/* void tls_enable_server_rpk(ctx, ssl)
+/* SSL_CTX *ctx;
+/* SSL *ssl;
/* DESCRIPTION
/* This module implements public and internal routines that
/* support the TLS client and server.
@@ -215,6 +223,12 @@
/*
/* tls_validate_digest() returns a static handle for the named
/* digest algorithm, or NULL on error.
+/*
+/* tls_enable_client_rpk() enables the use of raw public keys in the
+/* client to server direction, if supported by the OpenSSL library.
+/*
+/* tls_enable_server_rpk() enables the use of raw public keys in the
+/* server to client direction, if supported by the OpenSSL library.
/* LICENSE
/* .ad
/* .fi
@@ -1028,7 +1042,6 @@ void tls_get_signature_params(TLS_SESS_STATE *TLScontext)
SSL *ssl = TLScontext->con;
int srvr = SSL_is_server(ssl);
EVP_PKEY *dh_pkey = 0;
- X509 *local_cert;
EVP_PKEY *local_pkey = 0;
X509 *peer_cert;
EVP_PKEY *peer_pkey = 0;
@@ -1060,18 +1073,23 @@ void tls_get_signature_params(TLS_SESS_STATE *TLScontext)
}
/*
- * On the client end, the certificate may be preset, but not used, so we
+ * On the client end, the certificate may be present, but not used, so we
* check via SSL_get_signature_nid(). This means that local signature
* data on clients requires at least 1.1.1a.
*/
- if (srvr || SSL_get_signature_nid(ssl, &nid))
- local_cert = SSL_get_certificate(ssl);
- else
- local_cert = 0;
-
+ if (srvr || SSL_get_signature_nid(ssl, &nid)) {
+ local_pkey = SSL_get_privatekey(ssl);
+ }
/* Signature algorithms for the local end of the connection */
- if (local_cert) {
- local_pkey = X509_get0_pubkey(local_cert);
+ if (local_pkey) {
+#if OPENSSL_VERSION_PREREQ(3,2)
+ if (srvr)
+ TLScontext->stoc_rpk = TLSEXT_cert_type_rpk ==
+ SSL_get_negotiated_server_cert_type(ssl);
+ else
+ TLScontext->ctos_rpk = TLSEXT_cert_type_rpk ==
+ SSL_get_negotiated_client_cert_type(ssl);
+#endif
/*
* Override the built-in name for the "ECDSA" algorithms OID, with
@@ -1097,7 +1115,6 @@ void tls_get_signature_params(TLS_SESS_STATE *TLScontext)
break;
#endif
}
- /* No X509_free(local_cert) */
}
/*
@@ -1107,9 +1124,26 @@ void tls_get_signature_params(TLS_SESS_STATE *TLScontext)
if (SSL_get_signature_nid(ssl, &nid) && nid != NID_undef)
locl_sig_dgst = OBJ_nid2sn(nid);
}
- /* Signature algorithms for the peer end of the connection */
- if ((peer_cert = TLS_PEEK_PEER_CERT(ssl)) != 0) {
+ peer_cert = TLS_PEEK_PEER_CERT(ssl);
+ if (peer_cert != 0) {
peer_pkey = X509_get0_pubkey(peer_cert);
+ }
+#if OPENSSL_VERSION_PREREQ(3,2)
+ else {
+ peer_pkey = SSL_get0_peer_rpk(ssl);
+ }
+#endif
+
+ /* Signature algorithms for the peer end of the connection */
+ if (peer_pkey != 0) {
+#if OPENSSL_VERSION_PREREQ(3,2)
+ if (srvr)
+ TLScontext->ctos_rpk = TLSEXT_cert_type_rpk ==
+ SSL_get_negotiated_client_cert_type(ssl);
+ else
+ TLScontext->stoc_rpk = TLSEXT_cert_type_rpk ==
+ SSL_get_negotiated_server_cert_type(ssl);
+#endif
/*
* Override the built-in name for the "ECDSA" algorithms OID, with
@@ -1144,8 +1178,9 @@ void tls_get_signature_params(TLS_SESS_STATE *TLScontext)
if (SSL_get_peer_signature_nid(ssl, &nid) && nid != NID_undef)
peer_sig_dgst = OBJ_nid2sn(nid);
- TLS_FREE_PEER_CERT(peer_cert);
}
+ TLS_FREE_PEER_CERT(peer_cert);
+
if (kex_name) {
TLScontext->kex_name = mystrdup(kex_name);
TLScontext->kex_curve = kex_curve;
@@ -1180,7 +1215,7 @@ void tls_log_summary(TLS_ROLE role, TLS_USAGE usage, TLS_SESS_STATE *ctx)
*/
vstring_sprintf(msg, "%s TLS connection %s %s %s%s%s: %s"
" with cipher %s (%d/%d bits)",
- !TLS_CERT_IS_PRESENT(ctx) ? "Anonymous" :
+ !TLS_CRED_IS_PRESENT(ctx) ? "Anonymous" :
TLS_CERT_IS_SECURED(ctx) ? "Verified" :
TLS_CERT_IS_TRUSTED(ctx) ? "Trusted" : "Untrusted",
usage == TLS_USAGE_NEW ? "established" : "reused",
@@ -1199,9 +1234,13 @@ void tls_log_summary(TLS_ROLE role, TLS_USAGE usage, TLS_SESS_STATE *ctx)
vstring_sprintf_append(msg, " server-signature %s",
ctx->srvr_sig_name);
if (ctx->srvr_sig_curve && *ctx->srvr_sig_curve)
- vstring_sprintf_append(msg, " (%s)", ctx->srvr_sig_curve);
+ vstring_sprintf_append(msg, " (%s%s)", ctx->srvr_sig_curve,
+ ctx->stoc_rpk ? " raw public key" : "");
else if (ctx->srvr_sig_bits > 0)
- vstring_sprintf_append(msg, " (%d bits)", ctx->srvr_sig_bits);
+ vstring_sprintf_append(msg, " (%d bit%s)", ctx->srvr_sig_bits,
+ ctx->stoc_rpk ? " raw public key" : "s");
+ else if (ctx->stoc_rpk)
+ vstring_sprintf_append(msg, " (raw public key)");
if (ctx->srvr_sig_dgst && *ctx->srvr_sig_dgst)
vstring_sprintf_append(msg, " server-digest %s",
ctx->srvr_sig_dgst);
@@ -1210,9 +1249,13 @@ void tls_log_summary(TLS_ROLE role, TLS_USAGE usage, TLS_SESS_STATE *ctx)
vstring_sprintf_append(msg, " client-signature %s",
ctx->clnt_sig_name);
if (ctx->clnt_sig_curve && *ctx->clnt_sig_curve)
- vstring_sprintf_append(msg, " (%s)", ctx->clnt_sig_curve);
+ vstring_sprintf_append(msg, " (%s%s)", ctx->clnt_sig_curve,
+ ctx->ctos_rpk ? " raw public key" : "");
else if (ctx->clnt_sig_bits > 0)
- vstring_sprintf_append(msg, " (%d bits)", ctx->clnt_sig_bits);
+ vstring_sprintf_append(msg, " (%d bit%s)", ctx->clnt_sig_bits,
+ ctx->ctos_rpk ? " raw public key" : "s");
+ else if (ctx->ctos_rpk)
+ vstring_sprintf_append(msg, " (raw public key)");
if (ctx->clnt_sig_dgst && *ctx->clnt_sig_dgst)
vstring_sprintf_append(msg, " client-digest %s",
ctx->clnt_sig_dgst);
@@ -1288,6 +1331,8 @@ TLS_SESS_STATE *tls_alloc_sess_context(int log_mask, const char *namaddr)
TLScontext->cipher_name = 0;
TLScontext->kex_name = 0;
TLScontext->kex_curve = 0;
+ TLScontext->ctos_rpk = 0;
+ TLScontext->stoc_rpk = 0;
TLScontext->clnt_sig_name = 0;
TLScontext->clnt_sig_curve = 0;
TLScontext->clnt_sig_dgst = 0;
@@ -1702,6 +1747,52 @@ const EVP_MD *tls_validate_digest(const char *dgst)
return md_alg;
}
+void tls_enable_client_rpk(SSL_CTX *ctx, SSL *ssl)
+{
+#if OPENSSL_VERSION_PREREQ(3,2)
+ static int warned = 0;
+ static const unsigned char cert_types_rpk[] = {
+ TLSEXT_cert_type_rpk,
+ TLSEXT_cert_type_x509
+ };
+
+ if ((ctx && !SSL_CTX_set1_client_cert_type(ctx, cert_types_rpk,
+ sizeof(cert_types_rpk))) ||
+ (ssl && !SSL_set1_client_cert_type(ssl, cert_types_rpk,
+ sizeof(cert_types_rpk)))) {
+ if (warned++) {
+ ERR_clear_error();
+ return;
+ }
+ msg_warn("Failed to enable client to server raw public key support");
+ tls_print_errors();
+ }
+#endif
+}
+
+void tls_enable_server_rpk(SSL_CTX *ctx, SSL *ssl)
+{
+#if OPENSSL_VERSION_PREREQ(3,2)
+ static int warned = 0;
+ static const unsigned char cert_types_rpk[] = {
+ TLSEXT_cert_type_rpk,
+ TLSEXT_cert_type_x509
+ };
+
+ if ((ctx && !SSL_CTX_set1_server_cert_type(ctx, cert_types_rpk,
+ sizeof(cert_types_rpk))) ||
+ (ssl && !SSL_set1_server_cert_type(ssl, cert_types_rpk,
+ sizeof(cert_types_rpk)))) {
+ if (warned++) {
+ ERR_clear_error();
+ return;
+ }
+ msg_warn("Failed to enable server to client raw public key support");
+ tls_print_errors();
+ }
+#endif
+}
+
#else
/*
diff --git a/postfix/src/tls/tls_proxy.h b/postfix/src/tls/tls_proxy.h
index 9bb94769a..65286398f 100644
--- a/postfix/src/tls/tls_proxy.h
+++ b/postfix/src/tls/tls_proxy.h
@@ -108,11 +108,11 @@ extern VSTREAM *tls_proxy_open(const char *, int, VSTREAM *, const char *,
((props)->a12), ((props)->a13), ((props)->a14))
#define TLS_PROXY_CLIENT_START_PROPS(props, a1, a2, a3, a4, a5, a6, a7, a8, \
- a9, a10, a11, a12, a13, a14) \
+ a9, a10, a11, a12, a13, a14, a15) \
(((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)->a12), ((props)->a13), ((props)->a14), ((props)->a15))
extern TLS_SESS_STATE *tls_proxy_context_receive(VSTREAM *);
extern void tls_proxy_context_free(TLS_SESS_STATE *);
@@ -170,6 +170,8 @@ extern void tls_proxy_server_start_free(TLS_SERVER_START_PROPS *);
#define TLS_ATTR_KEX_NAME "key_exchange"
#define TLS_ATTR_KEX_CURVE "key_exchange_curve"
#define TLS_ATTR_KEX_BITS "key_exchange_bits"
+#define TLS_ATTR_CTOS_RPK "ctos_rpk"
+#define TLS_ATTR_STOC_RPK "stoc_rpk"
#define TLS_ATTR_CLNT_SIG_NAME "clnt_signature"
#define TLS_ATTR_CLNT_SIG_CURVE "clnt_signature_curve"
#define TLS_ATTR_CLNT_SIG_BITS "clnt_signature_bits"
@@ -239,6 +241,7 @@ extern void tls_proxy_server_start_free(TLS_SERVER_START_PROPS *);
* TLS_CLIENT_START_PROPS attributes.
*/
#define TLS_ATTR_TIMEOUT "timeout"
+#define TLS_ATTR_ENABLE_RPK "enable_rpk"
#define TLS_ATTR_TLS_LEVEL "tls_level"
#define TLS_ATTR_NEXTHOP "nexthop"
#define TLS_ATTR_HOST "host"
diff --git a/postfix/src/tls/tls_proxy_client_print.c b/postfix/src/tls/tls_proxy_client_print.c
index 1cc5778eb..81e50b924 100644
--- a/postfix/src/tls/tls_proxy_client_print.c
+++ b/postfix/src/tls/tls_proxy_client_print.c
@@ -257,6 +257,7 @@ int tls_proxy_client_start_print(ATTR_PRINT_COMMON_FN print_fn,
ret = print_fn(fp, flags | ATTR_FLAG_MORE,
SEND_ATTR_INT(TLS_ATTR_TIMEOUT, props->timeout),
+ SEND_ATTR_INT(TLS_ATTR_ENABLE_RPK, props->enable_rpk),
SEND_ATTR_INT(TLS_ATTR_TLS_LEVEL, props->tls_level),
SEND_ATTR_STR(TLS_ATTR_NEXTHOP,
STRING_OR_EMPTY(props->nexthop)),
diff --git a/postfix/src/tls/tls_proxy_client_scan.c b/postfix/src/tls/tls_proxy_client_scan.c
index a69388c96..d36cf4d7e 100644
--- a/postfix/src/tls/tls_proxy_client_scan.c
+++ b/postfix/src/tls/tls_proxy_client_scan.c
@@ -451,6 +451,7 @@ int tls_proxy_client_start_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp,
props->dane = 0; /* scan_fn may return early */
ret = scan_fn(fp, flags | ATTR_FLAG_MORE,
RECV_ATTR_INT(TLS_ATTR_TIMEOUT, &props->timeout),
+ RECV_ATTR_INT(TLS_ATTR_ENABLE_RPK, &props->enable_rpk),
RECV_ATTR_INT(TLS_ATTR_TLS_LEVEL, &props->tls_level),
RECV_ATTR_STR(TLS_ATTR_NEXTHOP, nexthop),
RECV_ATTR_STR(TLS_ATTR_HOST, host),
@@ -478,7 +479,7 @@ int tls_proxy_client_start_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp,
props->cipher_grade = vstring_export(cipher_grade);
props->cipher_exclusions = vstring_export(cipher_exclusions);
props->mdalg = vstring_export(mdalg);
- ret = (ret == 14 ? 1 : -1);
+ ret = (ret == 15 ? 1 : -1);
if (ret != 1) {
tls_proxy_client_start_free(props);
props = 0;
diff --git a/postfix/src/tls/tls_proxy_context_print.c b/postfix/src/tls/tls_proxy_context_print.c
index 04123cb02..930410a46 100644
--- a/postfix/src/tls/tls_proxy_context_print.c
+++ b/postfix/src/tls/tls_proxy_context_print.c
@@ -88,6 +88,10 @@ int tls_proxy_context_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp,
STRING_OR_EMPTY(tp->kex_curve)),
SEND_ATTR_INT(TLS_ATTR_KEX_BITS,
tp->kex_bits),
+ SEND_ATTR_INT(TLS_ATTR_CTOS_RPK,
+ tp->ctos_rpk),
+ SEND_ATTR_INT(TLS_ATTR_STOC_RPK,
+ tp->stoc_rpk),
SEND_ATTR_STR(TLS_ATTR_CLNT_SIG_NAME,
STRING_OR_EMPTY(tp->clnt_sig_name)),
SEND_ATTR_STR(TLS_ATTR_CLNT_SIG_CURVE,
diff --git a/postfix/src/tls/tls_proxy_context_scan.c b/postfix/src/tls/tls_proxy_context_scan.c
index 1d463ada9..48aaff627 100644
--- a/postfix/src/tls/tls_proxy_context_scan.c
+++ b/postfix/src/tls/tls_proxy_context_scan.c
@@ -113,6 +113,8 @@ int tls_proxy_context_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp,
RECV_ATTR_STR(TLS_ATTR_KEX_NAME, kex_name),
RECV_ATTR_STR(TLS_ATTR_KEX_CURVE, kex_curve),
RECV_ATTR_INT(TLS_ATTR_KEX_BITS, &tls_context->kex_bits),
+ RECV_ATTR_INT(TLS_ATTR_CTOS_RPK, &tls_context->ctos_rpk),
+ RECV_ATTR_INT(TLS_ATTR_STOC_RPK, &tls_context->stoc_rpk),
RECV_ATTR_STR(TLS_ATTR_CLNT_SIG_NAME, clnt_sig_name),
RECV_ATTR_STR(TLS_ATTR_CLNT_SIG_CURVE, clnt_sig_curve),
RECV_ATTR_INT(TLS_ATTR_CLNT_SIG_BITS, &tls_context->clnt_sig_bits),
@@ -139,7 +141,7 @@ int tls_proxy_context_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp,
tls_context->srvr_sig_curve = vstring_export(srvr_sig_curve);
tls_context->srvr_sig_dgst = vstring_export(srvr_sig_dgst);
tls_context->namaddr = vstring_export(namaddr);
- ret = (ret == 22 ? 1 : -1);
+ ret = (ret == 24 ? 1 : -1);
if (ret != 1) {
tls_proxy_context_free(tls_context);
tls_context = 0;
diff --git a/postfix/src/tls/tls_server.c b/postfix/src/tls/tls_server.c
index 262cda952..88b332642 100644
--- a/postfix/src/tls/tls_server.c
+++ b/postfix/src/tls/tls_server.c
@@ -62,8 +62,8 @@
/* available as:
/* .IP TLScontext->peer_status
/* A bitmask field that records the status of the peer certificate
-/* verification. One or more of TLS_CERT_FLAG_PRESENT and
-/* TLS_CERT_FLAG_TRUSTED.
+/* verification. One or more of TLS_CRED_FLAG_CERT, TLS_CRED_FLAG_RPK
+/* and TLS_CERT_FLAG_TRUSTED.
/* .IP TLScontext->peer_CN
/* Extracted CommonName of the peer, or zero-length string
/* when information could not be extracted.
@@ -636,6 +636,13 @@ TLS_APPL_STATE *tls_server_init(const TLS_SERVER_INIT_PROPS *props)
return (0);
}
+ /*
+ * Always support server->client raw public keys, if they're good enough
+ * for the client, they're good enough for us.
+ */
+ tls_enable_server_rpk(server_ctx, NULL);
+ tls_enable_server_rpk(sni_ctx, NULL);
+
/*
* Upref and share the cert store. Sadly we can't yet use
* SSL_CTX_set1_cert_store(3) which was added in OpenSSL 1.1.0.
@@ -865,11 +872,19 @@ TLS_SESS_STATE *tls_server_start(const TLS_SERVER_START_PROPS *props)
tls_free_context(TLScontext);
return (0);
}
-#ifdef SSL_SECOP_PEER
- /* When authenticating the peer, use 80-bit plus OpenSSL security level */
+
+ /*
+ * When encryption is mandatory use the 80-bit plus OpenSSL security level.
+ */
if (props->requirecert)
SSL_set_security_level(TLScontext->con, 1);
-#endif
+
+ /*
+ * Also enable client->server raw public keys, provided we're not
+ * interested in client certificate fingerprints.
+ */
+ if (props->enable_rpk)
+ tls_enable_client_rpk(NULL, TLScontext->con);
/*
* Before really starting anything, try to seed the PRNG a little bit
@@ -946,6 +961,7 @@ TLS_SESS_STATE *tls_server_post_accept(TLS_SESS_STATE *TLScontext)
{
const SSL_CIPHER *cipher;
X509 *peer;
+ EVP_PKEY *pkey = 0;
char buf[CCERT_BUFSIZ];
/* Turn off packet dump if only dumping the handshake */
@@ -966,8 +982,17 @@ TLS_SESS_STATE *tls_server_post_accept(TLS_SESS_STATE *TLScontext)
* actual information. We want to save it for later use.
*/
peer = TLS_PEEK_PEER_CERT(TLScontext->con);
+ if (peer) {
+ pkey = X509_get0_pubkey(peer);
+ }
+#if OPENSSL_VERSION_PREREQ(3,2)
+ else {
+ pkey = SSL_get0_peer_rpk(TLScontext->con);
+ }
+#endif
+
if (peer != NULL) {
- TLScontext->peer_status |= TLS_CERT_FLAG_PRESENT;
+ TLScontext->peer_status |= TLS_CRED_FLAG_CERT;
if (SSL_get_verify_result(TLScontext->con) == X509_V_OK)
TLScontext->peer_status |= TLS_CERT_FLAG_TRUSTED;
@@ -981,16 +1006,23 @@ TLS_SESS_STATE *tls_server_post_accept(TLS_SESS_STATE *TLScontext)
}
TLScontext->peer_CN = tls_peer_CN(peer, TLScontext);
TLScontext->issuer_CN = tls_issuer_CN(peer, TLScontext);
- TLScontext->peer_cert_fprint = tls_cert_fprint(peer, TLScontext->mdalg);
- TLScontext->peer_pkey_fprint = tls_pkey_fprint(peer, TLScontext->mdalg);
+ TLScontext->peer_cert_fprint =
+ tls_cert_fprint(peer, TLScontext->mdalg);
+ TLScontext->peer_pkey_fprint =
+ tls_pkey_fprint(pkey, TLScontext->mdalg);
if (TLScontext->log_mask & (TLS_LOG_VERBOSE | TLS_LOG_PEERCERT)) {
- msg_info("%s: subject_CN=%s, issuer=%s, fingerprint=%s"
- ", pkey_fingerprint=%s",
+ msg_info("%s: subject_CN=%s, issuer=%s%s%s%s%s",
TLScontext->namaddr,
TLScontext->peer_CN, TLScontext->issuer_CN,
- TLScontext->peer_cert_fprint,
- TLScontext->peer_pkey_fprint);
+ *TLScontext->peer_cert_fprint ?
+ ", cert fingerprint=" : "",
+ *TLScontext->peer_cert_fprint ?
+ TLScontext->peer_cert_fprint : "",
+ *TLScontext->peer_pkey_fprint ?
+ ", pkey fingerprint=" : "",
+ *TLScontext->peer_pkey_fprint ?
+ TLScontext->peer_pkey_fprint : "");
}
TLS_FREE_PEER_CERT(peer);
@@ -1013,7 +1045,22 @@ TLS_SESS_STATE *tls_server_post_accept(TLS_SESS_STATE *TLScontext)
TLScontext->peer_CN = mystrdup("");
TLScontext->issuer_CN = mystrdup("");
TLScontext->peer_cert_fprint = mystrdup("");
- TLScontext->peer_pkey_fprint = mystrdup("");
+ if (!pkey) {
+ TLScontext->peer_pkey_fprint = mystrdup("");
+ } else {
+
+ /*
+ * Raw public keys don't involve CA trust, and we don't have a
+ * way to associate DANE TLSA RRs with clients just yet, we just
+ * make the fingerprint available to the access(5) layer.
+ */
+ TLScontext->peer_status |= TLS_CRED_FLAG_RPK;
+ TLScontext->peer_pkey_fprint =
+ tls_pkey_fprint(pkey, TLScontext->mdalg);
+ if (TLScontext->log_mask & (TLS_LOG_VERBOSE | TLS_LOG_PEERCERT))
+ msg_info("%s: raw public key fingerprint=%s",
+ TLScontext->namaddr, TLScontext->peer_pkey_fprint);
+ }
}
/*
diff --git a/postfix/src/tls/tls_verify.c b/postfix/src/tls/tls_verify.c
index f32f32b4a..c643f1838 100644
--- a/postfix/src/tls/tls_verify.c
+++ b/postfix/src/tls/tls_verify.c
@@ -144,6 +144,7 @@ int tls_verify_certificate_callback(int ok, X509_STORE_CTX *ctx)
int depth;
SSL *con;
TLS_SESS_STATE *TLScontext;
+ EVP_PKEY *rpk = 0;
/* May be NULL as of OpenSSL 1.0, thanks for the API change! */
cert = X509_STORE_CTX_get_current_cert(ctx);
@@ -151,6 +152,10 @@ int tls_verify_certificate_callback(int ok, X509_STORE_CTX *ctx)
con = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
TLScontext = SSL_get_ex_data(con, TLScontext_index);
depth = X509_STORE_CTX_get_error_depth(ctx);
+#if OPENSSL_VERSION_PREREQ(3,2)
+ if (cert == 0)
+ rpk = X509_STORE_CTX_get0_rpk(ctx);
+#endif
/*
* Transient failures to load the (DNS or synthetic TLSA) trust settings
@@ -174,12 +179,15 @@ int tls_verify_certificate_callback(int ok, X509_STORE_CTX *ctx)
update_error_state(TLScontext, depth, cert, err);
if (TLScontext->log_mask & TLS_LOG_VERBOSE) {
- if (cert)
+ if (cert) {
X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf));
- else
- strcpy(buf, "");
- msg_info("%s: depth=%d verify=%d subject=%s",
- TLScontext->namaddr, depth, ok, printable(buf, '?'));
+ msg_info("%s: depth=%d verify=%d subject=%s",
+ TLScontext->namaddr, depth, ok, printable(buf, '?'));
+ } else if (rpk) {
+ msg_info("%s: verify=%d raw public key", TLScontext->namaddr, ok);
+ } else {
+ msg_info("%s: depth=%d verify=%d", TLScontext->namaddr, depth, ok);
+ }
}
return (1);
}
diff --git a/postfix/src/tlsproxy/tlsproxy.c b/postfix/src/tlsproxy/tlsproxy.c
index 7c0d8147b..0ebf52c68 100644
--- a/postfix/src/tlsproxy/tlsproxy.c
+++ b/postfix/src/tlsproxy/tlsproxy.c
@@ -237,6 +237,12 @@
/* .IP "\fBtlsproxy_tls_chain_files ($smtpd_tls_chain_files)\fR"
/* Files with the Postfix \fBtlsproxy\fR(8) server keys and certificate
/* chains in PEM format.
+/* .PP
+/* Available in Postfix version 3.9 and later:
+/* .IP "\fBtlsproxy_tls_enable_rpk ($smtpd_tls_enable_rpk)\fR"
+/* Request that remote SMTP clients send an RFC7250 raw public key
+/* instead of an X.509 certificate, when asking or requiring client
+/* authentication.
/* STARTTLS CLIENT CONTROLS
/* .ad
/* .fi
@@ -436,6 +442,7 @@ bool var_smtpd_use_tls;
bool var_smtpd_enforce_tls;
bool var_smtpd_tls_ask_ccert;
bool var_smtpd_tls_req_ccert;
+bool var_smtpd_tls_enable_rpk;
bool var_smtpd_tls_set_sessid;
char *var_smtpd_relay_ccerts;
char *var_smtpd_tls_chain_files;
@@ -465,6 +472,7 @@ bool var_tlsp_use_tls;
bool var_tlsp_enforce_tls;
bool var_tlsp_tls_ask_ccert;
bool var_tlsp_tls_req_ccert;
+bool var_tlsp_tls_enable_rpk;
bool var_tlsp_tls_set_sessid;
char *var_tlsp_tls_chain_files;
char *var_tlsp_tls_cert_file;
@@ -1081,6 +1089,7 @@ static int tlsp_server_start_pre_handshake(TLSP_STATE *state)
timeout = 0, /* unused */
requirecert = (var_tlsp_tls_req_ccert
&& var_tlsp_enforce_tls),
+ enable_rpk = var_tlsp_tls_enable_rpk,
serverid = state->server_id,
namaddr = state->remote_endpt,
cipher_grade = cipher_grade,
@@ -1827,6 +1836,7 @@ int main(int argc, char **argv)
VAR_SMTPD_ENFORCE_TLS, DEF_SMTPD_ENFORCE_TLS, &var_smtpd_enforce_tls,
VAR_SMTPD_TLS_ACERT, DEF_SMTPD_TLS_ACERT, &var_smtpd_tls_ask_ccert,
VAR_SMTPD_TLS_RCERT, DEF_SMTPD_TLS_RCERT, &var_smtpd_tls_req_ccert,
+ VAR_SMTPD_TLS_ENABLE_RPK, DEF_SMTPD_TLS_ENABLE_RPK, &var_smtpd_tls_enable_rpk,
VAR_SMTPD_TLS_SET_SESSID, DEF_SMTPD_TLS_SET_SESSID, &var_smtpd_tls_set_sessid,
VAR_SMTP_USE_TLS, DEF_SMTP_USE_TLS, &var_smtp_use_tls,
VAR_SMTP_ENFORCE_TLS, DEF_SMTP_ENFORCE_TLS, &var_smtp_enforce_tls,
@@ -1837,6 +1847,7 @@ int main(int argc, char **argv)
VAR_TLSP_ENFORCE_TLS, DEF_TLSP_ENFORCE_TLS, &var_tlsp_enforce_tls,
VAR_TLSP_TLS_ACERT, DEF_TLSP_TLS_ACERT, &var_tlsp_tls_ask_ccert,
VAR_TLSP_TLS_RCERT, DEF_TLSP_TLS_RCERT, &var_tlsp_tls_req_ccert,
+ VAR_TLSP_TLS_ENABLE_RPK, DEF_TLSP_TLS_ENABLE_RPK, &var_tlsp_tls_enable_rpk,
VAR_TLSP_TLS_SET_SESSID, DEF_TLSP_TLS_SET_SESSID, &var_tlsp_tls_set_sessid,
VAR_TLSP_CLNT_USE_TLS, DEF_TLSP_CLNT_USE_TLS, &var_tlsp_clnt_use_tls,
VAR_TLSP_CLNT_ENFORCE_TLS, DEF_TLSP_CLNT_ENFORCE_TLS, &var_tlsp_clnt_enforce_tls,