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