Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,8 @@ check_symbol_exists(DH_get_2048_256 "openssl/dh.h" TS_USE_GET_DH_2048_256)
check_symbol_exists(OPENSSL_NO_TLS_3 "openssl/ssl.h" TS_NO_USE_TLS12)
check_symbol_exists(SSL_CTX_set_client_hello_cb "openssl/ssl.h" HAVE_SSL_CTX_SET_CLIENT_HELLO_CB)
check_symbol_exists(SSL_CTX_set_select_certificate_cb "openssl/ssl.h" HAVE_SSL_CTX_SET_SELECT_CERTIFICATE_CB)
check_symbol_exists(SSL_CTX_set1_server_cert_type "openssl/ssl.h" HAVE_SSL_CTX_SET1_SERVER_CERT_TYPE)
check_symbol_exists(SSL_CREDENTIAL_new_raw_public_key "openssl/ssl.h" HAVE_SSL_CREDENTIAL_NEW_RAW_PUBLIC_KEY)
check_symbol_exists(SSL_set1_verify_cert_store "openssl/ssl.h" TS_HAS_VERIFY_CERT_STORE)
check_symbol_exists(SSL_get_shared_curve "openssl/ssl.h" HAVE_SSL_GET_SHARED_CURVE)
check_symbol_exists(SSL_get_curve_name "openssl/ssl.h" HAVE_SSL_GET_CURVE_NAME)
Expand Down Expand Up @@ -696,6 +698,12 @@ else()
set(TS_USE_HELLO_CB FALSE)
endif()

if(HAVE_SSL_CTX_SET1_SERVER_CERT_TYPE OR HAVE_SSL_CREDENTIAL_NEW_RAW_PUBLIC_KEY)
set(TS_USE_RPK TRUE)
else()
set(TS_USE_RPK FALSE)
endif()

if(HAVE_SSL_SET_MAX_EARLY_DATA
OR HAVE_SSL_READ_EARL_DATA
OR HAVE_SSL_WRITE_EARLY_DATA
Expand Down
51 changes: 51 additions & 0 deletions doc/admin-guide/files/sni.yaml.en.rst
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,35 @@ client_sni_policy Outbound Policy of SNI on outbound con

If not specified, the value of :ts:cv:`proxy.config.ssl.client.sni_policy` is used.

client_rpk_enabled Outbound Set to :code:`true` to offer a raw public key (`RFC 7250
<https://www.rfc-editor.org/rfc/rfc7250>`_), derived from
``client_cert``/``client_key`` (or the global
:ts:cv:`proxy.config.ssl.client.cert.filename` if those are not set), as an alternative
to an X.509 certificate for this outbound connection. The raw public key is offered
alongside the X.509 certificate, not instead of it: if the next hop does not support
raw public keys, the connection negotiates a normal certificate exchange instead.

Only available in builds linked against a TLS library with RFC 7250 support
(OpenSSL 3.2 or later, or a sufficiently recent BoringSSL). If this key is set on a
build without that support, |TS| logs a warning and ignores it.

server_rpk_ca Outbound The file containing the raw public key(s) (RFC 7250) that this next hop is trusted
to present, PEM-encoded. The file may contain more than one key concatenated together,
which allows an "old" and "new" key to both be trusted during a planned key rotation.

If this is relative, it is relative to the path in
:ts:cv:`proxy.config.ssl.client.CA.cert.path`.

Raw public keys have no certificate chain, issuer, or subject alternative name, so
when the next hop authenticates with one, |TS| checks the offered key against this
trusted set instead of the usual certificate chain and hostname checks --
``verify_server_properties``'s :code:`NAME` check has nothing to act on for a raw
public key. ``verify_server_policy`` still governs whether a key that does not match
this trusted set is fatal (:code:`ENFORCED`) or only logged (:code:`PERMISSIVE`).

Only available in builds linked against a TLS library with RFC 7250 support; see
``client_rpk_enabled`` above.

http2 Inbound Indicates whether the H2 protocol should be added to or removed from the
protocol negotiation list. The valid values are :code:`on` or :code:`off`.

Expand Down Expand Up @@ -415,6 +444,15 @@ In addition ``verify_server_properties`` specifies what |TS| will check when per
Verify both the signature and the SNI in the origin certificate.


If ``client_rpk_enabled`` is set and the next hop authenticates with a raw public key rather than
a certificate, ``verify_server_properties``'s :code:`NAME` check has nothing to act on -- a raw
public key has no subject alternative name -- so ``server_rpk_ca`` (matching the offered key
against a configured trusted set) takes its place. ``verify_server_policy`` still governs whether
a key that is not in the trusted set is fatal, exactly as for a failed certificate check. Since
raw public keys are negotiated alongside X.509 rather than instead of it, this only applies when
the next hop actually offers one; a next hop that does not support RFC 7250 falls back to a normal
certificate exchange, and the usual certificate checks apply.

If ``tunnel_route`` is specified, none of the certificate verification will be done because the TLS
negotiation will be tunneled to the upstream target, making those values irrelevant for that
configuration item. This option is explained in more detail in :ref:`sni-routing`.
Expand All @@ -430,6 +468,19 @@ Disable HTTP/2 for ``no-http2.example.com``.
- fqdn: no-http2.example.com
http2: off

Offer a raw public key (RFC 7250) alongside the certificate when connecting to
``parent.example.com``, and pin the exact key it must present. The connection falls back to a
normal certificate exchange if ``parent.example.com`` does not (yet) support raw public keys --
the expected state throughout a rolling upgrade of that next hop, not a failure.

.. code-block:: yaml

sni:
- fqdn: parent.example.com
client_rpk_enabled: true
server_rpk_ca: parent-rpk-trusted.pem
verify_server_policy: ENFORCED

Require client certificate verification for ``foo.com`` and any server name ending with ``.yahoo.com``. Therefore, client
request for a server name ending with yahoo.com (e.g., def.yahoo.com, abc.yahoo.com etc.) will cause |TS| require and verify
the client certificate.
Expand Down
43 changes: 43 additions & 0 deletions doc/admin-guide/files/ssl_multicert.yaml.en.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
specific language governing permissions and limitations
under the License.

.. include:: ../../common.defs

==================
ssl_multicert.yaml
==================
Expand Down Expand Up @@ -129,6 +131,33 @@ ssl_key_dialog: builtin|"exec:/path/to/program [args]" (optional)
program runs a security check to ensure that the system is not
compromised by an attacker before providing the pass phrase.

ssl_rpk_enabled: 1|0 (optional)
Set to `1` to offer a raw public key (`RFC 7250
<https://www.rfc-editor.org/rfc/rfc7250>`_), derived from this entry's ``ssl_cert_name``/
``ssl_key_name``, as an alternative to the X.509 certificate for inbound connections matching
this entry. The raw public key is offered alongside the certificate, not instead of it: a
client that does not support RFC 7250 negotiates a normal certificate exchange instead.
Defaults to `0`.

Only available in builds linked against a TLS library with RFC 7250 support (OpenSSL 3.2 or
later, or a sufficiently recent BoringSSL). If this key is set on a build without that support,
|TS| logs a warning and ignores it.

ssl_client_rpk_ca_name: FILENAME (optional)
The name of the file containing the raw public key(s) (RFC 7250) that a client is trusted to
present for mutual TLS on this entry, PEM-encoded. *FILENAME* is resolved relative to the
:ts:cv:`proxy.config.ssl.CA.cert.path` configuration variable. The file may contain more than
one key concatenated together, which allows an "old" and "new" key to both be trusted during a
planned key rotation.

A client authenticating with a raw public key has no certificate chain to verify, so this
trusted set takes the place of the usual client-certificate verification for that connection;
whether the client certificate level configuration is set to fail closed or only log applies
here in the same way it does for a failed certificate check.

Only available in builds linked against a TLS library with RFC 7250 support; see
``ssl_rpk_enabled`` above.

action: tunnel (optional)
If set to ``tunnel``, Traffic Server will not participate in the
TLS handshake and will blind tunnel the connection instead.
Expand Down Expand Up @@ -253,6 +282,20 @@ pass phrase to decrypt the keys.
- ssl_cert_name: server2.pem
ssl_key_dialog: "exec:/usr/bin/mypass foo 'ba r'"

The following example configures Traffic Server to use the SSL certificate
``server.pem`` for all requests, and offers a raw public key (RFC 7250)
alongside it. Clients presenting a raw public key for mutual TLS are checked
against ``client-rpk-trusted.pem`` instead of the usual certificate chain.

.. code-block:: yaml

ssl_multicert:
- dest_ip: "*"
ssl_cert_name: server.pem
ssl_key_name: server.key
ssl_rpk_enabled: 1
ssl_client_rpk_ca_name: client-rpk-trusted.pem

Migration from ssl_multicert.config
===================================

Expand Down
22 changes: 12 additions & 10 deletions include/config/ssl_multicert.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,18 @@ namespace config
* Represents a single certificate entry in ssl_multicert configuration.
*/
struct SSLMultiCertEntry {
std::string ssl_cert_name; ///< Certificate file name (required unless action is tunnel).
std::string dest_ip{"*"}; ///< IP address to match (default "*").
std::string ssl_key_name; ///< Private key file name (optional).
std::string ssl_ca_name; ///< CA certificate file name (optional).
std::string ssl_ocsp_name; ///< OCSP response file name (optional).
std::string ssl_key_dialog; ///< Passphrase dialog method (optional).
std::string dest_fqdn; ///< Destination FQDN (optional).
std::string action; ///< Action (e.g., "tunnel").
std::optional<int> ssl_ticket_enabled; ///< Session ticket enabled (optional).
std::optional<int> ssl_ticket_number; ///< Number of session tickets (optional).
std::string ssl_cert_name; ///< Certificate file name (required unless action is tunnel).
std::string dest_ip{"*"}; ///< IP address to match (default "*").
std::string ssl_key_name; ///< Private key file name (optional).
std::string ssl_ca_name; ///< CA certificate file name (optional).
std::string ssl_ocsp_name; ///< OCSP response file name (optional).
std::string ssl_key_dialog; ///< Passphrase dialog method (optional).
std::string dest_fqdn; ///< Destination FQDN (optional).
std::string action; ///< Action (e.g., "tunnel").
std::optional<int> ssl_ticket_enabled; ///< Session ticket enabled (optional).
std::optional<int> ssl_ticket_number; ///< Number of session tickets (optional).
std::optional<int> ssl_rpk_enabled; ///< Offer a RFC 7250 raw public key alongside X.509 (optional).
std::string ssl_client_rpk_ca_name; ///< Trusted client raw public keys file name (optional).
};

/// A configuration is a vector of certificate entries.
Expand Down
7 changes: 5 additions & 2 deletions include/iocore/net/SSLSNIConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,11 @@

// Properties for the next hop server
struct NextHopProperty {
std::string client_cert_file; // full path to client cert file for lookup
std::string client_key_file; // full path to client key file for lookup
std::string client_cert_file; // full path to client cert file for lookup
std::string client_key_file; // full path to client key file for lookup
bool client_rpk_enabled = false; // offer a RFC 7250 raw public key (derived from the configured client
// cert/key) alongside X.509 when connecting to this next hop
std::string server_rpk_ca_file; // full path to the PEM of trusted next-hop raw public keys to pin against
YamlSNIConfig::Policy verify_server_policy = YamlSNIConfig::Policy::UNSET; // whether to verify the next hop
YamlSNIConfig::Property verify_server_properties = YamlSNIConfig::Property::UNSET; // what to verify on the next hop
};
Expand Down
4 changes: 4 additions & 0 deletions include/iocore/net/YamlSNIConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ TSDECL(verify_server_properties);
TSDECL(verify_origin_server);
TSDECL(client_cert);
TSDECL(client_key);
TSDECL(client_rpk_enabled);
TSDECL(server_rpk_ca);
TSDECL(client_sni_policy);
TSDECL(server_cipher_suite);
TSDECL(server_TLSv1_3_cipher_suites);
Expand Down Expand Up @@ -105,6 +107,8 @@ struct YamlSNIConfig {
Property verify_server_properties = Property::UNSET;
std::string client_cert;
std::string client_key;
bool client_rpk_enabled = false;
std::string server_rpk_ca;
std::string client_sni_policy;
std::string server_cipher_suite;
std::string server_TLSv1_3_cipher_suites;
Expand Down
3 changes: 3 additions & 0 deletions include/tscore/ink_config.h.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ const int DEFAULT_STACKSIZE = @DEFAULT_STACK_SIZE@;
#cmakedefine01 TS_USE_QUIC
#cmakedefine01 TS_USE_QMUX
#cmakedefine01 TS_USE_REMOTE_UNWINDING
#cmakedefine01 TS_USE_RPK
#cmakedefine01 TS_USE_TLS13
#cmakedefine01 TS_USE_TLS_ASYNC
#cmakedefine01 TS_USE_TPROXY
Expand All @@ -178,6 +179,8 @@ const int DEFAULT_STACKSIZE = @DEFAULT_STACK_SIZE@;
#cmakedefine HAVE_SSL_SET_MAX_EARLY_DATA
#cmakedefine01 HAVE_SSL_CTX_SET_CLIENT_HELLO_CB
#cmakedefine01 HAVE_SSL_CTX_SET_SELECT_CERTIFICATE_CB
#cmakedefine01 HAVE_SSL_CTX_SET1_SERVER_CERT_TYPE
#cmakedefine01 HAVE_SSL_CREDENTIAL_NEW_RAW_PUBLIC_KEY
#cmakedefine01 HAVE_SSL_GET_SHARED_CURVE
#cmakedefine01 HAVE_SSL_GET_CURVE_NAME
#cmakedefine01 HAVE_SSL_GET0_GROUP_NAME
Expand Down
43 changes: 30 additions & 13 deletions src/config/ssl_multicert.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,21 +43,24 @@ constexpr swoc::Errata::Severity ERRATA_WARN_SEV{static_cast<swoc::Errata::sever
constexpr swoc::Errata::Severity ERRATA_ERROR_SEV{static_cast<swoc::Errata::severity_type>(DL_Error)};

// YAML key names.
constexpr char KEY_SSL_CERT_NAME[] = "ssl_cert_name";
constexpr char KEY_DEST_IP[] = "dest_ip";
constexpr char KEY_SSL_KEY_NAME[] = "ssl_key_name";
constexpr char KEY_SSL_CA_NAME[] = "ssl_ca_name";
constexpr char KEY_SSL_OCSP_NAME[] = "ssl_ocsp_name";
constexpr char KEY_SSL_KEY_DIALOG[] = "ssl_key_dialog";
constexpr char KEY_DEST_FQDN[] = "dest_fqdn";
constexpr char KEY_SSL_TICKET_ENABLED[] = "ssl_ticket_enabled";
constexpr char KEY_SSL_TICKET_NUMBER[] = "ssl_ticket_number";
constexpr char KEY_ACTION[] = "action";
constexpr char KEY_SSL_MULTICERT[] = "ssl_multicert";
constexpr char KEY_SSL_CERT_NAME[] = "ssl_cert_name";
constexpr char KEY_DEST_IP[] = "dest_ip";
constexpr char KEY_SSL_KEY_NAME[] = "ssl_key_name";
constexpr char KEY_SSL_CA_NAME[] = "ssl_ca_name";
constexpr char KEY_SSL_OCSP_NAME[] = "ssl_ocsp_name";
constexpr char KEY_SSL_KEY_DIALOG[] = "ssl_key_dialog";
constexpr char KEY_DEST_FQDN[] = "dest_fqdn";
constexpr char KEY_SSL_TICKET_ENABLED[] = "ssl_ticket_enabled";
constexpr char KEY_SSL_TICKET_NUMBER[] = "ssl_ticket_number";
constexpr char KEY_SSL_RPK_ENABLED[] = "ssl_rpk_enabled";
constexpr char KEY_SSL_CLIENT_RPK_CA_NAME[] = "ssl_client_rpk_ca_name";
constexpr char KEY_ACTION[] = "action";
constexpr char KEY_SSL_MULTICERT[] = "ssl_multicert";

std::set<std::string> const valid_keys = {
KEY_SSL_CERT_NAME, KEY_DEST_IP, KEY_SSL_KEY_NAME, KEY_SSL_CA_NAME, KEY_SSL_OCSP_NAME,
KEY_SSL_KEY_DIALOG, KEY_DEST_FQDN, KEY_SSL_TICKET_ENABLED, KEY_SSL_TICKET_NUMBER, KEY_ACTION,
KEY_SSL_CERT_NAME, KEY_DEST_IP, KEY_SSL_KEY_NAME, KEY_SSL_CA_NAME,
KEY_SSL_OCSP_NAME, KEY_SSL_KEY_DIALOG, KEY_DEST_FQDN, KEY_SSL_TICKET_ENABLED,
KEY_SSL_TICKET_NUMBER, KEY_ACTION, KEY_SSL_RPK_ENABLED, KEY_SSL_CLIENT_RPK_CA_NAME,
};

/**
Expand Down Expand Up @@ -128,6 +131,8 @@ emit_entry(YAML::Emitter &emitter, config::SSLMultiCertEntry const &entry)
write_field(KEY_ACTION, entry.action);
write_int_field(KEY_SSL_TICKET_ENABLED, entry.ssl_ticket_enabled);
write_int_field(KEY_SSL_TICKET_NUMBER, entry.ssl_ticket_number);
write_int_field(KEY_SSL_RPK_ENABLED, entry.ssl_rpk_enabled);
write_field(KEY_SSL_CLIENT_RPK_CA_NAME, entry.ssl_client_rpk_ca_name);

emitter << YAML::EndMap;
}
Expand Down Expand Up @@ -176,6 +181,14 @@ template <> struct convert<config::SSLMultiCertEntry> {
entry.ssl_ticket_number = node[KEY_SSL_TICKET_NUMBER].as<int>();
}

if (node[KEY_SSL_RPK_ENABLED]) {
entry.ssl_rpk_enabled = node[KEY_SSL_RPK_ENABLED].as<int>();
}

if (node[KEY_SSL_CLIENT_RPK_CA_NAME]) {
entry.ssl_client_rpk_ca_name = node[KEY_SSL_CLIENT_RPK_CA_NAME].as<std::string>();
}

if (node[KEY_ACTION]) {
entry.action = node[KEY_ACTION].as<std::string>();
}
Expand Down Expand Up @@ -330,6 +343,10 @@ SSLMultiCertParser::parse_legacy(std::string_view content)
entry.ssl_ticket_enabled = swoc::svtoi(value);
} else if (key == KEY_SSL_TICKET_NUMBER) {
entry.ssl_ticket_number = swoc::svtoi(value);
} else if (key == KEY_SSL_RPK_ENABLED) {
entry.ssl_rpk_enabled = swoc::svtoi(value);
} else if (key == KEY_SSL_CLIENT_RPK_CA_NAME) {
entry.ssl_client_rpk_ca_name = value;
} else if (unknown_keys.insert(key).second) {
errata.note(ERRATA_NOTE_SEV, "Ignoring unknown ssl_multicert key '{}' in legacy format", key);
}
Expand Down
2 changes: 2 additions & 0 deletions src/iocore/net/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ add_library(
SSLNetVConnection.cc
SSLNextProtocolAccept.cc
SSLNextProtocolSet.cc
SSLRPKUtils.cc
SSLSNIConfig.cc
SSLStats.cc
SSLSessionCache.cc
Expand Down Expand Up @@ -147,6 +148,7 @@ if(BUILD_TESTING)
unit_tests/test_ProxyProtocol.cc
unit_tests/test_SSLCertLookup.cc
unit_tests/test_SSLNetVConnectionAsyncEp.cc
unit_tests/test_SSLRPKUtils.cc
unit_tests/test_SSLSNIConfig.cc
unit_tests/test_YamlSNIConfig.cc
unit_tests/test_OCSPStapling.cc
Expand Down
Loading