From 4bedc99bbbc9f88129cee9a084fc58cee8237942 Mon Sep 17 00:00:00 2001 From: mdw Date: Sat, 17 Jun 2000 10:54:29 +0000 Subject: [PATCH] Program to generate and verify signatures on multiple files. --- dsig.c | 1487 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1487 insertions(+) create mode 100644 dsig.c diff --git a/dsig.c b/dsig.c new file mode 100644 index 0000000..9818367 --- /dev/null +++ b/dsig.c @@ -0,0 +1,1487 @@ +/* -*-c-*- + * + * $Id: dsig.c,v 1.1 2000/06/17 10:54:29 mdw Exp $ + * + * Verify signatures on distribuitions of files + * + * (c) 2000 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. + */ + +/*----- Revision history --------------------------------------------------* + * + * $Log: dsig.c,v $ + * Revision 1.1 2000/06/17 10:54:29 mdw + * Program to generate and verify signatures on multiple files. + * + */ + +/*----- Header files ------------------------------------------------------*/ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "dsa.h" +#include "getdate.h" +#include "grand.h" +#include "ghash.h" +#include "key.h" +#include "key-data.h" +#include "md5.h" +#include "noise.h" +#include "rmd160.h" +#include "rsa.h" +#include "sha.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); + 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; + key_packstruct ks[RSA_PRIVFETCHSZ]; + key_packdef *kp; + octet *p; + size_t sz; + mp *x; + int e; + + kp = key_fetchinit(rsa_privfetch, ks, &rp); + if ((e = key_fetch(kp, k)) != 0) { + key_fetchdone(kp); + return (e); + } + sz = mp_octets(rp.n); + if (sz < msz + 11) + die(EXIT_FAILURE, "key too small (help!)"); + DENSURE(d, sz); + p = d->buf + d->len; + memcpy(p + sz - msz, m, msz); + p[0] = 0; + p[1] = 1; + memset(p + 2, 0xff, sz - msz - 3); + p[sz - msz - 1] = 0; + memcpy(p + sz - msz, m, msz); + x = mp_loadb(MP_NEW, p, sz); + x = rsa_decrypt(&rp, x, x, &rand_global); + mp_storeb(x, p, sz); + d->len += sz; + mp_drop(x); + 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; + key_packstruct ks[RSA_PUBFETCHSZ]; + key_packdef *kp; + octet *p; + size_t sz; + mp *x; + mpmont mm; + int ok = 0; + int e; + int i; + + kp = key_fetchinit(rsa_pubfetch, ks, &rp); + if ((e = key_fetch(kp, k)) != 0) { + key_fetchdone(kp); + return (e); + } + x = mp_loadb(MP_NEW, s, ssz); + mpmont_create(&mm, rp.n); + x = mpmont_exp(&mm, x, x, rp.e); + mpmont_destroy(&mm); + sz = mp_octets(rp.n); + p = xmalloc(sz); + mp_storeb(x, p, sz); + mp_drop(x); + key_fetchdone(kp); + if (msz + 11 >= sz || p[0] != 0 || p[1] != 1 || p[sz - msz - 1] != 0) + goto done; + for (i = 2; i < sz - msz - 1; i++) { + if (p[i] != 0xff) + goto done; + } + if (memcmp(m, p + sz - msz, msz) != 0) + goto done; + ok = 1; +done: + free(p); + 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, &sha, &md5, 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); +} + +/*----- Data formatting ---------------------------------------------------*/ + +/* --- Binary data structure --- * + * + * The binary format, which is used for hashing and for the optional binary + * output, consists of a sequence of tagged blocks. The tag describes the + * format and meaining of the following data. + */ + +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 */ + T_DATE, /* Creation date of signature */ + T_EXPIRE, /* Expiry date of signature */ + T_FILE, /* File and corresponding hash */ + T_SIGNATURE, /* Final signature block */ + + /* --- Error messages --- */ + + E_EOF = -1, + E_BIN = -2, + E_TAG = -3, + E_DATE = -4 +}; + +/* --- Name translation table --- */ + +static const char *tagtab[] = { + "ident:", "sigalg:", "hashalg:", "keyid:", + "comment:", "date:", "expires:", "file:", + "signature:", + 0 +}; + +static const char *errtab[] = { + "Off-by-one bug", + "Unexpected end-of-file", + "Binary object too large", + "Unrecognized tag", + "Bad date string" +}; + +/* --- Memory representation of block types --- */ + +typedef struct block { + int tag; /* Type tag */ + dstr d; /* String data */ + dstr b; /* Binary data */ + time_t t; /* Timestamp */ + 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((unsigned char)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 = '\n'; 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); + 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 + * @dstr *d@ = a string to write on + * + * Returns: --- + * + * Use: Writes a textual representation of the timestamp to the + * string. + */ + +static void timestring(time_t t, dstr *d) +{ + if (t == KEXP_FOREVER) + DPUTS(d, "forever"); + else { + struct tm *tm = localtime(&t); + DENSURE(d, 32); + d->len += strftime(d->buf + d->len, 32, "%Y-%m-%d %H:%M:%S %Z", tm); + DPUTZ(d); + } +} + +/* --- @breset@ --- * + * + * Arguments: @block *b@ = block to reset + * + * Returns: --- + * + * Use: Resets a block so that more stuff can be put in it. + */ + +static void breset(block *b) +{ + b->tag = 0; + DRESET(&b->d); + DRESET(&b->b); + b->k = 0; + b->t = KEXP_EXPIRE; +} + +/* --- @binit@ --- * + * + * Arguments: @block *b@ = block to initialize + * + * Returns: --- + * + * Use: Initializes a block as something to read into. + */ + +static void binit(block *b) +{ + dstr_create(&b->d); + dstr_create(&b->b); + breset(b); +} + +/* --- @bdestroy@ --- * + * + * Arguments: @block *b@ = block to destroy + * + * Returns: --- + * + * Use: Destroys a block's contents. + */ + +static void bdestroy(block *b) +{ + dstr_destroy(&b->d); + dstr_destroy(&b->b); +} + +/* --- @bget@ --- * + * + * Arguments: @block *b@ = pointer to block + * @FILE *fp@ = stream to read from + * @unsigned bin@ = binary switch + * + * Returns: Tag of block, or an error tag. + * + * Use: Reads a block from a stream. + */ + +static int bget(block *b, FILE *fp, unsigned bin) +{ + int tag; + + /* --- Read the tag --- */ + + if (bin) + tag = getc(fp); + else { + dstr d = DSTR_INIT; + if (getstring(fp, &d, 0)) + return (E_EOF); + for (tag = 0; tagtab[tag]; tag++) { + if (strcmp(tagtab[tag], d.buf) == 0) + goto done; + } + return (E_TAG); + done:; + } + + /* --- Decide what to do next --- */ + + breset(b); + b->tag = tag; + switch (tag) { + + /* --- Reading of strings --- */ + + case T_IDENT: + case T_COMMENT: + case T_SIGALG: + case T_HASHALG: + if (getstring(fp, &b->d, bin)) + return (E_EOF); + break; + + /* --- Timestamps --- */ + + case T_DATE: + case T_EXPIRE: + if (bin) { + octet buf[8]; + if (fread(buf, sizeof(buf), 1, fp) < 1) + return (E_EOF); + b->t = ((time_t)(((LOAD32(buf + 0) << 16) << 16) & ~MASK32) | + (time_t)LOAD32(buf + 4)); + } else { + if (getstring(fp, &b->d, 0)) + return (E_EOF); + if (strcmp(b->d.buf, "forever") == 0) + b->t = KEXP_FOREVER; + else if ((b->t = get_date(b->d.buf, 0)) == -1) + return (E_DATE); + } + break; + + /* --- Key ids --- */ + + case T_KEYID: + if (bin) { + octet buf[4]; + if (fread(buf, sizeof(buf), 1, fp) < 1) + return (E_EOF); + b->k = LOAD32(buf); + } else { + if (getstring(fp, &b->d, 0)) + return (E_EOF); + b->k = strtoul(b->d.buf, 0, 16); + } + break; + + /* --- Reading of binary data --- */ + + case T_FILE: + case T_SIGNATURE: + if (bin) { + octet buf[2]; + uint32 sz; + if (fread(buf, sizeof(buf), 1, fp) < 1) + return (E_EOF); + sz = LOAD16(buf); + if (sz > 4096) + return (E_BIN); + DENSURE(&b->b, sz); + if (fread(b->b.buf + b->b.len, 1, sz, fp) < sz) + return (E_EOF); + b->b.len += sz; + } else { + base64_ctx b64; + if (getstring(fp, &b->d, 0)) + 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)) + return (E_EOF); + break; + + /* --- Anything else --- */ + + default: + return (E_TAG); + } + + return (tag); +} + +/* --- @blob@ --- * + * + * Arguments: @block *b@ = pointer to block to emit + * @dstr *d@ = output buffer + * + * Returns: --- + * + * Use: Encodes a block in a binary format. + */ + +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); + break; + 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); + d->len += 8; + break; + case T_KEYID: + DENSURE(d, 4); + STORE32(d->buf + d->len, b->k); + d->len += 4; + break; + case T_FILE: + case T_SIGNATURE: + DENSURE(d, 2); + STORE16(d->buf + d->len, b->b.len); + d->len += 2; + DPUTD(d, &b->b); + if (b->tag == T_FILE) { + DPUTD(d, &b->d); + DPUTC(d, 0); + } + break; + } +} + +/* --- @bwrite@ --- * + * + * Arguments: @block *b@ = pointer to block to write + * @FILE *fp@ = stream to write on + * + * Returns: --- + * + * Use: Writes a block on a stream in a textual format. + */ + +static void bwrite(block *b, FILE *fp) +{ + fputs(tagtab[b->tag], 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; + case T_DATE: + case T_EXPIRE: { + dstr d = DSTR_INIT; + timestring(b->t, &d); + putstring(fp, d.buf, 0); + dstr_destroy(&d); + } break; + case T_KEYID: + fprintf(fp, "%08lx", (unsigned long)b->k); + break; + case T_FILE: + case T_SIGNATURE: { + dstr d = DSTR_INIT; + base64_ctx b64; + base64_init(&b64); + b64.maxline = 0; + base64_encode(&b64, b->b.buf, b->b.len, &d); + base64_encode(&b64, 0, 0, &d); + dstr_write(&d, fp); + if (b->tag == T_FILE) { + putc(' ', fp); + putstring(fp, b->d.buf, 0); + } + } break; + } + putc('\n', fp); +} + +/* --- @bemit@ --- * + * + * Arguments: @block *b@ = pointer to block to write + * @FILE *fp@ = file to write on + * @ghash *h@ = pointer to hash function + * @unsigned bin@ = binary/text flag + * + * Returns: --- + * + * Use: Spits out a block properly. + */ + +static void bemit(block *b, FILE *fp, ghash *h, unsigned bin) +{ + if (h || (fp && bin)) { + dstr d = DSTR_INIT; + blob(b, &d); + if (h) + h->ops->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 + * @const void *p@ = pointer to data to be written + * @size_t sz@ = size of the data to write + * + * Returns: --- + * + * Use: Emits a hex dump to a stream. + */ + +static void fhex(FILE *fp, const void *p, size_t sz) +{ + const octet *q = p; + if (!sz) + return; + for (;;) { + fprintf(fp, "%02x", *q++); + 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 + }; + + unsigned f = 0; + const char *kt = 0; + const char *ki = 0; + key_file kf; + key *k; + const sig *s = sigtab; + const gchash *gch = &rmd160; + ghash *h; + time_t exp = KEXP_EXPIRE; + unsigned verb = 0; + const char *ifile = 0; + const char *ofile = 0; + const char *c = 0; + FILE *ifp, *ofp; + dstr d = DSTR_INIT; + block b; + int e; + + for (;;) { + static struct option opts[] = { + { "null", 0, 0, '0' }, + { "binary", 0, 0, 'b' }, + { "verbose", 0, 0, 'v' }, + { "quiet", 0, 0, 'q' }, + { "algorithm", OPTF_ARGREQ, 0, 'a' }, + { "hash", OPTF_ARGREQ, 0, 'h' }, + { "comment", OPTF_ARGREQ, 0, 'c' }, + { "file", OPTF_ARGREQ, 0, 'f' }, + { "output", OPTF_ARGREQ, 0, 'o' }, + { "keytype", OPTF_ARGREQ, 0, 't' }, + { "keyid", OPTF_ARGREQ, 0, 'i' }, + { "key", OPTF_ARGREQ, 0, 'i' }, + { "expire", OPTF_ARGREQ, 0, 'e' }, + { 0, 0, 0, 0 } + }; + int i = mdwopt(argc, argv, "+0vqb a:h:c: f:o: t:i:k:e:", opts, 0, 0, 0); + if (i < 0) + break; + switch (i) { + case '0': + f |= f_raw; + break; + case 'b': + f |= f_bin; + break; + case 'v': + verb++; + 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); + } + break; + case 'c': + c = optarg; + break; + case 'f': + ifile = optarg; + break; + case 'o': + ofile = optarg; + break; + case 't': + kt = optarg; + break; + case 'i': + case 'k': + ki = optarg; + break; + case 'e': + if (strcmp(optarg, "forever") == 0) + exp = KEXP_FOREVER; + else if ((exp = get_date(optarg, 0)) == -1) + die(EXIT_FAILURE, "bad expiry time"); + break; + default: + f |= f_bogus; + break; + } + } + if (optind != argc || (f & f_bogus)) + die(EXIT_FAILURE, "Usage: sign [-options]"); + + /* --- Locate the signing key --- */ + + if (key_open(&kf, keyring, KOPEN_WRITE, keyreport, 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); + } + 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); + } + + /* --- Open files --- */ + + if (!ifile) + ifp = stdin; + else if ((ifp = fopen(ifile, (f & f_raw) ? "rb" : "r")) == 0) { + die(EXIT_FAILURE, "couldn't open input file `%s': %s", + ifile, strerror(errno)); + } + + if (!ofile) + ofp = stdout; + else if ((ofp = fopen(ofile, (f & f_bin) ? "wb" : "w")) == 0) { + die(EXIT_FAILURE, "couldn't open output file `%s': %s", + ofile, strerror(errno)); + } + + /* --- Emit the start of the output --- */ + + 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); + + /* --- Start hashing, and emit the datestamps and things --- */ + + { + time_t now = time(0); + + h = gch->init(); + breset(&b); b.tag = T_DATE; b.t = now; bemit(&b, ofp, 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); + if (c) { + breset(&b); b.tag = T_COMMENT; DPUTS(&b.d, c); + bemit(&b, ofp, h, f & f_bin); + } + + if (!(f & f_bin)) + putc('\n', ofp); + } + + /* --- Now hash the various files --- */ + + for (;;) { + + /* --- Stop on an output error --- */ + + if (ferror(ofp)) { + f |= f_bogus; + break; + } + + /* --- Read the next filename to hash --- */ + + 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); + } + bemit(&b, ofp, h, f & f_bin); + } + } + + /* --- Create the signature --- */ + + 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) { + moan("error creating signature: %s", key_strerror(e)); + f |= f_bogus; + } + if (!(f & f_bogus)) { + bemit(&b, ofp, 0, f & f_bin); + key_used(&kf, k, exp); + } + } + + /* --- Tidy up at the end --- */ + + bdestroy(&b); + if (ifile) + fclose(ifp); + if (ofile) { + if (fclose(ofp)) + f |= f_bogus; + } else { + if (fflush(ofp)) + f |= f_bogus; + } + if ((e = key_close(&kf)) != 0) { + switch (e) { + case KWRITE_FAIL: + die(EXIT_FAILURE, "couldn't write file `%s': %s", + keyring, strerror(errno)); + case KWRITE_BROKEN: + die(EXIT_FAILURE, "keyring file `%s' broken: %s (repair manually)", + keyring, strerror(errno)); + } + } + if (f & f_bogus) + die(EXIT_FAILURE, "error(s) occurred while creating signature"); + return (EXIT_SUCCESS); +} + +/*----- Signature verification --------------------------------------------*/ + +static int verify(int argc, char *argv[]) +{ + enum { + f_bogus = 1, + f_bin = 2, + f_ok = 4 + }; + + unsigned f = 0; + unsigned verb = 1; + key_file kf; + key *k = 0; + sig *s = sigtab; + const gchash *gch = &rmd160; + dstr d = DSTR_INIT; + ghash *h; + FILE *fp; + block b; + int e; + + /* --- Parse the options --- */ + + for (;;) { + static struct option opts[] = { + { "verbose", 0, 0, 'v' }, + { "quiet", 0, 0, 'q' }, + { 0, 0, 0, 0 } + }; + int i = mdwopt(argc, argv, "+vq", opts, 0, 0, 0); + if (i < 0) + break; + switch (i) { + case 'v': + verb++; + break; + case 'q': + if (verb) + verb--; + break; + default: + f |= f_bogus; + break; + } + } + argc -= optind; + argv += optind; + if ((f & f_bogus) || argc > 1) + die(EXIT_FAILURE, "Usage: verify [-qv] [file]"); + + /* --- Open the key file, and start reading the input file --- */ + + if (key_open(&kf, keyring, KOPEN_READ, keyreport, 0)) + die(EXIT_FAILURE, "couldn't open keyring `%s'\n", keyring); + if (argc < 1) + fp = stdin; + else { + if ((fp = fopen(argv[0], "rb")) == 0) { + die(EXIT_FAILURE, "couldn't open file `%s': %s\n", + argv[0], strerror(errno)); + } + if (getc(fp) == 0) { + ungetc(0, fp); + f |= f_bin; + } else { + fclose(fp); + if ((fp = fopen(argv[0], "r")) == 0) { + die(EXIT_FAILURE, "couldn't open file `%s': %s\n", + argv[0], strerror(errno)); + } + } + } + + /* --- Read the introductory matter --- */ + + binit(&b); + for (;;) { + breset(&b); + e = bget(&b, fp, f & f_bin); + if (e < 0) + die(EXIT_FAILURE, "error reading packet: %s\n", errtab[-e]); + if (e >= T_BEGIN) + break; + switch (e) { + case T_IDENT: + 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) + printf("FAIL key %08lx not found\n", (unsigned long)b.k); + exit(EXIT_FAILURE); + } + if (verb > 2) { + DRESET(&b.d); + key_fulltag(k, &b.d); + printf("INFO key: %s\n", b.d.buf); + } + break; + default: + die(EXIT_FAILURE, "(internal) unknown packet type\n"); + break; + } + } + + /* --- 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); + } + for (;;) { + switch (e) { + case T_COMMENT: + if (verb > 1) + printf("INFO comment: `%s'\n", b.d.buf); + bemit(&b, 0, h, 0); + break; + case T_DATE: + if (verb > 2) { + DRESET(&b.d); + timestring(b.t, &b.d); + printf("INFO date: %s\n", b.d.buf); + } + bemit(&b, 0, h, 0); + break; + case T_EXPIRE: { + time_t now = time(0); + if (b.t < now) { + if (verb > 1) + puts("BAD signature has expired"); + f |= f_bogus; + } + if (verb > 2) { + DRESET(&b.d); + timestring(b.t, &b.d); + printf("INFO expires: %s\n", b.d.buf); + } + bemit(&b, 0, h, 0); + } break; + case T_FILE: + DRESET(&d); + DENSURE(&d, gch->hashsz); + if (fhash(gch, 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 || + memcmp(d.buf, b.b.buf, b.b.len) != 0) { + if (verb > 1) + printf("BAD file `%s' has incorrect hash\n", b.d.buf); + f |= f_bogus; + } else if (verb > 3) { + fputs("INFO hash: ", stdout); + fhex(stdout, b.b.buf, b.b.len); + printf(" %s\n", b.d.buf); + } + bemit(&b, 0, 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"); + } + f |= f_bogus; + } else if (verb > 2) + puts("INFO good signature"); + goto done; + default: + if (verb) + printf("FAIL invalid packet type %i\n", e); + exit(EXIT_FAILURE); + break; + } + breset(&b); + e = bget(&b, fp, f & f_bin); + if (e < 0) { + if (verb) + printf("FAIL error reading packet: %s\n", errtab[-e]); + exit(EXIT_FAILURE); + } + } +done: + bdestroy(&b); + dstr_destroy(&d); + key_close(&kf); + if (fp != stdin) + fclose(fp); + if (verb) { + if (f & f_bogus) + puts("FAIL signature invalid"); + else + puts("OK signature verified"); + } + return (f & f_bogus ? EXIT_FAILURE : EXIT_SUCCESS); +} + +/*----- Main code ---------------------------------------------------------*/ + +typedef struct cmd { + const char *name; + int (*func)(int /*argc*/, char */*argv*/[]); + const char *help; +} cmd; + +static cmd cmdtab[] = { +/* { "manifest", manifest, */ +/* "manifest [-0] [-o output]" }, */ + { "sign", sign, + "sign [-options]\n\ + [-0v] [-a alg] [-h hash] [-t keytype] [-i keyid]\n\ + [-e expire] [-f file] [-o output]" }, + { "verify", verify, + "verify [-qv] [file]" }, + { 0, 0, 0 } +}; + +static void version(FILE *fp) +{ + pquis(fp, "$, Catacomb version " VERSION "\n"); +} + +static void usage(FILE *fp) +{ + pquis(fp, "Usage: $ [-k keyring] command [args]\n"); +} + +static void help(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); +} + +/* --- @main@ --- * + * + * Arguments: @int argc@ = number of command line arguments + * @char *argv[]@ = vector of command line arguments + * + * Returns: Zero if successful, nonzero otherwise. + * + * Use: Signs or verifies signatures on lists of files. Useful for + * ensuring that a distribution is unmolested. + */ + +int main(int argc, char *argv[]) +{ + unsigned f = 0; + cmd *c = 0, *cc = 0; + size_t n; + + enum { + f_bogus = 1 + }; + + /* --- Initialize the library --- */ + + ego(argv[0]); + sub_init(); + rand_noisesrc(RAND_GLOBAL, &noise_source); + rand_seed(RAND_GLOBAL, 160); + + /* --- 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': + help(stdout); + 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 --- */ + + 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)); +} + +/*----- That's all, folks -------------------------------------------------*/ -- 2.11.0