Last active
March 11, 2022 16:52
-
-
Save 64/39c7f82c159b53a104961caf965696e1 to your computer and use it in GitHub Desktop.
TLS 1.3 example server using picotls library
This file contains 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 <arpa/inet.h> | |
#include <sys/types.h> | |
#include <sys/socket.h> | |
#include <signal.h> | |
#include <netdb.h> | |
#include <errno.h> | |
#include <fcntl.h> | |
#include <picotls.h> | |
#include <picotls/openssl.h> | |
#include <openssl/err.h> | |
#include <openssl/engine.h> | |
#include <openssl/pem.h> | |
#define CERT_PATH "../example.crt" | |
#define PKEY_PATH "../example.key" | |
ptls_context_t *ctx = NULL; | |
ptls_handshake_properties_t *hsprop = NULL; | |
const int signals_list[] = { SIGINT, SIGTERM }; | |
void ossl_start(void) { | |
ERR_load_crypto_strings(); | |
OpenSSL_add_all_algorithms(); | |
#if !defined(OPENSSL_NO_ENGINE) | |
ENGINE_load_builtin_engines(); | |
ENGINE_register_all_ciphers(); | |
ENGINE_register_all_digests(); | |
#endif | |
} | |
int read_cert(ptls_openssl_verify_certificate_t *verify_certificate) { | |
FILE *fp; | |
X509 *cert; | |
if ((fp = fopen(CERT_PATH, "r")) == NULL) { | |
fprintf(stderr, "Failed to open certificate file at %s\n", CERT_PATH); | |
return -1; | |
} | |
while((cert = PEM_read_X509(fp, NULL, NULL, NULL)) != NULL) { | |
ptls_iovec_t *dst = ctx->certificates.list + ctx->certificates.count++; | |
dst->len = i2d_X509(cert, &dst->base); | |
} | |
fclose(fp); | |
if (ctx->certificates.count == 0) { | |
fprintf(stderr, "Failed to load certificates from file at %s\n", CERT_PATH); | |
return -1; | |
} | |
if (ptls_openssl_init_verify_certificate(verify_certificate, NULL) != 0) { | |
fprintf(stderr, "Failed to verify certificate from file at %s\n", CERT_PATH); | |
return -1; | |
} | |
ctx->verify_certificate = &verify_certificate->super; | |
return 0; | |
} | |
int read_pkey(ptls_openssl_sign_certificate_t *sign_certificate) { | |
FILE *fp; | |
if ((fp = fopen(PKEY_PATH, "r")) == NULL) { | |
fprintf(stderr, "Failed to open private key file at %s\n", PKEY_PATH); | |
return -1; | |
} | |
EVP_PKEY *pkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL); | |
fclose(fp); | |
if (pkey == NULL) { | |
fprintf(stderr, "Failed to load private key from file at %s\n", PKEY_PATH); | |
return -1; | |
} | |
int rv = ptls_openssl_init_sign_certificate(sign_certificate, pkey); | |
EVP_PKEY_free(pkey); | |
if (rv) | |
fprintf(stderr, "Failed to sign private key from file at %s\n", PKEY_PATH); | |
return rv; | |
} | |
int resolve_address(struct sockaddr *sa, socklen_t *salen, const char *host, const char *port) { | |
struct addrinfo hints, *res; | |
int err; | |
memset(&hints, 0, sizeof(hints)); | |
hints.ai_socktype = SOCK_STREAM; | |
hints.ai_protocol = IPPROTO_TCP; | |
hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV | AI_PASSIVE; | |
if ((err = getaddrinfo(host, port, &hints, &res)) != 0 || res == NULL) { | |
fprintf(stderr, "Failed to resolve address '%s:%s': %s\n", host, port, | |
err != 0 ? gai_strerror(err) : "getaddrinfo returned NULL\n"); | |
return -1; | |
} | |
memcpy(sa, res->ai_addr, res->ai_addrlen); | |
*salen = res->ai_addrlen; | |
freeaddrinfo(res); | |
return 0; | |
} | |
int write_all(int fd, const uint8_t *data, size_t len) { | |
ssize_t wret; | |
while (len != 0) { | |
while ((wret = write(fd, data, len)) == -1 && errno == EINTR) | |
; | |
if (wret <= 0) { | |
fprintf(stderr, "Write to %d failed.\n", fd); | |
return -1; | |
} | |
data += wret; | |
len -= wret; | |
} | |
return 0; | |
} | |
int do_handshake(int fd, ptls_t *tls, ptls_buffer_t *wbuf, char *rbuf, size_t *rbuf_len, ptls_handshake_properties_t *hsprop, ptls_iovec_t unused) { | |
size_t input_len = *rbuf_len; | |
int ret; | |
ssize_t rret = 0; | |
*rbuf_len = 0; | |
while ((ret = ptls_handshake(tls, wbuf, rbuf, rbuf_len, hsprop)) == PTLS_ERROR_IN_PROGRESS) { | |
if (write_all(fd, wbuf->base, wbuf->off) != 0) | |
return -1; | |
wbuf->off = 0; | |
while ((rret = read(fd, rbuf, input_len)) == -1 && errno == EINTR) | |
; | |
if (rret < 0) { | |
perror("Read from client failed"); | |
return -1; | |
} | |
*rbuf_len = rret; | |
} | |
if (ret != PTLS_ALERT_CLOSE_NOTIFY) { | |
fprintf(stderr, "Handshake failed with error code %d.\n", ret); | |
return -1; | |
} | |
if (write_all(fd, wbuf->base, wbuf->off) != 0) | |
return -1; | |
if (rret != *rbuf_len) | |
memmove(rbuf, rbuf + *rbuf_len, rret - *rbuf_len); | |
*rbuf_len = rret - *rbuf_len; | |
return 0; | |
} | |
int decrypt_and_print(ptls_t *tls, const uint8_t *input, size_t inlen) { | |
ptls_buffer_t decryptbuf; | |
uint8_t decryptbuf_small[1024]; | |
int ret; | |
ptls_buffer_init(&decryptbuf, decryptbuf_small, sizeof(decryptbuf_small)); | |
while (inlen != 0) { | |
size_t consumed = inlen; | |
if ((ret = ptls_receive(tls, &decryptbuf, input, &consumed)) != 0) { | |
fprintf(stderr, "Failed to decrypt: %d\n", ret); | |
ret = -1; | |
goto exit; | |
} | |
input += consumed; | |
inlen -= consumed; | |
if (decryptbuf.off != 0) { | |
if (write_all(1, decryptbuf.base, decryptbuf.off) != 0) { | |
ret = -1; | |
goto exit; | |
} | |
decryptbuf.off = 0; | |
} | |
} | |
ret = 0; | |
exit: | |
ptls_buffer_dispose(&decryptbuf); | |
return ret; | |
} | |
int handle_connection(int server, int client) { | |
int rv = 0; | |
char rbuf[1024], wbuf_small[1024]; | |
ptls_buffer_t wbuf; | |
printf("Connection received\n"); | |
ptls_t *tls = ptls_new(ctx, 1); | |
ptls_buffer_init(&wbuf, wbuf_small, sizeof(wbuf_small)); | |
size_t rbuf_len = sizeof(rbuf); | |
if (do_handshake(client, tls, &wbuf, rbuf, &rbuf_len, hsprop, (ptls_iovec_t){ NULL, 0 }) != 0) { | |
rv = -1; | |
goto exit; | |
} | |
wbuf.off = 0; | |
if (decrypt_and_print(tls, (const uint8_t*)rbuf, rbuf_len) != 0) { | |
rv = -1; | |
goto exit; | |
} | |
// Send a message to the client: | |
if ((rv = ptls_send(tls, &wbuf, "Hello, World!\n", strlen("Hello, World!\n"))) != 0) { | |
fprintf(stderr, "Failed to encrypt message to client: %d\n", rv); | |
rv = -1; | |
goto exit; | |
} | |
if (write_all(client, wbuf.base, wbuf.off) != 0) { | |
rv = -1; | |
goto exit; | |
} | |
exit: | |
ptls_buffer_dispose(&wbuf); | |
ptls_free(tls); | |
return rv; | |
} | |
int run_server(struct sockaddr *sa, socklen_t sa_len) { | |
int fd, on = 1; | |
if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { | |
perror("socket(2) failed"); | |
return -1; | |
} else if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0) { | |
perror("setsockopt(SO_REUSEADDR) failed"); | |
return -1; | |
} else if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)) != 0) { | |
perror("setsockopt(SO_REUSEPORT) failed"); | |
return -1; | |
} else if (bind(fd, sa, sa_len) != 0) { | |
perror("bind(2) failed"); | |
return -1; | |
} else if (listen(fd, SOMAXCONN) != 0) { | |
perror("listen(2) failed"); | |
return -1; | |
} | |
fd_set active_fd_set; | |
FD_ZERO(&active_fd_set); | |
FD_SET(fd, &active_fd_set); | |
const int maxfd = fd; | |
int rv = 0; | |
while (1) { | |
int sel_rv = select(maxfd + 1, &active_fd_set, NULL, NULL, NULL); | |
if (sel_rv < 0) { | |
if (errno == EINTR) | |
break; | |
else { | |
perror("select failed"); | |
rv = -1; | |
break; | |
} | |
} | |
if (FD_ISSET(fd, &active_fd_set)) { | |
int connection; | |
if ((connection = accept(fd, NULL, 0)) != -1) { | |
handle_connection(fd, connection); | |
close(connection); | |
} | |
} | |
} | |
close(fd); | |
return rv; | |
} | |
void signal_handler(int info) { | |
} | |
int main(int argc, char *argv[]) { | |
ossl_start(); | |
struct sockaddr_storage sa; | |
socklen_t sa_len; | |
ptls_iovec_t certs[16] = { { NULL } }; | |
ptls_openssl_sign_certificate_t sign_cert = { { NULL } }; | |
ptls_openssl_verify_certificate_t verify_certificate = { { NULL } }; | |
ptls_handshake_properties_t h = { { { NULL } } }; | |
ptls_context_t c = { | |
ptls_openssl_random_bytes, | |
ptls_openssl_key_exchanges, | |
ptls_openssl_cipher_suites, | |
{ certs, 0 }, | |
NULL, | |
NULL, | |
&sign_cert.super | |
}; | |
ctx = &c; | |
hsprop = &h; | |
// Setup signal handlers | |
int i, rv = 0; | |
for (i = 0; i < (sizeof(signals_list) / sizeof(int)); i++) { | |
if (signal(signals_list[i], signal_handler) == SIG_ERR) { | |
fprintf(stderr, "Failed to setup signal handlers\n"); | |
rv = -1; | |
} | |
} | |
if (rv == 0) | |
rv = read_cert(&verify_certificate); | |
if (rv == 0) | |
rv = read_pkey(&sign_cert); | |
if (rv == 0) | |
rv = resolve_address((struct sockaddr*)&sa, &sa_len, "127.0.0.1", "8000"); | |
if (rv == 0) | |
rv = run_server((struct sockaddr*)&sa, sa_len); | |
printf("\nProgram exited with code %d.\n", rv); | |
return rv; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment