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