diff options
| author | eunhhu <[email protected]> | 2026-09-27 18:26:33 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-09-27 18:26:41 +0100 |
| commit | c635e41d8576f5587d47e51a98dfd4a8920315f0 (patch) | |
| tree | d83cf41008fb57c5bfe442dd85097758d83de2a1 /serve | |
| parent | bd0476ff4a7bc638d4c97e03db2bf815f0389e9a (diff) | |
| download | pure-c635e41d8576f5587d47e51a98dfd4a8920315f0.tar.gz pure-c635e41d8576f5587d47e51a98dfd4a8920315f0.zip | |
Seal delivered sends to the receiver's key (086)
--seal OUTPUT=KEY seals each request's body whole to the receiving node's
public key with HPKE (RFC 9180, base mode: X25519, HKDF-SHA256, AES-128-GCM
where the CPU has AES, else ChaCha20-Poly1305), bound to the request's
source, first number and count; X-Pure-Seal names the suite. A receiver's
--key is a raw 32-byte file, made 0600 when missing, held in OpenSSL's
secure heap; it opens the body where it lies (400 seal when it does not
open) and zeroes the plain text when the request's ticket goes.
--sealed-only refuses unsealed deliveries (403 unsealed).
The check puts a recording relay between two apps: every order applies
once, no body shows an item or a field name, a flipped bit is refused, a
replayed request applies nothing twice, and a seal to another key does not
open. Its hosts choose their own ports (--port 0). Sealed sends drain at
233-235k a second against 469-470k plain (Raspberry Pi 5): three X25519
operations a request, 164 us each. The plain request path measured the
same as before.
Diffstat (limited to 'serve')
| -rw-r--r-- | serve/deliver.c | 42 | ||||
| -rw-r--r-- | serve/deliver.h | 4 | ||||
| -rw-r--r-- | serve/seal.c | 142 | ||||
| -rw-r--r-- | serve/seal.h | 32 | ||||
| -rw-r--r-- | serve/serve.c | 91 |
5 files changed, 299 insertions, 12 deletions
diff --git a/serve/deliver.c b/serve/deliver.c index fd0cb66..391e0db 100644 --- a/serve/deliver.c +++ b/serve/deliver.c @@ -1,5 +1,6 @@ #define _POSIX_C_SOURCE 200809L #include "deliver.h" +#include "seal.h" #include "message-json.h" #include "module-text.h" #include <errno.h> @@ -50,6 +51,8 @@ struct PureWire { size_t answer_head, answer_count, answer_capacity; Answer taken; /* the one the owner took last, kept until it takes the next */ uint32_t placed[2 * PURE_MODULE_FIELDS]; + bool sealed; /* the sends go sealed to peer's key */ + unsigned char peer[PURE_SEAL_KEY]; }; /* ---- TLS: one client context, verifying the receiver's certificate against @@ -554,33 +557,56 @@ static bool form_answer(const PureWire *w, int status, const Text *body, const P static int send_request(PureWire *w, const char *request, size_t size, const Pending *p, Answer *answer); +void pure_wire_seal(PureWire *w, const unsigned char peer[PURE_SEAL_KEY]) { + memcpy(w->peer, peer, PURE_SEAL_KEY); + w->sealed = true; +} + /* One request for count sends from p (numbered in a row): one object, or * an array of them for a receiver that takes batches. A final answer to a * send whose answers are wired is made into *answer. */ static int post(PureWire *w, const Pending *p, unsigned count, Answer *answer) { size_t body = count > 1 ? count + 1 : 0; for (unsigned i = 0; i < count; ++i) body += p[i].size; - char batch[32] = ""; + size_t length = body + (w->sealed ? PURE_SEAL_OVERHEAD : 0); + char batch[32] = "", seal[48] = ""; if (count > 1) snprintf(batch, sizeof(batch), "X-Pure-Count: %u\r\n", count); + if (w->sealed) snprintf(seal, sizeof(seal), "X-Pure-Seal: %s\r\n", pure_seal_suite()); /* The request in one piece, its head and then a batch in its brackets: * one write where there were two a send (a batch drained at 91k sends a - * second, and at 470k in one piece). */ + * second, and at 470k in one piece). Sealed, the body is sealed whole, + * bound to its source, first number and count. */ enum { HEAD = 2048 }; /* the longest path, host and source leave room to spare */ - char *request = malloc(HEAD + body); - if (!request) return AGAIN; + char *request = malloc(HEAD + length); + unsigned char *plain = w->sealed ? malloc(body) : NULL; + if (!request || (w->sealed && !plain)) { + free(request); + free(plain); + return AGAIN; + } int head = snprintf(request, HEAD, "POST %s HTTP/1.1\r\nHost: %s:%s\r\nContent-Type: application/json\r\nContent-Length: %zu\r\n" - "X-Pure-Source: %s\r\nX-Pure-Sequence: %llu\r\n%sIdempotency-Key: %s-%llu\r\n\r\n", - w->path, w->host, w->port, body, w->source, (unsigned long long)p->seq, batch, w->source, + "X-Pure-Source: %s\r\nX-Pure-Sequence: %llu\r\n%s%sIdempotency-Key: %s-%llu\r\n\r\n", + w->path, w->host, w->port, length, w->source, (unsigned long long)p->seq, batch, seal, w->source, (unsigned long long)p->seq); - char *at = request + head; + unsigned char *at = plain ? plain : (unsigned char *)request + head; for (unsigned i = 0; i < count; ++i) { if (count > 1) *at++ = i ? ',' : '['; memcpy(at, p[i].body, p[i].size); at += p[i].size; } if (count > 1) *at++ = ']'; - int result = send_request(w, request, (size_t)(at - request), p, answer); + if (plain) { + char aad[PURE_SOURCE + 48]; + snprintf(aad, sizeof(aad), "%s %llu %u", w->source, (unsigned long long)p->seq, count); + bool sealed = pure_seal(w->peer, aad, plain, body, (unsigned char *)request + head); + free(plain); + if (!sealed) { + free(request); + return AGAIN; + } + } + int result = send_request(w, request, (size_t)head + length, p, answer); free(request); return result; } diff --git a/serve/deliver.h b/serve/deliver.h index 2d1da29..70b03b9 100644 --- a/serve/deliver.h +++ b/serve/deliver.h @@ -23,6 +23,10 @@ void pure_wire_trust(const char *ca_file); PureWire *pure_wire_open(const PureModule *m, const char *output, const char *url, const char *source, char *error, size_t size); uint32_t pure_wire_output(const PureWire *); +/* Seal the wire's sends to the receiver's public key (serve/seal.h): each + * request's body, bound to its source, first number and count, goes with + * X-Pure-Seal naming the suite. Before pure_wire_start. */ +void pure_wire_seal(PureWire *, const unsigned char peer[32]); /* The owner's side: queue a saved send numbered after the last queued one * (it is encoded now); false when it cannot be (a bytes field that is not * UTF-8 has no JSON form), and the wire then stops at it. */ diff --git a/serve/seal.c b/serve/seal.c new file mode 100644 index 0000000..c1cc55b --- /dev/null +++ b/serve/seal.c @@ -0,0 +1,142 @@ +#define _GNU_SOURCE +#include "seal.h" +#include <errno.h> +#include <fcntl.h> +#include <openssl/crypto.h> +#include <openssl/evp.h> +#include <openssl/hpke.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; }; + +static const unsigned char info[] = "pure sealed send 1"; +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 +} + +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; + 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) { + 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)); + EVP_PKEY *key = NULL; + size_t length = PURE_SEAL_KEY; + if (!raw || !made) { snprintf(error, size, "out of memory"); goto fail; } + int fd = open(path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); + struct stat info_; + if (fd >= 0) { + bool ok = !fstat(fd, &info_) && S_ISREG(info_.st_mode) && !(info_.st_mode & 077) && + read(fd, raw, PURE_SEAL_KEY) == PURE_SEAL_KEY && info_.st_size == PURE_SEAL_KEY; + close(fd); + if (!ok) { snprintf(error, size, "%s: a key is 32 bytes that only its owner may read (0600)", path); goto fail; } + key = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL, raw, PURE_SEAL_KEY); + } else if (errno == ENOENT) { + /* A new key: made, written whole and synced before it is used. */ + key = EVP_PKEY_Q_keygen(NULL, NULL, "X25519"); + fd = open(path, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, 0600); + bool ok = key && fd >= 0 && EVP_PKEY_get_raw_private_key(key, raw, &length) == 1 && length == PURE_SEAL_KEY && + write(fd, raw, PURE_SEAL_KEY) == PURE_SEAL_KEY && !fsync(fd); + if (fd >= 0 && close(fd)) ok = false; + if (!ok) { snprintf(error, size, "%s: cannot make a key there", path); unlink(path); goto fail; } + } else { snprintf(error, size, "%s: %s", path, strerror(errno)); goto fail; } + length = PURE_SEAL_KEY; + if (!key || EVP_PKEY_get_raw_public_key(key, public_key, &length) != 1 || length != PURE_SEAL_KEY) { + snprintf(error, size, "%s: not an X25519 key", path); + goto fail; + } + for (size_t i = 0; i < PURE_SEAL_KEY; ++i) snprintf(public_hex + 2 * i, 3, "%02x", public_key[i]); + OPENSSL_secure_clear_free(raw, PURE_SEAL_KEY); + made->key = key; + return made; +fail: + EVP_PKEY_free(key); + if (raw) OPENSSL_secure_clear_free(raw, PURE_SEAL_KEY); + free(made); + return NULL; +} + +void pure_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) { + OSSL_HPKE_SUITE suite; + if (!suite_named(pure_seal_suite(), &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 && + encapsulated == PURE_SEAL_KEY && + OSSL_HPKE_seal(context, out + PURE_SEAL_KEY, &sealed, (const unsigned char *)aad, strlen(aad), plain, size) == 1 && + sealed == size + 16; + OSSL_HPKE_CTX_free(context); + 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) { + 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); + size_t opened = size - PURE_SEAL_OVERHEAD; + bool ok = context && + OSSL_HPKE_decap(context, sealed, PURE_SEAL_KEY, key->key, info, sizeof(info) - 1) == 1 && + OSSL_HPKE_open(context, plain, &opened, (const unsigned char *)aad, strlen(aad), sealed + PURE_SEAL_KEY, + size - PURE_SEAL_KEY) == 1 && + opened == size - PURE_SEAL_OVERHEAD; + OSSL_HPKE_CTX_free(context); + return ok; +} diff --git a/serve/seal.h b/serve/seal.h new file mode 100644 index 0000000..4ce3844 --- /dev/null +++ b/serve/seal.h @@ -0,0 +1,32 @@ +#ifndef PURE_SEAL_H +#define PURE_SEAL_H +#include <stdbool.h> +#include <stddef.h> + +/* Sends sealed end to end (docs/research/086-sealed-sends.md): a receiving + * node's key opens what a sender sealed to its public key, and nothing + * between them (a relay, a proxy, a mailbox) can read or change it. HPKE + * (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. */ +enum { PURE_SEAL_KEY = 32, PURE_SEAL_OVERHEAD = 32 + 16 }; /* the encapsulated key, then the tag */ + +typedef struct PureSealKey PureSealKey; +/* A node's key: read from path, or made and written there (0600) when there + * 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 *); +/* 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. */ +const char *pure_seal_suite(void); +/* plain sealed to peer, bound to aad: size + PURE_SEAL_OVERHEAD bytes into out. */ +bool pure_seal(const unsigned char peer[PURE_SEAL_KEY], const char *aad, const unsigned char *plain, size_t size, + unsigned char *out); +/* Opened with key under suite (either this machine's or the other known one): + * size - PURE_SEAL_OVERHEAD bytes into plain; false when it does not open + * (another key, another aad, or changed on the way). */ +bool pure_unseal(const PureSealKey *, const char *suite, const char *aad, const unsigned char *sealed, size_t size, + unsigned char *plain); +#endif diff --git a/serve/serve.c b/serve/serve.c index 2ea7adb..8f0bb8d 100644 --- a/serve/serve.c +++ b/serve/serve.c @@ -30,6 +30,7 @@ * threads hand it their ready requests and answer what it hands back. Its * bursts take the requests of every thread, one save each. */ #include "deliver.h" +#include "seal.h" #include "message-json.h" #include "module-store.h" #include "module-text.h" @@ -69,6 +70,11 @@ typedef struct Host Host; typedef enum { ROUTE_NONE, ROUTE_OBJECT, ROUTE_VIEW, ROUTE_INPUT } RouteKind; typedef struct { RouteKind kind; uint32_t target; size_t from, count; } Route; +/* This node's key, for deliveries sealed to it (--key), and whether it + * takes only those (--sealed-only). */ +static PureSealKey *seal_key; +static bool sealed_only; + /* Allocated when accepted and reused after closing: memory follows the * connections that are open, not the configured maximum. */ typedef struct Connection { @@ -94,6 +100,11 @@ typedef struct Connection { char source[PURE_SOURCE + 1]; /* a delivered message's sender (X-Pure-Source), or empty */ uint64_t sequence; /* and its number (X-Pure-Sequence) */ unsigned batch; /* how many numbered messages the body holds (X-Pure-Count) */ + /* A sealed delivery: the suite it names (X-Pure-Seal), what the seal is + * bound to (source, number, count), and how many plain bytes the body + * holds once opened, cleared when its ticket goes. */ + char seal[40], aad[PURE_SOURCE + 48]; + size_t opened; int64_t expected; size_t body_size, received, in_used; unsigned char *body; @@ -247,6 +258,8 @@ static bool number(const char *text, uint64_t maximum, uint64_t *value) { static void release_ticket(void *owner) { Connection *c = owner; + if (c->opened && c->body) explicit_bzero(c->body, c->opened); /* an opened seal's plain text is gone with it */ + c->opened = 0; if (c->ticket.runtime && !pure_runtime_release(c->ticket)) abort(); c->ticket = (PureTicket){0}; c->body = NULL; @@ -413,6 +426,7 @@ static bool parse_head(Connection *c, char *head, size_t size, char **query) { c->source[0] = 0; c->sequence = 0; c->batch = 1; + c->seal[0] = 0; bool counted = false; c->body_size = 0; c->json = false; @@ -464,6 +478,10 @@ static bool parse_head(Connection *c, char *head, size_t size, char **query) { } else if (!strcasecmp(line, "X-Pure-Sequence")) { if (c->sequence || !number(value, UINT64_MAX, &n) || !n) return false; c->sequence = n; + } else if (!strcasecmp(line, "X-Pure-Seal")) { + /* A delivery sealed to this node's key, in the suite named. */ + if (*c->seal || strlen(value) >= sizeof(c->seal)) return false; + strcpy(c->seal, value); } else if (!strcasecmp(line, "X-Pure-Count")) { /* Numbered messages from X-Pure-Sequence on, as a JSON array. */ if (counted || !number(value, BATCH, &n) || !n) return false; @@ -600,9 +618,31 @@ static const char *convert(Connection *c) { return NULL; } +/* A sealed body opened where it lies, or false (another key, another + * delivery's binding, or changed on the way). */ +static bool open_seal(Connection *c) { + if (c->body_size < PURE_SEAL_OVERHEAD) return false; + size_t size = c->body_size - PURE_SEAL_OVERHEAD; + unsigned char *plain = malloc(size ? size : 1); + bool opened = plain && pure_unseal(seal_key, c->seal, c->aad, c->body, c->body_size, plain); + if (opened) { + memcpy(c->body, plain, size); + c->body_size = size; + c->opened = size; + } + if (plain) explicit_bzero(plain, size); + free(plain); + return opened; +} + /* A request's body is in: an app input's JSON becomes its payload, and the * request is ready, or it is answered 400 and never runs. */ static void body_done(Connection *c) { + if (*c->seal && !open_seal(c)) { + release_ticket(c); /* never made ready: it never runs */ + respond(c, 400, "Bad Request", "seal", 0, (PureSpan){0}); + return; + } const char *why = c->route == ROUTE_INPUT && c->json ? convert(c) : NULL; if (why) { release_ticket(c); /* never made ready: it never runs */ @@ -623,6 +663,13 @@ static void start_request(Connection *c, size_t head_size) { memmove(c->in, c->in + head_size, extra); c->in_used = extra; if (!parse_head(c, head, head_size, &query)) { refuse(c, 400, "Bad Request", "request"); return; } + /* A node that keeps its deliveries sealed refuses the others; a seal + * needs this node's key, and binds the delivery's source, number and count. */ + if (*c->source && sealed_only && !*c->seal) { refuse(c, 403, "Forbidden", "unsealed"); return; } + if (*c->seal) { + if (!*c->source || !seal_key || !c->json) { refuse(c, 400, "Bad Request", "seal"); return; } + snprintf(c->aad, sizeof(c->aad), "%s %llu %u", c->source, (unsigned long long)c->sequence, c->batch); + } if (c->body_size > PURE_OBJECT_LIMIT) { refuse(c, 413, "Content Too Large", "limit"); return; } ++host->requests; Route route = {.kind = c->route}; @@ -1459,6 +1506,7 @@ static void accept_all(Host *host) { c->out = NULL; c->out_count = c->out_at = 0; c->body = NULL; + c->opened = 0; c->in_used = 0; struct epoll_event event = {.events = EPOLLIN | EPOLLOUT | EPOLLRDHUP | EPOLLET, .data.ptr = c}; if (epoll_ctl(host->epoll, EPOLL_CTL_ADD, fd, &event)) { @@ -1652,6 +1700,29 @@ static bool open_wires(Executor *ex, const PureModule *m, const char *const *sen return true; } +/* --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) { + for (unsigned i = 0; i < count; ++i) { + const char *equals = strchr(seals[i], '='); + unsigned char peer[PURE_SEAL_KEY]; + char output[PURE_MODULE_NAME]; + size_t length = equals ? (size_t)(equals - seals[i]) : 0; + PureWire *wire = NULL; + if (length && length < sizeof(output)) { + memcpy(output, seals[i], length); + output[length] = 0; + for (unsigned k = 0; k < ex->wire_count; ++k) + if (!strcmp(m->input_names[pure_wire_output(ex->wires[k])], output)) wire = ex->wires[k]; + } + if (!wire || !pure_seal_peer(equals + 1, peer)) { + fprintf(stderr, "--seal takes OUTPUT=KEY for an output given to --send, the key as 64 hex digits: %s\n", seals[i]); + return false; + } + pure_wire_seal(wire, peer); + } + return true; +} + /* --reply OUTPUT=INPUT: the answers to that output's sends become INPUT's events. */ static bool wire_replies(Executor *ex, const PureModule *m, const char *const *replies, unsigned count) { for (unsigned i = 0; i < count; ++i) { @@ -1690,14 +1761,18 @@ int main(int argc, char **argv) { static PureModule module; static Owner owner; const char *module_path = NULL, *store = NULL, *name = NULL, *sends[PURE_MODULE_INPUTS], *replies[PURE_MODULE_INPUTS]; - unsigned send_count = 0, reply_count = 0; + const char *seals[PURE_MODULE_INPUTS], *key_path = NULL; + unsigned send_count = 0, reply_count = 0, seal_count = 0; unsigned port = 8767, threads = 1; size_t budget = 64u << 20, capacity = 1024; uint64_t timeout_ms = 10000; for (int i = 1; i < argc; i += 2) { uint64_t value; + if (!strcmp(argv[i], "--sealed-only")) { sealed_only = true; --i; continue; } if (i + 1 == argc) goto usage; if (!strcmp(argv[i], "--module")) module_path = argv[i + 1]; + else if (!strcmp(argv[i], "--key")) key_path = argv[i + 1]; + else if (!strcmp(argv[i], "--seal") && seal_count < PURE_MODULE_INPUTS) seals[seal_count++] = argv[i + 1]; else if (!strcmp(argv[i], "--store")) store = argv[i + 1]; else if (!strcmp(argv[i], "--send") && send_count < PURE_MODULE_INPUTS) sends[send_count++] = argv[i + 1]; else if (!strcmp(argv[i], "--name")) name = argv[i + 1]; @@ -1715,6 +1790,12 @@ int main(int argc, char **argv) { bool documents; if (!module_path || !store || !load_module(&module, module_path, &documents, &show, &edit)) goto usage; if (!threads) threads = auto_threads(module.states); + char public_key[2 * PURE_SEAL_KEY + 1] = ""; + if (key_path) { + char error[256]; + if (!(seal_key = pure_seal_key(key_path, public_key, error, sizeof(error)))) { fprintf(stderr, "--key %s\n", error); return 2; } + } + if (sealed_only && !seal_key) { fputs("--sealed-only needs --key\n", stderr); return 2; } if (pure_store_init(store) != PURE_OK) { fputs("Cannot initialize the store directory\n", stderr); return 1; } signal(SIGPIPE, SIG_IGN); struct sigaction stop_action = {.sa_handler = stop}; @@ -1745,7 +1826,8 @@ int main(int argc, char **argv) { owner.executor.reads_first = true; owner.executor.publishes = true; if (!open_wires(&owner.executor, &module, sends, send_count, name) || - !wire_replies(&owner.executor, &module, replies, reply_count)) return 2; + !wire_replies(&owner.executor, &module, replies, reply_count) || + !wire_seals(&owner.executor, &module, seals, seal_count)) return 2; after_save(&owner.executor); /* the snapshot, and the sends stored before */ for (unsigned i = 0; i < owner.executor.wire_count; ++i) if (!pure_wire_start(owner.executor.wires[i], wire_delivered, &owner)) { fputs("Cannot start a wire\n", stderr); return 1; } @@ -1760,7 +1842,8 @@ int main(int argc, char **argv) { if (!start_shard(&hosts[i], &module, documents, show, edit, store, budget, &port)) return 1; } if (owned && pthread_create(&owner_thread, NULL, run_owner, &owner)) { fputs("Cannot start the owner\n", stderr); return 1; } - printf("{\"port\":%u,\"threads\":%u}\n", port, threads); + if (*public_key) printf("{\"port\":%u,\"threads\":%u,\"public_key\":\"%s\"}\n", port, threads, public_key); + else printf("{\"port\":%u,\"threads\":%u}\n", port, threads); fflush(stdout); for (unsigned i = 1; i < threads; ++i) if (pthread_create(&thread[i], NULL, run_shard, &hosts[i])) { fputs("Cannot start a thread\n", stderr); return 1; } @@ -1815,6 +1898,6 @@ int main(int argc, char **argv) { usage: fputs("pure-serve --module FILE.pmod --store DIRECTORY [--port 8767] [--threads 1|N|auto] [--budget BYTES] " "[--connections 1024] [--timeout-ms 10000] [--send OUTPUT=http[s]://host:port/path ...] [--name NAME] " - "[--send-ca FILE] [--reply OUTPUT=INPUT ...]\n", stderr); + "[--send-ca FILE] [--reply OUTPUT=INPUT ...] [--seal OUTPUT=KEY ...] [--key FILE] [--sealed-only]\n", stderr); return 2; } |
