From a77b4dba237d001073d2d1c5d55c674a196c949f Mon Sep 17 00:00:00 2001 From: Matt Caswell Date: Wed, 17 Apr 2019 10:30:53 +0100 Subject: [PATCH] Write a test for receiving a KeyUpdate (update requested) while writing Reviewed-by: Ben Kaduk (Merged from https://github.com/openssl/openssl/pull/8773) --- test/sslapitest.c | 92 +++++++++++++++++++++++++++++++++++++++++++++ test/ssltestlib.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++ test/ssltestlib.h | 3 ++ 3 files changed, 191 insertions(+) diff --git a/test/sslapitest.c b/test/sslapitest.c index 3504eea63e..47c5be9e2d 100644 --- a/test/sslapitest.c +++ b/test/sslapitest.c @@ -4737,6 +4737,11 @@ static int test_key_update(void) || !TEST_int_eq(SSL_read(serverssl, buf, sizeof(buf)), strlen(mess))) goto end; + + if (!TEST_int_eq(SSL_write(serverssl, mess, strlen(mess)), strlen(mess)) + || !TEST_int_eq(SSL_read(clientssl, buf, sizeof(buf)), + strlen(mess))) + goto end; } testresult = 1; @@ -4749,6 +4754,91 @@ static int test_key_update(void) return testresult; } + +/* + * Test we can handle a KeyUpdate (update requested) message while write data + * is pending. + * Test 0: Client sends KeyUpdate while Server is writing + * Test 1: Server sends KeyUpdate while Client is writing + */ +static int test_key_update_in_write(int tst) +{ + SSL_CTX *cctx = NULL, *sctx = NULL; + SSL *clientssl = NULL, *serverssl = NULL; + int testresult = 0; + char buf[20]; + static char *mess = "A test message"; + BIO *bretry = BIO_new(bio_s_always_retry()); + BIO *tmp = NULL; + SSL *peerupdate = NULL, *peerwrite = NULL; + + if (!TEST_ptr(bretry) + || !TEST_true(create_ssl_ctx_pair(TLS_server_method(), + TLS_client_method(), + TLS1_3_VERSION, + 0, + &sctx, &cctx, cert, privkey)) + || !TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl, + NULL, NULL)) + || !TEST_true(create_ssl_connection(serverssl, clientssl, + SSL_ERROR_NONE))) + goto end; + + peerupdate = tst == 0 ? clientssl : serverssl; + peerwrite = tst == 0 ? serverssl : clientssl; + + if (!TEST_true(SSL_key_update(peerupdate, SSL_KEY_UPDATE_REQUESTED)) + || !TEST_true(SSL_do_handshake(peerupdate))) + goto end; + + /* Swap the writing endpoint's write BIO to force a retry */ + tmp = SSL_get_wbio(peerwrite); + if (!TEST_ptr(tmp) || !TEST_true(BIO_up_ref(tmp))) { + tmp = NULL; + goto end; + } + SSL_set0_wbio(peerwrite, bretry); + bretry = NULL; + + /* Write data that we know will fail with SSL_ERROR_WANT_WRITE */ + if (!TEST_int_eq(SSL_write(peerwrite, mess, strlen(mess)), -1) + || !TEST_int_eq(SSL_get_error(peerwrite, 0), SSL_ERROR_WANT_WRITE)) + goto end; + + /* Reinstate the original writing endpoint's write BIO */ + SSL_set0_wbio(peerwrite, tmp); + tmp = NULL; + + /* Now read some data - we will read the key update */ + if (!TEST_int_eq(SSL_read(peerwrite, buf, sizeof(buf)), -1) + || !TEST_int_eq(SSL_get_error(peerwrite, 0), SSL_ERROR_WANT_READ)) + goto end; + + /* + * Complete the write we started previously and read it from the other + * endpoint + */ + if (!TEST_int_eq(SSL_write(peerwrite, mess, strlen(mess)), strlen(mess)) + || !TEST_int_eq(SSL_read(peerupdate, buf, sizeof(buf)), strlen(mess))) + goto end; + + /* Write more data to ensure we send the KeyUpdate message back */ + if (!TEST_int_eq(SSL_write(peerwrite, mess, strlen(mess)), strlen(mess)) + || !TEST_int_eq(SSL_read(peerupdate, buf, sizeof(buf)), strlen(mess))) + goto end; + + testresult = 1; + + end: + SSL_free(serverssl); + SSL_free(clientssl); + SSL_CTX_free(sctx); + SSL_CTX_free(cctx); + BIO_free(bretry); + BIO_free(tmp); + + return testresult; +} #endif /* OPENSSL_NO_TLS1_3 */ static int test_ssl_clear(int idx) @@ -6457,6 +6547,7 @@ int setup_tests(void) #ifndef OPENSSL_NO_TLS1_3 ADD_ALL_TESTS(test_export_key_mat_early, 3); ADD_TEST(test_key_update); + ADD_ALL_TESTS(test_key_update_in_write, 2); #endif ADD_ALL_TESTS(test_ssl_clear, 2); ADD_ALL_TESTS(test_max_fragment_len_ext, OSSL_NELEM(max_fragment_len_test)); @@ -6477,4 +6568,5 @@ int setup_tests(void) void cleanup_tests(void) { bio_s_mempacket_test_free(); + bio_s_always_retry_free(); } diff --git a/test/ssltestlib.c b/test/ssltestlib.c index 8d7ab610fb..4cabc1fbb3 100644 --- a/test/ssltestlib.c +++ b/test/ssltestlib.c @@ -70,9 +70,11 @@ static int tls_dump_puts(BIO *bp, const char *str); /* Choose a sufficiently large type likely to be unused for this custom BIO */ #define BIO_TYPE_TLS_DUMP_FILTER (0x80 | BIO_TYPE_FILTER) #define BIO_TYPE_MEMPACKET_TEST 0x81 +#define BIO_TYPE_ALWAYS_RETRY 0x82 static BIO_METHOD *method_tls_dump = NULL; static BIO_METHOD *meth_mem = NULL; +static BIO_METHOD *meth_always_retry = NULL; /* Note: Not thread safe! */ const BIO_METHOD *bio_f_tls_dump_filter(void) @@ -620,6 +622,100 @@ static int mempacket_test_puts(BIO *bio, const char *str) return mempacket_test_write(bio, str, strlen(str)); } +static int always_retry_new(BIO *bi); +static int always_retry_free(BIO *a); +static int always_retry_read(BIO *b, char *out, int outl); +static int always_retry_write(BIO *b, const char *in, int inl); +static long always_retry_ctrl(BIO *b, int cmd, long num, void *ptr); +static int always_retry_gets(BIO *bp, char *buf, int size); +static int always_retry_puts(BIO *bp, const char *str); + +const BIO_METHOD *bio_s_always_retry(void) +{ + if (meth_always_retry == NULL) { + if (!TEST_ptr(meth_always_retry = BIO_meth_new(BIO_TYPE_ALWAYS_RETRY, + "Always Retry")) + || !TEST_true(BIO_meth_set_write(meth_always_retry, + always_retry_write)) + || !TEST_true(BIO_meth_set_read(meth_always_retry, + always_retry_read)) + || !TEST_true(BIO_meth_set_puts(meth_always_retry, + always_retry_puts)) + || !TEST_true(BIO_meth_set_gets(meth_always_retry, + always_retry_gets)) + || !TEST_true(BIO_meth_set_ctrl(meth_always_retry, + always_retry_ctrl)) + || !TEST_true(BIO_meth_set_create(meth_always_retry, + always_retry_new)) + || !TEST_true(BIO_meth_set_destroy(meth_always_retry, + always_retry_free))) + return NULL; + } + return meth_always_retry; +} + +void bio_s_always_retry_free(void) +{ + BIO_meth_free(meth_always_retry); +} + +static int always_retry_new(BIO *bio) +{ + BIO_set_init(bio, 1); + return 1; +} + +static int always_retry_free(BIO *bio) +{ + BIO_set_data(bio, NULL); + BIO_set_init(bio, 0); + return 1; +} + +static int always_retry_read(BIO *bio, char *out, int outl) +{ + BIO_set_retry_read(bio); + return -1; +} + +static int always_retry_write(BIO *bio, const char *in, int inl) +{ + BIO_set_retry_write(bio); + return -1; +} + +static long always_retry_ctrl(BIO *bio, int cmd, long num, void *ptr) +{ + long ret = 1; + + switch (cmd) { + case BIO_CTRL_FLUSH: + BIO_set_retry_write(bio); + /* fall through */ + case BIO_CTRL_EOF: + case BIO_CTRL_RESET: + case BIO_CTRL_DUP: + case BIO_CTRL_PUSH: + case BIO_CTRL_POP: + default: + ret = 0; + break; + } + return ret; +} + +static int always_retry_gets(BIO *bio, char *buf, int size) +{ + BIO_set_retry_read(bio); + return -1; +} + +static int always_retry_puts(BIO *bio, const char *str) +{ + BIO_set_retry_write(bio); + return -1; +} + int create_ssl_ctx_pair(const SSL_METHOD *sm, const SSL_METHOD *cm, int min_proto_version, int max_proto_version, SSL_CTX **sctx, SSL_CTX **cctx, char *certfile, diff --git a/test/ssltestlib.h b/test/ssltestlib.h index 6573e385ba..32d3bfb3f6 100644 --- a/test/ssltestlib.h +++ b/test/ssltestlib.h @@ -33,6 +33,9 @@ void bio_f_tls_dump_filter_free(void); const BIO_METHOD *bio_s_mempacket_test(void); void bio_s_mempacket_test_free(void); +const BIO_METHOD *bio_s_always_retry(void); +void bio_s_always_retry_free(void); + /* Packet types - value 0 is reserved */ #define INJECT_PACKET 1 #define INJECT_PACKET_IGNORE_REC_SEQ 2