aboutsummaryrefslogtreecommitdiffstats
path: root/link/link-protocol.c
diff options
context:
space:
mode:
Diffstat (limited to 'link/link-protocol.c')
-rw-r--r--link/link-protocol.c239
1 files changed, 112 insertions, 127 deletions
diff --git a/link/link-protocol.c b/link/link-protocol.c
index 8845ea5..a81b00a 100644
--- a/link/link-protocol.c
+++ b/link/link-protocol.c
@@ -290,7 +290,7 @@ struct PureLinkSender {
uint64_t epoch_at, epoch_bytes, epoch_every, epoch_most, heard_at, offer_again;
};
-static bool out_keys(PureLinkSender *l);
+static bool seal_in(PureLinkSender *l, uint32_t n, const unsigned char secret[32]);
static bool offer(PureLinkSender *l, uint32_t n);
static void begin_epoch(PureLinkSender *l, const unsigned char their[PURE_SEAL_KEY], uint64_t now);
@@ -949,11 +949,7 @@ void pure_link_sender_tick(PureLinkSender *l, uint64_t now) {
/* Nothing heard for long with packets out: the receiver lost the
* epoch (it started again without its memo), or its answers are
* kept from us. Offers only now, in epoch 0, until one begins. */
- memcpy(l->before_secret, l->epoch_secret, 32);
- l->before_epoch = l->epoch;
- memcpy(l->epoch_secret, l->secret, 32);
- l->epoch = 0;
- out_keys(l);
+ seal_in(l, 0, l->secret);
offer(l, 1);
l->renegotiating = true;
l->offer_again = 0;
@@ -1008,8 +1004,14 @@ PureLinkSender *pure_link_sender(const char *source, const char *input, uint32_t
return l;
}
-/* Keys of its packets in the epoch it is in. */
-static bool out_keys(PureLinkSender *l) {
+/* Its packets sealed in epoch n (its secret) from now on: the one it was
+ * in is the one before, whose answers may still come. */
+static bool seal_in(PureLinkSender *l, uint32_t n, const unsigned char secret[32]) {
+ memcpy(l->before_secret, l->epoch_secret, 32);
+ l->before_epoch = l->epoch;
+ memmove(l->epoch_secret, secret, 32);
+ l->epoch = n;
+ l->epoch_bytes = 0;
pure_aead_free(l->out);
l->out = aead_of(l->epoch_secret, l->own_public, l->peer, l->run, 0, l->out_base);
++l->counts.keyed;
@@ -1034,16 +1036,12 @@ static void begin_epoch(PureLinkSender *l, const unsigned char their[PURE_SEAL_K
explicit_bzero(fresh, sizeof(fresh));
l->offering = false;
l->epoch_at = now; /* failed: offered again a whole epoch on */
- if (!ok) return;
- memcpy(l->before_secret, l->epoch_secret, 32);
- l->before_epoch = l->epoch;
- memcpy(l->epoch_secret, next, 32);
+ if (ok) {
+ seal_in(l, l->offer_epoch, next);
+ l->renegotiating = false;
+ ++l->counts.epochs;
+ }
explicit_bzero(next, sizeof(next));
- l->epoch = l->offer_epoch;
- l->epoch_bytes = 0;
- l->renegotiating = false;
- ++l->counts.epochs;
- out_keys(l);
}
/* The receiver's key, with the secret made (or given) into l->secret: the
@@ -1057,9 +1055,7 @@ static bool keyed(PureLinkSender *l, const unsigned char peer[PURE_SEAL_KEY], co
if (secret) memcpy(l->secret, secret, 32);
if (!secret) ++l->counts.secrets;
if (!secret && !(l->own && pure_link_secret(l->own, l->peer, l->secret))) return false;
- memcpy(l->epoch_secret, l->secret, 32);
- l->epoch = l->before_epoch = 0;
- return out_keys(l) && offer(l, 1);
+ return seal_in(l, 0, l->secret) && offer(l, 1);
}
bool pure_link_sender_peer(PureLinkSender *l, const unsigned char peer[PURE_SEAL_KEY]) { return keyed(l, peer, NULL); }
@@ -1110,6 +1106,13 @@ void pure_link_sender_free(PureLinkSender *l) {
* packet goes on without a handshake. */
typedef struct { uint64_t first, last; } Range;
+/* An epoch of a sender's run: its number, its secret, and the keys of the
+ * sender's packets in it (set: there is one). */
+typedef struct {
+ uint32_t n;
+ bool set;
+ unsigned char secret[32], key[PURE_AEAD_KEY], base[PURE_AEAD_NONCE];
+} Epoch;
typedef struct Active Active;
typedef struct {
Active *active;
@@ -1124,26 +1127,20 @@ typedef struct {
* bytes that came from it (RFC 9000 ยง8). */
bool checked;
uint64_t challenge, bytes_in, bytes_out;
- /* Epochs: the sender's, as its packets opened last (the keys in and out
- * below are of it); the one before, whose packets may still come; an
- * offer accepted and not yet used; an offer taken, accepted in the
- * turn. Their secrets and keys are last. */
- uint32_t epoch, before_epoch, accept_epoch, waiting_epoch;
- bool has_before, accepted, waits;
- unsigned char in_key[PURE_AEAD_KEY], out_key[PURE_AEAD_KEY], in_base[PURE_AEAD_NONCE], out_base[PURE_AEAD_NONCE];
+ unsigned char out_key[PURE_AEAD_KEY], out_base[PURE_AEAD_NONCE]; /* the answers', in the epoch now */
uint64_t next_pn, largest_at;
_Alignas(8) unsigned char address[PURE_LINK_ADDRESS]; /* where it sends from: a socket's address */
size_t address_size;
Range got[RANGES]; /* the numbers received, newest first */
unsigned got_count;
- /* The epoch's secret; the accepted one's, its keys in, the key offered
- * and ours (the same offer again gets the same answer); the keys in of
- * the epoch before; the offer taken: the epoch of the packet it came in,
- * and its key. */
- unsigned char epoch_secret[32], accept_secret[32], offered[PURE_SEAL_KEY], accept_public[PURE_SEAL_KEY];
- unsigned char accept_in_key[PURE_AEAD_KEY], accept_in_base[PURE_AEAD_NONCE];
- unsigned char before_in_key[PURE_AEAD_KEY], before_in_base[PURE_AEAD_NONCE];
- unsigned char waiting_in, waiting[PURE_SEAL_KEY];
+ /* Epochs: the sender's now (as its packets opened last), the one before
+ * (its late packets), and the next, accepted and not yet used, with the
+ * key it offered and ours (the same offer again gets the same ACCEPT).
+ * An offer taken waits for the turn (waits; next.n its epoch, waiting_in
+ * the epoch of the packet it came in, offered its key). */
+ Epoch now, before, next;
+ bool waits;
+ unsigned char waiting_in, offered[PURE_SEAL_KEY], accept_public[PURE_SEAL_KEY];
} Run;
typedef struct { uint32_t from, to; } Span; /* bytes [from, to) */
typedef struct {
@@ -1461,12 +1458,12 @@ static void remember_epoch(PureLinkReceiver *s, uint32_t peer, const Run *r) {
unsigned char *e = place + MEMO_EPOCH;
memset(e, 0, 16);
__atomic_thread_fence(__ATOMIC_RELEASE);
- memcpy(e + 16, r->accept_secret, 32);
- memcpy(e + 48, r->epoch_secret, 32);
+ memcpy(e + 16, r->next.secret, 32);
+ memcpy(e + 48, r->now.secret, 32);
memcpy(e + 80, r->offered, PURE_SEAL_KEY);
memcpy(e + 112, r->accept_public, PURE_SEAL_KEY);
put64(e, r->run);
- put32(e + 8, r->accept_epoch);
+ put32(e + 8, r->next.n);
__atomic_thread_fence(__ATOMIC_RELEASE);
put32(e + 12, epoch_check(e));
}
@@ -1853,47 +1850,49 @@ static bool hand_over(PureLinkReceiver *s, Stream *st) {
return true;
}
-/* Keys in (from the sender) and out (answers) of an epoch's secret. */
-static bool epoch_keys(const PureLinkReceiver *s, const Peer *peer, Run *r, const unsigned char secret[32]) {
- return derive(secret, peer->key, s->own_public, r->run, 0, r->in_key, r->in_base) &&
- derive(secret, s->own_public, peer->key, r->answer_run, r->run, r->out_key, r->out_base);
+/* e as epoch n of r's sender, from its secret: the keys of its packets. */
+static bool epoch_made(const PureLinkReceiver *s, const Peer *peer, const Run *r, Epoch *e, uint32_t n,
+ const unsigned char secret[32]) {
+ e->n = n;
+ e->set = true;
+ memmove(e->secret, secret, 32);
+ return derive(e->secret, peer->key, s->own_public, r->run, 0, e->key, e->base);
}
-/* A run not seen: in the epoch the memo has for it (its accepted one, or the
- * one before with that one accepted), or epoch 0. False when the packet's
- * epoch is none of these. */
+/* The sender is in epoch e now: the one it was in is the one before, and
+ * the answers are sealed in e. */
+static bool epoch_now(const PureLinkReceiver *s, const Peer *peer, Run *r, const Epoch *e) {
+ r->before = r->now;
+ r->now = *e;
+ return derive(r->now.secret, s->own_public, peer->key, r->answer_run, r->run, r->out_key, r->out_base);
+}
+
+/* A run not seen: in the epoch the memo has for it (the one accepted, or
+ * the one before with that one accepted and not yet used), or epoch 0.
+ * False when the packet's epoch (e) is none of these. */
static bool run_begun(PureLinkReceiver *s, const Peer *peer, const Active *a, Run *r, unsigned e) {
const unsigned char *memo = memo_epoch(s, (uint32_t)(peer - s->peers), r->run);
uint32_t n = memo ? get32(memo + 8) : 0;
- const unsigned char *secret = a->secret;
- if (memo && e == (n & EPOCH_MASK)) r->epoch = n, secret = memo + 16;
+ Epoch made;
+ bool ok;
+ if (memo && e == (n & EPOCH_MASK)) /* it uses the one accepted: the one before may still come */
+ ok = epoch_made(s, peer, r, &made, n - 1, memo + 48) && epoch_now(s, peer, r, &made) &&
+ epoch_made(s, peer, r, &made, n, memo + 16) && epoch_now(s, peer, r, &made);
else if (memo && n && e == ((n - 1) & EPOCH_MASK)) { /* it had not used the one accepted yet */
- r->epoch = n - 1, secret = memo + 48;
- r->accepted = true;
- r->accept_epoch = n;
- memcpy(r->accept_secret, memo + 16, 32);
+ ok = epoch_made(s, peer, r, &made, n - 1, memo + 48) && epoch_now(s, peer, r, &made) &&
+ epoch_made(s, peer, r, &r->next, n, memo + 16);
memcpy(r->offered, memo + 80, PURE_SEAL_KEY);
memcpy(r->accept_public, memo + 112, PURE_SEAL_KEY);
- if (!derive(r->accept_secret, peer->key, s->own_public, r->run, 0, r->accept_in_key, r->accept_in_base)) return false;
- } else if (e) return false;
- memcpy(r->epoch_secret, secret, 32);
- if (memo && r->epoch == n && n && /* its packets of the epoch before may still come */
- derive(memo + 48, peer->key, s->own_public, r->run, 0, r->before_in_key, r->before_in_base))
- r->has_before = true, r->before_epoch = n - 1;
- return epoch_keys(s, peer, r, secret);
+ } else
+ ok = !e && epoch_made(s, peer, r, &made, 0, a->secret) && epoch_now(s, peer, r, &made);
+ explicit_bzero(&made, sizeof(made));
+ return ok;
}
/* The sender uses the epoch accepted: it is its epoch now. */
static void confirm(PureLinkReceiver *s, const Peer *peer, Run *r) {
- memcpy(r->before_in_key, r->in_key, PURE_AEAD_KEY);
- memcpy(r->before_in_base, r->in_base, PURE_AEAD_NONCE);
- r->before_epoch = r->epoch;
- r->has_before = true;
- r->epoch = r->accept_epoch;
- memcpy(r->epoch_secret, r->accept_secret, 32);
- explicit_bzero(r->accept_secret, sizeof(r->accept_secret));
- r->accepted = false;
- epoch_keys(s, peer, r, r->epoch_secret);
+ epoch_now(s, peer, r, &r->next);
+ explicit_bzero(&r->next, sizeof(r->next));
++s->counts.epochs;
}
@@ -1913,11 +1912,11 @@ static bool has_token(double *tokens, uint64_t *at, double rate, double most, ui
* (the sender waits for the epoch). */
static void offer_taken(PureLinkReceiver *s, Active *a, Run *r, uint32_t n, const unsigned char offered[PURE_SEAL_KEY],
unsigned e, bool blocked, uint64_t now) {
- if (r->accepted && r->accept_epoch == n && !memcmp(r->offered, offered, PURE_SEAL_KEY)) {
+ if (r->next.set && r->next.n == n && !memcmp(r->offered, offered, PURE_SEAL_KEY)) {
mark_due(s, r); /* said again */
return;
}
- if (!(n == 1 && !e) && !(n == r->epoch + 1 && e == (r->epoch & EPOCH_MASK))) return;
+ if (!(n == 1 && !e) && !(n == r->now.n + 1 && e == (r->now.n & EPOCH_MASK))) return;
if (r->waits || (a->accepted_at && now - a->accepted_at < ACCEPT_EVERY_NS)) return;
if (!blocked && !has_token(&s->accept_tokens, &s->accept_at, ACCEPTS, ACCEPTS_AT_ONCE, now)) return; /* it offers again */
void *offers = room(s->offers, &s->offer_capacity, s->offer_count, sizeof(*s->offers), 16);
@@ -1927,10 +1926,11 @@ static void offer_taken(PureLinkReceiver *s, Active *a, Run *r, uint32_t n, cons
s->offers[s->offer_count++].id = r->run;
if (!blocked) --s->accept_tokens;
a->accepted_at = now;
+ explicit_bzero(&r->next, sizeof(r->next)); /* an offer accepted before is let go: it offers another */
+ r->next.n = n;
r->waits = true;
- r->waiting_epoch = n;
r->waiting_in = (unsigned char)e;
- memcpy(r->waiting, offered, PURE_SEAL_KEY);
+ memcpy(r->offered, offered, PURE_SEAL_KEY);
}
/* The offers taken, accepted (after the answers of the packets they came
@@ -1942,9 +1942,9 @@ static void accept_waiting(PureLinkReceiver *s, uint64_t now) {
r->waits = false;
Active *a = r->active;
const Peer *peer = &s->peers[a->peer];
- uint32_t n = r->waiting_epoch;
+ uint32_t n = r->next.n;
const unsigned char *base = n == 1 && !r->waiting_in ? a->secret
- : n == r->epoch + 1 && r->waiting_in == (r->epoch & EPOCH_MASK) ? r->epoch_secret
+ : n == r->now.n + 1 && r->waiting_in == (r->now.n & EPOCH_MASK) ? r->now.secret
: NULL;
if (!base) continue; /* its epoch moved on since */
if (!s->ours_ready || now - s->ours_at >= OURS_LIFE) { /* a new pair for this second's offers */
@@ -1953,25 +1953,21 @@ static void accept_waiting(PureLinkReceiver *s, uint64_t now) {
s->ours_at = now;
++s->counts.secrets;
}
- unsigned char fresh[32];
+ unsigned char fresh[32], secret[32];
++s->counts.secrets;
- bool ok = pure_link_agree(s->ours_private, r->waiting, fresh) && epoch_secret_of(base, fresh, n, r->accept_secret) &&
- derive(r->accept_secret, peer->key, s->own_public, r->run, 0, r->accept_in_key, r->accept_in_base);
+ bool ok = pure_link_agree(s->ours_private, r->offered, fresh) && epoch_secret_of(base, fresh, n, secret) &&
+ epoch_made(s, peer, r, &r->next, n, secret);
explicit_bzero(fresh, sizeof(fresh));
- if (!ok) continue;
- memcpy(r->accept_public, s->ours_public, PURE_SEAL_KEY);
- memcpy(r->offered, r->waiting, PURE_SEAL_KEY);
- r->accept_epoch = n;
- r->accepted = true;
- if (base == a->secret && r->epoch) { /* begun again from epoch 0: so are its answers, the old epoch before */
- memcpy(r->before_in_key, r->in_key, PURE_AEAD_KEY);
- memcpy(r->before_in_base, r->in_base, PURE_AEAD_NONCE);
- r->before_epoch = r->epoch;
- r->has_before = true;
- r->epoch = 0;
- memcpy(r->epoch_secret, a->secret, 32);
- epoch_keys(s, peer, r, r->epoch_secret);
+ explicit_bzero(secret, sizeof(secret));
+ if (!ok) {
+ explicit_bzero(&r->next, sizeof(r->next));
+ continue;
}
+ memcpy(r->accept_public, s->ours_public, PURE_SEAL_KEY);
+ Epoch zero;
+ if (base == a->secret && r->now.n && epoch_made(s, peer, r, &zero, 0, a->secret)) /* begun again from 0: so are its answers */
+ epoch_now(s, peer, r, &zero);
+ explicit_bzero(&zero, sizeof(zero));
remember_epoch(s, a->peer, r);
mark_due(s, r);
}
@@ -1983,8 +1979,6 @@ static bool may_key(PureLinkReceiver *s, uint64_t now) {
return has_token(&s->key_tokens, &s->key_at, KEYINGS, KEYINGS, now);
}
-enum { AS_NOW, AS_ACCEPTED, AS_BEFORE, AS_ZERO };
-
/* A packet from peer, if it opens with the keys of peer's run (made for a
* run not seen yet, and for a peer at rest its active state): its frames. */
static bool take_from(PureLinkReceiver *s, Peer *peer, const unsigned char *packet, size_t n, const void *from,
@@ -1996,43 +1990,34 @@ static bool take_from(PureLinkReceiver *s, Peer *peer, const unsigned char *pack
if (!r && !may_key(s, now)) return false;
bool woke = !a;
if (woke && !(a = activate(s, peer, now))) return false;
- Run made = {.active = a, .run = run, .seen = now, .used = true};
- /* The keys of the packet's epoch: the one accepted, its run's now, the
- * one before, or epoch 0's for a sender that begins its epochs again;
- * for a run not seen, as the memo has it. Two may share the four bits
- * (a chain begun again): each is tried. */
- unsigned char zero_key[PURE_AEAD_KEY], zero_base[PURE_AEAD_NONCE];
- const unsigned char *keys[4], *bases[4];
- int as[4], tries = 0;
+ Run made = {.active = a, .run = run, .seen = now, .used = true}, *k = r ? r : &made;
if (!r) {
made.answer_run = random64();
++s->counts.keyed;
- if (run_begun(s, peer, a, &made, e)) {
- if (made.accepted && e == (made.accept_epoch & EPOCH_MASK))
- keys[tries] = made.accept_in_key, bases[tries] = made.accept_in_base, as[tries++] = AS_ACCEPTED;
- keys[tries] = made.in_key, bases[tries] = made.in_base, as[tries++] = AS_NOW;
- }
- } else {
- if (r->accepted && e == (r->accept_epoch & EPOCH_MASK))
- keys[tries] = r->accept_in_key, bases[tries] = r->accept_in_base, as[tries++] = AS_ACCEPTED;
- if (e == (r->epoch & EPOCH_MASK)) keys[tries] = r->in_key, bases[tries] = r->in_base, as[tries++] = AS_NOW;
- if (r->has_before && e == (r->before_epoch & EPOCH_MASK))
- keys[tries] = r->before_in_key, bases[tries] = r->before_in_base, as[tries++] = AS_BEFORE;
- if (!e && r->epoch && derive(a->secret, peer->key, s->own_public, run, 0, zero_key, zero_base))
- keys[tries] = zero_key, bases[tries] = zero_base, as[tries++] = AS_ZERO;
- }
+ if (!run_begun(s, peer, a, &made, e)) k = NULL;
+ }
+ /* The keys of the packet's epoch: the next, now's, the one before, or
+ * epoch 0's for a sender that begins its epochs again. Two may share
+ * the four bits (a chain begun again): each is tried. */
+ Epoch zero = {0};
+ const Epoch *tried[4], *opened = NULL;
+ int tries = 0;
+ if (k && k->next.set && e == (k->next.n & EPOCH_MASK)) tried[tries++] = &k->next;
+ if (k && e == (k->now.n & EPOCH_MASK)) tried[tries++] = &k->now;
+ if (k && k->before.set && e == (k->before.n & EPOCH_MASK)) tried[tries++] = &k->before;
+ if (k && !e && k->now.n && epoch_made(s, peer, k, &zero, 0, a->secret)) tried[tries++] = &zero;
unsigned char plain[DATAGRAM_MOST], nonce[PURE_AEAD_NONCE];
size_t size = n - HEADER - PURE_AEAD_TAG;
- int opened = -1;
- for (int t = 0; t < tries && opened < 0; ++t) {
- const unsigned char *give = keys[t];
- nonce_of(nonce, bases[t], pn);
- if (s->open_keyed && !memcmp(s->open_key, keys[t], PURE_AEAD_KEY)) give = NULL; /* the key it has */
- else memcpy(s->open_key, keys[t], PURE_AEAD_KEY), s->open_keyed = true;
- if (pure_aead_open(s->aead, give, nonce, packet, HEADER, packet + HEADER, n - HEADER, plain)) opened = as[t];
- }
- explicit_bzero(zero_key, sizeof(zero_key));
- if (opened < 0) {
+ for (int t = 0; t < tries && !opened; ++t) {
+ const unsigned char *give = tried[t]->key;
+ nonce_of(nonce, tried[t]->base, pn);
+ if (s->open_keyed && !memcmp(s->open_key, give, PURE_AEAD_KEY)) give = NULL; /* the key it has */
+ else memcpy(s->open_key, give, PURE_AEAD_KEY), s->open_keyed = true;
+ if (pure_aead_open(s->aead, give, nonce, packet, HEADER, packet + HEADER, n - HEADER, plain)) opened = tried[t];
+ }
+ bool uses_next = k && opened == &k->next;
+ explicit_bzero(&zero, sizeof(zero));
+ if (!opened) {
explicit_bzero(&made, sizeof(made));
if (!r) s->key_tokens = s->key_tokens > 1 ? s->key_tokens - 1 : 0; /* keys for nothing */
if (woke) let_go(s, a);
@@ -2042,7 +2027,7 @@ static bool take_from(PureLinkReceiver *s, Peer *peer, const unsigned char *pack
bool fresh = !r;
if (!r) r = install_run(a, &made);
explicit_bzero(&made, sizeof(made));
- if (opened == AS_ACCEPTED) confirm(s, peer, r);
+ if (uses_next) confirm(s, peer, r);
if (!fresh && had(r, pn)) { /* again (opened first: nothing unsealed answers): its acknowledgment was lost, perhaps */
r->ack_due = true;
mark_due(s, r);
@@ -2165,9 +2150,9 @@ static void answer(PureLinkReceiver *s, uint64_t now, PureLinkSend *send, void *
put64(body + at, r->challenge);
at += 8;
}
- if (r->accepted) { /* the sender's next epoch, until it uses it */
+ if (r->next.set) { /* the sender's next epoch, until it uses it */
body[at] = F_ACCEPT;
- put32(body + at + 1, r->accept_epoch);
+ put32(body + at + 1, r->next.n);
memcpy(body + at + 5, r->accept_public, PURE_SEAL_KEY);
memcpy(body + at + EPOCH_FRAME, r->offered, ANSWERS);
at += ACCEPT_FRAME;
@@ -2203,7 +2188,7 @@ static void answer(PureLinkReceiver *s, uint64_t now, PureLinkSend *send, void *
if (s->seal_keyed && !memcmp(s->seal_key, r->out_key, PURE_AEAD_KEY)) give = NULL; /* the key it has */
else memcpy(s->seal_key, r->out_key, PURE_AEAD_KEY), s->seal_keyed = true;
size_t total =
- seal_packet(s->aead, give, r->out_base, s->own_id, r->epoch, r->answer_run, r->next_pn++, body, at, packet);
+ seal_packet(s->aead, give, r->out_base, s->own_id, r->now.n, r->answer_run, r->next_pn++, body, at, packet);
if (total && send(context, packet, total, r->address, r->address_size)) {
++s->counts.packets;
if (!r->checked) r->bytes_out += total;