diff --git a/src/ssl.c b/src/ssl.c index 2c8b95a06..279aa3b32 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -2053,6 +2053,30 @@ static int wolfSSL_read_internal(WOLFSSL* ssl, void* data, int sz, int peek) if (ssl == NULL || data == NULL || sz < 0) return BAD_FUNC_ARG; +#if defined(WOLFSSL_ERROR_CODE_OPENSSL) && defined(OPENSSL_EXTRA) + /* This additional logic is meant to simulate following openSSL behavior: + * After bidirectional SSL_shutdown complete, SSL_read returns 0 and + * SSL_get_error_code returns SSL_ERROR_ZERO_RETURN. + * This behavior is used to know the disconnect of the underlying + * transport layer. + * + * In this logic, CBIORecv is called with a read size of 0 to check the + * transport layer status. It also returns WOLFSSL_FAILURE so that + * SSL_read does not return a positive number on failure. + */ + + /* make sure bidirectional TLS shutdown completes */ + if (ssl->error == WOLFSSL_ERROR_SYSCALL) { + /* ask the underlying transport the connection is closed */ + if (ssl->CBIORecv(ssl, (char*)data, 0, ssl->IOCB_ReadCtx) == + WOLFSSL_CBIO_ERR_CONN_CLOSE) { + ssl->options.isClosed = 1; + ssl->error = WOLFSSL_ERROR_ZERO_RETURN; + } + return WOLFSSL_FAILURE; + } +#endif + #ifdef HAVE_WRITE_DUP if (ssl->dupWrite && ssl->dupSide == WRITE_DUP_SIDE) { WOLFSSL_MSG("Write dup side cannot read"); diff --git a/tests/api.c b/tests/api.c index de5eee318..ae9d6742c 100644 --- a/tests/api.c +++ b/tests/api.c @@ -42987,6 +42987,262 @@ static int test_wolfSSL_CTX_set_ecdh_auto(void) (void)ctx; return ret; } + +#if defined(OPENSSL_EXTRA) && defined(WOLFSSL_ERROR_CODE_OPENSSL) +static THREAD_RETURN WOLFSSL_THREAD SSL_read_test_server_thread(void* args) +{ + callback_functions* callbacks = NULL; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + SOCKET_T sfd = 0; + SOCKET_T cfd = 0; + word16 port; + char msg[] = "I hear you fa shizzle!"; + int len = (int) XSTRLEN(msg); + char input[1024]; + int ret, err; + + if (!args) + return 0; + + ((func_args*)args)->return_code = TEST_FAIL; + + callbacks = ((func_args*)args)->callbacks; + ctx = wolfSSL_CTX_new(callbacks->method()); + +#if defined(USE_WINDOWS_API) + port = ((func_args*)args)->signal->port; +#else + /* Let tcp_listen assign port */ + port = 0; +#endif + +#ifdef WOLFSSL_TIRTOS + fdOpenSession(Task_self()); +#endif + + AssertIntEQ(WOLFSSL_SUCCESS, + wolfSSL_CTX_load_verify_locations(ctx, caCertFile, 0)); + + AssertIntEQ(WOLFSSL_SUCCESS, + wolfSSL_CTX_use_certificate_file(ctx, svrCertFile, + WOLFSSL_FILETYPE_PEM)); + + AssertIntEQ(WOLFSSL_SUCCESS, + wolfSSL_CTX_use_PrivateKey_file(ctx, svrKeyFile, + WOLFSSL_FILETYPE_PEM)); + + if (callbacks->ctx_ready) + callbacks->ctx_ready(ctx); + + ssl = wolfSSL_new(ctx); + AssertNotNull(ssl); + + /* listen and accept */ + tcp_accept(&sfd, &cfd, (func_args*)args, port, 0, 0, 0, 0, 1); + CloseSocket(sfd); + + AssertIntEQ(WOLFSSL_SUCCESS, wolfSSL_set_fd(ssl, cfd)); + + if (callbacks->ssl_ready) + callbacks->ssl_ready(ssl); + + do { + err = 0; /* Reset error */ + ret = wolfSSL_accept(ssl); + if (ret != WOLFSSL_SUCCESS) { + err = wolfSSL_get_error(ssl, 0); + } + } while (ret != WOLFSSL_SUCCESS && err == WC_PENDING_E); + + if (ret != WOLFSSL_SUCCESS) { + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + CloseSocket(cfd); + ((func_args*)args)->return_code = TEST_FAIL; + return 0; + } + + /* read and write data */ + XMEMSET( input, 0, sizeof(input)); + + while (1) { + ret = wolfSSL_read(ssl, input, sizeof(input)); + if (ret > 0) { + break; + } + else { + err = wolfSSL_get_error(ssl,ret); + if (err == WOLFSSL_ERROR_WANT_READ) { + continue; + } + break; + } + } + + if (err == WOLFSSL_ERROR_ZERO_RETURN) { + do { + ret = wolfSSL_write(ssl, msg, len); + if (ret > 0) { + break; + } + } while (ret < 0); + } + + /* bidirectional shutdown */ + while ((ret = wolfSSL_shutdown(ssl)) != WOLFSSL_SUCCESS) { + continue; + } + + /* wait for the peer to disconnect the tcp connection */ + do { + ret = wolfSSL_read(ssl, input, sizeof(input)); + err = wolfSSL_get_error(ssl, ret); + } while (ret > 0 || err != WOLFSSL_ERROR_ZERO_RETURN); + + /* detect TCP disconnect */ + AssertIntLE(ret,WOLFSSL_FAILURE); + AssertIntEQ(wolfSSL_get_error(ssl, ret), WOLFSSL_ERROR_ZERO_RETURN); + + ((func_args*)args)->return_code = TEST_SUCCESS; + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + CloseSocket(cfd); + + return 0; +} +static THREAD_RETURN WOLFSSL_THREAD SSL_read_test_client_thread(void* args) +{ + callback_functions* callbacks = NULL; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + SOCKET_T sfd = 0; + char msg[] = "hello wolfssl server!"; + int len = (int) XSTRLEN(msg); + char input[1024]; + int idx; + int ret, err; + + if (!args) + return 0; + + ((func_args*)args)->return_code = TEST_FAIL; + callbacks = ((func_args*)args)->callbacks; + ctx = wolfSSL_CTX_new(callbacks->method()); + +#ifdef WOLFSSL_TIRTOS + fdOpenSession(Task_self()); +#endif + + AssertIntEQ(WOLFSSL_SUCCESS, + wolfSSL_CTX_load_verify_locations(ctx, caCertFile, 0)); + + AssertIntEQ(WOLFSSL_SUCCESS, + wolfSSL_CTX_use_certificate_file(ctx, cliCertFile, + WOLFSSL_FILETYPE_PEM)); + + AssertIntEQ(WOLFSSL_SUCCESS, + wolfSSL_CTX_use_PrivateKey_file(ctx, cliKeyFile, + WOLFSSL_FILETYPE_PEM)); + + AssertNotNull((ssl = wolfSSL_new(ctx))); + + tcp_connect(&sfd, wolfSSLIP, ((func_args*)args)->signal->port, 0, 0, ssl); + + AssertIntEQ(WOLFSSL_SUCCESS, wolfSSL_set_fd(ssl, sfd)); + + do { + err = 0; /* Reset error */ + ret = wolfSSL_connect(ssl); + if (ret != WOLFSSL_SUCCESS) { + err = wolfSSL_get_error(ssl, 0); + } + } while (ret != WOLFSSL_SUCCESS && err == WC_PENDING_E); + + ret = wolfSSL_write(ssl, msg, len); + + if (0 < (idx = wolfSSL_read(ssl, input, sizeof(input)-1))) { + input[idx] = 0; + } + + ret = wolfSSL_shutdown(ssl); + if ( ret == WOLFSSL_SHUTDOWN_NOT_DONE) { + ret = wolfSSL_shutdown(ssl); + } + AssertIntEQ(ret, WOLFSSL_SUCCESS); + + ((func_args*)args)->return_code = TEST_SUCCESS; + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + CloseSocket(sfd); + return 0; +} +#endif /* OPENSSL_EXTRA && WOLFSSL_ERROR_CODE_OPENSSL */ + +/* This test is to check wolfSSL_read behaves as same as + * openSSL when it is called after SSL_shutdown completes. + */ +static int test_wolfSSL_read_detect_TCP_disconnect(void) +{ + int ret = 0; +#if defined(OPENSSL_EXTRA) && defined(WOLFSSL_ERROR_CODE_OPENSSL) + tcp_ready ready; + func_args client_args; + func_args server_args; + THREAD_TYPE serverThread; + THREAD_TYPE clientThread; + callback_functions server_cbf; + callback_functions client_cbf; + + + printf(testingFmt, "wolfSSL_read_detect_TCP_disconnect()"); + +#ifdef WOLFSSL_TIRTOS + fdOpenSession(Task_self()); +#endif + StartTCP(); + InitTcpReady(&ready); + +#if defined(USE_WINDOWS_API) + /* use RNG to get random port if using windows */ + ready.port = GetRandomPort(); +#endif + + XMEMSET(&client_args, 0, sizeof(func_args)); + XMEMSET(&server_args, 0, sizeof(func_args)); + + XMEMSET(&server_cbf, 0, sizeof(callback_functions)); + XMEMSET(&client_cbf, 0, sizeof(callback_functions)); + + server_cbf.method = wolfTLSv1_2_server_method; + client_cbf.method = wolfTLSv1_2_client_method; + + server_args.callbacks = &server_cbf; + client_args.callbacks = &client_cbf; + + server_args.signal = &ready; + client_args.signal = &ready; + + start_thread(SSL_read_test_server_thread, &server_args, &serverThread); + + wait_tcp_ready(&server_args); + + start_thread(SSL_read_test_client_thread, &client_args, &clientThread); + + join_thread(clientThread); + join_thread(serverThread); + + AssertTrue(client_args.return_code); + AssertTrue(server_args.return_code); + + FreeTcpReady(&ready); + + printf(resultFmt, passed); +#endif + return ret; +} static void test_wolfSSL_CTX_get_min_proto_version(void) { #if defined(OPENSSL_EXTRA) || defined(OPENSSL_ALL) @@ -43892,7 +44148,7 @@ void ApiTest(void) test_wolfSSL_EC_get_builtin_curves(); test_wolfSSL_CRYPTO_memcmp(); - + test_wolfSSL_read_detect_TCP_disconnect(); /* test the no op functions for compatibility */ test_no_op_functions();