}
#endif
+/*----- Protocol summary --------------------------------------------------*
+ *
+ * * Request
+ * memz KEYTAG tag of wanted secret
+ * ge U public vector
+ *
+ * * Response
+ * ge V public vector: V = v P
+ * ge W encrypted clue: W = R - Y = r P - v U
+ * mem[TAGSZ] TAG MAC tag on ciphertext
+ * mem[KSZ] CT secret, encrypted with Z = r X
+ */
+
/*----- Listening for requests --------------------------------------------*/
/* Rate limiting parameters.
#define RATE_REFILL 10 /* Credits per second. */
#define RATE_CREDIT 1000 /* Initial credit. */
+static time_t now;
+
+/* Secrets table.
+ *
+ * The server doesn't want to maintain state for each client. Instead, we
+ * generate a global secret, and derive per-client secrets from it. A secret
+ * needs to have an expiry time (at which point we won't use it for new
+ * requests) and a deletion time (at which point we just forget that it ever
+ * existed). This lets us roll over to a new secret without leaving existing
+ * clients completely in the lurch.
+ *
+ * Secrets are kept in a linked list, ordered by expiry time. At any given
+ * time there is at most one unexpired secret (because we only make a new one
+ * when the old one expires).
+ */
+
+struct secret {
+ struct secret *next;
+ uint32 seq;
+ time_t t_exp, t_del;
+ octet x[32];
+};
+static struct secret *secrets = 0, *live_secret = 0;
+static uint32 next_secret_seq = 0;
+#define T_SECEXP 30
+#define T_SECDEL 45
+
+static void kill_dead_secrets(void)
+{
+ struct secret *s = secrets, *ss;
+
+ for (s = secrets; s && s->t_del <= now; s = ss) {
+ ss = s->next;
+ DESTROY(s);
+ }
+ secrets = 0;
+ if (!s) live_secret = 0;
+}
+
+static struct secret *find_secret(uint32 seq)
+{
+ struct secret *s;
+
+ kill_dead_secrets();
+ for (s = secrets; s; s = s->next)
+ if (s->seq == seq) return (s);
+ return (0);
+}
+
+static struct secret *fresh_secret(void)
+{
+ struct secret *s;
+
+ if (live_secret && live_secret->t_exp > now) return (live_secret);
+ kill_dead_secrets();
+
+ s = CREATE(struct secret);
+ s->seq = next_secret_seq++;
+ s->next = 0;
+ rand_get(RAND_GLOBAL, s->x, sizeof(s->x));
+ s->t_exp = now + T_SECEXP; s->t_del = now + T_SECDEL;
+ if (live_secret) live_secret->next = s;
+ else secrets = s;
+ live_secret = s;
+ return (s);
+}
+
+static int fetch_key(const char *tag, struct sockaddr_in *sin,
+ key **ky, struct kinfo *k)
+{
+ dstr d = DSTR_INIT, dd = DSTR_INIT;
+ key_data **kkd;
+ char *p, *q;
+ const char *pp;
+ struct in_addr in;
+ int ch, mlen, rc = -1;
+
+ /* Find the key. */
+ kfupdate();
+ if (key_qtag(kf, tag, &d, ky, &kkd)) {
+ complain(LOG_WARNING, "unknown key tag `%s' from %s:%d",
+ tag, inet_ntoa(sin->sin_addr), ntohs(sin->sin_port));
+ goto done;
+ }
+
+ /* And make sure that it has the right shape. */
+ if (((*ky)->k->e & KF_ENCMASK) != KENC_BINARY) {
+ complain(LOG_ERR, "key %s is not plain binary data", d.buf);
+ goto done;
+ }
+
+ /* Find the list of clients, and look up the caller's address in the
+ * list. Entries have the form ADDRESS[/LEN][=TAG] and are separated by
+ * `;'.
+ */
+ if ((pp = key_getattr(kf, *ky, "clients")) == 0) {
+ complain(LOG_WARNING,
+ "key %s requested from %s:%d has no `clients' attribute",
+ d.buf, inet_ntoa(sin->sin_addr), ntohs(sin->sin_port));
+ goto done;
+ }
+ dstr_puts(&dd, pp);
+ p = dd.buf;
+ while (*p) {
+ q = p;
+ while (isdigit((unsigned char)*q) || *q == '.') q++;
+ ch = *q; *q++ = 0;
+ if (!inet_aton(p, &in)) goto skip;
+ if (ch != '/')
+ mlen = 32;
+ else {
+ p = q;
+ while (isdigit((unsigned char)*q)) q++;
+ ch = *q; *q++ = 0;
+ mlen = atoi(p);
+ }
+ if (((sin->sin_addr.s_addr ^ in.s_addr) &
+ htonl(0xffffffff << (32 - mlen))) == 0)
+ goto match;
+ skip:
+ if (!ch) break;
+ p = q;
+ while (*p && *p != ';') p++;
+ if (*p) p++;
+ }
+ complain(LOG_WARNING, "access to key %s denied to %s:%d",
+ d.buf, inet_ntoa(sin->sin_addr), ntohs(sin->sin_port));
+ goto done;
+
+match:
+ /* Build a tag name for the caller's KEM key, either from the client
+ * match or the source address.
+ */
+ if (ch != '=') {
+ DRESET(&dd);
+ dstr_puts(&dd, "client-");
+ dstr_puts(&dd, inet_ntoa(sin->sin_addr));
+ p = dd.buf;
+ } else {
+ p = q;
+ while (*q && *q != ';') q++;
+ if (*q == ';') *q++ = 0;
+ }
+
+ /* Report the match. */
+ complain(LOG_NOTICE, "client %s:%d (`%s') requests key %s",
+ inet_ntoa(sin->sin_addr), ntohs(sin->sin_port), p, d.buf);
+
+ /* Load the KEM key. */
+ if (loadkey(p, k, 0)) goto done;
+ D( debug_ge("X", k.g, k.X); )
+
+ /* All complete. */
+ rc = 0;
+
+done:
+ /* Clean everything up. */
+ dstr_destroy(&d);
+ dstr_destroy(&dd);
+ if (rc) k_free(k);
+ return (rc);
+}
+
+static int respond_v0(buf *bin, buf *bout, struct sockaddr_in *sin)
+{
+ ge *R = 0, *U = 0, *V = 0, *W = 0, *Y = 0, *Z = 0;
+ mp *r = MP_NEW, *v = MP_NEW;
+ octet *kk, *t, *tt;
+ char *p;
+ size_t sz;
+ ghash *h = 0;
+ gmac *m = 0;
+ gcipher *c = 0;
+ struct kinfo k;
+ key *ky;
+ size_t ksz;
+ int rc = -1;
+
+ /* Clear out the key state. */
+ k_init(&k);
+
+ /* Extract the key tag name. */
+ if ((p = buf_getmemz(bin, &sz)) == 0) {
+ complain(LOG_WARNING, "invalid key tag from %s:%d",
+ inet_ntoa(sin->sin_addr), ntohs(sin->sin_port));
+ goto done;
+ }
+
+ /* Find the client's key and check that it's allowed. */
+ if (fetch_key(p, sin, &ky, &k)) goto done;
+
+ /* Read the caller's ephemeral key. */
+ R = G_CREATE(k.g); W = G_CREATE(k.g);
+ U = G_CREATE(k.g); V = G_CREATE(k.g);
+ Y = G_CREATE(k.g); Z = G_CREATE(k.g);
+ if (G_FROMBUF(k.g, bin, U)) {
+ complain(LOG_WARNING, "failed to read ephemeral vector from %s:%d",
+ inet_ntoa(sin->sin_addr), ntohs(sin->sin_port));
+ goto done;
+ }
+ D( debug_ge("U", k.g, U); )
+ if (BLEFT(bin)) {
+ complain(LOG_WARNING, "trailing junk in request from %s:%d",
+ inet_ntoa(sin->sin_addr), ntohs(sin->sin_port));
+ goto done;
+ }
+
+ /* Ephemeral Diffie--Hellman. Choose v in GF(q) at random; compute
+ * V = v P and -Y = (-v) U.
+ */
+ v = mprand_range(v, k.g->r, &rand_global, 0);
+ G_EXP(k.g, V, k.g->g, v);
+ D( debug_mp("v", v); debug_ge("V", k.g, V); )
+ v = mp_sub(v, k.g->r, v);
+ G_EXP(k.g, Y, U, v);
+ D( debug_ge("-Y", k.g, Y); )
+
+ /* DLIES. Choose r in GF(q) at random; compute R = r P and Z = r X. Mask
+ * the clue R as W = R - Y. (Doing the subtraction here makes life easier
+ * at the other end, since we can determine -Y by negating v whereas the
+ * recipient must subtract vectors which may be less efficient.)
+ */
+ r = mprand_range(r, k.g->r, &rand_global, 0);
+ G_EXP(k.g, R, k.g->g, r);
+ D( debug_mp("r", r); debug_ge("R", k.g, R); )
+ G_EXP(k.g, Z, k.X, r);
+ G_MUL(k.g, W, R, Y);
+ D( debug_ge("Z", k.g, Z); debug_ge("W", k.g, W); )
+
+ /* Derive encryption and integrity keys. */
+ derive(&k, R, Z, "cipher", k.cc->name, k.cc->keysz, &kk, &ksz);
+ c = GC_INIT(k.cc, kk, ksz);
+ derive(&k, R, Z, "mac", k.mc->name, k.mc->keysz, &kk, &ksz);
+ m = GM_KEY(k.mc, kk, ksz);
+
+ /* Build the ciphertext and compute a MAC tag over it. */
+ rc = 0;
+ if (G_TOBUF(k.g, bout, V) ||
+ G_TOBUF(k.g, bout, W))
+ goto done;
+ if ((t = buf_get(bout, k.tagsz)) == 0) goto done;
+ sz = ky->k->u.k.sz;
+ if (BENSURE(bout, sz)) goto done;
+ GC_ENCRYPT(c, ky->k->u.k.k, BCUR(bout), sz);
+ h = GM_INIT(m);
+ GH_HASH(h, BCUR(bout), sz);
+ tt = GH_DONE(h, 0); memcpy(t, tt, k.tagsz);
+ BSTEP(bout, sz);
+
+done:
+ /* Clear everything up and go home. */
+ if (R) G_DESTROY(k.g, R);
+ if (U) G_DESTROY(k.g, U);
+ if (V) G_DESTROY(k.g, V);
+ if (W) G_DESTROY(k.g, W);
+ if (Y) G_DESTROY(k.g, Y);
+ if (Z) G_DESTROY(k.g, Z);
+ if (c) GC_DESTROY(c);
+ if (m) GM_DESTROY(m);
+ if (h) GH_DESTROY(h);
+ if (r) MP_DROP(r);
+ if (v) MP_DROP(v);
+ k_free(&k);
+ return (rc);
+}
+
static int dolisten(int argc, char *argv[])
{
int sk;
- char *p, *q, ch;
- const char *pp;
+ char *p;
char *aspec;
ssize_t n;
- size_t sz;
fd_set fdin;
struct sockaddr_in sin;
- struct in_addr in;
- int mlen;
socklen_t len;
buf bin, bout;
- dstr d = DSTR_INIT, dd = DSTR_INIT;
FILE *fp = 0;
- key *ky;
- key_data **kkd;
- mp *r = MP_NEW, *v = MP_NEW;
- ge *R = 0, *U = 0, *V = 0, *W = 0, *Y = 0, *Z = 0;
- ghash *h = 0;
- gmac *m = 0;
- gcipher *c = 0;
- octet *kk, *t, *tt;
- size_t ksz;
- struct kinfo k;
unsigned bucket = 0, toks;
- time_t last = 0, now;
+ time_t last = 0;
/* Set up the socket address. */
sin.sin_family = AF_INET;
for (;;) {
- /* Clear out the key state. */
- k_init(&k);
-
/* Wait for something to happen. */
FD_ZERO(&fdin);
FD_SET(sk, &fdin);
if (n < 0) {
if (errno != EAGAIN && errno != EINTR)
complain(LOG_ERR, "unexpected receive error: %s", strerror(errno));
- goto again;
+ continue;
}
/* Refill the bucket, and see whether we should reject this packet. */
last = now;
if (bucket > RATE_CREDIT &&
grand_range(&rand_global, bucket - RATE_CREDIT))
- goto again;
+ continue;
bucket++;
/* Set up the input buffer for parsing the request. */
buf_init(&bin, ibuf, n);
-
- /* Extract the key tag name. */
- if ((p = buf_getmemz(&bin, &sz)) == 0) {
- complain(LOG_WARNING, "invalid key tag from %s:%d",
- inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
- goto again;
- }
-
- /* Find the key. */
- kfupdate();
- if (key_qtag(kf, p, &d, &ky, &kkd)) {
- complain(LOG_WARNING, "unknown key tag `%s' from %s:%d",
- p, inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
- goto again;
- }
-
- /* And make sure that it has the right shape. */
- if ((ky->k->e & KF_ENCMASK) != KENC_BINARY) {
- complain(LOG_ERR, "key %s is not plain binary data", d.buf);
- goto again;
- }
-
- /* Find the list of clients, and look up the caller's address in the
- * list. Entries have the form ADDRESS[/LEN][=TAG] and are separated by
- * `;'.
- */
- if ((pp = key_getattr(kf, ky, "clients")) == 0) {
- complain(LOG_WARNING,
- "key %s requested from %s:%d has no `clients' attribute",
- d.buf, inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
- goto again;
- }
- dstr_puts(&dd, pp);
- p = dd.buf;
- while (*p) {
- q = p;
- while (isdigit((unsigned char)*q) || *q == '.') q++;
- ch = *q; *q++ = 0;
- if (!inet_aton(p, &in)) goto skip;
- if (ch != '/')
- mlen = 32;
- else {
- p = q;
- while (isdigit((unsigned char)*q)) q++;
- ch = *q; *q++ = 0;
- mlen = atoi(p);
- }
- if (((sin.sin_addr.s_addr ^ in.s_addr) &
- (0xffffffff << (32 - mlen))) == 0)
- goto match;
- skip:
- if (!ch) break;
- p = q;
- while (*p && *p != ';') p++;
- if (*p) p++;
- }
- complain(LOG_WARNING, "access to key %s denied to %s:%d",
- d.buf, inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
- goto again;
-
- match:
- /* Build a tag name for the caller's KEM key, either from the client
- * match or the source address.
- */
- if (ch != '=') {
- DRESET(&dd);
- dstr_puts(&dd, "client-");
- dstr_puts(&dd, inet_ntoa(sin.sin_addr));
- p = dd.buf;
- } else {
- p = q;
- while (*q && *q != ';') q++;
- if (*q == ';') *q++ = 0;
- }
-
- /* Report the match. */
- complain(LOG_NOTICE, "client %s:%d (`%s') requests key %s",
- inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), p, d.buf);
-
- /* Load the KEM key. */
- if (loadkey(p, &k, 0)) goto again;
- D( debug_ge("X", k.g, k.X); )
-
- /* Read the caller's ephemeral key. */
- R = G_CREATE(k.g); W = G_CREATE(k.g);
- U = G_CREATE(k.g); V = G_CREATE(k.g);
- Y = G_CREATE(k.g); Z = G_CREATE(k.g);
- if (G_FROMBUF(k.g, &bin, U)) {
- complain(LOG_WARNING, "failed to read ephemeral vector from %s:%d",
- inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
- goto again;
- }
- D( debug_ge("U", k.g, U); )
- if (BLEFT(&bin)) {
- complain(LOG_WARNING, "trailing junk in request from %s:%d",
- inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
- goto again;
- }
-
- /* Ephemeral Diffie--Hellman. Choose v in GF(q) at random; compute
- * V = v P and -Y = (-v) U.
- */
- v = mprand_range(v, k.g->r, &rand_global, 0);
- G_EXP(k.g, V, k.g->g, v);
- D( debug_mp("v", v); debug_ge("V", k.g, V); )
- v = mp_sub(v, k.g->r, v);
- G_EXP(k.g, Y, U, v);
- D( debug_ge("-Y", k.g, Y); )
-
- /* DLIES. Choose r in GF(q) at random; compute R = r P and Z = r X.
- * Mask the clue R as W = R - Y. (Doing the subtraction here makes life
- * easier at the other end, since we can determine -Y by negating v
- * whereas the recipient must subtract vectors which may be less
- * efficient.)
- */
- r = mprand_range(r, k.g->r, &rand_global, 0);
- G_EXP(k.g, R, k.g->g, r);
- D( debug_mp("r", r); debug_ge("R", k.g, R); )
- G_EXP(k.g, Z, k.X, r);
- G_MUL(k.g, W, R, Y);
- D( debug_ge("Z", k.g, Z); debug_ge("W", k.g, W); )
-
- /* Derive encryption and integrity keys. */
- derive(&k, R, Z, "cipher", k.cc->name, k.cc->keysz, &kk, &ksz);
- c = GC_INIT(k.cc, kk, ksz);
- derive(&k, R, Z, "mac", k.mc->name, k.mc->keysz, &kk, &ksz);
- m = GM_KEY(k.mc, kk, ksz);
-
- /* Build the ciphertext and compute a MAC tag over it. */
buf_init(&bout, obuf, sizeof(obuf));
- if (G_TOBUF(k.g, &bout, V) ||
- G_TOBUF(k.g, &bout, W))
- goto bad;
- if ((t = buf_get(&bout, k.tagsz)) == 0) goto bad;
- sz = ky->k->u.k.sz;
- if (BENSURE(&bout, sz)) goto bad;
- GC_ENCRYPT(c, ky->k->u.k.k, BCUR(&bout), sz);
- h = GM_INIT(m);
- GH_HASH(h, BCUR(&bout), sz);
- tt = GH_DONE(h, 0); memcpy(t, tt, k.tagsz);
- BSTEP(&bout, sz);
+
+ /* Handle the client's message. */
+ if (respond_v0(&bin, &bout, &sin)) continue;
/* Send the reply packet back to the caller. */
+ if (!BOK(&bout)) goto bad;
if (sendto(sk, BBASE(&bout), BLEN(&bout), 0,
(struct sockaddr *)&sin, len) < 0) {
complain(LOG_ERR, "failed to send response to %s:%d: %s",
inet_ntoa(sin.sin_addr), ntohs(sin.sin_port),
strerror(errno));
- goto again;
+ continue;
}
- goto again;
+ continue;
bad:
/* Report a problem building the reply. */
complain(LOG_ERR, "failed to construct response to %s:%d",
inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
-
- again:
- /* Free stuff for the next iteration. */
- DRESET(&d); DRESET(&dd);
- if (R) { G_DESTROY(k.g, R); R = 0; }
- if (U) { G_DESTROY(k.g, U); U = 0; }
- if (V) { G_DESTROY(k.g, V); V = 0; }
- if (W) { G_DESTROY(k.g, W); W = 0; }
- if (Y) { G_DESTROY(k.g, Y); Y = 0; }
- if (Z) { G_DESTROY(k.g, Z); Z = 0; }
- if (c) { GC_DESTROY(c); c = 0; }
- if (m) { GM_DESTROY(m); m = 0; }
- if (h) { GH_DESTROY(h); h = 0; }
- k_free(&k);
}
return (-1);
struct query {
struct query *next;
+ const char *tag;
octet *k;
size_t sz;
struct server *s;
struct server *next;
struct sockaddr_in sin;
struct kinfo k;
+ const struct client_protocol *proto;
mp *u;
ge *U;
octet *h;
};
+struct client_protocol {
+ const char *name;
+ int (*setup)(struct query *, struct server *);
+ int (*receive)(struct query *, struct server *, buf *, buf *);
+ int (*retransmit)(struct query *, struct server *, buf *);
+};
+
/* Record a successful fetch of key material for a query Q. The data starts
* at K and is SZ bytes long. The data is copied: it's safe to overwrite it.
*/
-static void donequery(struct query *q, const void *k, size_t sz)
- { q->k = xmalloc(sz); memcpy(q->k, k, sz); q->sz = sz; nq--; }
+static int donequery(struct query *q, struct server *s,
+ const void *k, size_t sz)
+{
+ octet *tt;
+ ghash *h = 0;
+ int diffp;
+
+ /* If we have a hash, check that the fragment matches it. */
+ if (s && s->h) {
+ h = GH_INIT(s->k.hc);
+ GH_HASH(h, k, sz);
+ tt = GH_DONE(h, 0);
+ diffp = memcmp(tt, s->h, h->ops->c->hashsz);
+ GH_DESTROY(h);
+ if (diffp) {
+ moan("response from %s:%d doesn't match hash",
+ inet_ntoa(s->sin.sin_addr), ntohs(s->sin.sin_port));
+ return (-1);
+ }
+ }
+
+ /* Stash a copy of the key fragment for later. */
+ q->k = xmalloc(sz);
+ memcpy(q->k, k, sz);
+ q->sz = sz; nq--;
+
+ /* All good. */
+ return (0);
+}
+
+static int setup_v0(struct query *q, struct server *s)
+{
+ /* Choose an ephemeral private key u. Let x be our private key. We
+ * compute U = u P and transmit this.
+ */
+ s->u = mprand_range(MP_NEW, s->k.g->r, &rand_global, 0);
+ s->U = G_CREATE(s->k.g);
+ G_EXP(s->k.g, s->U, s->k.g->g, s->u);
+ D( debug_mp("u", s->u); debug_ge("U", s->k.g, s->U); )
+
+ return (0);
+}
+
+static int retransmit_v0(struct query *q, struct server *s, buf *bout)
+{
+ buf_putstrz(bout, q->tag);
+ G_TOBUF(s->k.g, bout, s->U);
+ return (0);
+}
+
+static int receive_v0(struct query *q, struct server *s, buf *bin, buf *bout)
+{
+ ge *R, *V = 0, *W = 0, *Y = 0, *Z = 0;
+ octet *kk, *t, *tt;
+ gcipher *c = 0;
+ gmac *m = 0;
+ ghash *h = 0;
+ size_t n, ksz;
+ octet *p;
+ int rc = -1;
+
+ R = G_CREATE(s->k.g);
+ V = G_CREATE(s->k.g); W = G_CREATE(s->k.g);
+ Y = G_CREATE(s->k.g); Z = G_CREATE(s->k.g);
+ if (G_FROMBUF(s->k.g, bin, V)) {
+ moan("invalid Diffie--Hellman vector from %s:%d",
+ inet_ntoa(s->sin.sin_addr), ntohs(s->sin.sin_port));
+ goto done;
+ }
+ if (G_FROMBUF(s->k.g, bin, W)) {
+ moan("invalid clue vector from %s:%d",
+ inet_ntoa(s->sin.sin_addr), ntohs(s->sin.sin_port));
+ goto done;
+ }
+ D( debug_ge("V", s->k.g, V); debug_ge("W", s->k.g, W); )
+
+ /* We have V and W from the server; determine Y = u V, R = W + Y and
+ * Z = x R, and then derive the symmetric keys.
+ */
+ G_EXP(s->k.g, Y, V, s->u);
+ G_MUL(s->k.g, R, W, Y);
+ G_EXP(s->k.g, Z, R, s->k.x);
+ D( debug_ge("R", s->k.g, R);
+ debug_ge("Y", s->k.g, Y);
+ debug_ge("Z", s->k.g, Z); )
+ derive(&s->k, R, Z, "cipher", s->k.cc->name, s->k.cc->keysz, &kk, &ksz);
+ c = GC_INIT(s->k.cc, kk, ksz);
+ derive(&s->k, R, Z, "mac", s->k.cc->name, s->k.cc->keysz, &kk, &ksz);
+ m = GM_KEY(s->k.mc, kk, ksz);
+
+ /* Find where the MAC tag is. */
+ if ((t = buf_get(bin, s->k.tagsz)) == 0) {
+ moan("missing tag from %s:%d",
+ inet_ntoa(s->sin.sin_addr), ntohs(s->sin.sin_port));
+ goto done;
+ }
+
+ /* Check the integrity of the ciphertext against the tag. */
+ p = BCUR(bin); n = BLEFT(bin);
+ h = GM_INIT(m);
+ GH_HASH(h, p, n);
+ tt = GH_DONE(h, 0);
+ if (!ct_memeq(t, tt, s->k.tagsz)) {
+ moan("incorrect tag from %s:%d",
+ inet_ntoa(s->sin.sin_addr), ntohs(s->sin.sin_port));
+ goto done;
+ }
+
+ /* Decrypt the result and declare this server done. */
+ GC_DECRYPT(c, p, p, n);
+ rc = donequery(q, s, p, n);
+
+done:
+ /* Clear up and go home. */
+ if (R) G_DESTROY(s->k.g, R);
+ if (V) G_DESTROY(s->k.g, V);
+ if (W) G_DESTROY(s->k.g, W);
+ if (Y) G_DESTROY(s->k.g, Y);
+ if (Z) G_DESTROY(s->k.g, Z);
+ if (c) GC_DESTROY(c);
+ if (m) GM_DESTROY(m);
+ if (h) GH_DESTROY(h);
+ return (rc);
+}
+
+static const struct client_protocol prototab[] = {
+ { "v0", setup_v0, receive_v0, retransmit_v0 },
+ { 0 }
+};
/* Initialize a query to a remote server. */
static struct query *qinit_net(const char *tag, const char *spec)
struct query *q;
struct server *s, **stail;
dstr d = DSTR_INIT, dd = DSTR_INIT;
+ const struct client_protocol *proto;
hex_ctx hc;
char *p, *pp, ch;
/* Allocate the query block. */
q = CREATE(struct query);
+ q->tag = tag;
stail = &q->s;
/* Put the spec somewhere we can hack at it. */
ch = *pp; *pp++ = 0;
s->sin.sin_port = htons(getport(p));
+ /* See if there's a protocol name. */
+ if (ch != '?')
+ p = "v0";
+ else {
+ p = pp;
+ pp += strcspn(pp, ";#=");
+ ch = *pp; *pp++ = 0;
+ }
+ for (proto = prototab; proto->name; proto++)
+ if (strcmp(proto->name, p) == 0) goto found_proto;
+ die(1, "unknown protocol name `%s'", p);
+ found_proto:
+ s->proto = proto;
+
/* If there's a key tag then extract that; otherwise use a default. */
if (ch != '=')
p = "udpkey-kem";
if (loadkey(p, &s->k, 1)) exit(1);
D( debug_mp("x", s->k.x); debug_ge("X", s->k.g, s->k.X); )
- /* Choose an ephemeral private key u. Let x be our private key. We
- * compute U = u P and transmit this.
- */
- s->u = mprand_range(MP_NEW, s->k.g->r, &rand_global, 0);
- s->U = G_CREATE(s->k.g);
- G_EXP(s->k.g, s->U, s->k.g->g, s->u);
- D( debug_mp("u", s->u); debug_ge("U", s->k.g, s->U); )
-
/* Link the server on. */
*stail = s; stail = &s->next;
ch = *pp++;
}
+ /* Initialize the protocol. */
+ if (s->proto->setup(q, s)) die(1, "failed to initialize protocol");
+
/* If there are more servers, then continue parsing. */
if (!ch) break;
else if (ch != ';') die(1, "invalid syntax: expected `;'");
if (snarf(file, &k, &sz))
die(1, "failed to read `%s': %s", file, strerror(errno));
q->s = 0;
- donequery(q, k, sz);
+ donequery(q, 0, k, sz);
return (q);
}
fd_set fdin;
struct timeval now, when, tv;
struct sockaddr_in sin;
- ge *R, *V = 0, *W = 0, *Y = 0, *Z = 0;
- octet *kk, *t, *tt;
- gcipher *c = 0;
- gmac *m = 0;
- ghash *h = 0;
socklen_t len;
unsigned next = 0;
buf bin, bout;
- size_t n, j, ksz;
+ size_t n, j;
ssize_t nn;
/* Create a socket. We just use the one socket for everything. We don't
if (q->k) continue;
for (s = q->s; s; s = s->next) {
buf_init(&bout, obuf, sizeof(obuf));
- buf_putstrz(&bout, tag);
- G_TOBUF(s->k.g, &bout, s->U);
+ if (s->proto->retransmit(q, s, &bout)) continue;
if (BBAD(&bout)) {
moan("overflow while constructing request!");
continue;
else if (errno == EINTR) continue;
else {
moan("error receiving reply: %s", strerror(errno));
- goto again;
+ continue;
}
}
}
moan("received reply from unexpected source %s:%d",
inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
- goto again;
+ continue;
found:
/* If the query we found has now been satisfied, ignore this packet.
*/
- if (q->k) goto again;
-
- /* Start parsing the reply. */
- buf_init(&bin, ibuf, nn);
- R = G_CREATE(s->k.g);
- V = G_CREATE(s->k.g); W = G_CREATE(s->k.g);
- Y = G_CREATE(s->k.g); Z = G_CREATE(s->k.g);
- if (G_FROMBUF(s->k.g, &bin, V)) {
- moan("invalid Diffie--Hellman vector from %s:%d",
- inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
- goto again;
- }
- if (G_FROMBUF(s->k.g, &bin, W)) {
- moan("invalid clue vector from %s:%d",
- inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
- goto again;
- }
- D( debug_ge("V", s->k.g, V); debug_ge("W", s->k.g, W); )
+ if (q->k) continue;
- /* We have V and W from the server; determine Y = u V, R = W + Y and
- * Z = x R, and then derive the symmetric keys.
+ /* Parse the reply, and either finish the job or get a message to
+ * send back to the server.
*/
- G_EXP(s->k.g, Y, V, s->u);
- G_MUL(s->k.g, R, W, Y);
- G_EXP(s->k.g, Z, R, s->k.x);
- D( debug_ge("R", s->k.g, R);
- debug_ge("Y", s->k.g, Y);
- debug_ge("Z", s->k.g, Z); )
- derive(&s->k, R, Z, "cipher", s->k.cc->name, s->k.cc->keysz,
- &kk, &ksz);
- c = GC_INIT(s->k.cc, kk, ksz);
- derive(&s->k, R, Z, "mac", s->k.cc->name, s->k.cc->keysz,
- &kk, &ksz);
- m = GM_KEY(s->k.mc, kk, ksz);
-
- /* Find where the MAC tag is. */
- if ((t = buf_get(&bin, s->k.tagsz)) == 0) {
- moan("missing tag from %s:%d",
- inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
- goto again;
- }
-
- /* Check the integrity of the ciphertext against the tag. */
- p = BCUR(&bin); n = BLEFT(&bin);
- h = GM_INIT(m);
- GH_HASH(h, p, n);
- tt = GH_DONE(h, 0);
- if (!ct_memeq(t, tt, s->k.tagsz)) {
- moan("incorrect tag from %s:%d",
- inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
- goto again;
- }
-
- /* Decrypt the result and declare this server done. */
- GC_DECRYPT(c, p, p, n);
- if (s->h) {
- GH_DESTROY(h);
- h = GH_INIT(s->k.hc);
- GH_HASH(h, p, n);
- tt = GH_DONE(h, 0);
- if (memcmp(tt, s->h, h->ops->c->hashsz) != 0) {
- moan("response from %s:%d doesn't match hash",
- inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
- goto again;
- }
+ buf_init(&bin, ibuf, nn);
+ buf_init(&bout, obuf, sizeof(obuf));
+ if (s->proto->receive(q, s, &bin, &bout)) continue;
+ if (q->k) continue;
+ if (!BLEN(&bout) && s->proto->retransmit(q, s, &bout)) continue;
+ if (BBAD(&bout)) {
+ moan("overflow while constructing request!");
+ continue;
}
- donequery(q, p, n);
-
- again:
- /* Tidy things up for the next run through. */
- if (R) { G_DESTROY(s->k.g, R); R = 0; }
- if (V) { G_DESTROY(s->k.g, V); V = 0; }
- if (W) { G_DESTROY(s->k.g, W); W = 0; }
- if (Y) { G_DESTROY(s->k.g, Y); Y = 0; }
- if (Z) { G_DESTROY(s->k.g, Z); Z = 0; }
- if (c) { GC_DESTROY(c); c = 0; }
- if (m) { GM_DESTROY(m); m = 0; }
- if (h) { GH_DESTROY(h); h = 0; }
+ sendto(sk, BBASE(&bout), BLEN(&bout), 0,
+ (struct sockaddr *)&s->sin, sizeof(s->sin));
}
}
}