5 * Generate and validate cryptographic cookies
7 * (c) 1999 Mark Wooding
10 /*----- Licensing notice --------------------------------------------------*
12 * This file is part of Catacomb.
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.
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.
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.
29 /*----- Header files ------------------------------------------------------*/
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>
52 /*----- Handy global state ------------------------------------------------*/
54 static const char *keyfile = "keyring";
56 /*----- Cookie format -----------------------------------------------------*/
58 /* --- Cookie header structure (unpacked) --- */
60 typedef struct cookie {
65 /* --- Size of a cookie header (packed) --- */
67 #define COOKIE_SZ (4 + 8)
69 /* --- @COOKIE_PACK@ --- *
71 * Arguments: @p@ = pointer to destination buffer
72 * @c@ = pointer to source cookie header block
74 * Use: Packs a cookie header into an octet buffer in a machine-
78 #define COOKIE_PACK(p, c) do { \
79 octet *_p = (octet *)(p); \
80 const cookie *_c = (c); \
81 STORE32(_p + 0, _c->k); \
82 STORE32(_p + 4, ((_c->exp & ~MASK32) >> 16) >> 16); \
83 STORE32(_p + 8, _c->exp); \
86 /* --- @COOKIE_UNPACK@ --- *
88 * Arguments: @c@ = pointer to destination cookie header
89 * @p@ = pointer to source buffer
91 * Use: Unpacks a cookie header from an octet buffer into a
92 * machine-specific but comprehensible structure.
95 #define COOKIE_UNPACK(c, p) do { \
97 const octet *_p = (const octet *)(p); \
98 _c->k = LOAD32(_p + 0); \
99 _c->exp = ((time_t)(((LOAD32(_p + 4) << 16) << 16) & ~MASK32) | \
100 (time_t)LOAD32(_p + 8)); \
103 /*----- Useful shared functions -------------------------------------------*/
105 /* --- @doopen@ --- *
107 * Arguments: @key_file *f@ = pointer to key file block
108 * @unsigned how@ = method to open file with
112 * Use: Opens a key file and handles errors by panicking
116 static void doopen(key_file *f, unsigned how)
118 if (key_open(f, keyfile, how, key_moan, 0)) {
119 die(EXIT_FAILURE, "couldn't open file `%s': %s",
120 keyfile, strerror(errno));
124 /* --- @doclose@ --- *
126 * Arguments: @key_file *f@ = pointer to key file block
130 * Use: Closes a key file and handles errors by panicking
134 static void doclose(key_file *f)
136 switch (key_close(f)) {
138 die(EXIT_FAILURE, "couldn't write file `%s': %s",
139 keyfile, strerror(errno));
141 die(EXIT_FAILURE, "keyring file `%s' broken: %s (repair manually)",
142 keyfile, strerror(errno));
146 /* --- @getmac@ --- *
148 * Arguments: @key *k@ = key to use
149 * @const char *app@ = application name
151 * Returns: The MAC to use.
153 * Use: Finds the right MAC for the given key.
156 static gmac *getmac(key *k, const char *app)
173 /* --- Pick out the right MAC --- */
176 if ((q = key_getattr(0, k, "mac")) != 0) {
179 } else if (strncmp(k->type, app, n) == 0 && k->type[n] == '-') {
180 dstr_puts(&d, k->type);
183 die(EXIT_FAILURE, "no MAC algorithm for key `%s'", t.buf);
184 if ((cm = gmac_byname(p)) == 0) {
185 die(EXIT_FAILURE, "MAC algorithm `%s' not found in key `%s'",
189 /* --- Unlock the key --- */
193 if ((e = key_unpack(&kp, k->k, &t)) != 0) {
194 die(EXIT_FAILURE, "error unpacking key `%s': %s",
195 t.buf, key_strerror(e));
198 /* --- Make the MAC object --- */
200 if (keysz(kb.sz, cm->keysz) != kb.sz)
201 die(EXIT_FAILURE, "key %s has bad length (%lu) for MAC %s",
202 t.buf, (unsigned long)kb.sz, cm->name);
203 m = cm->key(kb.k, kb.sz);
208 /*----- Command implementation --------------------------------------------*/
210 /* --- @cmd_gen@ --- */
212 static int cmd_gen(int argc, char *argv[])
218 const char *tag = "cookie";
220 cookie c = { 0, KEXP_EXPIRE };
225 octet buf[COOKIE_SZ];
228 /* --- Various useful flag bits --- */
232 /* --- Parse options for the subcommand --- */
235 static struct option opt[] = {
236 { "bits", OPTF_ARGREQ, 0, 'b' },
237 { "expire", OPTF_ARGREQ, 0, 'e' },
238 { "key", OPTF_ARGREQ, 0, 'k' },
241 int i = mdwopt(argc, argv, "+b:e:i:t:", opt, 0, 0, 0);
245 /* --- Handle the various options --- */
249 /* --- Fetch a size in bits --- */
252 if (!(bits = atoi(optarg)) || bits % 8)
253 die(EXIT_FAILURE, "bad number of bits: `%s'", optarg);
256 /* --- Fetch an expiry time --- */
259 if (strcmp(optarg, "forever") == 0)
260 c.exp = KEXP_FOREVER;
261 else if ((c.exp = get_date(optarg, 0)) == -1)
262 die(EXIT_FAILURE, "bad expiry date: `%s'", optarg);
265 /* --- Fetch a key type --- */
271 /* --- Other things are bogus --- */
279 /* --- Various sorts of bogosity --- */
281 if (fl & f_bogus || optind + 1 < argc)
283 "Usage: generate [-b BITS] [-e TIME] [-k TAG] [DATA]");
285 /* --- Choose a default expiry time --- */
287 if (c.exp == KEXP_EXPIRE)
288 c.exp = time(0) + 7 * 24 * 60 * 60;
290 /* --- Open the key file and get the key --- */
292 doopen(&f, KOPEN_WRITE);
293 if ((k = key_bytag(&f, tag)) == 0) {
294 die(EXIT_FAILURE, "no key with tag `%s' in keyring `%s'",
299 if ((err = key_used(&f, k, c.exp)) != 0)
300 die(EXIT_FAILURE, "can't generate cookie: %s", key_strerror(err));
301 m = getmac(k, "cookie");
302 if (bits/8 > GM_CLASS(m)->hashsz) {
303 die(EXIT_FAILURE, "inapproriate bit length for `%s' MACs",
307 /* --- Store and MAC the cookie --- */
309 COOKIE_PACK(buf, &c);
312 GH_HASH(h, buf, sizeof(buf));
314 GH_HASH(h, argv[optind], strlen(argv[optind]));
317 /* --- Encode and emit the finished cookie --- */
321 base64_encode(&b, buf, sizeof(buf), &d);
322 base64_encode(&b, t, bits/8, &d);
323 base64_encode(&b, 0, 0, &d);
336 /* --- @cmd_verify@ --- */
338 static int cmd_verify(int argc, char *argv[])
343 int bits = -1, minbits = 32;
352 time_t now = time(0);
354 /* --- Various useful flag bits --- */
360 /* --- Parse options for the subcommand --- */
363 static struct option opt[] = {
364 { "bits", OPTF_ARGREQ, 0, 'b' },
365 { "min-bits", OPTF_ARGREQ, 0, 'm' },
366 { "forever", 0, 0, 'f' },
367 { "quiet", 0, 0, 'q' },
368 { "verbose", 0, 0, 'v' },
369 { "utc", 0, 0, 'u' },
372 int i = mdwopt(argc, argv, "+b:m:fqvu", opt, 0, 0, 0);
376 /* --- Handle the various options --- */
380 /* --- Fetch a size in bits --- */
383 if (!(bits = atoi(optarg)) || bits % 8)
384 die(EXIT_FAILURE, "bad number of bits: `%s'", optarg);
387 if (!(minbits = atoi(optarg)) || minbits % 8)
388 die(EXIT_FAILURE, "bad number of bits: `%s'", optarg);
391 /* --- Miscellaneous flags --- */
406 /* --- Other things are bogus --- */
414 /* --- Various sorts of bogosity --- */
416 if (fl & f_bogus || optind == argc || optind + 2 < argc) {
418 "Usage: verify [-fuqv] [-b BITS] [-m BITS] COOKIE [DATA]");
420 doopen(&f, KOPEN_READ);
422 /* --- Decode the base64 wrapping --- */
425 base64_decode(&b, argv[optind], strlen(argv[optind]), &d);
426 base64_decode(&b, 0, 0, &d);
428 if (d.len < COOKIE_SZ + 1) {
429 if (v) printf("FAIL cookie too small\n");
433 /* --- Extract the relevant details --- */
435 COOKIE_UNPACK(&c, d.buf);
439 if (c.exp == KEXP_FOREVER)
440 strcpy(buf, "forever");
447 fmt = "%Y-%m-%d %H:%M:%S UTC";
449 tm = localtime(&c.exp);
450 fmt = "%Y-%m-%d %H:%M:%S %Z";
452 strftime(buf, sizeof(buf), fmt, tm);
454 printf("INFO keyid = %08lx; expiry = %s\n", (unsigned long)c.k, buf);
457 /* --- Check the authentication token width --- */
459 cbits = (d.len - COOKIE_SZ) * 8;
460 if (v > 2) printf("INFO authentication token width = %i bits\n", cbits);
462 if (cbits < minbits) {
463 if (v) printf("FAIL authentication token too narrow\n");
468 if (v) printf("FAIL authentication token width doesn't match\n");
472 /* --- Get the key --- */
474 if ((k = key_byid(&f, c.k)) == 0) {
475 if (v) printf("FAIL keyid %08lx unavailable\n", (unsigned long)c.k);
479 /* --- Check that the cookie authenticates OK --- */
481 m = getmac(k, "cookie");
483 GH_HASH(h, d.buf, COOKIE_SZ);
484 if (argv[optind + 1])
485 GH_HASH(h, argv[optind + 1], strlen(argv[optind + 1]));
488 if (memcmp(t, d.buf + COOKIE_SZ, cbits / 8) != 0) {
489 if (v) printf("FAIL bad authentication token\n");
493 /* --- See whether the cookie has expired --- */
495 if (c.exp == KEXP_FOREVER) {
496 if (!(fl & f_forever)) {
497 if (v) printf("FAIL forever cookies not allowed\n");
500 if (k->exp != KEXP_FOREVER) {
501 if (v) printf("FAIL cookie lasts forever but key will expire\n");
504 } else if (c.exp < now) {
505 if (v) printf("FAIL cookie has expired\n");
509 if (v) printf("OK\n");
526 /*----- Main command table ------------------------------------------------*/
528 static int cmd_help(int, char **);
532 listtab[i].name, listtab[i].name) \
533 LI("Message authentication algorithms", mac, \
534 gmactab[i], gmactab[i]->name)
536 MAKELISTTAB(listtab, LISTS)
538 static int cmd_show(int argc, char *argv[])
540 return (displaylists(listtab, argv + 1));
543 static cmd cmds[] = {
544 { "help", cmd_help, "help [COMMAND...]" },
545 { "show", cmd_show, "show [ITEM...]" },
546 { "generate", cmd_gen,
547 "generate [-b BITS] [-e TIME] [-k TAG] [DATA]", "\
550 -b, --bits=N Use an N-bit token in the cookie.\n\
551 -e, --expire=TIME Make the cookie expire after TIME.\n\
552 -k, --key=TAG Use key TAG to create the token.\n\
554 { "verify", cmd_verify,
555 "verify [-fuqv] [-b BITS] [-m BITS] COOKIE [DATA]", "\
558 -b, --bits=N Accept tokens exactly N bits long only.\n\
559 -m, --min-bits=N Accept tokens N bits long or more.\n\
560 -f, --forever Accept cookies which never expire.\n\
561 -u, --utc Output cookie expiry dates in UTC.\n\
562 -q, --quiet Produce less output while checking cookies.\n\
563 -v, --verbose Produce more output while checking cookies.\n\
568 static int cmd_help(int argc, char *argv[])
570 sc_help(cmds, stdout, argv + 1);
574 /*----- Main code ---------------------------------------------------------*/
576 /* --- Helpful GNUy functions --- */
578 static void usage(FILE *fp)
580 fprintf(fp, "Usage: %s [-k KEYRING] COMMAND [ARGS]\n", QUIS);
583 void version(FILE *fp)
585 fprintf(fp, "%s, Catacomb version " VERSION "\n", QUIS);
588 void help_global(FILE *fp)
592 Generates and validates cryptographic cookies. Command line options\n\
595 -h, --help [COMMAND] Display this help text (or help for COMMAND).\n\
596 -v, --version Display version number.\n\
597 -u, --usage Display short usage summary.\n\
599 -k, --key-file=FILE Read and write keys in FILE.\n",
605 * Arguments: @int argc@ = number of command line arguments
606 * @char *argv[]@ = array of arguments
608 * Returns: Zero if OK, nonzero if not.
610 * Use: Generates and validates cryptographic cookies.
613 int main(int argc, char *argv[])
620 /* --- Initialize the library --- */
625 /* --- Options parsing --- */
628 static struct option opt[] = {
630 /* --- Standard GNUy help options --- */
632 { "help", 0, 0, 'h' },
633 { "version", 0, 0, 'v' },
634 { "usage", 0, 0, 'u' },
636 /* --- Actual relevant options --- */
638 { "keyring", OPTF_ARGREQ, 0, 'k' },
640 /* --- Magic terminator --- */
644 int i = mdwopt(argc, argv, "+hvu k:", opt, 0, 0, 0);
650 /* --- Helpful GNUs --- */
659 sc_help(cmds, stdout, argv + optind);
662 /* --- Real genuine useful options --- */
668 /* --- Bogus things --- */
676 if ((f & f_bogus) || optind == argc) {
681 /* --- Dispatch to appropriate command handler --- */
686 return (findcmd(cmds, argv[0])->cmd(argc, argv));
692 /*----- That's all, folks -------------------------------------------------*/