X-Git-Url: https://git.distorted.org.uk/u/mdw/catacomb/blobdiff_plain/c65df27983057ec76ed0e72bb370f9a5ae7dad28..097fb6f2f97575ce17738b4afb3216e9492de2b4:/dsig.c diff --git a/dsig.c b/dsig.c index c45d204..a7fa5a9 100644 --- a/dsig.c +++ b/dsig.c @@ -7,7 +7,7 @@ * (c) 2000 Straylight/Edgeware */ -/*----- Licensing notice --------------------------------------------------* +/*----- Licensing notice --------------------------------------------------* * * This file is part of Catacomb. * @@ -15,12 +15,12 @@ * 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, @@ -29,6 +29,8 @@ /*----- Header files ------------------------------------------------------*/ +#define _FILE_OFFSET_BITS 64 + #include "config.h" #include @@ -108,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 @@ -366,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) @@ -386,7 +223,7 @@ static int bget(block *b, FILE *fp, unsigned bin) case T_IDENT: case T_COMMENT: - if (getstring(fp, &b->d, bin)) + if (getstring(fp, &b->d, GSF_FILE | (bin ? GSF_RAW : 0))) return (E_EOF); break; @@ -401,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; @@ -419,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); } @@ -443,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; @@ -585,61 +423,13 @@ static void bemit(block *b, FILE *fp, ghash *h, unsigned bin) 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 - * @void *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, void *b) -{ - FILE *fp = fopen(file, "rb"); - ghash *h = GH_INIT(c); - char buf[4096]; - size_t sz; - int rc = 0; - - if (!fp) - return (-1); - while ((sz = fread(buf, 1, sizeof(buf), fp)) > 0) - GH_HASH(h, buf, sz); - if (ferror(fp)) - rc = -1; - GH_DONE(h, b); - GH_DESTROY(h); - fclose(fp); - return (rc); -} - /* --- @fhex@ --- * * * Arguments: @FILE *fp@ = file to write on @@ -662,51 +452,57 @@ static void fhex(FILE *fp, const void *p, size_t sz) if (!sz) break; } -} +} /*----- Signature generation ----------------------------------------------*/ static int sign(int argc, char *argv[]) { -#define f_raw 1u +#define f_bogus 1u #define f_bin 2u -#define f_bogus 4u +#define f_nocheck 4u unsigned f = 0; const char *ki = "dsig"; key_file kf; key *k; 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' }, { "comment", OPTF_ARGREQ, 0, 'c' }, { "file", OPTF_ARGREQ, 0, 'f' }, + { "hashes", OPTF_ARGREQ, 0, 'h' }, { "output", OPTF_ARGREQ, 0, 'o' }, { "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" "c:" "f:o:" "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; @@ -714,16 +510,25 @@ 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 'C': + f |= f_nocheck; + break; case 'c': c = optarg; break; case 'f': ifile = optarg; break; + case 'h': + hfile = optarg; + break; case 'o': ofile = optarg; break; @@ -743,10 +548,12 @@ static int sign(int argc, char *argv[]) } if (optind != argc || (f & f_bogus)) 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 ((k = key_bytag(&kf, ki)) == 0) die(EXIT_FAILURE, "couldn't find key `%s'", ki); @@ -759,19 +566,20 @@ static int sign(int argc, char *argv[]) /* --- Check the key --- */ - if ((err = s->ops->check(s)) != 0) + 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", @@ -783,7 +591,7 @@ 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_KEYID; b.k = k->id; bemit(&b, ofp, 0, f & f_bin); @@ -807,32 +615,72 @@ static int sign(int argc, char *argv[]) /* --- Now hash the various files --- */ - for (;;) { + 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; - /* --- Stop on an output error --- */ - - 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, GH_CLASS(s->h)->hashsz); - if (fhash(GH_CLASS(s->h), 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); + if (ferror(ofp)) { + f |= f_bogus; + break; } - bemit(&b, ofp, s->h, f & f_bin); + + /* --- 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); + } + fhash_free(&fh); } } @@ -878,18 +726,26 @@ static int sign(int argc, char *argv[]) die(EXIT_FAILURE, "error(s) occurred while creating signature"); return (EXIT_SUCCESS); -#undef f_raw #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[]) { #define f_bogus 1u #define f_bin 2u #define f_ok 4u +#define f_nocheck 8u unsigned f = 0; unsigned verb = 1; @@ -898,6 +754,7 @@ static int verify(int argc, char *argv[]) sig *s; dstr d = DSTR_INIT; const char *err; + fhashstate fh; FILE *fp; block b; int e; @@ -907,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; @@ -929,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; @@ -996,9 +865,10 @@ static int verify(int argc, char *argv[]) } s = getsig(k, "dsig", 0); - if (verb && (err = s->ops->check(s)) != 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: @@ -1031,7 +901,7 @@ static int verify(int argc, char *argv[]) case T_FILE: DRESET(&d); DENSURE(&d, GH_CLASS(s->h)->hashsz); - if (fhash(GH_CLASS(s->h), b.d.buf, d.buf)) { + if (fhash(&fh, b.d.buf, d.buf)) { if (verb > 1) { printf("BAD error reading file `%s': %s\n", b.d.buf, strerror(errno)); @@ -1072,6 +942,9 @@ 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); @@ -1089,6 +962,7 @@ done: #undef f_bogus #undef f_bin #undef f_ok +#undef f_nocheck } /*----- Main code ---------------------------------------------------------*/ @@ -1114,8 +988,8 @@ static cmd cmdtab[] = { { "help", cmd_help, "help [COMMAND...]" }, { "show", cmd_show, "show [ITEM...]" }, { "sign", sign, - "sign [-0bqv] [-c COMMENT] [-k TAG] [-e EXPIRE]\n\t\ -[-f FILE] [-o OUTPUT]", + "sign [-0bpqvC] [-c COMMENT] [-k TAG] [-e EXPIRE]\n\t\ +[-f FILE] [-h FILE] [-o OUTPUT]", "\ Options:\n\ \n\ @@ -1123,18 +997,23 @@ Options:\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 } };