diff options
Diffstat (limited to 'serve')
| -rw-r--r-- | serve/crypto-openssl.c | 30 | ||||
| -rw-r--r-- | serve/crypto.c | 14 | ||||
| -rw-r--r-- | serve/crypto.h | 12 | ||||
| -rw-r--r-- | serve/link.c | 95 | ||||
| -rw-r--r-- | serve/seal.h | 15 |
5 files changed, 100 insertions, 66 deletions
diff --git a/serve/crypto-openssl.c b/serve/crypto-openssl.c index 6ff1ad4..67ce5fd 100644 --- a/serve/crypto-openssl.c +++ b/serve/crypto-openssl.c @@ -207,10 +207,23 @@ static bool link_secret(const PureSealKey *own, const unsigned char peer[PURE_SE return ok && any; } +/* HKDF, fetched once. */ +static EVP_KDF *hkdf(void) { + static EVP_KDF *kept; + EVP_KDF *kdf = __atomic_load_n(&kept, __ATOMIC_ACQUIRE), *none = NULL; + if (kdf) return kdf; + if ((kdf = EVP_KDF_fetch(NULL, "HKDF", NULL)) && + !__atomic_compare_exchange_n(&kept, &none, kdf, false, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) { + EVP_KDF_free(kdf); /* another thread's is kept */ + kdf = none; + } + return kdf; +} + static bool link_derive(const unsigned char secret[32], const unsigned char *info, size_t info_size, unsigned char *out, size_t out_size) { static const char salt[] = "pure link 0"; - EVP_KDF *kdf = EVP_KDF_fetch(NULL, "HKDF", NULL); + EVP_KDF *kdf = hkdf(); EVP_KDF_CTX *context = kdf ? EVP_KDF_CTX_new(kdf) : NULL; OSSL_PARAM params[] = {OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, (char *)"SHA256", 0), OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY, (void *)secret, 32), @@ -219,7 +232,6 @@ static bool link_derive(const unsigned char secret[32], const unsigned char *inf OSSL_PARAM_construct_end()}; bool ok = context && EVP_KDF_derive(context, out, out_size, params) == 1; EVP_KDF_CTX_free(context); - EVP_KDF_free(kdf); return ok; } @@ -243,22 +255,24 @@ static PureAead *aead(const unsigned char key[PURE_AEAD_KEY]) { return a; } -static bool aead_seal(PureAead *a, const unsigned char nonce[PURE_AEAD_NONCE], const unsigned char *aad, - size_t aad_size, const unsigned char *plain, size_t size, unsigned char *out) { +static bool aead_seal(PureAead *a, const unsigned char *key, const unsigned char nonce[PURE_AEAD_NONCE], + const unsigned char *aad, size_t aad_size, const unsigned char *plain, size_t size, + unsigned char *out) { int n = 0, last = 0; - return size <= INT_MAX && EVP_EncryptInit_ex(a->seal, NULL, NULL, NULL, nonce) == 1 && + return size <= INT_MAX && EVP_EncryptInit_ex(a->seal, NULL, NULL, key, nonce) == 1 && (!aad_size || EVP_EncryptUpdate(a->seal, NULL, &n, aad, (int)aad_size) == 1) && (!size || EVP_EncryptUpdate(a->seal, out, &n, plain, (int)size) == 1) && EVP_EncryptFinal_ex(a->seal, out + size, &last) == 1 && EVP_CIPHER_CTX_ctrl(a->seal, EVP_CTRL_GCM_GET_TAG, PURE_AEAD_TAG, out + size) == 1; } -static bool aead_open(PureAead *a, const unsigned char nonce[PURE_AEAD_NONCE], const unsigned char *aad, - size_t aad_size, const unsigned char *sealed, size_t size, unsigned char *plain) { +static bool aead_open(PureAead *a, const unsigned char *key, const unsigned char nonce[PURE_AEAD_NONCE], + const unsigned char *aad, size_t aad_size, const unsigned char *sealed, size_t size, + unsigned char *plain) { int n = 0, last = 0; if (size < PURE_AEAD_TAG || size - PURE_AEAD_TAG > INT_MAX) return false; size_t body = size - PURE_AEAD_TAG; - return EVP_DecryptInit_ex(a->open, NULL, NULL, NULL, nonce) == 1 && + return EVP_DecryptInit_ex(a->open, NULL, NULL, key, nonce) == 1 && (!aad_size || EVP_DecryptUpdate(a->open, NULL, &n, aad, (int)aad_size) == 1) && (!body || EVP_DecryptUpdate(a->open, plain, &n, sealed, (int)body) == 1) && EVP_CIPHER_CTX_ctrl(a->open, EVP_CTRL_GCM_SET_TAG, PURE_AEAD_TAG, (void *)(sealed + body)) == 1 && diff --git a/serve/crypto.c b/serve/crypto.c index d43de93..da63b37 100644 --- a/serve/crypto.c +++ b/serve/crypto.c @@ -152,14 +152,16 @@ PureAead *pure_aead(const unsigned char key[PURE_AEAD_KEY]) { return c ? c->aead(key) : NULL; } -bool pure_aead_seal(PureAead *a, const unsigned char nonce[PURE_AEAD_NONCE], const unsigned char *aad, size_t aad_size, - const unsigned char *plain, size_t size, unsigned char *out) { - return crypto->aead_seal(a, nonce, aad, aad_size, plain, size, out); +bool pure_aead_seal(PureAead *a, const unsigned char *key, const unsigned char nonce[PURE_AEAD_NONCE], + const unsigned char *aad, size_t aad_size, const unsigned char *plain, size_t size, + unsigned char *out) { + return crypto->aead_seal(a, key, nonce, aad, aad_size, plain, size, out); } -bool pure_aead_open(PureAead *a, const unsigned char nonce[PURE_AEAD_NONCE], const unsigned char *aad, - size_t aad_size, const unsigned char *sealed, size_t size, unsigned char *plain) { - return crypto->aead_open(a, nonce, aad, aad_size, sealed, size, plain); +bool pure_aead_open(PureAead *a, const unsigned char *key, const unsigned char nonce[PURE_AEAD_NONCE], + const unsigned char *aad, size_t aad_size, const unsigned char *sealed, size_t size, + unsigned char *plain) { + return crypto->aead_open(a, key, nonce, aad, aad_size, sealed, size, plain); } void pure_aead_free(PureAead *a) { if (a) crypto->aead_free(a); } diff --git a/serve/crypto.h b/serve/crypto.h index b8a48fd..53e9c2c 100644 --- a/serve/crypto.h +++ b/serve/crypto.h @@ -8,7 +8,7 @@ * 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 = 3 }; +enum { PURE_CRYPTO_VERSION = 4 }; typedef struct { unsigned version; @@ -38,10 +38,12 @@ typedef struct { bool (*link_derive)(const unsigned char secret[32], const unsigned char *info, size_t info_size, unsigned char *out, size_t out_size); PureAead *(*aead)(const unsigned char key[PURE_AEAD_KEY]); - bool (*aead_seal)(PureAead *, const unsigned char nonce[PURE_AEAD_NONCE], const unsigned char *aad, size_t aad_size, - const unsigned char *plain, size_t size, unsigned char *out); - bool (*aead_open)(PureAead *, const unsigned char nonce[PURE_AEAD_NONCE], const unsigned char *aad, - size_t aad_size, const unsigned char *sealed, size_t size, unsigned char *plain); + bool (*aead_seal)(PureAead *, const unsigned char *key, const unsigned char nonce[PURE_AEAD_NONCE], + const unsigned char *aad, size_t aad_size, const unsigned char *plain, size_t size, + unsigned char *out); + bool (*aead_open)(PureAead *, const unsigned char *key, const unsigned char nonce[PURE_AEAD_NONCE], + const unsigned char *aad, size_t aad_size, const unsigned char *sealed, size_t size, + unsigned char *plain); void (*aead_free)(PureAead *); } PureCrypto; #endif diff --git a/serve/link.c b/serve/link.c index f9e5c07..ed1b695 100644 --- a/serve/link.c +++ b/serve/link.c @@ -20,7 +20,7 @@ enum { VERSION = 0x70, HEADER = 21, DATAGRAM = 1200, BODY = DATAGRAM - HEADER - PURE_AEAD_TAG, PIECE = 1000 }; enum { F_STREAM = 1, F_RECORD = 2, F_ACK = 3, F_STORED = 4, F_PING = 5, STORED_GAP = 1 }; -enum { RANGES = 32, RUNS = 4, UNITS = 64, CREDIT = 1024, BATCH = 256, RECORD_HEAD = 19 }; +enum { RANGES = 32, RUNS = 2, UNITS = 64, CREDIT = 1024, BATCH = 256, RECORD_HEAD = 19 }; enum { WINDOW_FIRST = 10 * DATAGRAM, WINDOW_LEAST = 2 * DATAGRAM }; #define MS UINT64_C(1000000) /* RFC 9002's values, but a first RTT of 100 ms (a LAN's, not the 333 ms of @@ -75,19 +75,29 @@ static uint64_t random64(void) { /* The key and nonce base of packets from `from` (in its run from_run) to * `to`, answering to_run (0 for none). */ -static PureAead *derive(const unsigned char secret[32], const unsigned char from[PURE_SEAL_KEY], - const unsigned char to[PURE_SEAL_KEY], uint64_t from_run, uint64_t to_run, - unsigned char base[PURE_AEAD_NONCE]) { +static bool derive(const unsigned char secret[32], const unsigned char from[PURE_SEAL_KEY], + const unsigned char to[PURE_SEAL_KEY], uint64_t from_run, uint64_t to_run, + unsigned char key[PURE_AEAD_KEY], unsigned char base[PURE_AEAD_NONCE]) { unsigned char info[16 + 2 * PURE_SEAL_KEY + 16], out[PURE_AEAD_KEY + PURE_AEAD_NONCE]; memcpy(info, "pure link 0 keys", 16); memcpy(info + 16, from, PURE_SEAL_KEY); memcpy(info + 16 + PURE_SEAL_KEY, to, PURE_SEAL_KEY); put64(info + 16 + 2 * PURE_SEAL_KEY, from_run); put64(info + 24 + 2 * PURE_SEAL_KEY, to_run); - if (!pure_link_derive(secret, info, sizeof(info), out, sizeof(out))) return NULL; + if (!pure_link_derive(secret, info, sizeof(info), out, sizeof(out))) return false; + memcpy(key, out, PURE_AEAD_KEY); memcpy(base, out + PURE_AEAD_KEY, PURE_AEAD_NONCE); - PureAead *aead = pure_aead(out); explicit_bzero(out, sizeof(out)); + return true; +} + +/* The same as a context of its own, for a sender's one link. */ +static PureAead *aead_of(const unsigned char secret[32], const unsigned char from[PURE_SEAL_KEY], + const unsigned char to[PURE_SEAL_KEY], uint64_t from_run, uint64_t to_run, + unsigned char base[PURE_AEAD_NONCE]) { + unsigned char key[PURE_AEAD_KEY]; + PureAead *aead = derive(secret, from, to, from_run, to_run, key, base) ? pure_aead(key) : NULL; + explicit_bzero(key, sizeof(key)); return aead; } @@ -96,16 +106,17 @@ static void nonce_of(unsigned char out[PURE_AEAD_NONCE], const unsigned char bas for (int i = 0; i < 8; ++i) out[4 + i] ^= (unsigned char)(pn >> (56 - 8 * i)); } -/* A packet: the header, then body sealed; its size, or 0. */ -static size_t seal_packet(PureAead *aead, const unsigned char base[PURE_AEAD_NONCE], uint32_t id, uint64_t run, - uint64_t pn, const unsigned char *body, size_t size, unsigned char *out) { +/* A packet: the header, then body sealed (with key, or the context's own + * when NULL); its size, or 0. */ +static size_t seal_packet(PureAead *aead, const unsigned char *key, const unsigned char base[PURE_AEAD_NONCE], uint32_t id, + uint64_t run, uint64_t pn, const unsigned char *body, size_t size, unsigned char *out) { unsigned char nonce[PURE_AEAD_NONCE]; out[0] = VERSION; put32(out + 1, id); put64(out + 5, run); put64(out + 13, pn); nonce_of(nonce, base, pn); - return pure_aead_seal(aead, nonce, out, HEADER, body, size, out + HEADER) ? HEADER + size + PURE_AEAD_TAG : 0; + return pure_aead_seal(aead, key, nonce, out, HEADER, body, size, out + HEADER) ? HEADER + size + PURE_AEAD_TAG : 0; } static size_t put_names(unsigned char *p, const char *source, const char *input) { @@ -437,14 +448,14 @@ static void take_answers(PureLink *l, uint64_t now) { uint64_t run = get64(packet + 5), pn = get64(packet + 13); if (!l->in || l->in_run != run) { pure_aead_free(l->in); - l->in = derive(l->secret, l->peer, l->own_public, run, l->run, l->in_base); + l->in = aead_of(l->secret, l->peer, l->own_public, run, l->run, l->in_base); l->in_run = run; if (!l->in) continue; } unsigned char nonce[PURE_AEAD_NONCE]; nonce_of(nonce, l->in_base, pn); size_t size = (size_t)n - HEADER - PURE_AEAD_TAG; - if (!pure_aead_open(l->in, nonce, packet, HEADER, packet + HEADER, (size_t)n - HEADER, plain)) continue; + if (!pure_aead_open(l->in, NULL, nonce, packet, HEADER, packet + HEADER, (size_t)n - HEADER, plain)) continue; ++l->counts.received; for (size_t at = 0; at < size;) { unsigned type = plain[at++]; @@ -503,7 +514,7 @@ static size_t fill(PureLink *l, unsigned char *body, Flight *f) { * refuses it (what it carried is then marked unsent again). */ static bool transmit(PureLink *l, Flight *f, const unsigned char *body, size_t size, uint64_t now) { unsigned char packet[DATAGRAM]; - size_t total = seal_packet(l->out, l->out_base, l->own_id, l->run, f->pn, body, size, packet); + size_t total = seal_packet(l->out, NULL, l->out_base, l->own_id, l->run, f->pn, body, size, packet); ++l->next_pn; bool sent = total && (send(l->fd, packet, total, 0) >= 0 || (errno != EAGAIN && errno != EWOULDBLOCK && errno != ENOBUFS && errno != EINTR)); @@ -657,7 +668,7 @@ bool pure_link_start(PureLink *l, void (*notify)(void *), void *context, char *e l->threshold = UINT64_MAX; l->credit = CREDIT; ok = ok && pure_link_secret(l->own, l->peer, l->secret) && - (l->out = derive(l->secret, l->own_public, l->peer, l->run, 0, l->out_base)) && + (l->out = aead_of(l->secret, l->own_public, l->peer, l->run, 0, l->out_base)) && (l->wake = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)) >= 0; l->notify = notify; l->context = context; @@ -716,10 +727,9 @@ typedef struct { Active *active; uint64_t run, seen; /* the sender's run; when it last sent */ bool used, ack_due, due; - PureAead *in, *out; - unsigned char in_base[PURE_AEAD_NONCE], out_base[PURE_AEAD_NONCE]; + unsigned char in_key[PURE_AEAD_KEY], out_key[PURE_AEAD_KEY], in_base[PURE_AEAD_NONCE], out_base[PURE_AEAD_NONCE]; uint64_t next_pn, largest_at; - struct sockaddr_storage address; + struct sockaddr_in6 address; /* or an IPv4 one */ socklen_t address_size; Range got[RANGES]; /* the numbers received, newest first */ unsigned got_count; @@ -760,8 +770,10 @@ typedef struct { uint32_t name, next; } Allowed; /* name: where in names (u8 len typedef struct { Stream *stream; uint64_t heard; bool stored, gap; } Done; /* A peer quiet this long lets its active state go; the active are looked at - * that often. */ -static const uint64_t IDLE_FIRST = 30000 * MS, SWEEP_EVERY = 250 * MS; + * that often; what they held goes back to the system once TRIM_AFTER have + * gone, or TRIM_WAIT after any went. */ +static const uint64_t IDLE_FIRST = 30000 * MS, SWEEP_EVERY = 250 * MS, TRIM_WAIT = 10000 * MS; +enum { TRIM_AFTER = 1024 }; struct PureLinkListener { const PureModule *module; @@ -769,7 +781,8 @@ struct PureLinkListener { unsigned char own_public[PURE_SEAL_KEY]; uint32_t own_id; unsigned port; - uint64_t run, idle, swept; + uint64_t run, idle, swept, trimmed, untrimmed; + PureAead *aead; /* keyed by each packet's run */ int fd, wake; pthread_t thread; bool started; @@ -931,10 +944,6 @@ static Active *activate(PureLinkListener *s, Peer *peer, uint64_t now) { } static void let_go(PureLinkListener *s, Active *a) { - for (unsigned i = 0; i < RUNS; ++i) { - pure_aead_free(a->runs[i].in); - pure_aead_free(a->runs[i].out); - } for (unsigned k = 0; k < a->stream_count; ++k) { while (a->streams[k]->held_count) drop_held(a->streams[k], 0); free(a->streams[k]->held); @@ -944,7 +953,7 @@ static void let_go(PureLinkListener *s, Active *a) { s->peers[a->peer].active = NULL; s->actives[a->place] = s->actives[--s->active_count]; s->actives[a->place]->place = a->place; - explicit_bzero(a->secret, sizeof(a->secret)); + explicit_bzero(a, sizeof(*a)); free(a); } @@ -958,7 +967,7 @@ static bool busy(const Active *a) { } /* Let go the active state of peers quiet for the idle period; what idle - * links held goes back to the system when there was much of it. */ + * links held goes back to the system. */ static void sweep(PureLinkListener *s, uint64_t now) { if (now - s->swept < SWEEP_EVERY) return; s->swept = now; @@ -969,7 +978,12 @@ static void sweep(PureLinkListener *s, uint64_t now) { else let_go(s, a), ++freed; } s->counts.swept += freed; - if (freed >= 1024) malloc_trim(0); + s->untrimmed += freed; + if (s->untrimmed >= TRIM_AFTER || (s->untrimmed && now - s->trimmed >= TRIM_WAIT)) { + malloc_trim(0); + s->untrimmed = 0; + s->trimmed = now; + } } static void mark_ready(PureLinkListener *s, Stream *st) { @@ -1002,8 +1016,6 @@ static Run *install_run(Active *a, const Run *made) { Run *r = &a->runs[i]; if (!r->used || (slot->used && r->seen < slot->seen)) slot = r; } - pure_aead_free(slot->in); - pure_aead_free(slot->out); bool due = slot->due; /* the answer list may hold the slot */ *slot = *made; slot->due = due; @@ -1150,17 +1162,14 @@ static bool take_from(PureLinkListener *s, Peer *peer, unsigned char *packet, si bool woke = !a; if (woke && !(a = activate(s, peer, now))) return false; Run made = {.active = a, .run = run, .seen = now, .used = true}; - if (!r) { - made.in = derive(a->secret, peer->key, s->own_public, run, 0, made.in_base); - made.out = derive(a->secret, s->own_public, peer->key, s->run, run, made.out_base); - } + bool keyed = r || (derive(a->secret, peer->key, s->own_public, run, 0, made.in_key, made.in_base) && + derive(a->secret, s->own_public, peer->key, s->run, run, made.out_key, made.out_base)); unsigned char plain[DATAGRAM], nonce[PURE_AEAD_NONCE]; nonce_of(nonce, r ? r->in_base : made.in_base, pn); size_t size = n - HEADER - PURE_AEAD_TAG; - PureAead *in = r ? r->in : made.in; - if (!in || (!r && !made.out) || !pure_aead_open(in, nonce, packet, HEADER, packet + HEADER, n - HEADER, plain)) { - pure_aead_free(made.in); - pure_aead_free(made.out); + if (!keyed || !pure_aead_open(s->aead, r ? r->in_key : made.in_key, nonce, packet, HEADER, packet + HEADER, + n - HEADER, plain)) { + explicit_bzero(&made, sizeof(made)); if (woke) let_go(s, a); return false; } @@ -1168,8 +1177,8 @@ static bool take_from(PureLinkListener *s, Peer *peer, unsigned char *packet, si if (!r) r = install_run(a, &made); a->seen = r->seen = now; note(r, pn, now); - memcpy(&r->address, from, from_size); /* where it is now: answers go there */ - r->address_size = from_size; + r->address_size = from_size <= sizeof(r->address) ? from_size : sizeof(r->address); + memcpy(&r->address, from, r->address_size); /* where it is now: answers go there */ ++s->counts.received; Stream *st = NULL; bool eliciting = false; @@ -1267,7 +1276,7 @@ static void answer(PureLinkListener *s, uint64_t now) { } r->ack_due = false; if (!at) continue; - size_t total = seal_packet(r->out, r->out_base, s->own_id, s->run, r->next_pn++, body, at, packet); + size_t total = seal_packet(s->aead, r->out_key, r->out_base, s->own_id, s->run, r->next_pn++, body, at, packet); if (total && sendto(s->fd, packet, total, 0, (const struct sockaddr *)&r->address, r->address_size) >= 0) ++s->counts.packets; } @@ -1360,9 +1369,10 @@ bool pure_link_serve(PureLinkListener *s, char *error, size_t size) { setsockopt(s->fd, SOL_SOCKET, SO_RCVBUF, &buffer, sizeof(buffer)); setsockopt(s->fd, SOL_SOCKET, SO_SNDBUF, &buffer, sizeof(buffer)); s->run = random64(); - s->swept = now_ns(); + s->swept = s->trimmed = now_ns(); s->wake = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); - if (s->wake < 0 || pthread_create(&s->thread, NULL, run_listener, s)) { + unsigned char key[PURE_AEAD_KEY] = {0}; /* each packet brings its run's */ + if (s->wake < 0 || !(s->aead = pure_aead(key)) || pthread_create(&s->thread, NULL, run_listener, s)) { snprintf(error, size, "--link-port %u: cannot start", s->port); return false; } @@ -1398,6 +1408,7 @@ void pure_link_listener_close(PureLinkListener *s, PureLinkCounts *counts) { counts->active = s->active_count; } while (s->active_count) let_go(s, s->actives[0]); + pure_aead_free(s->aead); free(s->actives); free(s->peers); free(s->index); diff --git a/serve/seal.h b/serve/seal.h index e802b46..18e3e5f 100644 --- a/serve/seal.h +++ b/serve/seal.h @@ -58,7 +58,10 @@ void pure_opener_free(PureOpener *); * private key and the other's public key: the same on both sides, and * never all zeros), keys from it (HKDF-SHA256 with the info given), and * AES-128-GCM under such a key with the nonce the caller gives (sealing may - * be in place: out may be plain). A context is used by one thread at a time. */ + * be in place: out may be plain). A context is used by one thread at a time; + * sealing or opening with a key given uses that key from then on, so one + * context serves many links (a key costs about as much as a small packet), + * and NULL keeps the key it has. */ typedef struct PureAead PureAead; enum { PURE_AEAD_KEY = 16, PURE_AEAD_NONCE = 12, PURE_AEAD_TAG = 16 }; bool pure_link_secret(const PureSealKey *own, const unsigned char peer[PURE_SEAL_KEY], unsigned char out[32]); @@ -66,10 +69,12 @@ bool pure_link_derive(const unsigned char secret[32], const unsigned char *info, size_t out_size); PureAead *pure_aead(const unsigned char key[PURE_AEAD_KEY]); /* size + PURE_AEAD_TAG bytes into out. */ -bool pure_aead_seal(PureAead *, const unsigned char nonce[PURE_AEAD_NONCE], const unsigned char *aad, size_t aad_size, - const unsigned char *plain, size_t size, unsigned char *out); +bool pure_aead_seal(PureAead *, const unsigned char *key, const unsigned char nonce[PURE_AEAD_NONCE], + const unsigned char *aad, size_t aad_size, const unsigned char *plain, size_t size, + unsigned char *out); /* size - PURE_AEAD_TAG bytes into plain; false when it does not open. */ -bool pure_aead_open(PureAead *, const unsigned char nonce[PURE_AEAD_NONCE], const unsigned char *aad, - size_t aad_size, const unsigned char *sealed, size_t size, unsigned char *plain); +bool pure_aead_open(PureAead *, const unsigned char *key, const unsigned char nonce[PURE_AEAD_NONCE], + const unsigned char *aad, size_t aad_size, const unsigned char *sealed, size_t size, + unsigned char *plain); void pure_aead_free(PureAead *); #endif |
