openssl/test/dtlstest.c

368 lines
11 KiB
C
Raw Normal View History

/*
* Copyright 2016-2020 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
*/
#include <string.h>
#include <openssl/bio.h>
#include <openssl/crypto.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include "helpers/ssltestlib.h"
#include "testutil.h"
static char *cert = NULL;
static char *privkey = NULL;
static unsigned int timer_cb_count;
#define NUM_TESTS 2
#define DUMMY_CERT_STATUS_LEN 12
static unsigned char certstatus[] = {
SSL3_RT_HANDSHAKE, /* Content type */
0xfe, 0xfd, /* Record version */
0, 1, /* Epoch */
0, 0, 0, 0, 0, 0x0f, /* Record sequence number */
0, DTLS1_HM_HEADER_LENGTH + DUMMY_CERT_STATUS_LEN - 2,
SSL3_MT_CERTIFICATE_STATUS, /* Cert Status handshake message type */
0, 0, DUMMY_CERT_STATUS_LEN, /* Message len */
0, 5, /* Message sequence */
0, 0, 0, /* Fragment offset */
0, 0, DUMMY_CERT_STATUS_LEN - 2, /* Fragment len */
0x80, 0x80, 0x80, 0x80, 0x80,
0x80, 0x80, 0x80, 0x80, 0x80 /* Dummy data */
};
#define RECORD_SEQUENCE 10
static unsigned int timer_cb(SSL *s, unsigned int timer_us)
{
++timer_cb_count;
if (timer_us == 0)
return 50000;
else
return 2 * timer_us;
}
static int test_dtls_unprocessed(int testidx)
{
SSL_CTX *sctx = NULL, *cctx = NULL;
SSL *serverssl1 = NULL, *clientssl1 = NULL;
BIO *c_to_s_fbio, *c_to_s_mempacket;
int testresult = 0;
timer_cb_count = 0;
if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
DTLS_client_method(),
DTLS1_VERSION, 0,
&sctx, &cctx, cert, privkey)))
return 0;
if (!TEST_true(SSL_CTX_set_cipher_list(cctx, "AES128-SHA")))
goto end;
c_to_s_fbio = BIO_new(bio_f_tls_dump_filter());
if (!TEST_ptr(c_to_s_fbio))
goto end;
/* BIO is freed by create_ssl_connection on error */
if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl1, &clientssl1,
NULL, c_to_s_fbio)))
goto end;
DTLS_set_timer_cb(clientssl1, timer_cb);
if (testidx == 1)
certstatus[RECORD_SEQUENCE] = 0xff;
/*
* Inject a dummy record from the next epoch. In test 0, this should never
* get used because the message sequence number is too big. In test 1 we set
* the record sequence number to be way off in the future.
*/
c_to_s_mempacket = SSL_get_wbio(clientssl1);
c_to_s_mempacket = BIO_next(c_to_s_mempacket);
mempacket_test_inject(c_to_s_mempacket, (char *)certstatus,
sizeof(certstatus), 1, INJECT_PACKET_IGNORE_REC_SEQ);
/*
* Create the connection. We use "create_bare_ssl_connection" here so that
* we can force the connection to not do "SSL_read" once partly connected.
* We don't want to accidentally read the dummy records we injected because
* they will fail to decrypt.
*/
if (!TEST_true(create_bare_ssl_connection(serverssl1, clientssl1,
SSL_ERROR_NONE, 0)))
goto end;
if (timer_cb_count == 0) {
printf("timer_callback was not called.\n");
goto end;
}
testresult = 1;
end:
SSL_free(serverssl1);
SSL_free(clientssl1);
SSL_CTX_free(sctx);
SSL_CTX_free(cctx);
return testresult;
}
#define CLI_TO_SRV_EPOCH_0_RECS 3
#define CLI_TO_SRV_EPOCH_1_RECS 1
#if !defined(OPENSSL_NO_EC) || !defined(OPENSSL_NO_DH)
Replace apps/server.pem with certificate with a sha256 signature. It replaces apps/server.pem that used a sha1 signature with a copy of test/certs/servercert.pem that is uses sha256. This caused the dtlstest to start failing. It's testing connection sbetween a dtls client and server. In particular it was checking that if we drop a record that the handshake recovers and still completes successfully. The test iterates a number of times. The first time through it drops the first record. The second time it drops the second one, and so on. In order to do this it has a hard-coded value for the expected number of records it should see in a handshake. That's ok because we completely control both sides of the handshake and know what records we expect to see. Small changes in message size would be tolerated because that is unlikely to have an impact on the number of records. Larger changes in message size however could increase or decrease the number of records and hence cause the test to fail. This particular test uses a mem bio which doesn't have all the CTRLs that the dgram BIO has. When we are using a dgram BIO we query that BIO to determine the MTU size. The smaller the MTU the more fragmented handshakes become. Since the mem BIO doesn't report an MTU we use a rather small default value and get quite a lot of records in our handshake. This has the tendency to increase the likelihood of the number of records changing in the test if the message size changes. It so happens that the new server certificate is smaller than the old one. AFAICT this is probably because the DNs for the Subject and Issuer are significantly shorter than previously. The result is that the number of records used to transmit the Certificate message is one less than it was before. This actually has a knock on impact for subsequent messages and how we fragment them resulting in one less ServerKeyExchange record too (the actual size of the ServerKeyExchange message hasn't changed, but where in that message it gets fragmented has). In total the number of records used in the handshake has decreased by 2 with the new server.pem file. Reviewed-by: Paul Dale <paul.dale@oracle.com> GH: #10784
2020-01-12 23:44:01 +08:00
# define SRV_TO_CLI_EPOCH_0_RECS 10
#else
/*
* In this case we have no ServerKeyExchange message, because we don't have
* ECDHE or DHE. When it is present it gets fragmented into 3 records in this
* test.
*/
# define SRV_TO_CLI_EPOCH_0_RECS 9
#endif
#define SRV_TO_CLI_EPOCH_1_RECS 1
#define TOTAL_FULL_HAND_RECORDS \
(CLI_TO_SRV_EPOCH_0_RECS + CLI_TO_SRV_EPOCH_1_RECS + \
SRV_TO_CLI_EPOCH_0_RECS + SRV_TO_CLI_EPOCH_1_RECS)
#define CLI_TO_SRV_RESUME_EPOCH_0_RECS 3
#define CLI_TO_SRV_RESUME_EPOCH_1_RECS 1
#define SRV_TO_CLI_RESUME_EPOCH_0_RECS 2
#define SRV_TO_CLI_RESUME_EPOCH_1_RECS 1
#define TOTAL_RESUME_HAND_RECORDS \
(CLI_TO_SRV_RESUME_EPOCH_0_RECS + CLI_TO_SRV_RESUME_EPOCH_1_RECS + \
SRV_TO_CLI_RESUME_EPOCH_0_RECS + SRV_TO_CLI_RESUME_EPOCH_1_RECS)
#define TOTAL_RECORDS (TOTAL_FULL_HAND_RECORDS + TOTAL_RESUME_HAND_RECORDS)
/*
* We are assuming a ServerKeyExchange message is sent in this test. If we don't
* have either DH or EC, then it won't be
*/
#if !defined(OPENSSL_NO_DH) || !defined(OPENSSL_NO_EC)
static int test_dtls_drop_records(int idx)
{
SSL_CTX *sctx = NULL, *cctx = NULL;
SSL *serverssl = NULL, *clientssl = NULL;
BIO *c_to_s_fbio, *mempackbio;
int testresult = 0;
int epoch = 0;
SSL_SESSION *sess = NULL;
int cli_to_srv_epoch0, cli_to_srv_epoch1, srv_to_cli_epoch0;
if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
DTLS_client_method(),
DTLS1_VERSION, 0,
&sctx, &cctx, cert, privkey)))
return 0;
if (!TEST_true(SSL_CTX_set_dh_auto(sctx, 1)))
goto end;
if (idx >= TOTAL_FULL_HAND_RECORDS) {
/* We're going to do a resumption handshake. Get a session first. */
if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl,
NULL, NULL))
|| !TEST_true(create_ssl_connection(serverssl, clientssl,
SSL_ERROR_NONE))
|| !TEST_ptr(sess = SSL_get1_session(clientssl)))
goto end;
SSL_shutdown(clientssl);
SSL_shutdown(serverssl);
SSL_free(serverssl);
SSL_free(clientssl);
serverssl = clientssl = NULL;
cli_to_srv_epoch0 = CLI_TO_SRV_RESUME_EPOCH_0_RECS;
cli_to_srv_epoch1 = CLI_TO_SRV_RESUME_EPOCH_1_RECS;
srv_to_cli_epoch0 = SRV_TO_CLI_RESUME_EPOCH_0_RECS;
idx -= TOTAL_FULL_HAND_RECORDS;
} else {
cli_to_srv_epoch0 = CLI_TO_SRV_EPOCH_0_RECS;
cli_to_srv_epoch1 = CLI_TO_SRV_EPOCH_1_RECS;
srv_to_cli_epoch0 = SRV_TO_CLI_EPOCH_0_RECS;
}
c_to_s_fbio = BIO_new(bio_f_tls_dump_filter());
if (!TEST_ptr(c_to_s_fbio))
goto end;
/* BIO is freed by create_ssl_connection on error */
if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl,
NULL, c_to_s_fbio)))
goto end;
if (sess != NULL) {
if (!TEST_true(SSL_set_session(clientssl, sess)))
goto end;
}
DTLS_set_timer_cb(clientssl, timer_cb);
DTLS_set_timer_cb(serverssl, timer_cb);
/* Work out which record to drop based on the test number */
if (idx >= cli_to_srv_epoch0 + cli_to_srv_epoch1) {
mempackbio = SSL_get_wbio(serverssl);
idx -= cli_to_srv_epoch0 + cli_to_srv_epoch1;
if (idx >= srv_to_cli_epoch0) {
epoch = 1;
idx -= srv_to_cli_epoch0;
}
} else {
mempackbio = SSL_get_wbio(clientssl);
if (idx >= cli_to_srv_epoch0) {
epoch = 1;
idx -= cli_to_srv_epoch0;
}
mempackbio = BIO_next(mempackbio);
}
BIO_ctrl(mempackbio, MEMPACKET_CTRL_SET_DROP_EPOCH, epoch, NULL);
BIO_ctrl(mempackbio, MEMPACKET_CTRL_SET_DROP_REC, idx, NULL);
if (!TEST_true(create_ssl_connection(serverssl, clientssl, SSL_ERROR_NONE)))
goto end;
if (sess != NULL && !TEST_true(SSL_session_reused(clientssl)))
goto end;
/* If the test did what we planned then it should have dropped a record */
if (!TEST_int_eq((int)BIO_ctrl(mempackbio, MEMPACKET_CTRL_GET_DROP_REC, 0,
NULL), -1))
goto end;
testresult = 1;
end:
SSL_SESSION_free(sess);
SSL_free(serverssl);
SSL_free(clientssl);
SSL_CTX_free(sctx);
SSL_CTX_free(cctx);
return testresult;
}
#endif /* !defined(OPENSSL_NO_DH) || !defined(OPENSSL_NO_EC) */
static const char dummy_cookie[] = "0123456";
static int generate_cookie_cb(SSL *ssl, unsigned char *cookie,
unsigned int *cookie_len)
{
memcpy(cookie, dummy_cookie, sizeof(dummy_cookie));
*cookie_len = sizeof(dummy_cookie);
return 1;
}
static int verify_cookie_cb(SSL *ssl, const unsigned char *cookie,
unsigned int cookie_len)
{
return TEST_mem_eq(cookie, cookie_len, dummy_cookie, sizeof(dummy_cookie));
}
static int test_cookie(void)
{
SSL_CTX *sctx = NULL, *cctx = NULL;
SSL *serverssl = NULL, *clientssl = NULL;
int testresult = 0;
if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
DTLS_client_method(),
DTLS1_VERSION, 0,
&sctx, &cctx, cert, privkey)))
return 0;
SSL_CTX_set_options(sctx, SSL_OP_COOKIE_EXCHANGE);
SSL_CTX_set_cookie_generate_cb(sctx, generate_cookie_cb);
SSL_CTX_set_cookie_verify_cb(sctx, verify_cookie_cb);
if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl,
NULL, NULL))
|| !TEST_true(create_ssl_connection(serverssl, clientssl,
SSL_ERROR_NONE)))
goto end;
testresult = 1;
end:
SSL_free(serverssl);
SSL_free(clientssl);
SSL_CTX_free(sctx);
SSL_CTX_free(cctx);
return testresult;
}
static int test_dtls_duplicate_records(void)
{
SSL_CTX *sctx = NULL, *cctx = NULL;
SSL *serverssl = NULL, *clientssl = NULL;
int testresult = 0;
if (!TEST_true(create_ssl_ctx_pair(NULL, DTLS_server_method(),
DTLS_client_method(),
DTLS1_VERSION, 0,
&sctx, &cctx, cert, privkey)))
return 0;
if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl,
NULL, NULL)))
goto end;
DTLS_set_timer_cb(clientssl, timer_cb);
DTLS_set_timer_cb(serverssl, timer_cb);
BIO_ctrl(SSL_get_wbio(clientssl), MEMPACKET_CTRL_SET_DUPLICATE_REC, 1, NULL);
BIO_ctrl(SSL_get_wbio(serverssl), MEMPACKET_CTRL_SET_DUPLICATE_REC, 1, NULL);
if (!TEST_true(create_ssl_connection(serverssl, clientssl, SSL_ERROR_NONE)))
goto end;
testresult = 1;
end:
SSL_free(serverssl);
SSL_free(clientssl);
SSL_CTX_free(sctx);
SSL_CTX_free(cctx);
return testresult;
}
OPT_TEST_DECLARE_USAGE("certfile privkeyfile\n")
int setup_tests(void)
{
if (!test_skip_common_options()) {
TEST_error("Error parsing test options\n");
return 0;
}
if (!TEST_ptr(cert = test_get_argument(0))
|| !TEST_ptr(privkey = test_get_argument(1)))
return 0;
ADD_ALL_TESTS(test_dtls_unprocessed, NUM_TESTS);
#if !defined(OPENSSL_NO_DH) || !defined(OPENSSL_NO_EC)
ADD_ALL_TESTS(test_dtls_drop_records, TOTAL_RECORDS);
#endif
ADD_TEST(test_cookie);
ADD_TEST(test_dtls_duplicate_records);
return 1;
}
void cleanup_tests(void)
{
bio_f_tls_dump_filter_free();
bio_s_mempacket_test_free();
}