Deploy the new <ctype.h> and `foocmp' macros from mLib.
[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>
141c1284 42#include <mLib/macros.h>
c65df279 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"
6d169e4a 49#include "ct.h"
c65df279 50#include "key.h"
51#include "gmac.h"
52#include "getdate.h"
53
54/*----- Handy global state ------------------------------------------------*/
55
56static const char *keyfile = "keyring";
57
58/*----- Cookie format -----------------------------------------------------*/
59
60/* --- Cookie header structure (unpacked) --- */
61
62typedef 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); \
e91d142c 84 STORE32(_p + 4, ((_c->exp & ~(unsigned long)MASK32) >> 16) >> 16); \
c65df279 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); \
e91d142c
MW
101 _c->exp = ((time_t)(((LOAD32(_p + 4) << 16) << 16) & \
102 ~(unsigned long)MASK32) | \
c65df279 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
119static 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
137static 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
159static 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;
141c1284 182 } else if (STRNCMP(k->type, ==, app, n) && k->type[n] == '-') {
c65df279 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
ef13e9a4 194 kp.e = KENC_BINARY;
c65df279 195 kp.p = &kb;
ef13e9a4 196 if ((e = key_unpack(&kp, k->k, &t)) != 0) {
c65df279 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
215static 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;
45c0fd36 230
c65df279 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':
141c1284 262 if (STRCMP(optarg, ==, "forever"))
c65df279 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
341static 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);
45c0fd36 392 break;
c65df279 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;
45c0fd36 408
c65df279 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
6d169e4a 491 if (!ct_memeq(t, d.buf + COOKIE_SZ, cbits / 8)) {
c65df279 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
519fail:
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
531static 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
539MAKELISTTAB(listtab, LISTS)
540
541static int cmd_show(int argc, char *argv[])
542{
543 return (displaylists(listtab, argv + 1));
544}
545
546static 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]", "\
551Options:\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]", "\
559Options:\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
571static 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
581static void usage(FILE *fp)
582{
583 fprintf(fp, "Usage: %s [-k KEYRING] COMMAND [ARGS]\n", QUIS);
584}
585
586void version(FILE *fp)
587{
588 fprintf(fp, "%s, Catacomb version " VERSION "\n", QUIS);
589}
590
591void help_global(FILE *fp)
592{
593 usage(fp);
594 fputs("\n\
595Generates and validates cryptographic cookies. Command line options\n\
596recognized 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
616int 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
45c0fd36 693}
c65df279 694
695/*----- That's all, folks -------------------------------------------------*/