Home | History | Annotate | Line # | Download | only in test
quicfaultstest.c revision 1.1
      1 /*
      2  * Copyright 2022-2024 The OpenSSL Project Authors. All Rights Reserved.
      3  *
      4  * Licensed under the Apache License 2.0 (the "License").  You may not use
      5  * this file except in compliance with the License.  You can obtain a copy
      6  * in the file LICENSE in the source distribution or at
      7  * https://www.openssl.org/source/license.html
      8  */
      9 
     10 #include <string.h>
     11 #include <openssl/ssl.h>
     12 #include "helpers/quictestlib.h"
     13 #include "internal/quic_error.h"
     14 #include "testutil.h"
     15 
     16 static char *cert = NULL;
     17 static char *privkey = NULL;
     18 
     19 /*
     20  * Basic test that just creates a connection and sends some data without any
     21  * faults injected.
     22  */
     23 static int test_basic(void)
     24 {
     25     int testresult = 0;
     26     SSL_CTX *cctx = SSL_CTX_new(OSSL_QUIC_client_method());
     27     QUIC_TSERVER *qtserv = NULL;
     28     SSL *cssl = NULL;
     29     char *msg = "Hello World!";
     30     size_t msglen = strlen(msg);
     31     unsigned char buf[80];
     32     size_t bytesread;
     33 
     34     if (!TEST_ptr(cctx))
     35         goto err;
     36 
     37     if (!TEST_true(qtest_create_quic_objects(NULL, cctx, NULL, cert, privkey, 0,
     38                                              &qtserv, &cssl, NULL, NULL)))
     39         goto err;
     40 
     41     if (!TEST_true(qtest_create_quic_connection(qtserv, cssl)))
     42         goto err;
     43 
     44     if (!TEST_int_eq(SSL_write(cssl, msg, msglen), msglen))
     45         goto err;
     46 
     47     ossl_quic_tserver_tick(qtserv);
     48     if (!TEST_true(ossl_quic_tserver_read(qtserv, 0, buf, sizeof(buf), &bytesread)))
     49         goto err;
     50 
     51     /*
     52      * We assume the entire message is read from the server in one go. In
     53      * theory this could get fragmented but its a small message so we assume
     54      * not.
     55      */
     56     if (!TEST_mem_eq(msg, msglen, buf, bytesread))
     57         goto err;
     58 
     59     testresult = 1;
     60  err:
     61     SSL_free(cssl);
     62     ossl_quic_tserver_free(qtserv);
     63     SSL_CTX_free(cctx);
     64     return testresult;
     65 }
     66 
     67 /*
     68  * Test that adding an unknown frame type is handled correctly
     69  */
     70 static int add_unknown_frame_cb(QTEST_FAULT *fault, QUIC_PKT_HDR *hdr,
     71                                 unsigned char *buf, size_t len, void *cbarg)
     72 {
     73     static size_t done = 0;
     74     /*
     75      * There are no "reserved" frame types which are definitately safe for us
     76      * to use for testing purposes - but we just use the highest possible
     77      * value (8 byte length integer) and with no payload bytes
     78      */
     79     unsigned char unknown_frame[] = {
     80         0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
     81     };
     82 
     83     /* We only ever add the unknown frame to one packet */
     84     if (done++)
     85         return 1;
     86 
     87     return qtest_fault_prepend_frame(fault, unknown_frame,
     88                                      sizeof(unknown_frame));
     89 }
     90 
     91 static int test_unknown_frame(void)
     92 {
     93     int testresult = 0, ret;
     94     SSL_CTX *cctx = SSL_CTX_new(OSSL_QUIC_client_method());
     95     QUIC_TSERVER *qtserv = NULL;
     96     SSL *cssl = NULL;
     97     char *msg = "Hello World!";
     98     size_t msglen = strlen(msg);
     99     unsigned char buf[80];
    100     size_t byteswritten;
    101     QTEST_FAULT *fault = NULL;
    102     uint64_t sid = UINT64_MAX;
    103 
    104     if (!TEST_ptr(cctx))
    105         goto err;
    106 
    107     if (!TEST_true(qtest_create_quic_objects(NULL, cctx, NULL, cert, privkey, 0,
    108                                              &qtserv, &cssl, &fault, NULL)))
    109         goto err;
    110 
    111     if (!TEST_true(qtest_create_quic_connection(qtserv, cssl)))
    112         goto err;
    113 
    114     /*
    115      * Write a message from the server to the client and add an unknown frame
    116      * type
    117      */
    118     if (!TEST_true(qtest_fault_set_packet_plain_listener(fault,
    119                                                          add_unknown_frame_cb,
    120                                                          NULL)))
    121         goto err;
    122 
    123     if (!TEST_true(ossl_quic_tserver_stream_new(qtserv, /*is_uni=*/0, &sid))
    124         || !TEST_uint64_t_eq(sid, 1))
    125         goto err;
    126 
    127     if (!TEST_true(ossl_quic_tserver_write(qtserv, sid, (unsigned char *)msg, msglen,
    128                                            &byteswritten)))
    129         goto err;
    130 
    131     if (!TEST_size_t_eq(msglen, byteswritten))
    132         goto err;
    133 
    134     ossl_quic_tserver_tick(qtserv);
    135     if (!TEST_true(SSL_handle_events(cssl)))
    136         goto err;
    137 
    138     if (!TEST_int_le(ret = SSL_read(cssl, buf, sizeof(buf)), 0))
    139         goto err;
    140 
    141     if (!TEST_int_eq(SSL_get_error(cssl, ret), SSL_ERROR_SSL))
    142         goto err;
    143 
    144     if (!TEST_int_eq(ERR_GET_REASON(ERR_peek_error()),
    145                      SSL_R_QUIC_PROTOCOL_ERROR))
    146         goto err;
    147 
    148     if (!TEST_true(qtest_check_server_frame_encoding_err(qtserv)))
    149         goto err;
    150 
    151     testresult = 1;
    152  err:
    153     qtest_fault_free(fault);
    154     SSL_free(cssl);
    155     ossl_quic_tserver_free(qtserv);
    156     SSL_CTX_free(cctx);
    157     return testresult;
    158 }
    159 
    160 /*
    161  * Test that a server that fails to provide transport params cannot be
    162  * connected to.
    163  */
    164 static int drop_extensions_cb(QTEST_FAULT *fault,
    165                                     QTEST_ENCRYPTED_EXTENSIONS *ee,
    166                                     size_t eelen, void *encextcbarg)
    167 {
    168     int *ext = (int *)encextcbarg;
    169 
    170     if (!qtest_fault_delete_extension(fault, *ext, ee->extensions,
    171                                       &ee->extensionslen, NULL))
    172         return 0;
    173 
    174     return 1;
    175 }
    176 
    177 static int test_drop_extensions(int idx)
    178 {
    179     int testresult = 0;
    180     SSL_CTX *cctx = SSL_CTX_new(OSSL_QUIC_client_method());
    181     QUIC_TSERVER *qtserv = NULL;
    182     SSL *cssl = NULL;
    183     QTEST_FAULT *fault = NULL;
    184     int ext, err;
    185 
    186     if (!TEST_ptr(cctx))
    187         goto err;
    188 
    189     if (!TEST_true(qtest_create_quic_objects(NULL, cctx, NULL, cert, privkey, 0,
    190                                              &qtserv, &cssl, &fault, NULL)))
    191         goto err;
    192 
    193     if (idx == 0) {
    194         ext = TLSEXT_TYPE_quic_transport_parameters;
    195         err = OSSL_QUIC_ERR_CRYPTO_MISSING_EXT;
    196     } else {
    197         ext = TLSEXT_TYPE_application_layer_protocol_negotiation;
    198         err = OSSL_QUIC_ERR_CRYPTO_NO_APP_PROTO;
    199     }
    200 
    201     if (!TEST_true(qtest_fault_set_hand_enc_ext_listener(fault,
    202                                                          drop_extensions_cb,
    203                                                          &ext)))
    204         goto err;
    205 
    206     /*
    207      * We expect the connection to fail because the server failed to provide
    208      * transport parameters
    209      */
    210     if (!TEST_false(qtest_create_quic_connection(qtserv, cssl)))
    211         goto err;
    212 
    213     if (!TEST_true(qtest_check_server_transport_err(qtserv, err)))
    214         goto err;
    215 
    216     testresult = 1;
    217  err:
    218     qtest_fault_free(fault);
    219     SSL_free(cssl);
    220     ossl_quic_tserver_free(qtserv);
    221     SSL_CTX_free(cctx);
    222     return testresult;
    223 }
    224 
    225 /*
    226  * Test that corrupted packets/datagrams are dropped and retransmitted
    227  */
    228 static int docorrupt = 0;
    229 
    230 static int on_packet_cipher_cb(QTEST_FAULT *fault, QUIC_PKT_HDR *hdr,
    231                                unsigned char *buf, size_t len, void *cbarg)
    232 {
    233     if (!docorrupt || len == 0)
    234         return 1;
    235 
    236     buf[(size_t)test_random() % len] ^= 0xff;
    237     docorrupt = 0;
    238 
    239     return 1;
    240 }
    241 
    242 static int on_datagram_cb(QTEST_FAULT *fault, BIO_MSG *m, size_t stride,
    243                           void *cbarg)
    244 {
    245     if (!docorrupt || m->data_len == 0)
    246         return 1;
    247 
    248     if (!qtest_fault_resize_datagram(fault, m->data_len - 1))
    249         return 1;
    250 
    251     docorrupt = 0;
    252 
    253     return 1;
    254 }
    255 
    256 /*
    257  * Test 1: Corrupt by flipping bits in an encrypted packet
    258  * Test 2: Corrupt by truncating an entire datagram
    259  */
    260 static int test_corrupted_data(int idx)
    261 {
    262     QTEST_FAULT *fault = NULL;
    263     int testresult = 0;
    264     SSL_CTX *cctx = SSL_CTX_new(OSSL_QUIC_client_method());
    265     QUIC_TSERVER *qtserv = NULL;
    266     SSL *cssl = NULL;
    267     char *msg = "Hello World!";
    268     size_t msglen = strlen(msg);
    269     unsigned char buf[80];
    270     size_t bytesread, byteswritten;
    271     uint64_t sid = UINT64_MAX;
    272 
    273     if (!TEST_ptr(cctx))
    274         goto err;
    275 
    276     if (!TEST_true(qtest_create_quic_objects(NULL, cctx, NULL, cert, privkey,
    277                                              QTEST_FLAG_FAKE_TIME, &qtserv,
    278                                              &cssl, &fault, NULL)))
    279         goto err;
    280 
    281     if (idx == 0) {
    282         /* Listen for encrypted packets being sent */
    283         if (!TEST_true(qtest_fault_set_packet_cipher_listener(fault,
    284                                                               on_packet_cipher_cb,
    285                                                               NULL)))
    286             goto err;
    287     } else {
    288         /* Listen for datagrams being sent */
    289         if (!TEST_true(qtest_fault_set_datagram_listener(fault,
    290                                                          on_datagram_cb,
    291                                                          NULL)))
    292             goto err;
    293     }
    294     if (!TEST_true(qtest_create_quic_connection(qtserv, cssl)))
    295         goto err;
    296 
    297     /* Corrupt the next server packet*/
    298     docorrupt = 1;
    299 
    300     if (!TEST_true(ossl_quic_tserver_stream_new(qtserv, /*is_uni=*/0, &sid))
    301         || !TEST_uint64_t_eq(sid, 1))
    302         goto err;
    303 
    304     /*
    305      * Send first 5 bytes of message. This will get corrupted and is treated as
    306      * "lost"
    307      */
    308     if (!TEST_true(ossl_quic_tserver_write(qtserv, sid, (unsigned char *)msg, 5,
    309                                            &byteswritten)))
    310         goto err;
    311 
    312     if (!TEST_size_t_eq(byteswritten, 5))
    313         goto err;
    314 
    315     /*
    316      * Introduce a small delay so that the above packet has time to be detected
    317      * as lost. Loss detection times are based on RTT which should be very
    318      * fast for us since there isn't really a network. The loss delay timer is
    319      * always at least 1ms though. We skip forward 100ms
    320      */
    321     qtest_add_time(100);
    322 
    323     /* Send rest of message */
    324     if (!TEST_true(ossl_quic_tserver_write(qtserv, sid, (unsigned char *)msg + 5,
    325                                            msglen - 5, &byteswritten)))
    326         goto err;
    327 
    328     if (!TEST_size_t_eq(byteswritten, msglen - 5))
    329         goto err;
    330 
    331     /*
    332      * Receive the corrupted packet. This should get dropped and is effectively
    333      * "lost". We also process the second packet which should be decrypted
    334      * successfully. Therefore we ack the frames in it
    335      */
    336     if (!TEST_true(SSL_handle_events(cssl)))
    337         goto err;
    338 
    339     /*
    340      * Process the ack. Detect that the first part of the message must have
    341      * been lost due to the time elapsed since it was sent and resend it
    342      */
    343     ossl_quic_tserver_tick(qtserv);
    344 
    345     /* Receive and process the newly arrived message data resend */
    346     if (!TEST_true(SSL_handle_events(cssl)))
    347         goto err;
    348 
    349     /* The whole message should now have arrived */
    350     if (!TEST_true(SSL_read_ex(cssl, buf, sizeof(buf), &bytesread)))
    351         goto err;
    352 
    353     if (!TEST_mem_eq(msg, msglen, buf, bytesread))
    354         goto err;
    355 
    356     /*
    357      * If the test was successful then we corrupted exactly one packet and
    358      * docorrupt was reset
    359      */
    360     if (!TEST_false(docorrupt))
    361         goto err;
    362 
    363     testresult = 1;
    364  err:
    365     qtest_fault_free(fault);
    366     SSL_free(cssl);
    367     ossl_quic_tserver_free(qtserv);
    368     SSL_CTX_free(cctx);
    369     return testresult;
    370 }
    371 
    372 OPT_TEST_DECLARE_USAGE("certsdir\n")
    373 
    374 int setup_tests(void)
    375 {
    376     char *certsdir = NULL;
    377 
    378     if (!test_skip_common_options()) {
    379         TEST_error("Error parsing test options\n");
    380         return 0;
    381     }
    382 
    383     if (!TEST_ptr(certsdir = test_get_argument(0)))
    384         return 0;
    385 
    386     cert = test_mk_file_path(certsdir, "servercert.pem");
    387     if (cert == NULL)
    388         goto err;
    389 
    390     privkey = test_mk_file_path(certsdir, "serverkey.pem");
    391     if (privkey == NULL)
    392         goto err;
    393 
    394     ADD_TEST(test_basic);
    395     ADD_TEST(test_unknown_frame);
    396     ADD_ALL_TESTS(test_drop_extensions, 2);
    397     ADD_ALL_TESTS(test_corrupted_data, 2);
    398 
    399     return 1;
    400 
    401  err:
    402     OPENSSL_free(cert);
    403     OPENSSL_free(privkey);
    404     return 0;
    405 }
    406 
    407 void cleanup_tests(void)
    408 {
    409     OPENSSL_free(cert);
    410     OPENSSL_free(privkey);
    411 }
    412