tests/Makefile.m4: Distribute the converted AES test-vector files.
[u/mdw/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 #define _FILE_OFFSET_BITS 64
32
33 #include "config.h"
34
35 #include <errno.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <time.h>
40
41 #include <mLib/base64.h>
42 #include <mLib/bits.h>
43 #include <mLib/dstr.h>
44 #include <mLib/mdwopt.h>
45 #include <mLib/quis.h>
46 #include <mLib/report.h>
47 #include <mLib/sub.h>
48
49 #include "cc.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 & ~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) & ~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 (memcmp(t, d.buf + COOKIE_SZ, cbits / 8) != 0) {
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 -------------------------------------------------*/