chiark / gitweb /
keyutil.c: Only copy the shared parts of a parameters key.
[catacomb] / cookie.c
1 /* -*-c-*-
2  *
3  * $Id$
4  *
5  * Generate and validate cryptographic cookies
6  *
7  * (c) 1999 Mark Wooding
8  */
9
10 /*----- Licensing notice --------------------------------------------------*
11  *
12  * This file is part of Catacomb.
13  *
14  * Catacomb is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 2 of the License, or
17  * (at your option) any later version.
18  *
19  * Catacomb is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with Catacomb; if not, write to the Free Software Foundation,
26  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27  */
28
29 /*----- Header files ------------------------------------------------------*/
30
31 #include "config.h"
32
33 #include <errno.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <time.h>
38
39 #include <mLib/base64.h>
40 #include <mLib/bits.h>
41 #include <mLib/dstr.h>
42 #include <mLib/mdwopt.h>
43 #include <mLib/quis.h>
44 #include <mLib/report.h>
45 #include <mLib/sub.h>
46
47 #include "cc.h"
48 #include "key.h"
49 #include "gmac.h"
50 #include "getdate.h"
51
52 /*----- Handy global state ------------------------------------------------*/
53
54 static const char *keyfile = "keyring";
55
56 /*----- Cookie format -----------------------------------------------------*/
57
58 /* --- Cookie header structure (unpacked) --- */
59
60 typedef struct cookie {
61   uint32 k;
62   time_t exp;
63 } cookie;
64
65 /* --- Size of a cookie header (packed) --- */
66
67 #define COOKIE_SZ (4 + 8)
68
69 /* --- @COOKIE_PACK@ --- *
70  *
71  * Arguments:   @p@ = pointer to destination buffer
72  *              @c@ = pointer to source cookie header block
73  *
74  * Use:         Packs a cookie header into an octet buffer in a machine-
75  *              independent way.
76  */
77
78 #define COOKIE_PACK(p, c) do {                                          \
79   octet *_p = (octet *)(p);                                             \
80   const cookie *_c = (c);                                               \
81   STORE32(_p + 0, _c->k);                                               \
82   STORE32(_p + 4, ((_c->exp & ~MASK32) >> 16) >> 16);                   \
83   STORE32(_p + 8, _c->exp);                                             \
84 } while (0)
85
86 /* --- @COOKIE_UNPACK@ --- *
87  *
88  * Arguments:   @c@ = pointer to destination cookie header
89  *              @p@ = pointer to source buffer
90  *
91  * Use:         Unpacks a cookie header from an octet buffer into a
92  *              machine-specific but comprehensible structure.
93  */
94
95 #define COOKIE_UNPACK(c, p) do {                                        \
96   cookie *_c = (c);                                                     \
97   const octet *_p = (const octet *)(p);                                 \
98   _c->k = LOAD32(_p + 0);                                               \
99   _c->exp = ((time_t)(((LOAD32(_p + 4) << 16) << 16) & ~MASK32) |       \
100              (time_t)LOAD32(_p + 8));                                   \
101 } while (0)
102
103 /*----- Useful shared functions -------------------------------------------*/
104
105 /* --- @doopen@ --- *
106  *
107  * Arguments:   @key_file *f@ = pointer to key file block
108  *              @unsigned how@ = method to open file with
109  *
110  * Returns:     ---
111  *
112  * Use:         Opens a key file and handles errors by panicking
113  *              appropriately.
114  */
115
116 static void doopen(key_file *f, unsigned how)
117 {
118   if (key_open(f, keyfile, how, key_moan, 0)) {
119     die(EXIT_FAILURE, "couldn't open file `%s': %s",
120         keyfile, strerror(errno));
121   }
122 }
123
124 /* --- @doclose@ --- *
125  *
126  * Arguments:   @key_file *f@ = pointer to key file block
127  *
128  * Returns:     ---
129  *
130  * Use:         Closes a key file and handles errors by panicking
131  *              appropriately.
132  */
133
134 static void doclose(key_file *f)
135 {
136   switch (key_close(f)) {
137     case KWRITE_FAIL:
138       die(EXIT_FAILURE, "couldn't write file `%s': %s",
139           keyfile, strerror(errno));
140     case KWRITE_BROKEN:
141       die(EXIT_FAILURE, "keyring file `%s' broken: %s (repair manually)",
142           keyfile, strerror(errno));
143   }
144 }
145
146 /* --- @getmac@ --- *
147  *
148  * Arguments:   @key *k@ = key to use
149  *              @const char *app@ = application name
150  *
151  * Returns:     The MAC to use.
152  *
153  * Use:         Finds the right MAC for the given key.
154  */
155
156 static gmac *getmac(key *k, const char *app)
157 {
158   dstr t = DSTR_INIT;
159   dstr d = DSTR_INIT;
160   char *p = 0;
161   const char *q;
162   size_t n;
163   key_bin kb;
164   key_packdef kp;
165   const gcmac *cm;
166   int e;
167   gmac *m;
168
169   /* --- Set up --- */
170
171   key_fulltag(k, &t);
172
173   /* --- Pick out the right MAC --- */
174
175   n = strlen(app);
176   if ((q = key_getattr(0, k, "mac")) != 0) {
177     dstr_puts(&d, q);
178     p = d.buf;
179   } else if (strncmp(k->type, app, n) == 0 && k->type[n] == '-') {
180     dstr_puts(&d, k->type);
181     p = d.buf + n + 1;
182   } else
183     die(EXIT_FAILURE, "no MAC algorithm for key `%s'", t.buf);
184   if ((cm = gmac_byname(p)) == 0) {
185     die(EXIT_FAILURE, "MAC algorithm `%s' not found in key `%s'",
186         p, t.buf);
187   }
188
189   /* --- Unlock the key --- */
190
191   kp.e = KENC_BINARY;
192   kp.p = &kb;
193   if ((e = key_unpack(&kp, k->k, &t)) != 0) {
194     die(EXIT_FAILURE, "error unpacking key `%s': %s",
195         t.buf, key_strerror(e));
196   }
197
198   /* --- Make the MAC object --- */
199
200   if (keysz(kb.sz, cm->keysz) != kb.sz)
201     die(EXIT_FAILURE, "key %s has bad length (%lu) for MAC %s",
202         t.buf, (unsigned long)kb.sz, cm->name);
203   m = cm->key(kb.k, kb.sz);
204   key_unpackdone(&kp);
205   return (m);
206 }
207
208 /*----- Command implementation --------------------------------------------*/
209
210 /* --- @cmd_gen@ --- */
211
212 static int cmd_gen(int argc, char *argv[])
213 {
214   key_file f;
215   key *k;
216   gmac *m;
217   ghash *h;
218   const char *tag = "cookie";
219   int err;
220   cookie c = { 0, KEXP_EXPIRE };
221   unsigned fl = 0;
222   int bits = 32;
223   const octet *t;
224   dstr d = DSTR_INIT;
225   octet buf[COOKIE_SZ];
226   base64_ctx b;
227
228   /* --- Various useful flag bits --- */
229
230 #define f_bogus 1u
231
232   /* --- Parse options for the subcommand --- */
233
234   for (;;) {
235     static struct option opt[] = {
236       { "bits",         OPTF_ARGREQ,    0,      'b' },
237       { "expire",       OPTF_ARGREQ,    0,      'e' },
238       { "key",          OPTF_ARGREQ,    0,      'k' },
239       { 0,              0,              0,      0 }
240     };
241     int i = mdwopt(argc, argv, "+b:e:i:t:", opt, 0, 0, 0);
242     if (i < 0)
243       break;
244
245     /* --- Handle the various options --- */
246
247     switch (i) {
248
249       /* --- Fetch a size in bits --- */
250
251       case 'b':
252         if (!(bits = atoi(optarg)) || bits % 8)
253           die(EXIT_FAILURE, "bad number of bits: `%s'", optarg);
254         break;
255
256       /* --- Fetch an expiry time --- */
257
258       case 'e':
259         if (strcmp(optarg, "forever") == 0)
260           c.exp = KEXP_FOREVER;
261         else if ((c.exp = get_date(optarg, 0)) == -1)
262           die(EXIT_FAILURE, "bad expiry date: `%s'", optarg);
263         break;
264
265       /* --- Fetch a key type --- */
266
267       case 'k':
268         tag = optarg;
269         break;
270
271       /* --- Other things are bogus --- */
272
273       default:
274         fl |= f_bogus;
275         break;
276     }
277   }
278
279   /* --- Various sorts of bogosity --- */
280
281   if (fl & f_bogus || optind + 1 < argc)
282     die(EXIT_FAILURE,
283         "Usage: generate [-b BITS] [-e TIME] [-k TAG] [DATA]");
284
285   /* --- Choose a default expiry time --- */
286
287   if (c.exp == KEXP_EXPIRE)
288     c.exp = time(0) + 7 * 24 * 60 * 60;
289
290   /* --- Open the key file and get the key --- */
291
292   doopen(&f, KOPEN_WRITE);
293   if ((k = key_bytag(&f, tag)) == 0) {
294     die(EXIT_FAILURE, "no key with tag `%s' in keyring `%s'",
295         tag, keyfile);
296   }
297
298   c.k = k->id;
299   if ((err = key_used(&f, k, c.exp)) != 0)
300     die(EXIT_FAILURE, "can't generate cookie: %s", key_strerror(err));
301   m = getmac(k, "cookie");
302   if (bits/8 > GM_CLASS(m)->hashsz) {
303     die(EXIT_FAILURE, "inapproriate bit length for `%s' MACs",
304         GM_CLASS(m)->name);
305   }
306
307   /* --- Store and MAC the cookie --- */
308
309   COOKIE_PACK(buf, &c);
310
311   h = GM_INIT(m);
312   GH_HASH(h, buf, sizeof(buf));
313   if (argv[optind])
314     GH_HASH(h, argv[optind], strlen(argv[optind]));
315   t = GH_DONE(h, 0);
316
317   /* --- Encode and emit the finished cookie --- */
318
319   base64_init(&b);
320   b.indent = "";
321   base64_encode(&b, buf, sizeof(buf), &d);
322   base64_encode(&b, t, bits/8, &d);
323   base64_encode(&b, 0, 0, &d);
324   DWRITE(&d, stdout);
325   fputc('\n', stdout);
326   DDESTROY(&d);
327   GH_DESTROY(h);
328   GM_DESTROY(m);
329
330   doclose(&f);
331   return (0);
332
333 #undef f_bogus
334 }
335
336 /* --- @cmd_verify@ --- */
337
338 static int cmd_verify(int argc, char *argv[])
339 {
340   key_file f;
341   dstr d = DSTR_INIT;
342   unsigned fl = 0;
343   int bits = -1, minbits = 32;
344   int v = 1;
345   base64_ctx b;
346   gmac *m;
347   ghash *h;
348   cookie c;
349   key *k;
350   int cbits;
351   const octet *t;
352   time_t now = time(0);
353
354   /* --- Various useful flag bits --- */
355
356 #define f_bogus 1u
357 #define f_forever 2u
358 #define f_utc 4u
359
360   /* --- Parse options for the subcommand --- */
361
362   for (;;) {
363     static struct option opt[] = {
364       { "bits",         OPTF_ARGREQ,    0,      'b' },
365       { "min-bits",     OPTF_ARGREQ,    0,      'm' },
366       { "forever",      0,              0,      'f' },
367       { "quiet",        0,              0,      'q' },
368       { "verbose",      0,              0,      'v' },
369       { "utc",          0,              0,      'u' },
370       { 0,              0,              0,      0 }
371     };
372     int i = mdwopt(argc, argv, "+b:m:fqvu", opt, 0, 0, 0);
373     if (i < 0)
374       break;
375
376     /* --- Handle the various options --- */
377
378     switch (i) {
379
380       /* --- Fetch a size in bits --- */
381
382       case 'b':
383         if (!(bits = atoi(optarg)) || bits % 8)
384           die(EXIT_FAILURE, "bad number of bits: `%s'", optarg);
385         break;
386       case 'm':
387         if (!(minbits = atoi(optarg)) || minbits % 8)
388           die(EXIT_FAILURE, "bad number of bits: `%s'", optarg);
389         break;
390
391       /* --- Miscellaneous flags --- */
392
393       case 'f':
394         fl |= f_forever;
395         break;
396       case 'u':
397         fl |= f_utc;
398         break;
399       case 'q':
400         if (v > 0) v--;
401         break;
402       case 'v':
403         v++;
404         break;
405
406       /* --- Other things are bogus --- */
407
408       default:
409         fl |= f_bogus;
410         break;
411     }
412   }
413
414   /* --- Various sorts of bogosity --- */
415
416   if (fl & f_bogus || optind == argc || optind + 2 < argc) {
417     die(EXIT_FAILURE,
418         "Usage: verify [-fuqv] [-b BITS] [-m BITS] COOKIE [DATA]");
419   }
420   doopen(&f, KOPEN_READ);
421
422   /* --- Decode the base64 wrapping --- */
423
424   base64_init(&b);
425   base64_decode(&b, argv[optind], strlen(argv[optind]), &d);
426   base64_decode(&b, 0, 0, &d);
427
428   if (d.len < COOKIE_SZ + 1) {
429     if (v) printf("FAIL cookie too small\n");
430     goto fail;
431   }
432
433   /* --- Extract the relevant details --- */
434
435   COOKIE_UNPACK(&c, d.buf);
436
437   if (v > 1) {
438     char buf[64];
439     if (c.exp == KEXP_FOREVER)
440       strcpy(buf, "forever");
441     else {
442       struct tm *tm;
443       const char *fmt;
444
445       if (fl & f_utc) {
446         tm = gmtime(&c.exp);
447         fmt = "%Y-%m-%d %H:%M:%S UTC";
448       } else {
449         tm = localtime(&c.exp);
450         fmt = "%Y-%m-%d %H:%M:%S %Z";
451       }
452       strftime(buf, sizeof(buf), fmt, tm);
453     }
454     printf("INFO keyid = %08lx; expiry = %s\n", (unsigned long)c.k, buf);
455   }
456
457   /* --- Check the authentication token width --- */
458
459   cbits = (d.len - COOKIE_SZ) * 8;
460   if (v > 2) printf("INFO authentication token width = %i bits\n", cbits);
461   if (bits == -1) {
462     if (cbits < minbits) {
463       if (v) printf("FAIL authentication token too narrow\n");
464       goto fail;
465     }
466   } else {
467     if (cbits != bits) {
468       if (v) printf("FAIL authentication token width doesn't match\n");
469       goto fail;
470     }
471   }
472   /* --- Get the key --- */
473
474   if ((k = key_byid(&f, c.k)) == 0) {
475     if (v) printf("FAIL keyid %08lx unavailable\n", (unsigned long)c.k);
476     goto fail;
477   }
478
479   /* --- Check that the cookie authenticates OK --- */
480
481   m = getmac(k, "cookie");
482   h = GM_INIT(m);
483   GH_HASH(h, d.buf, COOKIE_SZ);
484   if (argv[optind + 1])
485     GH_HASH(h, argv[optind + 1], strlen(argv[optind + 1]));
486   t = GH_DONE(h, 0);
487
488   if (memcmp(t, d.buf + COOKIE_SZ, cbits / 8) != 0) {
489     if (v) printf("FAIL bad authentication token\n");
490     goto fail;
491   }
492
493   /* --- See whether the cookie has expired --- */
494
495   if (c.exp == KEXP_FOREVER) {
496     if (!(fl & f_forever)) {
497       if (v) printf("FAIL forever cookies not allowed\n");
498       goto fail;
499     }
500     if (k->exp != KEXP_FOREVER) {
501       if (v) printf("FAIL cookie lasts forever but key will expire\n");
502       goto fail;
503     }
504   } else if (c.exp < now) {
505     if (v) printf("FAIL cookie has expired\n");
506     goto fail;
507   }
508
509   if (v) printf("OK\n");
510   key_close(&f);
511   GM_DESTROY(m);
512   GH_DESTROY(h);
513   dstr_destroy(&d);
514   return (0);
515
516 fail:
517   key_close(&f);
518   dstr_destroy(&d);
519   return (1);
520
521 #undef f_bogus
522 #undef f_forever
523 #undef f_utc
524 }
525
526 /*----- Main command table ------------------------------------------------*/
527
528 static int cmd_help(int, char **);
529
530 #define LISTS(LI)                                                       \
531   LI("Lists", list,                                                     \
532      listtab[i].name, listtab[i].name)                                  \
533   LI("Message authentication algorithms", mac,                          \
534      gmactab[i], gmactab[i]->name)
535
536 MAKELISTTAB(listtab, LISTS)
537
538 static int cmd_show(int argc, char *argv[])
539 {
540   return (displaylists(listtab, argv + 1));
541 }
542
543 static cmd cmds[] = {
544   { "help", cmd_help, "help [COMMAND...]" },
545   { "show", cmd_show, "show [ITEM...]" },
546   { "generate", cmd_gen,
547     "generate [-b BITS] [-e TIME] [-k TAG] [DATA]", "\
548 Options:\n\
549 \n\
550 -b, --bits=N            Use an N-bit token in the cookie.\n\
551 -e, --expire=TIME       Make the cookie expire after TIME.\n\
552 -k, --key=TAG           Use key TAG to create the token.\n\
553 " },
554   { "verify", cmd_verify,
555     "verify [-fuqv] [-b BITS] [-m BITS] COOKIE [DATA]", "\
556 Options:\n\
557 \n\
558 -b, --bits=N            Accept tokens exactly N bits long only.\n\
559 -m, --min-bits=N        Accept tokens N bits long or more.\n\
560 -f, --forever           Accept cookies which never expire.\n\
561 -u, --utc               Output cookie expiry dates in UTC.\n\
562 -q, --quiet             Produce less output while checking cookies.\n\
563 -v, --verbose           Produce more output while checking cookies.\n\
564 " },
565   { 0, 0, 0 }
566 };
567
568 static int cmd_help(int argc, char *argv[])
569 {
570   sc_help(cmds, stdout, argv + 1);
571   return (0);
572 }
573
574 /*----- Main code ---------------------------------------------------------*/
575
576 /* --- Helpful GNUy functions --- */
577
578 static void usage(FILE *fp)
579 {
580   fprintf(fp, "Usage: %s [-k KEYRING] COMMAND [ARGS]\n", QUIS);
581 }
582
583 void version(FILE *fp)
584 {
585   fprintf(fp, "%s, Catacomb version " VERSION "\n", QUIS);
586 }
587
588 void help_global(FILE *fp)
589 {
590   usage(fp);
591   fputs("\n\
592 Generates and validates cryptographic cookies.  Command line options\n\
593 recognized are:\n\
594 \n\
595 -h, --help [COMMAND]    Display this help text (or help for COMMAND).\n\
596 -v, --version           Display version number.\n\
597 -u, --usage             Display short usage summary.\n\
598 \n\
599 -k, --key-file=FILE     Read and write keys in FILE.\n",
600         fp);
601 }
602
603 /* --- @main@ --- *
604  *
605  * Arguments:   @int argc@ = number of command line arguments
606  *              @char *argv[]@ = array of arguments
607  *
608  * Returns:     Zero if OK, nonzero if not.
609  *
610  * Use:         Generates and validates cryptographic cookies.
611  */
612
613 int main(int argc, char *argv[])
614 {
615   unsigned f = 0;
616
617 #define f_bogus 1u
618 #define f_forever 2u
619
620   /* --- Initialize the library --- */
621
622   ego(argv[0]);
623   sub_init();
624
625   /* --- Options parsing --- */
626
627   for (;;) {
628     static struct option opt[] = {
629
630       /* --- Standard GNUy help options --- */
631
632       { "help",         0,              0,      'h' },
633       { "version",      0,              0,      'v' },
634       { "usage",        0,              0,      'u' },
635
636       /* --- Actual relevant options --- */
637
638       { "keyring",      OPTF_ARGREQ,    0,      'k' },
639
640       /* --- Magic terminator --- */
641
642       { 0,              0,              0,      0 }
643     };
644     int i = mdwopt(argc, argv, "+hvu k:", opt, 0, 0, 0);
645
646     if (i < 0)
647       break;
648     switch (i) {
649
650       /* --- Helpful GNUs --- */
651
652       case 'u':
653         usage(stdout);
654         exit(0);
655       case 'v':
656         version(stdout);
657         exit(0);
658       case 'h':
659         sc_help(cmds, stdout, argv + optind);
660         exit(0);
661
662       /* --- Real genuine useful options --- */
663
664       case 'k':
665         keyfile = optarg;
666         break;
667
668       /* --- Bogus things --- */
669
670       default:
671         f |= f_bogus;
672         break;
673     }
674   }
675
676   if ((f & f_bogus) || optind == argc) {
677     usage(stderr);
678     exit(EXIT_FAILURE);
679   }
680
681   /* --- Dispatch to appropriate command handler --- */
682
683   argc -= optind;
684   argv += optind;
685   optind = 0;
686   return (findcmd(cmds, argv[0])->cmd(argc, argv));
687
688 #undef f_bogus
689 #undef f_forever
690 }
691
692 /*----- That's all, folks -------------------------------------------------*/