2aeb9ef3e39780372362e6834cc6e64ab20b4504
[u/mdw/catacomb] / dsig.c
1 /* -*-c-*-
2 *
3 * $Id: dsig.c,v 1.12 2004/04/17 09:58:37 mdw Exp $
4 *
5 * Verify signatures on distribuitions of files
6 *
7 * (c) 2000 Straylight/Edgeware
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 Library General Public License as
16 * published by the Free Software Foundation; either version 2 of the
17 * License, or (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 Library General Public License for more details.
23 *
24 * You should have received a copy of the GNU Library General Public
25 * License along with Catacomb; if not, write to the Free
26 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
27 * MA 02111-1307, USA.
28 */
29
30 /*----- Header files ------------------------------------------------------*/
31
32 #include "config.h"
33
34 #include <ctype.h>
35 #include <errno.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39
40 #include <mLib/alloc.h>
41 #include <mLib/base64.h>
42 #include <mLib/mdwopt.h>
43 #include <mLib/quis.h>
44 #include <mLib/report.h>
45 #include <mLib/sub.h>
46
47 #include "getdate.h"
48 #include "rand.h"
49 #include "ghash.h"
50 #include "key.h"
51 #include "key-data.h"
52 #include "noise.h"
53 #include "cc.h"
54
55 /*----- Data formatting ---------------------------------------------------*/
56
57 /* --- Binary data structure --- *
58 *
59 * The binary format, which is used for hashing and for the optional binary
60 * output, consists of a sequence of tagged blocks. The tag describes the
61 * format and meaining of the following data.
62 */
63
64 enum {
65 /* --- Block tags --- */
66
67 T_IDENT = 0, /* An identifying marker */
68 T_KEYID, /* Key identifier */
69 T_BEGIN, /* Begin hashing here */
70 T_COMMENT = T_BEGIN, /* A textual comment */
71 T_DATE, /* Creation date of signature */
72 T_EXPIRE, /* Expiry date of signature */
73 T_FILE, /* File and corresponding hash */
74 T_SIGNATURE, /* Final signature block */
75
76 /* --- Error messages --- */
77
78 E_EOF = -1,
79 E_BIN = -2,
80 E_TAG = -3,
81 E_DATE = -4
82 };
83
84 /* --- Name translation table --- */
85
86 static const char *tagtab[] = {
87 "ident:", "keyid:",
88 "comment:", "date:", "expires:", "file:",
89 "signature:",
90 0
91 };
92
93 static const char *errtab[] = {
94 "Off-by-one bug",
95 "Unexpected end-of-file",
96 "Binary object too large",
97 "Unrecognized tag",
98 "Bad date string"
99 };
100
101 /* --- Memory representation of block types --- */
102
103 typedef struct block {
104 int tag; /* Type tag */
105 dstr d; /* String data */
106 dstr b; /* Binary data */
107 time_t t; /* Timestamp */
108 uint32 k; /* Keyid */
109 } block;
110
111 /* --- @getstring@ --- *
112 *
113 * Arguments: @FILE *fp@ = stream from which to read
114 * @dstr *d@ = destination string
115 * @unsigned raw@ = raw or cooked read
116 *
117 * Returns: Zero if OK, nonzero on end-of-file.
118 *
119 * Use: Reads a filename (or something similar) from a stream.
120 */
121
122 static int getstring(FILE *fp, dstr *d, unsigned raw)
123 {
124 int ch;
125 int q = 0;
126
127 /* --- Raw: just read exactly what's written up to a null byte --- */
128
129 if (raw) {
130 if ((ch = getc(fp)) == EOF)
131 return (EOF);
132 for (;;) {
133 if (!ch)
134 break;
135 DPUTC(d, ch);
136 if ((ch = getc(fp)) == EOF)
137 break;
138 }
139 DPUTZ(d);
140 return (0);
141 }
142
143 /* --- Skip as far as whitespace --- *
144 *
145 * Also skip past comments.
146 */
147
148 again:
149 ch = getc(fp);
150 while (isspace(ch))
151 ch = getc(fp);
152 if (ch == '#') {
153 do ch = getc(fp); while (ch != '\n' && ch != EOF);
154 goto again;
155 }
156 if (ch == EOF)
157 return (EOF);
158
159 /* --- If the character is a quote then read a quoted string --- */
160
161 switch (ch) {
162 case '`':
163 ch = '\'';
164 case '\'':
165 case '\"':
166 q = ch;
167 ch = getc(fp);
168 break;
169 }
170
171 /* --- Now read all sorts of interesting things --- */
172
173 for (;;) {
174
175 /* --- Handle an escaped thing --- */
176
177 if (ch == '\\') {
178 ch = getc(fp);
179 if (ch == EOF)
180 break;
181 switch (ch) {
182 case 'a': ch = '\a'; break;
183 case 'b': ch = '\b'; break;
184 case 'f': ch = '\f'; break;
185 case 'n': ch = '\n'; break;
186 case 'r': ch = '\r'; break;
187 case 't': ch = '\t'; break;
188 case 'v': ch = '\v'; break;
189 }
190 DPUTC(d, ch);
191 ch = getc(fp);
192 continue;
193 }
194
195 /* --- If it's a quote or some other end marker then stop --- */
196
197 if (ch == q || (!q && isspace((unsigned char)ch)))
198 break;
199
200 /* --- Otherwise contribute and continue --- */
201
202 DPUTC(d, ch);
203 if ((ch = getc(fp)) == EOF)
204 break;
205 }
206
207 /* --- Done --- */
208
209 DPUTZ(d);
210 return (0);
211 }
212
213 /* --- @putstring@ --- *
214 *
215 * Arguments: @FILE *fp@ = stream to write on
216 * @const char *p@ = pointer to text
217 * @unsigned raw@ = whether the string is to be written raw
218 *
219 * Returns: ---
220 *
221 * Use: Emits a string to a stream.
222 */
223
224 static void putstring(FILE *fp, const char *p, unsigned raw)
225 {
226 size_t sz = strlen(p);
227 unsigned qq;
228 const char *q;
229
230 /* --- Just write the string null terminated if raw --- */
231
232 if (raw) {
233 fwrite(p, 1, sz + 1, fp);
234 return;
235 }
236
237 /* --- Check for any dodgy characters --- */
238
239 qq = 0;
240 for (q = p; *q; q++) {
241 if (isspace((unsigned char)*q)) {
242 qq = '\"';
243 break;
244 }
245 }
246
247 if (qq)
248 putc(qq, fp);
249
250 /* --- Emit the string --- */
251
252 for (q = p; *q; q++) {
253 switch (*q) {
254 case '\a': fputc('\\', fp); fputc('a', fp); break;
255 case '\b': fputc('\\', fp); fputc('b', fp); break;
256 case '\f': fputc('\\', fp); fputc('f', fp); break;
257 case '\n': fputc('\\', fp); fputc('n', fp); break;
258 case '\r': fputc('\\', fp); fputc('r', fp); break;
259 case '\t': fputc('\\', fp); fputc('t', fp); break;
260 case '\v': fputc('\\', fp); fputc('v', fp); break;
261 case '`': fputc('\\', fp); fputc('`', fp); break;
262 case '\'': fputc('\\', fp); fputc('\'', fp); break;
263 case '\"': fputc('\\', fp); fputc('\"', fp); break;
264 default:
265 putc(*q, fp);
266 break;
267 }
268 }
269
270 /* --- Done --- */
271
272 if (qq)
273 putc(qq, fp);
274 }
275
276 /* --- @timestring@ --- *
277 *
278 * Arguments: @time_t t@ = a timestamp
279 * @dstr *d@ = a string to write on
280 *
281 * Returns: ---
282 *
283 * Use: Writes a textual representation of the timestamp to the
284 * string.
285 */
286
287 static void timestring(time_t t, dstr *d)
288 {
289 if (t == KEXP_FOREVER)
290 DPUTS(d, "forever");
291 else {
292 struct tm *tm = localtime(&t);
293 DENSURE(d, 32);
294 d->len += strftime(d->buf + d->len, 32, "%Y-%m-%d %H:%M:%S %Z", tm);
295 DPUTZ(d);
296 }
297 }
298
299 /* --- @breset@ --- *
300 *
301 * Arguments: @block *b@ = block to reset
302 *
303 * Returns: ---
304 *
305 * Use: Resets a block so that more stuff can be put in it.
306 */
307
308 static void breset(block *b)
309 {
310 b->tag = 0;
311 DRESET(&b->d);
312 DRESET(&b->b);
313 b->k = 0;
314 b->t = KEXP_EXPIRE;
315 }
316
317 /* --- @binit@ --- *
318 *
319 * Arguments: @block *b@ = block to initialize
320 *
321 * Returns: ---
322 *
323 * Use: Initializes a block as something to read into.
324 */
325
326 static void binit(block *b)
327 {
328 dstr_create(&b->d);
329 dstr_create(&b->b);
330 breset(b);
331 }
332
333 /* --- @bdestroy@ --- *
334 *
335 * Arguments: @block *b@ = block to destroy
336 *
337 * Returns: ---
338 *
339 * Use: Destroys a block's contents.
340 */
341
342 static void bdestroy(block *b)
343 {
344 dstr_destroy(&b->d);
345 dstr_destroy(&b->b);
346 }
347
348 /* --- @bget@ --- *
349 *
350 * Arguments: @block *b@ = pointer to block
351 * @FILE *fp@ = stream to read from
352 * @unsigned bin@ = binary switch
353 *
354 * Returns: Tag of block, or an error tag.
355 *
356 * Use: Reads a block from a stream.
357 */
358
359 static int bget(block *b, FILE *fp, unsigned bin)
360 {
361 int tag;
362
363 /* --- Read the tag --- */
364
365 if (bin)
366 tag = getc(fp);
367 else {
368 dstr d = DSTR_INIT;
369 if (getstring(fp, &d, 0))
370 return (E_EOF);
371 for (tag = 0; tagtab[tag]; tag++) {
372 if (strcmp(tagtab[tag], d.buf) == 0)
373 goto done;
374 }
375 return (E_TAG);
376 done:;
377 }
378
379 /* --- Decide what to do next --- */
380
381 breset(b);
382 b->tag = tag;
383 switch (tag) {
384
385 /* --- Reading of strings --- */
386
387 case T_IDENT:
388 case T_COMMENT:
389 if (getstring(fp, &b->d, bin))
390 return (E_EOF);
391 break;
392
393 /* --- Timestamps --- */
394
395 case T_DATE:
396 case T_EXPIRE:
397 if (bin) {
398 octet buf[8];
399 if (fread(buf, sizeof(buf), 1, fp) < 1)
400 return (E_EOF);
401 b->t = ((time_t)(((LOAD32(buf + 0) << 16) << 16) & ~MASK32) |
402 (time_t)LOAD32(buf + 4));
403 } else {
404 if (getstring(fp, &b->d, 0))
405 return (E_EOF);
406 if (strcmp(b->d.buf, "forever") == 0)
407 b->t = KEXP_FOREVER;
408 else if ((b->t = get_date(b->d.buf, 0)) == -1)
409 return (E_DATE);
410 }
411 break;
412
413 /* --- Key ids --- */
414
415 case T_KEYID:
416 if (bin) {
417 octet buf[4];
418 if (fread(buf, sizeof(buf), 1, fp) < 1)
419 return (E_EOF);
420 b->k = LOAD32(buf);
421 } else {
422 if (getstring(fp, &b->d, 0))
423 return (E_EOF);
424 b->k = strtoul(b->d.buf, 0, 16);
425 }
426 break;
427
428 /* --- Reading of binary data --- */
429
430 case T_FILE:
431 case T_SIGNATURE:
432 if (bin) {
433 octet buf[2];
434 uint32 sz;
435 if (fread(buf, sizeof(buf), 1, fp) < 1)
436 return (E_EOF);
437 sz = LOAD16(buf);
438 if (sz > 4096)
439 return (E_BIN);
440 DENSURE(&b->b, sz);
441 if (fread(b->b.buf + b->b.len, 1, sz, fp) < sz)
442 return (E_EOF);
443 b->b.len += sz;
444 } else {
445 base64_ctx b64;
446 if (getstring(fp, &b->d, 0))
447 return (E_EOF);
448 base64_init(&b64);
449 base64_decode(&b64, b->d.buf, b->d.len, &b->b);
450 base64_decode(&b64, 0, 0, &b->b);
451 DRESET(&b->d);
452 }
453 if (tag == T_FILE && getstring(fp, &b->d, bin))
454 return (E_EOF);
455 break;
456
457 /* --- Anything else --- */
458
459 default:
460 return (E_TAG);
461 }
462
463 return (tag);
464 }
465
466 /* --- @blob@ --- *
467 *
468 * Arguments: @block *b@ = pointer to block to emit
469 * @dstr *d@ = output buffer
470 *
471 * Returns: ---
472 *
473 * Use: Encodes a block in a binary format.
474 */
475
476 static void blob(block *b, dstr *d)
477 {
478 DPUTC(d, b->tag);
479 switch (b->tag) {
480 case T_IDENT:
481 case T_COMMENT:
482 DPUTD(d, &b->d);
483 DPUTC(d, 0);
484 break;
485 case T_DATE:
486 case T_EXPIRE:
487 DENSURE(d, 8);
488 STORE32(d->buf + d->len, ((b->t & ~MASK32) >> 16) >> 16);
489 STORE32(d->buf + d->len + 4, b->t);
490 d->len += 8;
491 break;
492 case T_KEYID:
493 DENSURE(d, 4);
494 STORE32(d->buf + d->len, b->k);
495 d->len += 4;
496 break;
497 case T_FILE:
498 case T_SIGNATURE:
499 DENSURE(d, 2);
500 STORE16(d->buf + d->len, b->b.len);
501 d->len += 2;
502 DPUTD(d, &b->b);
503 if (b->tag == T_FILE) {
504 DPUTD(d, &b->d);
505 DPUTC(d, 0);
506 }
507 break;
508 }
509 }
510
511 /* --- @bwrite@ --- *
512 *
513 * Arguments: @block *b@ = pointer to block to write
514 * @FILE *fp@ = stream to write on
515 *
516 * Returns: ---
517 *
518 * Use: Writes a block on a stream in a textual format.
519 */
520
521 static void bwrite(block *b, FILE *fp)
522 {
523 fputs(tagtab[b->tag], fp);
524 putc(' ', fp);
525 switch (b->tag) {
526 case T_IDENT:
527 case T_COMMENT:
528 putstring(fp, b->d.buf, 0);
529 break;
530 case T_DATE:
531 case T_EXPIRE: {
532 dstr d = DSTR_INIT;
533 timestring(b->t, &d);
534 putstring(fp, d.buf, 0);
535 dstr_destroy(&d);
536 } break;
537 case T_KEYID:
538 fprintf(fp, "%08lx", (unsigned long)b->k);
539 break;
540 case T_FILE:
541 case T_SIGNATURE: {
542 dstr d = DSTR_INIT;
543 base64_ctx b64;
544 base64_init(&b64);
545 b64.maxline = 0;
546 base64_encode(&b64, b->b.buf, b->b.len, &d);
547 base64_encode(&b64, 0, 0, &d);
548 dstr_write(&d, fp);
549 if (b->tag == T_FILE) {
550 putc(' ', fp);
551 putstring(fp, b->d.buf, 0);
552 }
553 } break;
554 }
555 putc('\n', fp);
556 }
557
558 /* --- @bemit@ --- *
559 *
560 * Arguments: @block *b@ = pointer to block to write
561 * @FILE *fp@ = file to write on
562 * @ghash *h@ = pointer to hash function
563 * @unsigned bin@ = binary/text flag
564 *
565 * Returns: ---
566 *
567 * Use: Spits out a block properly.
568 */
569
570 static void bemit(block *b, FILE *fp, ghash *h, unsigned bin)
571 {
572 if (h || (fp && bin)) {
573 dstr d = DSTR_INIT;
574 blob(b, &d);
575 if (h)
576 GH_HASH(h, d.buf, d.len);
577 if (fp && bin)
578 fwrite(d.buf, d.len, 1, fp);
579 }
580 if (fp && !bin)
581 bwrite(b, fp);
582 }
583
584 /*----- Static variables --------------------------------------------------*/
585
586 static const char *keyring = "keyring";
587
588 /*----- Other shared functions --------------------------------------------*/
589
590 /* --- @keyreport@ --- *
591 *
592 * Arguments: @const char *file@ = filename containing the error
593 * @int line@ = line number in file
594 * @const char *err@ = error text message
595 * @void *p@ = unimportant pointer
596 *
597 * Returns: ---
598 *
599 * Use: Reports errors during the opening of a key file.
600 */
601
602 static void keyreport(const char *file, int line, const char *err, void *p)
603 {
604 moan("error in keyring `%s' at line `%s': %s", file, line, err);
605 }
606
607 /* --- @fhash@ --- *
608 *
609 * Arguments: @const gchash *c@ = pointer to hash class
610 * @const char *file@ = file to hash
611 * @void *b@ = pointer to output buffer
612 *
613 * Returns: Zero if it worked, or nonzero for a system error.
614 *
615 * Use: Hashes a file.
616 */
617
618 static int fhash(const gchash *c, const char *file, void *b)
619 {
620 FILE *fp = fopen(file, "rb");
621 ghash *h = GH_INIT(c);
622 char buf[4096];
623 size_t sz;
624 int rc = 0;
625
626 if (!fp)
627 return (-1);
628 while ((sz = fread(buf, 1, sizeof(buf), fp)) > 0)
629 GH_HASH(h, buf, sz);
630 if (ferror(fp))
631 rc = -1;
632 GH_DONE(h, b);
633 GH_DESTROY(h);
634 fclose(fp);
635 return (rc);
636 }
637
638 /* --- @fhex@ --- *
639 *
640 * Arguments: @FILE *fp@ = file to write on
641 * @const void *p@ = pointer to data to be written
642 * @size_t sz@ = size of the data to write
643 *
644 * Returns: ---
645 *
646 * Use: Emits a hex dump to a stream.
647 */
648
649 static void fhex(FILE *fp, const void *p, size_t sz)
650 {
651 const octet *q = p;
652 if (!sz)
653 return;
654 for (;;) {
655 fprintf(fp, "%02x", *q++);
656 sz--;
657 if (!sz)
658 break;
659 }
660 }
661
662 /*----- Signature generation ----------------------------------------------*/
663
664 static int sign(int argc, char *argv[])
665 {
666 #define f_raw 1u
667 #define f_bin 2u
668 #define f_bogus 4u
669
670 unsigned f = 0;
671 const char *ki = "dsig";
672 key_file kf;
673 key *k;
674 sig *s;
675 time_t exp = KEXP_EXPIRE;
676 unsigned verb = 0;
677 const char *ifile = 0;
678 const char *ofile = 0;
679 const char *c = 0;
680 const char *err;
681 FILE *ifp, *ofp;
682 dstr d = DSTR_INIT;
683 block b;
684 int e;
685
686 for (;;) {
687 static struct option opts[] = {
688 { "null", 0, 0, '0' },
689 { "binary", 0, 0, 'b' },
690 { "verbose", 0, 0, 'v' },
691 { "quiet", 0, 0, 'q' },
692 { "comment", OPTF_ARGREQ, 0, 'c' },
693 { "file", OPTF_ARGREQ, 0, 'f' },
694 { "output", OPTF_ARGREQ, 0, 'o' },
695 { "key", OPTF_ARGREQ, 0, 'k' },
696 { "expire", OPTF_ARGREQ, 0, 'e' },
697 { 0, 0, 0, 0 }
698 };
699 int i = mdwopt(argc, argv, "+0vqb" "c:" "f:o:" "k:e:", opts, 0, 0, 0);
700 if (i < 0)
701 break;
702 switch (i) {
703 case '0':
704 f |= f_raw;
705 break;
706 case 'b':
707 f |= f_bin;
708 break;
709 case 'v':
710 verb++;
711 break;
712 case 'q':
713 if (verb > 0)
714 verb--;
715 break;
716 case 'c':
717 c = optarg;
718 break;
719 case 'f':
720 ifile = optarg;
721 break;
722 case 'o':
723 ofile = optarg;
724 break;
725 case 'k':
726 ki = optarg;
727 break;
728 case 'e':
729 if (strcmp(optarg, "forever") == 0)
730 exp = KEXP_FOREVER;
731 else if ((exp = get_date(optarg, 0)) == -1)
732 die(EXIT_FAILURE, "bad expiry time");
733 break;
734 default:
735 f |= f_bogus;
736 break;
737 }
738 }
739 if (optind != argc || (f & f_bogus))
740 die(EXIT_FAILURE, "Usage: sign [-options]");
741
742 /* --- Locate the signing key --- */
743
744 if (key_open(&kf, keyring, KOPEN_WRITE, keyreport, 0))
745 die(EXIT_FAILURE, "couldn't open keyring `%s'", keyring);
746 if ((k = key_bytag(&kf, ki)) == 0)
747 die(EXIT_FAILURE, "couldn't find key `%s'", ki);
748 key_fulltag(k, &d);
749 if (exp == KEXP_FOREVER && k->exp != KEXP_FOREVER) {
750 die(EXIT_FAILURE, "key `%s' expires: can't create nonexpiring signature",
751 d.buf);
752 }
753 s = getsig(k, "dsig", 1);
754
755 /* --- Check the key --- */
756
757 if ((err = s->ops->check(s)) != 0)
758 moan("key `%s' fails check: %s", d.buf, err);
759
760 /* --- Open files --- */
761
762 if (!ifile)
763 ifp = stdin;
764 else if ((ifp = fopen(ifile, (f & f_raw) ? "rb" : "r")) == 0) {
765 die(EXIT_FAILURE, "couldn't open input file `%s': %s",
766 ifile, strerror(errno));
767 }
768
769 if (!ofile)
770 ofp = stdout;
771 else if ((ofp = fopen(ofile, (f & f_bin) ? "wb" : "w")) == 0) {
772 die(EXIT_FAILURE, "couldn't open output file `%s': %s",
773 ofile, strerror(errno));
774 }
775
776 /* --- Emit the start of the output --- */
777
778 binit(&b); b.tag = T_IDENT;
779 dstr_putf(&b.d, "%s, Catacomb version " VERSION, QUIS);
780 bemit(&b, ofp, 0, f & f_bin);
781
782 breset(&b); b.tag = T_KEYID; b.k = k->id;
783 bemit(&b, ofp, 0, f & f_bin);
784
785 /* --- Start hashing, and emit the datestamps and things --- */
786
787 {
788 time_t now = time(0);
789
790 breset(&b); b.tag = T_DATE; b.t = now; bemit(&b, ofp, s->h, f & f_bin);
791 if (exp == KEXP_EXPIRE)
792 exp = now + 86400 * 28;
793 breset(&b); b.tag = T_EXPIRE; b.t = exp; bemit(&b, ofp, s->h, f & f_bin);
794 if (c) {
795 breset(&b); b.tag = T_COMMENT; DPUTS(&b.d, c);
796 bemit(&b, ofp, s->h, f & f_bin);
797 }
798
799 if (!(f & f_bin))
800 putc('\n', ofp);
801 }
802
803 /* --- Now hash the various files --- */
804
805 for (;;) {
806
807 /* --- Stop on an output error --- */
808
809 if (ferror(ofp)) {
810 f |= f_bogus;
811 break;
812 }
813
814 /* --- Read the next filename to hash --- */
815
816 breset(&b);
817 if (getstring(ifp, &b.d, f & f_raw))
818 break;
819 b.tag = T_FILE;
820 DENSURE(&b.b, GH_CLASS(s->h)->hashsz);
821 if (fhash(GH_CLASS(s->h), b.d.buf, b.b.buf)) {
822 moan("Error reading `%s': %s", b.d.buf, strerror(errno));
823 f |= f_bogus;
824 } else {
825 b.b.len += GH_CLASS(s->h)->hashsz;
826 if (verb) {
827 fhex(stderr, b.b.buf, b.b.len);
828 fprintf(stderr, " %s\n", b.d.buf);
829 }
830 bemit(&b, ofp, s->h, f & f_bin);
831 }
832 }
833
834 /* --- Create the signature --- */
835
836 if (!(f & f_bogus)) {
837 breset(&b);
838 b.tag = T_SIGNATURE;
839 if ((e = s->ops->doit(s, &b.b)) != 0) {
840 moan("error creating signature: %s", key_strerror(e));
841 f |= f_bogus;
842 }
843 if (!(f & f_bogus)) {
844 bemit(&b, ofp, 0, f & f_bin);
845 key_used(&kf, k, exp);
846 }
847 }
848
849 /* --- Tidy up at the end --- */
850
851 freesig(s);
852 bdestroy(&b);
853 if (ifile)
854 fclose(ifp);
855 if (ofile) {
856 if (fclose(ofp))
857 f |= f_bogus;
858 } else {
859 if (fflush(ofp))
860 f |= f_bogus;
861 }
862 if ((e = key_close(&kf)) != 0) {
863 switch (e) {
864 case KWRITE_FAIL:
865 die(EXIT_FAILURE, "couldn't write file `%s': %s",
866 keyring, strerror(errno));
867 case KWRITE_BROKEN:
868 die(EXIT_FAILURE, "keyring file `%s' broken: %s (repair manually)",
869 keyring, strerror(errno));
870 }
871 }
872 if (f & f_bogus)
873 die(EXIT_FAILURE, "error(s) occurred while creating signature");
874 return (EXIT_SUCCESS);
875
876 #undef f_raw
877 #undef f_bin
878 #undef f_bogus
879 }
880
881 /*----- Signature verification --------------------------------------------*/
882
883 static int verify(int argc, char *argv[])
884 {
885 #define f_bogus 1u
886 #define f_bin 2u
887 #define f_ok 4u
888
889 unsigned f = 0;
890 unsigned verb = 1;
891 key_file kf;
892 key *k = 0;
893 sig *s;
894 dstr d = DSTR_INIT;
895 const char *err;
896 FILE *fp;
897 block b;
898 int e;
899
900 /* --- Parse the options --- */
901
902 for (;;) {
903 static struct option opts[] = {
904 { "verbose", 0, 0, 'v' },
905 { "quiet", 0, 0, 'q' },
906 { 0, 0, 0, 0 }
907 };
908 int i = mdwopt(argc, argv, "+vq", opts, 0, 0, 0);
909 if (i < 0)
910 break;
911 switch (i) {
912 case 'v':
913 verb++;
914 break;
915 case 'q':
916 if (verb)
917 verb--;
918 break;
919 default:
920 f |= f_bogus;
921 break;
922 }
923 }
924 argc -= optind;
925 argv += optind;
926 if ((f & f_bogus) || argc > 1)
927 die(EXIT_FAILURE, "Usage: verify [-qv] [file]");
928
929 /* --- Open the key file, and start reading the input file --- */
930
931 if (key_open(&kf, keyring, KOPEN_READ, keyreport, 0))
932 die(EXIT_FAILURE, "couldn't open keyring `%s'\n", keyring);
933 if (argc < 1)
934 fp = stdin;
935 else {
936 if ((fp = fopen(argv[0], "rb")) == 0) {
937 die(EXIT_FAILURE, "couldn't open file `%s': %s\n",
938 argv[0], strerror(errno));
939 }
940 if (getc(fp) == 0) {
941 ungetc(0, fp);
942 f |= f_bin;
943 } else {
944 fclose(fp);
945 if ((fp = fopen(argv[0], "r")) == 0) {
946 die(EXIT_FAILURE, "couldn't open file `%s': %s\n",
947 argv[0], strerror(errno));
948 }
949 }
950 }
951
952 /* --- Read the introductory matter --- */
953
954 binit(&b);
955 for (;;) {
956 breset(&b);
957 e = bget(&b, fp, f & f_bin);
958 if (e < 0)
959 die(EXIT_FAILURE, "error reading packet: %s", errtab[-e]);
960 if (e >= T_BEGIN)
961 break;
962 switch (e) {
963 case T_IDENT:
964 if (verb > 2)
965 printf("INFO ident: `%s'\n", b.d.buf);
966 break;
967 case T_KEYID:
968 if ((k = key_byid(&kf, b.k)) == 0) {
969 if (verb)
970 printf("FAIL key %08lx not found\n", (unsigned long)b.k);
971 exit(EXIT_FAILURE);
972 }
973 if (verb > 2) {
974 DRESET(&b.d);
975 key_fulltag(k, &b.d);
976 printf("INFO key: %s\n", b.d.buf);
977 }
978 break;
979 default:
980 die(EXIT_FAILURE, "(internal) unknown packet type\n");
981 break;
982 }
983 }
984
985 /* --- Initialize the hash function and start reading hashed packets --- */
986
987 if (!k) {
988 if (verb)
989 puts("FAIL no keyid packet found");
990 exit(EXIT_FAILURE);
991 }
992
993 s = getsig(k, "dsig", 0);
994 if (verb && (err = s->ops->check(s)) != 0)
995 printf("WARN public key fails check: %s", err);
996
997 for (;;) {
998 switch (e) {
999 case T_COMMENT:
1000 if (verb > 1)
1001 printf("INFO comment: `%s'\n", b.d.buf);
1002 bemit(&b, 0, s->h, 0);
1003 break;
1004 case T_DATE:
1005 if (verb > 2) {
1006 DRESET(&b.d);
1007 timestring(b.t, &b.d);
1008 printf("INFO date: %s\n", b.d.buf);
1009 }
1010 bemit(&b, 0, s->h, 0);
1011 break;
1012 case T_EXPIRE: {
1013 time_t now = time(0);
1014 if (b.t < now) {
1015 if (verb > 1)
1016 puts("BAD signature has expired");
1017 f |= f_bogus;
1018 }
1019 if (verb > 2) {
1020 DRESET(&b.d);
1021 timestring(b.t, &b.d);
1022 printf("INFO expires: %s\n", b.d.buf);
1023 }
1024 bemit(&b, 0, s->h, 0);
1025 } break;
1026 case T_FILE:
1027 DRESET(&d);
1028 DENSURE(&d, GH_CLASS(s->h)->hashsz);
1029 if (fhash(GH_CLASS(s->h), b.d.buf, d.buf)) {
1030 if (verb > 1) {
1031 printf("BAD error reading file `%s': %s\n",
1032 b.d.buf, strerror(errno));
1033 }
1034 f |= f_bogus;
1035 } else if (b.b.len != GH_CLASS(s->h)->hashsz ||
1036 memcmp(d.buf, b.b.buf, b.b.len) != 0) {
1037 if (verb > 1)
1038 printf("BAD file `%s' has incorrect hash\n", b.d.buf);
1039 f |= f_bogus;
1040 } else if (verb > 3) {
1041 fputs("INFO hash: ", stdout);
1042 fhex(stdout, b.b.buf, b.b.len);
1043 printf(" %s\n", b.d.buf);
1044 }
1045 bemit(&b, 0, s->h, 0);
1046 break;
1047 case T_SIGNATURE:
1048 if (s->ops->doit(s, &b.b)) {
1049 if (verb > 1)
1050 puts("BAD bad signature");
1051 f |= f_bogus;
1052 } else if (verb > 2)
1053 puts("INFO good signature");
1054 goto done;
1055 default:
1056 if (verb)
1057 printf("FAIL invalid packet type %i\n", e);
1058 exit(EXIT_FAILURE);
1059 break;
1060 }
1061 breset(&b);
1062 e = bget(&b, fp, f & f_bin);
1063 if (e < 0) {
1064 if (verb)
1065 printf("FAIL error reading packet: %s\n", errtab[-e]);
1066 exit(EXIT_FAILURE);
1067 }
1068 }
1069 done:
1070 bdestroy(&b);
1071 dstr_destroy(&d);
1072 freesig(s);
1073 key_close(&kf);
1074 if (fp != stdin)
1075 fclose(fp);
1076 if (verb) {
1077 if (f & f_bogus)
1078 puts("FAIL signature invalid");
1079 else
1080 puts("OK signature verified");
1081 }
1082 return (f & f_bogus ? EXIT_FAILURE : EXIT_SUCCESS);
1083
1084 #undef f_bogus
1085 #undef f_bin
1086 #undef f_ok
1087 }
1088
1089 /*----- Main code ---------------------------------------------------------*/
1090
1091 typedef struct cmd {
1092 const char *name;
1093 int (*func)(int /*argc*/, char */*argv*/[]);
1094 const char *usage;
1095 const char *help;
1096 } cmd;
1097
1098 static cmd cmdtab[] = {
1099 { "sign", sign,
1100 "sign [-0bqv] [-c comment] [-k tag] [-e expire] [-f file] [-o output]",
1101 "\
1102 Options:\n\
1103 \n\
1104 -0, --null Read null-terminated filenames from stdin.\n\
1105 -b, --binary Produce a binary output file.\n\
1106 -q, --quiet Produce fewer messages while working.\n\
1107 -v, --verbose Produce more messages while working.\n\
1108 -c, --comment=COMMENT Include COMMENT in the output file.\n\
1109 -f, --file=FILE Read filenames to hash from FILE.\n\
1110 -o, --output=FILE Write the signed result to FILE.\n\
1111 -k, --key=TAG Use a key named by TAG.\n\
1112 -e, --expire=TIME The signature should expire after TIME.\n\
1113 " },
1114 { "verify", verify,
1115 "verify [-qv] [file]", "\
1116 Options:\n\
1117 \n\
1118 -q, --quiet Produce fewer messages while working.\n\
1119 -v, --verbose Produce more messages while working.\n\
1120 " },
1121 { 0, 0, 0 }
1122 };
1123
1124 /* --- @findcmd@ --- *
1125 *
1126 * Arguments: @const char *name@ = a command name
1127 *
1128 * Returns: Pointer to the command structure.
1129 *
1130 * Use: Looks up a command by name. If the command isn't found, an
1131 * error is reported and the program is terminated.
1132 */
1133
1134 static cmd *findcmd(const char *name)
1135 {
1136 cmd *c, *chosen = 0;
1137 size_t sz = strlen(name);
1138
1139 for (c = cmdtab; c->name; c++) {
1140 if (strncmp(name, c->name, sz) == 0) {
1141 if (c->name[sz] == 0) {
1142 chosen = c;
1143 break;
1144 } else if (chosen)
1145 die(EXIT_FAILURE, "ambiguous command name `%s'", name);
1146 else
1147 chosen = c;
1148 }
1149 }
1150 if (!chosen)
1151 die(EXIT_FAILURE, "unknown command name `%s'", name);
1152 return (chosen);
1153 }
1154
1155 static void version(FILE *fp)
1156 {
1157 pquis(fp, "$, Catacomb version " VERSION "\n");
1158 }
1159
1160 static void usage(FILE *fp)
1161 {
1162 pquis(fp, "Usage: $ [-k keyring] command [args]\n");
1163 }
1164
1165 static void help(FILE *fp, char **argv)
1166 {
1167 cmd *c;
1168
1169 if (*argv) {
1170 c = findcmd(*argv);
1171 fprintf(fp, "Usage: %s [-k keyring] %s\n", QUIS, c->usage);
1172 if (c->help) {
1173 fputc('\n', fp);
1174 fputs(c->help, fp);
1175 }
1176 } else {
1177 version(fp);
1178 fputc('\n', fp);
1179 usage(fp);
1180 fputs("\n\
1181 Create and verify signatures on lists of files.\n\
1182 \n", fp);
1183 for (c = cmdtab; c->name; c++)
1184 fprintf(fp, "%s\n", c->usage);
1185 }
1186 }
1187
1188 /* --- @main@ --- *
1189 *
1190 * Arguments: @int argc@ = number of command line arguments
1191 * @char *argv[]@ = vector of command line arguments
1192 *
1193 * Returns: Zero if successful, nonzero otherwise.
1194 *
1195 * Use: Signs or verifies signatures on lists of files. Useful for
1196 * ensuring that a distribution is unmolested.
1197 */
1198
1199 int main(int argc, char *argv[])
1200 {
1201 unsigned f = 0;
1202
1203 #define f_bogus 1u
1204
1205 /* --- Initialize the library --- */
1206
1207 ego(argv[0]);
1208 sub_init();
1209 rand_noisesrc(RAND_GLOBAL, &noise_source);
1210 rand_seed(RAND_GLOBAL, 160);
1211
1212 /* --- Parse options --- */
1213
1214 for (;;) {
1215 static struct option opts[] = {
1216 { "help", 0, 0, 'h' },
1217 { "version", 0, 0, 'v' },
1218 { "usage", 0, 0, 'u' },
1219 { "keyring", OPTF_ARGREQ, 0, 'k' },
1220 { 0, 0, 0, 0 }
1221 };
1222 int i = mdwopt(argc, argv, "+hvu k:", opts, 0, 0, 0);
1223 if (i < 0)
1224 break;
1225 switch (i) {
1226 case 'h':
1227 help(stdout, argv + optind);
1228 exit(0);
1229 break;
1230 case 'v':
1231 version(stdout);
1232 exit(0);
1233 break;
1234 case 'u':
1235 usage(stdout);
1236 exit(0);
1237 case 'k':
1238 keyring = optarg;
1239 break;
1240 default:
1241 f |= f_bogus;
1242 break;
1243 }
1244 }
1245
1246 argc -= optind;
1247 argv += optind;
1248 optind = 0;
1249 if (f & f_bogus || argc < 1) {
1250 usage(stderr);
1251 exit(EXIT_FAILURE);
1252 }
1253
1254 /* --- Dispatch to the correct subcommand handler --- */
1255
1256 return (findcmd(argv[0])->func(argc, argv));
1257
1258 #undef f_bogus
1259 }
1260
1261 /*----- That's all, folks -------------------------------------------------*/