chiark / gitweb /
server/: Replace the Diffie--Hellman group abstraction.
[tripe] / server / keymgmt.c
1 /* -*-c-*-
2  *
3  * Key loading and storing
4  *
5  * (c) 2001 Straylight/Edgeware
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This file is part of Trivial IP Encryption (TrIPE).
11  *
12  * TrIPE is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * TrIPE is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with TrIPE; if not, write to the Free Software Foundation,
24  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25  */
26
27 /*----- Header files ------------------------------------------------------*/
28
29 #include "tripe.h"
30
31 /*----- Algswitch stuff ---------------------------------------------------*/
32
33 /* --- @algs_get@ --- *
34  *
35  * Arguments:   @algswitch *a@ = where to put the algorithms
36  *              @dstr *e@ = where to write error tokens
37  *              @key_file *kf@ = key file
38  *              @key *k@ = key to inspect
39  *
40  * Returns:     Zero if OK; nonzero on error.
41  *
42  * Use:         Extracts an algorithm choice from a key.
43  */
44
45 static int algs_get(algswitch *a, dstr *e, key_file *kf, key *k)
46 {
47   const char *p;
48   const bulkops *bops;
49   dstr d = DSTR_INIT, dd = DSTR_INIT;
50   int rc = -1;
51
52   /* --- Hash function --- */
53
54   if ((p = key_getattr(kf, k, "hash")) == 0) p = "rmd160";
55   if ((a->h = ghash_byname(p)) == 0) {
56     a_format(e, "unknown-hash", "%s", p, A_END);
57     goto done;
58   }
59
60   /* --- Symmetric encryption for key derivation --- */
61
62   if ((p = key_getattr(kf, k, "mgf")) == 0) {
63     dstr_reset(&d);
64     dstr_putf(&d, "%s-mgf", a->h->name);
65     p = d.buf;
66   }
67   if ((a->mgf = gcipher_byname(p)) == 0) {
68     a_format(e, "unknown-mgf-cipher", "%s", p, A_END);
69     goto done;
70   }
71
72   /* --- Bulk crypto transform --- */
73
74   if ((p = key_getattr(kf, k, "bulk")) == 0) p = "v0";
75   for (bops = bulktab; bops->name && strcmp(p, bops->name) != 0; bops++);
76   if (!bops->name) {
77     a_format(e, "unknown-bulk-transform", "%s", p, A_END);
78     goto done;
79   }
80   if ((a->bulk = bops->getalgs(a, e, kf, k)) == 0) goto done;
81   a->bulk->ops = bops;
82
83   /* --- All done --- */
84
85   rc = 0;
86 done:
87   dstr_destroy(&d);
88   dstr_destroy(&dd);
89   return (rc);
90 }
91
92 /* --- @algs_check@ --- *
93  *
94  * Arguments:   @algswitch *a@ = a choice of algorithms
95  *              @dstr *e@ = where to write error tokens
96  *              @const dhgrp *grp@ = the group we're working in
97  *
98  * Returns:     Zero if OK; nonzero on error.
99  *
100  * Use:         Checks an algorithm choice for sensibleness.  This also
101  *              derives some useful information from the choices, and you
102  *              must call this before committing the algorithm selection
103  *              for use by @keyset@ functions.
104  */
105
106 static int algs_check(algswitch *a, dstr *e, const dhgrp *grp)
107 {
108   a->hashsz = a->h->hashsz;
109
110   if (keysz(a->hashsz, a->mgf->keysz) != a->hashsz) {
111     a_format(e, "mgf", "%s", a->mgf->name,
112              "restrictive-key-schedule",
113              A_END);
114     return (-1);
115   }
116
117   if (a->bulk->ops->checkalgs(a->bulk, a, e)) return (-1);
118
119   return (0);
120 }
121
122 /* --- @km_samealgsp@ --- *
123  *
124  * Arguments:   @const kdata *kdx, *kdy@ = two key data objects
125  *
126  * Returns:     Nonzero if their two algorithm selections are the same.
127  *
128  * Use:         Checks sameness of algorithm selections: used to ensure that
129  *              peers are using sensible algorithms.
130  */
131
132 int km_samealgsp(const kdata *kdx, const kdata *kdy)
133 {
134   const algswitch *a = &kdx->algs, *aa = &kdy->algs;
135
136   return (kdx->grp->ops == kdy->grp->ops &&
137           kdx->grp->ops->samegrpp(kdx->grp, kdy->grp) &&
138           a->mgf == aa->mgf && a->h == aa->h &&
139           a->bulk->ops == aa->bulk->ops &&
140           a->bulk->ops->samealgsp(a->bulk, aa->bulk));
141 }
142
143 /*----- Key data and key nodes --------------------------------------------*/
144
145 typedef struct keyhalf {
146   const char *kind;
147   int (*load)(key_file *, key *, key_data *,
148               const dhops *, kdata *, dstr *, dstr *);
149   const char *kr;
150   key_file *kf;
151   fwatch w;
152   sym_table tab;
153 } keyhalf;
154
155 /* --- @kh_loadpub@, @kh_loadpriv@ --- *
156  *
157  * Arguments:   @const dhops *dh@ = Diffie--Hellman operations for key type
158  *              @key_file *kf@ = key file from which the key was loaded
159  *              @key *k@ = the key object we're loading
160  *              @key_data *d@ = the key data to load
161  *              @kdata *kd@ = our key-data object to fill in
162  *              @dstr *t@ = the key tag name
163  *              @dstr *e@ = a string to write error tokens to
164  *
165  * Returns:     Zero on success, @-1@ on error.
166  *
167  * Use:         These functions handle the main difference between public and
168  *              private key halves.  They are responsible for setting @grp@,
169  *              @k@ and @K@ appropriately in all keys, handling the mismatch
170  *              between the largely half-indifferent calling code and the
171  *              group-specific loading functions.
172  *
173  *              The function @kh_loadpriv@ is also responsible for checking
174  *              the group for goodness.  We don't bother checking public
175  *              keys, because each public key we actually end up using must
176  *              share a group with a private key which we'll already have
177  *              checked.
178  */
179
180 static int kh_loadpub(key_file *kf, key *k, key_data *d,
181                       const dhops *dh, kdata *kd, dstr *t, dstr *e)
182 {
183   int rc;
184
185   if ((rc = dh->ldpub(kf, k, d, kd, t, e)) != 0)
186     goto fail_0;
187   kd->grp->ops = dh;
188   if (kd->grp->ops->checkge(kd->grp, kd->K)) {
189     a_format(e, "bad-public-group-element", A_END);
190     goto fail_1;
191   }
192   return (0);
193
194 fail_1:
195   kd->grp->ops->freege(kd->grp, kd->K);
196   kd->grp->ops->freegrp(kd->grp);
197 fail_0:
198   return (-1);
199 }
200
201 static int kh_loadpriv(key_file *kf, key *k, key_data *d,
202                        const dhops *dh, kdata *kd, dstr *t, dstr *e)
203 {
204   int rc;
205   const char *err;
206
207   if ((rc = dh->ldpriv(kf, k, d, kd, t, e)) != 0)
208     goto fail_0;
209   kd->grp->ops = dh;
210   if ((err = kd->grp->ops->checkgrp(kd->grp)) != 0) {
211     a_format(e, "bad-group", "%s", err, A_END);
212     goto fail_1;
213   }
214   return (0);
215
216 fail_1:
217   kd->grp->ops->freesc(kd->grp, kd->k);
218   kd->grp->ops->freege(kd->grp, kd->K);
219   kd->grp->ops->freegrp(kd->grp);
220 fail_0:
221   return (-1);
222 }
223
224 static struct keyhalf
225   priv = { "private", kh_loadpriv },
226   pub = { "public", kh_loadpub };
227
228 /* --- @keymoan@ --- *
229  *
230  * Arguments:   @const char *file@ = name of the file
231  *              @int line@ = line number in file
232  *              @const char *msg@ = error message
233  *              @void *p@ = argument pointer (indicates which keyring)
234  *
235  * Returns:     ---
236  *
237  * Use:         Reports an error message about loading a key file.
238  */
239
240 static void keymoan(const char *file, int line, const char *msg, void *p)
241 {
242   keyhalf *kh = p;
243
244   if (!line) {
245     a_warn("KEYMGMT", "%s-keyring", kh->kind, "%s", file,
246            "io-error", "?ERRNO", A_END);
247   } else {
248     a_warn("KEYMGMT", "%s-keyring", kh->kind, "%s", file, "line", "%d", line,
249            "%s", msg, A_END);
250   }
251 }
252
253 /* --- @kh_reopen@ --- *
254  *
255  * Arguments:   @keyhalf *kh@ = pointer to keyhalf structure
256  *
257  * Returns:     Zero on success, @-1@ on error.
258  *
259  * Use:         Reopens the key file for the appropriate key half.  If this
260  *              fails, everything is left as it was; if it succeeds, then the
261  *              old file is closed (if it was non-null) and the new one put
262  *              in its place.
263  */
264
265 static int kh_reopen(keyhalf *kh)
266 {
267   key_file *kf = CREATE(key_file);
268
269   if (key_open(kf, kh->kr, KOPEN_READ, keymoan, kh)) {
270     a_warn("KEYMGMT", "%s-keyring", kh->kind, "%s", kh->kr,
271            "io-error", "?ERRNO", A_END);
272     DESTROY(kf);
273     return (-1);
274   } else {
275     if (kh->kf) {
276       key_close(kh->kf);
277       DESTROY(kh->kf);
278     }
279     kh->kf = kf;
280     return (0);
281   }
282 }
283
284 /* --- @kh_init@ --- *
285  *
286  * Arguments:   @keyhalf *kh@ = pointer to keyhalf structure to set up
287  *              @const char *kr@ = name of the keyring file
288  *
289  * Returns:     ---
290  *
291  * Use:         Initialize a keyhalf structure, maintaining the private or
292  *              public keys.  Intended to be called during initialization:
293  *              exits if there's some kind of problem.
294  */
295
296 static void kh_init(keyhalf *kh, const char *kr)
297 {
298   kh->kr = kr;
299   fwatch_init(&kh->w, kr);
300   sym_create(&kh->tab);
301   kh->kf = 0;
302
303   if (kh_reopen(kh))
304     die(EXIT_FAILURE, "failed to load %s keyring `%s'", kh->kind, kr);
305 }
306
307 /* --- @kh_load@ --- *
308  *
309  * Arguments:   @keyhalf *kh@ = pointer to keyhalf
310  *              @const char *tag@ = key tag to be loaded
311  *              @int complainp@ = whether to complain about missing keys
312  *
313  * Returns:     Pointer to a @kdata@ structure if successful, or null on
314  *              failure.
315  *
316  * Use:         Attempts to load a key from the current key file.  This
317  *              function always reads data from the file: it's used when
318  *              there's a cache miss from @kh_find@, and when refreshing the
319  *              known keys in @kh_refresh@.  The returned kdata has a
320  *              reference count of exactly 1, and has no home knode.
321  */
322
323 static kdata *kh_load(keyhalf *kh, const char *tag, int complainp)
324 {
325   dstr t = DSTR_INIT;
326   dstr e = DSTR_INIT;
327   key *k;
328   key_data **d;
329   kdata *kd;
330   const char *ty;
331   const dhops *dh;
332   T( const dhgrp *g; )
333
334   /* --- Find the key and grab its tag --- */
335
336   if (key_qtag(kh->kf, tag, &t, &k, &d)) {
337     if (complainp) {
338       a_warn("KEYMGMT", "%s-keyring", kh->kind, "%s", kh->kr,
339              "key-not-found", "%s", tag, A_END);
340     }
341     goto fail_0;
342   }
343
344   /* --- Find the key's group type and the appropriate operations --- *
345    *
346    * There are several places to look for the key type.  The most obvious is
347    * the `kx-group' key attribute.  But there's also the key type itself, for
348    * compatibility reasons.
349    */
350
351   ty = key_getattr(kh->kf, k, "kx-group");
352   if (!ty && strncmp(k->type, "tripe-", 6) == 0) ty = k->type + 6;
353   if (!ty) ty = "dh";
354
355   for (dh = dhtab; dh->name; dh++)
356     if (strcmp(dh->name, ty) == 0) goto founddh;
357   a_warn("KEYMGMT", "%s-keyring", kh->kind,
358          "%s", kh->kr, "key", "%s", t.buf,
359          "unknown-group-type", "%s", ty, A_END);
360   goto fail_0;
361
362 founddh:
363   kd = CREATE(kdata);
364   if (kh->load(kh->kf, k, *d, dh, kd, &t, &e)) {
365     a_warn("KEYMGMT", "%s-keyring", kh->kind,
366            "%s", kh->kr, "key", "%s", t.buf,
367            "*%s", e.buf, A_END);
368     goto fail_1;
369   }
370
371   if (algs_get(&kd->algs, &e, kh->kf, k) ||
372       (kd->k && algs_check(&kd->algs, &e, kd->grp))) {
373     a_warn("KEYMGMT", "%s-keyring", kh->kind,
374            "%s", kh->kr, "key", "%s", t.buf,
375            "*%s", e.buf, A_END);
376     goto fail_2;
377   }
378
379   kd->tag = xstrdup(t.buf);
380   kd->ref = 1;
381   kd->kn = 0;
382   kd->t_exp = k->exp;
383
384   IF_TRACING(T_KEYMGMT, {
385     trace(T_KEYMGMT, "keymgmt: loaded %s key `%s'", kh->kind, t.buf);
386     IF_TRACING(T_CRYPTO, {
387       g = kd->grp;
388       g->ops->tracegrp(g);
389       if (kd->k)
390         trace(T_CRYPTO, "crypto: k = %s", g->ops->scstr(g, kd->k));
391       trace(T_CRYPTO, "crypto: K = %s", g->ops->gestr(g, kd->K));
392       kd->algs.bulk->ops->tracealgs(kd->algs.bulk);
393     })
394   })
395
396   goto done;
397
398 fail_2:
399   if (kd->k) kd->grp->ops->freesc(kd->grp, kd->k);
400   kd->grp->ops->freege(kd->grp, kd->K);
401   kd->grp->ops->freegrp(kd->grp);
402 fail_1:
403   DESTROY(kd);
404 fail_0:
405   kd = 0;
406 done:
407   dstr_destroy(&t);
408   dstr_destroy(&e);
409   return (kd);
410 }
411
412 /* --- @kh_find@ --- *
413  *
414  * Arguments:   @keyhalf *kh@ = pointer to the keyhalf
415  *              @const char *tag@ = key to be obtained
416  *              @int complainp@ = whether to complain about missing keys
417  *
418  * Returns:     A pointer to the kdata, or null on error.
419  *
420  * Use:         Obtains kdata, maybe from the cache.  This won't update a
421  *              stale cache entry, though @kh_refresh@ ought to have done
422  *              that already.  The returned kdata object may be shared with
423  *              other users.  (One of this function's responsibilities, over
424  *              @kh_load@, is to set the home knode of a freshly loaded
425  *              kdata.)
426  */
427
428 static kdata *kh_find(keyhalf *kh, const char *tag, int complainp)
429 {
430   knode *kn;
431   kdata *kd;
432   unsigned f;
433
434   kn = sym_find(&kh->tab, tag, -1, sizeof(knode), &f);
435
436   if (f) {
437     if (kn->f & KNF_BROKEN) {
438       T( if (complainp)
439            trace(T_KEYMGMT, "keymgmt: key `%s' marked as broken", tag); )
440       return (0);
441     }
442
443     kd = kn->kd;
444     if (kd) kd->ref++;
445     T( trace(T_KEYMGMT, "keymgmt: %scache hit for key `%s'",
446              kd ? "" : "negative ", tag); )
447     return (kd);
448   } else {
449     kd = kh_load(kh, tag, complainp);
450     kn->kd = kd;
451     kn->kh = kh;
452     kn->f = 0;
453     if (!kd)
454       kn->f |= KNF_BROKEN;
455     else {
456       kd->kn = kn;
457       kd->ref++;
458     }
459     return (kd);
460   }
461 }
462
463 /* --- @kh_refresh@ --- *
464  *
465  * Arguments:   @keyhalf *kh@ = pointer to the keyhalf
466  *
467  * Returns:     Zero if nothing needs to be done; nonzero if peers should
468  *              refresh their keys.
469  *
470  * Use:         Refreshes cached keys from files.
471  *
472  *              Each active knode is examined to see if a new key is
473  *              available: the return value is nonzero if any new keys are.
474  *              A key is considered new if its algorithms, public key, or
475  *              expiry time are/is different.
476  *
477  *              Stub knodes (with no kdata attached) are removed, so that a
478  *              later retry can succeed if the file has been fixed.  (This
479  *              doesn't count as a change, since no peers should be relying
480  *              on a nonexistent key.)
481  */
482
483 static int kh_refresh(keyhalf *kh)
484 {
485   knode *kn;
486   kdata *kd;
487   sym_iter i;
488   int changep = 0;
489
490   if (!fwatch_update(&kh->w, kh->kr) || kh_reopen(kh))
491     return (0);
492
493   T( trace(T_KEYMGMT, "keymgmt: rescan %s keyring `%s'", kh->kind, kh->kr); )
494   for (sym_mkiter(&i, &kh->tab); (kn = sym_next(&i)) != 0; ) {
495     if (!kn->kd) {
496       T( trace(T_KEYMGMT, "keymgmt: discard stub entry for key `%s'",
497                SYM_NAME(kn)); )
498       sym_remove(&kh->tab, kn);
499       continue;
500     }
501     if ((kd = kh_load(kh, SYM_NAME(kn), 1)) == 0) {
502       if (!(kn->f & KNF_BROKEN)) {
503         T( trace(T_KEYMGMT, "keymgmt: failed to load new key `%s': "
504                  "marking it as broken",
505                  SYM_NAME(kn)); )
506         kn->f |= KNF_BROKEN;
507       }
508       continue;
509     }
510     kn->f &= ~KNF_BROKEN;
511     if (kd->t_exp == kn->kd->t_exp &&
512         km_samealgsp(kd, kn->kd) &&
513         kd->grp->ops->eq(kd->grp, kd->K, kn->kd->K)) {
514       T( trace(T_KEYMGMT, "keymgmt: key `%s' unchanged", SYM_NAME(kn)); )
515       continue;
516     }
517     T( trace(T_KEYMGMT, "keymgmt: loaded new version of key `%s'",
518              SYM_NAME(kn)); )
519     km_unref(kn->kd);
520     kd->kn = kn;
521     kn->kd = kd;
522     changep = 1;
523   }
524
525   return (changep);
526 }
527
528 /*----- Main code ---------------------------------------------------------*/
529
530 const char *tag_priv;
531 kdata *master;
532
533 /* --- @km_init@ --- *
534  *
535  * Arguments:   @const char *privkr@ = private keyring file
536  *              @const char *pubkr@ = public keyring file
537  *              @const char *ptag@ = default private-key tag
538  *
539  * Returns:     ---
540  *
541  * Use:         Initializes the key-management machinery, loading the
542  *              keyrings and so on.
543  */
544
545 void km_init(const char *privkr, const char *pubkr, const char *ptag)
546 {
547   const gchash *const *hh;
548
549   for (hh = ghashtab; *hh; hh++) {
550     if ((*hh)->hashsz > MAXHASHSZ) {
551       die(EXIT_FAILURE, "INTERNAL ERROR: %s hash length %lu > MAXHASHSZ %d",
552           (*hh)->name, (unsigned long)(*hh)->hashsz, MAXHASHSZ);
553     }
554   }
555
556   kh_init(&priv, privkr);
557   kh_init(&pub, pubkr);
558
559   tag_priv = ptag;
560   if ((master = km_findpriv(ptag)) == 0) exit(EXIT_FAILURE);
561 }
562
563 /* --- @km_reload@ --- *
564  *
565  * Arguments:   ---
566  *
567  * Returns:     Zero if OK, nonzero to force reloading of keys.
568  *
569  * Use:         Checks the keyrings to see if they need reloading.
570  */
571
572 int km_reload(void)
573 {
574   int changep = 0;
575   kdata *kd;
576
577   if (kh_refresh(&priv)) {
578     changep = 1;
579     kd = master->kn->kd;
580     if (kd != master) {
581       km_unref(master);
582       km_ref(kd);
583       master = kd;
584     }
585   }
586   if (kh_refresh(&pub))
587     changep = 1;
588   return (changep);
589 }
590
591 /* --- @km_findpub@, @km_findpriv@ --- *
592  *
593  * Arguments:   @const char *tag@ = key tag to load
594  *
595  * Returns:     Pointer to the kdata object if successful, or null on error.
596  *
597  * Use:         Fetches a public or private key from the keyring.
598  */
599
600 kdata *km_findpub(const char *tag) { return (kh_find(&pub, tag, 1)); }
601
602 kdata *km_findpriv(const char *tag)
603 {
604   kdata *kd;
605
606   /* Unpleasantness for the sake of compatibility. */
607   if (!tag && (kd = kh_find(&priv, "tripe", 0)) != 0) return (kd);
608   else return (kh_find(&priv, tag ? tag : "tripe-dh", 1));
609 }
610
611 /* --- @km_tag@ --- *
612  *
613  * Arguments:   @kdata *kd@ - pointer to the kdata object
614  *
615  * Returns:     A pointer to the short tag by which the kdata was loaded.
616  */
617
618 const char *km_tag(kdata *kd) { return (SYM_NAME(kd->kn)); }
619
620 /* --- @km_ref@ --- *
621  *
622  * Arguments:   @kdata *kd@ = pointer to the kdata object
623  *
624  * Returns:     ---
625  *
626  * Use:         Claim a new reference to a kdata object.
627  */
628
629 void km_ref(kdata *kd) { kd->ref++; }
630
631 /* --- @km_unref@ --- *
632  *
633  * Arguments:   @kdata *kd@ = pointer to the kdata object
634  *
635  * Returns:     ---
636  *
637  * Use:         Releases a reference to a kdata object.
638  */
639
640 void km_unref(kdata *kd)
641 {
642   if (--kd->ref) return;
643   if (kd->k) kd->grp->ops->freesc(kd->grp, kd->k);
644   kd->grp->ops->freege(kd->grp, kd->K);
645   kd->grp->ops->freegrp(kd->grp);
646   xfree(kd->tag);
647   DESTROY(kd);
648 }
649
650 /*----- That's all, folks -------------------------------------------------*/