aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile14
-rw-r--r--README.md2
-rw-r--r--docs/design/009-layout.md8
-rw-r--r--research/experiments/013-parallel-syncs/variants.py5
-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
11 files changed, 276 insertions, 112 deletions
diff --git a/Makefile b/Makefile
index 11d14d7..ee82d93 100644
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,9 @@ BUILD ?= build
# states as drafts; state.h and store.h are what it asks of storage.
# store/ the tree store (one file; commits are one contiguous write and one
# sync; formats PURETRE3 and PUREREC1).
-# serve/ pure-serve: the HTTP host, outbound delivery, JSON in place.
+# serve/ pure-serve: the HTTP host, outbound delivery, JSON in place; with
+# pure-crypto.so beside it, OpenSSL's part (TLS to https receivers,
+# sealed sends), mapped only by a server that uses one.
# cli/ command-line hosts.
# libpure.a is the app model with the tree store: what a host embeds (Vega's
# surfaces link it). prototypes/ keeps the earlier document runtime (000-077).
@@ -19,12 +21,12 @@ BUILD ?= build
INCLUDES = -Icore
MODEL = core/module.c core/module-list.c core/module-save.c core/module-text.c core/module-store.c core/flow.c
STORE = store/store-tree.c
-SERVE = serve/serve.c serve/deliver.c serve/message-json.c serve/seal.c
+SERVE = serve/serve.c serve/deliver.c serve/message-json.c serve/crypto.c
HEADERS = $(wildcard core/*.h) $(wildcard serve/*.h)
OBJECTS = $(patsubst %.c,$(BUILD)/obj/%.o,$(MODEL) $(STORE))
.PHONY: all check
-all: $(BUILD)/libpure.a $(BUILD)/pure-serve $(BUILD)/pure-run $(BUILD)/pure-edit-module
+all: $(BUILD)/libpure.a $(BUILD)/pure-serve $(BUILD)/pure-crypto.so $(BUILD)/pure-run $(BUILD)/pure-edit-module
$(BUILD)/obj/%.o: %.c $(HEADERS)
@mkdir -p $(dir $@)
@@ -38,7 +40,11 @@ $(BUILD)/libpure.a: $(OBJECTS)
# It links after the library, last as it always was, so code keeps its place.
$(BUILD)/pure-serve: $(SERVE) core/runtime.c $(BUILD)/libpure.a $(HEADERS)
$(CC) $(CPPFLAGS) $(CFLAGS) $(INCLUDES) -Iserve -DPURE_ASYNC_EFFECTS $(SERVE) $(BUILD)/libpure.a core/runtime.c \
- $(LDFLAGS) -lssl -lcrypto -lm -pthread $(LDLIBS) -o $@
+ $(LDFLAGS) -lm -pthread $(LDLIBS) -o $@
+
+$(BUILD)/pure-crypto.so: serve/crypto-openssl.c $(HEADERS)
+ $(CC) $(CPPFLAGS) $(CFLAGS) $(INCLUDES) -Iserve -fPIC -shared serve/crypto-openssl.c $(LDFLAGS) -lssl -lcrypto \
+ $(LDLIBS) -o $@
$(BUILD)/pure-run: cli/pure-run.c $(BUILD)/libpure.a $(HEADERS)
$(CC) $(CPPFLAGS) $(CFLAGS) $(INCLUDES) cli/pure-run.c $(BUILD)/libpure.a $(LDFLAGS) -lm $(LDLIBS) -o $@
diff --git a/README.md b/README.md
index 6ba38db..9762cc9 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@ The source follows the principle below, a small core with the rest selected arou
|---|---|---|
| `core/` | execution, ownership, limits and completion (`runtime.c`); the app model: modules verified before they run, events as transactions, states as drafts (`module*.c`); what the core asks of storage (`state.h`, `store.h`) | `build/libpure.a` (with the store) |
| `store/` | the tree store: one file, a commit is one contiguous write and one sync (formats `PURETRE3`, `PUREREC1`) | in `libpure.a` |
-| `serve/` | `pure-serve`: the HTTP host, outbound delivery, JSON read in place | `build/pure-serve` |
+| `serve/` | `pure-serve`: the HTTP host, outbound delivery, JSON read in place; OpenSSL's part (TLS to https receivers, sealed sends) apart, mapped only by a server that uses one | `build/pure-serve`, `build/pure-crypto.so` |
| `cli/` | command-line hosts | `build/pure-run`, `build/pure-edit-module` |
| `prototypes/` | the earlier document runtime (000-077), built against `core/` and `store/` | `make -C prototypes/connections` |
| `tests/` | the hosts checked end to end, with apps built by aup (the sibling checkout) | `make check` |
diff --git a/docs/design/009-layout.md b/docs/design/009-layout.md
index bcaf64e..d985003 100644
--- a/docs/design/009-layout.md
+++ b/docs/design/009-layout.md
@@ -29,10 +29,16 @@
|---|---|---|
| `core/` | 실행·소유권·한도·완료(`runtime.c`, `flow.c`), 앱 모델(`module*.c`: 실행 전에 검증한 모듈, 트랜잭션인 이벤트, draft인 상태), 코어가 저장소에 요구하는 것(`state.h`, `store.h`) | `build/libpure.a`(저장소와 함께) |
| `store/` | 트리 저장소. 한 파일, 커밋은 한 연속 쓰기와 동기화 한 번 | `libpure.a` 안 |
-| `serve/` | `pure-serve`: HTTP 호스트, 나가는 전달, 제자리 JSON | `build/pure-serve` |
+| `serve/` | `pure-serve`: HTTP 호스트, 나가는 전달, 제자리 JSON. OpenSSL을 쓰는 부분(https 수신자로의 TLS, 봉인한 전달)은 따로 두어 쓰는 서버만 매핑한다 | `build/pure-serve`, `build/pure-crypto.so` |
| `cli/` | 명령행 호스트 | `build/pure-run`, `build/pure-edit-module` |
| `prototypes/` | 이전 문서 런타임. `core/`와 `store/`를 가져다 빌드한다 | `make -C prototypes/connections` |
+OpenSSL은 pure-serve가 쓰는 것 중 가장 크다. 링크만 해도 아무 일도 하지 않는 프로세스의 상주 메모리가 2.3 MiB 늘고,
+그중 0.66 MiB는 그 프로세스만의 것(재배치한 데이터)이다. 그래서 OpenSSL을 쓰는 코드(`serve/crypto-openssl.c`)는
+`pure-crypto.so`로 따로 빌드하고, pure-serve는 https 수신자나 봉인을 처음 쓸 때 실행 파일 옆의 그것을 연다
+(`serve/crypto.c`). 쓰지 않는 서버는 유휴 RSS가 4.5 → 2.3 MiB, 자기만의 메모리가 1.3 → 0.86 MiB다. 정적 링크는 RSS가 조금
+줄지만(3.8 MiB) 자기만의 메모리가 2.3 MiB로 는다. OpenSSL의 표를 모두 재배치하기 때문이다.
+
검사도 부품을 따른다. 부품의 단위 검사는 그 옆에(`core/test-module.c`, `store/test-tree-store.c`), 저장소 전체를 끝에서
끝까지 보는 검사는 `tests/check.py`에 둔다. aup와 Vega도 같다(aup `compiler/`, Vega `lib/`·`surfaces/`·`symbols/`, 둘 다
`tests/check.py`). pure-serve의 HTTP 검사는 전에 aup의 검사 안에 있었다. 호스트를 바꾸면 aup에서 확인해야 했던 것이다.
diff --git a/research/experiments/013-parallel-syncs/variants.py b/research/experiments/013-parallel-syncs/variants.py
index ee49d2a..0b07243 100644
--- a/research/experiments/013-parallel-syncs/variants.py
+++ b/research/experiments/013-parallel-syncs/variants.py
@@ -41,10 +41,11 @@ def build(work, name, patches):
store.write_text(source)
binary = work / f'pure-serve-{len(name)}'
subprocess.run(['cc', '-O2', '-std=c17', f'-I{ROOT / "core"}', f'-I{ROOT / "serve"}', '-DPURE_ASYNC_EFFECTS', '-pthread',
- *[str(ROOT / s) for s in ('serve/serve.c', 'serve/deliver.c', 'serve/message-json.c', 'core/module.c',
+ *[str(ROOT / s) for s in ('serve/serve.c', 'serve/deliver.c', 'serve/message-json.c', 'serve/crypto.c',
+ 'core/module.c',
'core/module-list.c', 'core/module-save.c', 'core/module-text.c',
'core/module-store.c')], str(store), str(ROOT / 'core/runtime.c'),
- '-lssl', '-lcrypto', '-lm', '-o', str(binary)], check=True)
+ '-lm', '-o', str(binary)], check=True)
return binary
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