QUIC: Test key update works correctly
authorHugo Landau <hlandau@openssl.org>
Tue, 23 May 2023 11:23:06 +0000 (12:23 +0100)
committerPauli <pauli@openssl.org>
Thu, 15 Jun 2023 23:26:48 +0000 (09:26 +1000)
Reviewed-by: Tomas Mraz <tomas@openssl.org>
Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Paul Dale <pauli@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/21029)

test/quic_multistream_test.c
test/testutil.h

index 3f56e0baa7687902ad1098c9126f6363e9340e7b..44da562da8ae44c3c8a47165904416809ca16d04 100644 (file)
@@ -59,6 +59,15 @@ struct helper {
 #endif
 
     OSSL_TIME       start_time;
+
+    /*
+     * This is a duration recording the amount of time we have skipped forwards
+     * for testing purposes relative to the real ossl_time_now() clock. We add
+     * a quantity of time to this every time we skip some time.
+     */
+    CRYPTO_RWLOCK   *time_lock;
+    OSSL_TIME       time_slip; /* protected by time_lock */
+
     int             init, blocking, check_spin_again;
     int             free_order;
 };
@@ -209,6 +218,31 @@ struct script_op {
 #define OP_S_UNBIND_STREAM_ID(stream_name) \
     {OPK_S_UNBIND_STREAM_ID, NULL, 0, NULL, #stream_name},
 
+static OSSL_TIME get_time(void *arg)
+{
+    struct helper *h = arg;
+    OSSL_TIME t;
+
+    if (!TEST_true(CRYPTO_THREAD_read_lock(h->time_lock)))
+        return ossl_time_zero();
+
+    t = ossl_time_add(ossl_time_now(), h->time_slip);
+
+    CRYPTO_THREAD_unlock(h->time_lock);
+    return t;
+}
+
+static int skip_time_ms(struct helper *h, const struct script_op *op)
+{
+    if (!TEST_true(CRYPTO_THREAD_write_lock(h->time_lock)))
+        return 0;
+
+    h->time_slip = ossl_time_add(h->time_slip, ossl_ms2time(op->arg2));
+
+    CRYPTO_THREAD_unlock(h->time_lock);
+    return 1;
+}
+
 static int check_rejected(struct helper *h, const struct script_op *op)
 {
     uint64_t stream_id = op->arg2;
@@ -246,6 +280,47 @@ static int check_stream_stopped(struct helper *h, const struct script_op *op)
     return 1;
 }
 
+static int override_key_update(struct helper *h, const struct script_op *op)
+{
+    QUIC_CHANNEL *ch = ossl_quic_conn_get_channel(h->c_conn);
+
+    ossl_quic_channel_set_txku_threshold_override(ch, op->arg2);
+    return 1;
+}
+
+static int check_key_update_ge(struct helper *h, const struct script_op *op)
+{
+    QUIC_CHANNEL *ch = ossl_quic_conn_get_channel(h->c_conn);
+    int64_t txke = (int64_t)ossl_quic_channel_get_tx_key_epoch(ch);
+    int64_t rxke = (int64_t)ossl_quic_channel_get_rx_key_epoch(ch);
+    int64_t diff = txke - rxke;
+
+    /*
+     * TXKE must always be equal to or ahead of RXKE.
+     * It can be ahead of RXKE by at most 1.
+     */
+    if (!TEST_int64_t_ge(diff, 0) || !TEST_int64_t_le(diff, 1))
+        return 0;
+
+    /* Caller specifies a minimum number of RXKEs which must have happened. */
+    if (!TEST_uint64_t_ge((uint64_t)rxke, op->arg2))
+        return 0;
+
+    return 1;
+}
+
+static int check_key_update_lt(struct helper *h, const struct script_op *op)
+{
+    QUIC_CHANNEL *ch = ossl_quic_conn_get_channel(h->c_conn);
+    uint64_t txke = ossl_quic_channel_get_tx_key_epoch(ch);
+
+    /* Caller specifies a maximum number of TXKEs which must have happened. */
+    if (!TEST_uint64_t_lt(txke, op->arg2))
+        return 0;
+
+    return 1;
+}
+
 static unsigned long stream_info_hash(const STREAM_INFO *info)
 {
     return OPENSSL_LH_strhash(info->name);
@@ -348,6 +423,9 @@ static void helper_cleanup(struct helper *h)
 
     SSL_CTX_free(h->c_ctx);
     h->c_ctx = NULL;
+
+    CRYPTO_THREAD_lock_free(h->time_lock);
+    h->time_lock = NULL;
 }
 
 static int helper_init(struct helper *h, int free_order)
@@ -360,6 +438,10 @@ static int helper_init(struct helper *h, int free_order)
     h->c_fd = -1;
     h->s_fd = -1;
     h->free_order = free_order;
+    h->time_slip = ossl_time_zero();
+
+    if (!TEST_ptr(h->time_lock = CRYPTO_THREAD_lock_new()))
+        goto err;
 
     if (!TEST_ptr(h->s_streams = lh_STREAM_INFO_new(stream_info_hash,
                                                     stream_info_cmp)))
@@ -397,8 +479,10 @@ static int helper_init(struct helper *h, int free_order)
     if (!BIO_up_ref(h->s_net_bio))
         goto err;
 
-    s_args.net_rbio = h->s_net_bio;
-    s_args.net_wbio = h->s_net_bio;
+    s_args.net_rbio     = h->s_net_bio;
+    s_args.net_wbio     = h->s_net_bio;
+    s_args.now_cb       = get_time;
+    s_args.now_cb_arg   = h;
 
     if (!TEST_ptr(h->s = ossl_quic_tserver_new(&s_args, certfile, keyfile)))
         goto err;
@@ -425,6 +509,10 @@ static int helper_init(struct helper *h, int free_order)
     if (!TEST_ptr(h->c_conn = SSL_new(h->c_ctx)))
         goto err;
 
+    /* Use custom time function for virtual time skip. */
+    if (!TEST_true(ossl_quic_conn_set_override_now_cb(h->c_conn, get_time, h)))
+        goto err;
+
     /* Takes ownership of our reference to the BIO. */
     SSL_set0_rbio(h->c_conn, h->c_net_bio);
     h->c_net_bio_own = NULL;
@@ -1776,6 +1864,91 @@ static const struct script_op script_16[] = {
     OP_END
 };
 
+/* 17. Key update test - unlimited */
+static const struct script_op script_17[] = {
+    OP_C_SET_ALPN           ("ossltest")
+    OP_C_CONNECT_WAIT       ()
+
+    OP_C_WRITE              (DEFAULT, "apple", 5)
+
+    OP_S_BIND_STREAM_ID     (a, C_BIDI_ID(0))
+    OP_S_READ_EXPECT        (a, "apple", 5)
+
+    OP_CHECK                (override_key_update, 1)
+
+    OP_BEGIN_REPEAT         (200)
+
+    OP_C_WRITE              (DEFAULT, "apple", 5)
+    OP_S_READ_EXPECT        (a, "apple", 5)
+
+    /*
+     * TXKU frequency is bounded by RTT because a previous TXKU needs to be
+     * acknowledged by the peer first before another one can be begin. By
+     * waiting this long, we eliminate any such concern and ensure as many key
+     * updates as possible can occur for the purposes of this test.
+     */
+    OP_CHECK                (skip_time_ms,    100)
+
+    OP_END_REPEAT           ()
+
+    /* At least 5 RXKUs detected */
+    OP_CHECK                (check_key_update_ge, 5)
+
+    /*
+     * Prove the connection is still healthy by sending something in both
+     * directions.
+     */
+    OP_C_WRITE              (DEFAULT, "xyzzy", 5)
+    OP_S_READ_EXPECT        (a, "xyzzy", 5)
+
+    OP_S_WRITE              (a, "plugh", 5)
+    OP_C_READ_EXPECT        (DEFAULT, "plugh", 5)
+
+    OP_END
+};
+
+/* 18. Key update test - RTT-bounded */
+static const struct script_op script_18[] = {
+    OP_C_SET_ALPN           ("ossltest")
+    OP_C_CONNECT_WAIT       ()
+
+    OP_C_WRITE              (DEFAULT, "apple", 5)
+
+    OP_S_BIND_STREAM_ID     (a, C_BIDI_ID(0))
+    OP_S_READ_EXPECT        (a, "apple", 5)
+
+    OP_CHECK                (override_key_update, 1)
+
+    OP_BEGIN_REPEAT         (200)
+
+    OP_C_WRITE              (DEFAULT, "apple", 5)
+    OP_S_READ_EXPECT        (a, "apple", 5)
+    OP_CHECK                (skip_time_ms,    2)
+
+    OP_END_REPEAT           ()
+
+    /*
+     * This time we simulate far less time passing between writes, so there are
+     * fewer opportunities to initiate TXKUs. Note that we ask for a TXKU every
+     * 1 packet above, which is absurd; thus this ensures we only actually
+     * generate TXKUs when we are allowed to.
+     */
+    OP_CHECK                (check_key_update_ge, 5)
+    OP_CHECK                (check_key_update_lt, 120)
+
+    /*
+     * Prove the connection is still healthy by sending something in both
+     * directions.
+     */
+    OP_C_WRITE              (DEFAULT, "xyzzy", 5)
+    OP_S_READ_EXPECT        (a, "xyzzy", 5)
+
+    OP_S_WRITE              (a, "plugh", 5)
+    OP_C_READ_EXPECT        (DEFAULT, "plugh", 5)
+
+    OP_END
+};
+
 static const struct script_op *const scripts[] = {
     script_1,
     script_2,
@@ -1793,6 +1966,8 @@ static const struct script_op *const scripts[] = {
     script_14,
     script_15,
     script_16,
+    script_17,
+    script_18,
 };
 
 static int test_script(int idx)
index 3a3aafc655768351b00c0629ddd4245ad5af5161..033c6f587d8002ba10a728024abf729ee0a1eb9a 100644 (file)
@@ -471,6 +471,13 @@ void test_perror(const char *s);
 # define TEST_ulong_gt(a, b)  test_ulong_gt(__FILE__, __LINE__, #a, #b, a, b)
 # define TEST_ulong_ge(a, b)  test_ulong_ge(__FILE__, __LINE__, #a, #b, a, b)
 
+# define TEST_int64_t_eq(a, b)  test_int64_t_eq(__FILE__, __LINE__, #a, #b, a, b)
+# define TEST_int64_t_ne(a, b)  test_int64_t_ne(__FILE__, __LINE__, #a, #b, a, b)
+# define TEST_int64_t_lt(a, b)  test_int64_t_lt(__FILE__, __LINE__, #a, #b, a, b)
+# define TEST_int64_t_le(a, b)  test_int64_t_le(__FILE__, __LINE__, #a, #b, a, b)
+# define TEST_int64_t_gt(a, b)  test_int64_t_gt(__FILE__, __LINE__, #a, #b, a, b)
+# define TEST_int64_t_ge(a, b)  test_int64_t_ge(__FILE__, __LINE__, #a, #b, a, b)
+
 # define TEST_uint64_t_eq(a, b)  test_uint64_t_eq(__FILE__, __LINE__, #a, #b, a, b)
 # define TEST_uint64_t_ne(a, b)  test_uint64_t_ne(__FILE__, __LINE__, #a, #b, a, b)
 # define TEST_uint64_t_lt(a, b)  test_uint64_t_lt(__FILE__, __LINE__, #a, #b, a, b)