aboutsummaryrefslogtreecommitdiff
path: root/include
diff options
context:
space:
mode:
authorDavid Benjamin <davidben@google.com>2024-05-22 13:56:56 -0400
committerBoringssl LUCI CQ <boringssl-scoped@luci-project-accounts.iam.gserviceaccount.com>2024-06-05 16:02:50 +0000
commitc1d9ac02514a138129872a036e3f8a1074dcb8bd (patch)
tree0a3dab51852bfa5e04be4debc2bb7e4dc4652144 /include
parente1a860c3745c77cb83228dde1b73fa62eaf43930 (diff)
downloadboringssl-c1d9ac02514a138129872a036e3f8a1074dcb8bd.zip
boringssl-c1d9ac02514a138129872a036e3f8a1074dcb8bd.tar.gz
boringssl-c1d9ac02514a138129872a036e3f8a1074dcb8bd.tar.bz2
Make SSL_select_next_proto more robust to invalid calls.
SSL_select_next_proto has some fairly complex preconditions: - The peer and supported list must be valid protocol lists - The supported list must not be empty. The peer list may be empty due to one of NPN's edge cases. In the context of how this function is meant to be used, these are reasonable preconditions. The caller should not serialize its own list wrong, and it makes no sense to try to negotiate a protocol when you don't support any protocols. In particular, it complicates NPN's weird "opportunistic" protocol. However, the preconditions are unchecked and a bit subtle. Violating them will result in memory errors. Bad syntax on the protocol lists is mostly not a concern (you should encode your own list correctly and the library checks the peer's list), but the second rule is somewhat of a mess in practice: Despite having the same precondition in reality, OpenSSL did not document this. Their documentation implies things which are impossible without this precondition, but they forgot to actually write down the precondition. There's an added complexity that OpenSSL never updated the parameter names to match the role reversal between ALPN and NPN. There are thus a few cases where a buggy caller may pass an empty "supported" list. - An NPN client called SSL_select_next_proto despite not actually supporting any NPN protocols. - An NPN client called SSL_select_next_proto, flipped the parameters, and the server advertised no protocols. - An ALPN server called SSL_select_next_proto, passed its own list in as the second parameter, despite not actually supporting any ALPN protocols. In these scenarios, the "opportunistic" protocol returned in the OPENSSL_NPN_NO_OVERLAP case will be out of bounds. If the caller discards it, this does not matter. If the caller returns it through the NPN or ALPN selection callback, they have a problem. ALPN servers are expected to discard it, though some may be buggy. NPN clients may implement either behavior. Older versions of some callers have exhibited variations on the above mistakes, so empirically folks don't always get it right. OpenSSL's wrong documentation also does not help matters. Instead, have SSL_select_next_proto just check these preconditions. That is not a performance-sensitive function and these preconditions are easy to check. While I'm here, rewrite it with CBS so it is much more straightforwardly correct. What to return when the preconditions fail is tricky, but we need to output *some* protocol, so we output the empty protocol. This, per the previous test and doc fixes, is actually fine in NPN, so one of the above buggy callers is not retroactively made OK. But it is not fine in ALPN, so we still need to document that callers need to avoid this state. To that end, revamp the documentation a bit. Thanks to Joe Birr-Pixton for reporting this! Fixed: 735 Change-Id: I4378a082385e7334e6abaa6705e6b15d6843f6c5 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/69028 Reviewed-by: Bob Beck <bbe@google.com> Commit-Queue: David Benjamin <davidben@google.com>
Diffstat (limited to 'include')
-rw-r--r--include/openssl/ssl.h52
1 files changed, 39 insertions, 13 deletions
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 6a92a28..63b66b4 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -3115,7 +3115,8 @@ OPENSSL_EXPORT int SSL_set_alpn_protos(SSL *ssl, const uint8_t *protos,
// SSL_CTX_set_alpn_select_cb sets a callback function on |ctx| that is called
// during ClientHello processing in order to select an ALPN protocol from the
-// client's list of offered protocols.
+// client's list of offered protocols. |SSL_select_next_proto| is an optional
+// utility function which may be useful in implementing this callback.
//
// The callback is passed a wire-format (i.e. a series of non-empty, 8-bit
// length-prefixed strings) ALPN protocol list in |in|. To select a protocol,
@@ -3290,7 +3291,8 @@ OPENSSL_EXPORT void SSL_CTX_set_next_protos_advertised_cb(
// SSL_CTX_set_next_proto_select_cb sets a callback that is called when a client
// needs to select a protocol from the server's provided list, passed in wire
// format in |in_len| bytes from |in|. The callback can assume that |in| is
-// syntactically valid.
+// syntactically valid. |SSL_select_next_proto| is an optional utility function
+// which may be useful in implementing this callback.
//
// On success, the callback should return |SSL_TLSEXT_ERR_OK| and set |*out| and
// |*out_len| to describe a buffer containing the selected protocol, or an
@@ -3324,21 +3326,45 @@ OPENSSL_EXPORT void SSL_get0_next_proto_negotiated(const SSL *ssl,
const uint8_t **out_data,
unsigned *out_len);
-// SSL_select_next_proto implements the standard protocol selection. It is
-// expected that this function is called from the callback set by
+// SSL_select_next_proto implements the standard protocol selection for either
+// ALPN servers or NPN clients. It is expected that this function is called from
+// the callback set by |SSL_CTX_set_alpn_select_cb| or
// |SSL_CTX_set_next_proto_select_cb|.
//
-// |peer| and |supported| must be vectors of 8-bit, length-prefixed byte strings
-// containing the peer and locally-configured protocols, respectively. The
-// length byte itself is not included in the length. A byte string of length 0
-// is invalid. No byte string may be truncated. |supported| is assumed to be
-// non-empty.
-//
-// This function finds the first protocol in |peer| which is also in
-// |supported|. If one was found, it sets |*out| and |*out_len| to point to it
-// and returns |OPENSSL_NPN_NEGOTIATED|. Otherwise, it returns
+// |peer| and |supported| contain the peer and locally-configured protocols,
+// respectively. This function finds the first protocol in |peer| which is also
+// in |supported|. If one was found, it sets |*out| and |*out_len| to point to
+// it and returns |OPENSSL_NPN_NEGOTIATED|. Otherwise, it returns
// |OPENSSL_NPN_NO_OVERLAP| and sets |*out| and |*out_len| to the first
// supported protocol.
+//
+// In ALPN, the server should only select protocols among those that the client
+// offered. Thus, if this function returns |OPENSSL_NPN_NO_OVERLAP|, the caller
+// should ignore |*out| and return |SSL_TLSEXT_ERR_ALERT_FATAL| from
+// |SSL_CTX_set_alpn_select_cb|'s callback to indicate there was no match.
+//
+// In NPN, the client may either select one of the server's protocols, or an
+// "opportunistic" protocol as described in Section 6 of
+// draft-agl-tls-nextprotoneg-03. When this function returns
+// |OPENSSL_NPN_NO_OVERLAP|, |*out| implicitly selects the first supported
+// protocol for use as the opportunistic protocol. The caller may use it,
+// ignore it and select a different opportunistic protocol, or ignore it and
+// select no protocol (empty string).
+//
+// |peer| and |supported| must be vectors of 8-bit, length-prefixed byte
+// strings. The length byte itself is not included in the length. A byte string
+// of length 0 is invalid. No byte string may be truncated. |supported| must be
+// non-empty; a caller that supports no ALPN/NPN protocols should skip
+// negotiating the extension, rather than calling this function. If any of these
+// preconditions do not hold, this function will return |OPENSSL_NPN_NO_OVERLAP|
+// and set |*out| and |*out_len| to an empty buffer for robustness, but callers
+// are not recommended to rely on this. An empty buffer is not a valid output
+// for |SSL_CTX_set_alpn_select_cb|'s callback.
+//
+// WARNING: |*out| and |*out_len| may alias either |peer| or |supported| and may
+// not be used after one of those buffers is modified or released. Additionally,
+// this function is not const-correct for compatibility reasons. Although |*out|
+// is a non-const pointer, callers may not modify the buffer though |*out|.
OPENSSL_EXPORT int SSL_select_next_proto(uint8_t **out, uint8_t *out_len,
const uint8_t *peer, unsigned peer_len,
const uint8_t *supported,