keyutil.c: Remove stray tabs and trailing space from the list format.
[u/mdw/catacomb] / dsig.c
CommitLineData
4bedc99b 1/* -*-c-*-
2 *
c65df279 3 * $Id$
4bedc99b 4 *
5 * Verify signatures on distribuitions of files
6 *
7 * (c) 2000 Straylight/Edgeware
8 */
9
45c0fd36 10/*----- Licensing notice --------------------------------------------------*
4bedc99b 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.
45c0fd36 18 *
4bedc99b 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.
45c0fd36 23 *
4bedc99b 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
4bedc99b 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
4bedc99b 47#include "getdate.h"
5c3f75ec 48#include "rand.h"
4bedc99b 49#include "ghash.h"
50#include "key.h"
51#include "key-data.h"
4bedc99b 52#include "noise.h"
5c3f75ec 53#include "cc.h"
4bedc99b 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
64enum {
65 /* --- Block tags --- */
66
67 T_IDENT = 0, /* An identifying marker */
4bedc99b 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
86static const char *tagtab[] = {
4e6e0188 87 "ident:", "keyid:",
4bedc99b 88 "comment:", "date:", "expires:", "file:",
89 "signature:",
90 0
91};
92
93static 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
103typedef 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
122static 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
148again:
149 ch = getc(fp);
d470270a 150 while (isspace(ch))
4bedc99b 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) {
365c07f5 182 case 'a': ch = '\a'; break;
4bedc99b 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);
365c07f5 191 ch = getc(fp);
4bedc99b 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
224static 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
287static 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
308static 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
326static 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
342static 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
359static 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:
4bedc99b 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
476static void blob(block *b, dstr *d)
477{
478 DPUTC(d, b->tag);
479 switch (b->tag) {
480 case T_IDENT:
4bedc99b 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);
c65df279 488 if (b->t == KEXP_FOREVER) {
489 STORE32(d->buf + d->len, 0xffffffff);
490 STORE32(d->buf + d->len + 4, 0xffffffff);
491 } else {
492 STORE32(d->buf + d->len, ((b->t & ~MASK32) >> 16) >> 16);
493 STORE32(d->buf + d->len + 4, b->t);
494 }
4bedc99b 495 d->len += 8;
496 break;
497 case T_KEYID:
498 DENSURE(d, 4);
499 STORE32(d->buf + d->len, b->k);
500 d->len += 4;
501 break;
502 case T_FILE:
503 case T_SIGNATURE:
504 DENSURE(d, 2);
505 STORE16(d->buf + d->len, b->b.len);
506 d->len += 2;
507 DPUTD(d, &b->b);
508 if (b->tag == T_FILE) {
509 DPUTD(d, &b->d);
510 DPUTC(d, 0);
511 }
512 break;
513 }
514}
515
516/* --- @bwrite@ --- *
517 *
518 * Arguments: @block *b@ = pointer to block to write
519 * @FILE *fp@ = stream to write on
520 *
521 * Returns: ---
522 *
523 * Use: Writes a block on a stream in a textual format.
524 */
525
526static void bwrite(block *b, FILE *fp)
527{
528 fputs(tagtab[b->tag], fp);
529 putc(' ', fp);
530 switch (b->tag) {
531 case T_IDENT:
4bedc99b 532 case T_COMMENT:
533 putstring(fp, b->d.buf, 0);
534 break;
535 case T_DATE:
536 case T_EXPIRE: {
537 dstr d = DSTR_INIT;
538 timestring(b->t, &d);
539 putstring(fp, d.buf, 0);
540 dstr_destroy(&d);
541 } break;
542 case T_KEYID:
543 fprintf(fp, "%08lx", (unsigned long)b->k);
544 break;
545 case T_FILE:
546 case T_SIGNATURE: {
547 dstr d = DSTR_INIT;
548 base64_ctx b64;
549 base64_init(&b64);
550 b64.maxline = 0;
551 base64_encode(&b64, b->b.buf, b->b.len, &d);
552 base64_encode(&b64, 0, 0, &d);
553 dstr_write(&d, fp);
554 if (b->tag == T_FILE) {
555 putc(' ', fp);
556 putstring(fp, b->d.buf, 0);
557 }
558 } break;
559 }
560 putc('\n', fp);
561}
562
563/* --- @bemit@ --- *
564 *
565 * Arguments: @block *b@ = pointer to block to write
566 * @FILE *fp@ = file to write on
567 * @ghash *h@ = pointer to hash function
568 * @unsigned bin@ = binary/text flag
569 *
570 * Returns: ---
571 *
572 * Use: Spits out a block properly.
573 */
574
575static void bemit(block *b, FILE *fp, ghash *h, unsigned bin)
576{
577 if (h || (fp && bin)) {
578 dstr d = DSTR_INIT;
579 blob(b, &d);
580 if (h)
4e6e0188 581 GH_HASH(h, d.buf, d.len);
4bedc99b 582 if (fp && bin)
583 fwrite(d.buf, d.len, 1, fp);
584 }
585 if (fp && !bin)
586 bwrite(b, fp);
587}
45c0fd36 588
4bedc99b 589/*----- Static variables --------------------------------------------------*/
590
591static const char *keyring = "keyring";
592
593/*----- Other shared functions --------------------------------------------*/
594
4bedc99b 595/* --- @fhash@ --- *
596 *
597 * Arguments: @const gchash *c@ = pointer to hash class
598 * @const char *file@ = file to hash
e45700eb 599 * @void *b@ = pointer to output buffer
4bedc99b 600 *
601 * Returns: Zero if it worked, or nonzero for a system error.
602 *
603 * Use: Hashes a file.
604 */
605
e45700eb 606static int fhash(const gchash *c, const char *file, void *b)
4bedc99b 607{
608 FILE *fp = fopen(file, "rb");
4e6e0188 609 ghash *h = GH_INIT(c);
4bedc99b 610 char buf[4096];
611 size_t sz;
612 int rc = 0;
613
614 if (!fp)
615 return (-1);
616 while ((sz = fread(buf, 1, sizeof(buf), fp)) > 0)
4e6e0188 617 GH_HASH(h, buf, sz);
4bedc99b 618 if (ferror(fp))
619 rc = -1;
4e6e0188 620 GH_DONE(h, b);
621 GH_DESTROY(h);
4bedc99b 622 fclose(fp);
623 return (rc);
624}
625
626/* --- @fhex@ --- *
627 *
628 * Arguments: @FILE *fp@ = file to write on
629 * @const void *p@ = pointer to data to be written
630 * @size_t sz@ = size of the data to write
631 *
632 * Returns: ---
633 *
634 * Use: Emits a hex dump to a stream.
635 */
636
637static void fhex(FILE *fp, const void *p, size_t sz)
638{
639 const octet *q = p;
640 if (!sz)
641 return;
642 for (;;) {
643 fprintf(fp, "%02x", *q++);
644 sz--;
645 if (!sz)
646 break;
4bedc99b 647 }
45c0fd36 648}
4bedc99b 649
650/*----- Signature generation ----------------------------------------------*/
651
652static int sign(int argc, char *argv[])
653{
16efd15b 654#define f_raw 1u
655#define f_bin 2u
656#define f_bogus 4u
946c3f72 657#define f_nocheck 8u
4bedc99b 658
659 unsigned f = 0;
4e6e0188 660 const char *ki = "dsig";
4bedc99b 661 key_file kf;
662 key *k;
4e6e0188 663 sig *s;
4bedc99b 664 time_t exp = KEXP_EXPIRE;
665 unsigned verb = 0;
666 const char *ifile = 0;
667 const char *ofile = 0;
668 const char *c = 0;
5c3f75ec 669 const char *err;
4bedc99b 670 FILE *ifp, *ofp;
671 dstr d = DSTR_INIT;
672 block b;
673 int e;
674
675 for (;;) {
676 static struct option opts[] = {
677 { "null", 0, 0, '0' },
678 { "binary", 0, 0, 'b' },
679 { "verbose", 0, 0, 'v' },
680 { "quiet", 0, 0, 'q' },
4bedc99b 681 { "comment", OPTF_ARGREQ, 0, 'c' },
682 { "file", OPTF_ARGREQ, 0, 'f' },
683 { "output", OPTF_ARGREQ, 0, 'o' },
4e6e0188 684 { "key", OPTF_ARGREQ, 0, 'k' },
4bedc99b 685 { "expire", OPTF_ARGREQ, 0, 'e' },
946c3f72 686 { "nocheck", OPTF_ARGREQ, 0, 'C' },
4bedc99b 687 { 0, 0, 0, 0 }
688 };
946c3f72 689 int i = mdwopt(argc, argv, "+0vqbC" "c:" "f:o:" "k:e:", opts, 0, 0, 0);
4bedc99b 690 if (i < 0)
691 break;
692 switch (i) {
693 case '0':
694 f |= f_raw;
695 break;
696 case 'b':
697 f |= f_bin;
698 break;
699 case 'v':
700 verb++;
701 break;
702 case 'q':
703 if (verb > 0)
704 verb--;
705 break;
946c3f72 706 case 'C':
707 f |= f_nocheck;
708 break;
4bedc99b 709 case 'c':
710 c = optarg;
711 break;
712 case 'f':
713 ifile = optarg;
714 break;
715 case 'o':
716 ofile = optarg;
717 break;
4bedc99b 718 case 'k':
719 ki = optarg;
720 break;
721 case 'e':
722 if (strcmp(optarg, "forever") == 0)
723 exp = KEXP_FOREVER;
724 else if ((exp = get_date(optarg, 0)) == -1)
725 die(EXIT_FAILURE, "bad expiry time");
726 break;
727 default:
728 f |= f_bogus;
729 break;
730 }
731 }
732 if (optind != argc || (f & f_bogus))
c65df279 733 die(EXIT_FAILURE, "Usage: sign [-OPTIONS]");
4bedc99b 734
735 /* --- Locate the signing key --- */
736
213e565f 737 if (key_open(&kf, keyring, KOPEN_WRITE, key_moan, 0))
4bedc99b 738 die(EXIT_FAILURE, "couldn't open keyring `%s'", keyring);
4e6e0188 739 if ((k = key_bytag(&kf, ki)) == 0)
740 die(EXIT_FAILURE, "couldn't find key `%s'", ki);
4bedc99b 741 key_fulltag(k, &d);
742 if (exp == KEXP_FOREVER && k->exp != KEXP_FOREVER) {
743 die(EXIT_FAILURE, "key `%s' expires: can't create nonexpiring signature",
744 d.buf);
745 }
5c3f75ec 746 s = getsig(k, "dsig", 1);
747
748 /* --- Check the key --- */
749
946c3f72 750 if (!(f & f_nocheck) && (err = s->ops->check(s)) != 0)
5c3f75ec 751 moan("key `%s' fails check: %s", d.buf, err);
4bedc99b 752
753 /* --- Open files --- */
754
755 if (!ifile)
756 ifp = stdin;
757 else if ((ifp = fopen(ifile, (f & f_raw) ? "rb" : "r")) == 0) {
758 die(EXIT_FAILURE, "couldn't open input file `%s': %s",
759 ifile, strerror(errno));
760 }
761
762 if (!ofile)
763 ofp = stdout;
764 else if ((ofp = fopen(ofile, (f & f_bin) ? "wb" : "w")) == 0) {
765 die(EXIT_FAILURE, "couldn't open output file `%s': %s",
766 ofile, strerror(errno));
767 }
768
769 /* --- Emit the start of the output --- */
770
771 binit(&b); b.tag = T_IDENT;
772 dstr_putf(&b.d, "%s, Catacomb version " VERSION, QUIS);
773 bemit(&b, ofp, 0, f & f_bin);
45c0fd36 774
4bedc99b 775 breset(&b); b.tag = T_KEYID; b.k = k->id;
4e6e0188 776 bemit(&b, ofp, 0, f & f_bin);
4bedc99b 777
778 /* --- Start hashing, and emit the datestamps and things --- */
779
780 {
781 time_t now = time(0);
782
4e6e0188 783 breset(&b); b.tag = T_DATE; b.t = now; bemit(&b, ofp, s->h, f & f_bin);
4bedc99b 784 if (exp == KEXP_EXPIRE)
785 exp = now + 86400 * 28;
4e6e0188 786 breset(&b); b.tag = T_EXPIRE; b.t = exp; bemit(&b, ofp, s->h, f & f_bin);
4bedc99b 787 if (c) {
788 breset(&b); b.tag = T_COMMENT; DPUTS(&b.d, c);
4e6e0188 789 bemit(&b, ofp, s->h, f & f_bin);
4bedc99b 790 }
791
792 if (!(f & f_bin))
793 putc('\n', ofp);
794 }
795
796 /* --- Now hash the various files --- */
797
798 for (;;) {
799
800 /* --- Stop on an output error --- */
801
802 if (ferror(ofp)) {
803 f |= f_bogus;
804 break;
805 }
806
807 /* --- Read the next filename to hash --- */
808
809 breset(&b);
810 if (getstring(ifp, &b.d, f & f_raw))
811 break;
812 b.tag = T_FILE;
4e6e0188 813 DENSURE(&b.b, GH_CLASS(s->h)->hashsz);
814 if (fhash(GH_CLASS(s->h), b.d.buf, b.b.buf)) {
4bedc99b 815 moan("Error reading `%s': %s", b.d.buf, strerror(errno));
816 f |= f_bogus;
817 } else {
4e6e0188 818 b.b.len += GH_CLASS(s->h)->hashsz;
4bedc99b 819 if (verb) {
820 fhex(stderr, b.b.buf, b.b.len);
821 fprintf(stderr, " %s\n", b.d.buf);
822 }
4e6e0188 823 bemit(&b, ofp, s->h, f & f_bin);
4bedc99b 824 }
825 }
826
827 /* --- Create the signature --- */
828
829 if (!(f & f_bogus)) {
830 breset(&b);
831 b.tag = T_SIGNATURE;
4e6e0188 832 if ((e = s->ops->doit(s, &b.b)) != 0) {
4bedc99b 833 moan("error creating signature: %s", key_strerror(e));
834 f |= f_bogus;
835 }
836 if (!(f & f_bogus)) {
837 bemit(&b, ofp, 0, f & f_bin);
838 key_used(&kf, k, exp);
839 }
840 }
841
842 /* --- Tidy up at the end --- */
843
4e6e0188 844 freesig(s);
4bedc99b 845 bdestroy(&b);
846 if (ifile)
847 fclose(ifp);
848 if (ofile) {
849 if (fclose(ofp))
850 f |= f_bogus;
851 } else {
852 if (fflush(ofp))
853 f |= f_bogus;
854 }
855 if ((e = key_close(&kf)) != 0) {
856 switch (e) {
857 case KWRITE_FAIL:
858 die(EXIT_FAILURE, "couldn't write file `%s': %s",
859 keyring, strerror(errno));
860 case KWRITE_BROKEN:
861 die(EXIT_FAILURE, "keyring file `%s' broken: %s (repair manually)",
862 keyring, strerror(errno));
863 }
864 }
865 if (f & f_bogus)
866 die(EXIT_FAILURE, "error(s) occurred while creating signature");
867 return (EXIT_SUCCESS);
16efd15b 868
869#undef f_raw
870#undef f_bin
871#undef f_bogus
946c3f72 872#undef f_nocheck
4bedc99b 873}
874
875/*----- Signature verification --------------------------------------------*/
876
877static int verify(int argc, char *argv[])
878{
16efd15b 879#define f_bogus 1u
880#define f_bin 2u
881#define f_ok 4u
946c3f72 882#define f_nocheck 8u
4bedc99b 883
884 unsigned f = 0;
885 unsigned verb = 1;
886 key_file kf;
887 key *k = 0;
4e6e0188 888 sig *s;
4bedc99b 889 dstr d = DSTR_INIT;
5c3f75ec 890 const char *err;
4bedc99b 891 FILE *fp;
892 block b;
893 int e;
894
895 /* --- Parse the options --- */
896
897 for (;;) {
898 static struct option opts[] = {
899 { "verbose", 0, 0, 'v' },
900 { "quiet", 0, 0, 'q' },
946c3f72 901 { "nocheck", 0, 0, 'C' },
4bedc99b 902 { 0, 0, 0, 0 }
903 };
946c3f72 904 int i = mdwopt(argc, argv, "+vqC", opts, 0, 0, 0);
4bedc99b 905 if (i < 0)
906 break;
907 switch (i) {
908 case 'v':
909 verb++;
910 break;
911 case 'q':
912 if (verb)
913 verb--;
914 break;
946c3f72 915 case 'C':
916 f |= f_nocheck;
917 break;
4bedc99b 918 default:
919 f |= f_bogus;
920 break;
921 }
922 }
923 argc -= optind;
924 argv += optind;
925 if ((f & f_bogus) || argc > 1)
946c3f72 926 die(EXIT_FAILURE, "Usage: verify [-qvC] [FILE]");
4bedc99b 927
928 /* --- Open the key file, and start reading the input file --- */
929
213e565f 930 if (key_open(&kf, keyring, KOPEN_READ, key_moan, 0))
4bedc99b 931 die(EXIT_FAILURE, "couldn't open keyring `%s'\n", keyring);
932 if (argc < 1)
933 fp = stdin;
934 else {
935 if ((fp = fopen(argv[0], "rb")) == 0) {
936 die(EXIT_FAILURE, "couldn't open file `%s': %s\n",
937 argv[0], strerror(errno));
938 }
939 if (getc(fp) == 0) {
940 ungetc(0, fp);
941 f |= f_bin;
942 } else {
943 fclose(fp);
944 if ((fp = fopen(argv[0], "r")) == 0) {
945 die(EXIT_FAILURE, "couldn't open file `%s': %s\n",
946 argv[0], strerror(errno));
947 }
948 }
949 }
950
951 /* --- Read the introductory matter --- */
952
953 binit(&b);
4e6e0188 954 for (;;) {
4bedc99b 955 breset(&b);
956 e = bget(&b, fp, f & f_bin);
957 if (e < 0)
4e6e0188 958 die(EXIT_FAILURE, "error reading packet: %s", errtab[-e]);
4bedc99b 959 if (e >= T_BEGIN)
960 break;
961 switch (e) {
962 case T_IDENT:
963 if (verb > 2)
964 printf("INFO ident: `%s'\n", b.d.buf);
965 break;
4bedc99b 966 case T_KEYID:
967 if ((k = key_byid(&kf, b.k)) == 0) {
968 if (verb)
969 printf("FAIL key %08lx not found\n", (unsigned long)b.k);
970 exit(EXIT_FAILURE);
971 }
972 if (verb > 2) {
973 DRESET(&b.d);
974 key_fulltag(k, &b.d);
975 printf("INFO key: %s\n", b.d.buf);
976 }
977 break;
978 default:
979 die(EXIT_FAILURE, "(internal) unknown packet type\n");
980 break;
981 }
982 }
983
984 /* --- Initialize the hash function and start reading hashed packets --- */
985
4bedc99b 986 if (!k) {
987 if (verb)
988 puts("FAIL no keyid packet found");
989 exit(EXIT_FAILURE);
990 }
bd5efd74 991
5c3f75ec 992 s = getsig(k, "dsig", 0);
946c3f72 993 if (!(f & f_nocheck) && verb && (err = s->ops->check(s)) != 0)
5c3f75ec 994 printf("WARN public key fails check: %s", err);
995
4bedc99b 996 for (;;) {
997 switch (e) {
998 case T_COMMENT:
999 if (verb > 1)
1000 printf("INFO comment: `%s'\n", b.d.buf);
4e6e0188 1001 bemit(&b, 0, s->h, 0);
4bedc99b 1002 break;
1003 case T_DATE:
1004 if (verb > 2) {
1005 DRESET(&b.d);
1006 timestring(b.t, &b.d);
1007 printf("INFO date: %s\n", b.d.buf);
1008 }
4e6e0188 1009 bemit(&b, 0, s->h, 0);
4bedc99b 1010 break;
1011 case T_EXPIRE: {
1012 time_t now = time(0);
c65df279 1013 if (b.t != KEXP_FOREVER && b.t < now) {
4bedc99b 1014 if (verb > 1)
1015 puts("BAD signature has expired");
1016 f |= f_bogus;
1017 }
1018 if (verb > 2) {
1019 DRESET(&b.d);
1020 timestring(b.t, &b.d);
1021 printf("INFO expires: %s\n", b.d.buf);
1022 }
4e6e0188 1023 bemit(&b, 0, s->h, 0);
4bedc99b 1024 } break;
1025 case T_FILE:
1026 DRESET(&d);
4e6e0188 1027 DENSURE(&d, GH_CLASS(s->h)->hashsz);
1028 if (fhash(GH_CLASS(s->h), b.d.buf, d.buf)) {
4bedc99b 1029 if (verb > 1) {
1030 printf("BAD error reading file `%s': %s\n",
1031 b.d.buf, strerror(errno));
1032 }
1033 f |= f_bogus;
4e6e0188 1034 } else if (b.b.len != GH_CLASS(s->h)->hashsz ||
4bedc99b 1035 memcmp(d.buf, b.b.buf, b.b.len) != 0) {
1036 if (verb > 1)
1037 printf("BAD file `%s' has incorrect hash\n", b.d.buf);
1038 f |= f_bogus;
1039 } else if (verb > 3) {
1040 fputs("INFO hash: ", stdout);
1041 fhex(stdout, b.b.buf, b.b.len);
1042 printf(" %s\n", b.d.buf);
1043 }
4e6e0188 1044 bemit(&b, 0, s->h, 0);
4bedc99b 1045 break;
1046 case T_SIGNATURE:
4e6e0188 1047 if (s->ops->doit(s, &b.b)) {
1048 if (verb > 1)
1049 puts("BAD bad signature");
4bedc99b 1050 f |= f_bogus;
1051 } else if (verb > 2)
1052 puts("INFO good signature");
1053 goto done;
1054 default:
1055 if (verb)
1056 printf("FAIL invalid packet type %i\n", e);
1057 exit(EXIT_FAILURE);
1058 break;
1059 }
1060 breset(&b);
1061 e = bget(&b, fp, f & f_bin);
1062 if (e < 0) {
1063 if (verb)
1064 printf("FAIL error reading packet: %s\n", errtab[-e]);
1065 exit(EXIT_FAILURE);
1066 }
1067 }
1068done:
1069 bdestroy(&b);
1070 dstr_destroy(&d);
4e6e0188 1071 freesig(s);
4bedc99b 1072 key_close(&kf);
1073 if (fp != stdin)
1074 fclose(fp);
1075 if (verb) {
1076 if (f & f_bogus)
1077 puts("FAIL signature invalid");
1078 else
1079 puts("OK signature verified");
1080 }
1081 return (f & f_bogus ? EXIT_FAILURE : EXIT_SUCCESS);
16efd15b 1082
1083#undef f_bogus
1084#undef f_bin
1085#undef f_ok
946c3f72 1086#undef f_nocheck
4bedc99b 1087}
1088
1089/*----- Main code ---------------------------------------------------------*/
1090
c65df279 1091#define LISTS(LI) \
1092 LI("Lists", list, \
1093 listtab[i].name, listtab[i].name) \
1094 LI("Signature schemes", sig, \
1095 sigtab[i].name, sigtab[i].name) \
1096 LI("Hash functions", hash, \
1097 ghashtab[i], ghashtab[i]->name)
1098
1099MAKELISTTAB(listtab, LISTS)
1100
1101int cmd_show(int argc, char *argv[])
1102{
1103 return (displaylists(listtab, argv + 1));
1104}
1105
1106static int cmd_help(int, char **);
4bedc99b 1107
1108static cmd cmdtab[] = {
c65df279 1109 { "help", cmd_help, "help [COMMAND...]" },
1110 { "show", cmd_show, "show [ITEM...]" },
4bedc99b 1111 { "sign", sign,
946c3f72 1112 "sign [-0bqvC] [-c COMMENT] [-k TAG] [-e EXPIRE]\n\t\
c65df279 1113[-f FILE] [-o OUTPUT]",
5c3f75ec 1114 "\
bd5efd74 1115Options:\n\
1116\n\
1117-0, --null Read null-terminated filenames from stdin.\n\
1118-b, --binary Produce a binary output file.\n\
1119-q, --quiet Produce fewer messages while working.\n\
1120-v, --verbose Produce more messages while working.\n\
946c3f72 1121-C, --nocheck Don't check the private key.\n\
bd5efd74 1122-c, --comment=COMMENT Include COMMENT in the output file.\n\
1123-f, --file=FILE Read filenames to hash from FILE.\n\
1124-o, --output=FILE Write the signed result to FILE.\n\
4e6e0188 1125-k, --key=TAG Use a key named by TAG.\n\
bd5efd74 1126-e, --expire=TIME The signature should expire after TIME.\n\
1127" },
4bedc99b 1128 { "verify", verify,
946c3f72 1129 "verify [-qvC] [FILE]", "\
bd5efd74 1130Options:\n\
1131\n\
1132-q, --quiet Produce fewer messages while working.\n\
1133-v, --verbose Produce more messages while working.\n\
946c3f72 1134-C, --nocheck Don't check the public key.\n\
bd5efd74 1135" },
4bedc99b 1136 { 0, 0, 0 }
1137};
1138
c65df279 1139static int cmd_help(int argc, char **argv)
bd5efd74 1140{
c65df279 1141 sc_help(cmdtab, stdout, argv + 1);
1142 return (0);
bd5efd74 1143}
1144
c65df279 1145void version(FILE *fp)
4bedc99b 1146{
1147 pquis(fp, "$, Catacomb version " VERSION "\n");
1148}
1149
1150static void usage(FILE *fp)
1151{
c65df279 1152 pquis(fp, "Usage: $ [-k KEYRING] COMMAND [ARGS]\n");
4bedc99b 1153}
1154
c65df279 1155void help_global(FILE *fp)
4bedc99b 1156{
c65df279 1157 usage(fp);
1158 fputs("\n\
4bedc99b 1159Create and verify signatures on lists of files.\n\
c65df279 1160\n\
1161Global command-line options:\n\
1162\n\
1163-h, --help [COMMAND...] Show this help message, or help for COMMANDs.\n\
1164-v, --version Show program version number.\n\
1165-u, --usage Show a terse usage message.\n\
1166\n\
1167-k, --keyring=FILE Read keys from FILE.\n",
1168 fp);
4bedc99b 1169}
1170
1171/* --- @main@ --- *
1172 *
1173 * Arguments: @int argc@ = number of command line arguments
1174 * @char *argv[]@ = vector of command line arguments
1175 *
1176 * Returns: Zero if successful, nonzero otherwise.
1177 *
1178 * Use: Signs or verifies signatures on lists of files. Useful for
1179 * ensuring that a distribution is unmolested.
1180 */
1181
1182int main(int argc, char *argv[])
1183{
1184 unsigned f = 0;
4bedc99b 1185
16efd15b 1186#define f_bogus 1u
4bedc99b 1187
1188 /* --- Initialize the library --- */
1189
1190 ego(argv[0]);
1191 sub_init();
1192 rand_noisesrc(RAND_GLOBAL, &noise_source);
1193 rand_seed(RAND_GLOBAL, 160);
1194
1195 /* --- Parse options --- */
1196
1197 for (;;) {
1198 static struct option opts[] = {
1199 { "help", 0, 0, 'h' },
1200 { "version", 0, 0, 'v' },
1201 { "usage", 0, 0, 'u' },
1202 { "keyring", OPTF_ARGREQ, 0, 'k' },
1203 { 0, 0, 0, 0 }
1204 };
1205 int i = mdwopt(argc, argv, "+hvu k:", opts, 0, 0, 0);
1206 if (i < 0)
1207 break;
1208 switch (i) {
1209 case 'h':
c65df279 1210 sc_help(cmdtab, stdout, argv + optind);
4bedc99b 1211 exit(0);
1212 break;
1213 case 'v':
1214 version(stdout);
1215 exit(0);
1216 break;
1217 case 'u':
1218 usage(stdout);
1219 exit(0);
1220 case 'k':
1221 keyring = optarg;
1222 break;
1223 default:
1224 f |= f_bogus;
1225 break;
1226 }
1227 }
1228
1229 argc -= optind;
1230 argv += optind;
1231 optind = 0;
1232 if (f & f_bogus || argc < 1) {
1233 usage(stderr);
1234 exit(EXIT_FAILURE);
1235 }
1236
1237 /* --- Dispatch to the correct subcommand handler --- */
1238
c65df279 1239 return (findcmd(cmdtab, argv[0])->cmd(argc, argv));
16efd15b 1240
1241#undef f_bogus
4bedc99b 1242}
1243
1244/*----- That's all, folks -------------------------------------------------*/