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