chiark / gitweb /
671cea6e8c7e79f42b2f6720ee4be3bc63db3bdf
[catacomb] / progs / catsign.c
1 /* -*-c-*-
2  *
3  * Sign files
4  *
5  * (c) 2005 Straylight/Edgeware
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This file is part of Catacomb.
11  *
12  * Catacomb is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU Library General Public License as
14  * published by the Free Software Foundation; either version 2 of the
15  * License, or (at your option) any later version.
16  *
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 Library General Public License for more details.
21  *
22  * You should have received a copy of the GNU Library General Public
23  * License along with Catacomb; if not, write to the Free
24  * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
25  * MA 02111-1307, USA.
26  */
27
28 /*----- Header files ------------------------------------------------------*/
29
30 #define _FILE_OFFSET_BITS 64
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 #include <time.h>
40
41 #include <mLib/base64.h>
42 #include <mLib/dstr.h>
43 #include <mLib/macros.h>
44 #include <mLib/mdwopt.h>
45 #include <mLib/quis.h>
46 #include <mLib/report.h>
47 #include <mLib/sub.h>
48
49 #include "buf.h"
50 #include "rand.h"
51 #include "noise.h"
52 #include "mprand.h"
53 #include "key.h"
54 #include "getdate.h"
55 #include "cc.h"
56
57 #include "ectab.h"
58 #include "ptab.h"
59
60 /*----- Static variables --------------------------------------------------*/
61
62 static const char *keyring = "keyring";
63
64 /*----- Data formats ------------------------------------------------------*
65  *
66  * Our crypto stuff is split into three parts: a header describing the key,
67  * the message itself, and the signature.  In a detached signature, the
68  * message is separate, and the header and signature are combined into one
69  * encoded chunk.  In an attached signature, the message is included.  There
70  * are two forms of message: binary and text.  Binary messages are divided
71  * into chunks, each preceded by a 2-octet length, and terminated by a
72  * zero-length chunk.  Text messages are byte-stuffed, as for RFC821, and
73  * trailing whitespace before a newline is ignored.
74  */
75
76 typedef struct sigmsg {
77   unsigned f;                           /* Flags */
78 #define F_DETACH 1u
79 #define F_BINARY 2u
80 #define F_SIGMASK 3u
81 #define F_HASHMASK (F_BINARY)
82   uint32 keyid;                         /* Key identifier */
83   time_t t;                             /* When the signature was made */
84   dstr kh;                              /* Key fingerprint (sanity check) */
85   dstr sig;                             /* Signature */
86   sig *s;                               /* Signature algorithm */
87 } sigmsg;
88
89 #define F_MIDLINE 16u
90 #define F_EOF 32u
91 #define F_NOCLOSE 64u
92 #define F_BOGUS 128u
93 #define F_BUFFER 256u
94 #define F_UTC 512u
95 #define F_NOCHECK 1024u
96 #define F_PROGRESS 2048u
97
98 /*----- Chunk I/O ---------------------------------------------------------*/
99
100 static void chunk_write(enc *e, const void *p, size_t n)
101 {
102   octet b[2];
103
104   assert(n <= 0xffff);
105   STORE16(b, n);
106   if (e->ops->write(e, b, 2) || e->ops->write(e, p, n))
107     die(EXIT_FAILURE, "error writing output: %s", strerror(errno));
108 }
109
110 static size_t chunk_read(enc *e, void *p)
111 {
112   octet b[2];
113   size_t n;
114
115   if (e->ops->read(e, b, 2) != 2)
116     goto err;
117   n = LOAD16(b);
118   if (n && e->ops->read(e, p, n) != n)
119     goto err;
120   return (n);
121
122 err:
123   if (!errno) die(EXIT_FAILURE, "unexpected end-of-file on input");
124   else die(EXIT_FAILURE, "error reading input: %s", strerror(errno));
125   return (0);
126 }
127
128 /*----- Message canonification --------------------------------------------*/
129
130 #define MSGBUFSZ 65536
131 #define MSGBUFTHRESH 32768
132
133 typedef struct msgcanon {
134   unsigned f;
135   FILE *fp;
136   enc *e;
137   fprogress ff;
138   size_t (*read)(struct msgcanon *, void *);
139   void (*write)(struct msgcanon *, const void *, size_t);
140   void (*close)(struct msgcanon *);
141 } msgcanon;
142
143 #define MC_INIT { 0 }
144
145 static size_t textread(msgcanon *m, void *bp)
146 {
147   size_t n = 0, nsp = 0;
148   int ch;
149   char *b = bp;
150   unsigned f = m->f;
151
152   if (f & F_EOF) return (0);
153   for (;;) {
154     if ((ch = getc(m->fp)) == EOF) goto eof;
155     if (!(f & (F_DETACH | F_MIDLINE)) && ch == '.') {
156       ch = getc(m->fp);
157       if (ch == '\n') goto eof;
158     }
159     if (ch == '\n') {
160       n -= nsp; nsp = 0;
161       if (n >= MSGBUFTHRESH) goto full;
162       b[n++] = ch;
163       f &= ~F_MIDLINE;
164     } else if (isspace(ch)) {
165       f |= F_MIDLINE;
166       if (n >= MSGBUFSZ) goto full;
167       b[n++] = ch; nsp++;
168     } else {
169       f |= F_MIDLINE;
170       nsp = 0;
171       if (n >= MSGBUFTHRESH) goto full;
172       b[n++] = ch;
173     }
174   }
175 eof:
176   f |= F_EOF;
177   goto done;
178 full:
179   ungetc(ch, m->fp);
180 done:
181   m->f = f;
182   if (m->f & F_PROGRESS) fprogress_update(&m->ff, n);
183   return (n);
184 }
185
186 static void textwrite(msgcanon *m, const void *bp, size_t n)
187 {
188   const char *p = bp, *l = p + n;
189   unsigned f = m->f;
190
191   while (p < l) {
192     if (!(f & (F_MIDLINE | F_DETACH)) &&
193         (*p == '.' || *p == '-'))
194       putc('.', m->fp);
195     if (*p == '\n') f &= ~F_MIDLINE;
196     else f |= F_MIDLINE;
197     putc(*p, m->fp);
198     p++;
199   }
200   m->f = f;
201 }
202
203 static size_t binreadembed(msgcanon *m, void *bp)
204 {
205   size_t n = chunk_read(m->e, bp);
206   if (m->f & F_PROGRESS) fprogress_update(&m->ff, n);
207   return (n);
208 }
209
210 static size_t binreaddetach(msgcanon *m, void *bp)
211 {
212   size_t n = fread(bp, 1, MSGBUFSZ - 1, m->fp);
213   if (m->f & F_PROGRESS) fprogress_update(&m->ff, n);
214   return (n);
215 }
216
217 static void binwriteembed(msgcanon *m, const void *bp, size_t n)
218   { chunk_write(m->e, bp, n); }
219
220 static void nullwrite(msgcanon *m, const void *bp, size_t n) { ; }
221
222 static void mcsetup_readfile(msgcanon *m, unsigned f, const char *fn)
223 {
224   m->f = F_DETACH | (f & (F_BINARY | F_PROGRESS));
225   if (!fn || strcmp(fn, "-") == 0) {
226     m->fp = stdin;
227     m->f |= F_NOCLOSE;
228   } else if ((m->fp = fopen(fn, (f & F_BINARY) ? "rb" : "r")) == 0)
229     die(EXIT_FAILURE, "couldn't open file `%s': %s", fn, strerror(errno));
230   if (m->f & F_PROGRESS) {
231     if (fprogress_init(&m->ff, fn, m->fp)) {
232       die(EXIT_FAILURE, "failed to initialize progress indicator: %s",
233           strerror(errno));
234     }
235   }
236   m->read = (f & F_BINARY) ? binreaddetach : textread;
237 }
238
239 static void mcsetup_writenull(msgcanon *m) { m->write = nullwrite; }
240
241 static void mcsetup_read(msgcanon *m, unsigned f, enc **ee,
242                          const char *fn, const char *dfn)
243 {
244   enc *e = *ee;
245
246   m->f = f | F_NOCLOSE;
247
248   if (dfn && !(f & F_DETACH)) die(EXIT_FAILURE, "signature is not detached");
249   if (f & F_BINARY) {
250     if (!(f & F_DETACH)) {
251       m->e = e;
252       *ee = 0;
253       m->fp = e->fp;
254       m->read = binreadembed;
255       if (m->f & F_PROGRESS) {
256         if (fprogress_init(&m->ff, fn, m->fp)) {
257           die(EXIT_FAILURE, "failed to initialize progress indicator: %s",
258               strerror(errno));
259         }
260       }
261     } else {
262       m->read = binreaddetach;
263       if (!dfn || strcmp(dfn, "-") == 0)
264         m->fp = stdin;
265       else if ((m->fp = fopen(dfn, "rb")) == 0)
266         die(EXIT_FAILURE, "can't open `%s': %s", dfn, strerror(errno));
267       else
268         m->f &= ~F_NOCLOSE;
269       if (m->f & F_PROGRESS) {
270         if (fprogress_init(&m->ff, dfn, m->fp)) {
271           die(EXIT_FAILURE, "failed to initialize progress indicator: %s",
272               strerror(errno));
273         }
274       }
275     }
276   } else {
277     m->read = textread;
278     if (!(f & F_DETACH)) {
279       if (e->ops->decdone(e) || ferror(e->fp))
280         die(EXIT_FAILURE, "error at end of signature header");
281       m->fp = e->fp;
282       e->ops->destroy(e);
283       *ee = 0;
284       if (m->f & F_PROGRESS) {
285         if (fprogress_init(&m->ff, fn, m->fp)) {
286           die(EXIT_FAILURE, "failed to initialize progress indicator: %s",
287               strerror(errno));
288         }
289       }
290     } else {
291       if (!dfn || strcmp(dfn, "-") == 0)
292         m->fp = stdin;
293       else if ((m->fp = fopen(dfn, "r")) == 0)
294         die(EXIT_FAILURE, "can't read file `%s': %s", dfn, strerror(errno));
295       else
296         m->f &= ~F_NOCLOSE;
297       if (m->f & F_PROGRESS) {
298         if (fprogress_init(&m->ff, dfn, m->fp)) {
299           die(EXIT_FAILURE, "failed to initialize progress indicator: %s",
300               strerror(errno));
301         }
302       }
303     }
304   }
305 }
306
307 static void mcsetup_write(msgcanon *m, unsigned f, enc **ee)
308 {
309   enc *e = *ee;
310
311   m->f = f | F_NOCLOSE;
312
313   if (f & F_DETACH)
314     m->write = nullwrite;
315   else if (f & F_BINARY) {
316     m->e = e;
317     *ee = 0;
318     m->fp = e->fp;
319     m->write = binwriteembed;
320   } else {
321     if (e->ops->encdone(e) || ferror(e->fp))
322       die(EXIT_FAILURE, "error at end of signature header");
323     m->fp = e->fp;
324     e->ops->destroy(e);
325     *ee = 0;
326     m->write = textwrite;
327   }
328 }
329
330 static void mc_endread(msgcanon *m, const encops *eops, enc **ee)
331 {
332   if (!(m->f & F_DETACH)) {
333     if (m->f & F_BINARY)
334       *ee = m->e;
335     else
336       *ee = initdec(eops, m->fp, checkbdry, "CATSIGN SIGNATURE");
337   }
338   if (m->fp && !(m->f & F_NOCLOSE)) {
339     if (ferror(m->fp) || fclose(m->fp))
340       die(EXIT_FAILURE, "error closing message file: %s", strerror(errno));
341   }
342   if (m->f & F_PROGRESS) fprogress_done(&m->ff);
343 }
344
345 static void mc_endwrite(msgcanon *m, const encops *eops, enc **ee)
346 {
347   if (!(m->f & F_BINARY)) {
348     if (m->f & F_MIDLINE) putc('\n', m->fp);
349     if (!(m->f & F_DETACH)) {
350       putc('.', m->fp); putc('\n', m->fp);
351       *ee = initenc(eops, m->fp, "CATSIGN SIGNATURE");
352     }
353   } else if (!(m->f & F_DETACH)) {
354     chunk_write(m->e, 0, 0);
355     *ee = m->e;
356   }
357   if (m->fp && !(m->f & F_NOCLOSE)) {
358     if (fflush(m->fp) || ferror(m->fp) || fclose(m->fp))
359       die(EXIT_FAILURE, "error closing message file: %s", strerror(errno));
360   }
361 }
362
363 /*----- Signature reading and writing -------------------------------------*/
364
365 static void sig_init(sigmsg *s, unsigned f, uint32 keyid)
366 {
367   s->f = f & F_SIGMASK;
368   s->keyid = keyid;
369   time(&s->t);
370   s->s = 0;
371   dstr_create(&s->kh);
372   dstr_create(&s->sig);
373 }
374
375 static void sig_destroy(sigmsg *s)
376 {
377   dstr_destroy(&s->kh);
378   dstr_destroy(&s->sig);
379   if (s->s) freesig(s->s);
380 }
381
382 static void sigtobuffer(sigmsg *s, buf *b, int hashp)
383 {
384   kludge64 t;
385
386   ASSIGN64(t, s->t);
387   if (hashp) buf_putu16(b, s->f & F_HASHMASK);
388   else buf_putu16(b, s->f & F_SIGMASK);
389   buf_putu32(b, s->keyid);
390   buf_putu32(b, HI64(t));
391   buf_putu32(b, LO64(t));
392   buf_putdstr16(b, &s->kh);
393   assert(BOK(b));
394 }
395
396 static void dohash(ghash *h, const void *p, size_t n)
397 {
398   GH_HASH(h, p, n);
399 }
400
401 static void sig_hash(sigmsg *s)
402 {
403   octet bb[16384];
404   buf b;
405
406   buf_init(&b, bb, sizeof(bb));
407   sigtobuffer(s, &b, 1);
408   dohash(s->s->h, BBASE(&b), BLEN(&b));
409 }
410
411 static void keyhash(key *k, sig *s, dstr *d)
412 {
413   ghash *h;
414   key_filter kf;
415
416   h = GH_INIT(s->ch);
417   kf.f = KCAT_PUB;
418   kf.m = KF_CATMASK;
419   key_fingerprint(k, h, &kf);
420   dstr_ensure(d, GH_CLASS(h)->hashsz);
421   GH_DONE(h, d->buf + d->len);
422   d->len += GH_CLASS(h)->hashsz;
423   GH_DESTROY(h);
424 }
425
426 static void sig_writeheader(enc *e, sigmsg *s)
427 {
428   octet bb[16384];
429   buf b;
430
431   buf_init(&b, bb, sizeof(bb));
432   sigtobuffer(s, &b, 0);
433   chunk_write(e, BBASE(&b), BLEN(&b));
434 }
435
436 static void sig_writesig(enc *e, sigmsg *s)
437   { chunk_write(e, s->sig.buf, s->sig.len); }
438
439 static void NORETURN diechoke(const char *m, void *p)
440   { die(EXIT_FAILURE, "%s%s%s", (const char *)p, p ? ": " : "", m); }
441
442 static void sig_readheader(enc *e, sigmsg *s,
443                            void NORETURN (*choke)(const char *, void *),
444                            void *p)
445 {
446   uint16 f;
447   octet bb[MSGBUFSZ];
448   uint32 x, y;
449   kludge64 t;
450   buf b;
451   size_t n;
452
453   n = chunk_read(e, bb);
454   buf_init(&b, bb, n);
455   if (buf_getu16(&b, &f)) choke("missing flags", p);
456   if (buf_getu32(&b, &x)) choke("missing keyid", p);
457   sig_init(s, f, x);
458   if (buf_getu32(&b, &x) || buf_getu32(&b, &y))
459     choke("missing datestamp", p);
460   SET64(t, x, y); s->t = GET64(time_t, t);
461   if (buf_getdstr16(&b, &s->kh))
462     choke("missing key hash", p);
463   if (BLEFT(&b))
464     choke("junk at end", p);
465 }
466
467 static void sig_readsig(enc *e, sigmsg *s)
468 {
469   octet bb[MSGBUFSZ];
470   size_t n;
471
472   n = chunk_read(e, bb);
473   dstr_putm(&s->sig, bb, n);
474 }
475
476 /*----- Signing -----------------------------------------------------------*/
477
478 static int sign(int argc, char *argv[])
479 {
480   const char *ef = "binary", *fn = 0, *of = 0, *kn = "ccsig", *err;
481   unsigned f = 0;
482   key_file kf;
483   key *k;
484   sigmsg s;
485   FILE *ofp = 0;
486   int i;
487   char bb[MSGBUFSZ];
488   size_t n;
489   dstr d = DSTR_INIT;
490   const encops *eo;
491   msgcanon mc_in = MC_INIT, mc_out = MC_INIT;
492   enc *e;
493
494   for (;;) {
495     static const struct option opt[] = {
496       { "armour",       0,              0,      'a' },
497       { "armor",        0,              0,      'a' },
498       { "binary",       0,              0,      'b' },
499       { "detach",       0,              0,      'd' },
500       { "key",          OPTF_ARGREQ,    0,      'k' },
501       { "format",       OPTF_ARGREQ,    0,      'f' },
502       { "output",       OPTF_ARGREQ,    0,      'o' },
503       { "progress",     0,              0,      'p' },
504       { "text",         0,              0,      't' },
505       { "nocheck",      0,              0,      'C' },
506       { 0,              0,              0,      0 }
507     };
508     i = mdwopt(argc, argv, "k:f:o:abdptC", opt, 0, 0, 0);
509     if (i < 0) break;
510     switch (i) {
511       case 'k': kn = optarg; break;
512       case 'f': ef = optarg; break;
513       case 'o': of = optarg; break;
514       case 'a': ef = "pem"; break;
515       case 't': f &= ~F_BINARY; break;
516       case 'b': f |= F_BINARY; break;
517       case 'd': f |= F_DETACH; break;
518       case 'C': f |= F_NOCHECK; break;
519       case 'p': f |= F_PROGRESS; break;
520       default: f |= F_BOGUS; break;
521     }
522   }
523   if (argc - optind > 1 || (f & F_BOGUS))
524     die(EXIT_FAILURE, "Usage: sign [-OPTIONS] [FILE]");
525
526   if (key_open(&kf, keyring, KOPEN_READ, key_moan, 0))
527     die(EXIT_FAILURE, "can't open keyring `%s'", keyring);
528   if ((k = key_bytag(&kf, kn)) == 0)
529     die(EXIT_FAILURE, "key `%s' not found", kn);
530
531   if ((eo = getenc(ef)) == 0)
532     die(EXIT_FAILURE, "encoding `%s' not found", ef);
533
534   fn = (optind >= argc) ? 0 : argv[optind++];
535
536   if (!of || strcmp(of, "-") == 0)
537     ofp = stdout;
538   else if ((ofp = fopen(of, eo->wmode)) == 0) {
539     die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
540         of, strerror(errno));
541   }
542
543   /* --- Start the work --- */
544
545   sig_init(&s, f, k->id);
546   dstr_reset(&d);
547   key_fulltag(k, &d);
548   s.s = getsig(k, "ccsig", 1);
549   if (!(f & F_NOCHECK) && (err = s.s->ops->check(s.s)) != 0)
550     moan("key %s fails check: %s", d.buf, err);
551   keyhash(k, s.s, &s.kh);
552   e = initenc(eo, ofp,
553               (f & F_DETACH) ? "CATSIGN SIGNATURE" :
554               (f & F_BINARY) ? "CATSIGN MESSAGE" :
555               "CATSIGN MESSAGE HEADER");
556   sig_writeheader(e, &s);
557
558   /* --- Hash the message --- */
559
560   mcsetup_readfile(&mc_in, f, fn);
561   mcsetup_write(&mc_out, f, &e);
562   sig_hash(&s);
563   for (;;) {
564     n = mc_in.read(&mc_in, bb);
565     if (!n) break;
566     dohash(s.s->h, bb, n);
567     mc_out.write(&mc_out, bb, n);
568   }
569   mc_endread(&mc_in, 0, 0);
570   mc_endwrite(&mc_out, eo, &e);
571
572   /* --- Write the signature --- */
573
574   if (s.s->ops->doit(s.s, &s.sig))
575     die(EXIT_FAILURE, "signature failed");
576   sig_writesig(e, &s);
577   e->ops->encdone(e);
578   if (fflush(ofp) || ferror(ofp) || fclose(ofp))
579     die(EXIT_FAILURE, "error writing signature: %s", strerror(errno));
580
581   /* --- All done --- */
582
583   freeenc(e);
584   key_close(&kf);
585   sig_destroy(&s);
586   dstr_destroy(&d);
587   return (0);
588 }
589
590 /*----- Verifying ---------------------------------------------------------*/
591
592 typedef struct vrfctx {
593   unsigned f, m;
594   int verb;
595   const char *what;
596 } vrfctx;
597
598 static int vrfbdry(const char *b, void *p)
599 {
600   vrfctx *v = p;
601
602   if (strcmp(b, "CATSIGN MESSAGE") == 0) {
603     v->f |= F_BINARY;
604     v->m |= F_BINARY | F_DETACH;
605     return (1);
606   } else if (strcmp(b, "CATSIGN MESSAGE HEADER") == 0) {
607     v->m |= F_BINARY | F_DETACH;
608     return (1);
609   } else if (strcmp(b, "CATSIGN SIGNATURE") == 0) {
610     v->f |= F_DETACH;
611     v->m |= F_DETACH;
612     return (1);
613   } else
614     return (0);
615 }
616
617 static void NORETURN vrfchoke(const char *m, void *p)
618 {
619   vrfctx *v = p;
620   if (v->verb) printf("FAIL %s: %s\n", v->what, m);
621   exit(EXIT_FAILURE);
622 }
623
624 static int verify(int argc, char *argv[])
625 {
626   const char *ef = "binary", *of = 0, *fn, *dfn = 0, *kn = 0, *err;
627   vrfctx v = { 0, 0, 1 };
628   key_file kf;
629   key *k, *kk = 0;
630   sigmsg s;
631   FILE *fp, *ofp = 0, *rfp = 0;
632   fprogress ff;
633   struct tm *tm;
634   int i;
635   char bb[MSGBUFSZ];
636   size_t n;
637   time_t t_fresh = 0;
638   dstr d = DSTR_INIT, dd = DSTR_INIT;
639   const encops *eo;
640   msgcanon mc_in = MC_INIT;
641   enc *e;
642
643   for (;;) {
644     static const struct option opt[] = {
645       { "armour",       0,              0,      'a' },
646       { "armor",        0,              0,      'a' },
647       { "buffer",       0,              0,      'b' },
648       { "key",          OPTF_ARGREQ,    0,      'k' },
649       { "format",       OPTF_ARGREQ,    0,      'f' },
650       { "output",       OPTF_ARGREQ,    0,      'o' },
651       { "progress",     0,              0,      'p' },
652       { "quiet",        0,              0,      'q' },
653       { "utc",          0,              0,      'u' },
654       { "fresh-time",   0,              0,      't' },
655       { "gmt",          0,              0,      'u' },
656       { "verbose",      0,              0,      'v' },
657       { "nocheck",      0,              0,      'C' },
658       { 0,              0,              0,      0 }
659     };
660     i = mdwopt(argc, argv, "k:f:o:abpqt:uvC", opt, 0, 0, 0);
661     if (i < 0) break;
662     switch (i) {
663       case 'a': ef = "pem"; break;
664       case 'b': v.f |= F_BUFFER; break;
665       case 'k': kn = optarg; break;
666       case 'f': ef = optarg; break;
667       case 'o': of = optarg; break;
668       case 'u': v.f |= F_UTC; break;
669       case 'C': v.f |= F_NOCHECK; break;
670       case 't':
671         if (strcmp(optarg, "always") == 0) t_fresh = 0;
672         else if ((t_fresh = get_date(optarg, 0)) < 0)
673           die(EXIT_FAILURE, "bad freshness time");
674         break;
675       case 'q': if (v.verb > 0) v.verb--; break;
676       case 'v': if (v.verb < 10) v.verb++; break;
677       case 'p': v.f |= F_PROGRESS; break;
678       default: v.f |= F_BOGUS; break;
679     }
680   }
681   if (argc - optind > 2 || (v.f & F_BOGUS))
682     die(EXIT_FAILURE, "Usage: verify [-OPTIONS] [FILE [MESSAGE]]");
683
684   if ((eo = getenc(ef)) == 0)
685     die(EXIT_FAILURE, "encoding `%s' not found", ef);
686
687   fn = optind < argc ? argv[optind++] : "-";
688   if (strcmp(fn, "-") == 0)
689     fp = stdin;
690   else if ((fp = fopen(fn, eo->rmode)) == 0) {
691     die(EXIT_FAILURE, "couldn't open file `%s': %s",
692         fn, strerror(errno));
693   }
694
695   if (key_open(&kf, keyring, KOPEN_READ, key_moan, 0))
696     die(EXIT_FAILURE, "can't open keyring `%s'", keyring);
697   if (kn && (kk = key_bytag(&kf, kn)) == 0)
698     die(EXIT_FAILURE, "key `%s' not found", kn);
699
700   e = initdec(eo, fp, vrfbdry, &v);
701
702   /* --- Read the header chunk --- */
703
704   v.what = "malformed header";
705   sig_readheader(e, &s, vrfchoke, &v);
706
707   if (((s.f ^ v.f) & v.m) != 0) {
708     if (v.verb) printf("FAIL boundary string inconsistent with contents\n");
709     exit(EXIT_FAILURE);
710   }
711   v.f |= s.f;
712
713   if ((k = key_byid(&kf, s.keyid)) == 0) {
714     if (v.verb) printf("FAIL key id %08lx not found\n",
715                        (unsigned long)s.keyid);
716     exit(EXIT_FAILURE);
717   }
718   if (kk && k->id != kk->id) {
719     if (v.verb) {
720       dstr_reset(&d); key_fulltag(k, &d);
721       dstr_reset(&dd); key_fulltag(kk, &dd);
722       printf("FAIL signing key is %s; expected key %s\n", d.buf, dd.buf);
723     }
724     exit(EXIT_FAILURE);
725   }
726
727   s.s = getsig(k, "ccsig", 0);
728   dstr_reset(&d); key_fulltag(k, &d);
729   if (!(v.f & F_NOCHECK) && v.verb && (err = s.s->ops->check(s.s)) != 0)
730     printf("WARN verification key %s fails check: %s\n", d.buf, err);
731
732   dstr_reset(&dd); keyhash(k, s.s, &dd);
733   if (dd.len != s.kh.len || memcmp(dd.buf, s.kh.buf, dd.len) != 0) {
734     if (v.verb) printf("FAIL key hash mismatch\n");
735     exit(EXIT_FAILURE);
736   }
737
738   /* --- Now a merry dance --- */
739
740   if (v.f & F_DETACH)
741     sig_readsig(e, &s);
742   if (optind < argc)
743     dfn = argv[optind++];
744   mcsetup_read(&mc_in, v.f, &e, fn, dfn);
745
746   if (!of && (v.f & F_DETACH)) {
747     rfp = ofp = 0;
748     v.f &= ~F_BUFFER;
749   } else if (!of || strcmp(of, "-") == 0) {
750     v.f |= F_BUFFER;
751     ofp = stdout;
752   }
753   if (of && !(v.f & F_BUFFER)) {
754     if ((ofp = fopen(of, (v.f & F_BINARY) ? "wb" : "w")) == 0) {
755       die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
756           of, strerror(errno));
757     }
758     rfp = ofp;
759   } else if ((rfp = tmpfile()) == 0)
760     die(EXIT_FAILURE, "couldn't create temporary file: %s", strerror(errno));
761
762   /* --- Read the message and verify the signature --- */
763
764   sig_hash(&s);
765   for (;;) {
766     n = mc_in.read(&mc_in, bb);
767     if (!n) break;
768     dohash(s.s->h, bb, n);
769     if (rfp) fwrite(bb, 1, n, rfp);
770   }
771   mc_endread(&mc_in, eo, &e);
772   if (!(v.f & F_DETACH))
773     sig_readsig(e, &s);
774   if (rfp && (ferror(rfp) || fflush(rfp))) {
775     if (v.verb) printf("FAIL error writing message: %s\n", strerror(errno));
776     exit(EXIT_FAILURE);
777   }
778
779   /* --- Check the signature --- */
780
781   if (s.s->ops->doit(s.s, &s.sig)) {
782     if (v.verb) printf("FAIL signature verification failed\n");
783     exit(EXIT_FAILURE);
784   }
785   if (t_fresh && s.t < t_fresh) {
786     if (v.verb) printf("FAIL signature is stale\n");
787     exit(EXIT_FAILURE);
788   }
789   if (s.t > time(0)) {
790     if (v.verb) printf("FAIL signature timestamp in the future\n");
791     exit(EXIT_FAILURE);
792   }
793   if (v.verb) {
794     tm = (v.f & F_UTC) ? gmtime(&s.t) : localtime(&s.t);
795     strftime(bb, sizeof(bb), "%Y-%m-%d %H:%M:%S %Z", tm);
796     printf("INFO good-signature %s\n", d.buf);
797     printf("INFO date %s\n", bb);
798   }
799
800   /* --- Unbuffer buffered input --- */
801
802   if (v.f & F_BUFFER) {
803     if (!ofp && (ofp = fopen(of, "wb")) == 0) {
804       die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
805           of, strerror(errno));
806     }
807     rewind(rfp);
808     if (v.f & F_PROGRESS) fprogress_init(&ff, "copying buffer", rfp);
809     if (v.verb && ofp == stdout) printf("DATA\n");
810     for (;;) {
811       n = fread(bb, 1, sizeof(bb), rfp);
812       if (!n) break;
813       if (v.f & F_PROGRESS) fprogress_update(&ff, n);
814       if (fwrite(bb, 1, n, ofp) < n) {
815         if (v.f & F_PROGRESS) fprogress_done(&ff);
816         die(EXIT_FAILURE, "error writing output: %s", strerror(errno));
817       }
818     }
819     if (v.f & F_PROGRESS) fprogress_done(&ff);
820     if (ferror(rfp) || fclose(rfp))
821       die(EXIT_FAILURE, "error unbuffering output: %s", strerror(errno));
822   }
823   if (ofp && (fflush(ofp) || ferror(ofp) || fclose(ofp)))
824       die(EXIT_FAILURE, "error writing output: %s", strerror(errno));
825
826   /* --- Tidy up --- */
827
828   e->ops->decdone(e);
829   if (v.verb && ofp != stdout)
830     printf("OK verified successfully\n");
831   freeenc(e);
832   key_close(&kf);
833   sig_destroy(&s);
834   dstr_destroy(&d);
835   dstr_destroy(&dd);
836   return (0);
837 }
838
839 /*----- Reformatting ------------------------------------------------------*/
840
841 static int format(int argc, char *argv[])
842 {
843   const char *ief = "binary", *oef = "binary";
844   const char *fn, *dfn = 0, *of = 0, *mf = 0;
845   sigmsg s;
846   FILE *fp, *ofp = 0, *mfp = 0;
847   int i;
848   size_t n;
849   msgcanon mc_in = MC_INIT, mc_out = MC_INIT;
850   char bb[MSGBUFSZ];
851   vrfctx v = { 0, 0, 1 };
852   unsigned f = 0, fm = ~F_SIGMASK, sf;
853   const encops *ieo, *oeo;
854   enc *ie, *oe;
855
856   for (;;) {
857     static const struct option opt[] = {
858       { "armour-in",    0,              0,      'a' },
859       { "armor-in",     0,              0,      'a' },
860       { "armour-out",   0,              0,      'A' },
861       { "armor-out",    0,              0,      'A' },
862       { "detach",       0,              0,      'D' },
863       { "embed",        0,              0,      'E' },
864       { "format-in",    OPTF_ARGREQ,    0,      'f' },
865       { "format-out",   OPTF_ARGREQ,    0,      'F' },
866       { "message",      OPTF_ARGREQ,    0,      'm' },
867       { "output",       OPTF_ARGREQ,    0,      'o' },
868       { "progress",     0,              0,      'p' },
869       { 0,              0,              0,      0 }
870     };
871     i = mdwopt(argc, argv, "f:F:m:o:apADE", opt, 0, 0, 0);
872     if (i < 0) break;
873     switch (i) {
874       case 'a': ief = "pem"; break;
875       case 'A': oef = "pem"; break;
876       case 'f': ief = optarg; break;
877       case 'F': oef = optarg; break;
878       case 'D': f |= F_DETACH; fm |= F_DETACH; break;
879       case 'E': f &= ~F_DETACH; fm |= F_DETACH; break;
880       case 'm': mf = optarg; break;
881       case 'o': of = optarg; break;
882       case 'p': f |= F_PROGRESS; break;
883       default: f |= F_BOGUS; break;
884     }
885   }
886
887   if (argc - optind > 2 || (f & F_BOGUS))
888     die(EXIT_FAILURE, "Usage: format [-OPTIONS] [FILE [MESSAGE]]");
889
890   if ((ieo = getenc(ief)) == 0)
891     die(EXIT_FAILURE, "encoding `%s' not found", ief);
892   if ((oeo = getenc(oef)) == 0)
893     die(EXIT_FAILURE, "encoding `%s' not found", oef);
894
895   fn = optind < argc ? argv[optind++] : "-";
896   if (strcmp(fn, "-") == 0)
897     fp = stdin;
898   else if ((fp = fopen(fn, ieo->rmode)) == 0) {
899     die(EXIT_FAILURE, "couldn't open file `%s': %s",
900         fn, strerror(errno));
901   }
902
903   if (optind < argc)
904     dfn = argv[optind++];
905
906   ie = initdec(ieo, fp, vrfbdry, &v);
907
908   /* --- Read the header chunk --- */
909
910   sig_readheader(ie, &s, diechoke, "malformed header");
911
912   if (((s.f ^ v.f) & v.m) != 0)
913     moan("boundary string inconsistent with contents (ignoring)");
914
915   mcsetup_read(&mc_in, s.f, &ie, fn, dfn);
916
917   /* --- Prepare the output stuff --- */
918
919   if (!of && !mf) of = "-";
920   sf = s.f;
921   f = (f & fm) | (s.f & ~fm);
922   s.f = f & F_SIGMASK;
923
924   if (sf & F_DETACH)
925     sig_readsig(ie, &s);
926
927   if (!of)
928     mcsetup_writenull(&mc_out);
929   else {
930     if (strcmp(of, "-") == 0)
931       ofp = stdout;
932     else if ((ofp = fopen(of, oeo->wmode)) == 0) {
933       die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
934           of, strerror(errno));
935     }
936     oe = initenc(oeo, ofp,
937                  (f & F_DETACH) ? "CATSIGN SIGNATURE" :
938                  (f & F_BINARY) ? "CATSIGN MESSAGE" :
939                  "CATSIGN MESSAGE HEADER");
940     sig_writeheader(oe, &s);
941     mcsetup_write(&mc_out, f, &oe);
942   }
943
944   if (mf) {
945     if (strcmp(mf, "-") == 0)
946       mfp = stdout;
947     else if ((mfp = fopen(mf, (f & F_BINARY) ? "wb" : "w")) == 0) {
948       die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
949           mf, strerror(errno));
950     }
951   }
952
953   /* --- Wade through the message body --- */
954
955   for (;;) {
956     n = mc_in.read(&mc_in, bb);
957     if (!n) break;
958     mc_out.write(&mc_out, bb, n);
959     if (mfp) fwrite(bb, 1, n, mfp);
960   }
961   mc_endread(&mc_in, ieo, &ie);
962   if (of) mc_endwrite(&mc_out, oeo, &oe);
963
964   /* --- Write the signature --- */
965
966   if (!(sf & F_DETACH))
967     sig_readsig(ie, &s);
968   if (of) {
969     sig_writesig(oe, &s);
970     oe->ops->encdone(oe);
971   }
972
973   /* --- All done --- */
974
975   ie->ops->decdone(ie);
976   if (ferror(fp) || fclose(fp))
977     die(EXIT_FAILURE, "error reading input signature: %s", strerror(errno));
978   if (ofp && (fflush(ofp) || ferror(ofp) || fclose(ofp)))
979     die(EXIT_FAILURE, "error writing output signature: %s", strerror(errno));
980   if (mfp && (fflush(mfp) || ferror(mfp) || fclose(mfp)))
981     die(EXIT_FAILURE, "error writing output message: %s", strerror(errno));
982   freeenc(ie);
983   if (of) freeenc(oe);
984   sig_destroy(&s);
985   return (0);
986 }
987
988 static void NORETURN infochoke(const char *m, void *p)
989 {
990   vrfctx *v = p;
991   printf("BAD %s: %s\n", v->what, m);
992   exit(EXIT_FAILURE);
993 }
994
995 static void infokeyreport(const char *file, int line,
996                           const char *err, void *p)
997 { /*whatever*/; }
998
999 static int info(int argc, char *argv[])
1000 {
1001   const char *ef = "binary";
1002   vrfctx v = { 0, 0, 1 };
1003   key_file kf;
1004   key *k;
1005   sigmsg s;
1006   FILE *fp;
1007   int i;
1008   struct tm *tm;
1009   char bb[256];
1010   dstr d = DSTR_INIT;
1011   const encops *eo;
1012   enc *e;
1013
1014   for (;;) {
1015     static const struct option opt[] = {
1016       { "armour",       0,              0,      'a' },
1017       { "armor",        0,              0,      'a' },
1018       { "format",       OPTF_ARGREQ,    0,      'f' },
1019       { "gmt",          0,              0,      'u' },
1020       { "utc",          0,              0,      'u' },
1021       { 0,              0,              0,      0 }
1022     };
1023     i = mdwopt(argc, argv, "f:au", opt, 0, 0, 0);
1024     if (i < 0) break;
1025     switch (i) {
1026       case 'a': ef = "pem"; break;
1027       case 'f': ef = optarg; break;
1028       case 'u': v.f |= F_UTC; break;
1029       default: v.f |= F_BOGUS; break;
1030     }
1031   }
1032   if (argc - optind > 1 || (v.f & F_BOGUS))
1033     die(EXIT_FAILURE, "Usage: info [-OPTIONS] [FILE]");
1034
1035   if ((eo = getenc(ef)) == 0)
1036     die(EXIT_FAILURE, "encoding `%s' not found", ef);
1037
1038   if (optind >= argc)
1039     fp = stdin;
1040   else if (strcmp(argv[optind], "-") == 0) {
1041     fp = stdin;
1042     optind++;
1043   } else if ((fp = fopen(argv[optind], eo->rmode)) == 0) {
1044     die(EXIT_FAILURE, "couldn't open file `%s': %s",
1045         argv[optind], strerror(errno));
1046   } else
1047     optind++;
1048
1049   if (key_open(&kf, keyring, KOPEN_READ, infokeyreport, 0)) {
1050     printf("NOTE can't open keyring `%s'\n", keyring);
1051     keyring = 0;
1052   }
1053   e = initdec(eo, fp, vrfbdry, &v);
1054
1055   v.what = "malformed header";
1056   sig_readheader(e, &s, infochoke, &v);
1057
1058   printf("INFO flags %sdetach %sbinary\n",
1059          (s.f & F_DETACH) ? "" : "!",
1060          (s.f & F_BINARY) ? "" : "!");
1061
1062   if (((s.f ^ v.f) & v.m) != 0) {
1063     printf("WARN boundary string inconsistent with contents\n");
1064     printf("INFO expected-flags");
1065     if (v.m & F_DETACH) printf(" %sdetach", (v.f & F_DETACH) ? "" : "!");
1066     if (v.m & F_BINARY) printf(" %sbinary", (v.f & F_BINARY) ? "" : "!");
1067     putchar('\n');
1068   }
1069   v.f |= s.f;
1070
1071   tm = (v.f & F_UTC) ? gmtime(&s.t) : localtime(&s.t);
1072   strftime(bb, sizeof(bb), "%Y-%m-%d %H:%M:%S %Z", tm);
1073   printf("INFO date %s\n", bb);
1074
1075   if (keyring && (k = key_byid(&kf, s.keyid)) != 0) {
1076     dstr_reset(&d); key_fulltag(k, &d);
1077     printf("INFO key %s\n", d.buf);
1078   } else
1079     printf("INFO unknown-key %08lx\n", (unsigned long)s.keyid);
1080
1081   if (keyring) key_close(&kf);
1082   dstr_destroy(&d);
1083   sig_destroy(&s);
1084   return (0);
1085 }
1086
1087 /*----- Main code ---------------------------------------------------------*/
1088
1089 #define LISTS(LI)                                                       \
1090   LI("Lists", list,                                                     \
1091      listtab[i].name, listtab[i].name)                                  \
1092   LI("Signature schemes", sig,                                          \
1093      sigtab[i].name, sigtab[i].name)                                    \
1094   LI("Encodings", enc,                                                  \
1095      enctab[i].name, enctab[i].name)                                    \
1096   LI("Hash functions", hash,                                            \
1097      ghashtab[i], ghashtab[i]->name)
1098
1099 MAKELISTTAB(listtab, LISTS)
1100
1101 int cmd_show(int argc, char *argv[])
1102 {
1103   return (displaylists(listtab, argv + 1));
1104 }
1105
1106 static int cmd_help(int, char **);
1107
1108 static cmd cmdtab[] = {
1109   { "help", cmd_help, "help [COMMAND...]" },
1110   { "show", cmd_show, "show [ITEM...]" },
1111   CMD_ENCODE,
1112   CMD_DECODE,
1113   { "sign", sign,
1114     "sign [-adptC] [-k TAG] [-f FORMAT] [-o OUTPUT] [FILE]", "\
1115 Options:\n\
1116 \n\
1117 -a, --armour            Same as `-f pem'.\n\
1118 -b, --binary            Treat the input message as binary data.\n\
1119 -d, --detach            Produce a detached signature.\n\
1120 -f, --format=FORMAT     Encode as FORMAT.\n\
1121 -k, --key=TAG           Use public encryption key named by TAG.\n\
1122 -o, --output=FILE       Write output to FILE.\n\
1123 -p, --progress          Show progress on large files.\n\
1124 -t, --text              Canonify input message as a text file.\n\
1125 -C, --nocheck           Don't check the private key.\n\
1126 " },
1127   { "verify", verify,
1128     "verify [-abpquvC] [-f FORMAT] [-k TAG] [-o OUTPUT]\n\t\
1129 [FILE [MESSAGE]]", "\
1130 Options:\n\
1131 \n\
1132 -a, --armour            Same as `-f pem'.\n\
1133 -b, --buffer            Buffer message until signature is verified.\n\
1134 -f, --format=FORMAT     Decode as FORMAT.\n\
1135 -k, --key=TAG           Require that the message be signed by key TAG.\n\
1136 -o, --output=FILE       Write message to FILE.\n\
1137 -p, --progress          Show progress on large files.\n\
1138 -q, --quiet             Produce fewer messages.\n\
1139 -t, --freshtime=TIME    Only accept signatures made after this time.\n\
1140 -u, --utc               Show dates in UTC rather than local time.\n\
1141 -v, --verbose           Produce more verbose messages.\n\
1142 -C, --nocheck           Don't check the public key.\n\
1143 " },
1144   { "info", info,
1145     "info [-au] [-f FORMAT] [FILE]", "\
1146 Options:\n\
1147 \n\
1148 -a, --armour            Same as `-f pem'.\n\
1149 -f, --format=FORMAT     Decode as FORMAT.\n\
1150 -u, --utc               Show dates in UTC rather than local time.\n\
1151 "},
1152   { "format", format,
1153     "format [-apuADE] [-f FORMAT] [-F format] [-m FILE] [-o FILE]\n\t\
1154 [FILE [MESSAGE]]", "\
1155 Options:\n\
1156 \n\
1157 -a, --armour-in         Same as `-f pem'.\n\
1158 -A, --armour-out        Same as `-F pem'.\n\
1159 -D, --detach            Create detached signature.\n\
1160 -E, --embed             Create signature with embedded message.\n\
1161 -f, --format-in=FORMAT  Decode input as FORMAT.\n\
1162 -F, --format-out=FORMAT Encode output as FORMAT.\n\
1163 -m, --message=FILE      Write message to FILE.\n\
1164 -o, --output=FILE       Write new signature to FILE.\n\
1165 -p, --progress          Show progress on large files.\n\
1166 "},
1167   { 0, 0, 0 }
1168 }; /* " Emacs seems confused. */
1169
1170 static int cmd_help(int argc, char **argv)
1171 {
1172   sc_help(cmdtab, stdout, argv + 1);
1173   return (0);
1174 }
1175
1176 void version(FILE *fp)
1177 {
1178   pquis(fp, "$, Catacomb version " VERSION "\n");
1179 }
1180
1181 static void usage(FILE *fp)
1182 {
1183   pquis(fp, "Usage: $ [-k KEYRING] COMMAND [ARGS]\n");
1184 }
1185
1186 void help_global(FILE *fp)
1187 {
1188   usage(fp);
1189   fputs("\n\
1190 Sign and verify data.\n\
1191 \n\
1192 Global command-line options:\n\
1193 \n\
1194 -h, --help [COMMAND...] Show this help message, or help for COMMANDs.\n\
1195 -v, --version           Show program version number.\n\
1196 -u, --usage             Show a terse usage message.\n\
1197 \n\
1198 -k, --keyring=FILE      Read keys from FILE.\n",
1199         fp);
1200 }
1201
1202 /* --- @main@ --- *
1203  *
1204  * Arguments:   @int argc@ = number of command line arguments
1205  *              @char *argv[]@ = vector of command line arguments
1206  *
1207  * Returns:     Zero if successful, nonzero otherwise.
1208  *
1209  * Use:         Encrypts or decrypts files.
1210  */
1211
1212 int main(int argc, char *argv[])
1213 {
1214   unsigned f = 0;
1215
1216 #define f_bogus 1u
1217
1218   /* --- Initialize the library --- */
1219
1220   ego(argv[0]);
1221   sub_init();
1222   rand_noisesrc(RAND_GLOBAL, &noise_source);
1223   rand_seed(RAND_GLOBAL, 160);
1224
1225   /* --- Parse options --- */
1226
1227   for (;;) {
1228     static struct option opts[] = {
1229       { "help",         0,              0,      'h' },
1230       { "version",      0,              0,      'v' },
1231       { "usage",        0,              0,      'u' },
1232       { "keyring",      OPTF_ARGREQ,    0,      'k' },
1233       { 0,              0,              0,      0 }
1234     };
1235     int i = mdwopt(argc, argv, "+hvu k:", opts, 0, 0, 0);
1236     if (i < 0)
1237       break;
1238     switch (i) {
1239       case 'h':
1240         sc_help(cmdtab, stdout, argv + optind);
1241         exit(0);
1242         break;
1243       case 'v':
1244         version(stdout);
1245         exit(0);
1246         break;
1247       case 'u':
1248         usage(stdout);
1249         exit(0);
1250       case 'k':
1251         keyring = optarg;
1252         break;
1253       default:
1254         f |= f_bogus;
1255         break;
1256     }
1257   }
1258
1259   argc -= optind;
1260   argv += optind;
1261   optind = 0;
1262   if (f & f_bogus || argc < 1) {
1263     usage(stderr);
1264     exit(EXIT_FAILURE);
1265   }
1266
1267   /* --- Dispatch to the correct subcommand handler --- */
1268
1269   return (findcmd(cmdtab, argv[0])->cmd(argc, argv));
1270
1271 #undef f_bogus
1272 }
1273
1274 /*----- That's all, folks -------------------------------------------------*/