diff --git a/CMakeLists.txt b/CMakeLists.txt index ceefbf8a..d728ac14 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -259,7 +259,6 @@ add_c_compiler_flag(-Wno-format-nonliteral) if (MINGW) add_c_compiler_flag(-Wno-format) endif() -if if (NOT CIVETWEB_ALLOW_WARNINGS) add_c_compiler_flag(-Werror) endif() diff --git a/include/civetweb.h b/include/civetweb.h index ba730ba0..7c81eb21 100644 --- a/include/civetweb.h +++ b/include/civetweb.h @@ -751,6 +751,21 @@ CIVETWEB_API struct mg_connection *mg_connect_client(const char *host, char *error_buffer, size_t error_buffer_size); + +struct mg_client_options { + const char *host; + int port; + const char *client_cert; + const char *server_cert; + /* TODO: add more data */ +}; + +CIVETWEB_API struct mg_connection * +mg_connect_client_secure(const struct mg_client_options *client_options, + char *error_buffer, + size_t error_buffer_size); + + enum { TIMEOUT_INFINITE = -1 }; /* Wait for a response from the server diff --git a/src/civetweb.c b/src/civetweb.c index 4785b412..37eec88a 100755 --- a/src/civetweb.c +++ b/src/civetweb.c @@ -9932,6 +9932,8 @@ sslize(struct mg_connection *conn, SSL_CTX *s, int (*func)(SSL *)) if (ret != 1) { err = SSL_get_error(conn->ssl, ret); (void)err; /* TODO: set some error message */ + SSL_free(conn->ssl); + conn->ssl = NULL; return 0; } @@ -9939,6 +9941,8 @@ sslize(struct mg_connection *conn, SSL_CTX *s, int (*func)(SSL *)) if (ret != 1) { err = SSL_get_error(conn->ssl, ret); (void)err; /* TODO: set some error message */ + SSL_free(conn->ssl); + conn->ssl = NULL; return 0; } @@ -10095,6 +10099,50 @@ verify_ssl_client(int preverify_ok, X509_STORE_CTX *x509_ctx) } #endif + +static int +ssl_use_pem_file(struct mg_context *ctx, const char *pem) +{ + if (SSL_CTX_use_certificate_file(ctx->ssl_ctx, pem, 1) == 0) { + mg_cry(fc(ctx), + "%s: cannot open certificate file %s: %s", + __func__, + pem, + ssl_error()); + return 0; + } + + /* could use SSL_CTX_set_default_passwd_cb_userdata */ + + if (SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, pem, 1) == 0) { + mg_cry(fc(ctx), + "%s: cannot open private key file %s: %s", + __func__, + pem, + ssl_error()); + return 0; + } + + if (SSL_CTX_check_private_key(ctx->ssl_ctx) == 0) { + mg_cry(fc(ctx), + "%s: certificate and private key do not match: %s", + __func__, + pem); + return 0; + } + + if (SSL_CTX_use_certificate_chain_file(ctx->ssl_ctx, pem) == 0) { + mg_cry(fc(ctx), + "%s: cannot use certificate chain file %s: %s", + __func__, + pem, + ssl_error()); + return 0; + } + return 1; +} + + /* Dynamically load SSL library. Set up ctx->ssl_ctx pointer. */ static int set_ssl_option(struct mg_context *ctx) @@ -10160,40 +10208,7 @@ set_ssl_option(struct mg_context *ctx) } if (pem != NULL) { - if (SSL_CTX_use_certificate_file(ctx->ssl_ctx, pem, 1) == 0) { - mg_cry(fc(ctx), - "%s: cannot open certificate file %s: %s", - __func__, - pem, - ssl_error()); - return 0; - } - - /* could use SSL_CTX_set_default_passwd_cb_userdata */ - - if (SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, pem, 1) == 0) { - mg_cry(fc(ctx), - "%s: cannot open private key file %s: %s", - __func__, - pem, - ssl_error()); - return 0; - } - - if (SSL_CTX_check_private_key(ctx->ssl_ctx) == 0) { - mg_cry(fc(ctx), - "%s: certificate and private key do not match: %s", - __func__, - pem); - return 0; - } - - if (SSL_CTX_use_certificate_chain_file(ctx->ssl_ctx, pem) == 0) { - mg_cry(fc(ctx), - "%s: cannot use certificate chain file %s: %s", - __func__, - pem, - ssl_error()); + if (!ssl_use_pem_file(ctx, pem)) { return 0; } } @@ -10473,20 +10488,25 @@ mg_close_connection(struct mg_connection *conn) } -struct mg_connection * -mg_connect_client(const char *host, - int port, - int use_ssl, - char *ebuf, - size_t ebuf_len) +static struct mg_connection * +mg_connect_client_impl(const struct mg_client_options *client_options, + int use_ssl, + char *ebuf, + size_t ebuf_len) { static struct mg_context fake_ctx; struct mg_connection *conn = NULL; SOCKET sock; union usa sa; - if (!connect_socket( - &fake_ctx, host, port, use_ssl, ebuf, ebuf_len, &sock, &sa)) { + if (!connect_socket(&fake_ctx, + client_options->host, + client_options->port, + use_ssl, + ebuf, + ebuf_len, + &sock, + &sa)) { ; } else if ((conn = (struct mg_connection *) mg_calloc(1, sizeof(*conn) + MAX_REQUEST_SIZE)) == NULL) { @@ -10541,16 +10561,53 @@ mg_connect_client(const char *host, conn->client.is_ssl = use_ssl ? 1 : 0; (void)pthread_mutex_init(&conn->mutex, &pthread_mutex_attr); + #ifndef NO_SSL if (use_ssl) { + fake_ctx.ssl_ctx = conn->client_ssl_ctx; + /* TODO: Check ssl_verify_peer and ssl_ca_path here. SSL_CTX_set_verify call is needed to switch off server * certificate checking, which is off by default in OpenSSL and on * in yaSSL. */ - SSL_CTX_set_verify(conn->client_ssl_ctx, SSL_VERIFY_NONE, NULL); + // TODO: SSL_CTX_set_verify(conn->client_ssl_ctx, SSL_VERIFY_PEER, // verify_ssl_server); - sslize(conn, conn->client_ssl_ctx, SSL_connect); + + if (client_options->client_cert) { + if (!ssl_use_pem_file(&fake_ctx, client_options->client_cert)) { + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + ebuf, + ebuf_len, + "Can not use SSL client certificate"); + SSL_CTX_free(conn->client_ssl_ctx); + closesocket(sock); + mg_free(conn); + conn = NULL; + } + } + + if (client_options->server_cert) { + SSL_CTX_load_verify_locations(conn->client_ssl_ctx, + client_options->server_cert, + NULL); + SSL_CTX_set_verify(conn->client_ssl_ctx, SSL_VERIFY_PEER, NULL); + } else { + SSL_CTX_set_verify(conn->client_ssl_ctx, SSL_VERIFY_NONE, NULL); + } + + if (!sslize(conn, conn->client_ssl_ctx, SSL_connect)) { + mg_snprintf(NULL, + NULL, /* No truncation check for ebuf */ + ebuf, + ebuf_len, + "SSL connection error"); + SSL_CTX_free(conn->client_ssl_ctx); + closesocket(sock); + mg_free(conn); + conn = NULL; + } } #endif } @@ -10558,6 +10615,37 @@ mg_connect_client(const char *host, return conn; } + +CIVETWEB_API struct mg_connection * +mg_connect_client_secure(const struct mg_client_options *client_options, + char *error_buffer, + size_t error_buffer_size) +{ + return mg_connect_client_impl(client_options, + 1, + error_buffer, + error_buffer_size); +} + + +struct mg_connection * +mg_connect_client(const char *host, + int port, + int use_ssl, + char *error_buffer, + size_t error_buffer_size) +{ + struct mg_client_options opts; + memset(&opts, 0, sizeof(opts)); + opts.host = host; + opts.port = port; + return mg_connect_client_impl(&opts, + use_ssl, + error_buffer, + error_buffer_size); +} + + static const struct { const char *proto; size_t proto_len; @@ -10568,6 +10656,7 @@ static const struct { {"wss://", 6, 443}, {NULL, 0, 0}}; + /* Check if the uri is valid. * return 0 for invalid uri, * return 1 for *, diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 30b74eb6..f26641a6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -146,6 +146,7 @@ civetweb_add_test(PublicServer "Check test environment") civetweb_add_test(PublicServer "Start threads") civetweb_add_test(PublicServer "Start Stop HTTP Server") civetweb_add_test(PublicServer "Start Stop HTTPS Server") +civetweb_add_test(PublicServer "TLS Server Client") civetweb_add_test(PublicServer "Server Requests") # Tests with main.c diff --git a/test/public_server.c b/test/public_server.c index 56506ec7..57da319f 100644 --- a/test/public_server.c +++ b/test/public_server.c @@ -161,6 +161,7 @@ START_TEST(test_the_test_environment) } END_TEST + static void *threading_data; static void * @@ -186,6 +187,7 @@ START_TEST(test_threading) } END_TEST + static int log_msg_func(const struct mg_connection *conn, const char *message) { @@ -271,6 +273,7 @@ START_TEST(test_mg_start_stop_http_server) } END_TEST + START_TEST(test_mg_start_stop_https_server) { #ifndef NO_SSL @@ -369,6 +372,12 @@ START_TEST(test_mg_server_and_client_tls) struct mg_callbacks callbacks; char errmsg[256]; + struct mg_connection *client_conn; + char client_err[256]; + const struct mg_request_info *client_ri; + int client_res; + struct mg_client_options client_options; + const char *OPTIONS[32]; /* initializer list here is rejected by CI test */ int opt_idx = 0; char server_cert[256]; @@ -432,6 +441,38 @@ START_TEST(test_mg_server_and_client_tls) test_sleep(1); + memset(client_err, 0, sizeof(client_err)); + client_conn = + mg_connect_client("127.0.0.1", 8443, 1, client_err, sizeof(client_err)); + ck_assert(client_conn == NULL); + ck_assert_str_ne(client_err, ""); + + memset(client_err, 0, sizeof(client_err)); + memset(&client_options, 0, sizeof(client_options)); + client_options.host = "127.0.0.1"; + client_options.port = 8443; + client_options.client_cert = client_cert; + client_options.server_cert = server_cert; + + client_conn = mg_connect_client_secure(&client_options, + client_err, + sizeof(client_err)); + ck_assert(client_conn != NULL); + ck_assert_str_eq(client_err, ""); + mg_printf(client_conn, "GET / HTTP/1.0\r\n\r\n"); + client_res = + mg_get_response(client_conn, client_err, sizeof(client_err), 10000); + ck_assert_int_ge(client_res, 0); + ck_assert_str_eq(client_err, ""); + client_ri = mg_get_request_info(client_conn); + ck_assert(client_ri != NULL); + ck_assert_str_eq(client_ri->uri, "200"); + /* TODO: ck_assert_str_eq(client_ri->request_method, "HTTP/1.0"); */ + client_res = (int)mg_read(client_conn, client_err, sizeof(client_err)); + ck_assert_int_gt(client_res, 0); + ck_assert_int_le(client_res, sizeof(client_err)); + mg_close_connection(client_conn); + /* TODO: A client API using a client certificate is missing */ test_sleep(1); @@ -1238,6 +1279,7 @@ START_TEST(test_request_handlers) } END_TEST + Suite * make_public_server_suite(void) { @@ -1247,8 +1289,7 @@ make_public_server_suite(void) TCase *const startthreads = tcase_create("Start threads"); TCase *const startstophttp = tcase_create("Start Stop HTTP Server"); TCase *const startstophttps = tcase_create("Start Stop HTTPS Server"); - TCase *const serverandclienttls = - tcase_create("Start Stop TLS Server Client"); + TCase *const serverandclienttls = tcase_create("TLS Server Client"); TCase *const serverrequests = tcase_create("Server Requests"); tcase_add_test(checktestenv, test_the_test_environment);