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