X-Git-Url: https://git.distorted.org.uk/u/mdw/catacomb/blobdiff_plain/fe9de6c9fbf52fa3a0eb0929fd18d615741f1d49..fa54fe1eda6977fc8aef0c154f8483e351e20bdd:/catsign.c diff --git a/catsign.c b/catsign.c new file mode 100644 index 0000000..5b5d7d0 --- /dev/null +++ b/catsign.c @@ -0,0 +1,1214 @@ +/* -*-c-*- + * + * $Id$ + * + * Sign files + * + * (c) 2005 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of Catacomb. + * + * Catacomb is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * Catacomb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with Catacomb; if not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "buf.h" +#include "rand.h" +#include "noise.h" +#include "mprand.h" +#include "key.h" +#include "cc.h" + +#include "ectab.h" +#include "ptab.h" + +/*----- Utilities ---------------------------------------------------------*/ + +/* --- @keyreport@ --- * + * + * Arguments: @const char *file@ = filename containing the error + * @int line@ = line number in file + * @const char *err@ = error text message + * @void *p@ = unimportant pointer + * + * Returns: --- + * + * Use: Reports errors during the opening of a key file. + */ + +static void keyreport(const char *file, int line, const char *err, void *p) +{ + moan("error in keyring `%s' at line `%s': %s", file, line, err); +} + +/*----- Static variables --------------------------------------------------*/ + +static const char *keyring = "keyring"; + +/*----- Data formats ------------------------------------------------------* + * + * Our crypto stuff is split into three parts: a header describing the key, + * the message itself, and the signature. In a detached signature, the + * message is separate, and the header and signature are combined into one + * encoded chunk. In an attached signature, the message is included. There + * are two forms of message: binary and text. Binary messages are divided + * into chunks, each preceded by a 2-octet length, and terminated by a + * zero-length chunk. Text messages are byte-stuffed, as for RFC821, and + * trailing whitespace before a newline is ignored. + */ + +typedef struct sigmsg { + unsigned f; /* Flags */ +#define F_DETACH 1u +#define F_BINARY 2u +#define F_SIGMASK 3u +#define F_HASHMASK (F_BINARY) + uint32 keyid; /* Key identifier */ + time_t t; /* When the signature was made */ + dstr kh; /* Key fingerprint (sanity check) */ + dstr sig; /* Signature */ + sig *s; /* Signature algorithm */ +} sigmsg; + +#define F_MIDLINE 16u +#define F_EOF 32u +#define F_NOCLOSE 64u +#define F_BOGUS 128u +#define F_BUFFER 256u +#define F_UTC 512u + +/*----- Chunk I/O ---------------------------------------------------------*/ + +static void chunk_write(enc *e, const void *p, size_t n) +{ + octet b[2]; + + assert(n <= 0xffff); + STORE16(b, n); + if (e->ops->write(e, b, 2) || e->ops->write(e, p, n)) + die(EXIT_FAILURE, "error writing output: %s", strerror(errno)); +} + +static size_t chunk_read(enc *e, void *p) +{ + octet b[2]; + size_t n; + + if (e->ops->read(e, b, 2) != 2) + goto err; + n = LOAD16(b); + if (n && e->ops->read(e, p, n) != n) + goto err; + return (n); + +err: + if (!errno) die(EXIT_FAILURE, "unexpected end-of-file on input"); + else die(EXIT_FAILURE, "error reading input: %s", strerror(errno)); + return (0); +} + +/*----- Message canonification --------------------------------------------*/ + +#define MSGBUFSZ 65536 +#define MSGBUFTHRESH 32768 + +typedef struct msgcanon { + unsigned f; + FILE *fp; + enc *e; + size_t (*read)(struct msgcanon *, void *); + void (*write)(struct msgcanon *, const void *, size_t); + void (*close)(struct msgcanon *); +} msgcanon; + +#define MC_INIT { 0 } + +static size_t textread(msgcanon *m, void *bp) +{ + size_t n = 0, nsp = 0; + int ch; + char *b = bp; + unsigned f = m->f; + + if (f & F_EOF) return (0); + for (;;) { + if ((ch = getc(m->fp)) == EOF) goto eof; + if (!(f & (F_DETACH | F_MIDLINE)) && ch == '.') { + ch = getc(m->fp); + if (ch == '\n') goto eof; + } + if (ch == '\n') { + n -= nsp; nsp = 0; + if (n >= MSGBUFTHRESH) goto full; + b[n++] = ch; + f &= ~F_MIDLINE; + } else if (isspace(ch)) { + f |= F_MIDLINE; + if (n >= MSGBUFSZ) goto full; + b[n++] = ch; nsp++; + } else { + f |= F_MIDLINE; + nsp = 0; + if (n >= MSGBUFTHRESH) goto full; + b[n++] = ch; + } + } +eof: + f |= F_EOF; + goto done; +full: + ungetc(ch, m->fp); +done: + m->f = f; + return (n); +} + +static void textwrite(msgcanon *m, const void *bp, size_t n) +{ + const char *p = bp, *l = p + n; + unsigned f = m->f; + + while (p < l) { + if (!(f & (F_MIDLINE | F_DETACH)) && + (*p == '.' || *p == '-')) + putc('.', m->fp); + if (*p == '\n') f &= ~F_MIDLINE; + else f |= F_MIDLINE; + putc(*p, m->fp); + p++; + } + m->f = f; +} + +static size_t binreadembed(msgcanon *m, void *bp) + { return (chunk_read(m->e, bp)); } +static size_t binreaddetach(msgcanon *m, void *bp) + { return (fread(bp, 1, MSGBUFSZ, m->fp)); } + +static void binwriteembed(msgcanon *m, const void *bp, size_t n) + { chunk_write(m->e, bp, n); } + +static void nullwrite(msgcanon *m, const void *bp, size_t n) { ; } + +static void mcsetup_readfile(msgcanon *m, unsigned f, const char *fn) +{ + m->f = F_DETACH | (f & F_BINARY); + if (!fn || strcmp(fn, "-") == 0) { + m->fp = stdin; + m->f |= F_NOCLOSE; + } else if ((m->fp = fopen(fn, (f & F_BINARY) ? "rb" : "r")) == 0) + die(EXIT_FAILURE, "couldn't open file `%s': %s", fn, strerror(errno)); + m->read = (f & F_BINARY) ? binreaddetach : textread; +} + +static void mcsetup_writenull(msgcanon *m) { m->write = nullwrite; } + +static void mcsetup_read(msgcanon *m, unsigned f, enc **ee, const char *dfn) +{ + enc *e = *ee; + + m->f = f | F_NOCLOSE; + + if (dfn && !(f & F_DETACH)) die(EXIT_FAILURE, "signature is not detached"); + if (f & F_BINARY) { + if (!(f & F_DETACH)) { + m->e = e; + *ee = 0; + m->fp = e->fp; + m->read = binreadembed; + } else { + m->read = binreaddetach; + if (!dfn || strcmp(dfn, "-") == 0) + m->fp = stdin; + else if ((m->fp = fopen(dfn, "rb")) == 0) + die(EXIT_FAILURE, "can't open `%s': %s", dfn, strerror(errno)); + else + m->f &= ~F_NOCLOSE; + } + } else { + m->read = textread; + if (!(f & F_DETACH)) { + if (e->ops->decdone(e) || ferror(e->fp)) + die(EXIT_FAILURE, "error at end of signature header"); + m->fp = e->fp; + e->ops->destroy(e); + *ee = 0; + } else if (!dfn || strcmp(dfn, "-") == 0) + m->fp = stdin; + else if ((m->fp = fopen(dfn, "r")) == 0) + die(EXIT_FAILURE, "can't read file `%s': %s", dfn, strerror(errno)); + else + m->f &= ~F_NOCLOSE; + } +} + +static void mcsetup_write(msgcanon *m, unsigned f, enc **ee) +{ + enc *e = *ee; + + m->f = f | F_NOCLOSE; + + if (f & F_DETACH) + m->write = nullwrite; + else if (f & F_BINARY) { + m->e = e; + *ee = 0; + m->fp = e->fp; + m->write = binwriteembed; + } else { + if (e->ops->encdone(e) || ferror(e->fp)) + die(EXIT_FAILURE, "error at end of signature header"); + m->fp = e->fp; + e->ops->destroy(e); + *ee = 0; + m->write = textwrite; + } +} + +static void mc_endread(msgcanon *m, const encops *eops, enc **ee) +{ + if (!(m->f & F_DETACH)) { + if (m->f & F_BINARY) + *ee = m->e; + else + *ee = initdec(eops, m->fp, checkbdry, "CATSIGN SIGNATURE"); + } + if (m->fp && !(m->f & F_NOCLOSE)) { + if (ferror(m->fp) || fclose(m->fp)) + die(EXIT_FAILURE, "error closing message file: %s", strerror(errno)); + } +} + +static void mc_endwrite(msgcanon *m, const encops *eops, enc **ee) +{ + if (!(m->f & F_BINARY)) { + if (m->f & F_MIDLINE) putc('\n', m->fp); + if (!(m->f & F_DETACH)) { + putc('.', m->fp); putc('\n', m->fp); + *ee = initenc(eops, m->fp, "CATSIGN SIGNATURE"); + } + } else if (!(m->f & F_DETACH)) { + chunk_write(m->e, 0, 0); + *ee = m->e; + } + if (m->fp && !(m->f & F_NOCLOSE)) { + if (fflush(m->fp) || ferror(m->fp) || fclose(m->fp)) + die(EXIT_FAILURE, "error closing message file: %s", strerror(errno)); + } +} + +/*----- Signature reading and writing -------------------------------------*/ + +static void sig_init(sigmsg *s, unsigned f, uint32 keyid) +{ + s->f = f & F_SIGMASK; + s->keyid = keyid; + time(&s->t); + s->s = 0; + dstr_create(&s->kh); + dstr_create(&s->sig); +} + +static void sig_destroy(sigmsg *s) +{ + dstr_destroy(&s->kh); + dstr_destroy(&s->sig); + if (s->s) freesig(s->s); +} + +static void sigtobuffer(sigmsg *s, buf *b, int hashp) +{ + kludge64 t; + + ASSIGN64(t, s->t); + if (hashp) buf_putu16(b, s->f & F_HASHMASK); + else buf_putu16(b, s->f & F_SIGMASK); + buf_putu32(b, s->keyid); + buf_putu32(b, HI64(t)); + buf_putu32(b, LO64(t)); + buf_putstr16(b, &s->kh); + assert(BOK(b)); +} + +static void dohash(ghash *h, const void *p, size_t n) +{ +/* trace_block(1, "hashing", p, n); */ + GH_HASH(h, p, n); +} + +static void sig_hash(sigmsg *s) +{ + octet bb[16384]; + buf b; + + buf_init(&b, bb, sizeof(bb)); + sigtobuffer(s, &b, 1); + dohash(s->s->h, BBASE(&b), BLEN(&b)); +} + +static void keyhash(key *k, sig *s, dstr *d) +{ + ghash *h; + key_filter kf; + + h = GH_INIT(GH_CLASS(s->h)); + kf.f = KCAT_PUB; + kf.m = KF_CATMASK; + key_fingerprint(k, h, &kf); + dstr_ensure(d, GH_CLASS(h)->hashsz); + GH_DONE(h, d->buf + d->len); + d->len += GH_CLASS(h)->hashsz; + GH_DESTROY(h); +} + +static void sig_writeheader(enc *e, sigmsg *s) +{ + octet bb[16384]; + buf b; + + buf_init(&b, bb, sizeof(bb)); + sigtobuffer(s, &b, 0); + chunk_write(e, BBASE(&b), BLEN(&b)); +} + +static void sig_writesig(enc *e, sigmsg *s) + { chunk_write(e, s->sig.buf, s->sig.len); } + +static void diechoke(const char *m, void *p) + { die(EXIT_FAILURE, "%s%s%s", p, p ? ": " : "", m); } + +static void sig_readheader(enc *e, sigmsg *s, + void (*choke)(const char *, void *), void *p) +{ + uint16 f; + octet bb[MSGBUFSZ]; + uint32 x, y; + kludge64 t; + buf b; + size_t n; + + n = chunk_read(e, bb); + buf_init(&b, bb, n); + if (buf_getu16(&b, &f)) choke("missing flags", p); + if (buf_getu32(&b, &x)) choke("missing keyid", p); + sig_init(s, f, x); + if (buf_getu32(&b, &x) || buf_getu32(&b, &y)) + choke("missing datestamp", p); + SET64(t, x, y); s->t = GET64(time_t, t); + if (buf_getstr16(&b, &s->kh)) + choke("missing key hash", p); + if (BLEFT(&b)) + choke("junk at end", p); +} + +static void sig_readsig(enc *e, sigmsg *s) +{ + octet bb[MSGBUFSZ]; + size_t n; + + n = chunk_read(e, bb); + dstr_putm(&s->sig, bb, n); +} + +/*----- Signing -----------------------------------------------------------*/ + +static int sign(int argc, char *argv[]) +{ + const char *ef = "binary", *fn = 0, *of = 0, *kn = "ccsig", *err; + unsigned f = 0; + key_file kf; + key *k; + sigmsg s; + FILE *ofp = 0; + int i; + char bb[MSGBUFSZ]; + size_t n; + dstr d = DSTR_INIT; + const encops *eo; + msgcanon mc_in = MC_INIT, mc_out = MC_INIT; + enc *e; + + for (;;) { + static const struct option opt[] = { + { "armour", 0, 0, 'a' }, + { "armor", 0, 0, 'a' }, + { "binary", 0, 0, 'b' }, + { "detach", 0, 0, 'd' }, + { "key", OPTF_ARGREQ, 0, 'k' }, + { "format", OPTF_ARGREQ, 0, 'f' }, + { "output", OPTF_ARGREQ, 0, 'o' }, + { "text", 0, 0, 't' }, + { 0, 0, 0, 0 } + }; + i = mdwopt(argc, argv, "k:f:o:abdt", opt, 0, 0, 0); + if (i < 0) break; + switch (i) { + case 'k': kn = optarg; break; + case 'f': ef = optarg; break; + case 'o': of = optarg; break; + case 'a': ef = "pem"; break; + case 't': f &= ~F_BINARY; break; + case 'b': f |= F_BINARY; break; + case 'd': f |= F_DETACH; break; + default: f |= F_BOGUS; break; + } + } + if (argc - optind > 1 || (f & F_BOGUS)) + die(EXIT_FAILURE, "Usage: sign [-OPTIONS] [FILE]"); + + if (key_open(&kf, keyring, KOPEN_READ, keyreport, 0)) + die(EXIT_FAILURE, "can't open keyring `%s'", keyring); + if ((k = key_bytag(&kf, kn)) == 0) + die(EXIT_FAILURE, "key `%s' not found", kn); + + if ((eo = getenc(ef)) == 0) + die(EXIT_FAILURE, "encoding `%s' not found", ef); + + fn = (optind >= argc) ? 0 : argv[optind++]; + + if (!of || strcmp(of, "-") == 0) + ofp = stdout; + else if ((ofp = fopen(of, eo->wmode)) == 0) { + die(EXIT_FAILURE, "couldn't open file `%s' for output: %s", + ofp, strerror(errno)); + } + + /* --- Start the work --- */ + + sig_init(&s, f, k->id); + dstr_reset(&d); + key_fulltag(k, &d); + s.s = getsig(k, "ccsig", 1); + if ((err = s.s->ops->check(s.s)) != 0) + moan("key %s fails check: %s", d.buf, err); + keyhash(k, s.s, &s.kh); + e = initenc(eo, ofp, + (f & F_DETACH) ? "CATSIGN SIGNATURE" : + (f & F_BINARY) ? "CATSIGN MESSAGE" : + "CATSIGN MESSAGE HEADER"); + sig_writeheader(e, &s); + + /* --- Hash the message --- */ + + mcsetup_readfile(&mc_in, f, fn); + mcsetup_write(&mc_out, f, &e); + sig_hash(&s); + for (;;) { + n = mc_in.read(&mc_in, bb); + if (!n) break; + dohash(s.s->h, bb, n); + mc_out.write(&mc_out, bb, n); + } + mc_endread(&mc_in, 0, 0); + mc_endwrite(&mc_out, eo, &e); + + /* --- Write the signature --- */ + + if (s.s->ops->doit(s.s, &s.sig)) + die(EXIT_FAILURE, "signature failed"); + sig_writesig(e, &s); + e->ops->encdone(e); + if (fflush(ofp) || ferror(ofp) || fclose(ofp)) + die(EXIT_FAILURE, "error writing signature: %s", strerror(errno)); + + /* --- All done --- */ + + freeenc(e); + key_close(&kf); + sig_destroy(&s); + dstr_destroy(&d); + return (0); +} + +/*----- Verifying ---------------------------------------------------------*/ + +typedef struct vrfctx { + unsigned f, m; + int verb; + const char *what; +} vrfctx; + +static int vrfbdry(const char *b, void *p) +{ + vrfctx *v = p; + + if (strcmp(b, "CATSIGN MESSAGE") == 0) { + v->f |= F_BINARY; + v->m |= F_BINARY | F_DETACH; + return (1); + } else if (strcmp(b, "CATSIGN MESSAGE HEADER") == 0) { + v->m |= F_BINARY | F_DETACH; + return (1); + } else if (strcmp(b, "CATSIGN SIGNATURE") == 0) { + v->f |= F_DETACH; + v->m |= F_DETACH; + return (1); + } else + return (0); +} + +static void vrfchoke(const char *m, void *p) +{ + vrfctx *v = p; + if (v->verb) printf("FAIL %s: %s\n", v->what, m); + exit(EXIT_FAILURE); +} + +static int verify(int argc, char *argv[]) +{ + const char *ef = "binary", *of = 0, *dfn = 0, *kn = 0, *err; + vrfctx v = { 0, 0, 1 }; + key_file kf; + key *k, *kk = 0; + sigmsg s; + FILE *fp, *ofp = 0, *rfp = 0; + struct tm *tm; + int i; + char bb[MSGBUFSZ]; + size_t n; + dstr d = DSTR_INIT, dd = DSTR_INIT; + const encops *eo; + msgcanon mc_in = MC_INIT; + enc *e; + + for (;;) { + static const struct option opt[] = { + { "armour", 0, 0, 'a' }, + { "armor", 0, 0, 'a' }, + { "buffer", 0, 0, 'b' }, + { "key", OPTF_ARGREQ, 0, 'k' }, + { "format", OPTF_ARGREQ, 0, 'f' }, + { "output", OPTF_ARGREQ, 0, 'o' }, + { "quiet", 0, 0, 'q' }, + { "utc", 0, 0, 'u' }, + { "gmt", 0, 0, 'u' }, + { "verbose", 0, 0, 'v' }, + { 0, 0, 0, 0 } + }; + i = mdwopt(argc, argv, "k:f:o:abquv", opt, 0, 0, 0); + if (i < 0) break; + switch (i) { + case 'a': ef = "pem"; break; + case 'b': v.f |= F_BUFFER; break; + case 'k': kn = optarg; break; + case 'f': ef = optarg; break; + case 'o': of = optarg; break; + case 'u': v.f |= F_UTC; break; + case 'q': if (v.verb > 0) v.verb--; break; + case 'v': if (v.verb < 10) v.verb++; break; + default: v.f |= F_BOGUS; break; + } + } + if (argc - optind > 2 || (v.f & F_BOGUS)) + die(EXIT_FAILURE, "Usage: verify [-OPTIONS] [FILE [MESSAGE]]"); + + if ((eo = getenc(ef)) == 0) + die(EXIT_FAILURE, "encoding `%s' not found", ef); + + if (optind >= argc) + fp = stdin; + else if (strcmp(argv[optind], "-") == 0) { + fp = stdin; + optind++; + } else if ((fp = fopen(argv[optind], eo->rmode)) == 0) { + die(EXIT_FAILURE, "couldn't open file `%s': %s", + argv[optind], strerror(errno)); + } else + optind++; + + if (key_open(&kf, keyring, KOPEN_READ, keyreport, 0)) + die(EXIT_FAILURE, "can't open keyring `%s'", keyring); + if (kn && (kk = key_bytag(&kf, kn)) == 0) + die(EXIT_FAILURE, "key `%s' not found", kn); + + e = initdec(eo, fp, vrfbdry, &v); + + /* --- Read the header chunk --- */ + + v.what = "malformed header"; + sig_readheader(e, &s, vrfchoke, &v); + + if (((s.f ^ v.f) & v.m) != 0) { + if (v.verb) printf("FAIL boundary string inconsistent with contents\n"); + exit(EXIT_FAILURE); + } + v.f |= s.f; + + if ((k = key_byid(&kf, s.keyid)) == 0) { + if (v.verb) printf("FAIL key id %08lx not found\n", + (unsigned long)s.keyid); + exit(EXIT_FAILURE); + } + if (kk && k->id != kk->id) { + if (v.verb) { + dstr_reset(&d); key_fulltag(k, &d); + dstr_reset(&dd); key_fulltag(kk, &dd); + printf("FAIL signing key is %s; expected key %s\n", d.buf, dd.buf); + } + exit(EXIT_FAILURE); + } + + s.s = getsig(k, "ccsig", 0); + dstr_reset(&d); key_fulltag(k, &d); + if (v.verb && (err = s.s->ops->check(s.s)) != 0) + printf("WARN verification key %s fails check: %s\n", d.buf, err); + + dstr_reset(&dd); keyhash(k, s.s, &dd); + if (dd.len != s.kh.len || memcmp(dd.buf, s.kh.buf, dd.len) != 0) { + if (v.verb) printf("FAIL key hash mismatch\n"); + exit(EXIT_FAILURE); + } + + /* --- Now a merry dance --- */ + + if (v.f & F_DETACH) + sig_readsig(e, &s); + if (optind < argc) + dfn = argv[optind++]; + mcsetup_read(&mc_in, v.f, &e, dfn); + + if (!of && (v.f & F_DETACH)) { + rfp = ofp = 0; + v.f &= ~F_BUFFER; + } else if (!of || strcmp(of, "-") == 0) { + v.f |= F_BUFFER; + ofp = stdout; + } + if (of && !(v.f & F_BUFFER)) { + if ((ofp = fopen(of, (v.f & F_BINARY) ? "wb" : "w")) == 0) { + die(EXIT_FAILURE, "couldn't open file `%s' for output: %s", + of, strerror(errno)); + } + rfp = ofp; + } else if ((rfp = tmpfile()) == 0) + die(EXIT_FAILURE, "couldn't create temporary file: %s", strerror(errno)); + + /* --- Read the message and verify the signature --- */ + + sig_hash(&s); + for (;;) { + n = mc_in.read(&mc_in, bb); + if (!n) break; + dohash(s.s->h, bb, n); + if (rfp) fwrite(bb, 1, n, rfp); + } + mc_endread(&mc_in, eo, &e); + if (!(v.f & F_DETACH)) + sig_readsig(e, &s); + if (rfp && (ferror(rfp) || fflush(rfp))) { + if (v.verb) printf("FAIL error writing message: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + /* --- Check the signature --- */ + + if (s.s->ops->doit(s.s, &s.sig)) { + if (v.verb) printf("FAIL signature verification failed\n"); + exit(EXIT_FAILURE); + } + if (v.verb) { + tm = (v.f & F_UTC) ? gmtime(&s.t) : localtime(&s.t); + strftime(bb, sizeof(bb), "%Y-%m-%d %H:%M:%S %Z", tm); + printf("INFO good-signature %s\n", d.buf); + printf("INFO date %s\n", bb); + } + + /* --- Unbuffer buffered input --- */ + + if (v.f & F_BUFFER) { + if (!ofp && (ofp = fopen(of, "wb")) == 0) { + die(EXIT_FAILURE, "couldn't open file `%s' for output: %s", + of, strerror(errno)); + } + rewind(rfp); + if (v.verb && ofp == stdout) printf("DATA\n"); + for (;;) { + n = fread(bb, 1, sizeof(bb), rfp); + if (!n) break; + if (fwrite(bb, 1, n, ofp) < n) + die(EXIT_FAILURE, "error writing output: %s", strerror(errno)); + } + if (ferror(rfp) || fclose(rfp)) + die(EXIT_FAILURE, "error unbuffering output: %s", strerror(errno)); + } + if (ofp && (fflush(ofp) || ferror(ofp) || fclose(ofp))) + die(EXIT_FAILURE, "error writing output: %s", strerror(errno)); + + /* --- Tidy up --- */ + + e->ops->decdone(e); + if (v.verb && ofp != stdout) + printf("OK verified successfully\n"); + freeenc(e); + key_close(&kf); + sig_destroy(&s); + dstr_destroy(&d); + dstr_destroy(&dd); + return (0); +} + +/*----- Reformatting ------------------------------------------------------*/ + +static int format(int argc, char *argv[]) +{ + const char *ief = "binary", *oef = "binary"; + const char *dfn = 0, *of = 0, *mf = 0; + sigmsg s; + FILE *fp, *ofp = 0, *mfp = 0; + int i; + size_t n; + msgcanon mc_in = MC_INIT, mc_out = MC_INIT; + char bb[MSGBUFSZ]; + vrfctx v = { 0, 0, 1 }; + unsigned f = 0, fm = ~F_SIGMASK, sf; + const encops *ieo, *oeo; + enc *ie, *oe; + + for (;;) { + static const struct option opt[] = { + { "armour-in", 0, 0, 'a' }, + { "armor-in", 0, 0, 'a' }, + { "armour-out", 0, 0, 'A' }, + { "armor-out", 0, 0, 'A' }, + { "detach", 0, 0, 'D' }, + { "embed", 0, 0, 'E' }, + { "format-in", OPTF_ARGREQ, 0, 'f' }, + { "format-out", OPTF_ARGREQ, 0, 'F' }, + { "message", OPTF_ARGREQ, 0, 'm' }, + { "output", OPTF_ARGREQ, 0, 'o' }, + { 0, 0, 0, 0 } + }; + i = mdwopt(argc, argv, "f:F:m:o:aADE", opt, 0, 0, 0); + if (i < 0) break; + switch (i) { + case 'a': ief = "pem"; break; + case 'A': oef = "pem"; break; + case 'f': ief = optarg; break; + case 'F': oef = optarg; break; + case 'D': f |= F_DETACH; fm |= F_DETACH; break; + case 'E': f &= ~F_DETACH; fm |= F_DETACH; break; + case 'm': mf = optarg; break; + case 'o': of = optarg; break; + default: f |= F_BOGUS; break; + } + } + + if (argc - optind > 2 || (f & F_BOGUS)) + die(EXIT_FAILURE, "Usage: format [-OPTIONS] [FILE [MESSAGE]]"); + + if ((ieo = getenc(ief)) == 0) + die(EXIT_FAILURE, "encoding `%s' not found", ief); + if ((oeo = getenc(oef)) == 0) + die(EXIT_FAILURE, "encoding `%s' not found", oef); + + if (optind >= argc) + fp = stdin; + else if (strcmp(argv[optind], "-") == 0) { + fp = stdin; + optind++; + } else if ((fp = fopen(argv[optind], ieo->rmode)) == 0) { + die(EXIT_FAILURE, "couldn't open file `%s': %s", + argv[optind], strerror(errno)); + } else + optind++; + + if (optind < argc) + dfn = argv[optind++]; + + ie = initdec(ieo, fp, vrfbdry, &v); + + /* --- Read the header chunk --- */ + + sig_readheader(ie, &s, diechoke, "malformed header"); + + if (((s.f ^ v.f) & v.m) != 0) + moan("boundary string inconsistent with contents (ignoring)"); + + mcsetup_read(&mc_in, s.f, &ie, dfn); + + /* --- Prepare the output stuff --- */ + + if (!of && !mf) of = "-"; + sf = s.f; + f = (f & fm) | (s.f & ~fm); + s.f = f & F_SIGMASK; + + if (sf & F_DETACH) + sig_readsig(ie, &s); + + if (!of) + mcsetup_writenull(&mc_out); + else { + if (strcmp(of, "-") == 0) + ofp = stdout; + else if ((ofp = fopen(of, oeo->wmode)) == 0) { + die(EXIT_FAILURE, "couldn't open file `%s' for output: %s", + of, strerror(errno)); + } + oe = initenc(oeo, ofp, + (f & F_DETACH) ? "CATSIGN SIGNATURE" : + (f & F_BINARY) ? "CATSIGN MESSAGE" : + "CATSIGN MESSAGE HEADER"); + sig_writeheader(oe, &s); + mcsetup_write(&mc_out, f, &oe); + } + + if (mf) { + if (strcmp(mf, "-") == 0) + mfp = stdout; + else if ((mfp = fopen(mf, (f & F_BINARY) ? "wb" : "w")) == 0) { + die(EXIT_FAILURE, "couldn't open file `%s' for output: %s", + mf, strerror(errno)); + } + } + + /* --- Wade through the message body --- */ + + for (;;) { + n = mc_in.read(&mc_in, bb); + if (!n) break; + mc_out.write(&mc_out, bb, n); + if (mfp) fwrite(bb, 1, n, mfp); + } + mc_endread(&mc_in, ieo, &ie); + if (of) mc_endwrite(&mc_out, oeo, &oe); + + /* --- Write the signature --- */ + + if (!(sf & F_DETACH)) + sig_readsig(ie, &s); + if (of) { + sig_writesig(oe, &s); + oe->ops->encdone(oe); + } + + /* --- All done --- */ + + ie->ops->decdone(ie); + if (ferror(fp) || fclose(fp)) + die(EXIT_FAILURE, "error reading input signature: %s", strerror(errno)); + if (ofp && (fflush(ofp) || ferror(ofp) || fclose(ofp))) + die(EXIT_FAILURE, "error writing output signature: %s", strerror(errno)); + if (mfp && (fflush(mfp) || ferror(mfp) || fclose(mfp))) + die(EXIT_FAILURE, "error writing output message: %s", strerror(errno)); + freeenc(ie); + if (of) freeenc(oe); + sig_destroy(&s); + return (0); +} + +static void infochoke(const char *m, void *p) +{ + vrfctx *v = p; + printf("BAD %s: %s\n", v->what, m); + exit(EXIT_FAILURE); +} + +static void infokeyreport(const char *file, int line, + const char *err, void *p) +{ /*whatever*/; } + +static int info(int argc, char *argv[]) +{ + const char *ef = "binary"; + vrfctx v = { 0, 0, 1 }; + key_file kf; + key *k; + sigmsg s; + FILE *fp; + int i; + struct tm *tm; + char bb[256]; + dstr d = DSTR_INIT; + const encops *eo; + enc *e; + + for (;;) { + static const struct option opt[] = { + { "armour", 0, 0, 'a' }, + { "armor", 0, 0, 'a' }, + { "format", OPTF_ARGREQ, 0, 'f' }, + { "gmt", 0, 0, 'u' }, + { "utc", 0, 0, 'u' }, + { 0, 0, 0, 0 } + }; + i = mdwopt(argc, argv, "f:au", opt, 0, 0, 0); + if (i < 0) break; + switch (i) { + case 'a': ef = "pem"; break; + case 'f': ef = optarg; break; + case 'u': v.f |= F_UTC; break; + default: v.f |= F_BOGUS; break; + } + } + if (argc - optind > 1 || (v.f & F_BOGUS)) + die(EXIT_FAILURE, "Usage: info [-OPTIONS] [FILE]"); + + if ((eo = getenc(ef)) == 0) + die(EXIT_FAILURE, "encoding `%s' not found", ef); + + if (optind >= argc) + fp = stdin; + else if (strcmp(argv[optind], "-") == 0) { + fp = stdin; + optind++; + } else if ((fp = fopen(argv[optind], eo->rmode)) == 0) { + die(EXIT_FAILURE, "couldn't open file `%s': %s", + argv[optind], strerror(errno)); + } else + optind++; + + if (key_open(&kf, keyring, KOPEN_READ, infokeyreport, 0)) { + printf("NOTE can't open keyring `%s'\n", keyring); + keyring = 0; + } + e = initdec(eo, fp, vrfbdry, &v); + + v.what = "malformed header"; + sig_readheader(e, &s, infochoke, &v); + + printf("INFO flags %sdetach %sbinary\n", + (s.f & F_DETACH) ? "" : "!", + (s.f & F_BINARY) ? "" : "!"); + + if (((s.f ^ v.f) & v.m) != 0) { + printf("WARN boundary string inconsistent with contents\n"); + printf("INFO expected-flags"); + if (v.m & F_DETACH) printf(" %sdetach", (v.f & F_DETACH) ? "" : "!"); + if (v.m & F_BINARY) printf(" %sbinary", (v.f & F_BINARY) ? "" : "!"); + putchar('\n'); + } + v.f |= s.f; + + tm = (v.f & F_UTC) ? gmtime(&s.t) : localtime(&s.t); + strftime(bb, sizeof(bb), "%Y-%m-%d %H:%M:%S %Z", tm); + printf("INFO date %s\n", bb); + + if (keyring && (k = key_byid(&kf, s.keyid)) != 0) { + dstr_reset(&d); key_fulltag(k, &d); + printf("INFO key %s\n", d.buf); + } else + printf("INFO unknown-key %08lx\n", (unsigned long)s.keyid); + + if (keyring) key_close(&kf); + dstr_destroy(&d); + sig_destroy(&s); + return (0); +} + +/*----- Main code ---------------------------------------------------------*/ + +#define LISTS(LI) \ + LI("Lists", list, \ + listtab[i].name, listtab[i].name) \ + LI("Signature schemes", sig, \ + sigtab[i].name, sigtab[i].name) \ + LI("Encodings", enc, \ + enctab[i].name, enctab[i].name) \ + LI("Hash functions", hash, \ + ghashtab[i], ghashtab[i]->name) + +MAKELISTTAB(listtab, LISTS) + +int cmd_show(int argc, char *argv[]) +{ + return (displaylists(listtab, argv + 1)); +} + +static int cmd_help(int, char **); + +static cmd cmdtab[] = { + { "help", cmd_help, "help [COMMAND...]" }, + { "show", cmd_show, "show [ITEM...]" }, + CMD_ENCODE, + CMD_DECODE, + { "sign", sign, + "sign [-adt] [-k TAG] [-f FORMAT] [-o OUTPUT] [FILE]", "\ +Options:\n\ +\n\ +-a, --armour Same as `-f pem'.\n\ +-b, --binary Treat the input message as binary data.\n\ +-d, --detach Produce a detached signature.\n\ +-f, --format=FORMAT Encode as FORMAT.\n\ +-k, --key=TAG Use public encryption key named by TAG.\n\ +-o, --output=FILE Write output to FILE.\n\ +-t, --text Canonify input message as a text file.\n\ +" }, + { "verify", verify, + "verify [-abquv] [-f FORMAT] [-k TAG] [-o OUTPUT]\n\t\ +[FILE [MESSAGE]]", "\ +Options:\n\ +\n\ +-a, --armour Same as `-f pem'.\n\ +-b, --buffer Buffer message until signature is verified.\n\ +-f, --format=FORMAT Decode as FORMAT.\n\ +-k, --key=TAG Require that the message be signed by key TAG.\n\ +-o, --output=FILE Write message to FILE.\n\ +-q, --quiet Produce fewer messages.\n\ +-u, --utc Show dates in UTC rather than local time.\n\ +-v, --verbose Produce more verbose messages.\n\ +" }, + { "info", info, + "info [-au] [-f FORMAT] [FILE]", "\ +Options:\n\ +\n\ +-a, --armour Same as `-f pem'.\n\ +-f, --format=FORMAT Decode as FORMAT.\n\ +-u, --utc Show dates in UTC rather than local time.\n\ +"}, + { "format", format, + "format [-auADE] [-f FORMAT] [-F format] [-m FILE] [-o FILE]\n\t\ +[FILE [MESSAGE]]", "\ +Options:\n\ +\n\ +-a, --armour-in Same as `-f pem'.\n\ +-A, --armour-out Same as `-F pem'.\n\ +-D, --detach Create detached signature.\n\ +-E, --embed Create signature with embedded message.\n\ +-f, --format-in=FORMAT Decode input as FORMAT.\n\ +-F, --format-out=FORMAT Encode output as FORMAT.\n\ +-m, --message=FILE Write message to FILE.\n\ +-o, --output=FILE Write new signature to FILE.\n\ +"}, + { 0, 0, 0 } +}; /* " Emacs seems confused. */ + +static int cmd_help(int argc, char **argv) +{ + sc_help(cmdtab, stdout, argv + 1); + return (0); +} + +void version(FILE *fp) +{ + pquis(fp, "$, Catacomb version " VERSION "\n"); +} + +static void usage(FILE *fp) +{ + pquis(fp, "Usage: $ [-k KEYRING] COMMAND [ARGS]\n"); +} + +void help_global(FILE *fp) +{ + usage(fp); + fputs("\n\ +Sign and verify data.\n\ +\n\ +Global command-line options:\n\ +\n\ +-h, --help [COMMAND...] Show this help message, or help for COMMANDs.\n\ +-v, --version Show program version number.\n\ +-u, --usage Show a terse usage message.\n\ +\n\ +-k, --keyring=FILE Read keys from FILE.\n", + fp); +} + +/* --- @main@ --- * + * + * Arguments: @int argc@ = number of command line arguments + * @char *argv[]@ = vector of command line arguments + * + * Returns: Zero if successful, nonzero otherwise. + * + * Use: Encrypts or decrypts files. + */ + +int main(int argc, char *argv[]) +{ + unsigned f = 0; + +#define f_bogus 1u + + /* --- Initialize the library --- */ + + ego(argv[0]); + sub_init(); + rand_noisesrc(RAND_GLOBAL, &noise_source); + rand_seed(RAND_GLOBAL, 160); +/* trace_on(stderr, 1); */ + + /* --- Parse options --- */ + + for (;;) { + static struct option opts[] = { + { "help", 0, 0, 'h' }, + { "version", 0, 0, 'v' }, + { "usage", 0, 0, 'u' }, + { "keyring", OPTF_ARGREQ, 0, 'k' }, + { 0, 0, 0, 0 } + }; + int i = mdwopt(argc, argv, "+hvu k:", opts, 0, 0, 0); + if (i < 0) + break; + switch (i) { + case 'h': + sc_help(cmdtab, stdout, argv + optind); + exit(0); + break; + case 'v': + version(stdout); + exit(0); + break; + case 'u': + usage(stdout); + exit(0); + case 'k': + keyring = optarg; + break; + default: + f |= f_bogus; + break; + } + } + + argc -= optind; + argv += optind; + optind = 0; + if (f & f_bogus || argc < 1) { + usage(stderr); + exit(EXIT_FAILURE); + } + + /* --- Dispatch to the correct subcommand handler --- */ + + return (findcmd(cmdtab, argv[0])->cmd(argc, argv)); + +#undef f_bogus +} + +/*----- That's all, folks -------------------------------------------------*/