From cee0628e0d53be82bd644ce258c3d3e90e64eced Mon Sep 17 00:00:00 2001 From: Jairus Christensen Date: Mon, 27 Feb 2023 09:36:15 -0700 Subject: [PATCH] [feat] SSL RTT in both client and server statem. SSL_get_handshake_rtt makes it available Reviewed-by: Tomas Mraz Reviewed-by: David von Oheimb (Merged from https://github.com/openssl/openssl/pull/20248) --- CHANGES.md | 7 ++ doc/build.info | 6 ++ doc/man3/SSL_get_handshake_rtt.pod | 57 ++++++++++++ include/openssl/ssl.h.in | 1 + ssl/ssl_lib.c | 15 ++++ ssl/ssl_local.h | 3 + ssl/statem/statem_clnt.c | 5 ++ ssl/statem/statem_srvr.c | 4 + test/build.info | 6 +- test/recipes/90-test_sslapi.t | 4 +- test/ssl_handshake_rtt_test.c | 138 +++++++++++++++++++++++++++++ util/libssl.num | 1 + 12 files changed, 245 insertions(+), 2 deletions(-) create mode 100644 doc/man3/SSL_get_handshake_rtt.pod create mode 100644 test/ssl_handshake_rtt_test.c diff --git a/CHANGES.md b/CHANGES.md index bf5e83e52b..cfe2fafa6e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -25,6 +25,13 @@ OpenSSL 3.2 ### Changes between 3.1 and 3.2 [xx XXX xxxx] + * TLS round-trip time calculation was added by a Brigham Young University + Capstone team partnering with Sandia National Laboratories. A new function + in ssl_lib titled SSL_get_handshake_rtt will calculate and retrieve this + value. + + *Jairus Christensen* + * Added the "-quic" option to s_client to enable connectivity to QUIC servers. QUIC requires the use of ALPN, so this must be specified via the "-alpn" option. Use of the "advanced" s_client command command via the "-adv" option diff --git a/doc/build.info b/doc/build.info index 6031a85d4d..6846b552d8 100644 --- a/doc/build.info +++ b/doc/build.info @@ -2551,6 +2551,10 @@ DEPEND[html/man3/SSL_get_fd.html]=man3/SSL_get_fd.pod GENERATE[html/man3/SSL_get_fd.html]=man3/SSL_get_fd.pod DEPEND[man/man3/SSL_get_fd.3]=man3/SSL_get_fd.pod GENERATE[man/man3/SSL_get_fd.3]=man3/SSL_get_fd.pod +DEPEND[html/man3/SSL_get_handshake_rtt.html]=man3/SSL_get_handshake_rtt.pod +GENERATE[html/man3/SSL_get_handshake_rtt.html]=man3/SSL_get_handshake_rtt.pod +DEPEND[man/man3/SSL_get_handshake_rtt.3]=man3/SSL_get_handshake_rtt.pod +GENERATE[man/man3/SSL_get_handshake_rtt.3]=man3/SSL_get_handshake_rtt.pod DEPEND[html/man3/SSL_get_peer_cert_chain.html]=man3/SSL_get_peer_cert_chain.pod GENERATE[html/man3/SSL_get_peer_cert_chain.html]=man3/SSL_get_peer_cert_chain.pod DEPEND[man/man3/SSL_get_peer_cert_chain.3]=man3/SSL_get_peer_cert_chain.pod @@ -3533,6 +3537,7 @@ html/man3/SSL_get_error.html \ html/man3/SSL_get_event_timeout.html \ html/man3/SSL_get_extms_support.html \ html/man3/SSL_get_fd.html \ +html/man3/SSL_get_handshake_rtt.html \ html/man3/SSL_get_peer_cert_chain.html \ html/man3/SSL_get_peer_certificate.html \ html/man3/SSL_get_peer_signature_nid.html \ @@ -4169,6 +4174,7 @@ man/man3/SSL_get_error.3 \ man/man3/SSL_get_event_timeout.3 \ man/man3/SSL_get_extms_support.3 \ man/man3/SSL_get_fd.3 \ +man/man3/SSL_get_handshake_rtt.3 \ man/man3/SSL_get_peer_cert_chain.3 \ man/man3/SSL_get_peer_certificate.3 \ man/man3/SSL_get_peer_signature_nid.3 \ diff --git a/doc/man3/SSL_get_handshake_rtt.pod b/doc/man3/SSL_get_handshake_rtt.pod new file mode 100644 index 0000000000..47c7f191d3 --- /dev/null +++ b/doc/man3/SSL_get_handshake_rtt.pod @@ -0,0 +1,57 @@ +=pod + +=head1 NAME + +SSL_get_handshake_rtt +- get round trip time for SSL Handshake + +=head1 SYNOPSIS + + #include + + int SSL_get_handshake_rtt(const SSL *s, uint64_t *rtt); + +=head1 DESCRIPTION + +SSL_get_handshake_rtt() retrieves the round-trip time (RTT) for I. + +This metric is represented in microseconds (us) as a uint64_t data type. + +=head1 NOTES + +This metric is created by taking two timestamps during the handshake and +providing the difference between these two times. + +When acting as the server, one timestamp is taken when the server is finished +writing to the client. This is during the ServerFinished in TLS 1.3 and +ServerHelloDone in TLS 1.2. The other timestamp is taken when the server is +done reading the client's response. This is after the client has responded +with ClientFinished. + +When acting as the client, one timestamp is taken when the client is finished +writing the ClientHello and early data (if any). The other is taken when +client is done reading the server's response. This is after ServerFinished in +TLS 1.3 and after ServerHelloDone in TLS 1.2. + +In addition to network propagation delay and network stack overhead, this +metric includes processing time on both endpoints, as this is based on TLS +protocol-level messages and the TLS protocol is not designed to measure +network timings. In some cases the processing time can be significant, +especially when the processing includes asymmetric cryptographic operations. + +=head1 RETURN VALUES + +Returns 1 if the TLS handshake RTT is successfully retrieved. +Returns 0 if the TLS handshake RTT cannot be determined yet. +Returns -1 if, while retrieving the TLS handshake RTT, an error occurs. + +=head1 COPYRIGHT + +Copyright 2023 The OpenSSL Project Authors. All Rights Reserved. + +Licensed under the Apache License 2.0 (the "License"). You may not use +this file except in compliance with the License. You can obtain a copy +in the file LICENSE in the source distribution or at +L. + +=cut diff --git a/include/openssl/ssl.h.in b/include/openssl/ssl.h.in index d580c8f19f..bf5ff7c06b 100644 --- a/include/openssl/ssl.h.in +++ b/include/openssl/ssl.h.in @@ -1948,6 +1948,7 @@ __owur int SSL_get_early_data_status(const SSL *s); __owur int SSL_get_error(const SSL *s, int ret_code); __owur const char *SSL_get_version(const SSL *s); +__owur int SSL_get_handshake_rtt(const SSL *s, uint64_t *rtt); /* This sets the 'default' SSL version that SSL_new() will create */ # ifndef OPENSSL_NO_DEPRECATED_3_0 diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c index 6848dbad7a..bad54e0955 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c @@ -4747,6 +4747,21 @@ const char *SSL_get_version(const SSL *s) return ssl_protocol_to_string(sc->version); } +__owur int SSL_get_handshake_rtt(const SSL *s, uint64_t *rtt) +{ + const SSL_CONNECTION *sc = SSL_CONNECTION_FROM_CONST_SSL(s); + + if (sc == NULL) + return -1; + if (sc->ts_msg_write.t <= 0 || sc->ts_msg_read.t <= 0) + return 0; /* data not (yet) available */ + if (sc->ts_msg_read.t < sc->ts_msg_write.t) + return -1; + + *rtt = ossl_time2us(ossl_time_subtract(sc->ts_msg_read, sc->ts_msg_write)); + return 1; +} + static int dup_ca_names(STACK_OF(X509_NAME) **dst, STACK_OF(X509_NAME) *src) { STACK_OF(X509_NAME) *sk; diff --git a/ssl/ssl_local.h b/ssl/ssl_local.h index 69a578dc5c..7ab84acc80 100644 --- a/ssl/ssl_local.h +++ b/ssl/ssl_local.h @@ -1249,6 +1249,9 @@ struct ssl_connection_st { int quiet_shutdown; /* we have shut things down, 0x01 sent, 0x02 for received */ int shutdown; + /* Timestamps used to calculate the handshake RTT */ + OSSL_TIME ts_msg_write; + OSSL_TIME ts_msg_read; /* where we are */ OSSL_STATEM statem; SSL_EARLY_DATA_STATE early_data_state; diff --git a/ssl/statem/statem_clnt.c b/ssl/statem/statem_clnt.c index e7a0d8ccfd..655b6ac17a 100644 --- a/ssl/statem/statem_clnt.c +++ b/ssl/statem/statem_clnt.c @@ -484,6 +484,8 @@ static WRITE_TRAN ossl_statem_client13_write_transition(SSL_CONNECTION *s) st->hand_state = TLS_ST_CW_COMP_CERT; else st->hand_state = TLS_ST_CW_CERT; + + s->ts_msg_read = ossl_time_now(); return WRITE_TRAN_CONTINUE; case TLS_ST_PENDING_EARLY_DATA_END: @@ -584,6 +586,7 @@ WRITE_TRAN ossl_statem_client_write_transition(SSL_CONNECTION *s) * No transition at the end of writing because we don't know what * we will be sent */ + s->ts_msg_write = ossl_time_now(); return WRITE_TRAN_FINISHED; case TLS_ST_CR_SRVR_HELLO: @@ -600,6 +603,7 @@ WRITE_TRAN ossl_statem_client_write_transition(SSL_CONNECTION *s) return WRITE_TRAN_CONTINUE; case TLS_ST_EARLY_DATA: + s->ts_msg_write = ossl_time_now(); return WRITE_TRAN_FINISHED; case DTLS_ST_CR_HELLO_VERIFY_REQUEST: @@ -607,6 +611,7 @@ WRITE_TRAN ossl_statem_client_write_transition(SSL_CONNECTION *s) return WRITE_TRAN_CONTINUE; case TLS_ST_CR_SRVR_DONE: + s->ts_msg_read = ossl_time_now(); if (s->s3.tmp.cert_req) st->hand_state = TLS_ST_CW_CERT; else diff --git a/ssl/statem/statem_srvr.c b/ssl/statem/statem_srvr.c index c33e8f64e9..6eb98040e8 100644 --- a/ssl/statem/statem_srvr.c +++ b/ssl/statem/statem_srvr.c @@ -547,12 +547,14 @@ static WRITE_TRAN ossl_statem_server13_write_transition(SSL_CONNECTION *s) case TLS_ST_SW_FINISHED: st->hand_state = TLS_ST_EARLY_DATA; + s->ts_msg_write = ossl_time_now(); return WRITE_TRAN_CONTINUE; case TLS_ST_EARLY_DATA: return WRITE_TRAN_FINISHED; case TLS_ST_SR_FINISHED: + s->ts_msg_read = ossl_time_now(); /* * Technically we have finished the handshake at this point, but we're * going to remain "in_init" for now and write out any session tickets @@ -702,9 +704,11 @@ WRITE_TRAN ossl_statem_server_write_transition(SSL_CONNECTION *s) return WRITE_TRAN_CONTINUE; case TLS_ST_SW_SRVR_DONE: + s->ts_msg_write = ossl_time_now(); return WRITE_TRAN_FINISHED; case TLS_ST_SR_FINISHED: + s->ts_msg_read = ossl_time_now(); if (s->hit) { st->hand_state = TLS_ST_OK; return WRITE_TRAN_CONTINUE; diff --git a/test/build.info b/test/build.info index 277b631a26..f6f19d6407 100644 --- a/test/build.info +++ b/test/build.info @@ -50,7 +50,7 @@ IF[{- !$disabled{tests} -}] dtlsv1listentest ct_test threadstest afalgtest d2i_test \ ssl_test_ctx_test ssl_test x509aux cipherlist_test asynciotest \ bio_callback_test bio_memleak_test bio_core_test bio_dgram_test param_build_test \ - bioprinttest sslapitest dtlstest sslcorrupttest \ + bioprinttest sslapitest ssl_handshake_rtt_test dtlstest sslcorrupttest \ bio_enc_test pkey_meth_test pkey_meth_kdf_test evp_kdf_test uitest \ cipherbytes_test threadstest_fips threadpool_test \ asn1_encode_test asn1_decode_test asn1_string_table_test \ @@ -494,6 +494,10 @@ IF[{- !$disabled{tests} -}] INCLUDE[sslapitest]=../include ../apps/include .. DEPEND[sslapitest]=../libcrypto ../libssl libtestutil.a + SOURCE[ssl_handshake_rtt_test]=ssl_handshake_rtt_test.c helpers/ssltestlib.c + INCLUDE[ssl_handshake_rtt_test]=../include ../apps/include .. + DEPEND[ssl_handshake_rtt_test]=../libcrypto.a ../libssl.a libtestutil.a + SOURCE[rpktest]=rpktest.c helpers/ssltestlib.c INCLUDE[rpktest]=../include ../apps/include .. DEPEND[rpktest]=../libcrypto ../libssl libtestutil.a diff --git a/test/recipes/90-test_sslapi.t b/test/recipes/90-test_sslapi.t index 9e9e32b51e..18d9f3d204 100644 --- a/test/recipes/90-test_sslapi.t +++ b/test/recipes/90-test_sslapi.t @@ -33,7 +33,7 @@ my $provconfnew = bldtop_file("test", "temp.cnf"); plan skip_all => "No TLS/SSL protocols are supported by this OpenSSL build" if alldisabled(grep { $_ ne "ssl3" } available_protocols("tls")); -plan tests => 3; +plan tests => 4; (undef, my $tmpfilename) = tempfile(); @@ -140,4 +140,6 @@ SKIP: { unlink $provconfnew; } +ok(run(test(["ssl_handshake_rtt_test"])),"running ssl_handshake_rtt_test"); + unlink $tmpfilename; diff --git a/test/ssl_handshake_rtt_test.c b/test/ssl_handshake_rtt_test.c new file mode 100644 index 0000000000..0e54284f04 --- /dev/null +++ b/test/ssl_handshake_rtt_test.c @@ -0,0 +1,138 @@ +/* + * Copyright 2023 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +/* + * We need access to the deprecated low level HMAC APIs for legacy purposes + * when the deprecated calls are not hidden + */ +#ifndef OPENSSL_NO_DEPRECATED_3_0 +# define OPENSSL_SUPPRESS_DEPRECATED +#endif + +#include +#include + +#include +#include +#include +#include +#include + +#include "helpers/ssltestlib.h" +#include "testutil.h" +#include "testutil/output.h" +#include "internal/ktls.h" +#include "../ssl/ssl_local.h" +#include "../ssl/statem/statem_local.h" + +static OSSL_LIB_CTX *libctx = NULL; +static char *cert = NULL; +static char *privkey = NULL; + +/* + * Test 0: Clientside handshake RTT (TLSv1.2) + * Test 1: Serverside handshake RTT (TLSv1.2) + * Test 2: Clientside handshake RTT (TLSv1.3) + * Test 3: Serverside handshake RTT (TLSv1.3) + * Test 4: Clientside handshake RTT with Early Data (TLSv1.3) + */ +static int test_handshake_rtt(int tst) +{ + SSL_CTX *cctx = NULL, *sctx = NULL; + SSL *clientssl = NULL, *serverssl = NULL; + int testresult = 0; + SSL_CONNECTION *s = NULL; + OSSL_STATEM *st = NULL; + uint64_t rtt; + +#ifdef OPENSSL_NO_TLS1_2 + if (tst <= 1) + return 1; +#endif +#ifdef OSSL_NO_USABLE_TLS1_3 + if (tst >= 2) + return 1; +#endif + + if (!TEST_true(create_ssl_ctx_pair(libctx, TLS_server_method(), + TLS_client_method(), + TLS1_VERSION, + (tst <= 1) ? TLS1_2_VERSION + : TLS1_3_VERSION, + &sctx, &cctx, cert, privkey)) + || !TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl, + NULL, NULL))) + goto end; + + s = SSL_CONNECTION_FROM_SSL(tst % 2 == 0 ? clientssl : serverssl); + if (!TEST_ptr(s) || !TEST_ptr(st = &s->statem)) + return 0; + + /* implicitly set handshake rtt with a delay */ + switch (tst) { + case 0: + st->hand_state = TLS_ST_CW_CLNT_HELLO; + ossl_statem_client_write_transition(s); + OSSL_sleep(1); + st->hand_state = TLS_ST_CR_SRVR_DONE; + ossl_statem_client_write_transition(s); + break; + case 1: + st->hand_state = TLS_ST_SW_SRVR_DONE; + ossl_statem_server_write_transition(s); + OSSL_sleep(1); + st->hand_state = TLS_ST_SR_FINISHED; + ossl_statem_server_write_transition(s); + break; + case 2: + st->hand_state = TLS_ST_CW_CLNT_HELLO; + ossl_statem_client_write_transition(s); + OSSL_sleep(1); + st->hand_state = TLS_ST_CR_SRVR_DONE; + ossl_statem_client_write_transition(s); + break; + case 3: + st->hand_state = TLS_ST_SW_SRVR_DONE; + ossl_statem_server_write_transition(s); + OSSL_sleep(1); + st->hand_state = TLS_ST_SR_FINISHED; + ossl_statem_server_write_transition(s); + break; + case 4: + st->hand_state = TLS_ST_EARLY_DATA; + ossl_statem_client_write_transition(s); + OSSL_sleep(1); + st->hand_state = TLS_ST_CR_SRVR_DONE; + ossl_statem_client_write_transition(s); + break; + } + + if (!TEST_int_gt(SSL_get_handshake_rtt(SSL_CONNECTION_GET_SSL(s), &rtt), 0)) + goto end; + /* 1 millisec is the absolute minimum it could be given the delay */ + if (!TEST_uint64_t_ge(rtt, 1000)) + goto end; + + testresult = 1; + + end: + SSL_free(serverssl); + SSL_free(clientssl); + SSL_CTX_free(sctx); + SSL_CTX_free(cctx); + + return testresult; +} + +int setup_tests(void) +{ + ADD_ALL_TESTS(test_handshake_rtt, 5); + + return 1; +} diff --git a/util/libssl.num b/util/libssl.num index 608c8fefbf..7f7b763075 100644 --- a/util/libssl.num +++ b/util/libssl.num @@ -558,6 +558,7 @@ SSL_add_expected_rpk ? 3_2_0 EXIST::FUNCTION: d2i_SSL_SESSION_ex ? 3_2_0 EXIST::FUNCTION: SSL_is_tls ? 3_2_0 EXIST::FUNCTION: SSL_is_quic ? 3_2_0 EXIST::FUNCTION: +SSL_get_handshake_rtt ? 3_2_0 EXIST::FUNCTION: SSL_new_stream ? 3_2_0 EXIST::FUNCTION: SSL_get0_connection ? 3_2_0 EXIST::FUNCTION: SSL_is_connection ? 3_2_0 EXIST::FUNCTION: -- 2.34.1