chiark / gitweb /
server/{keymgmt.c,tripe.h}: Unify public and private key handling.
authorMark Wooding <mdw@distorted.org.uk>
Wed, 25 Jan 2012 10:30:11 +0000 (10:30 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Mon, 7 May 2012 14:35:10 +0000 (15:35 +0100)
We introduce a new `kdata' object which represents a particular instance
of a key and remembers all sorts of useful things about it including its
name, associated algorithms, and so on.  It can represent either a
public or private key, since the only difference is whether it has a
private scalar.  These kdata objects are reference counted and exposed
to callers, though the old interface is, for now, retained for
compatibility.

Internally, the private/public split is pushed quite far down using a
`keyhalf' structure to keep track of the differences between the two
kinds.

There's a layer of caching and continuity management.  Each key tag maps
to a `knode' object which represents all the versions (i.e., different
kdata representations) of that key, and allows you to find the current
version.  A kdata object has a reference back to its home knode.

Since private keys are handled (mostly) in the same way as public keys,
there can also be multiple versions of private keys, and multiple
different private keys.  The code maintains a `preferred' private key,
and exports a pointer to its kdata as `master'.

As mentioned, there are some compatibility interfaces in place at the
moment, which maintain the old global key information and prevent
inconsistencies.  For example, currently the `master' kdata is only
allowed to advance if its group matches the previous one.  The plan is
to remove this global information, and allow peers to use different
versions of different private keys; peers will be responsible for
ensuring the consistency of the keys they use, and will be able to do
this without interfering with each other.

server/keymgmt.c
server/tripe.h

index c7d0c6b..69ea74a 100644 (file)
 
 #include "tripe.h"
 
-/*----- Global variables --------------------------------------------------*/
-
-group *gg;
-mp *kpriv;
-ge *kpub;
-algswitch algs;
-size_t indexsz;
-
-/*----- Static variables --------------------------------------------------*/
-
-static key_file *kf_pub;
-static const char *kr_priv, *kr_pub, *tag_priv;
-static fwatch w_priv, w_pub;
-
 /*----- Key groups --------------------------------------------------------*/
 
+/* The key-loading functions here must fill in the kdata slot @g@ and
+ * either @kpriv@ or @kpub@ as appropriate.  The caller will take care of
+ * determining @kpub@ given a private key, and of ensuring that @kpriv@ is
+ * null for a public key.
+ */
+
 typedef struct kgops {
   const char *ty;
-  int (*loadpriv)(key_data *, group **, mp **, dstr *, dstr *);
-  int (*loadpub)(key_data *, group **, ge **, dstr *, dstr *);
+  int (*loadpriv)(key_data *, kdata *, dstr *, dstr *);
+  int (*loadpub)(key_data *, kdata *, dstr *, dstr *);
 } kgops;
 
 /* --- Diffie-Hellman --- */
 
-static int kgdh_priv(key_data *kd, group **g, mp **x, dstr *t, dstr *e)
+static int kgdh_priv(key_data *d, kdata *kd, dstr *t, dstr *e)
 {
   key_packstruct kps[DH_PRIVFETCHSZ];
   key_packdef *kp;
@@ -60,12 +52,12 @@ static int kgdh_priv(key_data *kd, group **g, mp **x, dstr *t, dstr *e)
   int rc;
 
   kp = key_fetchinit(dh_privfetch, kps, &dp);
-  if ((rc = key_unpack(kp, kd, t)) != 0) {
+  if ((rc = key_unpack(kp, d, t)) != 0) {
     a_format(e, "unpack-failed", "%s", key_strerror(rc), A_END);
     goto fail_0;
   }
-  *g = group_prime(&dp.dp);
-  *x = MP_COPY(dp.x);
+  kd->g = group_prime(&dp.dp);
+  kd->kpriv = MP_COPY(dp.x);
   rc = 0;
   goto done;
 fail_0:
@@ -75,7 +67,7 @@ done:
   return (rc);
 }
 
-static int kgdh_pub(key_data *kd, group **g, ge **p, dstr *t, dstr *e)
+static int kgdh_pub(key_data *d, kdata *kd, dstr *t, dstr *e)
 {
   key_packstruct kps[DH_PUBFETCHSZ];
   key_packdef *kp;
@@ -83,21 +75,21 @@ static int kgdh_pub(key_data *kd, group **g, ge **p, dstr *t, dstr *e)
   int rc;
 
   kp = key_fetchinit(dh_pubfetch, kps, &dp);
-  if ((rc = key_unpack(kp, kd, t)) != 0) {
+  if ((rc = key_unpack(kp, d, t)) != 0) {
     a_format(e, "unpack-failed", "%s", key_strerror(rc), A_END);
     goto fail_0;
   }
-  *g = group_prime(&dp.dp);
-  *p = G_CREATE(*g);
-  if (G_FROMINT(*g, *p, dp.y)) {
+  kd->g = group_prime(&dp.dp);
+  kd->kpub = G_CREATE(kd->g);
+  if (G_FROMINT(kd->g, kd->kpub, dp.y)) {
     a_format(e, "bad-public-vector", A_END);
     goto fail_1;
   }
   rc = 0;
   goto done;
 fail_1:
-  G_DESTROY(*g, *p);
-  G_DESTROYGROUP(*g);
+  G_DESTROY(kd->g, kd->kpub);
+  G_DESTROYGROUP(kd->g);
 fail_0:
   rc = -1;
 done:
@@ -109,7 +101,7 @@ static const kgops kgdh_ops = { "dh", kgdh_priv, kgdh_pub };
 
 /* --- Elliptic curve --- */
 
-static int kgec_priv(key_data *kd, group **g, mp **x, dstr *t, dstr *e)
+static int kgec_priv(key_data *d, kdata *kd, dstr *t, dstr *e)
 {
   key_packstruct kps[EC_PRIVFETCHSZ];
   key_packdef *kp;
@@ -119,7 +111,7 @@ static int kgec_priv(key_data *kd, group **g, mp **x, dstr *t, dstr *e)
   int rc;
 
   kp = key_fetchinit(ec_privfetch, kps, &ep);
-  if ((rc = key_unpack(kp, kd, t)) != 0) {
+  if ((rc = key_unpack(kp, d, t)) != 0) {
     a_format(e, "unpack-failed", "%s", key_strerror(rc), A_END);
     goto fail_0;
   }
@@ -127,8 +119,8 @@ static int kgec_priv(key_data *kd, group **g, mp **x, dstr *t, dstr *e)
     a_format(e, "decode-failed", "%s", err, A_END);
     goto fail_0;
   }
-  *g = group_ec(&ei);
-  *x = MP_COPY(ep.x);
+  kd->g = group_ec(&ei);
+  kd->kpriv = MP_COPY(ep.x);
   rc = 0;
   goto done;
 fail_0:
@@ -138,7 +130,7 @@ done:
   return (rc);
 }
 
-static int kgec_pub(key_data *kd, group **g, ge **p, dstr *t, dstr *e)
+static int kgec_pub(key_data *d, kdata *kd, dstr *t, dstr *e)
 {
   key_packstruct kps[EC_PUBFETCHSZ];
   key_packdef *kp;
@@ -148,7 +140,7 @@ static int kgec_pub(key_data *kd, group **g, ge **p, dstr *t, dstr *e)
   int rc;
 
   kp = key_fetchinit(ec_pubfetch, kps, &ep);
-  if ((rc = key_unpack(kp, kd, t)) != 0) {
+  if ((rc = key_unpack(kp, d, t)) != 0) {
     a_format(e, "unpack-failed", "%s", key_strerror(rc), A_END);
     goto fail_0;
   }
@@ -156,17 +148,17 @@ static int kgec_pub(key_data *kd, group **g, ge **p, dstr *t, dstr *e)
     a_format(e, "decode-failed", "%s", err, A_END);
     goto fail_0;
   }
-  *g = group_ec(&ei);
-  *p = G_CREATE(*g);
-  if (G_FROMEC(*g, *p, &ep.p)) {
+  kd->g = group_ec(&ei);
+  kd->kpub = G_CREATE(kd->g);
+  if (G_FROMEC(kd->g, kd->kpub, &ep.p)) {
     a_format(e, "bad-public-vector", A_END);
     goto fail_1;
   }
   rc = 0;
   goto done;
 fail_1:
-  G_DESTROY(*g, *p);
-  G_DESTROYGROUP(*g);
+  G_DESTROY(kd->g, kd->kpub);
+  G_DESTROYGROUP(kd->g);
 fail_0:
   rc = -1;
 done:
@@ -326,23 +318,106 @@ static int algs_check(algswitch *a, dstr *e, const group *g)
   return (0);
 }
 
-/* --- @algs_samep@ --- *
+/* --- @km_samealgsp@ --- *
  *
- * Arguments:  @const algswitch *a, *aa@ = two algorithm selections
+ * Arguments:  @const kdata *kdx, *kdy@ = two key data objects
  *
- * Returns:    Nonzero if the two selections are the same.
+ * Returns:    Nonzero if their two algorithm selections are the same.
  *
  * Use:                Checks sameness of algorithm selections: used to ensure that
  *             peers are using sensible algorithms.
  */
 
-static int algs_samep(const algswitch *a, const algswitch *aa)
+int km_samealgsp(const kdata *kdx, const kdata *kdy)
 {
-  return (a->c == aa->c && a->mgf == aa->mgf && a->h == aa->h &&
+  const algswitch *a = &kdx->algs, *aa = &kdy->algs;
+
+  return (group_samep(kdx->g, kdy->g) && a->c == aa->c &&
+         a->mgf == aa->mgf && a->h == aa->h &&
          a->m == aa->m && a->tagsz == aa->tagsz);
 }
 
-/*----- Main code ---------------------------------------------------------*/
+/*----- Key data and key nodes --------------------------------------------*/
+
+typedef struct keyhalf {
+  const char *kind;
+  int (*load)(const kgops *, key_data *, kdata *, dstr *, dstr *);
+  const char *kr;
+  key_file *kf;
+  fwatch w;
+  sym_table tab;
+} keyhalf;
+
+/* --- @kh_loadpub@, @kh_loadpriv@ --- *
+ *
+ * Arguments:  @const kgops *ko@ = key-group operations for key type
+ *             @key_data *d@ = key data object as stored in keyring
+ *             @kdata *kd@ = our key-data object to fill in
+ *             @dstr *t@ = the key tag name
+ *             @dstr *e@ = a string to write error tokens to
+ *
+ * Returns:    Zero on success, @-1@ on error.
+ *
+ * Use:                These functions handle the main difference between public and
+ *             private key halves.  They are responsible for setting @g@,
+ *             @kpriv@ and @kpub@ appropriately in all keys, handling the
+ *             mismatch between the largely half-indifferent calling code
+ *             and the group-specific loading functions.
+ *
+ *             The function @kh_loadpriv@ is also responsible for checking
+ *             the group for goodness.  We don't bother checking public
+ *             keys, because each public key we actually end up using must
+ *             share a group with a private key which we'll already have
+ *             checked.
+ */
+
+static int kh_loadpub(const kgops *ko, key_data *d, kdata *kd,
+                     dstr *t, dstr *e)
+{
+  int rc;
+
+  if ((rc = ko->loadpub(d, kd, t, e)) != 0)
+    goto fail_0;
+  if (group_check(kd->g, kd->kpub)) {
+    a_format(e, "bad-public-group-element");
+    goto fail_1;
+  }
+  kd->kpriv = 0;
+  return (0);
+
+fail_1:
+  G_DESTROY(kd->g, kd->kpub);
+  G_DESTROYGROUP(kd->g);
+fail_0:
+  return (-1);
+}
+
+static int kh_loadpriv(const kgops *ko, key_data *d, kdata *kd,
+                      dstr *t, dstr *e)
+{
+  int rc;
+  const char *err;
+
+  if ((rc = ko->loadpriv(d, kd, t, e)) != 0)
+    goto fail_0;
+  if ((err = G_CHECK(kd->g, &rand_global)) != 0) {
+    a_format(e, "bad-group", "%s", err, A_END);
+    goto fail_1;
+  }
+  kd->kpub = G_CREATE(kd->g);
+  G_EXP(kd->g, kd->kpub, kd->g->g, kd->kpriv);
+  return (0);
+
+fail_1:
+  mp_drop(kd->kpriv);
+  G_DESTROYGROUP(kd->g);
+fail_0:
+  return (-1);
+}
+
+static struct keyhalf
+  priv = { "private", kh_loadpriv },
+  pub = { "public", kh_loadpub };
 
 /* --- @keymoan@ --- *
  *
@@ -358,271 +433,356 @@ static int algs_samep(const algswitch *a, const algswitch *aa)
 
 static void keymoan(const char *file, int line, const char *msg, void *p)
 {
-  const char *kind = p;
+  keyhalf *kh = p;
 
   if (!line) {
-    a_warn("KEYMGMT", "%s-keyring", kind, "%s", file,
+    a_warn("KEYMGMT", "%s-keyring", kh->kind, "%s", file,
           "io-error", "?ERRNO", A_END);
   } else {
-    a_warn("KEYMGMT", "%s-keyring", kind, "%s", file, "line", "%d", line,
+    a_warn("KEYMGMT", "%s-keyring", kh->kind, "%s", file, "line", "%d", line,
           "%s", msg, A_END);
   }
 }
 
-/* --- @keykg@ --- *
+/* --- @kh_reopen@ --- *
  *
- * Arguments:  @key_file *kf@ = pointer to key file
- *             @key *k@ = pointer to key
- *             @const char **tyr@ = where to put the type string
+ * Arguments:  @keyhalf *kh@ = pointer to keyhalf structure
  *
- * Returns:    Pointer to indicated key-group options, or null.
+ * Returns:    Zero on success, @-1@ on error.
  *
- * Use:                Looks up a key's group indicator and tries to find a matching
- *             table entry.
+ * Use:                Reopens the key file for the appropriate key half.  If this
+ *             fails, everything is left as it was; if it succeeds, then the
+ *             old file is closed (if it was non-null) and the new one put
+ *             in its place.
  */
 
-static const kgops *keykg(key_file *kf, key *k, const char **tyr)
+static int kh_reopen(keyhalf *kh)
 {
-  const char *ty;
-  const kgops **ko;
+  key_file *kf = CREATE(key_file);
 
-  /* --- Look up the key type in the table --- *
-   *
-   * There are several places to look for this.  The most obvious is the
-   * `kx-group' key attribute.  But there's also the key type itself.
-   */
+  if (key_open(kf, kh->kr, KOPEN_READ, keymoan, kh)) {
+    a_warn("KEYMGMT", "%s-keyring", kh->kind, "%s", kh->kr,
+          "read-error", "?ERRNO", A_END);
+    DESTROY(kf);
+    return (-1);
+  } else {
+    if (kh->kf) {
+      key_close(kh->kf);
+      DESTROY(kh->kf);
+    }
+    kh->kf = kf;
+    return (0);
+  }
+}
 
-  ty = key_getattr(kf, k, "kx-group");
-  if (!ty && strncmp(k->type, "tripe-", 6) == 0) ty = k->type + 6;
-  if (!ty) ty = "dh";
-  if (tyr) *tyr = ty;
+/* --- @kh_init@ --- *
+ *
+ * Arguments:  @keyhalf *kh@ = pointer to keyhalf structure to set up
+ *             @const char *kr@ = name of the keyring file
+ *
+ * Returns:    ---
+ *
+ * Use:                Initialize a keyhalf structure, maintaining the private or
+ *             public keys.  Intended to be called during initialization:
+ *             exits if there's some kind of problem.
+ */
 
-  for (ko = kgtab; *ko; ko++) {
-    if (strcmp((*ko)->ty, ty) == 0)
-      return (*ko);
-  }
-  return (0);
+static void kh_init(keyhalf *kh, const char *kr)
+{
+  kh->kr = kr;
+  fwatch_init(&kh->w, kr);
+  sym_create(&kh->tab);
+  kh->kf = 0;
+
+  if (kh_reopen(kh))
+    die(EXIT_FAILURE, "failed to load %s keyring `%s'", kh->kind, kr);
 }
 
-/* --- @loadpriv@ --- *
+/* --- @kh_load@ --- *
  *
- * Arguments:  @dstr *d@ = string to write errors in
+ * Arguments:  @keyhalf *kh@ = pointer to keyhalf
+ *             @const char *tag@ = key tag to be loaded
+ *             @int complainp@ = whether to complain about missing keys
  *
- * Returns:    Zero if OK, nonzero on error.
+ * Returns:    Pointer to a @kdata@ structure if successful, or null on
+ *             failure.
  *
- * Use:                Loads the private key from its keyfile.
+ * Use:                Attempts to load a key from the current key file.  This
+ *             function always reads data from the file: it's used when
+ *             there's a cache miss from @kh_find@, and when refreshing the
+ *             known keys in @kh_refresh@.  The returned kdata has a
+ *             reference count of exactly 1, and has no home knode.
  */
 
-static int loadpriv(void)
+static kdata *kh_load(keyhalf *kh, const char *tag, int complainp)
 {
-  key_file kf;
-  key *k;
-  key_data **kd;
   dstr t = DSTR_INIT;
   dstr e = DSTR_INIT;
-  group *g = 0;
-  mp *x = 0;
-  int rc = -1;
-  const kgops *ko;
-  const char *err, *tag, *ty;
-  algswitch a;
-
-  /* --- Open the private key file --- */
-
-  if (key_open(&kf, kr_priv, KOPEN_READ, keymoan, "private"))
-    goto done_0;
+  key *k;
+  key_data **d;
+  kdata *kd;
+  const char *ty;
+  const kgops **ko;
 
-  /* --- Find the private key --- */
+  /* --- Find the key and grab its tag --- */
 
-  if (tag_priv ?
-      key_qtag(&kf, tag = tag_priv, &t, &k, &kd) :
-      key_qtag(&kf, tag = "tripe", &t, &k, &kd) &&
-       key_qtag(&kf, tag = "tripe-dh", &t, &k, &kd)) {
-    a_warn("KEYMGMT", "private-keyring", "%s", kr_priv,
-          "key-not-found", "%s", tag, A_END);
-    goto done_1;
+  if (key_qtag(kh->kf, tag, &t, &k, &d)) {
+    if (complainp) {
+      a_warn("KEYMGMT", "%s-keyring", kh->kind, "%s", kh->kr,
+            "key-not-found", "%s", tag, A_END);
+    }
+    goto fail_0;
   }
 
-  /* --- Look up the key type in the table --- */
-
-  if ((ko = keykg(&kf, k, &ty)) == 0) {
-    a_warn("KEYMGMT", "private-keyring",
-          "%s", kr_priv, "key", "%s", t.buf,
-          "unknown-group-type", "%s", ty, A_END);
-    goto done_1;
-  }
+  /* --- Find the key's group type and the appropriate operations --- *
+   *
+   * There are several places to look for the key type.  The most obvious is
+   * the `kx-group' key attribute.  But there's also the key type itself, for
+   * compatibility reasons.
+   */
 
-  /* --- Load the key --- */
+  ty = key_getattr(kh->kf, k, "kx-group");
+  if (!ty && strncmp(k->type, "tripe-", 6) == 0) ty = k->type + 6;
+  if (!ty) ty = "dh";
 
-  if (ko->loadpriv(*kd, &g, &x, &t, &e)) {
-    a_warn("KEYMGMT", "private-keyring",
-          "%s", kr_priv, "key", "%s", t.buf,
+  for (ko = kgtab; *ko; ko++)
+    if (strcmp((*ko)->ty, ty) == 0) goto foundko;
+  a_warn("KEYMGMT", "%s-keyring", kh->kind,
+        "%s", kh->kr, "key", "%s", t.buf,
+        "unknown-group-type", "%s", ty, A_END);
+  goto fail_0;
+
+foundko:
+  kd = CREATE(kdata);
+  if (kh->load(*ko, *d, kd, &t, &e)) {
+    a_warn("KEYMGMT", "%s-keyring", kh->kind,
+          "%s", kh->kr, "key" "%s", t.buf,
           "*%s", e.buf, A_END);
-    goto done_1;
-  }
-
-  /* --- Check that the key is sensible --- */
-
-  if ((err = G_CHECK(g, &rand_global)) != 0) {
-    a_warn("KEYMGMT", "private-keyring",
-          "%s", kr_priv, "key", "%s", t.buf,
-          "bad-group", "%s", err, A_END);
-    goto done_1;
+    goto fail_1;
   }
 
-  /* --- Collect the algorithms --- */
-
-  if (algs_get(&a, &e, &kf, k) ||
-      algs_check(&a, &e, g)) {
-    a_warn("KEYMGMT", "private-keyring",
-          "%s", kr_priv, "key", "%s", t.buf,
+  if (algs_get(&kd->algs, &e, kh->kf, k) ||
+      (kd->kpriv && algs_check(&kd->algs, &e, kd->g))) {
+    a_warn("KEYMGMT", "%s-keyring", kh->kind,
+          "%s", kh->kr, "key", "%s", t.buf,
           "*%s", e.buf, A_END);
-    goto done_1;
-  }
-
-  /* --- Good, we're happy --- *
-   *
-   * Dodginess!         We change the group over here, but don't free any old group
-   * elements.  This assumes that the new group is basically the same as the
-   * old one, and will happily adopt the existing elements.  If it isn't,
-   * then we lose badly.  Check this, then.
-   */
-
-  if (gg) {
-    if (!group_samep(g, gg)) {
-      a_warn("KEYMGMT", "private-keyring",
-            "%s", kr_priv, "key", "%s", t.buf,
-            "changed-group", A_END);
-      goto done_1;
-    }
-    G_DESTROYGROUP(gg);
+    goto fail_2;
   }
-  if (kpriv)
-    mp_drop(kpriv);
 
-  if (kpub)
-    G_DESTROY(g, kpub);
-  kpub = G_CREATE(g);
-  G_EXP(g, kpub, g->g, x);
-  indexsz = mp_octets(g->r);
-
-  /* --- Dump out the group --- */
+  kd->tag = xstrdup(t.buf);
+  kd->indexsz = mp_octets(kd->g->r);
+  kd->ref = 1;
+  kd->kn = 0;
+  kd->t_exp = k->exp;
 
   IF_TRACING(T_KEYMGMT, {
-    trace(T_KEYMGMT, "keymgmt: extracted private key `%s'", t.buf);
+    trace(T_KEYMGMT, "keymgmt: loaded %s key `%s'", kh->kind, t.buf);
     IF_TRACING(T_CRYPTO, {
-      trace(T_CRYPTO, "crypto: r = %s", mpstr(g->r));
-      trace(T_CRYPTO, "crypto: h = %s", mpstr(g->h));
-      trace(T_CRYPTO, "crypto: x = %s", mpstr(x));
-      trace(T_CRYPTO, "crypto: cipher = %s", a.c->name);
-      trace(T_CRYPTO, "crypto: mgf = %s", a.mgf->name);
-      trace(T_CRYPTO, "crypto: hash = %s", a.h->name);
+      trace(T_CRYPTO, "crypto: r = %s", mpstr(kd->g->r));
+      trace(T_CRYPTO, "crypto: h = %s", mpstr(kd->g->h));
+      if (kd->kpriv)
+       trace(T_CRYPTO, "crypto: x = %s", mpstr(kd->kpriv));
+      trace(T_CRYPTO, "crypto: cipher = %s", kd->algs.c->name);
+      trace(T_CRYPTO, "crypto: mgf = %s", kd->algs.mgf->name);
+      trace(T_CRYPTO, "crypto: hash = %s", kd->algs.h->name);
       trace(T_CRYPTO, "crypto: mac = %s/%lu",
-           a.m->name, (unsigned long)a.tagsz * 8);
+           kd->algs.m->name, (unsigned long)kd->algs.tagsz * 8);
     })
   })
 
-  /* --- Success! --- */
-
-  gg = g; g = 0;
-  algs = a;
-  kpriv = x; x = 0;
-  rc = 0;
-
-  /* --- Tidy up --- */
+  goto done;
 
-done_1:
-  key_close(&kf);
-done_0:
+fail_2:
+  if (kd->kpriv) mp_drop(kd->kpriv);
+  G_DESTROY(kd->g, kd->kpub);
+  G_DESTROYGROUP(kd->g);
+fail_1:
+  DESTROY(kd);
+fail_0:
+  kd = 0;
+done:
   dstr_destroy(&t);
   dstr_destroy(&e);
-  if (x) mp_drop(x);
-  if (g) G_DESTROYGROUP(g);
-  return (rc);
+  return (kd);
 }
 
-/* --- @loadpub@ --- *
+/* --- @kh_find@ --- *
  *
- * Arguments:  @dstr *d@ = string to write errors to
+ * Arguments:  @keyhalf *kh@ = pointer to the keyhalf
+ *             @const char *tag@ = key to be obtained
+ *             @int complainp@ = whether to complain about missing keys
  *
- * Returns:    Zero if OK, nonzero on error.
+ * Returns:    A pointer to the kdata, or null on error.
  *
- * Use:                Reloads the public keyring.
+ * Use:                Obtains kdata, maybe from the cache.  This won't update a
+ *             stale cache entry, though @kh_refresh@ ought to have done
+ *             that already.  The returned kdata object may be shared with
+ *             other users.  (One of this function's responsibilities, over
+ *             @kh_load@, is to set the home knode of a freshly loaded
+ *             kdata.)
  */
 
-static int loadpub(void)
+static kdata *kh_find(keyhalf *kh, const char *tag, int complainp)
 {
-  key_file *kf = CREATE(key_file);
+  knode *kn;
+  kdata *kd;
+  unsigned f;
 
-  if (key_open(kf, kr_pub, KOPEN_READ, keymoan, "public")) {
-    DESTROY(kf);
-    return (-1);
+  kn = sym_find(&kh->tab, tag, -1, sizeof(knode), &f);
+
+  if (f) {
+    if (kn->f & KNF_BROKEN) {
+      T( if (complainp)
+          trace(T_KEYMGMT, "keymgmt: key `%s' marked as broken", tag); )
+      return (0);
+    }
+
+    kd = kn->kd;
+    if (kd) kd->ref++;
+    T( trace(T_KEYMGMT, "keymgmt: %scache hit for key `%s'",
+            kd ? "" : "negative ", tag); )
+    return (kd);
+  } else {
+    kd = kh_load(kh, tag, complainp);
+    kn->kd = kd;
+    kn->kh = kh;
+    kn->f = 0;
+    if (!kd)
+      kn->f |= KNF_BROKEN;
+    else {
+      kd->kn = kn;
+      kd->ref++;
+    }
+    return (kd);
   }
-  kf_pub = kf;
-  T( trace(T_KEYMGMT, "keymgmt: loaded public keyring `%s'", kr_pub); )
-  return (0);
 }
 
-/* --- @km_reload@ --- *
+/* --- @kh_refresh@ --- *
  *
- * Arguments:  ---
+ * Arguments:  @keyhalf *kh@ = pointer to the keyhalf
  *
- * Returns:    Zero if OK, nonzero to force reloading of keys.
+ * Returns:    Zero if nothing needs to be done; nonzero if peers should
+ *             refresh their keys.
  *
- * Use:                Checks the keyrings to see if they need reloading.
+ * Use:                Refreshes cached keys from files.
+ *
+ *             Each active knode is examined to see if a new key is
+ *             available: the return value is nonzero if any new keys are.
+ *             A key is considered new if its algorithms, public key, or
+ *             expiry time are/is different.
+ *
+ *             Stub knodes (with no kdata attached) are removed, so that a
+ *             later retry can succeed if the file has been fixed.  (This
+ *             doesn't count as a change, since no peers should be relying
+ *             on a nonexistent key.)
  */
 
-int km_reload(void)
+static int kh_refresh(keyhalf *kh)
 {
-  key_file *kf;
-  int reload = 0;
+  knode *kn;
+  kdata *kd;
+  sym_iter i;
+  int changep = 0;
+
+  if (!fwatch_update(&kh->w, kh->kr) || kh_reopen(kh))
+    return (0);
+
+  T( trace(T_KEYMGMT, "keymgmt: rescan %s keyring `%s'", kh->kind, kh->kr); )
+  for (sym_mkiter(&i, &kh->tab); (kn = sym_next(&i)) != 0; ) {
+    if (!kn->kd) {
+      T( trace(T_KEYMGMT, "keymgmt: discard stub entry for key `%s'",
+              SYM_NAME(kn)); )
+      sym_remove(&kh->tab, kn);
+      continue;
+    }
+    if ((kd = kh_load(kh, SYM_NAME(kn), 1)) == 0) {
+      if (!(kn->f & KNF_BROKEN)) {
+       T( trace(T_KEYMGMT, "keymgmt: failed to load new key `%s': "
+                "marking it as broken",
+                SYM_NAME(kn)); )
+       kn->f |= KNF_BROKEN;
+      }
+      continue;
+    }
+    kn->f &= ~KNF_BROKEN;
+    if (kd->t_exp == kn->kd->t_exp &&
+       km_samealgsp(kd, kn->kd) &&
+       G_EQ(kd->g, kd->kpub, kn->kd->kpub)) {
+      T( trace(T_KEYMGMT, "keymgmt: key `%s' unchanged", SYM_NAME(kn)); )
+      continue;
+    }
+    T( trace(T_KEYMGMT, "keymgmt: loaded new version of key `%s'",
+            SYM_NAME(kn)); )
+    km_unref(kn->kd);
+    kd->kn = kn;
+    kn->kd = kd;
+    changep = 1;
+  }
 
-  /* --- Check the private key first --- */
+  return (changep);
+}
 
-  if (fwatch_update(&w_priv, kr_priv)) {
-    T( trace(T_KEYMGMT, "keymgmt: private keyring updated: reloading..."); )
-    if (!loadpriv())
-      reload = 1;
-  }
+/*----- Main code ---------------------------------------------------------*/
 
-  /* --- Now check the public keys --- */
+const char *tag_priv;
+kdata *master;
 
-  if (fwatch_update(&w_pub, kr_pub)) {
-    T( trace(T_KEYMGMT, "keymgmt: public keyring updated: reloading..."); )
-    kf = kf_pub;
-    if (!loadpub()) {
-      reload = 1;
-      key_close(kf);
-      DESTROY(kf);
+group *gg;
+mp *kpriv;
+ge *kpub;
+algswitch algs;
+size_t indexsz;
+
+/* --- @update_compat@ --- *
+ *
+ * Arguments:  @kdata *kd@ = proposed new master key
+ *
+ * Returns:    Zero on success, nonzero to refuse the replacement.
+ *
+ * Use:                Updates the exported private key variables for compatibility.
+ */
+
+static int update_compat(kdata *kd)
+{
+  if (gg) {
+    if (!group_samep(kd->g, gg)) {
+      a_warn("KEYMGMT", "private-keyring",
+            "%s", kd->kn->kh->kr, "key", "%s", kd->tag,
+            "changed-group", A_END);
+      return (-1);
     }
+    G_DESTROYGROUP(gg);
   }
+  gg = kd->g;
 
-  /* --- Done --- */
+  if (kpriv) mp_drop(kpriv);
+  kpriv = MP_COPY(kd->kpriv);
+  if (kpub) G_DESTROY(gg, kpub);
+  kpub = G_CREATE(gg);
+  G_COPY(gg, kpub, kd->kpub);
 
-  return (reload);
+  algs = kd->algs;
+  indexsz = kd->indexsz;
+
+  return (0);
 }
 
 /* --- @km_init@ --- *
  *
- * Arguments:  @const char *priv@ = private keyring file
- *             @const char *pub@ = public keyring file
- *             @const char *tag@ = tag to load
+ * Arguments:  @const char *privkr@ = private keyring file
+ *             @const char *pubkr@ = public keyring file
+ *             @const char *ptag@ = default private-key tag
  *
  * Returns:    ---
  *
- * Use:                Initializes, and loads the private key.
+ * Use:                Initializes the key-management machinery, loading the
+ *             keyrings and so on.
  */
 
-void km_init(const char *priv, const char *pub, const char *tag)
+void km_init(const char *privkr, const char *pubkr, const char *ptag)
 {
   const gchash *const *hh;
 
-  kr_priv = priv;
-  kr_pub = pub;
-  tag_priv = tag;
-  fwatch_init(&w_priv, kr_priv);
-  fwatch_init(&w_pub, kr_pub);
-
   for (hh = ghashtab; *hh; hh++) {
     if ((*hh)->hashsz > MAXHASHSZ) {
       die(EXIT_FAILURE, "INTERNAL ERROR: %s hash length %lu > MAXHASHSZ %d",
@@ -630,118 +790,132 @@ void km_init(const char *priv, const char *pub, const char *tag)
     }
   }
 
-  if (loadpriv() || loadpub())
-    exit(EXIT_FAILURE);
+  kh_init(&priv, privkr);
+  kh_init(&pub, pubkr);
+
+  tag_priv = ptag;
+  if ((master = km_findpriv(ptag)) == 0) exit(EXIT_FAILURE);
+
+  if (update_compat(master)) exit(EXIT_FAILURE);
 }
 
-/* --- @km_getpubkey@ --- *
+/* --- @km_reload@ --- *
  *
- * Arguments:  @const char *tag@ = public key tag to load
- *             @ge *kpub@ = where to put the public key
- *             @time_t *t_exp@ = where to put the expiry time
+ * Arguments:  ---
  *
- * Returns:    Zero if OK, nonzero if it failed.
+ * Returns:    Zero if OK, nonzero to force reloading of keys.
  *
- * Use:                Fetches a public key from the keyring.
+ * Use:                Checks the keyrings to see if they need reloading.
  */
 
-int km_getpubkey(const char *tag, ge *kpub, time_t *t_exp)
+int km_reload(void)
 {
-  key *k;
-  key_data **kd;
-  dstr t = DSTR_INIT;
-  dstr e = DSTR_INIT;
-  const kgops *ko;
-  const char *ty;
-  group *g = 0;
-  ge *p = 0;
-  algswitch a;
-  int rc = -1;
+  int changep = 0;
+  kdata *kd;
+
+  if (kh_refresh(&priv)) {
+    changep = 1;
+    kd = master->kn->kd;
+    if (kd != master && !update_compat(kd)) {
+      km_unref(master);
+      km_ref(kd);
+      master = kd;
+    }
+  }
+  if (kh_refresh(&pub))
+    changep = 1;
+  return (changep);
+}
 
-  /* --- Find the key --- */
+/* --- @km_findpub@, @km_findpriv@ --- *
+ *
+ * Arguments:  @const char *tag@ = key tag to load
+ *
+ * Returns:    Pointer to the kdata object if successful, or null on error.
+ *
+ * Use:                Fetches a public or private key from the keyring.
+ */
 
-  if (key_qtag(kf_pub, tag, &t, &k, &kd)) {
-    a_warn("KEYMGMT", "public-keyring", "%s", kr_pub,
-          "key-not-found", "%s", tag, A_END);
-    goto done;
-  }
+kdata *km_findpub(const char *tag) { return (kh_find(&pub, tag, 1)); }
 
-  /* --- Look up the key type in the table --- */
+kdata *km_findpriv(const char *tag)
+{
+  kdata *kd;
 
-  if ((ko = keykg(kf_pub, k, &ty)) == 0) {
-    a_warn("KEYMGMT", "public-keyring",
-          "%s", kr_pub, "key", "%s", t.buf,
-          "unknown-group-type", "%s", ty, A_END);
-    goto done;
-  }
+  /* Unpleasantness for the sake of compatibility. */
+  if (!tag && (kd = kh_find(&priv, "tripe", 0)) != 0) return (kd);
+  else return (kh_find(&priv, tag ? tag : "tripe-dh", 1));
+}
 
-  /* --- Load the key --- */
+/* --- @km_tag@ --- *
+ *
+ * Arguments:  @kdata *kd@ - pointer to the kdata object
+ *
+ * Returns:    A pointer to the short tag by which the kdata was loaded.
+ */
 
-  if (ko->loadpub(*kd, &g, &p, &t, &e)) {
-    a_warn("KEYMGMT", "public-keyring",
-          "%s", kr_pub, "key", "%s", t.buf,
-          "*%s", e.buf, A_END);
-    goto done;
-  }
+const char *km_tag(kdata *kd) { return (SYM_NAME(kd->kn)); }
 
-  /* --- Ensure that the group is correct --- *
-   *
-   * Dodginess!         We assume that if this works, our global group is willing to
-   * adopt this public element.  Probably reasonable.
-   */
+/* --- @km_ref@ --- *
+ *
+ * Arguments:  @kdata *kd@ = pointer to the kdata object
+ *
+ * Returns:    ---
+ *
+ * Use:                Claim a new reference to a kdata object.
+ */
 
-  if (!group_samep(gg, g)) {
-    a_warn("KEYMGMT", "public-keyring",
-          "%s", kr_pub, "key", "%s", t.buf,
-          "*%s", e.buf, A_END);
-    goto done;
-  }
+void km_ref(kdata *kd) { kd->ref++; }
 
-  /* --- Check the public group element --- */
+/* --- @km_unref@ --- *
+ *
+ * Arguments:  @kdata *kd@ = pointer to the kdata object
+ *
+ * Returns:    ---
+ *
+ * Use:                Releases a reference to a kdata object.
+ */
 
-  if (group_check(gg, p)) {
-    a_warn("KEYMGMT", "public-keyring",
-          "%s", kr_pub, "key", "%s", t.buf,
-          "bad-public-group-element",
-          A_END);
-    goto done;
-  }
+void km_unref(kdata *kd)
+{
+  if (--kd->ref) return;
+  if (kd->kpriv) mp_drop(kd->kpriv);
+  G_DESTROY(kd->g, kd->kpub);
+  xfree(kd->tag);
+  G_DESTROYGROUP(kd->g);
+}
+
+/* --- @km_getpubkey@ --- *
+ *
+ * Arguments:  @const char *tag@ = public key tag to load
+ *             @ge *kpub@ = where to put the public key
+ *             @time_t *t_exp@ = where to put the expiry time
+ *
+ * Returns:    Zero if OK, nonzero if it failed.
+ *
+ * Use:                Fetches a public key from the keyring.  (Temporary
+ *             compatibility hack.)
+ */
 
-  /* --- Check the algorithms --- */
+int km_getpubkey(const char *tag, ge *kpub, time_t *t_exp)
+{
+  kdata *kd;
+  int rc = -1;
 
-  if (algs_get(&a, &e, kf_pub, k)) {
-    a_warn("KEYMGMT", "public-keyring",
-          "%s", kr_pub, "key", "%s", t.buf,
-          "*%s", e.buf, A_END);
-    goto done;
-  }
-  if (!algs_samep(&a, &algs)) {
+  if ((kd = km_findpub(tag)) == 0)
+    goto done_0;
+  if (!km_samealgsp(kd, master)) {
     a_warn("KEYMGMT", "public-keyring",
-          "%s", kr_pub, "key", "%s", t.buf,
+          "%s", kd->kn->kh->kr, "key", "%s", kd->tag,
           "algorithm-mismatch", A_END);
-    goto done;
+    goto done_1;
   }
-
-  /* --- Dump the public key --- */
-
-  IF_TRACING(T_KEYMGMT, {
-    trace(T_KEYMGMT, "keymgmt: extracted public key `%s'", t.buf);
-    trace(T_CRYPTO, "crypto: p = %s", gestr(gg, p));
-  })
-
-  /* --- OK, accept the public key --- */
-
-  *t_exp = k->exp;
-  G_COPY(gg, kpub, p);
+  G_COPY(gg, kpub, kd->kpub);
+  *t_exp = kd->t_exp;
   rc = 0;
-
-  /* --- Tidy up --- */
-
-done:
-  if (p) G_DESTROY(g, p);
-  if (g) G_DESTROYGROUP(g);
-  dstr_destroy(&t);
-  dstr_destroy(&e);
+done_1:
+  km_unref(kd);
+done_0:
   return (rc);
 }
 
index 2ffe978..217a88d 100644 (file)
@@ -155,6 +155,26 @@ typedef struct algswitch {
 
 extern algswitch algs;
 
+typedef struct kdata {
+  unsigned ref;                                /* Reference counter */
+  struct knode *kn;                    /* Pointer to cache entry */
+  char *tag;                           /* Full tag name of the key */
+  group *g;                            /* The group we work in */
+  size_t indexsz;                      /* Size of exponent for the group */
+  mp *kpriv;                           /* The private key (or null) */
+  ge *kpub;                            /* The public key */
+  time_t t_exp;                                /* Expiry time of the key */
+  algswitch algs;                      /* Collection of algorithms */
+} kdata;
+
+typedef struct knode {
+  sym_base _b;                         /* Symbol table intrusion */
+  unsigned f;                          /* Various flags */
+#define KNF_BROKEN 1u                  /*   Don't use this key any more */
+  struct keyhalf *kh;                  /* Pointer to the home keyhalf */
+  kdata *kd;                           /* Pointer to the key data */
+} knode;
+
 #define MAXHASHSZ 64                   /* Largest possible hash size */
 
 #define HASH_STRING(h, s) GH_HASH((h), (s), sizeof(s))
@@ -506,6 +526,8 @@ extern ge *kpub;                    /* Our public key */
 extern octet buf_i[PKBUFSZ], buf_o[PKBUFSZ], buf_t[PKBUFSZ], buf_u[PKBUFSZ];
 extern const tunnel_ops *tunnels[];    /* Table of tunnels (0-term) */
 extern const tunnel_ops *tun_default;  /* Default tunnel to use */
+extern kdata *master;                  /* Default private key */
+extern const char *tag_priv;           /* Default private key tag */
 
 #ifndef NTRACE
 extern const trace_opt tr_opts[];      /* Trace options array */
@@ -518,6 +540,21 @@ extern unsigned tr_flags;          /* Trace options flags */
 
 /*----- Key management ----------------------------------------------------*/
 
+/* --- @km_init@ --- *
+ *
+ * Arguments:  @const char *privkr@ = private keyring file
+ *             @const char *pubkr@ = public keyring file
+ *             @const char *ptag@ = default private-key tag
+ *
+ * Returns:    ---
+ *
+ * Use:                Initializes the key-management machinery, loading the
+ *             keyrings and so on.
+ */
+
+extern void km_init(const char */*privkr*/, const char */*pubkr*/,
+                   const char */*ptag*/);
+
 /* --- @km_reload@ --- *
  *
  * Arguments:  ---
@@ -529,19 +566,60 @@ extern unsigned tr_flags;         /* Trace options flags */
 
 extern int km_reload(void);
 
-/* --- @km_init@ --- *
+/* --- @km_findpub@, @km_findpriv@ --- *
+ *
+ * Arguments:  @const char *tag@ = key tag to load
+ *
+ * Returns:    Pointer to the kdata object if successful, or null on error.
+ *
+ * Use:                Fetches a public or private key from the keyring.
+ */
+
+extern kdata *km_findpub(const char */*tag*/);
+extern kdata *km_findpriv(const char */*tag*/);
+
+/* --- @km_samealgsp@ --- *
+ *
+ * Arguments:  @const kdata *kdx, *kdy@ = two key data objects
  *
- * Arguments:  @const char *kr_priv@ = private keyring file
- *             @const char *kr_pub@ = public keyring file
- *             @const char *tag@ = tag to load
+ * Returns:    Nonzero if their two algorithm selections are the same.
+ *
+ * Use:                Checks sameness of algorithm selections: used to ensure that
+ *             peers are using sensible algorithms.
+ */
+
+extern int km_samealgsp(const kdata */*kdx*/, const kdata */*kdy*/);
+
+/* --- @km_ref@ --- *
+ *
+ * Arguments:  @kdata *kd@ = pointer to the kdata object
  *
  * Returns:    ---
  *
- * Use:                Initializes, and loads the private key.
+ * Use:                Claim a new reference to a kdata object.
+ */
+
+extern void km_ref(kdata */*kd*/);
+
+/* --- @km_unref@ --- *
+ *
+ * Arguments:  @kdata *kd@ = pointer to the kdata object
+ *
+ * Returns:    ---
+ *
+ * Use:                Releases a reference to a kdata object.
+ */
+
+extern void km_unref(kdata */*kd*/);
+
+/* --- @km_tag@ --- *
+ *
+ * Arguments:  @kdata *kd@ - pointer to the kdata object
+ *
+ * Returns:    A pointer to the short tag by which the kdata was loaded.
  */
 
-extern void km_init(const char */*kr_priv*/, const char */*kr_pub*/,
-                   const char */*tag*/);
+extern const char *km_tag(kdata */*kd*/);
 
 /* --- @km_getpubkey@ --- *
  *
@@ -551,7 +629,8 @@ extern void km_init(const char */*kr_priv*/, const char */*kr_pub*/,
  *
  * Returns:    Zero if OK, nonzero if it failed.
  *
- * Use:                Fetches a public key from the keyring.
+ * Use:                Fetches a public key from the keyring.  (Temporary
+ *             compatibility hack.)
  */
 
 extern int km_getpubkey(const char */*tag*/, ge */*kpub*/,