X-Git-Url: https://git.distorted.org.uk/u/mdw/catacomb/blobdiff_plain/d470270aa476e8100eb78b1958ce5dd557983195..097fb6f2f97575ce17738b4afb3216e9492de2b4:/dsig.c diff --git a/dsig.c b/dsig.c index e591538..a7fa5a9 100644 --- a/dsig.c +++ b/dsig.c @@ -1,13 +1,13 @@ /* -*-c-*- * - * $Id: dsig.c,v 1.4 2000/08/04 23:23:44 mdw Exp $ + * $Id$ * * Verify signatures on distribuitions of files * * (c) 2000 Straylight/Edgeware */ -/*----- Licensing notice --------------------------------------------------* +/*----- Licensing notice --------------------------------------------------* * * This file is part of Catacomb. * @@ -15,37 +15,22 @@ * 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. */ -/*----- Revision history --------------------------------------------------* - * - * $Log: dsig.c,v $ - * Revision 1.4 2000/08/04 23:23:44 mdw - * Various fixes. - * - * Revision 1.3 2000/07/15 20:53:23 mdw - * More hash functions. Bug fix in getstring. - * - * Revision 1.2 2000/07/01 11:27:22 mdw - * Use new PKCS#1 padding functions rather than rolling by hand. - * - * Revision 1.1 2000/06/17 10:54:29 mdw - * Program to generate and verify signatures on multiple files. - * - */ - /*----- Header files ------------------------------------------------------*/ +#define _FILE_OFFSET_BITS 64 + #include "config.h" #include @@ -62,195 +47,12 @@ #include #include "getdate.h" -#include "grand.h" +#include "rand.h" #include "ghash.h" #include "key.h" #include "key-data.h" #include "noise.h" - -#include "dsa.h" -#include "rsa.h" -#include "pkcs1.h" - -#include "md4.h" -#include "md5.h" -#include "rmd128.h" -#include "rmd160.h" -#include "rmd256.h" -#include "rmd320.h" -#include "sha.h" -#include "tiger.h" - -/*----- Digital signature algorithm ---------------------------------------*/ - -static int dsasign(key *k, const void *m, size_t msz, dstr *d) -{ - dsa_priv dp; - key_packstruct ks[DSA_PRIVFETCHSZ]; - key_packdef *kp; - size_t sz; - octet *p; - int e; - - kp = key_fetchinit(dsa_privfetch, ks, &dp); - if ((e = key_fetch(kp, k)) != 0) { - key_fetchdone(kp); - return (e); - } - sz = mp_octets(dp.dp.q); - if (sz < msz) - die(EXIT_FAILURE, "hash function too wide for this signing key"); - DENSURE(d, sz * 2); - p = d->buf + d->len; - rand_get(RAND_GLOBAL, p, sz); - dsa_sign(&dp.dp, dp.x, m, msz, p, sz, p, sz, p + sz, sz); - d->len += sz * 2; - key_fetchdone(kp); - return (0); -} - -static int dsaverify(key *k, const void *m, size_t msz, - const void *s, size_t ssz) -{ - dsa_pub dp; - key_packstruct ks[DSA_PUBFETCHSZ]; - key_packdef *kp; - size_t sz; - int e; - - kp = key_fetchinit(dsa_pubfetch, ks, &dp); - if ((e = key_fetch(kp, k)) != 0) { - key_fetchdone(kp); - return (e); - } - sz = ssz / 2; - e = dsa_verify(&dp.dp, dp.y, m, msz, s, sz, s + sz, sz); - key_fetchdone(kp); - return (e); -} - -/*----- RSA signing -------------------------------------------------------*/ - -static int rsasign(key *k, const void *m, size_t msz, dstr *d) -{ - rsa_priv rp; - rsa_privctx rpc; - pkcs1 pk = { 0, 0, 0 }; - key_packstruct ks[RSA_PRIVFETCHSZ]; - key_packdef *kp; - int e; - - kp = key_fetchinit(rsa_privfetch, ks, &rp); - if ((e = key_fetch(kp, k)) != 0) { - key_fetchdone(kp); - return (e); - } - rsa_privcreate(&rpc, &rp, &rand_global); - if (rsa_sign(&rpc, m, msz, d, pkcs1_sigencode, &pk) < 0) - die(EXIT_FAILURE, "internal error in rsasign (key too small?)"); - rsa_privdestroy(&rpc); - key_fetchdone(kp); - return (0); -} - -static int rsaverify(key *k, const void *m, size_t msz, - const void *s, size_t ssz) -{ - rsa_pub rp; - rsa_pubctx rpc; - pkcs1 pk = { 0, 0, 0 }; - key_packstruct ks[RSA_PUBFETCHSZ]; - key_packdef *kp; - int ok = 0; - dstr d = DSTR_INIT; - int e; - - kp = key_fetchinit(rsa_pubfetch, ks, &rp); - if ((e = key_fetch(kp, k)) != 0) { - key_fetchdone(kp); - return (e); - } - rsa_pubcreate(&rpc, &rp); - if (rsa_verify(&rpc, s, ssz, &d, pkcs1_sigdecode, &pk) > 0 && - msz == d.len && memcmp(d.buf, m, msz) == 0) - ok = 1; - dstr_destroy(&d); - rsa_pubdestroy(&rpc); - key_fetchdone(kp); - return (ok); -} - -/*----- Algorithm choice --------------------------------------------------*/ - -typedef struct sig { - const char *name; - const char *type; - int (*sign)(key */*k*/, const void */*m*/, size_t /*msz*/, dstr */*d*/); - int (*verify)(key */*k*/, const void */*m*/, size_t /*msz*/, - const void */*s*/, size_t /*ssz*/); -} sig; - -static const gchash *hashtab[] = { - &rmd160, &tiger, &sha, &rmd128, &rmd256, &rmd320, &md5, &md4, 0 }; -static sig sigtab[] = { - { "dsa", "dsig-dsa", dsasign, dsaverify }, - { "rsa", "dsig-rsa", rsasign, rsaverify }, - { 0, 0, 0 } -}; - -/* --- @gethash@ --- * - * - * Arguments: @const char *name@ = pointer to name string - * - * Returns: Pointer to appropriate hash class. - * - * Use: Chooses a hash function by name. - */ - -static const gchash *gethash(const char *name) -{ - const gchash **g, *gg = 0; - size_t sz = strlen(name); - for (g = hashtab; *g; g++) { - if (strncmp(name, (*g)->name, sz) == 0) { - if ((*g)->name[sz] == 0) { - gg = *g; - break; - } else if (gg) - return (0); - else - gg = *g; - } - } - return (gg); -} - -/* --- @getsig@ --- * - * - * Arguments: @const char *name@ = pointer to name string - * - * Returns: Pointer to appropriate signature type. - * - * Use: Chooses a signature algorithm by name. - */ - -static sig *getsig(const char *name) -{ - sig *s, *ss = 0; - size_t sz = strlen(name); - for (s = sigtab; s->name; s++) { - if (strncmp(name, s->name, sz) == 0) { - if (s->name[sz] == 0) { - ss = s; - break; - } else if (ss) - return (0); - else - ss = s; - } - } - return (ss); -} +#include "cc.h" /*----- Data formatting ---------------------------------------------------*/ @@ -265,8 +67,6 @@ enum { /* --- Block tags --- */ T_IDENT = 0, /* An identifying marker */ - T_SIGALG, /* Signature algorithm used */ - T_HASHALG, /* Hash algorithm used */ T_KEYID, /* Key identifier */ T_BEGIN, /* Begin hashing here */ T_COMMENT = T_BEGIN, /* A textual comment */ @@ -286,7 +86,7 @@ enum { /* --- Name translation table --- */ static const char *tagtab[] = { - "ident:", "sigalg:", "hashalg:", "keyid:", + "ident:", "keyid:", "comment:", "date:", "expires:", "file:", "signature:", 0 @@ -310,171 +110,6 @@ typedef struct block { uint32 k; /* Keyid */ } block; -/* --- @getstring@ --- * - * - * Arguments: @FILE *fp@ = stream from which to read - * @dstr *d@ = destination string - * @unsigned raw@ = raw or cooked read - * - * Returns: Zero if OK, nonzero on end-of-file. - * - * Use: Reads a filename (or something similar) from a stream. - */ - -static int getstring(FILE *fp, dstr *d, unsigned raw) -{ - int ch; - int q = 0; - - /* --- Raw: just read exactly what's written up to a null byte --- */ - - if (raw) { - if ((ch = getc(fp)) == EOF) - return (EOF); - for (;;) { - if (!ch) - break; - DPUTC(d, ch); - if ((ch = getc(fp)) == EOF) - break; - } - DPUTZ(d); - return (0); - } - - /* --- Skip as far as whitespace --- * - * - * Also skip past comments. - */ - -again: - ch = getc(fp); - while (isspace(ch)) - ch = getc(fp); - if (ch == '#') { - do ch = getc(fp); while (ch != '\n' && ch != EOF); - goto again; - } - if (ch == EOF) - return (EOF); - - /* --- If the character is a quote then read a quoted string --- */ - - switch (ch) { - case '`': - ch = '\''; - case '\'': - case '\"': - q = ch; - ch = getc(fp); - break; - } - - /* --- Now read all sorts of interesting things --- */ - - for (;;) { - - /* --- Handle an escaped thing --- */ - - if (ch == '\\') { - ch = getc(fp); - if (ch == EOF) - break; - switch (ch) { - case 'a': ch = '\a'; break; - case 'b': ch = '\b'; break; - case 'f': ch = '\f'; break; - case 'n': ch = '\n'; break; - case 'r': ch = '\r'; break; - case 't': ch = '\t'; break; - case 'v': ch = '\v'; break; - } - DPUTC(d, ch); - ch = getc(fp); - continue; - } - - /* --- If it's a quote or some other end marker then stop --- */ - - if (ch == q || (!q && isspace((unsigned char)ch))) - break; - - /* --- Otherwise contribute and continue --- */ - - DPUTC(d, ch); - if ((ch = getc(fp)) == EOF) - break; - } - - /* --- Done --- */ - - DPUTZ(d); - return (0); -} - -/* --- @putstring@ --- * - * - * Arguments: @FILE *fp@ = stream to write on - * @const char *p@ = pointer to text - * @unsigned raw@ = whether the string is to be written raw - * - * Returns: --- - * - * Use: Emits a string to a stream. - */ - -static void putstring(FILE *fp, const char *p, unsigned raw) -{ - size_t sz = strlen(p); - unsigned qq; - const char *q; - - /* --- Just write the string null terminated if raw --- */ - - if (raw) { - fwrite(p, 1, sz + 1, fp); - return; - } - - /* --- Check for any dodgy characters --- */ - - qq = 0; - for (q = p; *q; q++) { - if (isspace((unsigned char)*q)) { - qq = '\"'; - break; - } - } - - if (qq) - putc(qq, fp); - - /* --- Emit the string --- */ - - for (q = p; *q; q++) { - switch (*q) { - case '\a': fputc('\\', fp); fputc('a', fp); break; - case '\b': fputc('\\', fp); fputc('b', fp); break; - case '\f': fputc('\\', fp); fputc('f', fp); break; - case '\n': fputc('\\', fp); fputc('n', fp); break; - case '\r': fputc('\\', fp); fputc('r', fp); break; - case '\t': fputc('\\', fp); fputc('t', fp); break; - case '\v': fputc('\\', fp); fputc('v', fp); break; - case '`': fputc('\\', fp); fputc('`', fp); break; - case '\'': fputc('\\', fp); fputc('\'', fp); break; - case '\"': fputc('\\', fp); fputc('\"', fp); break; - default: - putc(*q, fp); - break; - } - } - - /* --- Done --- */ - - if (qq) - putc(qq, fp); -} - /* --- @timestring@ --- * * * Arguments: @time_t t@ = a timestamp @@ -568,7 +203,7 @@ static int bget(block *b, FILE *fp, unsigned bin) tag = getc(fp); else { dstr d = DSTR_INIT; - if (getstring(fp, &d, 0)) + if (getstring(fp, &d, GSF_FILE)) return (E_EOF); for (tag = 0; tagtab[tag]; tag++) { if (strcmp(tagtab[tag], d.buf) == 0) @@ -588,9 +223,7 @@ static int bget(block *b, FILE *fp, unsigned bin) case T_IDENT: case T_COMMENT: - case T_SIGALG: - case T_HASHALG: - if (getstring(fp, &b->d, bin)) + if (getstring(fp, &b->d, GSF_FILE | (bin ? GSF_RAW : 0))) return (E_EOF); break; @@ -605,7 +238,7 @@ static int bget(block *b, FILE *fp, unsigned bin) b->t = ((time_t)(((LOAD32(buf + 0) << 16) << 16) & ~MASK32) | (time_t)LOAD32(buf + 4)); } else { - if (getstring(fp, &b->d, 0)) + if (getstring(fp, &b->d, GSF_FILE)) return (E_EOF); if (strcmp(b->d.buf, "forever") == 0) b->t = KEXP_FOREVER; @@ -623,7 +256,7 @@ static int bget(block *b, FILE *fp, unsigned bin) return (E_EOF); b->k = LOAD32(buf); } else { - if (getstring(fp, &b->d, 0)) + if (getstring(fp, &b->d, GSF_FILE)) return (E_EOF); b->k = strtoul(b->d.buf, 0, 16); } @@ -647,14 +280,15 @@ static int bget(block *b, FILE *fp, unsigned bin) b->b.len += sz; } else { base64_ctx b64; - if (getstring(fp, &b->d, 0)) + if (getstring(fp, &b->d, GSF_FILE)) return (E_EOF); base64_init(&b64); base64_decode(&b64, b->d.buf, b->d.len, &b->b); base64_decode(&b64, 0, 0, &b->b); DRESET(&b->d); } - if (tag == T_FILE && getstring(fp, &b->d, bin)) + if (tag == T_FILE && + getstring(fp, &b->d, GSF_FILE | (bin ? GSF_RAW : 0))) return (E_EOF); break; @@ -682,8 +316,6 @@ static void blob(block *b, dstr *d) DPUTC(d, b->tag); switch (b->tag) { case T_IDENT: - case T_SIGALG: - case T_HASHALG: case T_COMMENT: DPUTD(d, &b->d); DPUTC(d, 0); @@ -691,8 +323,13 @@ static void blob(block *b, dstr *d) case T_DATE: case T_EXPIRE: DENSURE(d, 8); - STORE32(d->buf + d->len, ((b->t & ~MASK32) >> 16) >> 16); - STORE32(d->buf + d->len + 4, b->t); + if (b->t == KEXP_FOREVER) { + STORE32(d->buf + d->len, 0xffffffff); + STORE32(d->buf + d->len + 4, 0xffffffff); + } else { + STORE32(d->buf + d->len, ((b->t & ~MASK32) >> 16) >> 16); + STORE32(d->buf + d->len + 4, b->t); + } d->len += 8; break; case T_KEYID: @@ -730,8 +367,6 @@ static void bwrite(block *b, FILE *fp) putc(' ', fp); switch (b->tag) { case T_IDENT: - case T_SIGALG: - case T_HASHALG: case T_COMMENT: putstring(fp, b->d.buf, 0); break; @@ -781,68 +416,20 @@ static void bemit(block *b, FILE *fp, ghash *h, unsigned bin) dstr d = DSTR_INIT; blob(b, &d); if (h) - h->ops->hash(h, d.buf, d.len); + GH_HASH(h, d.buf, d.len); if (fp && bin) fwrite(d.buf, d.len, 1, fp); } if (fp && !bin) bwrite(b, fp); } - + /*----- Static variables --------------------------------------------------*/ static const char *keyring = "keyring"; /*----- Other shared functions --------------------------------------------*/ -/* --- @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); -} - -/* --- @fhash@ --- * - * - * Arguments: @const gchash *c@ = pointer to hash class - * @const char *file@ = file to hash - * @octet *b@ = pointer to output buffer - * - * Returns: Zero if it worked, or nonzero for a system error. - * - * Use: Hashes a file. - */ - -static int fhash(const gchash *c, const char *file, octet *b) -{ - FILE *fp = fopen(file, "rb"); - ghash *h = c->init(); - char buf[4096]; - size_t sz; - int rc = 0; - - if (!fp) - return (-1); - while ((sz = fread(buf, 1, sizeof(buf), fp)) > 0) - h->ops->hash(h, buf, sz); - if (ferror(fp)) - rc = -1; - h->ops->done(h, b); - h->ops->destroy(h); - fclose(fp); - return (rc); -} - /* --- @fhex@ --- * * * Arguments: @FILE *fp@ = file to write on @@ -864,61 +451,58 @@ static void fhex(FILE *fp, const void *p, size_t sz) sz--; if (!sz) break; -/* putc(' ', fp); */ } -} +} /*----- Signature generation ----------------------------------------------*/ static int sign(int argc, char *argv[]) { - enum { - f_raw = 1, - f_bin = 2, - f_bogus = 4 - }; +#define f_bogus 1u +#define f_bin 2u +#define f_nocheck 4u unsigned f = 0; - const char *kt = 0; - const char *ki = 0; + const char *ki = "dsig"; key_file kf; key *k; - const sig *s = sigtab; - const gchash *gch = &rmd160; - ghash *h; + sig *s; + fhashstate fh; time_t exp = KEXP_EXPIRE; unsigned verb = 0; - const char *ifile = 0; + const char *ifile = 0, *hfile = 0; const char *ofile = 0; const char *c = 0; + const char *err; FILE *ifp, *ofp; dstr d = DSTR_INIT; + hfpctx hfp; block b; - int e; + int e, hf, n; for (;;) { static struct option opts[] = { { "null", 0, 0, '0' }, { "binary", 0, 0, 'b' }, { "verbose", 0, 0, 'v' }, + { "progress", 0, 0, 'p' }, { "quiet", 0, 0, 'q' }, - { "algorithm", OPTF_ARGREQ, 0, 'a' }, - { "hash", OPTF_ARGREQ, 0, 'h' }, { "comment", OPTF_ARGREQ, 0, 'c' }, { "file", OPTF_ARGREQ, 0, 'f' }, + { "hashes", OPTF_ARGREQ, 0, 'h' }, { "output", OPTF_ARGREQ, 0, 'o' }, - { "keytype", OPTF_ARGREQ, 0, 't' }, - { "keyid", OPTF_ARGREQ, 0, 'i' }, - { "key", OPTF_ARGREQ, 0, 'i' }, + { "key", OPTF_ARGREQ, 0, 'k' }, { "expire", OPTF_ARGREQ, 0, 'e' }, + { "nocheck", OPTF_ARGREQ, 0, 'C' }, { 0, 0, 0, 0 } }; - int i = mdwopt(argc, argv, "+0vqb a:h:c: f:o: t:i:k:e:", opts, 0, 0, 0); + int i = mdwopt(argc, argv, "+0vpqbC" "c:" "f:h:o:" "k:e:", + opts, 0, 0, 0); if (i < 0) break; switch (i) { case '0': - f |= f_raw; + f |= GSF_RAW; break; case 'b': f |= f_bin; @@ -926,21 +510,15 @@ static int sign(int argc, char *argv[]) case 'v': verb++; break; + case 'p': + f |= FHF_PROGRESS; + break; case 'q': if (verb > 0) verb--; break; - case 'a': - if ((s = getsig(optarg)) == 0) { - die(EXIT_FAILURE, "unknown or ambiguous signature algorithm `%s'", - optarg); - } - break; - case 'h': - if ((gch = gethash(optarg)) == 0) { - die(EXIT_FAILURE, "unknown or ambiguous hash function `%s'", - optarg); - } + case 'C': + f |= f_nocheck; break; case 'c': c = optarg; @@ -948,13 +526,12 @@ static int sign(int argc, char *argv[]) case 'f': ifile = optarg; break; + case 'h': + hfile = optarg; + break; case 'o': ofile = optarg; break; - case 't': - kt = optarg; - break; - case 'i': case 'k': ki = optarg; break; @@ -970,37 +547,39 @@ static int sign(int argc, char *argv[]) } } if (optind != argc || (f & f_bogus)) - die(EXIT_FAILURE, "Usage: sign [-options]"); + die(EXIT_FAILURE, "Usage: sign [-OPTIONS]"); + if (hfile && ifile) + die(EXIT_FAILURE, "Inconsistent options `-h' and `-f'"); /* --- Locate the signing key --- */ - if (key_open(&kf, keyring, KOPEN_WRITE, keyreport, 0)) + if (key_open(&kf, keyring, KOPEN_WRITE, key_moan, 0)) die(EXIT_FAILURE, "couldn't open keyring `%s'", keyring); - if (ki) { - if ((k = key_bytag(&kf, ki)) == 0) - die(EXIT_FAILURE, "couldn't find key `%s'", ki); - } else { - if (!kt) - kt = s->type; - if ((k = key_bytype(&kf, kt)) == 0) - die(EXIT_FAILURE, "no appropriate key of type `%s'", kt); - } + if ((k = key_bytag(&kf, ki)) == 0) + die(EXIT_FAILURE, "couldn't find key `%s'", ki); key_fulltag(k, &d); if (exp == KEXP_FOREVER && k->exp != KEXP_FOREVER) { die(EXIT_FAILURE, "key `%s' expires: can't create nonexpiring signature", d.buf); } + s = getsig(k, "dsig", 1); + + /* --- Check the key --- */ + + if (!(f & f_nocheck) && (err = s->ops->check(s)) != 0) + moan("key `%s' fails check: %s", d.buf, err); /* --- Open files --- */ - if (!ifile) + if (hfile) ifile = hfile; + if (!ifile || strcmp(ifile, "-") == 0) ifp = stdin; - else if ((ifp = fopen(ifile, (f & f_raw) ? "rb" : "r")) == 0) { + else if ((ifp = fopen(ifile, (f & f_bin) ? "rb" : "r")) == 0) { die(EXIT_FAILURE, "couldn't open input file `%s': %s", ifile, strerror(errno)); } - if (!ofile) + if (!ofile || strcmp(ofile, "-") == 0) ofp = stdout; else if ((ofp = fopen(ofile, (f & f_bin) ? "wb" : "w")) == 0) { die(EXIT_FAILURE, "couldn't open output file `%s': %s", @@ -1012,12 +591,6 @@ static int sign(int argc, char *argv[]) binit(&b); b.tag = T_IDENT; dstr_putf(&b.d, "%s, Catacomb version " VERSION, QUIS); bemit(&b, ofp, 0, f & f_bin); - - breset(&b); b.tag = T_SIGALG; DPUTS(&b.d, s->name); - bemit(&b, ofp, 0, f & f_bin); - - breset(&b); b.tag = T_HASHALG; DPUTS(&b.d, gch->name); - bemit(&b, ofp, 0, f & f_bin); breset(&b); b.tag = T_KEYID; b.k = k->id; bemit(&b, ofp, 0, f & f_bin); @@ -1027,14 +600,13 @@ static int sign(int argc, char *argv[]) { time_t now = time(0); - h = gch->init(); - breset(&b); b.tag = T_DATE; b.t = now; bemit(&b, ofp, h, f & f_bin); + breset(&b); b.tag = T_DATE; b.t = now; bemit(&b, ofp, s->h, f & f_bin); if (exp == KEXP_EXPIRE) exp = now + 86400 * 28; - breset(&b); b.tag = T_EXPIRE; b.t = exp; bemit(&b, ofp, h, f & f_bin); + breset(&b); b.tag = T_EXPIRE; b.t = exp; bemit(&b, ofp, s->h, f & f_bin); if (c) { breset(&b); b.tag = T_COMMENT; DPUTS(&b.d, c); - bemit(&b, ofp, h, f & f_bin); + bemit(&b, ofp, s->h, f & f_bin); } if (!(f & f_bin)) @@ -1043,32 +615,72 @@ static int sign(int argc, char *argv[]) /* --- Now hash the various files --- */ - for (;;) { - - /* --- Stop on an output error --- */ + if (hfile) { + hfp.f = f; + hfp.fp = ifp; + hfp.ee = &encodingtab[ENC_HEX]; + hfp.gch = GH_CLASS(s->h); + hfp.dline = &d; + hfp.dfile = &b.d; - if (ferror(ofp)) { - f |= f_bogus; - break; + n = 0; + for (;;) { + breset(&b); + DENSURE(&b.b, hfp.gch->hashsz); + hfp.hbuf = (octet *)b.b.buf; + if (ferror(ofp)) { f |= f_bogus; break; } + if ((hf = hfparse(&hfp)) == HF_EOF) break; + n++; + + switch (hf) { + case HF_HASH: + if (hfp.gch != GH_CLASS(s->h)) { + moan("%s:%d: incorrect hash function `%s' (should be `%s')", + hfile, n, hfp.gch->name, GH_CLASS(s->h)->name); + f |= f_bogus; + } + break; + case HF_BAD: + moan("%s:%d: invalid hash-file line", hfile, n); + f |= f_bogus; + break; + case HF_FILE: + b.tag = T_FILE; + b.b.len += hfp.gch->hashsz; + bemit(&b, ofp, s->h, f & f_bin); + break; + } } + } else { + for (;;) { - /* --- Read the next filename to hash --- */ + /* --- Stop on an output error --- */ - breset(&b); - if (getstring(ifp, &b.d, f & f_raw)) - break; - b.tag = T_FILE; - DENSURE(&b.b, h->ops->c->hashsz); - if (fhash(gch, b.d.buf, b.b.buf)) { - moan("Error reading `%s': %s", b.d.buf, strerror(errno)); - f |= f_bogus; - } else { - b.b.len += h->ops->c->hashsz; - if (verb) { - fhex(stderr, b.b.buf, b.b.len); - fprintf(stderr, " %s\n", b.d.buf); + if (ferror(ofp)) { + f |= f_bogus; + break; + } + + /* --- Read the next filename to hash --- */ + + fhash_init(&fh, GH_CLASS(s->h), f | FHF_BINARY); + breset(&b); + if (getstring(ifp, &b.d, GSF_FILE | f)) + break; + b.tag = T_FILE; + DENSURE(&b.b, GH_CLASS(s->h)->hashsz); + if (fhash(&fh, b.d.buf, b.b.buf)) { + moan("error reading `%s': %s", b.d.buf, strerror(errno)); + f |= f_bogus; + } else { + b.b.len += GH_CLASS(s->h)->hashsz; + if (verb) { + fhex(stderr, b.b.buf, b.b.len); + fprintf(stderr, " %s\n", b.d.buf); + } + bemit(&b, ofp, s->h, f & f_bin); } - bemit(&b, ofp, h, f & f_bin); + fhash_free(&fh); } } @@ -1077,9 +689,7 @@ static int sign(int argc, char *argv[]) if (!(f & f_bogus)) { breset(&b); b.tag = T_SIGNATURE; - DENSURE(&b.d, h->ops->c->hashsz); - h->ops->done(h, b.d.buf); - if ((e = s->sign(k, b.d.buf, h->ops->c->hashsz, &b.b)) != 0) { + if ((e = s->ops->doit(s, &b.b)) != 0) { moan("error creating signature: %s", key_strerror(e)); f |= f_bogus; } @@ -1091,6 +701,7 @@ static int sign(int argc, char *argv[]) /* --- Tidy up at the end --- */ + freesig(s); bdestroy(&b); if (ifile) fclose(ifp); @@ -1114,26 +725,36 @@ static int sign(int argc, char *argv[]) if (f & f_bogus) die(EXIT_FAILURE, "error(s) occurred while creating signature"); return (EXIT_SUCCESS); + +#undef f_bin +#undef f_bogus +#undef f_nocheck } /*----- Signature verification --------------------------------------------*/ +static int checkjunk(const char *path, const struct stat *st, void *p) +{ + if (!st) printf("JUNK (error %s) %s\n", strerror(errno), path); + else printf("JUNK %s %s\n", describefile(st), path); + return (0); +} + static int verify(int argc, char *argv[]) { - enum { - f_bogus = 1, - f_bin = 2, - f_ok = 4 - }; +#define f_bogus 1u +#define f_bin 2u +#define f_ok 4u +#define f_nocheck 8u unsigned f = 0; unsigned verb = 1; key_file kf; key *k = 0; - sig *s = sigtab; - const gchash *gch = &rmd160; + sig *s; dstr d = DSTR_INIT; - ghash *h; + const char *err; + fhashstate fh; FILE *fp; block b; int e; @@ -1143,20 +764,32 @@ static int verify(int argc, char *argv[]) for (;;) { static struct option opts[] = { { "verbose", 0, 0, 'v' }, + { "progress", 0, 0, 'p' }, { "quiet", 0, 0, 'q' }, + { "nocheck", 0, 0, 'C' }, + { "junk", 0, 0, 'j' }, { 0, 0, 0, 0 } }; - int i = mdwopt(argc, argv, "+vq", opts, 0, 0, 0); + int i = mdwopt(argc, argv, "+vpqCj", opts, 0, 0, 0); if (i < 0) break; switch (i) { case 'v': verb++; break; + case 'p': + f |= FHF_PROGRESS; + break; case 'q': if (verb) verb--; break; + case 'C': + f |= f_nocheck; + break; + case 'j': + f |= FHF_JUNK; + break; default: f |= f_bogus; break; @@ -1165,11 +798,11 @@ static int verify(int argc, char *argv[]) argc -= optind; argv += optind; if ((f & f_bogus) || argc > 1) - die(EXIT_FAILURE, "Usage: verify [-qv] [file]"); + die(EXIT_FAILURE, "Usage: verify [-qvC] [FILE]"); /* --- Open the key file, and start reading the input file --- */ - if (key_open(&kf, keyring, KOPEN_READ, keyreport, 0)) + if (key_open(&kf, keyring, KOPEN_READ, key_moan, 0)) die(EXIT_FAILURE, "couldn't open keyring `%s'\n", keyring); if (argc < 1) fp = stdin; @@ -1193,11 +826,11 @@ static int verify(int argc, char *argv[]) /* --- Read the introductory matter --- */ binit(&b); - for (;;) { + for (;;) { breset(&b); e = bget(&b, fp, f & f_bin); if (e < 0) - die(EXIT_FAILURE, "error reading packet: %s\n", errtab[-e]); + die(EXIT_FAILURE, "error reading packet: %s", errtab[-e]); if (e >= T_BEGIN) break; switch (e) { @@ -1205,24 +838,6 @@ static int verify(int argc, char *argv[]) if (verb > 2) printf("INFO ident: `%s'\n", b.d.buf); break; - case T_SIGALG: - if ((s = getsig(b.d.buf)) == 0) { - if (verb) - printf("FAIL unknown signature algorithm `%s'\n", b.d.buf); - exit(EXIT_FAILURE); - } - if (verb > 2) - printf("INFO signature algorithm: %s\n", s->name); - break; - case T_HASHALG: - if ((gch = gethash(b.d.buf)) == 0) { - if (verb) - printf("FAIL unknown hash function `%s'\n", b.d.buf); - exit(EXIT_FAILURE); - } - if (verb > 2) - printf("INFO hash function algorithm: %s\n", gch->name); - break; case T_KEYID: if ((k = key_byid(&kf, b.k)) == 0) { if (verb) @@ -1243,18 +858,23 @@ static int verify(int argc, char *argv[]) /* --- Initialize the hash function and start reading hashed packets --- */ - h = gch->init(); if (!k) { if (verb) puts("FAIL no keyid packet found"); exit(EXIT_FAILURE); } + + s = getsig(k, "dsig", 0); + if (!(f & f_nocheck) && verb && (err = s->ops->check(s)) != 0) + printf("WARN public key fails check: %s", err); + + fhash_init(&fh, GH_CLASS(s->h), f | FHF_BINARY); for (;;) { switch (e) { case T_COMMENT: if (verb > 1) printf("INFO comment: `%s'\n", b.d.buf); - bemit(&b, 0, h, 0); + bemit(&b, 0, s->h, 0); break; case T_DATE: if (verb > 2) { @@ -1262,11 +882,11 @@ static int verify(int argc, char *argv[]) timestring(b.t, &b.d); printf("INFO date: %s\n", b.d.buf); } - bemit(&b, 0, h, 0); + bemit(&b, 0, s->h, 0); break; case T_EXPIRE: { time_t now = time(0); - if (b.t < now) { + if (b.t != KEXP_FOREVER && b.t < now) { if (verb > 1) puts("BAD signature has expired"); f |= f_bogus; @@ -1276,18 +896,18 @@ static int verify(int argc, char *argv[]) timestring(b.t, &b.d); printf("INFO expires: %s\n", b.d.buf); } - bemit(&b, 0, h, 0); + bemit(&b, 0, s->h, 0); } break; case T_FILE: DRESET(&d); - DENSURE(&d, gch->hashsz); - if (fhash(gch, b.d.buf, d.buf)) { + DENSURE(&d, GH_CLASS(s->h)->hashsz); + if (fhash(&fh, b.d.buf, d.buf)) { if (verb > 1) { printf("BAD error reading file `%s': %s\n", b.d.buf, strerror(errno)); } f |= f_bogus; - } else if (b.b.len != gch->hashsz || + } else if (b.b.len != GH_CLASS(s->h)->hashsz || memcmp(d.buf, b.b.buf, b.b.len) != 0) { if (verb > 1) printf("BAD file `%s' has incorrect hash\n", b.d.buf); @@ -1297,21 +917,12 @@ static int verify(int argc, char *argv[]) fhex(stdout, b.b.buf, b.b.len); printf(" %s\n", b.d.buf); } - bemit(&b, 0, h, 0); + bemit(&b, 0, s->h, 0); break; case T_SIGNATURE: - DRESET(&b.d); - DENSURE(&b.d, h->ops->c->hashsz); - b.d.len += h->ops->c->hashsz; - h->ops->done(h, b.d.buf); - e = s->verify(k, b.d.buf, b.d.len, b.b.buf, b.b.len); - if (e != 1) { - if (verb > 1) { - if (e < 0) { - printf("BAD error unpacking key: %s\n", key_strerror(e)); - } else - puts("BAD bad signature"); - } + if (s->ops->doit(s, &b.b)) { + if (verb > 1) + puts("BAD bad signature"); f |= f_bogus; } else if (verb > 2) puts("INFO good signature"); @@ -1331,8 +942,12 @@ static int verify(int argc, char *argv[]) } } done: + if ((f & FHF_JUNK) && fhash_junk(&fh, checkjunk, 0)) + f |= f_bogus; + fhash_free(&fh); bdestroy(&b); dstr_destroy(&d); + freesig(s); key_close(&kf); if (fp != stdin) fclose(fp); @@ -1343,49 +958,96 @@ done: puts("OK signature verified"); } return (f & f_bogus ? EXIT_FAILURE : EXIT_SUCCESS); + +#undef f_bogus +#undef f_bin +#undef f_ok +#undef f_nocheck } /*----- Main code ---------------------------------------------------------*/ -typedef struct cmd { - const char *name; - int (*func)(int /*argc*/, char */*argv*/[]); - const char *help; -} cmd; +#define LISTS(LI) \ + LI("Lists", list, \ + listtab[i].name, listtab[i].name) \ + LI("Signature schemes", sig, \ + sigtab[i].name, sigtab[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[] = { -/* { "manifest", manifest, */ -/* "manifest [-0] [-o output]" }, */ + { "help", cmd_help, "help [COMMAND...]" }, + { "show", cmd_show, "show [ITEM...]" }, { "sign", sign, - "sign [-options]\n\ - [-0v] [-a alg] [-h hash] [-t keytype] [-i keyid]\n\ - [-e expire] [-f file] [-o output]" }, + "sign [-0bpqvC] [-c COMMENT] [-k TAG] [-e EXPIRE]\n\t\ +[-f FILE] [-h FILE] [-o OUTPUT]", + "\ +Options:\n\ +\n\ +-0, --null Read null-terminated filenames from stdin.\n\ +-b, --binary Produce a binary output file.\n\ +-q, --quiet Produce fewer messages while working.\n\ +-v, --verbose Produce more messages while working.\n\ +-p, --progress Show progress on large files.\n\ +-C, --nocheck Don't check the private key.\n\ +-c, --comment=COMMENT Include COMMENT in the output file.\n\ +-f, --file=FILE Read filenames to hash from FILE.\n\ +-h, --hashes=FILE Read precomputed hashes from FILE.\n\ +-o, --output=FILE Write the signed result to FILE.\n\ +-k, --key=TAG Use a key named by TAG.\n\ +-e, --expire=TIME The signature should expire after TIME.\n\ +" }, { "verify", verify, - "verify [-qv] [file]" }, + "verify [-pqvC] [FILE]", "\ +Options:\n\ +\n\ +-q, --quiet Produce fewer messages while working.\n\ +-v, --verbose Produce more messages while working.\n\ +-p, --progress Show progress on large files.\n\ +-C, --nocheck Don't check the public key.\n\ +" }, { 0, 0, 0 } }; -static void version(FILE *fp) +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"); + pquis(fp, "Usage: $ [-k KEYRING] COMMAND [ARGS]\n"); } -static void help(FILE *fp) +void help_global(FILE *fp) { - cmd *c; - version(fp); - fputc('\n', fp); usage(fp); fputs("\n\ Create and verify signatures on lists of files.\n\ -\n", fp); - for (c = cmdtab; c->name; c++) - fprintf(fp, "%s\n", c->help); +\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@ --- * @@ -1402,12 +1064,8 @@ Create and verify signatures on lists of files.\n\ int main(int argc, char *argv[]) { unsigned f = 0; - cmd *c = 0, *cc = 0; - size_t n; - enum { - f_bogus = 1 - }; +#define f_bogus 1u /* --- Initialize the library --- */ @@ -1431,7 +1089,7 @@ int main(int argc, char *argv[]) break; switch (i) { case 'h': - help(stdout); + sc_help(cmdtab, stdout, argv + optind); exit(0); break; case 'v': @@ -1460,21 +1118,9 @@ int main(int argc, char *argv[]) /* --- Dispatch to the correct subcommand handler --- */ - n = strlen(argv[0]); - for (c = cmdtab; c->name; c++) { - if (strncmp(argv[0], c->name, n) == 0) { - if (c->name[n] == 0) { - cc = c; - break; - } else if (cc) - die(EXIT_FAILURE, "ambiguous command name `%s'", argv[0]); - else - cc = c; - } - } - if (!cc) - die(EXIT_FAILURE, "unknown command `%s'", argv[0]); - return (cc->func(argc, argv)); + return (findcmd(cmdtab, argv[0])->cmd(argc, argv)); + +#undef f_bogus } /*----- That's all, folks -------------------------------------------------*/