Created
March 11, 2026 06:02
-
-
Save masakielastic/b918fc6356ede38e4e9d90433b650c03 to your computer and use it in GitHub Desktop.
TLS client
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| #include <netdb.h> | |
| #include <sys/socket.h> | |
| #include <openssl/ssl.h> | |
| #include <openssl/err.h> | |
| #include <openssl/x509_vfy.h> | |
| typedef struct tls_client_config { | |
| const char *host; | |
| const char *port; | |
| const char *server_name; | |
| const char *ca_file; | |
| const char *ca_path; | |
| int min_proto_version; | |
| int verify_peer; | |
| } tls_client_config; | |
| typedef enum tls_client_state { | |
| TLS_CLIENT_STATE_INIT = 0, | |
| TLS_CLIENT_STATE_TCP_OPEN, | |
| TLS_CLIENT_STATE_TLS_OPEN, | |
| TLS_CLIENT_STATE_HANDSHAKING, | |
| TLS_CLIENT_STATE_READY, | |
| TLS_CLIENT_STATE_CLOSED, | |
| TLS_CLIENT_STATE_ERROR | |
| } tls_client_state; | |
| typedef struct tls_client { | |
| tls_client_config config; | |
| int fd; | |
| SSL_CTX *ctx; | |
| SSL *ssl; | |
| tls_client_state state; | |
| } tls_client; | |
| static void print_openssl_error(const char *where) { | |
| fprintf(stderr, "%s\n", where); | |
| ERR_print_errors_fp(stderr); | |
| } | |
| static int tcp_connect(const char *host, const char *port) { | |
| struct addrinfo hints; | |
| struct addrinfo *res = NULL; | |
| struct addrinfo *rp; | |
| int fd = -1; | |
| int rc; | |
| memset(&hints, 0, sizeof(hints)); | |
| hints.ai_family = AF_UNSPEC; | |
| hints.ai_socktype = SOCK_STREAM; | |
| rc = getaddrinfo(host, port, &hints, &res); | |
| if (rc != 0) { | |
| fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rc)); | |
| return -1; | |
| } | |
| for (rp = res; rp != NULL; rp = rp->ai_next) { | |
| fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); | |
| if (fd < 0) { | |
| continue; | |
| } | |
| if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0) { | |
| break; | |
| } | |
| close(fd); | |
| fd = -1; | |
| } | |
| freeaddrinfo(res); | |
| return fd; | |
| } | |
| static SSL_CTX *tls_client_ctx_create(const tls_client_config *cfg) { | |
| SSL_CTX *ctx = NULL; | |
| if (cfg == NULL) { | |
| fprintf(stderr, "tls_client_ctx_create: cfg is NULL\n"); | |
| return NULL; | |
| } | |
| if (OPENSSL_init_ssl(0, NULL) != 1) { | |
| print_openssl_error("OPENSSL_init_ssl failed"); | |
| return NULL; | |
| } | |
| ctx = SSL_CTX_new(TLS_client_method()); | |
| if (ctx == NULL) { | |
| print_openssl_error("SSL_CTX_new failed"); | |
| return NULL; | |
| } | |
| if (cfg->min_proto_version != 0) { | |
| if (SSL_CTX_set_min_proto_version(ctx, cfg->min_proto_version) != 1) { | |
| print_openssl_error("SSL_CTX_set_min_proto_version failed"); | |
| SSL_CTX_free(ctx); | |
| return NULL; | |
| } | |
| } | |
| if (cfg->verify_peer) { | |
| SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); | |
| if (cfg->ca_file != NULL || cfg->ca_path != NULL) { | |
| if (SSL_CTX_load_verify_locations(ctx, cfg->ca_file, cfg->ca_path) != 1) { | |
| print_openssl_error("SSL_CTX_load_verify_locations failed"); | |
| SSL_CTX_free(ctx); | |
| return NULL; | |
| } | |
| } else { | |
| if (SSL_CTX_set_default_verify_paths(ctx) != 1) { | |
| print_openssl_error("SSL_CTX_set_default_verify_paths failed"); | |
| SSL_CTX_free(ctx); | |
| return NULL; | |
| } | |
| } | |
| } else { | |
| SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); | |
| } | |
| return ctx; | |
| } | |
| static SSL *tls_client_ssl_create(SSL_CTX *ctx, int fd, const tls_client_config *cfg) { | |
| SSL *ssl = NULL; | |
| if (ctx == NULL || cfg == NULL) { | |
| fprintf(stderr, "tls_client_ssl_create: invalid argument\n"); | |
| return NULL; | |
| } | |
| ssl = SSL_new(ctx); | |
| if (ssl == NULL) { | |
| print_openssl_error("SSL_new failed"); | |
| return NULL; | |
| } | |
| if (SSL_set_fd(ssl, fd) != 1) { | |
| print_openssl_error("SSL_set_fd failed"); | |
| SSL_free(ssl); | |
| return NULL; | |
| } | |
| if (cfg->server_name != NULL) { | |
| if (SSL_set_tlsext_host_name(ssl, cfg->server_name) != 1) { | |
| print_openssl_error("SSL_set_tlsext_host_name failed"); | |
| SSL_free(ssl); | |
| return NULL; | |
| } | |
| if (cfg->verify_peer) { | |
| if (SSL_set1_host(ssl, cfg->server_name) != 1) { | |
| print_openssl_error("SSL_set1_host failed"); | |
| SSL_free(ssl); | |
| return NULL; | |
| } | |
| } | |
| } | |
| return ssl; | |
| } | |
| static void tls_client_init(tls_client *client, const tls_client_config *cfg) | |
| { | |
| memset(client, 0, sizeof(*client)); | |
| client->fd = -1; | |
| client->state = TLS_CLIENT_STATE_INIT; | |
| if (cfg != NULL) { | |
| client->config = *cfg; | |
| } | |
| } | |
| static int tls_client_open_tls(tls_client *client) | |
| { | |
| if (client->state != TLS_CLIENT_STATE_TCP_OPEN) { | |
| fprintf(stderr, "tcp not opened\n"); | |
| return 0; | |
| } | |
| client->ctx = tls_client_ctx_create(&client->config); | |
| if (!client->ctx) { | |
| client->state = TLS_CLIENT_STATE_ERROR; | |
| return 0; | |
| } | |
| client->ssl = tls_client_ssl_create(client->ctx, client->fd, &client->config); | |
| if (!client->ssl) { | |
| client->state = TLS_CLIENT_STATE_ERROR; | |
| return 0; | |
| } | |
| client->state = TLS_CLIENT_STATE_TLS_OPEN; | |
| return 1; | |
| } | |
| static int tls_client_do_handshake(tls_client *client) | |
| { | |
| int rc; | |
| if (client->state != TLS_CLIENT_STATE_TLS_OPEN) { | |
| fprintf(stderr, "tls not ready\n"); | |
| return 0; | |
| } | |
| client->state = TLS_CLIENT_STATE_HANDSHAKING; | |
| rc = SSL_connect(client->ssl); | |
| if (rc != 1) { | |
| long vr = SSL_get_verify_result(client->ssl); | |
| fprintf(stderr, "SSL_connect failed\n"); | |
| fprintf(stderr, | |
| "verify_result=%ld: %s\n", | |
| vr, | |
| X509_verify_cert_error_string(vr)); | |
| ERR_print_errors_fp(stderr); | |
| client->state = TLS_CLIENT_STATE_ERROR; | |
| return 0; | |
| } | |
| client->state = TLS_CLIENT_STATE_READY; | |
| return 1; | |
| } | |
| static int tls_client_connect(tls_client *client) { | |
| if (!tls_client_open_tcp(client)) { | |
| return 0; | |
| } | |
| if (!tls_client_open_tls(client)) { | |
| return 0; | |
| } | |
| if (!tls_client_do_handshake(client)) { | |
| return 0; | |
| } | |
| return 1; | |
| } | |
| static int tls_client_write_all(tls_client *client, const void *buf, size_t len) | |
| { | |
| const unsigned char *p = buf; | |
| size_t written = 0; | |
| if (client->state != TLS_CLIENT_STATE_READY) { | |
| fprintf(stderr, "connection not ready\n"); | |
| return 0; | |
| } | |
| while (written < len) { | |
| int n = SSL_write(client->ssl, | |
| p + written, | |
| (int)(len - written)); | |
| if (n <= 0) { | |
| print_openssl_error("SSL_write failed"); | |
| client->state = TLS_CLIENT_STATE_ERROR; | |
| return 0; | |
| } | |
| written += n; | |
| } | |
| return 1; | |
| } | |
| static int tls_client_read_to_stdout(tls_client *client) | |
| { | |
| char buf[4096]; | |
| int n; | |
| if (client->state != TLS_CLIENT_STATE_READY) { | |
| fprintf(stderr, "connection not ready\n"); | |
| return 0; | |
| } | |
| while ((n = SSL_read(client->ssl, buf, sizeof(buf))) > 0) { | |
| if (fwrite(buf, 1, n, stdout) != (size_t)n) { | |
| perror("fwrite"); | |
| client->state = TLS_CLIENT_STATE_ERROR; | |
| return 0; | |
| } | |
| } | |
| return 1; | |
| } | |
| static void tls_client_close(tls_client *client) | |
| { | |
| if (!client) return; | |
| if (client->ssl) { | |
| SSL_shutdown(client->ssl); | |
| SSL_free(client->ssl); | |
| client->ssl = NULL; | |
| } | |
| if (client->ctx) { | |
| SSL_CTX_free(client->ctx); | |
| client->ctx = NULL; | |
| } | |
| if (client->fd >= 0) { | |
| close(client->fd); | |
| client->fd = -1; | |
| } | |
| client->state = TLS_CLIENT_STATE_CLOSED; | |
| } | |
| int main(void) | |
| { | |
| tls_client client; | |
| tls_client_config cfg = { | |
| .host = "example.com", | |
| .port = "443", | |
| .server_name = "example.com", | |
| .ca_file = NULL, | |
| .ca_path = NULL, | |
| .min_proto_version = TLS1_2_VERSION, | |
| .verify_peer = 1 | |
| }; | |
| const char *req = | |
| "GET / HTTP/1.1\r\n" | |
| "Host: example.com\r\n" | |
| "Connection: close\r\n" | |
| "\r\n"; | |
| tls_client_init(&client, &cfg); | |
| if (!tls_client_open_tcp(&client)) | |
| goto cleanup; | |
| if (!tls_client_open_tls(&client)) | |
| goto cleanup; | |
| if (!tls_client_do_handshake(&client)) | |
| goto cleanup; | |
| if (!tls_client_write_all(&client, req, strlen(req))) | |
| goto cleanup; | |
| tls_client_read_to_stdout(&client); | |
| cleanup: | |
| tls_client_close(&client); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment