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