Skip to content

Instantly share code, notes, and snippets.

@masakielastic
Created March 11, 2026 06:02
Show Gist options
  • Select an option

  • Save masakielastic/b918fc6356ede38e4e9d90433b650c03 to your computer and use it in GitHub Desktop.

Select an option

Save masakielastic/b918fc6356ede38e4e9d90433b650c03 to your computer and use it in GitHub Desktop.
TLS client
#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