+static time_t now;
+
+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);
+}
+