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