3 * Generate and validate cryptographic cookies
5 * (c) 1999 Mark Wooding
8 /*----- Licensing notice --------------------------------------------------*
10 * This file is part of Catacomb.
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.
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.
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.
27 /*----- Header files ------------------------------------------------------*/
29 #define _FILE_OFFSET_BITS 64
39 #include <mLib/base64.h>
40 #include <mLib/bits.h>
41 #include <mLib/dstr.h>
42 #include <mLib/macros.h>
43 #include <mLib/mdwopt.h>
44 #include <mLib/quis.h>
45 #include <mLib/report.h>
54 /*----- Handy global state ------------------------------------------------*/
56 static const char *keyfile
= "keyring";
58 /*----- Cookie format -----------------------------------------------------*/
60 /* --- Cookie header structure (unpacked) --- */
62 typedef struct cookie
{
67 /* --- Size of a cookie header (packed) --- */
69 #define COOKIE_SZ (4 + 8)
71 /* --- @COOKIE_PACK@ --- *
73 * Arguments: @p@ = pointer to destination buffer
74 * @c@ = pointer to source cookie header block
76 * Use: Packs a cookie header into an octet buffer in a machine-
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 & ~(unsigned long)MASK32) >> 16) >> 16); \
85 STORE32(_p + 8, _c->exp); \
88 /* --- @COOKIE_UNPACK@ --- *
90 * Arguments: @c@ = pointer to destination cookie header
91 * @p@ = pointer to source buffer
93 * Use: Unpacks a cookie header from an octet buffer into a
94 * machine-specific but comprehensible structure.
97 #define COOKIE_UNPACK(c, p) do { \
99 const octet *_p = (const octet *)(p); \
100 _c->k = LOAD32(_p + 0); \
101 _c->exp = ((time_t)(((LOAD32(_p + 4) << 16) << 16) & \
102 ~(unsigned long)MASK32) | \
103 (time_t)LOAD32(_p + 8)); \
106 /*----- Useful shared functions -------------------------------------------*/
108 /* --- @doopen@ --- *
110 * Arguments: @key_file *f@ = pointer to key file block
111 * @unsigned how@ = method to open file with
115 * Use: Opens a key file and handles errors by panicking
119 static void doopen(key_file
*f
, unsigned how
)
121 if (key_open(f
, keyfile
, how
, key_moan
, 0)) {
122 die(EXIT_FAILURE
, "couldn't open file `%s': %s",
123 keyfile
, strerror(errno
));
127 /* --- @doclose@ --- *
129 * Arguments: @key_file *f@ = pointer to key file block
133 * Use: Closes a key file and handles errors by panicking
137 static void doclose(key_file
*f
)
139 switch (key_close(f
)) {
141 die(EXIT_FAILURE
, "couldn't write file `%s': %s",
142 keyfile
, strerror(errno
));
144 die(EXIT_FAILURE
, "keyring file `%s' broken: %s (repair manually)",
145 keyfile
, strerror(errno
));
149 /* --- @getmac@ --- *
151 * Arguments: @key *k@ = key to use
152 * @const char *app@ = application name
154 * Returns: The MAC to use.
156 * Use: Finds the right MAC for the given key.
159 static gmac
*getmac(key
*k
, const char *app
)
176 /* --- Pick out the right MAC --- */
179 if ((q
= key_getattr(0, k
, "mac")) != 0) {
182 } else if (STRNCMP(k
->type
, ==, app
, n
) && k
->type
[n
] == '-') {
183 dstr_puts(&d
, k
->type
);
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'",
192 /* --- Unlock the key --- */
196 if ((e
= key_unpack(&kp
, k
->k
, &t
)) != 0) {
197 die(EXIT_FAILURE
, "error unpacking key `%s': %s",
198 t
.buf
, key_strerror(e
));
201 /* --- Make the MAC object --- */
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
);
211 /*----- Command implementation --------------------------------------------*/
213 /* --- @cmd_gen@ --- */
215 static int cmd_gen(int argc
, char *argv
[])
221 const char *tag
= "cookie";
223 cookie c
= { 0, KEXP_EXPIRE
};
228 octet buf
[COOKIE_SZ
];
231 /* --- Various useful flag bits --- */
235 /* --- Parse options for the subcommand --- */
238 static struct option opt
[] = {
239 { "bits", OPTF_ARGREQ
, 0, 'b' },
240 { "expire", OPTF_ARGREQ
, 0, 'e' },
241 { "key", OPTF_ARGREQ
, 0, 'k' },
244 int i
= mdwopt(argc
, argv
, "+b:e:i:t:", opt
, 0, 0, 0);
248 /* --- Handle the various options --- */
252 /* --- Fetch a size in bits --- */
255 if (!(bits
= atoi(optarg
)) || bits
% 8)
256 die(EXIT_FAILURE
, "bad number of bits: `%s'", optarg
);
259 /* --- Fetch an expiry time --- */
262 if (STRCMP(optarg
, ==, "forever"))
263 c
.exp
= KEXP_FOREVER
;
264 else if ((c
.exp
= get_date(optarg
, 0)) == -1)
265 die(EXIT_FAILURE
, "bad expiry date: `%s'", optarg
);
268 /* --- Fetch a key type --- */
274 /* --- Other things are bogus --- */
282 /* --- Various sorts of bogosity --- */
284 if (fl
& f_bogus
|| optind
+ 1 < argc
)
286 "Usage: generate [-b BITS] [-e TIME] [-k TAG] [DATA]");
288 /* --- Choose a default expiry time --- */
290 if (c
.exp
== KEXP_EXPIRE
)
291 c
.exp
= time(0) + 7 * 24 * 60 * 60;
293 /* --- Open the key file and get the key --- */
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'",
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",
310 /* --- Store and MAC the cookie --- */
312 COOKIE_PACK(buf
, &c
);
315 GH_HASH(h
, buf
, sizeof(buf
));
317 GH_HASH(h
, argv
[optind
], strlen(argv
[optind
]));
320 /* --- Encode and emit the finished cookie --- */
324 base64_encode(&b
, buf
, sizeof(buf
), &d
);
325 base64_encode(&b
, t
, bits
/8, &d
);
326 base64_encode(&b
, 0, 0, &d
);
339 /* --- @cmd_verify@ --- */
341 static int cmd_verify(int argc
, char *argv
[])
346 int bits
= -1, minbits
= 32;
355 time_t now
= time(0);
357 /* --- Various useful flag bits --- */
363 /* --- Parse options for the subcommand --- */
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' },
375 int i
= mdwopt(argc
, argv
, "+b:m:fqvu", opt
, 0, 0, 0);
379 /* --- Handle the various options --- */
383 /* --- Fetch a size in bits --- */
386 if (!(bits
= atoi(optarg
)) || bits
% 8)
387 die(EXIT_FAILURE
, "bad number of bits: `%s'", optarg
);
390 if (!(minbits
= atoi(optarg
)) || minbits
% 8)
391 die(EXIT_FAILURE
, "bad number of bits: `%s'", optarg
);
394 /* --- Miscellaneous flags --- */
409 /* --- Other things are bogus --- */
417 /* --- Various sorts of bogosity --- */
419 if (fl
& f_bogus
|| optind
== argc
|| optind
+ 2 < argc
) {
421 "Usage: verify [-fuqv] [-b BITS] [-m BITS] COOKIE [DATA]");
423 doopen(&f
, KOPEN_READ
);
425 /* --- Decode the base64 wrapping --- */
428 base64_decode(&b
, argv
[optind
], strlen(argv
[optind
]), &d
);
429 base64_decode(&b
, 0, 0, &d
);
431 if (d
.len
< COOKIE_SZ
+ 1) {
432 if (v
) printf("FAIL cookie too small\n");
436 /* --- Extract the relevant details --- */
438 COOKIE_UNPACK(&c
, d
.buf
);
442 if (c
.exp
== KEXP_FOREVER
)
443 strcpy(buf
, "forever");
450 fmt
= "%Y-%m-%d %H:%M:%S UTC";
452 tm
= localtime(&c
.exp
);
453 fmt
= "%Y-%m-%d %H:%M:%S %Z";
455 strftime(buf
, sizeof(buf
), fmt
, tm
);
457 printf("INFO keyid = %08lx; expiry = %s\n", (unsigned long)c
.k
, buf
);
460 /* --- Check the authentication token width --- */
462 cbits
= (d
.len
- COOKIE_SZ
) * 8;
463 if (v
> 2) printf("INFO authentication token width = %i bits\n", cbits
);
465 if (cbits
< minbits
) {
466 if (v
) printf("FAIL authentication token too narrow\n");
471 if (v
) printf("FAIL authentication token width doesn't match\n");
475 /* --- Get the key --- */
477 if ((k
= key_byid(&f
, c
.k
)) == 0) {
478 if (v
) printf("FAIL keyid %08lx unavailable\n", (unsigned long)c
.k
);
482 /* --- Check that the cookie authenticates OK --- */
484 m
= getmac(k
, "cookie");
486 GH_HASH(h
, d
.buf
, COOKIE_SZ
);
487 if (argv
[optind
+ 1])
488 GH_HASH(h
, argv
[optind
+ 1], strlen(argv
[optind
+ 1]));
491 if (!ct_memeq(t
, d
.buf
+ COOKIE_SZ
, cbits
/ 8)) {
492 if (v
) printf("FAIL bad authentication token\n");
496 /* --- See whether the cookie has expired --- */
498 if (c
.exp
== KEXP_FOREVER
) {
499 if (!(fl
& f_forever
)) {
500 if (v
) printf("FAIL forever cookies not allowed\n");
503 if (k
->exp
!= KEXP_FOREVER
) {
504 if (v
) printf("FAIL cookie lasts forever but key will expire\n");
507 } else if (c
.exp
< now
) {
508 if (v
) printf("FAIL cookie has expired\n");
512 if (v
) printf("OK\n");
529 /*----- Main command table ------------------------------------------------*/
531 static int cmd_help(int, char **);
535 listtab[i].name, listtab[i].name) \
536 LI("Message authentication algorithms", mac, \
537 gmactab[i], gmactab[i]->name)
539 MAKELISTTAB(listtab
, LISTS
)
541 static int cmd_show(int argc
, char *argv
[])
543 return (displaylists(listtab
, argv
+ 1));
546 static 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]", "\
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\
557 { "verify", cmd_verify
,
558 "verify [-fuqv] [-b BITS] [-m BITS] COOKIE [DATA]", "\
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\
571 static int cmd_help(int argc
, char *argv
[])
573 sc_help(cmds
, stdout
, argv
+ 1);
577 /*----- Main code ---------------------------------------------------------*/
579 /* --- Helpful GNUy functions --- */
581 static void usage(FILE *fp
)
583 fprintf(fp
, "Usage: %s [-k KEYRING] COMMAND [ARGS]\n", QUIS
);
586 void version(FILE *fp
)
588 fprintf(fp
, "%s, Catacomb version " VERSION
"\n", QUIS
);
591 void help_global(FILE *fp
)
595 Generates and validates cryptographic cookies. Command line options\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\
602 -k, --key-file=FILE Read and write keys in FILE.\n",
608 * Arguments: @int argc@ = number of command line arguments
609 * @char *argv[]@ = array of arguments
611 * Returns: Zero if OK, nonzero if not.
613 * Use: Generates and validates cryptographic cookies.
616 int main(int argc
, char *argv
[])
623 /* --- Initialize the library --- */
628 /* --- Options parsing --- */
631 static struct option opt
[] = {
633 /* --- Standard GNUy help options --- */
635 { "help", 0, 0, 'h' },
636 { "version", 0, 0, 'v' },
637 { "usage", 0, 0, 'u' },
639 /* --- Actual relevant options --- */
641 { "keyring", OPTF_ARGREQ
, 0, 'k' },
643 /* --- Magic terminator --- */
647 int i
= mdwopt(argc
, argv
, "+hvu k:", opt
, 0, 0, 0);
653 /* --- Helpful GNUs --- */
662 sc_help(cmds
, stdout
, argv
+ optind
);
665 /* --- Real genuine useful options --- */
671 /* --- Bogus things --- */
679 if ((f
& f_bogus
) || optind
== argc
) {
684 /* --- Dispatch to appropriate command handler --- */
689 return (findcmd(cmds
, argv
[0])->cmd(argc
, argv
));
695 /*----- That's all, folks -------------------------------------------------*/