Skip to content

Commit

Permalink
Add renegotiation callback
Browse files Browse the repository at this point in the history
  • Loading branch information
lrstewart committed Oct 2, 2022
1 parent d1ab84f commit d403473
Show file tree
Hide file tree
Showing 7 changed files with 320 additions and 4 deletions.
42 changes: 42 additions & 0 deletions compliance/specs/exceptions/rfc5746/4.2.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
target = "https://tools.ietf.org/rfc/rfc5746#4.2"

[[exception]]
quote = '''
If clients nevertheless choose to renegotiate, they
MUST behave as described below.
'''
reason = '''
s2n-tls does not support insecure renegotiation
and does not renegotiate if secure_renegotiation is FALSE.
'''

[[exception]]
quote = '''
Clients that choose to renegotiate MUST provide either the
TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV or "renegotiation_info" in
their ClientHello. In a legitimate renegotiation with an un-upgraded
server, that server should ignore both of these signals. However, if
the server (incorrectly) fails to ignore extensions, sending the
"renegotiation_info" extension may cause a handshake failure. Thus,
it is permitted, though NOT RECOMMENDED, for the client to simply
send the SCSV. This is the only situation in which clients are
permitted to not send the "renegotiation_info" extension in a
ClientHello that is used for renegotiation.
'''
reason = '''
s2n-tls does not support insecure renegotiation
and does not renegotiate if secure_renegotiation is FALSE.
'''

[[exception]]
quote = '''
When the ServerHello is received, the client MUST verify that it does
not contain the "renegotiation_info" extension. If it does, the
client MUST abort the handshake. (Because the server has already
indicated it does not support secure renegotiation, the only way that
this can happen is if the server is broken or there is an attack.)
'''
reason = '''
s2n-tls does not support insecure renegotiation
and does not renegotiate if secure_renegotiation is FALSE.
'''
179 changes: 178 additions & 1 deletion tests/unit/s2n_client_hello_request_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,24 @@ static S2N_RESULT s2n_send_client_hello_request(struct s2n_connection *server_co
return S2N_RESULT_OK;
}

struct s2n_test_reneg_req_ctx {
s2n_renegotiate_response app_decision;
uint8_t call_count;
};

static int s2n_test_reneg_req_cb(struct s2n_connection *conn, void *context, s2n_renegotiate_response *response)
{
POSIX_ENSURE_REF(context);
POSIX_ENSURE_REF(response);

struct s2n_test_reneg_req_ctx *test_context = (struct s2n_test_reneg_req_ctx *) context;
*response = test_context->app_decision;
POSIX_ENSURE_LT(test_context->call_count, UINT8_MAX);
test_context->call_count++;

return S2N_SUCCESS;
}

int main(int argc, char **argv)
{
BEGIN_TEST();
Expand All @@ -84,6 +102,13 @@ int main(int argc, char **argv)
EXPECT_SUCCESS(s2n_config_set_unsafe_for_testing(config));
EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key_to_store(config, chain_and_key));

DEFER_CLEANUP(struct s2n_config *config_with_reneg_cb = s2n_config_new(), s2n_config_ptr_free);
EXPECT_NOT_NULL(config_with_reneg_cb);
EXPECT_SUCCESS(s2n_config_set_cipher_preferences(config_with_reneg_cb, "default"));
EXPECT_SUCCESS(s2n_config_set_unsafe_for_testing(config_with_reneg_cb));
EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key_to_store(config_with_reneg_cb, chain_and_key));
EXPECT_SUCCESS(s2n_config_set_renegotiate_request_cb(config_with_reneg_cb, s2n_test_reneg_req_cb, NULL));

/* Test: Hello requests received during the handshake are a no-op */
{
DEFER_CLEANUP(struct s2n_connection *client_conn = s2n_connection_new(S2N_CLIENT),
Expand Down Expand Up @@ -186,6 +211,7 @@ int main(int argc, char **argv)

/* Complete the handshake */
EXPECT_SUCCESS(s2n_negotiate_test_server_and_client(server_conn, client_conn));
EXPECT_TRUE(client_conn->secure_renegotiation);

/* Send some data */
EXPECT_OK(s2n_test_send_and_recv(server_conn, client_conn));
Expand All @@ -199,12 +225,65 @@ int main(int argc, char **argv)
EXPECT_OK(s2n_test_send_and_recv(client_conn, server_conn));
}

/* Test: Hello requests received after the handshake trigger a no_renegotiation alert. */
/* Test: Hello requests received after the handshake trigger a no_renegotiation alert
* if renegotiation callbacks not set.
*
*= https://tools.ietf.org/rfc/rfc5246#section-7.4.1.1
*= type=test
*# This message MAY be ignored by
*# the client if it does not wish to renegotiate a session, or the
*# client may, if it wishes, respond with a no_renegotiation alert.
**/
{
DEFER_CLEANUP(struct s2n_connection *client_conn = s2n_connection_new(S2N_CLIENT),
s2n_connection_ptr_free);
EXPECT_NOT_NULL(client_conn);
EXPECT_SUCCESS(s2n_connection_set_config(client_conn, config));
EXPECT_NULL(client_conn->config->renegotiate_request_cb);

DEFER_CLEANUP(struct s2n_connection *server_conn = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
EXPECT_NOT_NULL(server_conn);
EXPECT_SUCCESS(s2n_connection_set_config(server_conn, config));

DEFER_CLEANUP(struct s2n_test_io_pair io_pair = { 0 }, s2n_io_pair_close);
EXPECT_SUCCESS(s2n_io_pair_init_non_blocking(&io_pair));
EXPECT_SUCCESS(s2n_connection_set_io_pair(client_conn, &io_pair));
EXPECT_SUCCESS(s2n_connection_set_io_pair(server_conn, &io_pair));

/* Complete the handshake */
EXPECT_SUCCESS(s2n_negotiate_test_server_and_client(server_conn, client_conn));
EXPECT_TRUE(client_conn->secure_renegotiation);

/* Send the hello request message. */
EXPECT_OK(s2n_send_client_hello_request(server_conn));

/* no_renegotation alert sent and received */
EXPECT_OK(s2n_test_send_and_recv(server_conn, client_conn));
EXPECT_ERROR_WITH_ERRNO(s2n_test_send_and_recv(client_conn, server_conn), S2N_ERR_ALERT);
EXPECT_EQUAL(s2n_connection_get_alert(server_conn), S2N_TLS_ALERT_NO_RENEGOTIATION);

/* Callback was not set */
EXPECT_NULL(client_conn->config->renegotiate_request_cb);
}

/* Test: Hello requests received after the handshake trigger a no_renegotiation alert
* if the application rejects the renegotiation request
*
*= https://tools.ietf.org/rfc/rfc5246#section-7.4.1.1
*= type=test
*# This message MAY be ignored by
*# the client if it does not wish to renegotiate a session, or the
*# client may, if it wishes, respond with a no_renegotiation alert.
**/
{
struct s2n_test_reneg_req_ctx ctx = { .app_decision = S2N_RENEGOTIATE_REJECT };

DEFER_CLEANUP(struct s2n_connection *client_conn = s2n_connection_new(S2N_CLIENT),
s2n_connection_ptr_free);
EXPECT_NOT_NULL(client_conn);
EXPECT_SUCCESS(s2n_connection_set_config(client_conn, config_with_reneg_cb));
EXPECT_SUCCESS(s2n_config_set_renegotiate_request_cb(config_with_reneg_cb, s2n_test_reneg_req_cb, &ctx));

DEFER_CLEANUP(struct s2n_connection *server_conn = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
Expand All @@ -218,6 +297,7 @@ int main(int argc, char **argv)

/* Complete the handshake */
EXPECT_SUCCESS(s2n_negotiate_test_server_and_client(server_conn, client_conn));
EXPECT_TRUE(client_conn->secure_renegotiation);

/* Send the hello request message. */
EXPECT_OK(s2n_send_client_hello_request(server_conn));
Expand All @@ -226,6 +306,103 @@ int main(int argc, char **argv)
EXPECT_OK(s2n_test_send_and_recv(server_conn, client_conn));
EXPECT_ERROR_WITH_ERRNO(s2n_test_send_and_recv(client_conn, server_conn), S2N_ERR_ALERT);
EXPECT_EQUAL(s2n_connection_get_alert(server_conn), S2N_TLS_ALERT_NO_RENEGOTIATION);

/* Callback triggered */
EXPECT_NOT_NULL(client_conn->config->renegotiate_request_cb);
EXPECT_EQUAL(ctx.call_count, 1);
}

/* Test: Hello requests received after the handshake do not trigger a no_renegotiation alert
* if the application accepts the renegotiation request
*/
{
struct s2n_test_reneg_req_ctx ctx = { .app_decision = S2N_RENEGOTIATE_ACCEPT };

DEFER_CLEANUP(struct s2n_connection *client_conn = s2n_connection_new(S2N_CLIENT),
s2n_connection_ptr_free);
EXPECT_NOT_NULL(client_conn);
EXPECT_SUCCESS(s2n_connection_set_config(client_conn, config_with_reneg_cb));
EXPECT_SUCCESS(s2n_config_set_renegotiate_request_cb(config_with_reneg_cb, s2n_test_reneg_req_cb, &ctx));

DEFER_CLEANUP(struct s2n_connection *server_conn = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
EXPECT_NOT_NULL(server_conn);
EXPECT_SUCCESS(s2n_connection_set_config(server_conn, config));

DEFER_CLEANUP(struct s2n_test_io_pair io_pair = { 0 }, s2n_io_pair_close);
EXPECT_SUCCESS(s2n_io_pair_init_non_blocking(&io_pair));
EXPECT_SUCCESS(s2n_connection_set_io_pair(client_conn, &io_pair));
EXPECT_SUCCESS(s2n_connection_set_io_pair(server_conn, &io_pair));

/* Complete the handshake */
EXPECT_SUCCESS(s2n_negotiate_test_server_and_client(server_conn, client_conn));
EXPECT_TRUE(client_conn->secure_renegotiation);

/* Send the hello request message. */
EXPECT_OK(s2n_send_client_hello_request(server_conn));

/* no_renegotation alert NOT sent and received */
EXPECT_OK(s2n_test_send_and_recv(server_conn, client_conn));
EXPECT_OK(s2n_test_send_and_recv(client_conn, server_conn));

/* Callback triggered */
EXPECT_NOT_NULL(client_conn->config->renegotiate_request_cb);
EXPECT_EQUAL(ctx.call_count, 1);
}

/* Test: Hello requests received after the handshake trigger a no_renegotiation alert
* if secure renegotiation is not supported, even if the application would have accepted the request.
*
*= https://tools.ietf.org/rfc/rfc5746#section-4.2
*= type=test
*# This text applies if the connection's "secure_renegotiation" flag is
*# set to FALSE.
*#
*# It is possible that un-upgraded servers will request that the client
*# renegotiate. It is RECOMMENDED that clients refuse this
*# renegotiation request. Clients that do so MUST respond to such
*# requests with a "no_renegotiation" alert (RFC 5246 requires this
*# alert to be at the "warning" level). It is possible that the
*# apparently un-upgraded server is in fact an attacker who is then
*# allowing the client to renegotiate with a different, legitimate,
*# upgraded server.
**/
{
struct s2n_test_reneg_req_ctx ctx = { .app_decision = S2N_RENEGOTIATE_ACCEPT };

DEFER_CLEANUP(struct s2n_connection *client_conn = s2n_connection_new(S2N_CLIENT),
s2n_connection_ptr_free);
EXPECT_NOT_NULL(client_conn);
EXPECT_SUCCESS(s2n_connection_set_config(client_conn, config_with_reneg_cb));
EXPECT_SUCCESS(s2n_config_set_renegotiate_request_cb(config_with_reneg_cb, s2n_test_reneg_req_cb, &ctx));

DEFER_CLEANUP(struct s2n_connection *server_conn = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
EXPECT_NOT_NULL(server_conn);
EXPECT_SUCCESS(s2n_connection_set_config(server_conn, config));

DEFER_CLEANUP(struct s2n_test_io_pair io_pair = { 0 }, s2n_io_pair_close);
EXPECT_SUCCESS(s2n_io_pair_init_non_blocking(&io_pair));
EXPECT_SUCCESS(s2n_connection_set_io_pair(client_conn, &io_pair));
EXPECT_SUCCESS(s2n_connection_set_io_pair(server_conn, &io_pair));

/* Complete the handshake */
EXPECT_SUCCESS(s2n_negotiate_test_server_and_client(server_conn, client_conn));

/* Force secure_renegotiation to be false */
client_conn->secure_renegotiation = false;

/* Send the hello request message. */
EXPECT_OK(s2n_send_client_hello_request(server_conn));

/* no_renegotation alert sent and received */
EXPECT_OK(s2n_test_send_and_recv(server_conn, client_conn));
EXPECT_ERROR_WITH_ERRNO(s2n_test_send_and_recv(client_conn, server_conn), S2N_ERR_ALERT);
EXPECT_EQUAL(s2n_connection_get_alert(server_conn), S2N_TLS_ALERT_NO_RENEGOTIATION);

/* Callback was not triggered */
EXPECT_NOT_NULL(client_conn->config->renegotiate_request_cb);
EXPECT_EQUAL(ctx.call_count, 0);
}

EXPECT_SUCCESS(s2n_cert_chain_and_key_free(chain_and_key));
Expand Down
31 changes: 31 additions & 0 deletions tests/unit/s2n_config_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ static int s2n_test_select_psk_identity_callback(struct s2n_connection *conn, vo
return S2N_SUCCESS;
}

static int s2n_test_reneg_req_cb(struct s2n_connection *conn, void *context, s2n_renegotiate_response *response)
{
return S2N_SUCCESS;
}

int main(int argc, char **argv)
{
BEGIN_TEST();
Expand Down Expand Up @@ -510,5 +515,31 @@ int main(int argc, char **argv)
EXPECT_FALSE(config->verify_after_sign);
}

/* Test s2n_config_set_renegotiate_request_cb */
{
uint8_t context = 0;
DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free);
EXPECT_NOT_NULL(config);

/* Unset by default */
EXPECT_EQUAL(config->renegotiate_request_cb, NULL);
EXPECT_EQUAL(config->renegotiate_request_ctx, NULL);

/* Safety */
EXPECT_FAILURE_WITH_ERRNO(s2n_config_set_renegotiate_request_cb(NULL, s2n_test_reneg_req_cb, &context), S2N_ERR_NULL);
EXPECT_SUCCESS(s2n_config_set_renegotiate_request_cb(config, NULL, &context));
EXPECT_SUCCESS(s2n_config_set_renegotiate_request_cb(config, s2n_test_reneg_req_cb, NULL));

/* Set */
EXPECT_SUCCESS(s2n_config_set_renegotiate_request_cb(config, s2n_test_reneg_req_cb, &context));
EXPECT_EQUAL(config->renegotiate_request_cb, s2n_test_reneg_req_cb);
EXPECT_EQUAL(config->renegotiate_request_ctx, &context);

/* Unset */
EXPECT_SUCCESS(s2n_config_set_renegotiate_request_cb(config, NULL, NULL));
EXPECT_EQUAL(config->renegotiate_request_cb, NULL);
EXPECT_EQUAL(config->renegotiate_request_ctx, NULL);
}

END_TEST();
}
35 changes: 32 additions & 3 deletions tls/s2n_client_hello_request.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,44 @@ S2N_RESULT s2n_client_hello_request_validate(struct s2n_connection *conn)
S2N_RESULT s2n_client_hello_request_recv(struct s2n_connection *conn)
{
RESULT_ENSURE_REF(conn);
RESULT_ENSURE_REF(conn->config);
RESULT_GUARD(s2n_client_hello_request_validate(conn));

/*
*= https://tools.ietf.org/rfc/rfc5746#section-4.2
*# This text applies if the connection's "secure_renegotiation" flag is
*# set to FALSE.
*#
*# It is possible that un-upgraded servers will request that the client
*# renegotiate. It is RECOMMENDED that clients refuse this
*# renegotiation request. Clients that do so MUST respond to such
*# requests with a "no_renegotiation" alert (RFC 5246 requires this
*# alert to be at the "warning" level). It is possible that the
*# apparently un-upgraded server is in fact an attacker who is then
*# allowing the client to renegotiate with a different, legitimate,
*# upgraded server.
*/
if (!conn->secure_renegotiation) {
RESULT_GUARD(s2n_queue_reader_no_renegotiation_alert(conn));
return S2N_RESULT_OK;
}

s2n_renegotiate_response response = S2N_RENEGOTIATE_REJECT;
if (conn->config->renegotiate_request_cb) {
RESULT_GUARD_POSIX((conn->config->renegotiate_request_cb)(
conn, conn->config->renegotiate_request_ctx, &response));
}

/*
*= https://tools.ietf.org/rfc/rfc5246#section-7.4.1.1
*# This message will be ignored by the client if the client is
*# currently negotiating a session. This message MAY be ignored by
*# This message MAY be ignored by
*# the client if it does not wish to renegotiate a session, or the
*# client may, if it wishes, respond with a no_renegotiation alert.
*/
RESULT_GUARD(s2n_queue_reader_no_renegotiation_alert(conn));
if (response != S2N_RENEGOTIATE_ACCEPT) {
RESULT_GUARD(s2n_queue_reader_no_renegotiation_alert(conn));
return S2N_RESULT_OK;
}

return S2N_RESULT_OK;
}
8 changes: 8 additions & 0 deletions tls/s2n_config.c
Original file line number Diff line number Diff line change
Expand Up @@ -1009,3 +1009,11 @@ int s2n_config_set_verify_after_sign(struct s2n_config *config, s2n_verify_after
}
return S2N_SUCCESS;
}

int s2n_config_set_renegotiate_request_cb(struct s2n_config *config, s2n_renegotiate_request_cb cb, void *ctx)
{
POSIX_ENSURE_REF(config);
config->renegotiate_request_cb = cb;
config->renegotiate_request_ctx = ctx;
return S2N_SUCCESS;
}
4 changes: 4 additions & 0 deletions tls/s2n_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "crypto/s2n_certificate.h"
#include "crypto/s2n_dhe.h"
#include "tls/s2n_psk.h"
#include "tls/s2n_renegotiate.h"
#include "tls/s2n_resume.h"
#include "tls/s2n_x509_validator.h"
#include "utils/s2n_blob.h"
Expand Down Expand Up @@ -157,6 +158,9 @@ struct s2n_config {

/* Used to override the stuffer size for a connection's `out` stuffer. */
uint32_t send_buffer_size_override;

void *renegotiate_request_ctx;
s2n_renegotiate_request_cb renegotiate_request_cb;
};

S2N_CLEANUP_RESULT s2n_config_ptr_free(struct s2n_config **config);
Expand Down
Loading

0 comments on commit d403473

Please sign in to comment.