aboutsummaryrefslogtreecommitdiffstats
path: root/serve
diff options
context:
space:
mode:
Diffstat (limited to 'serve')
-rw-r--r--serve/crypto-openssl.c (renamed from serve/seal.c)107
-rw-r--r--serve/crypto.c121
-rw-r--r--serve/crypto.h31
-rw-r--r--serve/deliver.c72
-rw-r--r--serve/seal.h5
-rw-r--r--serve/serve.c5
-rw-r--r--serve/tls.h18
7 files changed, 255 insertions, 104 deletions
diff --git a/serve/seal.c b/serve/crypto-openssl.c
index c1cc55b..38b4628 100644
--- a/serve/seal.c
+++ b/serve/crypto-openssl.c
@@ -1,72 +1,80 @@
#define _GNU_SOURCE
-#include "seal.h"
+/* pure-crypto.so: what pure-serve asks of OpenSSL (serve/crypto.h), mapped
+ * only when a server seals, opens a seal or reaches an https receiver. It
+ * exports one table, pure_crypto. */
+#include "crypto.h"
+#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <openssl/crypto.h>
#include <openssl/evp.h>
#include <openssl/hpke.h>
+#include <openssl/ssl.h>
+#include <openssl/x509v3.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
-#if defined(__aarch64__) && defined(__linux__)
-#include <asm/hwcap.h>
-#include <sys/auxv.h>
-#endif
-struct PureSealKey { EVP_PKEY *key; };
+/* ---- TLS: one client context, verifying the receiver's certificate against
+ * the system's CAs or the file given, and its name. */
+static SSL_CTX *tls_context;
-static const unsigned char info[] = "pure sealed send 1";
-static const char aes_name[] = "x25519-sha256-aes128gcm", chacha_name[] = "x25519-sha256-chacha20poly1305";
+static bool tls_start(const char *ca_file) {
+ SSL_CTX *context = SSL_CTX_new(TLS_client_method());
+ if (!context) return false;
+ if (!SSL_CTX_set_min_proto_version(context, TLS1_2_VERSION) ||
+ !(ca_file ? SSL_CTX_load_verify_locations(context, ca_file, NULL) : SSL_CTX_set_default_verify_paths(context))) {
+ SSL_CTX_free(context);
+ return false;
+ }
+ SSL_CTX_set_verify(context, SSL_VERIFY_PEER, NULL);
+ SSL_CTX_set_mode(context, SSL_MODE_AUTO_RETRY);
+ tls_context = context;
+ return true;
+}
-/* AES-GCM where AES is in the CPU (two to three times ChaCha20-Poly1305 from
- * a kilobyte up on a Cortex-A76, research 086); ChaCha20-Poly1305, fast in
- * plain code, elsewhere. */
-static bool has_aes(void) {
-#if defined(__aarch64__) && defined(__linux__)
- return (getauxval(AT_HWCAP) & HWCAP_AES) && (getauxval(AT_HWCAP) & HWCAP_PMULL);
-#elif defined(__x86_64__) && (defined(__GNUC__) || defined(__clang__))
- return __builtin_cpu_supports("aes") && __builtin_cpu_supports("pclmul");
-#else
- return false;
-#endif
+/* TLS over a connected socket: the receiver's certificate must name the host
+ * (a DNS name, or an IP address written as one). */
+static PureTls *tls_open(const char *host, int fd) {
+ SSL *ssl = SSL_new(tls_context);
+ if (!ssl) return NULL;
+ unsigned char address[16];
+ bool literal = inet_pton(AF_INET, host, address) == 1 || inet_pton(AF_INET6, host, address) == 1;
+ bool named = literal ? X509_VERIFY_PARAM_set1_ip_asc(SSL_get0_param(ssl), host) == 1 :
+ SSL_set_tlsext_host_name(ssl, host) == 1 && SSL_set1_host(ssl, host) == 1;
+ if (!named || SSL_set_fd(ssl, fd) != 1 || SSL_connect(ssl) != 1 || SSL_get_verify_result(ssl) != X509_V_OK) {
+ SSL_free(ssl);
+ return NULL;
+ }
+ return (PureTls *)ssl;
}
+static int tls_read(PureTls *tls, void *bytes, int size) { return SSL_read((SSL *)tls, bytes, size); }
+static int tls_write(PureTls *tls, const void *bytes, int size) { return SSL_write((SSL *)tls, bytes, size); }
+static void tls_close(PureTls *tls) { SSL_free((SSL *)tls); }
+
+/* ---- Sealing: HPKE base mode (RFC 9180), X25519 and HKDF-SHA256, with the
+ * AEAD the suite names. */
+struct PureSealKey { EVP_PKEY *key; };
+
+static const unsigned char info[] = "pure sealed send 1";
+
static bool suite_named(const char *name, OSSL_HPKE_SUITE *out) {
OSSL_HPKE_SUITE suite = {OSSL_HPKE_KEM_ID_X25519, OSSL_HPKE_KDF_ID_HKDF_SHA256, 0};
- if (!strcmp(name, aes_name)) suite.aead_id = OSSL_HPKE_AEAD_ID_AES_GCM_128;
- else if (!strcmp(name, chacha_name)) suite.aead_id = OSSL_HPKE_AEAD_ID_CHACHA_POLY1305;
+ if (!strcmp(name, "x25519-sha256-aes128gcm")) suite.aead_id = OSSL_HPKE_AEAD_ID_AES_GCM_128;
+ else if (!strcmp(name, "x25519-sha256-chacha20poly1305")) suite.aead_id = OSSL_HPKE_AEAD_ID_CHACHA_POLY1305;
else return false;
*out = suite;
return true;
}
-const char *pure_seal_suite(void) { return has_aes() ? aes_name : chacha_name; }
-
static pthread_once_t secure_once = PTHREAD_ONCE_INIT;
static void secure_heap(void) { CRYPTO_secure_malloc_init(16384, 32); }
-static bool hex_value(char c, unsigned *v) {
- if (c >= '0' && c <= '9') *v = (unsigned)(c - '0');
- else if (c >= 'a' && c <= 'f') *v = (unsigned)(c - 'a' + 10);
- else if (c >= 'A' && c <= 'F') *v = (unsigned)(c - 'A' + 10);
- else return false;
- return true;
-}
-
-bool pure_seal_peer(const char *hex, unsigned char out[PURE_SEAL_KEY]) {
- if (strlen(hex) != 2 * PURE_SEAL_KEY) return false;
- for (size_t i = 0; i < PURE_SEAL_KEY; ++i) {
- unsigned high, low;
- if (!hex_value(hex[2 * i], &high) || !hex_value(hex[2 * i + 1], &low)) return false;
- out[i] = (unsigned char)(high << 4 | low);
- }
- return true;
-}
-
-PureSealKey *pure_seal_key(const char *path, char public_hex[2 * PURE_SEAL_KEY + 1], char *error, size_t size) {
+static PureSealKey *seal_key(const char *path, char public_hex[2 * PURE_SEAL_KEY + 1], char *error, size_t size) {
pthread_once(&secure_once, secure_heap);
unsigned char *raw = OPENSSL_secure_malloc(PURE_SEAL_KEY), public_key[PURE_SEAL_KEY];
PureSealKey *made = calloc(1, sizeof(*made));
@@ -106,16 +114,16 @@ fail:
return NULL;
}
-void pure_seal_key_free(PureSealKey *key) {
+static void seal_key_free(PureSealKey *key) {
if (!key) return;
EVP_PKEY_free(key->key);
free(key);
}
-bool pure_seal(const unsigned char peer[PURE_SEAL_KEY], const char *aad, const unsigned char *plain, size_t size,
- unsigned char *out) {
+static bool seal(const char *suite_name, const unsigned char peer[PURE_SEAL_KEY], const char *aad,
+ const unsigned char *plain, size_t size, unsigned char *out) {
OSSL_HPKE_SUITE suite;
- if (!suite_named(pure_seal_suite(), &suite)) return false;
+ if (!suite_named(suite_name, &suite)) return false;
OSSL_HPKE_CTX *context = OSSL_HPKE_CTX_new(OSSL_HPKE_MODE_BASE, suite, OSSL_HPKE_ROLE_SENDER, NULL, NULL);
size_t encapsulated = PURE_SEAL_KEY, sealed = size + 16;
bool ok = context && OSSL_HPKE_encap(context, out, &encapsulated, peer, PURE_SEAL_KEY, info, sizeof(info) - 1) == 1 &&
@@ -126,8 +134,8 @@ bool pure_seal(const unsigned char peer[PURE_SEAL_KEY], const char *aad, const u
return ok;
}
-bool pure_unseal(const PureSealKey *key, const char *suite_name, const char *aad, const unsigned char *sealed, size_t size,
- unsigned char *plain) {
+static bool unseal(const PureSealKey *key, const char *suite_name, const char *aad, const unsigned char *sealed,
+ size_t size, unsigned char *plain) {
OSSL_HPKE_SUITE suite;
if (!key || size < PURE_SEAL_OVERHEAD || !suite_named(suite_name, &suite)) return false;
OSSL_HPKE_CTX *context = OSSL_HPKE_CTX_new(OSSL_HPKE_MODE_BASE, suite, OSSL_HPKE_ROLE_RECEIVER, NULL, NULL);
@@ -140,3 +148,6 @@ bool pure_unseal(const PureSealKey *key, const char *suite_name, const char *aad
OSSL_HPKE_CTX_free(context);
return ok;
}
+
+const PureCrypto pure_crypto = {PURE_CRYPTO_VERSION, tls_start, tls_open, tls_read, tls_write, tls_close,
+ seal_key, seal_key_free, seal, unseal};
diff --git a/serve/crypto.c b/serve/crypto.c
new file mode 100644
index 0000000..8806ad2
--- /dev/null
+++ b/serve/crypto.c
@@ -0,0 +1,121 @@
+#define _GNU_SOURCE
+/* Sealing and TLS for pure-serve (serve/seal.h, serve/tls.h), by way of
+ * pure-crypto.so (serve/crypto.h): mapped, and OpenSSL with it, the first
+ * time a server needs it. What needs no OpenSSL (a key's hex, the suite
+ * this machine seals with) is here. */
+#include "crypto.h"
+#include <dlfcn.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#if defined(__aarch64__) && defined(__linux__)
+#include <asm/hwcap.h>
+#include <sys/auxv.h>
+#endif
+
+static pthread_once_t once = PTHREAD_ONCE_INIT;
+static const PureCrypto *crypto;
+static char why[512];
+
+/* pure-crypto.so beside the executable, or where PURE_CRYPTO names it. */
+static void load(void) {
+ char path[4096];
+ const char *named = getenv("PURE_CRYPTO");
+ if (named) snprintf(path, sizeof(path), "%s", named);
+ else {
+ ssize_t length = readlink("/proc/self/exe", path, sizeof(path) - 32);
+ char *slash = length > 0 ? memrchr(path, '/', (size_t)length) : NULL;
+ if (!slash) { snprintf(why, sizeof(why), "pure-crypto.so: cannot find pure-serve's directory"); return; }
+ strcpy(slash + 1, "pure-crypto.so");
+ }
+ void *library = dlopen(path, RTLD_NOW | RTLD_LOCAL);
+ const PureCrypto *table = library ? dlsym(library, "pure_crypto") : NULL;
+ if (!table || table->version != PURE_CRYPTO_VERSION) {
+ snprintf(why, sizeof(why), "%s", !library ? dlerror() : "pure-crypto.so is not this pure-serve's");
+ return;
+ }
+ crypto = table;
+}
+
+static const PureCrypto *loaded(char *error, size_t size) {
+ pthread_once(&once, load);
+ if (!crypto && error) snprintf(error, size, "%s", why);
+ return crypto;
+}
+
+/* ---- TLS */
+
+bool pure_tls_start(const char *ca_file, char *error, size_t size) {
+ static int started; /* 1 made, -1 failed; by the thread that opens wires */
+ const PureCrypto *c = loaded(error, size);
+ if (!c) return false;
+ if (!started) started = c->tls_start(ca_file) ? 1 : -1;
+ if (started < 0) snprintf(error, size, "no TLS context: %s cannot be read", ca_file ? ca_file : "the system's CAs");
+ return started > 0;
+}
+
+/* A session exists only once the table is loaded. */
+PureTls *pure_tls_open(const char *host, int fd) { return crypto ? crypto->tls_open(host, fd) : NULL; }
+int pure_tls_read(PureTls *tls, void *bytes, int size) { return crypto->tls_read(tls, bytes, size); }
+int pure_tls_write(PureTls *tls, const void *bytes, int size) { return crypto->tls_write(tls, bytes, size); }
+void pure_tls_close(PureTls *tls) { if (tls) crypto->tls_close(tls); }
+
+/* ---- Sealing */
+
+static const char aes_name[] = "x25519-sha256-aes128gcm", chacha_name[] = "x25519-sha256-chacha20poly1305";
+
+/* AES-GCM where AES is in the CPU (two to three times ChaCha20-Poly1305 from
+ * a kilobyte up on a Cortex-A76, research 086); ChaCha20-Poly1305, fast in
+ * plain code, elsewhere. */
+static bool has_aes(void) {
+#if defined(__aarch64__) && defined(__linux__)
+ return (getauxval(AT_HWCAP) & HWCAP_AES) && (getauxval(AT_HWCAP) & HWCAP_PMULL);
+#elif defined(__x86_64__) && (defined(__GNUC__) || defined(__clang__))
+ return __builtin_cpu_supports("aes") && __builtin_cpu_supports("pclmul");
+#else
+ return false;
+#endif
+}
+
+const char *pure_seal_suite(void) { return has_aes() ? aes_name : chacha_name; }
+
+static bool hex_value(char c, unsigned *v) {
+ if (c >= '0' && c <= '9') *v = (unsigned)(c - '0');
+ else if (c >= 'a' && c <= 'f') *v = (unsigned)(c - 'a' + 10);
+ else if (c >= 'A' && c <= 'F') *v = (unsigned)(c - 'A' + 10);
+ else return false;
+ return true;
+}
+
+bool pure_seal_peer(const char *hex, unsigned char out[PURE_SEAL_KEY]) {
+ if (strlen(hex) != 2 * PURE_SEAL_KEY) return false;
+ for (size_t i = 0; i < PURE_SEAL_KEY; ++i) {
+ unsigned high, low;
+ if (!hex_value(hex[2 * i], &high) || !hex_value(hex[2 * i + 1], &low)) return false;
+ out[i] = (unsigned char)(high << 4 | low);
+ }
+ return true;
+}
+
+bool pure_seal_ready(char *error, size_t size) { return loaded(error, size) != NULL; }
+
+PureSealKey *pure_seal_key(const char *path, char public_hex[2 * PURE_SEAL_KEY + 1], char *error, size_t size) {
+ const PureCrypto *c = loaded(error, size);
+ return c ? c->seal_key(path, public_hex, error, size) : NULL;
+}
+
+/* A key exists only once the table is loaded. */
+void pure_seal_key_free(PureSealKey *key) { if (key) crypto->seal_key_free(key); }
+
+bool pure_seal(const unsigned char peer[PURE_SEAL_KEY], const char *aad, const unsigned char *plain, size_t size,
+ unsigned char *out) {
+ const PureCrypto *c = loaded(NULL, 0);
+ return c && c->seal(pure_seal_suite(), peer, aad, plain, size, out);
+}
+
+bool pure_unseal(const PureSealKey *key, const char *suite, const char *aad, const unsigned char *sealed, size_t size,
+ unsigned char *plain) {
+ return key && crypto->unseal(key, suite, aad, sealed, size, plain);
+}
diff --git a/serve/crypto.h b/serve/crypto.h
new file mode 100644
index 0000000..5083b23
--- /dev/null
+++ b/serve/crypto.h
@@ -0,0 +1,31 @@
+#ifndef PURE_CRYPTO_H
+#define PURE_CRYPTO_H
+#include "seal.h"
+#include "tls.h"
+
+/* What pure-serve asks of OpenSSL, as the table pure-crypto.so exports
+ * (serve/crypto-openssl.c). pure-serve maps it, and OpenSSL with it, the
+ * first time a server seals, opens a seal or connects to an https receiver
+ * (serve/crypto.c): one that does none of these maps no OpenSSL, 2.3 MiB
+ * less resident and 0.6 MiB less of its own. */
+enum { PURE_CRYPTO_VERSION = 1 };
+
+typedef struct {
+ unsigned version;
+ /* TLS client: the context (verifying against ca_file, or the system's
+ * CAs when NULL), sessions over connected sockets, their reads and
+ * writes (as SSL_read and SSL_write: bytes, or <= 0), and their end. */
+ bool (*tls_start)(const char *ca_file);
+ PureTls *(*tls_open)(const char *host, int fd);
+ int (*tls_read)(PureTls *, void *bytes, int size);
+ int (*tls_write)(PureTls *, const void *bytes, int size);
+ void (*tls_close)(PureTls *);
+ /* Sealing, as serve/seal.h says, in the suite named. */
+ PureSealKey *(*seal_key)(const char *path, char public_hex[2 * PURE_SEAL_KEY + 1], char *error, size_t size);
+ void (*seal_key_free)(PureSealKey *);
+ bool (*seal)(const char *suite, const unsigned char peer[PURE_SEAL_KEY], const char *aad,
+ const unsigned char *plain, size_t size, unsigned char *out);
+ bool (*unseal)(const PureSealKey *, const char *suite, const char *aad, const unsigned char *sealed, size_t size,
+ unsigned char *plain);
+} PureCrypto;
+#endif
diff --git a/serve/deliver.c b/serve/deliver.c
index 391e0db..99f2f53 100644
--- a/serve/deliver.c
+++ b/serve/deliver.c
@@ -7,9 +7,7 @@
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
-#include <openssl/ssl.h>
-#include <openssl/x509v3.h>
-#include <arpa/inet.h>
+#include "tls.h"
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
@@ -44,8 +42,8 @@ struct PureWire {
void (*notify)(void *);
void *context;
int fd; /* the connection kept for the next send, or -1; changed under the lock */
- bool tls; /* https: the connection is TLS */
- SSL *ssl; /* its TLS session */
+ bool tls; /* https: the connection is TLS */
+ PureTls *session; /* its TLS session */
int answer_input; /* the input answers become events of, or -1 */
Answer *answers; /* made, for the owner to take in order */
size_t answer_head, answer_count, answer_capacity;
@@ -55,27 +53,12 @@ struct PureWire {
unsigned char peer[PURE_SEAL_KEY];
};
-/* ---- TLS: one client context, verifying the receiver's certificate against
- * the system's CAs or the file pure_wire_trust named, and its name. */
-static pthread_once_t tls_once = PTHREAD_ONCE_INIT;
-static SSL_CTX *tls_context;
+/* TLS verifies a receiver's certificate against the system's CAs or the
+ * file pure_wire_trust named (serve/tls.h). */
static const char *tls_ca;
void pure_wire_trust(const char *ca_file) { tls_ca = ca_file; }
-static void make_tls_context(void) {
- SSL_CTX *context = SSL_CTX_new(TLS_client_method());
- if (!context) return;
- if (!SSL_CTX_set_min_proto_version(context, TLS1_2_VERSION) ||
- !(tls_ca ? SSL_CTX_load_verify_locations(context, tls_ca, NULL) : SSL_CTX_set_default_verify_paths(context))) {
- SSL_CTX_free(context);
- return;
- }
- SSL_CTX_set_verify(context, SSL_VERIFY_PEER, NULL);
- SSL_CTX_set_mode(context, SSL_MODE_AUTO_RETRY);
- tls_context = context;
-}
-
/* ---- A send as JSON: its fields in order, a group's fields as an object. */
typedef struct { char *bytes; size_t size, capacity; bool failed; } Text;
@@ -213,13 +196,7 @@ PureWire *pure_wire_open(const PureModule *m, const char *output, const char *ur
snprintf(error, size, "a URL is http[s]://host[:port]/path");
return NULL;
}
- if (tls) {
- pthread_once(&tls_once, make_tls_context);
- if (!tls_context) {
- snprintf(error, size, "no TLS context: %s cannot be read", tls_ca ? tls_ca : "the system's CAs");
- return NULL;
- }
- }
+ if (tls && !pure_tls_start(tls_ca, error, size)) return NULL;
const char *host = url + (tls ? 8 : 7), *slash = strchr(host, '/'), *colon = memchr(host, ':', slash ? (size_t)(slash - host) : strlen(host));
const char *host_end = colon ? colon : slash ? slash : host + strlen(host);
const char *port = colon ? colon + 1 : tls ? "443" : "80";
@@ -341,7 +318,7 @@ static bool fill(Reader *r) {
if (r->end == sizeof(r->bytes)) return false;
PureWire *w = r->wire;
ssize_t n;
- if (w->ssl) n = SSL_read(w->ssl, r->bytes + r->end, (int)(sizeof(r->bytes) - r->end));
+ if (w->session) n = pure_tls_read(w->session, r->bytes + r->end, (int)(sizeof(r->bytes) - r->end));
else do n = recv(w->fd, r->bytes + r->end, sizeof(r->bytes) - r->end, 0); while (n < 0 && errno == EINTR);
if (n <= 0) return false;
#ifdef TCP_QUICKACK
@@ -392,9 +369,9 @@ static bool take(Reader *r, uint64_t size, Text *into) {
static bool send_all(PureWire *w, const char *bytes, size_t size) {
while (size) {
- ssize_t n = w->ssl ? SSL_write(w->ssl, bytes, (int)(size < INT32_MAX ? size : INT32_MAX)) :
- send(w->fd, bytes, size, MSG_NOSIGNAL);
- if (n < 0 && !w->ssl && errno == EINTR) continue;
+ ssize_t n = w->session ? pure_tls_write(w->session, bytes, (int)(size < INT32_MAX ? size : INT32_MAX)) :
+ send(w->fd, bytes, size, MSG_NOSIGNAL);
+ if (n < 0 && !w->session && errno == EINTR) continue;
if (n <= 0) return false;
bytes += n;
size -= (size_t)n;
@@ -402,22 +379,6 @@ static bool send_all(PureWire *w, const char *bytes, size_t size) {
return true;
}
-/* TLS over a connected socket: the receiver's certificate must name the host
- * (a DNS name, or an IP address written as one). */
-static SSL *start_tls(const PureWire *w, int fd) {
- SSL *ssl = SSL_new(tls_context);
- if (!ssl) return NULL;
- unsigned char address[16];
- bool literal = inet_pton(AF_INET, w->host, address) == 1 || inet_pton(AF_INET6, w->host, address) == 1;
- bool named = literal ? X509_VERIFY_PARAM_set1_ip_asc(SSL_get0_param(ssl), w->host) == 1 :
- SSL_set_tlsext_host_name(ssl, w->host) == 1 && SSL_set1_host(ssl, w->host) == 1;
- if (!named || SSL_set_fd(ssl, fd) != 1 || SSL_connect(ssl) != 1 || SSL_get_verify_result(ssl) != X509_V_OK) {
- SSL_free(ssl);
- return NULL;
- }
- return ssl;
-}
-
static int connect_to(const PureWire *w) {
struct addrinfo hints = {.ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM}, *list;
if (getaddrinfo(w->host, w->port, &hints, &list)) return -1;
@@ -441,9 +402,9 @@ static int connect_to(const PureWire *w) {
static void drop_connection(PureWire *w) {
pthread_mutex_lock(&w->lock);
- if (w->ssl) SSL_free(w->ssl);
+ pure_tls_close(w->session);
if (w->fd >= 0) close(w->fd);
- w->ssl = NULL;
+ w->session = NULL;
w->fd = -1;
pthread_mutex_unlock(&w->lock);
}
@@ -623,13 +584,14 @@ static int send_request(PureWire *w, const char *request, size_t size, const Pen
bool stopping = w->stopping;
pthread_mutex_unlock(&w->lock);
if (stopping) return AGAIN;
- SSL *ssl = w->tls ? start_tls(w, fd) : NULL;
- if (w->tls && !ssl) {
+ /* The receiver's certificate must name the host. */
+ PureTls *session = w->tls ? pure_tls_open(w->host, fd) : NULL;
+ if (w->tls && !session) {
drop_connection(w);
return TLS_FAILED;
}
pthread_mutex_lock(&w->lock);
- w->ssl = ssl;
+ w->session = session;
pthread_mutex_unlock(&w->lock);
}
int status;
@@ -743,7 +705,7 @@ void pure_wire_close(PureWire *w) {
pthread_cond_broadcast(&w->changed);
pthread_mutex_unlock(&w->lock);
if (w->started) pthread_join(w->thread, NULL);
- if (w->ssl) SSL_free(w->ssl);
+ pure_tls_close(w->session);
if (w->fd >= 0) close(w->fd);
for (size_t i = 0; i < w->count; ++i) {
free(w->queue[w->head + i].body);
diff --git a/serve/seal.h b/serve/seal.h
index 4ce3844..663d860 100644
--- a/serve/seal.h
+++ b/serve/seal.h
@@ -9,7 +9,8 @@
* (RFC 9180) base mode: X25519, HKDF-SHA256, and AES-128-GCM where the CPU
* has AES instructions, else ChaCha20-Poly1305. A private key lives in
* OpenSSL's secure heap: locked in memory, out of core dumps, cleared when
- * freed. */
+ * freed. OpenSSL comes with pure-crypto.so (serve/crypto.c), mapped the
+ * first time a key is read or a send sealed. */
enum { PURE_SEAL_KEY = 32, PURE_SEAL_OVERHEAD = 32 + 16 }; /* the encapsulated key, then the tag */
typedef struct PureSealKey PureSealKey;
@@ -17,6 +18,8 @@ typedef struct PureSealKey PureSealKey;
* is none; its public half in hex. NULL with why in error. */
PureSealKey *pure_seal_key(const char *path, char public_hex[2 * PURE_SEAL_KEY + 1], char *error, size_t size);
void pure_seal_key_free(PureSealKey *);
+/* Whether this machine can seal (pure-crypto.so maps), with why not in error. */
+bool pure_seal_ready(char *error, size_t size);
/* A public key from its hex. */
bool pure_seal_peer(const char *hex, unsigned char out[PURE_SEAL_KEY]);
/* The suite this machine seals with, as the X-Pure-Seal header names it. */
diff --git a/serve/serve.c b/serve/serve.c
index 4f633ed..5088d8a 100644
--- a/serve/serve.c
+++ b/serve/serve.c
@@ -1754,6 +1754,11 @@ static bool open_wires(Executor *ex, const PureModule *m, const char *const *sen
/* --seal OUTPUT=KEY: that output's sends sealed to the receiver's public key (hex). */
static bool wire_seals(Executor *ex, const PureModule *m, const char *const *seals, unsigned count) {
+ char error[512];
+ if (count && !pure_seal_ready(error, sizeof(error))) {
+ fprintf(stderr, "--seal: %s\n", error);
+ return false;
+ }
for (unsigned i = 0; i < count; ++i) {
const char *equals = strchr(seals[i], '=');
unsigned char peer[PURE_SEAL_KEY];
diff --git a/serve/tls.h b/serve/tls.h
new file mode 100644
index 0000000..704924d
--- /dev/null
+++ b/serve/tls.h
@@ -0,0 +1,18 @@
+#ifndef PURE_TLS_H
+#define PURE_TLS_H
+#include <stdbool.h>
+#include <stddef.h>
+
+/* TLS to https receivers (serve/crypto.c): one client context, verifying a
+ * receiver's certificate against the system's CAs or ca_file, and its name. */
+typedef struct PureTls PureTls;
+/* The context, made once; false with why in error when it cannot be. */
+bool pure_tls_start(const char *ca_file, char *error, size_t size);
+/* A session over a connected socket to host (a DNS name, or an IP address
+ * written as one), its certificate verified; NULL when it fails. */
+PureTls *pure_tls_open(const char *host, int fd);
+/* As SSL_read and SSL_write: the bytes moved, or <= 0. */
+int pure_tls_read(PureTls *, void *bytes, int size);
+int pure_tls_write(PureTls *, const void *bytes, int size);
+void pure_tls_close(PureTls *);
+#endif