X-Git-Url: https://git.distorted.org.uk/~mdw/udpkey/blobdiff_plain/a5f873bee5d69f4f12160360ec9a756b7c1c907a..0057c20e40fd5e747d64b50fe96ca3d996916146:/udpkey.c diff --git a/udpkey.c b/udpkey.c index 452a857..2715c65 100644 --- a/udpkey.c +++ b/udpkey.c @@ -150,7 +150,7 @@ static int snarf(const char *name, void **p, size_t *sz) * otherwise write to stderr. Don't use `%m' because that won't work when * writing to stderr. */ -static void complain(int sev, const char *msg, ...) +static void PRINTF_LIKE(2, 3) complain(int sev, const char *msg, ...) { va_list ap; @@ -469,12 +469,25 @@ static void debug_ge(const char *what, group *g, ge *X) } #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. * * There's a probabilistic rate-limiting mechanism. A counter starts at 0. - * Every time we oricess a request, we increment the counter. The counter + * Every time we process a request, we increment the counter. The counter * drops by RATE_REFILL every second. If the counter is below RATE_CREDIT * then the request is processed; otherwise it is processed with probability * 1/(counter - RATE_CREDIT). @@ -482,34 +495,285 @@ static void debug_ge(const char *what, group *g, ge *X) #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; @@ -542,9 +806,6 @@ static int dolisten(int argc, char *argv[]) for (;;) { - /* Clear out the key state. */ - k_init(&k); - /* Wait for something to happen. */ FD_ZERO(&fdin); FD_SET(sk, &fdin); @@ -558,7 +819,7 @@ static int dolisten(int argc, char *argv[]) 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. */ @@ -570,182 +831,32 @@ static int dolisten(int argc, char *argv[]) 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); @@ -755,6 +866,7 @@ static int dolisten(int argc, char *argv[]) struct query { struct query *next; + const char *tag; octet *k; size_t sz; struct server *s; @@ -764,16 +876,151 @@ struct server { 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) @@ -781,11 +1028,13 @@ 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. */ @@ -813,6 +1062,20 @@ static struct query *qinit_net(const char *tag, const char *spec) 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"; @@ -824,14 +1087,6 @@ static struct query *qinit_net(const char *tag, const char *spec) 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; @@ -850,6 +1105,9 @@ static struct query *qinit_net(const char *tag, const char *spec) 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 `;'"); @@ -876,11 +1134,11 @@ static struct query *qinit_file(const char *tag, const char *file) 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); } -/* Reransmission and timeout parameters. */ +/* Retransmission and timeout parameters. */ #define TO_NEXT(t) (((t) + 2)*4/3) /* Timeout growth function */ #define TO_MAX 30 /* When to give up */ @@ -895,15 +1153,10 @@ static int doquery(int argc, char *argv[]) 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 @@ -943,8 +1196,7 @@ static int doquery(int argc, char *argv[]) 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; @@ -976,12 +1228,12 @@ static int doquery(int argc, char *argv[]) else if (errno == EINTR) continue; else { moan("error receiving reply: %s", strerror(errno)); - goto again; + continue; } } - /* Wee whether this corresponds to any of our servers. Don't just - * check the active servers, since this may be late replies caused by + /* See whether this corresponds to any of our servers. Don't just + * check the active servers, since this may be late a reply caused by * retransmissions or similar. */ for (q = qq; q; q = q->next) { @@ -993,89 +1245,27 @@ static int doquery(int argc, char *argv[]) } 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)); } } } @@ -1105,13 +1295,13 @@ static int doquery(int argc, char *argv[]) static void usage(FILE *fp) { pquis(fp, "Usage: \n\ - $ [-OPTS] LABEL {ADDR:PORT | FILE} ...\n\ + $ [-OPTS] LABEL {ADDR:PORT[=TAG][#HASH];... | FILE} ...\n\ $ [-OPTS] -l [ADDR:]PORT\n\ "); } static void version(FILE *fp) - { pquis(fp, "$, version " VERSION); } + { pquis(fp, "$, version " VERSION "\n"); } static void help(FILE *fp) {