Repository: aws/s2n-tls (C)

Issue:
supported_versions should use client instead of actual protocol version
### Problem:

Currently, the supported_versions extension is only sent if actual_protocol_version >= TLS1.3 ([link](https://github.com/aws/s2n-tls/blob/main/tls/extensions/s2n_client_supported_versions.c#L55)).

But this can cause issues when upgrading a fleet from TLS1.2 to TLS1.3. An s2n-tls client that supports TLS1.3 can connect to an un-upgraded TLS1.2 server and get a ticket. It can then try to use that TLS1.2 ticket to resume with an upgraded TLS1.3 server. The ticket sets its actual protocol version to TLS1.2 ([link](https://github.com/aws/s2n-tls/blob/main/tls/s2n_resume.c#L219)), so it doesn't send the supported_versions extensions. The TLS1.3 server therefore negotiates TLS1.2 whether or not it accepts the ticket. Because the client also supports TLS1.3, this is flagged as a downgrade and the connection is closed.

### Solution:

We should send the client supported_versions extensions if client_protocol_version>=S2N_TLS13, not actual_protocol_version. We should also make sure that we use client_protocol_version when figuring out the proper contents of the list. We should make sure that this change allows the server in the scenario described above to negotiate TLS1.3 if it rejects the ticket.

* **Does this change what S2N sends over the wire?** Yes, it will now wend the supported_versions extension in situations where it didn't before. I *think* this is fine; client_protocol_version would still need to be >=TLS1.3, so it shouldn't affect customers without TLS1.3 enabled in their security policies.
* **Does this change any public APIs?** No
* **Which versions of TLS will this impact?** TLS1.3. Sort of TLS1.2, but only if you also support TLS1.3.

### Requirements / Acceptance Criteria:

What must a solution address in order to solve the problem? How do we know the solution is complete?

* **RFC links:** Links to relevant RFC(s)
* **Related Issues:** Link any relevant issues
* **Will the Usage Guide or other documentation need to be updated?**
* **Testing:** How will this change be tested? Call out new integration tests, functional tests, or particularly interesting/important unit tests.
  * **Will this change trigger SAW changes?** Changes to the state machine, the s2n_handshake_io code that controls state transitions, the DRBG, or the corking/uncorking logic could trigger SAW failures.
  * **Should this change be fuzz tested?** Will it handle untrusted input? Create a separate issue to track the fuzzing work.

### Out of scope:

Is there anything the solution will intentionally NOT address?

[//]: #  (NOTE: If you believe this might be a security issue, please email aws-security@amazon.com instead of creating a GitHub issue. For more details, see the AWS Vulnerability Reporting Guide: https://aws.amazon.com/security/vulnerability-reporting/ )




Fix (patch):
diff --git a/tls/s2n_client_hello.c b/tls/s2n_client_hello.c
index 91d86643713..4e3c33ed306 100644
--- a/tls/s2n_client_hello.c
+++ b/tls/s2n_client_hello.c
@@ -64,27 +64,34 @@ static S2N_RESULT s2n_generate_client_session_id(struct s2n_connection *conn)
         return S2N_RESULT_OK;
     }
 
-    /* Only generate the session id for pre-TLS1.3 if using tickets */
-    if (conn->client_protocol_version < S2N_TLS13 && !conn->config->use_tickets) {
-        return S2N_RESULT_OK;
+    /* Only generate the session id if using tickets */
+    bool generate = conn->config->use_tickets;
+
+    /* TLS1.3 doesn't require session ids. The field is actually renamed to legacy_session_id.
+     * However, we still set a session id if dealing with troublesome middleboxes
+     * (middlebox compatibility mode) or if trying to use a TLS1.2 ticket.
+     */
+    if (conn->client_protocol_version >= S2N_TLS13) {
+        generate = s2n_is_middlebox_compat_enabled(conn) || conn->resume_protocol_version;
     }
 
-    /* Only generate the session id for TLS1.3 if in middlebox compatibility mode
-     *
-     * s2n_connection_get_protocol_version, which returns conn->actual_protocol_version, is used here because
-     * s2n_tls12_client_deserialize_session_state sets actual_protocol_version based on the protocol the 
-     * server that issued the session ticket indicated. If we are attempting to resume a session for that
-     * session ticket, we should base the decision of whether to generate a session ID on the protocol version
-     * we are attempting to resume with. */
-    if (s2n_connection_get_protocol_version(conn) >= S2N_TLS13 && !s2n_is_middlebox_compat_enabled(conn)) {
+    /* Session id not needed - no-op */
+    if (!generate) {
         return S2N_RESULT_OK;
     }
 
+    /* QUIC should not allow session ids for any reason.
+     *
+     *= https://tools.ietf.org/rfc/rfc9001#section-8.4
+     *# A server SHOULD treat the receipt of a TLS ClientHello with a non-empty
+     *# legacy_session_id field as a connection error of type PROTOCOL_VIOLATION.
+     */
+    RESULT_ENSURE(!conn->quic_enabled, S2N_ERR_UNSUPPORTED_WITH_QUIC);
+
     struct s2n_blob session_id = { 0 };
     RESULT_GUARD_POSIX(s2n_blob_init(&session_id, conn->session_id, S2N_TLS_SESSION_ID_MAX_LEN));
     RESULT_GUARD(s2n_get_public_random_data(&session_id));
     conn->session_id_len = S2N_TLS_SESSION_ID_MAX_LEN;
-
     return S2N_RESULT_OK;
 }
 
diff --git a/tls/s2n_connection.h b/tls/s2n_connection.h
index 976035146f3..60d29b6cad1 100644
--- a/tls/s2n_connection.h
+++ b/tls/s2n_connection.h
@@ -201,6 +201,11 @@ struct s2n_connection {
     uint8_t client_protocol_version;
     uint8_t server_protocol_version;
     uint8_t actual_protocol_version;
+    /* The version stored in the ticket / session we are resuming.
+     * We expect the connection to negotiate this version during
+     * the resumption handshake.
+     */
+    uint8_t resume_protocol_version;
 
     /* Flag indicating whether a protocol version has been
      * negotiated yet. */
diff --git a/tls/s2n_resume.c b/tls/s2n_resume.c
index 778139ef854..c6670200177 100644
--- a/tls/s2n_resume.c
+++ b/tls/s2n_resume.c
@@ -43,6 +43,23 @@ int s2n_allowed_to_cache_connection(struct s2n_connection *conn)
     return config->use_session_cache;
 }
 
+/* If a protocol version is required before the actual_protocol_version
+ * is negotiated, we should fall back to resume_protocol_version if available.
+ *
+ * This covers the case where the application requests a ticket / session state
+ * before a NewSessionTicket message has been sent or received. Historically,
+ * in that case we return the ticket / session state already set for the connection.
+ * resume_protocol_version represents the protocol version of that existing ticket / state.
+ */
+static uint8_t s2n_resume_protocol_version(struct s2n_connection *conn)
+{
+    if (!IS_NEGOTIATED(conn) && conn->resume_protocol_version) {
+        return conn->resume_protocol_version;
+    } else {
+        return conn->actual_protocol_version;
+    }
+}
+
 static int s2n_tls12_serialize_resumption_state(struct s2n_connection *conn, struct s2n_stuffer *to)
 {
     POSIX_ENSURE_REF(to);
@@ -58,7 +75,7 @@ static int s2n_tls12_serialize_resumption_state(struct s2n_connection *conn, str
 
     /* Write the entry */
     POSIX_GUARD(s2n_stuffer_write_uint8(to, S2N_SERIALIZED_FORMAT_TLS12_V3));
-    POSIX_GUARD(s2n_stuffer_write_uint8(to, conn->actual_protocol_version));
+    POSIX_GUARD(s2n_stuffer_write_uint8(to, s2n_resume_protocol_version(conn)));
     POSIX_GUARD(s2n_stuffer_write_bytes(to, conn->secure->cipher_suite->iana_value, S2N_TLS_CIPHER_SUITE_LEN));
     POSIX_GUARD(s2n_stuffer_write_uint64(to, now));
     POSIX_GUARD(s2n_stuffer_write_bytes(to, conn->secrets.version.tls12.master_secret, S2N_TLS_SECRET_LEN));
@@ -126,7 +143,7 @@ static S2N_RESULT s2n_tls13_serialize_resumption_state(struct s2n_connection *co
 
 static S2N_RESULT s2n_serialize_resumption_state(struct s2n_connection *conn, struct s2n_stuffer *out)
 {
-    if (conn->actual_protocol_version < S2N_TLS13) {
+    if (s2n_resume_protocol_version(conn) < S2N_TLS13) {
         RESULT_GUARD_POSIX(s2n_tls12_serialize_resumption_state(conn, out));
     } else {
         RESULT_GUARD(s2n_tls13_serialize_resumption_state(conn, out));
@@ -216,7 +233,7 @@ static S2N_RESULT s2n_tls12_client_deserialize_session_state(struct s2n_connecti
     RESULT_ENSURE_REF(conn);
     RESULT_ENSURE_REF(from);
 
-    RESULT_GUARD_POSIX(s2n_stuffer_read_uint8(from, &conn->actual_protocol_version));
+    RESULT_GUARD_POSIX(s2n_stuffer_read_uint8(from, &conn->resume_protocol_version));
 
     uint8_t *cipher_suite_wire = s2n_stuffer_raw_read(from, S2N_TLS_CIPHER_SUITE_LEN);
     RESULT_ENSURE_REF(cipher_suite_wire);
@@ -505,7 +522,7 @@ S2N_RESULT s2n_connection_get_session_state_size(struct s2n_connection *conn, si
     RESULT_ENSURE_REF(conn->secure);
     RESULT_ENSURE_REF(state_size);
 
-    if (conn->actual_protocol_version < S2N_TLS13) {
+    if (s2n_resume_protocol_version(conn) < S2N_TLS13) {
         *state_size = S2N_TLS12_STATE_SIZE_IN_BYTES;
         return S2N_RESULT_OK;
     }
diff --git a/tls/s2n_server_hello.c b/tls/s2n_server_hello.c
index 3879410e878..b95d0157f9b 100644
--- a/tls/s2n_server_hello.c
+++ b/tls/s2n_server_hello.c
@@ -218,7 +218,7 @@ static int s2n_server_hello_parse(struct s2n_connection *conn)
             POSIX_BAIL(S2N_ERR_PROTOCOL_VERSION_UNSUPPORTED);
         }
 
-        uint8_t actual_protocol_version = MIN(conn->server_protocol_version, conn->client_protocol_version);
+        conn->actual_protocol_version = MIN(conn->server_protocol_version, conn->client_protocol_version);
 
         /*
          *= https://tools.ietf.org/rfc/rfc5077#section-3.4
@@ -230,8 +230,8 @@ static int s2n_server_hello_parse(struct s2n_connection *conn)
          */
         if (session_ids_match) {
             /* check if the resumed session state is valid */
-            S2N_ERROR_IF(conn->actual_protocol_version != actual_protocol_version, S2N_ERR_BAD_MESSAGE);
-            S2N_ERROR_IF(memcmp(conn->secure->cipher_suite->iana_value, cipher_suite_wire, S2N_TLS_CIPHER_SUITE_LEN) != 0,
+            POSIX_ENSURE(conn->resume_protocol_version == conn->actual_protocol_version, S2N_ERR_BAD_MESSAGE);
+            POSIX_ENSURE(memcmp(conn->secure->cipher_suite->iana_value, cipher_suite_wire, S2N_TLS_CIPHER_SUITE_LEN) == 0,
                     S2N_ERR_BAD_MESSAGE);
 
             /* Session is resumed */
@@ -239,7 +239,6 @@ static int s2n_server_hello_parse(struct s2n_connection *conn)
         } else {
             conn->session_id_len = session_id_len;
             POSIX_CHECKED_MEMCPY(conn->session_id, session_id, session_id_len);
-            conn->actual_protocol_version = actual_protocol_version;
             POSIX_GUARD(s2n_set_cipher_as_client(conn, cipher_suite_wire));
             /* Erase master secret which might have been set for session resumption */
             POSIX_CHECKED_MEMSET((uint8_t *) conn->secrets.version.tls12.master_secret, 0, S2N_TLS_SECRET_LEN);
diff --git a/tls/s2n_tls13_secrets.h b/tls/s2n_tls13_secrets.h
index 3ef37af72f0..b99f76df8fd 100644
--- a/tls/s2n_tls13_secrets.h
+++ b/tls/s2n_tls13_secrets.h
@@ -40,6 +40,8 @@ struct s2n_tls13_secrets {
     uint8_t client_app_secret[S2N_TLS13_SECRET_MAX_LEN];
     uint8_t server_app_secret[S2N_TLS13_SECRET_MAX_LEN];
     uint8_t resumption_master_secret[S2N_TLS13_SECRET_MAX_LEN];
+
+    s2n_extract_secret_type_t extract_secret_type;
 };
 
 S2N_RESULT s2n_tls13_empty_transcripts_init();