static S2N_RESULT s2n_client_hello_verify_for_retry()

in tls/s2n_client_hello.c [213:354]


static S2N_RESULT s2n_client_hello_verify_for_retry(struct s2n_connection *conn,
        struct s2n_client_hello *old_ch, struct s2n_client_hello *new_ch,
        uint8_t *previous_client_random)
{
    RESULT_ENSURE_REF(conn);
    RESULT_ENSURE_REF(old_ch);
    RESULT_ENSURE_REF(new_ch);
    RESULT_ENSURE_REF(previous_client_random);

    if (!s2n_is_hello_retry_handshake(conn)) {
        return S2N_RESULT_OK;
    }

    /*
     *= https://www.rfc-editor.org/rfc/rfc8446#section-4.1.2
     *# The client will also send a
     *# ClientHello when the server has responded to its ClientHello with a
     *# HelloRetryRequest.  In that case, the client MUST send the same
     *# ClientHello without modification, except as follows:
     *
     * All of the exceptions that follow are extensions.
     */
    RESULT_ENSURE(old_ch->legacy_version == new_ch->legacy_version, S2N_ERR_BAD_MESSAGE);
    RESULT_ENSURE(old_ch->compression_methods.size == new_ch->compression_methods.size, S2N_ERR_BAD_MESSAGE);
    RESULT_ENSURE(s2n_constant_time_equals(old_ch->compression_methods.data, new_ch->compression_methods.data,
                          new_ch->compression_methods.size),
            S2N_ERR_BAD_MESSAGE);

    /* Some clients are not compliant with TLS 1.3 RFC, and send mismatching values in their second
     * ClientHello. For increased compatibility, these checks are skipped outside of tests. The
     * checks are still included in tests to ensure the s2n-tls client remains compliant.
     */
    if (s2n_in_test()) {
        /* In the past, the s2n-tls client updated the client random in the second ClientHello
         * which is not allowed by RFC8446: https://github.com/aws/s2n-tls/pull/3311. Although the
         * issue was addressed, its existence means that old versions of the s2n-tls client will
         * fail this validation.
         */
        RESULT_ENSURE(s2n_constant_time_equals(
                              previous_client_random,
                              conn->handshake_params.client_random,
                              S2N_TLS_RANDOM_DATA_LEN),
                S2N_ERR_BAD_MESSAGE);

        /* Some clients have been found to send a mismatching legacy session ID. */
        RESULT_ENSURE(old_ch->session_id.size == new_ch->session_id.size, S2N_ERR_BAD_MESSAGE);
        RESULT_ENSURE(s2n_constant_time_equals(old_ch->session_id.data, new_ch->session_id.data,
                              new_ch->session_id.size),
                S2N_ERR_BAD_MESSAGE);

        /* Some clients have been found to send a mismatching cipher suite list. */
        RESULT_ENSURE(old_ch->cipher_suites.size == new_ch->cipher_suites.size, S2N_ERR_BAD_MESSAGE);
        RESULT_ENSURE(s2n_constant_time_equals(old_ch->cipher_suites.data, new_ch->cipher_suites.data,
                              new_ch->cipher_suites.size),
                S2N_ERR_BAD_MESSAGE);
    }

    /*
     * Now enforce that the extensions also exactly match,
     * except for the exceptions described in the RFC.
     */
    for (size_t i = 0; i < s2n_array_len(s2n_supported_extensions); i++) {
        s2n_parsed_extension *old_extension = &old_ch->extensions.parsed_extensions[i];
        uint32_t old_size = old_extension->extension.size;
        s2n_parsed_extension *new_extension = &new_ch->extensions.parsed_extensions[i];
        uint32_t new_size = new_extension->extension.size;

        /* The extension type is only set if the extension is present.
         * Look for a non-zero-length extension.
         */
        uint16_t extension_type = 0;
        if (old_size != 0) {
            extension_type = old_extension->extension_type;
        } else if (new_size != 0) {
            extension_type = new_extension->extension_type;
        } else {
            continue;
        }

        switch (extension_type) {
            /*
             *= https://www.rfc-editor.org/rfc/rfc8446#section-4.1.2
             *#    -  If a "key_share" extension was supplied in the HelloRetryRequest,
             *#       replacing the list of shares with a list containing a single
             *#       KeyShareEntry from the indicated group.
             */
            case TLS_EXTENSION_KEY_SHARE:
                /* Handled when parsing the key share extension */
                break;
            /*
             *= https://www.rfc-editor.org/rfc/rfc8446#section-4.1.2
             *#    -  Removing the "early_data" extension (Section 4.2.10) if one was
             *#       present.  Early data is not permitted after a HelloRetryRequest.
             */
            case TLS_EXTENSION_EARLY_DATA:
                RESULT_ENSURE(new_size == 0, S2N_ERR_BAD_MESSAGE);
                break;
            /*
             *= https://www.rfc-editor.org/rfc/rfc8446#section-4.1.2
             *#    -  Including a "cookie" extension if one was provided in the
             *#       HelloRetryRequest.
             */
            case TLS_EXTENSION_COOKIE:
                /* Handled when parsing the cookie extension */
                break;
            /*
             *= https://www.rfc-editor.org/rfc/rfc8446#section-4.1.2
             *#    -  Updating the "pre_shared_key" extension if present by recomputing
             *#       the "obfuscated_ticket_age" and binder values and (optionally)
             *#       removing any PSKs which are incompatible with the server's
             *#       indicated cipher suite.
             */
            case TLS_EXTENSION_PRE_SHARED_KEY:
                /* Handled when parsing the psk extension */
                break;

            /* Some clients have been found to send mismatching supported versions in their second
             * ClientHello. The extension isn't compared byte-for-byte for increased compatibility
             * with these clients.
             */
            case TLS_EXTENSION_SUPPORTED_VERSIONS:
                /* Additional HRR validation for the supported versions extension is performed when
                 * parsing the extension.
                 */
                break;

            /*
             * No more exceptions.
             * All other extensions must match.
             */
            default:
                RESULT_ENSURE(old_size == new_size, S2N_ERR_BAD_MESSAGE);
                RESULT_ENSURE(s2n_constant_time_equals(
                                      new_extension->extension.data,
                                      old_extension->extension.data,
                                      old_size),
                        S2N_ERR_BAD_MESSAGE);
        }
    }

    return S2N_RESULT_OK;
}