text: /*
   * Copyright (c) 2020 Friedt Professional Engineering Services, Inc
   *
   * SPDX-License-Identifier: Apache-2.0
   */
  
  #include <logging/log.h>
  #include <net/net_core.h>
  #include <net/net_ip.h>
  #include <net/socket.h>
  #include <net/tls_credentials.h>
  #include <posix/unistd.h>
  #include <sys/util.h>
  #include <ztest.h>
  
  LOG_MODULE_REGISTER(tls_test, CONFIG_NET_SOCKETS_LOG_LEVEL);
  
  /**
   * @brief An encrypted message to pass between server and client.
   *
   * The answer to life, the universe, and everything.
   *
   * See also <a href="https://en.wikipedia.org/wiki/42_(number)#The_Hitchhiker's_Guide_to_the_Galaxy">42</a>.
   */
  #define SECRET "forty-two"
  
  /**
   * @brief Size of the encrypted message passed between server and client.
   */
  #define SECRET_SIZE (sizeof(SECRET) - 1)
  
  /** @brief Stack size for the server thread */
  #define STACK_SIZE 8192
  
  /** @brief TCP port for the server thread */
  #define PORT 4242
  
  /** @brief arbitrary timeout value in ms */
  #define TIMEOUT 1000
  
  /**
   * @brief Application-dependent TLS credential identifiers
   *
   * Since both the server and client exist in the same test
   * application in this case, both the server and client credentials
   * are loaded together.
   *
   * The server would normally need
   * - SERVER_CERTIFICATE_TAG (for both public and private keys)
   * - CA_CERTIFICATE_TAG (only when client authentication is required)
   *
   * The client would normally load
   * - CA_CERTIFICATE_TAG (always required, to verify the server)
   * - CLIENT_CERTIFICATE_TAG (for both public and private keys, only when
   *   client authentication is required)
   */
  enum tls_tag {
  	/** The Certificate Authority public key */
  	CA_CERTIFICATE_TAG,
  	/** Used for both the public and private server keys */
  	SERVER_CERTIFICATE_TAG,
  	/** Used for both the public and private client keys */
  	CLIENT_CERTIFICATE_TAG,
  };
  
  /** @brief synchronization object for server & client threads */
  static struct k_sem server_sem;
  
  /** @brief The server thread stack */
  static K_THREAD_STACK_DEFINE(server_stack, STACK_SIZE);
  /** @brief the server thread object */
  static struct k_thread server_thread;
  
  #ifdef CONFIG_TLS_CREDENTIALS
  /**
   * @brief The Certificate Authority (CA) Certificate
   *
   * The client needs the CA cert to verify the server public key. TLS client
   * sockets are always required to verify the server public key.
   *
   * Additionally, when the peer verification mode is
   * @ref TLS_PEER_VERIFY_OPTIONAL or @ref TLS_PEER_VERIFY_REQUIRED, then
   * the server also needs the CA cert in order to verify the client. This
   * type of configuration is often referred to as *mutual authentication*.
   */
  static const unsigned char ca[] = {
  #include "ca.inc"
  };
  
  /**
   * @brief The Server Certificate
   *
   * This is the public key of the server.
   */
  static const unsigned char server[] = {
  #include "server.inc"
  };
  
  /**
   * @brief The Server Private Key
   *
   * This is the private key of the server.
   */
  static const unsigned char server_privkey[] = {
  #include "server_privkey.inc"
  };
  
  /**
   * @brief The Client Certificate
   *
   * This is the public key of the client.
   */
  static const unsigned char client[] = {
  #include "client.inc"
  };
  
  /**
   * @brief The Client Private Key
   *
   * This is the private key of the client.
   */
  static const unsigned char client_privkey[] = {
  #include "client_privkey.inc"
  };
  #else /* CONFIG_TLS_CREDENTIALS */
  #define ca NULL
  #define server NULL
  #define server_privkey NULL
  #define client NULL
  #define client_privkey NULL
  #endif /* CONFIG_TLS_CREDENTIALS */
  
  /**
   * @brief The server thread function
   *
   * This function simply accepts a client connection and
   * echoes the first @ref SECRET_SIZE bytes of the first
   * packet. After that, the server is closed and connections
   * are no longer accepted.
   *
   * @param arg0 a pointer to the int representing the server file descriptor
   * @param arg1 ignored
   * @param arg2 ignored
   */
  static void server_thread_fn(void *arg0, void *arg1, void *arg2)
  {
  	const int server_fd = POINTER_TO_INT(arg0);
  
  	int r;
  	int client_fd;
  	socklen_t addrlen;
  	char addrstr[INET_ADDRSTRLEN];
  	struct sockaddr_in sa;
  	char *addrstrp;
  
  	k_thread_name_set(k_current_get(), "server");
  
  	NET_DBG("Server thread running");
  
  	memset(&sa, 0, sizeof(sa));
  	addrlen = sizeof(sa);
  
  	NET_DBG("Accepting client connection..");
  	k_sem_give(&server_sem);
  	r = accept(server_fd, (struct sockaddr *)&sa, &addrlen);
  	zassert_not_equal(r, -1, "accept() failed (%d)", r);
  	client_fd = r;
  
  	memset(addrstr, '\0', sizeof(addrstr));
  	addrstrp = (char *)inet_ntop(AF_INET, &sa.sin_addr,
  				     addrstr, sizeof(addrstr));
  	zassert_not_equal(addrstrp, NULL, "inet_ntop() failed (%d)", errno);
  
  	NET_DBG("accepted connection from [%s]:%d as fd %d",
  		log_strdup(addrstr), ntohs(sa.sin_port), client_fd);
  
  	NET_DBG("calling recv()");
  	r = recv(client_fd, addrstr, sizeof(addrstr), 0);
  	zassert_not_equal(r, -1, "recv() failed (%d)", errno);
  	zassert_equal(r, SECRET_SIZE, "expected: %zu actual: %d", SECRET_SIZE, r);
  
  	NET_DBG("calling send()");
  	r = send(client_fd, SECRET, SECRET_SIZE, 0);
  	zassert_not_equal(r, -1, "send() failed (%d)", errno);
  	zassert_equal(r, SECRET_SIZE, "expected: %zu actual: %d", SECRET_SIZE, r);
  
  	NET_DBG("closing client fd");
  	r = close(client_fd);
  	zassert_not_equal(r, -1, "close() failed on the server fd (%d)", errno);
  }
  
  static void test_common(int peer_verify)
  {
  	const int yes = true;
  
  	int r;
  	int server_fd;
  	int client_fd;
  	int proto = IPPROTO_TCP;
  	char *addrstrp;
  	k_tid_t server_thread_id;
  	struct sockaddr_in sa;
  	char addrstr[INET_ADDRSTRLEN];
  
  	k_sem_init(&server_sem, 0, 1);
  
  	/* set the common protocol for both client and server */
  	if (IS_ENABLED(CONFIG_NET_SOCKETS_SOCKOPT_TLS)) {
  		proto = IPPROTO_TLS_1_2;
  	}
  	/*
  	 * Server socket setup
  	 */
  
  	NET_DBG("Creating server socket");
  	r = socket(AF_INET, SOCK_STREAM, proto);
  	zassert_not_equal(r, -1, "failed to create server socket (%d)", errno);
  	server_fd = r;
  
  	r = setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
  	zassert_not_equal(r, -1, "failed to set SO_REUSEADDR (%d)", errno);
  
  	if (IS_ENABLED(CONFIG_TLS_CREDENTIALS)
  	    && IS_ENABLED(CONFIG_NET_SOCKETS_SOCKOPT_TLS)) {
  
  		static const sec_tag_t server_tag_list_verify_none[] = {
  			SERVER_CERTIFICATE_TAG,
  		};
  
  		static const sec_tag_t server_tag_list_verify[] = {
  			CA_CERTIFICATE_TAG,
  			SERVER_CERTIFICATE_TAG,
  		};
  
  		const sec_tag_t *sec_tag_list;
  		size_t sec_tag_list_size;
  
  		switch (peer_verify) {
  		case TLS_PEER_VERIFY_NONE:
  			sec_tag_list = server_tag_list_verify_none;
  			sec_tag_list_size = sizeof(server_tag_list_verify_none);
  			break;
  		case TLS_PEER_VERIFY_OPTIONAL:
  		case TLS_PEER_VERIFY_REQUIRED:
  			sec_tag_list = server_tag_list_verify;
  			sec_tag_list_size = sizeof(server_tag_list_verify);
  
  			r = setsockopt(server_fd, SOL_TLS, TLS_PEER_VERIFY,
  				       &peer_verify, sizeof(peer_verify));
  			zassert_not_equal(r, -1,
  					  "failed to set TLS_PEER_VERIFY (%d)", errno);
  			break;
  		default:
  			zassert_true(false,
  				     "unrecognized TLS peer verify type %d",
  				     peer_verify);
  			return;
  		}
  
  		r = setsockopt(server_fd, SOL_TLS, TLS_SEC_TAG_LIST,
  			       sec_tag_list, sec_tag_list_size);
  		zassert_not_equal(r, -1, "failed to set TLS_SEC_TAG_LIST (%d)",
  				  errno);
  
  		r = setsockopt(server_fd, SOL_TLS, TLS_HOSTNAME, "localhost",
  			       sizeof("localhost"));
  		zassert_not_equal(r, -1, "failed to set TLS_HOSTNAME (%d)",
  				  errno);
  	}
  
  	memset(&sa, 0, sizeof(sa));
  	/* The server listens on all network interfaces */
  	sa.sin_addr.s_addr = INADDR_ANY;
  	sa.sin_family = AF_INET;
  	sa.sin_port = htons(PORT);
  
  	r = bind(server_fd, (struct sockaddr *)&sa, sizeof(sa));
  	zassert_not_equal(r, -1, "failed to bind (%d)", errno);
  
  	r = listen(server_fd, 1);
  	zassert_not_equal(r, -1, "failed to listen (%d)", errno);
  
  	memset(addrstr, '\0', sizeof(addrstr));
  	addrstrp = (char *)inet_ntop(AF_INET, &sa.sin_addr,
  				     addrstr, sizeof(addrstr));
  	zassert_not_equal(addrstrp, NULL, "inet_ntop() failed (%d)", errno);
  
  	NET_DBG("listening on [%s]:%d as fd %d",
  		log_strdup(addrstr), ntohs(sa.sin_port), server_fd);
  
  	NET_DBG("Creating server thread");
  	server_thread_id = k_thread_create(&server_thread, server_stack,
  					   STACK_SIZE, server_thread_fn,
  					   INT_TO_POINTER(server_fd), NULL, NULL,
  					   K_PRIO_PREEMPT(8), 0, K_NO_WAIT);
  
  	r = k_sem_take(&server_sem, K_MSEC(TIMEOUT));
  	zassert_equal(0, r, "failed to synchronize with server thread (%d)", r);
  
  	/*
  	 * Client socket setup
  	 */
  
  	k_thread_name_set(k_current_get(), "client");
  
  	NET_DBG("Creating client socket");
  	r = socket(AF_INET, SOCK_STREAM, proto);
  	zassert_not_equal(r, -1, "failed to create client socket (%d)", errno);
  	client_fd = r;
  
  	if (IS_ENABLED(CONFIG_TLS_CREDENTIALS)
  	    && IS_ENABLED(CONFIG_NET_SOCKETS_SOCKOPT_TLS)) {
  
  		static const sec_tag_t client_tag_list_verify_none[] = {
  			CA_CERTIFICATE_TAG,
  		};
  
  		static const sec_tag_t client_tag_list_verify[] = {
  			CA_CERTIFICATE_TAG,
  			CLIENT_CERTIFICATE_TAG,
  		};
  
  		const sec_tag_t *sec_tag_list;
  		size_t sec_tag_list_size;
  
  		switch (peer_verify) {
  		case TLS_PEER_VERIFY_NONE:
  			sec_tag_list = client_tag_list_verify_none;
  			sec_tag_list_size = sizeof(client_tag_list_verify_none);
  			break;
  		case TLS_PEER_VERIFY_OPTIONAL:
  		case TLS_PEER_VERIFY_REQUIRED:
  			sec_tag_list = client_tag_list_verify;
  			sec_tag_list_size = sizeof(client_tag_list_verify);
  			break;
  		default:
  			zassert_true(false, "unrecognized TLS peer verify type %d",
  				     peer_verify);
  			return;
  		}
  
  		r = setsockopt(client_fd, SOL_TLS, TLS_SEC_TAG_LIST,
  			       sec_tag_list, sec_tag_list_size);
  		zassert_not_equal(r, -1, "failed to set TLS_SEC_TAG_LIST (%d)",
  				  errno);
  
  		r = setsockopt(client_fd, SOL_TLS, TLS_HOSTNAME, "localhost",
  			       sizeof("localhost"));
  		zassert_not_equal(r, -1, "failed to set TLS_HOSTNAME (%d)", errno);
  	}
  
  	r = inet_pton(AF_INET, CONFIG_NET_CONFIG_MY_IPV4_ADDR, &sa.sin_addr.s_addr);
  	zassert_not_equal(-1, r, "inet_pton() failed (%d)", errno);
  	zassert_not_equal(0, r, "%s is not a valid IPv4 address", CONFIG_NET_CONFIG_MY_IPV4_ADDR);
  	zassert_equal(1, r, "inet_pton() failed to convert %s", CONFIG_NET_CONFIG_MY_IPV4_ADDR);
  
  	memset(addrstr, '\0', sizeof(addrstr));
  	addrstrp = (char *)inet_ntop(AF_INET, &sa.sin_addr,
  				     addrstr, sizeof(addrstr));
  	zassert_not_equal(addrstrp, NULL, "inet_ntop() failed (%d)", errno);
  
  	NET_DBG("connecting to [%s]:%d with fd %d",
  		log_strdup(addrstr), ntohs(sa.sin_port), client_fd);
  
  	r = connect(client_fd, (struct sockaddr *)&sa, sizeof(sa));
  	zassert_not_equal(r, -1, "failed to connect (%d)", errno);
  
  	/*
  	 * The main part of the test
  	 */
  
  	NET_DBG("Calling send()");
  	r = send(client_fd, SECRET, SECRET_SIZE, 0);
  	zassert_not_equal(r, -1, "send() failed (%d)", errno);
  	zassert_equal(SECRET_SIZE, r, "expected: %zu actual: %d", SECRET_SIZE, r);
  
  	NET_DBG("Calling recv()");
  	memset(addrstr, 0, sizeof(addrstr));
  	r = recv(client_fd, addrstr, sizeof(addrstr), 0);
  	zassert_not_equal(r, -1, "recv() failed (%d)", errno);
  	zassert_equal(SECRET_SIZE, r, "expected: %zu actual: %d", SECRET_SIZE, r);
  
  	zassert_mem_equal(SECRET, addrstr, SECRET_SIZE,
  			  "expected: %s actual: %s", SECRET, addrstr);
  
  	/*
  	 * Cleanup resources
  	 */
  
  	NET_DBG("closing client fd");
  	r = close(client_fd);
  	zassert_not_equal(-1, r, "close() failed on the client fd (%d)", errno);
  
  	NET_DBG("closing server fd");
  	r = close(server_fd);
  	zassert_not_equal(-1, r, "close() failed on the server fd (%d)", errno);
  
  	r = k_thread_join(&server_thread, K_FOREVER);
  	zassert_equal(0, r, "k_thread_join() failed (%d)", r);
  }
  
  static void test_tls_peer_verify_none(void)
  {
  	test_common(TLS_PEER_VERIFY_NONE);
  }
  
  static void test_tls_peer_verify_optional(void)
  {
  	test_common(TLS_PEER_VERIFY_OPTIONAL);
  }
  
  static void test_tls_peer_verify_required(void)
  {
  	test_common(TLS_PEER_VERIFY_REQUIRED);
  }
  
  void test_main(void)
  {
  	int r;
  
  	/*
  	 * Load both client & server credentials
  	 *
  	 * Normally, this would be split into separate applications but
  	 * for testing purposes, we just use separate threads.
  	 *
  	 * Also, it has to be done before tests are run, otherwise
  	 * there are errors due to attempts to load too many certificates.
  	 *
  	 * The server would normally load
  	 * - server public key
  	 * - server private key
  	 * - ca cert (only when client authentication is required)
  	 *
  	 * The client would normally load
  	 * - ca cert (to verify the server)
  	 * - client public key (only when client authentication is required)
  	 * - client private key (only when client authentication is required)
  	 */
  	if (IS_ENABLED(CONFIG_TLS_CREDENTIALS)) {
  		NET_DBG("Loading credentials");
  		r = tls_credential_add(CA_CERTIFICATE_TAG,
  				       TLS_CREDENTIAL_CA_CERTIFICATE,
  				       ca, sizeof(ca));
  		zassert_equal(r, 0, "failed to add CA Certificate (%d)", r);
  
  		r = tls_credential_add(SERVER_CERTIFICATE_TAG,
  				       TLS_CREDENTIAL_SERVER_CERTIFICATE,
  				       server, sizeof(server));
  		zassert_equal(r, 0, "failed to add Server Certificate (%d)", r);
  
  		r = tls_credential_add(SERVER_CERTIFICATE_TAG,
  				       TLS_CREDENTIAL_PRIVATE_KEY,
  				       server_privkey, sizeof(server_privkey));
  		zassert_equal(r, 0, "failed to add Server Private Key (%d)", r);
  
  		r = tls_credential_add(CLIENT_CERTIFICATE_TAG,
  				       TLS_CREDENTIAL_SERVER_CERTIFICATE,
  				       client, sizeof(client));
  		zassert_equal(r, 0, "failed to add Client Certificate (%d)", r);
  
  		r = tls_credential_add(CLIENT_CERTIFICATE_TAG,
  				       TLS_CREDENTIAL_PRIVATE_KEY,
  				       client_privkey, sizeof(client_privkey));
  		zassert_equal(r, 0, "failed to add Client Private Key (%d)", r);
  	}
  
  	ztest_test_suite(
  		tls_socket_api_extension,
  		ztest_unit_test(test_tls_peer_verify_none),
  		ztest_unit_test(test_tls_peer_verify_optional),
  		ztest_unit_test(test_tls_peer_verify_required)
  		);
  
  	ztest_run_test_suite(tls_socket_api_extension);
  }
  