diff options
| author | eunhhu <[email protected]> | 2026-09-24 17:14:54 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-09-24 17:14:54 +0100 |
| commit | b1ce1927022d374198441afafeb27199fc8a052a (patch) | |
| tree | 6021f175f4c67ed03b44240eaa2f90d846f12f6f /prototypes/common | |
| parent | d34f9426d5317a1948f12f229439946728bc342a (diff) | |
| download | pure-b1ce1927022d374198441afafeb27199fc8a052a.tar.gz pure-b1ce1927022d374198441afafeb27199fc8a052a.zip | |
Establish native micro runtime checkpoint
Diffstat (limited to 'prototypes/common')
| -rw-r--r-- | prototypes/common/tls-link.c | 217 | ||||
| -rw-r--r-- | prototypes/common/tls-link.h | 32 |
2 files changed, 249 insertions, 0 deletions
diff --git a/prototypes/common/tls-link.c b/prototypes/common/tls-link.c new file mode 100644 index 0000000..adfaac2 --- /dev/null +++ b/prototypes/common/tls-link.c @@ -0,0 +1,217 @@ +#define _POSIX_C_SOURCE 200809L +#include "tls-link.h" +#include <openssl/err.h> +#include <openssl/x509v3.h> +#include <arpa/inet.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <netdb.h> +#include <netinet/tcp.h> +#include <poll.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <time.h> +#include <unistd.h> + +uint64_t pure_link_now_ms(void) { + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts)) abort(); + return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000; +} +static bool ready(int fd, short events, uint64_t deadline) { + for (;;) { + uint64_t now = pure_link_now_ms(); + if (now >= deadline) return false; + int remaining = (int)(deadline - now); + struct pollfd p = {.fd = fd, .events = events}; + int result = poll(&p, 1, remaining); + if (result > 0) return !(p.revents & (POLLERR | POLLNVAL)) && (p.revents & (events | POLLHUP)); + if (!result) return false; + if (errno != EINTR) return false; + } +} +bool pure_link_nonblocking(int fd) { + int flags = fcntl(fd, F_GETFL, 0); + return flags >= 0 && fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0; +} +static bool tls_retry(PureLink *channel, int code, uint64_t deadline) { + if (code == SSL_ERROR_WANT_READ) return ready(channel->fd, POLLIN, deadline); + if (code == SSL_ERROR_WANT_WRITE) return ready(channel->fd, POLLOUT, deadline); + return false; +} +int pure_link_read(PureLink *channel, void *output, size_t size, uint64_t deadline) { + size_t at = 0; + while (at < size) { + if (pure_link_now_ms() >= deadline) return -1; + size_t received = 0; + ERR_clear_error(); + int result = SSL_read_ex(channel->ssl, (unsigned char *)output + at, size - at, &received); + if (result == 1) { if (!received) return -1; at += received; continue; } + int code = SSL_get_error(channel->ssl, result); + if (code == SSL_ERROR_ZERO_RETURN && !at) return 0; + if (!tls_retry(channel, code, deadline)) return -1; + } + return 1; +} +bool pure_link_write(PureLink *channel, const void *input, size_t size, uint64_t deadline) { + size_t at = 0; + while (at < size) { + if (pure_link_now_ms() >= deadline) return false; + size_t sent = 0; + size_t chunk = size - at; + if (chunk > 16384) chunk = 16384; + ERR_clear_error(); + int result = SSL_write_ex(channel->ssl, (const unsigned char *)input + at, chunk, &sent); + if (result == 1) { if (!sent) return false; at += sent; continue; } + int code = SSL_get_error(channel->ssl, result); + if (!tls_retry(channel, code, deadline)) return false; + } + return true; +} +int pure_link_read_frame(PureLink *channel, unsigned char *output, size_t capacity, + size_t *size, uint64_t deadline) { + unsigned char prefix[2]; + int first = pure_link_read(channel, prefix, 2, deadline); + if (first != 1) return first; + size_t length = ((size_t)prefix[0] << 8) | prefix[1]; + if (!length || length > capacity || pure_link_read(channel, output, length, deadline) != 1) return -1; + *size = length; + return 1; +} +bool pure_link_write_frame(PureLink *channel, const unsigned char *data, size_t size, + uint64_t deadline) { + if (!size || size > UINT16_MAX) return false; + unsigned char prefix[2] = {(unsigned char)(size >> 8), (unsigned char)size}; + return pure_link_write(channel, prefix, 2, deadline) && pure_link_write(channel, data, size, deadline); +} +void pure_link_close(PureLink *channel, bool healthy) { + if (channel->ssl) { + if (healthy) (void)SSL_shutdown(channel->ssl); + SSL_free(channel->ssl); + } + if (channel->fd >= 0) close(channel->fd); + *channel = (PureLink){.fd = -1}; +} +static int select_alpn(SSL *ssl, const unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int length, void *arg) { + (void)ssl; + const unsigned char *alpn = arg; + for (unsigned int at = 0; at < length;) { + unsigned int n = in[at++]; + if (!n || n > length - at) return SSL_TLSEXT_ERR_ALERT_FATAL; + if (n == alpn[0] && !memcmp(in + at, alpn + 1, n)) { + *out = alpn + 1; *outlen = alpn[0]; return SSL_TLSEXT_ERR_OK; + } + at += n; + } + return SSL_TLSEXT_ERR_ALERT_FATAL; +} +SSL_CTX *pure_link_context(bool server, const char *ca, const char *cert, + const char *key, const unsigned char *alpn) { + SSL_CTX *ctx = SSL_CTX_new(server ? TLS_server_method() : TLS_client_method()); + if (!ctx) return NULL; + if (!alpn || !alpn[0] || !SSL_CTX_set_min_proto_version(ctx, TLS1_3_VERSION) || + !SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION) || + !SSL_CTX_set_max_early_data(ctx, 0) || + !SSL_CTX_load_verify_locations(ctx, ca, NULL) || + SSL_CTX_use_certificate_chain_file(ctx, cert) != 1 || + SSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM) != 1 || + SSL_CTX_check_private_key(ctx) != 1) { + SSL_CTX_free(ctx); return NULL; + } + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | (server ? SSL_VERIFY_FAIL_IF_NO_PEER_CERT : 0), NULL); + if (server) SSL_CTX_set_alpn_select_cb(ctx, select_alpn, (void *)alpn); + return ctx; +} +bool pure_link_handshake(PureLink *channel, SSL_CTX *ctx, bool server, + const char *peer_name, const unsigned char *alpn) { + /* This protocol writes a small reply header followed by a separate TLS + * body record. Disable Nagle on the connected TCP socket so that the + * header cannot hold the body behind a delayed acknowledgement. */ + int enabled = 1; + if (setsockopt(channel->fd, IPPROTO_TCP, TCP_NODELAY, &enabled, sizeof(enabled))) return false; + channel->ssl = SSL_new(ctx); + if (!channel->ssl || SSL_set_fd(channel->ssl, channel->fd) != 1) return false; + if (server) SSL_set_accept_state(channel->ssl); + else { + SSL_set_connect_state(channel->ssl); + SSL_set_hostflags(channel->ssl, X509_CHECK_FLAG_NO_WILDCARDS | X509_CHECK_FLAG_NEVER_CHECK_SUBJECT); + if (SSL_set1_host(channel->ssl, peer_name) != 1 || + SSL_set_tlsext_host_name(channel->ssl, peer_name) != 1 || + SSL_set_alpn_protos(channel->ssl, alpn, alpn[0] + 1u) != 0) return false; + } + uint64_t deadline = pure_link_now_ms() + PURE_LINK_DEADLINE_MS; + for (;;) { + ERR_clear_error(); + int result = SSL_do_handshake(channel->ssl); + if (result == 1) break; + int code = SSL_get_error(channel->ssl, result); + if (!tls_retry(channel, code, deadline)) return false; + } + if (SSL_version(channel->ssl) != TLS1_3_VERSION || SSL_get_verify_result(channel->ssl) != X509_V_OK) + return false; + X509 *peer = SSL_get1_peer_certificate(channel->ssl); + if (!peer) return false; + bool verified = !server || X509_check_host(peer, peer_name, 0, + X509_CHECK_FLAG_NO_WILDCARDS | X509_CHECK_FLAG_NEVER_CHECK_SUBJECT, NULL) == 1; + X509_free(peer); + const unsigned char *chosen = NULL; + unsigned int length = 0; + SSL_get0_alpn_selected(channel->ssl, &chosen, &length); + return verified && length == alpn[0] && !memcmp(chosen, alpn + 1, length); +} +bool pure_link_port_number(const char *text, bool allow_zero) { + if (!text || !*text) return false; + unsigned long value = 0; + for (const char *p = text; *p; ++p) { + if (*p < '0' || *p > '9') return false; + unsigned digit = (unsigned)(*p - '0'); + if (value > (65535ul - digit) / 10) return false; + value = value * 10 + digit; + } + return allow_zero || value != 0; +} +int pure_link_listen_at(const char *host, const char *port) { + struct addrinfo hints = {.ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM, + .ai_flags = AI_PASSIVE | AI_NUMERICHOST}; + struct addrinfo *addresses = NULL; + if (getaddrinfo(host, port, &hints, &addresses)) return -1; + int listener = -1; + for (struct addrinfo *it = addresses; it; it = it->ai_next) { + int fd = socket(it->ai_family, it->ai_socktype, it->ai_protocol); + if (fd < 0) continue; + int enabled = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enabled, sizeof(enabled)); + if (bind(fd, it->ai_addr, it->ai_addrlen) == 0 && listen(fd, 32) == 0 && pure_link_nonblocking(fd)) { + listener = fd; break; + } + close(fd); + } + freeaddrinfo(addresses); + return listener; +} +int pure_link_connect_to(const char *host, const char *port) { + struct addrinfo hints = {.ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM}; + struct addrinfo *addresses = NULL; + if (getaddrinfo(host, port, &hints, &addresses)) return -1; + int connected = -1; + uint64_t deadline = pure_link_now_ms() + PURE_LINK_DEADLINE_MS; + for (struct addrinfo *it = addresses; it; it = it->ai_next) { + int fd = socket(it->ai_family, it->ai_socktype, it->ai_protocol); + if (fd < 0) continue; + if (pure_link_nonblocking(fd)) { + int result = connect(fd, it->ai_addr, it->ai_addrlen); + if (result == 0 || (result < 0 && errno == EINPROGRESS && ready(fd, POLLOUT, deadline))) { + int error = 0; socklen_t size = sizeof(error); + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &size) == 0 && !error) { + connected = fd; break; + } + } + } + close(fd); + } + freeaddrinfo(addresses); + return connected; +} diff --git a/prototypes/common/tls-link.h b/prototypes/common/tls-link.h new file mode 100644 index 0000000..53e05ff --- /dev/null +++ b/prototypes/common/tls-link.h @@ -0,0 +1,32 @@ +#ifndef PURE_TLS_LINK_H +#define PURE_TLS_LINK_H +#include <openssl/ssl.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +enum { PURE_LINK_DEADLINE_MS = 5000 }; +typedef struct { int fd; SSL *ssl; } PureLink; + +uint64_t pure_link_now_ms(void); +bool pure_link_nonblocking(int fd); +/* read returns 1 for all bytes, 0 for clean EOF before any byte, -1 for an + * incomplete frame, TLS failure or deadline. Never treat -1 as an EOF. */ +int pure_link_read(PureLink *, void *output, size_t size, uint64_t deadline); +bool pure_link_write(PureLink *, const void *input, size_t size, uint64_t deadline); +int pure_link_read_frame(PureLink *, unsigned char *output, size_t capacity, + size_t *size, uint64_t deadline); +bool pure_link_write_frame(PureLink *, const unsigned char *data, size_t size, + uint64_t deadline); +void pure_link_close(PureLink *, bool healthy); + +/* alpn is a static length-prefixed protocol name, retained by SSL_CTX's + * callback until the context is freed. Its content has exactly alpn[0]+1 bytes. */ +SSL_CTX *pure_link_context(bool server, const char *ca, const char *cert, + const char *key, const unsigned char *alpn); +bool pure_link_handshake(PureLink *, SSL_CTX *, bool server, const char *peer_name, + const unsigned char *alpn); +bool pure_link_port_number(const char *text, bool allow_zero); +int pure_link_listen_at(const char *host, const char *port); +int pure_link_connect_to(const char *host, const char *port); +#endif |
