8e2236eff8
Certain callback APIs allow the callback to request async processing by trickling a particular error value up the stack to the application as an error return from the handshake function. In those cases, SSL_want() returns a code specific to the type of async processing needed. The create_ssl_connection() helper function for the tests is very helpful for several things, including creating API tests. However, it does not currently let us test the async processing functionality of these callback interfaces, because the special SSL error codes are treated as generic errors and the helper continues to loop until it reaches its maximum iteration count. Add a new parameter, 'want', that indicates an expected/desired special SSL error code, so that the helper will terminate when either side reports that error, giving control back to the calling function and allowing the test to proceed. Reviewed-by: Matt Caswell <matt@openssl.org> Reviewed-by: Richard Levitte <levitte@openssl.org> (Merged from https://github.com/openssl/openssl/pull/2279)
397 lines
12 KiB
C
397 lines
12 KiB
C
/*
|
|
* Copyright 2016 The OpenSSL Project Authors. All Rights Reserved.
|
|
*
|
|
* Licensed under the OpenSSL licenses, (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
* https://www.openssl.org/source/license.html
|
|
* or in the file LICENSE in the source distribution.
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <openssl/ssl.h>
|
|
#include <openssl/bio.h>
|
|
#include <openssl/err.h>
|
|
|
|
#include "../ssl/packet_locl.h"
|
|
|
|
#include "ssltestlib.h"
|
|
|
|
/* Should we fragment records or not? 0 = no, !0 = yes*/
|
|
static int fragment = 0;
|
|
|
|
static int async_new(BIO *bi);
|
|
static int async_free(BIO *a);
|
|
static int async_read(BIO *b, char *out, int outl);
|
|
static int async_write(BIO *b, const char *in, int inl);
|
|
static long async_ctrl(BIO *b, int cmd, long num, void *ptr);
|
|
static int async_gets(BIO *bp, char *buf, int size);
|
|
static int async_puts(BIO *bp, const char *str);
|
|
|
|
/* Choose a sufficiently large type likely to be unused for this custom BIO */
|
|
# define BIO_TYPE_ASYNC_FILTER (0x80 | BIO_TYPE_FILTER)
|
|
|
|
static BIO_METHOD *methods_async = NULL;
|
|
|
|
struct async_ctrs {
|
|
unsigned int rctr;
|
|
unsigned int wctr;
|
|
};
|
|
|
|
static const BIO_METHOD *bio_f_async_filter()
|
|
{
|
|
if (methods_async == NULL) {
|
|
methods_async = BIO_meth_new(BIO_TYPE_ASYNC_FILTER, "Async filter");
|
|
if ( methods_async == NULL
|
|
|| !BIO_meth_set_write(methods_async, async_write)
|
|
|| !BIO_meth_set_read(methods_async, async_read)
|
|
|| !BIO_meth_set_puts(methods_async, async_puts)
|
|
|| !BIO_meth_set_gets(methods_async, async_gets)
|
|
|| !BIO_meth_set_ctrl(methods_async, async_ctrl)
|
|
|| !BIO_meth_set_create(methods_async, async_new)
|
|
|| !BIO_meth_set_destroy(methods_async, async_free))
|
|
return NULL;
|
|
}
|
|
return methods_async;
|
|
}
|
|
|
|
static int async_new(BIO *bio)
|
|
{
|
|
struct async_ctrs *ctrs;
|
|
|
|
ctrs = OPENSSL_zalloc(sizeof(struct async_ctrs));
|
|
if (ctrs == NULL)
|
|
return 0;
|
|
|
|
BIO_set_data(bio, ctrs);
|
|
BIO_set_init(bio, 1);
|
|
return 1;
|
|
}
|
|
|
|
static int async_free(BIO *bio)
|
|
{
|
|
struct async_ctrs *ctrs;
|
|
|
|
if (bio == NULL)
|
|
return 0;
|
|
ctrs = BIO_get_data(bio);
|
|
OPENSSL_free(ctrs);
|
|
BIO_set_data(bio, NULL);
|
|
BIO_set_init(bio, 0);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int async_read(BIO *bio, char *out, int outl)
|
|
{
|
|
struct async_ctrs *ctrs;
|
|
int ret = 0;
|
|
BIO *next = BIO_next(bio);
|
|
|
|
if (outl <= 0)
|
|
return 0;
|
|
if (next == NULL)
|
|
return 0;
|
|
|
|
ctrs = BIO_get_data(bio);
|
|
|
|
BIO_clear_retry_flags(bio);
|
|
|
|
if (ctrs->rctr > 0) {
|
|
ret = BIO_read(next, out, 1);
|
|
if (ret <= 0 && BIO_should_read(next))
|
|
BIO_set_retry_read(bio);
|
|
ctrs->rctr = 0;
|
|
} else {
|
|
ctrs->rctr++;
|
|
BIO_set_retry_read(bio);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#define MIN_RECORD_LEN 6
|
|
|
|
#define CONTENTTYPEPOS 0
|
|
#define VERSIONHIPOS 1
|
|
#define VERSIONLOPOS 2
|
|
#define DATAPOS 5
|
|
|
|
static int async_write(BIO *bio, const char *in, int inl)
|
|
{
|
|
struct async_ctrs *ctrs;
|
|
int ret = 0;
|
|
size_t written = 0;
|
|
BIO *next = BIO_next(bio);
|
|
|
|
if (inl <= 0)
|
|
return 0;
|
|
if (next == NULL)
|
|
return 0;
|
|
|
|
ctrs = BIO_get_data(bio);
|
|
|
|
BIO_clear_retry_flags(bio);
|
|
|
|
if (ctrs->wctr > 0) {
|
|
ctrs->wctr = 0;
|
|
if (fragment) {
|
|
PACKET pkt;
|
|
|
|
if (!PACKET_buf_init(&pkt, (const unsigned char *)in, inl))
|
|
abort();
|
|
|
|
while (PACKET_remaining(&pkt) > 0) {
|
|
PACKET payload, wholebody;
|
|
unsigned int contenttype, versionhi, versionlo, data;
|
|
unsigned int msgtype = 0, negversion = 0;
|
|
|
|
if ( !PACKET_get_1(&pkt, &contenttype)
|
|
|| !PACKET_get_1(&pkt, &versionhi)
|
|
|| !PACKET_get_1(&pkt, &versionlo)
|
|
|| !PACKET_get_length_prefixed_2(&pkt, &payload))
|
|
abort();
|
|
|
|
/* Pretend we wrote out the record header */
|
|
written += SSL3_RT_HEADER_LENGTH;
|
|
|
|
wholebody = payload;
|
|
if (contenttype == SSL3_RT_HANDSHAKE
|
|
&& !PACKET_get_1(&wholebody, &msgtype))
|
|
abort();
|
|
|
|
if (msgtype == SSL3_MT_SERVER_HELLO
|
|
&& (!PACKET_forward(&wholebody,
|
|
SSL3_HM_HEADER_LENGTH - 1)
|
|
|| !PACKET_get_net_2(&wholebody, &negversion)))
|
|
abort();
|
|
|
|
while (PACKET_get_1(&payload, &data)) {
|
|
/* Create a new one byte long record for each byte in the
|
|
* record in the input buffer
|
|
*/
|
|
char smallrec[MIN_RECORD_LEN] = {
|
|
0, /* Content type */
|
|
0, /* Version hi */
|
|
0, /* Version lo */
|
|
0, /* Length hi */
|
|
1, /* Length lo */
|
|
0 /* Data */
|
|
};
|
|
|
|
smallrec[CONTENTTYPEPOS] = contenttype;
|
|
smallrec[VERSIONHIPOS] = versionhi;
|
|
smallrec[VERSIONLOPOS] = versionlo;
|
|
smallrec[DATAPOS] = data;
|
|
ret = BIO_write(next, smallrec, MIN_RECORD_LEN);
|
|
if (ret <= 0)
|
|
abort();
|
|
written++;
|
|
}
|
|
/*
|
|
* We can't fragment anything after the ServerHello (or CCS <=
|
|
* TLS1.2), otherwise we get a bad record MAC
|
|
* TODO(TLS1.3): Change TLS1_3_VERSION_DRAFT to TLS1_3_VERSION
|
|
* before release
|
|
*/
|
|
if (contenttype == SSL3_RT_CHANGE_CIPHER_SPEC
|
|
|| (negversion == TLS1_3_VERSION_DRAFT
|
|
&& msgtype == SSL3_MT_SERVER_HELLO)) {
|
|
fragment = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/* Write any data we have left after fragmenting */
|
|
ret = 0;
|
|
if ((int)written < inl) {
|
|
ret = BIO_write(next, in + written, inl - written);
|
|
}
|
|
|
|
if (ret <= 0 && BIO_should_write(next))
|
|
BIO_set_retry_write(bio);
|
|
else
|
|
ret += written;
|
|
} else {
|
|
ctrs->wctr++;
|
|
BIO_set_retry_write(bio);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static long async_ctrl(BIO *bio, int cmd, long num, void *ptr)
|
|
{
|
|
long ret;
|
|
BIO *next = BIO_next(bio);
|
|
|
|
if (next == NULL)
|
|
return 0;
|
|
|
|
switch (cmd) {
|
|
case BIO_CTRL_DUP:
|
|
ret = 0L;
|
|
break;
|
|
default:
|
|
ret = BIO_ctrl(next, cmd, num, ptr);
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int async_gets(BIO *bio, char *buf, int size)
|
|
{
|
|
/* We don't support this - not needed anyway */
|
|
return -1;
|
|
}
|
|
|
|
static int async_puts(BIO *bio, const char *str)
|
|
{
|
|
return async_write(bio, str, strlen(str));
|
|
}
|
|
|
|
#define MAX_ATTEMPTS 100
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
SSL_CTX *serverctx = NULL, *clientctx = NULL;
|
|
SSL *serverssl = NULL, *clientssl = NULL;
|
|
BIO *s_to_c_fbio = NULL, *c_to_s_fbio = NULL;
|
|
int test, err = 1, ret;
|
|
size_t i, j;
|
|
const char testdata[] = "Test data";
|
|
char buf[sizeof(testdata)];
|
|
|
|
CRYPTO_set_mem_debug(1);
|
|
CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON);
|
|
|
|
if (argc != 3) {
|
|
printf("Invalid argument count\n");
|
|
goto end;
|
|
}
|
|
|
|
if (!create_ssl_ctx_pair(TLS_server_method(), TLS_client_method(),
|
|
&serverctx, &clientctx, argv[1], argv[2])) {
|
|
printf("Failed to create SSL_CTX pair\n");
|
|
goto end;
|
|
}
|
|
|
|
/*
|
|
* We do 2 test runs. The first time around we just do a normal handshake
|
|
* with lots of async io going on. The second time around we also break up
|
|
* all records so that the content is only one byte length (up until the
|
|
* CCS)
|
|
*/
|
|
for (test = 1; test < 3; test++) {
|
|
if (test == 2)
|
|
fragment = 1;
|
|
|
|
|
|
s_to_c_fbio = BIO_new(bio_f_async_filter());
|
|
c_to_s_fbio = BIO_new(bio_f_async_filter());
|
|
if (s_to_c_fbio == NULL || c_to_s_fbio == NULL) {
|
|
printf("Failed to create filter BIOs\n");
|
|
BIO_free(s_to_c_fbio);
|
|
BIO_free(c_to_s_fbio);
|
|
goto end;
|
|
}
|
|
|
|
/* BIOs get freed on error */
|
|
if (!create_ssl_objects(serverctx, clientctx, &serverssl, &clientssl,
|
|
s_to_c_fbio, c_to_s_fbio)) {
|
|
printf("Test %d failed: Create SSL objects failed\n", test);
|
|
goto end;
|
|
}
|
|
|
|
if (!create_ssl_connection(serverssl, clientssl, SSL_ERROR_NONE)) {
|
|
printf("Test %d failed: Create SSL connection failed\n", test);
|
|
goto end;
|
|
}
|
|
|
|
/*
|
|
* Send and receive some test data. Do the whole thing twice to ensure
|
|
* we hit at least one async event in both reading and writing
|
|
*/
|
|
for (j = 0; j < 2; j++) {
|
|
int len;
|
|
|
|
/*
|
|
* Write some test data. It should never take more than 2 attempts
|
|
* (the first one might be a retryable fail).
|
|
*/
|
|
for (ret = -1, i = 0, len = 0; len != sizeof(testdata) && i < 2;
|
|
i++) {
|
|
ret = SSL_write(clientssl, testdata + len,
|
|
sizeof(testdata) - len);
|
|
if (ret > 0) {
|
|
len += ret;
|
|
} else {
|
|
int ssl_error = SSL_get_error(clientssl, ret);
|
|
|
|
if (ssl_error == SSL_ERROR_SYSCALL ||
|
|
ssl_error == SSL_ERROR_SSL) {
|
|
printf("Test %d failed: Failed to write app data\n", test);
|
|
err = -1;
|
|
goto end;
|
|
}
|
|
}
|
|
}
|
|
if (len != sizeof(testdata)) {
|
|
err = -1;
|
|
printf("Test %d failed: Failed to write all app data\n", test);
|
|
goto end;
|
|
}
|
|
/*
|
|
* Now read the test data. It may take more attemps here because
|
|
* it could fail once for each byte read, including all overhead
|
|
* bytes from the record header/padding etc.
|
|
*/
|
|
for (ret = -1, i = 0, len = 0; len != sizeof(testdata) &&
|
|
i < MAX_ATTEMPTS; i++)
|
|
{
|
|
ret = SSL_read(serverssl, buf + len, sizeof(buf) - len);
|
|
if (ret > 0) {
|
|
len += ret;
|
|
} else {
|
|
int ssl_error = SSL_get_error(serverssl, ret);
|
|
|
|
if (ssl_error == SSL_ERROR_SYSCALL ||
|
|
ssl_error == SSL_ERROR_SSL) {
|
|
printf("Test %d failed: Failed to read app data\n", test);
|
|
err = -1;
|
|
goto end;
|
|
}
|
|
}
|
|
}
|
|
if (len != sizeof(testdata)
|
|
|| memcmp(buf, testdata, sizeof(testdata)) != 0) {
|
|
err = -1;
|
|
printf("Test %d failed: Unexpected app data received\n", test);
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
/* Also frees the BIOs */
|
|
SSL_free(clientssl);
|
|
SSL_free(serverssl);
|
|
clientssl = serverssl = NULL;
|
|
}
|
|
|
|
printf("Test success\n");
|
|
|
|
err = 0;
|
|
end:
|
|
if (err)
|
|
ERR_print_errors_fp(stderr);
|
|
|
|
SSL_free(clientssl);
|
|
SSL_free(serverssl);
|
|
SSL_CTX_free(clientctx);
|
|
SSL_CTX_free(serverctx);
|
|
|
|
# ifndef OPENSSL_NO_CRYPTO_MDEBUG
|
|
CRYPTO_mem_leaks_fp(stderr);
|
|
# endif
|
|
|
|
return err;
|
|
}
|