Browse Source

Client API using SSL certificates (draft, Step 1/?)

bel 9 năm trước cách đây
mục cha
commit
ada9d9b00b
5 tập tin đã thay đổi với 192 bổ sung47 xóa
  1. 0 1
      CMakeLists.txt
  2. 15 0
      include/civetweb.h
  3. 133 44
      src/civetweb.c
  4. 1 0
      test/CMakeLists.txt
  5. 43 2
      test/public_server.c

+ 0 - 1
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()

+ 15 - 0
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

+ 133 - 44
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 *,

+ 1 - 0
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

+ 43 - 2
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);