X-Git-Url: https://git.distorted.org.uk/u/mdw/catacomb/blobdiff_plain/ba6e6b64033b1f9de49feccb5c9cd438354481f7..0f00dc4c8eb47e67bc0f148c2dd109f73a451e0a:/progs/cookie.c diff --git a/progs/cookie.c b/progs/cookie.c new file mode 100644 index 0000000..56849e5 --- /dev/null +++ b/progs/cookie.c @@ -0,0 +1,692 @@ +/* -*-c-*- + * + * Generate and validate cryptographic cookies + * + * (c) 1999 Mark Wooding + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of Catacomb. + * + * Catacomb is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Catacomb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Catacomb; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/*----- Header files ------------------------------------------------------*/ + +#define _FILE_OFFSET_BITS 64 + +#include "config.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "cc.h" +#include "key.h" +#include "gmac.h" +#include "getdate.h" + +/*----- Handy global state ------------------------------------------------*/ + +static const char *keyfile = "keyring"; + +/*----- Cookie format -----------------------------------------------------*/ + +/* --- Cookie header structure (unpacked) --- */ + +typedef struct cookie { + uint32 k; + time_t exp; +} cookie; + +/* --- Size of a cookie header (packed) --- */ + +#define COOKIE_SZ (4 + 8) + +/* --- @COOKIE_PACK@ --- * + * + * Arguments: @p@ = pointer to destination buffer + * @c@ = pointer to source cookie header block + * + * Use: Packs a cookie header into an octet buffer in a machine- + * independent way. + */ + +#define COOKIE_PACK(p, c) do { \ + octet *_p = (octet *)(p); \ + const cookie *_c = (c); \ + STORE32(_p + 0, _c->k); \ + STORE32(_p + 4, ((_c->exp & ~MASK32) >> 16) >> 16); \ + STORE32(_p + 8, _c->exp); \ +} while (0) + +/* --- @COOKIE_UNPACK@ --- * + * + * Arguments: @c@ = pointer to destination cookie header + * @p@ = pointer to source buffer + * + * Use: Unpacks a cookie header from an octet buffer into a + * machine-specific but comprehensible structure. + */ + +#define COOKIE_UNPACK(c, p) do { \ + cookie *_c = (c); \ + const octet *_p = (const octet *)(p); \ + _c->k = LOAD32(_p + 0); \ + _c->exp = ((time_t)(((LOAD32(_p + 4) << 16) << 16) & ~MASK32) | \ + (time_t)LOAD32(_p + 8)); \ +} while (0) + +/*----- Useful shared functions -------------------------------------------*/ + +/* --- @doopen@ --- * + * + * Arguments: @key_file *f@ = pointer to key file block + * @unsigned how@ = method to open file with + * + * Returns: --- + * + * Use: Opens a key file and handles errors by panicking + * appropriately. + */ + +static void doopen(key_file *f, unsigned how) +{ + if (key_open(f, keyfile, how, key_moan, 0)) { + die(EXIT_FAILURE, "couldn't open file `%s': %s", + keyfile, strerror(errno)); + } +} + +/* --- @doclose@ --- * + * + * Arguments: @key_file *f@ = pointer to key file block + * + * Returns: --- + * + * Use: Closes a key file and handles errors by panicking + * appropriately. + */ + +static void doclose(key_file *f) +{ + switch (key_close(f)) { + case KWRITE_FAIL: + die(EXIT_FAILURE, "couldn't write file `%s': %s", + keyfile, strerror(errno)); + case KWRITE_BROKEN: + die(EXIT_FAILURE, "keyring file `%s' broken: %s (repair manually)", + keyfile, strerror(errno)); + } +} + +/* --- @getmac@ --- * + * + * Arguments: @key *k@ = key to use + * @const char *app@ = application name + * + * Returns: The MAC to use. + * + * Use: Finds the right MAC for the given key. + */ + +static gmac *getmac(key *k, const char *app) +{ + dstr t = DSTR_INIT; + dstr d = DSTR_INIT; + char *p = 0; + const char *q; + size_t n; + key_bin kb; + key_packdef kp; + const gcmac *cm; + int e; + gmac *m; + + /* --- Set up --- */ + + key_fulltag(k, &t); + + /* --- Pick out the right MAC --- */ + + n = strlen(app); + if ((q = key_getattr(0, k, "mac")) != 0) { + dstr_puts(&d, q); + p = d.buf; + } else if (strncmp(k->type, app, n) == 0 && k->type[n] == '-') { + dstr_puts(&d, k->type); + p = d.buf + n + 1; + } else + die(EXIT_FAILURE, "no MAC algorithm for key `%s'", t.buf); + if ((cm = gmac_byname(p)) == 0) { + die(EXIT_FAILURE, "MAC algorithm `%s' not found in key `%s'", + p, t.buf); + } + + /* --- Unlock the key --- */ + + kp.e = KENC_BINARY; + kp.p = &kb; + if ((e = key_unpack(&kp, k->k, &t)) != 0) { + die(EXIT_FAILURE, "error unpacking key `%s': %s", + t.buf, key_strerror(e)); + } + + /* --- Make the MAC object --- */ + + if (keysz(kb.sz, cm->keysz) != kb.sz) + die(EXIT_FAILURE, "key %s has bad length (%lu) for MAC %s", + t.buf, (unsigned long)kb.sz, cm->name); + m = cm->key(kb.k, kb.sz); + key_unpackdone(&kp); + return (m); +} + +/*----- Command implementation --------------------------------------------*/ + +/* --- @cmd_gen@ --- */ + +static int cmd_gen(int argc, char *argv[]) +{ + key_file f; + key *k; + gmac *m; + ghash *h; + const char *tag = "cookie"; + int err; + cookie c = { 0, KEXP_EXPIRE }; + unsigned fl = 0; + int bits = 32; + const octet *t; + dstr d = DSTR_INIT; + octet buf[COOKIE_SZ]; + base64_ctx b; + + /* --- Various useful flag bits --- */ + +#define f_bogus 1u + + /* --- Parse options for the subcommand --- */ + + for (;;) { + static struct option opt[] = { + { "bits", OPTF_ARGREQ, 0, 'b' }, + { "expire", OPTF_ARGREQ, 0, 'e' }, + { "key", OPTF_ARGREQ, 0, 'k' }, + { 0, 0, 0, 0 } + }; + int i = mdwopt(argc, argv, "+b:e:i:t:", opt, 0, 0, 0); + if (i < 0) + break; + + /* --- Handle the various options --- */ + + switch (i) { + + /* --- Fetch a size in bits --- */ + + case 'b': + if (!(bits = atoi(optarg)) || bits % 8) + die(EXIT_FAILURE, "bad number of bits: `%s'", optarg); + break; + + /* --- Fetch an expiry time --- */ + + case 'e': + if (strcmp(optarg, "forever") == 0) + c.exp = KEXP_FOREVER; + else if ((c.exp = get_date(optarg, 0)) == -1) + die(EXIT_FAILURE, "bad expiry date: `%s'", optarg); + break; + + /* --- Fetch a key type --- */ + + case 'k': + tag = optarg; + break; + + /* --- Other things are bogus --- */ + + default: + fl |= f_bogus; + break; + } + } + + /* --- Various sorts of bogosity --- */ + + if (fl & f_bogus || optind + 1 < argc) + die(EXIT_FAILURE, + "Usage: generate [-b BITS] [-e TIME] [-k TAG] [DATA]"); + + /* --- Choose a default expiry time --- */ + + if (c.exp == KEXP_EXPIRE) + c.exp = time(0) + 7 * 24 * 60 * 60; + + /* --- Open the key file and get the key --- */ + + doopen(&f, KOPEN_WRITE); + if ((k = key_bytag(&f, tag)) == 0) { + die(EXIT_FAILURE, "no key with tag `%s' in keyring `%s'", + tag, keyfile); + } + + c.k = k->id; + if ((err = key_used(&f, k, c.exp)) != 0) + die(EXIT_FAILURE, "can't generate cookie: %s", key_strerror(err)); + m = getmac(k, "cookie"); + if (bits/8 > GM_CLASS(m)->hashsz) { + die(EXIT_FAILURE, "inapproriate bit length for `%s' MACs", + GM_CLASS(m)->name); + } + + /* --- Store and MAC the cookie --- */ + + COOKIE_PACK(buf, &c); + + h = GM_INIT(m); + GH_HASH(h, buf, sizeof(buf)); + if (argv[optind]) + GH_HASH(h, argv[optind], strlen(argv[optind])); + t = GH_DONE(h, 0); + + /* --- Encode and emit the finished cookie --- */ + + base64_init(&b); + b.indent = ""; + base64_encode(&b, buf, sizeof(buf), &d); + base64_encode(&b, t, bits/8, &d); + base64_encode(&b, 0, 0, &d); + DWRITE(&d, stdout); + fputc('\n', stdout); + DDESTROY(&d); + GH_DESTROY(h); + GM_DESTROY(m); + + doclose(&f); + return (0); + +#undef f_bogus +} + +/* --- @cmd_verify@ --- */ + +static int cmd_verify(int argc, char *argv[]) +{ + key_file f; + dstr d = DSTR_INIT; + unsigned fl = 0; + int bits = -1, minbits = 32; + int v = 1; + base64_ctx b; + gmac *m; + ghash *h; + cookie c; + key *k; + int cbits; + const octet *t; + time_t now = time(0); + + /* --- Various useful flag bits --- */ + +#define f_bogus 1u +#define f_forever 2u +#define f_utc 4u + + /* --- Parse options for the subcommand --- */ + + for (;;) { + static struct option opt[] = { + { "bits", OPTF_ARGREQ, 0, 'b' }, + { "min-bits", OPTF_ARGREQ, 0, 'm' }, + { "forever", 0, 0, 'f' }, + { "quiet", 0, 0, 'q' }, + { "verbose", 0, 0, 'v' }, + { "utc", 0, 0, 'u' }, + { 0, 0, 0, 0 } + }; + int i = mdwopt(argc, argv, "+b:m:fqvu", opt, 0, 0, 0); + if (i < 0) + break; + + /* --- Handle the various options --- */ + + switch (i) { + + /* --- Fetch a size in bits --- */ + + case 'b': + if (!(bits = atoi(optarg)) || bits % 8) + die(EXIT_FAILURE, "bad number of bits: `%s'", optarg); + break; + case 'm': + if (!(minbits = atoi(optarg)) || minbits % 8) + die(EXIT_FAILURE, "bad number of bits: `%s'", optarg); + break; + + /* --- Miscellaneous flags --- */ + + case 'f': + fl |= f_forever; + break; + case 'u': + fl |= f_utc; + break; + case 'q': + if (v > 0) v--; + break; + case 'v': + v++; + break; + + /* --- Other things are bogus --- */ + + default: + fl |= f_bogus; + break; + } + } + + /* --- Various sorts of bogosity --- */ + + if (fl & f_bogus || optind == argc || optind + 2 < argc) { + die(EXIT_FAILURE, + "Usage: verify [-fuqv] [-b BITS] [-m BITS] COOKIE [DATA]"); + } + doopen(&f, KOPEN_READ); + + /* --- Decode the base64 wrapping --- */ + + base64_init(&b); + base64_decode(&b, argv[optind], strlen(argv[optind]), &d); + base64_decode(&b, 0, 0, &d); + + if (d.len < COOKIE_SZ + 1) { + if (v) printf("FAIL cookie too small\n"); + goto fail; + } + + /* --- Extract the relevant details --- */ + + COOKIE_UNPACK(&c, d.buf); + + if (v > 1) { + char buf[64]; + if (c.exp == KEXP_FOREVER) + strcpy(buf, "forever"); + else { + struct tm *tm; + const char *fmt; + + if (fl & f_utc) { + tm = gmtime(&c.exp); + fmt = "%Y-%m-%d %H:%M:%S UTC"; + } else { + tm = localtime(&c.exp); + fmt = "%Y-%m-%d %H:%M:%S %Z"; + } + strftime(buf, sizeof(buf), fmt, tm); + } + printf("INFO keyid = %08lx; expiry = %s\n", (unsigned long)c.k, buf); + } + + /* --- Check the authentication token width --- */ + + cbits = (d.len - COOKIE_SZ) * 8; + if (v > 2) printf("INFO authentication token width = %i bits\n", cbits); + if (bits == -1) { + if (cbits < minbits) { + if (v) printf("FAIL authentication token too narrow\n"); + goto fail; + } + } else { + if (cbits != bits) { + if (v) printf("FAIL authentication token width doesn't match\n"); + goto fail; + } + } + /* --- Get the key --- */ + + if ((k = key_byid(&f, c.k)) == 0) { + if (v) printf("FAIL keyid %08lx unavailable\n", (unsigned long)c.k); + goto fail; + } + + /* --- Check that the cookie authenticates OK --- */ + + m = getmac(k, "cookie"); + h = GM_INIT(m); + GH_HASH(h, d.buf, COOKIE_SZ); + if (argv[optind + 1]) + GH_HASH(h, argv[optind + 1], strlen(argv[optind + 1])); + t = GH_DONE(h, 0); + + if (memcmp(t, d.buf + COOKIE_SZ, cbits / 8) != 0) { + if (v) printf("FAIL bad authentication token\n"); + goto fail; + } + + /* --- See whether the cookie has expired --- */ + + if (c.exp == KEXP_FOREVER) { + if (!(fl & f_forever)) { + if (v) printf("FAIL forever cookies not allowed\n"); + goto fail; + } + if (k->exp != KEXP_FOREVER) { + if (v) printf("FAIL cookie lasts forever but key will expire\n"); + goto fail; + } + } else if (c.exp < now) { + if (v) printf("FAIL cookie has expired\n"); + goto fail; + } + + if (v) printf("OK\n"); + key_close(&f); + GM_DESTROY(m); + GH_DESTROY(h); + dstr_destroy(&d); + return (0); + +fail: + key_close(&f); + dstr_destroy(&d); + return (1); + +#undef f_bogus +#undef f_forever +#undef f_utc +} + +/*----- Main command table ------------------------------------------------*/ + +static int cmd_help(int, char **); + +#define LISTS(LI) \ + LI("Lists", list, \ + listtab[i].name, listtab[i].name) \ + LI("Message authentication algorithms", mac, \ + gmactab[i], gmactab[i]->name) + +MAKELISTTAB(listtab, LISTS) + +static int cmd_show(int argc, char *argv[]) +{ + return (displaylists(listtab, argv + 1)); +} + +static cmd cmds[] = { + { "help", cmd_help, "help [COMMAND...]" }, + { "show", cmd_show, "show [ITEM...]" }, + { "generate", cmd_gen, + "generate [-b BITS] [-e TIME] [-k TAG] [DATA]", "\ +Options:\n\ +\n\ +-b, --bits=N Use an N-bit token in the cookie.\n\ +-e, --expire=TIME Make the cookie expire after TIME.\n\ +-k, --key=TAG Use key TAG to create the token.\n\ +" }, + { "verify", cmd_verify, + "verify [-fuqv] [-b BITS] [-m BITS] COOKIE [DATA]", "\ +Options:\n\ +\n\ +-b, --bits=N Accept tokens exactly N bits long only.\n\ +-m, --min-bits=N Accept tokens N bits long or more.\n\ +-f, --forever Accept cookies which never expire.\n\ +-u, --utc Output cookie expiry dates in UTC.\n\ +-q, --quiet Produce less output while checking cookies.\n\ +-v, --verbose Produce more output while checking cookies.\n\ +" }, + { 0, 0, 0 } +}; + +static int cmd_help(int argc, char *argv[]) +{ + sc_help(cmds, stdout, argv + 1); + return (0); +} + +/*----- Main code ---------------------------------------------------------*/ + +/* --- Helpful GNUy functions --- */ + +static void usage(FILE *fp) +{ + fprintf(fp, "Usage: %s [-k KEYRING] COMMAND [ARGS]\n", QUIS); +} + +void version(FILE *fp) +{ + fprintf(fp, "%s, Catacomb version " VERSION "\n", QUIS); +} + +void help_global(FILE *fp) +{ + usage(fp); + fputs("\n\ +Generates and validates cryptographic cookies. Command line options\n\ +recognized are:\n\ +\n\ +-h, --help [COMMAND] Display this help text (or help for COMMAND).\n\ +-v, --version Display version number.\n\ +-u, --usage Display short usage summary.\n\ +\n\ +-k, --key-file=FILE Read and write keys in FILE.\n", + fp); +} + +/* --- @main@ --- * + * + * Arguments: @int argc@ = number of command line arguments + * @char *argv[]@ = array of arguments + * + * Returns: Zero if OK, nonzero if not. + * + * Use: Generates and validates cryptographic cookies. + */ + +int main(int argc, char *argv[]) +{ + unsigned f = 0; + +#define f_bogus 1u +#define f_forever 2u + + /* --- Initialize the library --- */ + + ego(argv[0]); + sub_init(); + + /* --- Options parsing --- */ + + for (;;) { + static struct option opt[] = { + + /* --- Standard GNUy help options --- */ + + { "help", 0, 0, 'h' }, + { "version", 0, 0, 'v' }, + { "usage", 0, 0, 'u' }, + + /* --- Actual relevant options --- */ + + { "keyring", OPTF_ARGREQ, 0, 'k' }, + + /* --- Magic terminator --- */ + + { 0, 0, 0, 0 } + }; + int i = mdwopt(argc, argv, "+hvu k:", opt, 0, 0, 0); + + if (i < 0) + break; + switch (i) { + + /* --- Helpful GNUs --- */ + + case 'u': + usage(stdout); + exit(0); + case 'v': + version(stdout); + exit(0); + case 'h': + sc_help(cmds, stdout, argv + optind); + exit(0); + + /* --- Real genuine useful options --- */ + + case 'k': + keyfile = optarg; + break; + + /* --- Bogus things --- */ + + default: + f |= f_bogus; + break; + } + } + + if ((f & f_bogus) || optind == argc) { + usage(stderr); + exit(EXIT_FAILURE); + } + + /* --- Dispatch to appropriate command handler --- */ + + argc -= optind; + argv += optind; + optind = 0; + return (findcmd(cmds, argv[0])->cmd(argc, argv)); + +#undef f_bogus +#undef f_forever +} + +/*----- That's all, folks -------------------------------------------------*/