New tool for signing and verifying messages.
authormdw <mdw>
Fri, 18 Mar 2005 00:28:07 +0000 (00:28 +0000)
committermdw <mdw>
Fri, 18 Mar 2005 00:28:07 +0000 (00:28 +0000)
Makefile.m4
catcrypt.1
catcrypt.c
catsign.1 [new file with mode: 0644]
catsign.c [new file with mode: 0644]
cc-enc.c
cc.h
dsig.1
hashsum.1

index 80e697d..df761d2 100644 (file)
@@ -253,7 +253,8 @@ gfx-sqr.lo: gfx-sqr-tab.h
 ## --- Utility programs ---
 
 bin_PROGRAMS = \
-       dsig key pixie cookie rspit factorial hashsum mkphrase catcrypt
+       dsig key pixie cookie rspit factorial hashsum mkphrase \
+       catcrypt catsign
 noinst_LIBRARIES = libcatcrypt.a
 bin_SCRIPTS = catacomb-config xpixie
 noinst_PROGRAMS = \
@@ -273,6 +274,7 @@ patsubst(MP_SOURCES, `\.c\>', `.lo') dsig.o keyutil.o rspit.o \
 dsig_SOURCES = dsig.c
 cookie_SOURCES = cookie.c
 catcrypt_SOURCES = catcrypt.c
+catsign_SOURCES = catsign.c
 key_SOURCES = keyutil.c
 hashsum_SOURCES = hashsum.c
 rspit_SOURCES = rspit.c
@@ -326,7 +328,9 @@ changequote(`, ')
 
 ## --- Documentation ---
 
-man_MANS = key.1 dsig.1 cookie.1 catcrypt.1 hashsum.1 keyring.5 pixie.1
+man_MANS = \
+       key.1 dsig.1 cookie.1 catcrypt.1 catsign.1 hashsum.1 \
+       keyring.5 pixie.1
 
 ## --- Other handy definitions ---
 
index e38f441..aa410b8 100644 (file)
@@ -70,7 +70,7 @@ is one of:
 .IR output ]
 .RI [ file ]
 .br
-.B encode
+.B decode
 .RB [ \-f
 .IR format ]
 .RB [ \-b
@@ -118,7 +118,10 @@ for more details about keyring files.
 Algorithms to be used with a particular key are described by attributes
 on the key, or its type.  The
 .B catcrypt
-command deals with both signing and key-encapsulation keys.
+command deals with both signing and key-encapsulation keys.  (Note that
+.B catcrypt
+uses signing keys in the same way as 
+.BR catsign (1).)
 .SS "Key-encapsulation keys"
 (Key encapsulation is a means of transmitting a short, known, random
 secret to a recipient.  It differs from encryption in technical ways
@@ -475,12 +478,16 @@ filename argument is given, this file is read instead.
 The following options are recognized.
 .TP
 .B "\-a, \-\-armour"
-Read ASCII-armoured output.  This is equivalent to specifying
+Read ASCII-armoured input.  This is equivalent to specifying
 .BR "\-f pem" .
 The variant spelling
 .B "\-\-armor"
 is also accepted.
 .TP
+.B "\-b, \-\-buffer"
+Buffer plaintext data until we're sure we've got it all.  This is forced
+on if output is to stdout, but is always available as an option.
+.TP
 .BI "\-f, \-\-format " format
 Read input encoded according to
 .IR format .
@@ -504,7 +511,7 @@ Output is written to standard output in a machine-readable format.
 Major problems cause the program to write a diagnostic to standard error
 and exit nonzero as usual.  The quantity of output varies depending on
 the verbosity level and whether the plaintext is also being written to
-standard output.  Output lines begin with a keyword.:
+standard output.  Output lines begin with a keyword:
 .TP
 .BI "FAIL " reason
 An error prevented decryption.  The program will exit nonzero.
@@ -520,10 +527,7 @@ being sent somewhere other than standard output.
 .B "DATA"
 The plaintext follows, starting just after the next newline character or
 sequence.  This is only produced if main output is being sent to
-standard output.  If anything goes wrong, a
-.B FAIL
-message is printed, preceded and followed by a newline, and the program
-exits nonzero.
+standard output.
 .TP
 .BI "INFO " note
 Any other information.
@@ -535,14 +539,12 @@ No output.  Watch the exit status.
 All messages.
 .PP
 .B Warning!
-All output written has been checked for authenticity.  However, since
-the input is chunked, a chunk will be checked and written before the
-authenticity of following chunks is established.  Don't rely on the
-output being complete until
+All output written has been checked for authenticity.  However, output
+can fail madway through for many reasons, and the resulting message may
+therefore be truncated.  Don't rely on the output being complete until 
+.B OK is printed or
 .B catcrypt decrypt
-prints
-.B OK
-and/or exits successfully.
+exits successfully.
 .SS "encode"
 The
 .B encode
@@ -674,6 +676,7 @@ truncated.
 That's it.  Nothing terribly controversial, really.
 .SH "SEE ALSO"
 .BR key (1),
+.BR catsign (1),
 .BR dsig (1),
 .BR hashsum (1),
 .BR keyring (5).
index c57fe70..08fbb53 100644 (file)
@@ -225,16 +225,16 @@ static int encrypt(int argc, char *argv[])
 
   dstr_reset(&d);
   key_fulltag(k, &d);
-  e = initenc(eo, ofp, "CATCRYPT ENCRYPTED MESSAGE", 1);
+  e = initenc(eo, ofp, "CATCRYPT ENCRYPTED MESSAGE");
   km = getkem(k, "cckem", 0);
   if ((err = km->ops->check(km)) != 0)
-    moan("key `%s' fails check: %s", d.buf, err);
+    moan("key %s fails check: %s", d.buf, err);
   if (sk) {
     dstr_reset(&d);
     key_fulltag(sk, &d);
     s = getsig(sk, "ccsig", 1);
     if ((err = s->ops->check(s)) != 0)
-      moan("key `%s' fails check: %s", d.buf, err);
+      moan("key %s fails check: %s", d.buf, err);
   }
 
   /* --- Build the header chunk --- */
@@ -327,10 +327,11 @@ static int encrypt(int argc, char *argv[])
 static int decrypt(int argc, char *argv[])
 {
   const char *of = 0;
-  FILE *ofp = 0;
+  FILE *ofp = 0, *rfp = 0;
   FILE *fp = 0;
   const char *ef = "binary";
   int i;
+  size_t n;
   dstr d = DSTR_INIT;
   buf b;
   key_file kf;
@@ -352,11 +353,13 @@ static int decrypt(int argc, char *argv[])
   enc *e;
 
 #define f_bogus 1u
+#define f_buffer 2u
 
   for (;;) {
     static const struct option opt[] = {
       { "armour",      0,              0,      'a' },
       { "armor",       0,              0,      'a' },
+      { "buffer",      0,              0,      'b' },
       { "verbose",     0,              0,      'v' },
       { "quiet",       0,              0,      'q' },
       { "format",      OPTF_ARGREQ,    0,      'f' },
@@ -367,6 +370,7 @@ static int decrypt(int argc, char *argv[])
     if (i < 0) break;
     switch (i) {
       case 'a': ef = "pem"; break;
+      case 'b': f |= f_buffer; break;
       case 'v': verb++; break;
       case 'q': if (verb) verb--; break;
       case 'f': ef = optarg; break;
@@ -394,7 +398,7 @@ static int decrypt(int argc, char *argv[])
   if (key_open(&kf, keyring, KOPEN_READ, keyreport, 0))
     die(EXIT_FAILURE, "can't open keyring `%s'", keyring);
 
-  e = initenc(eo, fp, "CATCRYPT ENCRYPTED MESSAGE", 0);
+  e = initdec(eo, fp, checkbdry, "CATCRYPT ENCRYPTED MESSAGE");
 
   /* --- Read the header chunk --- */
 
@@ -454,20 +458,25 @@ static int decrypt(int argc, char *argv[])
     if (verb) {
       dstr_reset(&d);
       key_fulltag(sk, &d);
-      printf("INFO good signature from %s\n", d.buf);
+      printf("INFO good-signature %s\n", d.buf);
     }
   } else if (verb)
-    printf("INFO no signature packet\n");
+    printf("INFO no-signature\n");
 
   /* --- Now decrypt the main body --- */
 
   if (!of || strcmp(of, "-") == 0) {
     ofp = stdout;
-    if (verb) printf("DATA\n");
-  } else if ((ofp = fopen(of, "wb")) == 0) {
-    die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
-       ofp, strerror(errno));
+    f |= f_buffer;
   }
+  if (!(f & f_buffer)) {
+    if ((ofp = fopen(of, "wb")) == 0) {
+      die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
+         ofp, strerror(errno));
+    }
+    rfp = ofp;
+  } else if ((rfp = tmpfile()) == 0)
+    die(EXIT_FAILURE, "couldn't create temporary file: %s", strerror(errno));
 
   seq = 0;
   dstr_ensure(&d, GC_CLASS(c)->blksz);
@@ -483,32 +492,48 @@ static int decrypt(int argc, char *argv[])
     seq++;
     chunk_read(e, &d, &b);
     if ((tag = buf_get(&b, GM_CLASS(m)->hashsz)) == 0) {
-      if (verb) printf("%sFAIL bad ciphertext chunk: no tag\n",
-                      ofp == stdout ? "\n" : "");
+      if (verb) printf("FAIL bad ciphertext chunk: no tag\n");
       exit(EXIT_FAILURE);
     }
     GH_HASH(h, BCUR(&b), BLEFT(&b));
     if (memcmp(tag, GH_DONE(h, 0), GM_CLASS(m)->hashsz) != 0) {
       if (verb)
-       printf("%sFAIL bad ciphertext chunk: authentication failure\n",
-              ofp == stdout ? "\n" : "");
+       printf("FAIL bad ciphertext chunk: authentication failure\n");
       exit(EXIT_FAILURE);
     }
     if (!BLEFT(&b))
       break;
     GC_DECRYPT(c, BCUR(&b), BCUR(&b), BLEFT(&b));
-    if (fwrite(BCUR(&b), 1, BLEFT(&b), ofp) != BLEFT(&b)) {
-      if (verb) printf("%sFAIL error writing output: %s\n",
-                      ofp == stdout ? "\n" : "", strerror(errno));
+    if (fwrite(BCUR(&b), 1, BLEFT(&b), rfp) != BLEFT(&b)) {
+      if (verb) printf("FAIL error writing output: %s\n", strerror(errno));
       exit(EXIT_FAILURE);
     }
   }
 
-  if (fflush(ofp) || ferror(ofp)) {
-    if (verb) printf("%sFAIL error writing output: %s\n",
-                    ofp == stdout ? "\n" : "", strerror(errno));
+  if (fflush(rfp) || ferror(rfp)) {
+    if (verb) printf("FAIL error writing output: %s\n", strerror(errno));
     exit(EXIT_FAILURE);
   }
+  if (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);
+    dstr_reset(&d);
+    dstr_ensure(&d, 65536);
+    if (ofp == stdout) printf("DATA\n");
+    for (;;) {
+      n = fread(d.buf, 1, d.sz, rfp);
+      if (!n) break;
+      if (fwrite(d.buf, 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));    
 
   e->ops->decdone(e);
   if (verb && ofp != stdout)
@@ -524,148 +549,7 @@ static int decrypt(int argc, char *argv[])
   return (0);
 
 #undef f_bogus
-}
-
-/*----- Test code ---------------------------------------------------------*/
-
-static int encode(int argc, char *argv[])
-{
-  const char *of = 0;
-  FILE *ofp = 0;
-  FILE *fp = 0;
-  const char *ef = "binary";
-  const char *bd = "MESSAGE";
-  int i;
-  size_t n;
-  char buf[4096];
-  unsigned f = 0;
-  const encops *eo;
-  enc *e;
-
-#define f_bogus 1u
-
-  for (;;) {
-    static const struct option opt[] = {
-      { "format",      OPTF_ARGREQ,    0,      'f' },
-      { "boundary",    OPTF_ARGREQ,    0,      'b' },
-      { "output",      OPTF_ARGREQ,    0,      'o' },
-      { 0,             0,              0,      0 }
-    };
-    i = mdwopt(argc, argv, "f:b:o:", opt, 0, 0, 0);
-    if (i < 0) break;
-    switch (i) {
-      case 'f': ef = optarg; break;
-      case 'b': bd = optarg; break;
-      case 'o': of = optarg; break;
-      default: f |= f_bogus; break;
-    }
-  }
-  if (argc - optind > 1 || (f & f_bogus))
-    die(EXIT_FAILURE, "Usage: encode [-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], "rb")) == 0) {
-    die(EXIT_FAILURE, "couldn't open file `%s': %s",
-       argv[optind], strerror(errno));
-  } else
-    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));
-  }
-
-  e = initenc(eo, ofp, bd, 1);
-
-  do {
-    n = fread(buf, 1, sizeof(buf), fp);
-    if (e->ops->write(e, buf, n))
-      die(EXIT_FAILURE, "error writing output: %s", strerror(errno));
-  } while (n == sizeof(buf));
-  e->ops->encdone(e);
-  freeenc(e);
-  return (0);
-
-#undef f_bogus
-}
-
-static int decode(int argc, char *argv[])
-{
-  const char *of = 0;
-  FILE *ofp = 0;
-  FILE *fp = 0;
-  const char *ef = "binary";
-  const char *bd = 0;
-  int i;
-  char buf[4096];
-  unsigned f = 0;
-  const encops *eo;
-  enc *e;
-
-#define f_bogus 1u
-
-  for (;;) {
-    static const struct option opt[] = {
-      { "format",      OPTF_ARGREQ,    0,      'f' },
-      { "boundary",    OPTF_ARGREQ,    0,      'b' },
-      { "output",      OPTF_ARGREQ,    0,      'o' },
-      { 0,             0,              0,      0 }
-    };
-    i = mdwopt(argc, argv, "f:b:o:", opt, 0, 0, 0);
-    if (i < 0) break;
-    switch (i) {
-      case 'f': ef = optarg; break;
-      case 'b': bd = optarg; break;
-      case 'o': of = optarg; break;
-      default: f |= f_bogus; break;
-    }
-  }
-  if (argc - optind > 1 || (f & f_bogus))
-    die(EXIT_FAILURE, "Usage: decode [-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 (!of || strcmp(of, "-") == 0)
-    ofp = stdout;
-  else if ((ofp = fopen(of, "wb")) == 0) {
-    die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
-       ofp, strerror(errno));
-  }
-
-  e = initenc(eo, fp, bd, 0);
-
-  do {
-    if ((i = e->ops->read(e, buf, sizeof(buf))) < 0)
-      die(EXIT_FAILURE, "error reading input: %s", strerror(errno));
-    if (fwrite(buf, 1, i, ofp) < i)
-      die(EXIT_FAILURE, "error writing output: %s", strerror(errno));
-  } while (i == sizeof(buf));
-  e->ops->decdone(e);
-  freeenc(e);
-  return (0);
-
-#undef f_bogus
+#undef f_buffer
 }
 
 /*----- Main code ---------------------------------------------------------*/
@@ -698,24 +582,8 @@ static int cmd_help(int, char **);
 static cmd cmdtab[] = {
   { "help", cmd_help, "help [COMMAND...]" },
   { "show", cmd_show, "show [ITEM...]" },
-  { "encode", encode,
-    "encode [-f FORMAT] [-b LABEL] [-o OUTPUT] [FILE]",
-    "\
-Options:\n\
-\n\
--f, --format=FORMAT    Encode to FORMAT.\n\
--b, --boundary=LABEL   PEM boundary is LABEL.\n\
--o, --output=FILE      Write output to FILE.\n\
-" },
-  { "decode", decode,
-    "decode [-f FORMAT] [-b LABEL] [-o OUTPUT] [FILE]",
-    "\
-Options:\n\
-\n\
--f, --format=FORMAT    Decode from FORMAT.\n\
--b, --boundary=LABEL   PEM boundary is LABEL.\n\
--o, --output=FILE      Write output to FILE.\n\
-" },
+  CMD_ENCODE,
+  CMD_DECODE,
   { "encrypt", encrypt,
     "encrypt [-a] [-k TAG] [-s TAG] [-f FORMAT]\n\t\
 [-o OUTPUT] [FILE]", "\
@@ -728,15 +596,16 @@ Options:\n\
 -o, --output=FILE      Write output to FILE.\n\
 " },
   { "decrypt", decrypt,
-    "decrypt [-aqv] [-f FORMAT] [-o OUTPUT] [FILE]", "\
+    "decrypt [-abqv] [-f FORMAT] [-o OUTPUT] [FILE]", "\
 Options:\n\
 \n\
 -a, --armour           Same as `-f pem'.\n\
--v, --verbose          Produce more verbose messages.\n\
--q, --quiet            Produce fewer messages.\n\
+-b, --buffer           Buffer output until we're sure we have it all.\n\
 -f, --format=FORMAT    Decode as FORMAT.\n\
 -o, --output=FILE      Write output to FILE.\n\
-" },
+-q, --quiet            Produce fewer messages.\n\
+-v, --verbose          Produce more verbose messages.\n\
+" }, /* ' emacs is confused */
   { 0, 0, 0 }
 };
 
diff --git a/catsign.1 b/catsign.1
new file mode 100644 (file)
index 0000000..ab07b2a
--- /dev/null
+++ b/catsign.1
@@ -0,0 +1,660 @@
+.\" -*-nroff-*-
+.de VS
+.sp 1
+.RS
+.nf
+.ft B
+..
+.de VE
+.ft R
+.fi
+.RE
+.sp 1
+..
+.ie t \{\
+.  if \n(.g \{\
+.    fam P
+.  \}
+.\}
+.de hP
+.IP
+.ft B
+\h'-\w'\\$1\ 'u'\\$1\ \c
+.ft P
+..
+.ie t .ds o \(bu
+.el .ds o o
+.TH catsign 1 "17 March 2005" "Straylight/Edgeware" "Catacomb cryptographic library"
+.SH NAME
+catsign \- sign and verify messages
+.SH SYNOPSIS
+.B catsign
+.RB [ \-k
+.IR keyring ]
+.I command
+.PP
+where
+.I command
+is one of:
+.PP
+.B help
+.RI [ command ...]
+.br
+.B show
+.RI [ item ...]
+.br
+.B sign
+.RB [ \-adt ]
+.RB [ \-k
+.IR tag ]
+.RB [ \-f
+.IR format ]
+.RB [ \-o
+.IR output ]
+.RI [ file ]
+.br
+.B verify
+.RB [ \-aquv ]
+.RB [ \-k
+.IR tag ]
+.RB [ \-f
+.IR format ]
+.br
+       
+.RB [ \-o
+.IR output ]
+.RI [ file
+.RI [ message ]]
+.br
+.B info
+.RB [ \-a ]
+.RB [ \-f
+.IR format ]
+.RI [ file ]
+.br
+.B format
+.RB [ \-auABDET ]
+.RB [ \-f
+.IR format ]
+.RB [ \-F
+.IR format ]
+.br
+       
+.RB [ \-m
+.IR file ]
+.RB [ \-o
+.IR output ]
+.RI [ file
+.RI [ message ]]
+.br
+.B encode
+.RB [ \-f
+.IR format ]
+.RB [ \-b
+.IR boundary ]
+.RB [ \-o
+.IR output ]
+.RI [ file ]
+.br
+.B decode
+.RB [ \-f
+.IR format ]
+.RB [ \-b
+.IR boundary ]
+.RB [ \-o
+.IR output ]
+.RI [ file ]
+.SH "DESCRIPTION"
+The
+.B catsign
+command signs and verifies messages.  It also works as a simple PEM
+encoder and decoder.  It provides a number of subcommands, by which the
+various operations may be carried out.
+.SS "Global options"
+Before the command name,
+.I "global options"
+may be given.  The following global options are supported:
+.TP
+.BR "\-h, \-\-help " [ \fIcommand ...]
+Writes a brief summary of
+.BR catsign 's
+various options to standard output, and returns a successful exit
+status.  With command names, gives help on those commands.
+.TP
+.B "\-v, \-\-version"
+Writes the program's version number to standard output, and returns a
+successful exit status.
+.TP
+.B "\-u, \-\-usage"
+Writes a very terse command line summary to standard output, and returns
+a successful exit status.
+.TP
+.BI "\-k, \-\-keyring " file
+Names the keyring file which
+.B key
+is to process.  The default keyring, used if this option doesn't specify
+one, is the file named
+.B keyring
+in the current directory.  See
+.BR key (1)
+and
+.BR keyring (5)
+for more details about keyring files.
+.SH "KEY SETUP"
+Algorithms to be used with a particular key are described by attributes
+on the key, or its type.  The
+.B catsign
+command deals with signing keys.  (Note that
+.B catsign
+uses signing keys in the same way as 
+.BR catcrypt (1).)
+.PP
+A
+.I sigalgspec
+has the form
+.IR sig \c
+.RB [ / \c
+.IR hash ].
+If a
+.B sig
+attribute is present on the key, then it must have this form; otherwise,
+the key's type must have the form
+.BI ccsig- \c
+.IR sigalgspec .
+Algorithm selections are taken from appropriately-named attributes, or,
+failing that, from the
+.IR sigalgspec .
+.PP
+The signature algorithm is chosen according to the setting of
+.I sig
+as follows.  Run
+.B catsign show sig
+for a list of supported signature algorithms.
+.TP
+.B rsapkcs1
+This is almost the same as the RSASSA-PKCS1-v1_5 algorithm described in
+RFC3447; the difference is that the hash is left bare rather than being
+wrapped in a DER-encoded 
+.B DigestInfo
+structure.  This doesn't affect security since the key can only be used
+with the one hash function anyway, and dropping the DER wrapping permits
+rapid adoption of new hash functions.  Regardless, use of this algorithm
+is not recommended, since the padding method has been shown vulnerable
+to attack.  Use the
+.B rsa
+algorithm of the
+.B key add
+command (see
+.BR key (1))
+to generate the key.
+.TP
+.B rsapss
+This is the RSASSA-PSS algorithm described in RFC3447.  It is the
+preferred RSA-based signature scheme.  Use the
+.B rsa
+algorithm of the
+.B key add
+command (see
+.BR key (1))
+to generate the key.
+.TP
+.B dsa
+This is the DSA algorithm described in FIPS180-1 and FIPS180-2.    Use the
+.B dsa
+algorithm of the
+.B key add
+command (see
+.BR key (1))
+to generate the key.
+.TP
+.B ecdsa
+This is the ECDSA algorithm described in ANSI X9.62 and FIPS180-2.  Use
+the
+.B ec
+algorithm of the
+.B key add
+command (see
+.BR key (1))
+to generate the key.
+.TP
+.B kcdsa
+This is the revised KCDSA (Korean Certificate-based Digital Signature
+Algorithm) described in
+.I The Revised Version of KCDSA
+.RB ( http://dasan.sejong.ac.kr/~chlim/pub/kcdsa1.ps ).
+Use the
+.B dh
+algorithm of the
+.B key add
+command with the
+.B \-LS
+options (see
+.BR key (1))
+to generate the key.
+.TP
+.B eckcdsa
+This is an unofficial elliptic-curve analogue of the KCDSA algorithm.
+Use the
+.B ec
+algorithm of the
+.B key add
+command (see
+.BR key (1))
+to generate the key.
+.PP
+As well as the signature algorithm itself, a hash function is used.
+This is taken from the
+.B hash
+attribute on the key, or, failing that, from the
+.I hash
+specified in the
+.IR sigalgspec ,
+or, if that is absent, determined by the signature algorithm as follows.
+.hP \*o
+For
+.BR rsapkcs1 ,
+.BR rsapss ,
+.BR dsa ,
+and
+.BR ecdsa ,
+the default hash function is
+.BR sha .
+.hP \*o
+For
+.BR kcdsa 
+and
+.BR eckcdsa ,
+the default hash function is
+.BR has160 .
+.PP
+Run
+.B catsign show hash
+for a list of supported hash functions.
+.SH "ENCODINGS"
+Two encodings for the ciphertext are supported.
+.TP
+.B binary
+The raw format, which has the benefit of being smaller, but needs to be
+attached to mail messages and generally handled with care.
+.TP
+.B pem
+PEM-encapsulated Base-64 encoded text.  This format can be included
+directly in email and picked out again automatically; but there is a
+4-to-3 data expansion as a result.
+.SH "SIGNATURE FORMATS"
+There are two basic signature formats understood by
+.BR catsign .
+.hP \*o
+Embedded signatures include (embed) the message they sign; hence they're
+complete in and of themselves.  The
+.B catsign
+program extracts the message during signature verification.
+.hP \*o
+Detached signatures are separate from the messages they sign, and both
+the original file and the signature are required for a successful
+verification.
+.PP
+Another important distinction is whether the message data is considered
+to be plain text or raw binary data.
+.hP \*o
+When dealing with plain text,
+.B catsign
+allows a limited quantity of leeway in the messages it processes.  It
+ignores trailing whitespace on a line, including stray carriage-returns,
+which may appear if Windows boxes have had their way with the data.  It
+also appends a final newline if there wasn't one before.  In embedded
+signatures, the text is left unencoded, so that the message is readable.
+.hP \*o
+Binary files are preserved completely, and no variation whatever is
+permitted.
+.PP
+The
+.VS
+catsign format
+.VE
+command can convert between detached and embedded signatures; it cannot
+convert between binary and text mode signatures.  (The data actually
+signed includes a flag saying whether the message is textual.  The
+rationale here is that what looks like an ASCII space before a newline
+may be devastatingly significant in a binary data file, and if a message
+is signed as raw binary then no changes whatever should be allowed.)
+.SH "COMMAND REFERENCE"
+.SS help
+The
+.B help
+command behaves exactly as the
+.B \-\-help
+option.  With no arguments, it shows an overview of
+.BR catsign 's
+options; with arguments, it describes the named subcommands.
+.SS show
+The
+.B show
+command prints various lists of tokens understood by
+.BR catsign .
+With no arguments, it prints all of the lists; with arguments, it prints
+just the named lists, in order.  The recognized lists can be enumerated
+using the
+.VS
+catsign show list
+.VE
+command.  The lists are as follows.
+.TP
+.B list
+The lists which can be enumerated by the
+.B show
+command.
+.TP
+.B sig
+The signature algorithms which can be used in a signing key's
+.B sig
+attribute.
+.TP
+.B hash
+The hash functions which can be used in a key's
+.B hash
+attribute.
+.TP
+.B enc
+The encodings which can be applied to encrypted messages; see 
+.B ENCODINGS
+above.
+.SS sign
+The
+.B sign
+command signs a message and writes out an appropriately-encoded
+signature.  By default, it reads a message from standard input and
+writes the signature to standard output.  If a filename argument is
+given, this file is read instead.
+.PP
+The following options are recognized.
+.TP
+.B "\-a, \-\-armour"
+Produce ASCII-armoured output.  This is equivalent to specifying
+.BR "\-f pem" .
+The variant spelling
+.B "\-\-armor"
+is also accepted.
+.TP
+.B "\-b, \-\-binary"
+Read and sign the input as binary data.  The default is to treat the
+input as text.
+.TP
+.B "\-d, \-\-detach"
+Produce a detached signature.  The default is to produce a signature
+with embedded message.
+.TP
+.BI "\-f, \-\-format " format
+Produce output encoded according to
+.IR format .
+.TP
+.BI "\-k, \-\-key " tag
+Use the signing key named
+.I tag
+in the current keyring; the default key is
+.BR ccsig .
+.TP
+.BI "\-o, \-\-ouptut " file
+Write output to
+.I file
+rather than to standard output.
+.TP
+.B "\-t, \-\-text"
+Read and sign the input as text.  This is the default.
+.SS verify
+The
+.B verify
+command checks a signature's validity, producing as output information
+about the signature and the signed message.
+.PP
+The first non-option argument is the name of the file containing the
+signature data; this may be omitted or 
+.RB ` \- '
+to indicate that the signature be read from standard input.  The second
+non-option argument, if any, is the name of the file to read the message
+from, if the signature is detached.  An error is reported if a message
+file is specified but the signature contains an embedded message
+already; if the signature is detached but no filename is given, then the
+message is expected on stdin (immediately after the signature, if any).
+.TP
+.B "\-a, \-\-armour"
+Read ASCII-armoured input.  This is equivalent to specifying
+.BR "\-f pem" .
+The variant spelling
+.B "\-\-armor"
+is also accepted.
+.TP
+.B "\-b, \-\-buffer"
+Buffer the message until the signature is verified.  This is forced on
+if output is to stdout, but is always available as an option.
+.TP
+.BI "\-f, \-\-format " format
+Read input encoded according to
+.IR format .
+.TP
+.B "\-v, \-\-verbose"
+Produce more verbose messages.  See below for the messages produced
+during decryption.  The default verbosity level is 1.  (Currently this
+is the most verbose setting.  This might not be the case always.)
+.B "\-q, \-\-quiet"
+Produce fewer messages.
+.TP
+.BI "\-k, \-\-key " tag
+Usually
+.B catsign
+uses the signature header to work out which key to use to verify a
+signature.  Using this option causes verification to fail unless the
+signature header specifies the key named
+.IR tag .
+.TP
+.B "\-u, \-\-utc"
+Show the datestamp in the signature in UTC rather than (your) local
+time.  The synonym
+.B \-\-gmt
+is also accepted.
+.TP
+.BI "\-o, \-\-output " file
+Write the verified message to
+.IR file .
+The file is written in text or binary
+mode as appropriate.  The default is to write the message to standard
+output unless verifying a detached signature, in which case nothing is
+written.
+.PP
+Output is written to standard output in a machine-readable format.
+Major problems cause the program to write a diagnostic to standard error
+and exit nonzero as usual.  The quantity of output varies depending on
+the verbosity level and whether the message is also being written to
+standard output.  Output lines begin with a keyword:
+.TP
+.BI "FAIL " reason
+An error prevented verification.  The program will exit nonzero.
+.TP
+.BI "WARN " reason
+.B catsign
+encountered a situation which may or may not invalidate the
+verification.
+.TP
+.BI "OK " message
+Verification was successful.  This is only produced if the message is
+being sent somewhere other than standard output.
+.TP
+.B "DATA"
+The message follows, starting just after the next newline character or
+sequence.  This is only produced if the message is being written to
+standard output.
+.TP
+.BI "INFO " note
+Any other information.
+.PP
+The information written at the various verbosity levels is as follows.
+.hP 0.
+No output.  Watch the exit status.
+.hP 1.
+All messages.
+.PP
+.B Warning!
+All output written has been checked for authenticity.  However, output
+can fail madway through for many reasons, and the resulting message may
+therefore be truncated.  Don't rely on the output being complete until 
+.B OK is printed or
+.B catsign verify
+exits successfully.
+.SS info
+The
+.B info
+command analyses a signature without verifying it, and prints
+interesting information about it.  This might be useful for diagnostic
+purposes.  No keys are needed for this operation, though you get more
+useful information if you have them.
+.PP
+If a non-option argument is given, and it is not
+.RB ` \- ',
+then it is taken to name the file containing the signature to parse;
+otherwise a signature is read from standard input.
+.PP
+The following options are recognized.
+.TP
+.B "\-a, \-\-armour"
+Read ASCII-armoured input.  This is equivalent to specifying
+.BR "\-f pem" .
+The variant spelling
+.B "\-\-armor"
+is also accepted.
+.TP
+.BI "\-f, \-\-format " format
+Read input encoded according to
+.IR format .
+.TP
+.B "\-u, \-\-utc"
+Show the datestamp in the signature in UTC rather than (your) local
+time.  The synonym
+.B \-\-gmt
+is also accepted.
+.PP
+A description of the signature block is produced on standard output; it
+is mostly machine-readable.  The first word on each line explains what
+kind of output it is.
+.TP
+.BI "BAD " message
+The signature data is invalid and cannot be parsed.
+.TP
+.BI "WARN " message
+Something is wrong with the data, but isn't fatal.
+.TP
+.BI "NOTE " message
+An environmental problem means that the information isn't as helpful as
+it might be.  For example, the keyring file can't be opened, so we don't
+know whether the verification key is there.
+.TP
+.BI "INFO flags " flags
+Describes the flags set in the signature header.  The
+.I flags
+are a list of flags, one per word, preceded by a
+.RB ` ! '
+if the flag is clear.
+.TP
+.BI "INFO expected-flags " flags
+If the PEM boundary string didn't match the actual signature data then
+this line is output, listing the expected flags and their settings.
+Problems with boundary mismatches can be resolved using the
+.B format
+command.
+.TP
+.BI "INFO date " yyyy "\-" mm "\-" dd " " hh ":" mm ":" ss " " tz
+Signature was (allegedly!) made at the given time and date.  If the
+.B \-u
+option was given, this will be in UTC.
+.TP
+.BI "INFO key " tag
+Signature was (allegedly!) made using the key
+.IR tag ,
+which is present in the current keyring.
+.TP
+.BI "INFO unknown-key " keyid
+Signature was (allegedly!) made using the key with id
+.IR keyid
+which is not in the current keyring (or the keyring wasn't found).
+.SS format
+The
+.B format
+command translates signatures between the various supported formats.
+This is a (slightly) more complex operation than re-encoding, though it
+does not require any cryptographic operations.
+.PP
+The first non-option argument is the name of the file containing the
+signature data; this may be omitted or 
+.RB ` \- '
+to indicate that the signature be read from standard input.  The second
+non-option argument, if any, is the name of the file to read the message
+from, if the signature is detached.  An error is reported if a message
+file is specified but the signature contains an embedded message
+already; if the signature is detached but no filename is given, then the
+message is expected on stdin (immediately after the signature, if any).
+.PP
+The options follow a rough convention: options describing the input
+format are lower-case and options specifying the output format are
+upper-case.  The following options are recognized.
+.TP "\-a, \-\-armour-in"
+Read ASCII-armoured input.  This is equivalent to specifying
+.BR "\-f pem" .
+The variant spelling
+.B "\-\-armor"
+is also accepted.
+.TP "\-A, \-\-armour-out"
+Produce ASCII-armoured output.  This is equivalent to specifying
+.BR "\-F pem" .
+The variant spelling
+.B "\-\-armor-out"
+is also accepted.
+.TP
+.B "\-D, \-\-detach"
+Produce a detached signature.  This may be used to detach a signature
+from an embedded message.
+.TP
+.B "\-E, \-\-embed"
+Produce a signature with embedded message.  This may be used to
+reattach a message to its detached signature.
+.TP
+.BI "\-f, \-\-format-in " format
+Read input encoded according to
+.IR format .
+.TP
+.BI "\-F, \-\-format-out " format
+Produce output encoded according to
+.IR format .
+.TP
+.BI "\-m, \-\-message " file
+Write the message to
+.IR file .
+If
+.I file
+is
+.RB ` \- '
+then write the message to standard output.  Don't send the message and
+signature to the same place because it doesn't work.
+.TP
+.BI "\-o, \-\-output " file
+Write the signature to
+.IR file .
+If no
+.B \-m
+or
+.B \-o
+option is given, a signature is written to standard output.
+.SH "BUGS"
+The trailing-whitespace deletion doesn't work for more than 32K of
+whitespace.  I don't think this is a big problem, really.
+.PP
+The
+.B format
+command does something unhelpful if message and signature are sent to
+the same file.
+.SH "SEE ALSO"
+.BR key (1),
+.BR catcrypt (1),
+.BR dsig (1),
+.BR hashsum (1),
+.BR keyring (5).
+.SH AUTHOR
+Mark Wooding, <mdw@nsict.org>
diff --git a/catsign.c b/catsign.c
new file mode 100644 (file)
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 <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <mLib/base64.h>
+#include <mLib/dstr.h>
+#include <mLib/mdwopt.h>
+#include <mLib/quis.h>
+#include <mLib/report.h>
+#include <mLib/sub.h>
+
+#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 -------------------------------------------------*/
index c3da95b..17c0a56 100644 (file)
--- a/cc-enc.c
+++ b/cc-enc.c
 
 /*----- Header files ------------------------------------------------------*/
 
+#include <errno.h>
 #include <stdio.h>
 
 #include <mLib/alloc.h>
 #include <mLib/base64.h>
 #include <mLib/dstr.h>
+#include <mLib/mdwopt.h>
 #include <mLib/report.h>
 #include <mLib/sub.h>
 
 #include "cc.h"
 
+typedef int encbdryp(const char *, void *);
+
 /*----- Main code ---------------------------------------------------------*/
 
 /* --- Binary --- */
 
-static enc *bin_init(FILE *fp, const char *msg)
+static enc *bin_encinit(FILE *fp, const char *msg)
+  { enc *e = CREATE(enc); return (e); }
+static enc *bin_decinit(FILE *fp, encbdryp *func, void *p)
   { enc *e = CREATE(enc); return (e); }
 
 static int bin_read(enc *e, void *p, size_t sz)
@@ -88,7 +94,9 @@ static enc *pem_encinit(FILE *fp, const char *msg)
   return (&pe->e);
 }
 
-static enc *pem_decinit(FILE *fp, const char *msg)
+int checkbdry(const char *b, void *p) { return (!p || strcmp(b, p) == 0); }
+
+static enc *pem_decinit(FILE *fp, encbdryp *func, void *p)
 {
   char buf[128];
   int i, d;
@@ -125,8 +133,7 @@ banner:
   /* --- Check we have the right framing --- */
 
   if (d != 5) goto top;
-  if (strncmp(buf, "BEGIN ", 6) != 0 ||
-      (msg && strcmp(buf + 6, msg) != 0))
+  if (strncmp(buf, "BEGIN ", 6) != 0 || (func && !func(buf + 6, p)))
     goto top;
 
   /* --- Ready --- */
@@ -262,7 +269,7 @@ static void pem_destroy(enc *e)
 
 const encops enctab[] = {
   { "binary", "rb", "wb",
-    bin_init, bin_init,
+    bin_encinit, bin_decinit,
     bin_read, bin_write,
     bin_done, bin_done,
     bin_destroy },
@@ -301,21 +308,41 @@ e_found:
  * Arguments:  @const encops *eo@ = operations (from @getenc@)
  *             @FILE *fp@ = file handle to attach
  *             @const char *msg@ = banner message
- *             @int wantenc@ = nonzero if we want to encode
  *
  * Returns:    The encoder object.
  *
  * Use:                Initializes an encoder.
  */
 
-enc *initenc(const encops *eo, FILE *fp, const char *msg, int wantenc)
+enc *initenc(const encops *eo, FILE *fp, const char *msg)
 {
-  enc *e = (wantenc ? eo->initenc : eo->initdec)(fp, msg);
+  enc *e = eo->initenc(fp, msg);
   e->ops = eo;
   e->fp = fp;
   return (e);  
 }
 
+/* --- @initdec@ --- *
+ *
+ * Arguments:  @const encops *eo@ = operations (from @getenc@)
+ *             @FILE *fp@ = file handle to attach
+ *             @int (*func)(const char *, void *)@ = banner check function
+ *             @void *p@ = argument for @func@
+ *
+ * Returns:    The encoder object.
+ *
+ * Use:                Initializes an encoder.
+ */
+
+enc *initdec(const encops *eo, FILE *fp,
+            int (*func)(const char *, void *), void *p)
+{
+  enc *e = eo->initdec(fp, func, p);
+  e->ops = eo;
+  e->fp = fp;
+  return (e);
+}
+
 /* --- @freeenc@ --- *
  *
  * Arguments:  @enc *e@ = encoder object
@@ -327,4 +354,146 @@ enc *initenc(const encops *eo, FILE *fp, const char *msg, int wantenc)
 
 void freeenc(enc *e) { e->ops->destroy(e); }
 
+/*----- Encoding and decoding commands ------------------------------------*/
+
+int cmd_encode(int argc, char *argv[])
+{
+  const char *of = 0;
+  FILE *ofp = 0;
+  FILE *fp = 0;
+  const char *ef = "binary";
+  const char *bd = "MESSAGE";
+  int i;
+  size_t n;
+  char buf[4096];
+  unsigned f = 0;
+  const encops *eo;
+  enc *e;
+
+#define f_bogus 1u
+
+  for (;;) {
+    static const struct option opt[] = {
+      { "format",      OPTF_ARGREQ,    0,      'f' },
+      { "boundary",    OPTF_ARGREQ,    0,      'b' },
+      { "output",      OPTF_ARGREQ,    0,      'o' },
+      { 0,             0,              0,      0 }
+    };
+    i = mdwopt(argc, argv, "f:b:o:", opt, 0, 0, 0);
+    if (i < 0) break;
+    switch (i) {
+      case 'f': ef = optarg; break;
+      case 'b': bd = optarg; break;
+      case 'o': of = optarg; break;
+      default: f |= f_bogus; break;
+    }
+  }
+  if (argc - optind > 1 || (f & f_bogus))
+    die(EXIT_FAILURE, "Usage: encode [-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], "rb")) == 0) {
+    die(EXIT_FAILURE, "couldn't open file `%s': %s",
+       argv[optind], strerror(errno));
+  } else
+    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));
+  }
+
+  e = initenc(eo, ofp, bd);
+
+  do {
+    n = fread(buf, 1, sizeof(buf), fp);
+    if (e->ops->write(e, buf, n))
+      die(EXIT_FAILURE, "error writing output: %s", strerror(errno));
+  } while (n == sizeof(buf));
+  e->ops->encdone(e);
+  freeenc(e);
+  return (0);
+
+#undef f_bogus
+}
+
+int cmd_decode(int argc, char *argv[])
+{
+  const char *of = 0;
+  FILE *ofp = 0;
+  FILE *fp = 0;
+  const char *ef = "binary";
+  const char *bd = 0;
+  int i;
+  char buf[4096];
+  unsigned f = 0;
+  const encops *eo;
+  enc *e;
+
+#define f_bogus 1u
+
+  for (;;) {
+    static const struct option opt[] = {
+      { "format",      OPTF_ARGREQ,    0,      'f' },
+      { "boundary",    OPTF_ARGREQ,    0,      'b' },
+      { "output",      OPTF_ARGREQ,    0,      'o' },
+      { 0,             0,              0,      0 }
+    };
+    i = mdwopt(argc, argv, "f:b:o:", opt, 0, 0, 0);
+    if (i < 0) break;
+    switch (i) {
+      case 'f': ef = optarg; break;
+      case 'b': bd = optarg; break;
+      case 'o': of = optarg; break;
+      default: f |= f_bogus; break;
+    }
+  }
+  if (argc - optind > 1 || (f & f_bogus))
+    die(EXIT_FAILURE, "Usage: decode [-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 (!of || strcmp(of, "-") == 0)
+    ofp = stdout;
+  else if ((ofp = fopen(of, "wb")) == 0) {
+    die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
+       ofp, strerror(errno));
+  }
+
+  e = initdec(eo, fp, checkbdry, (/*unconst*/ void *)bd);
+
+  do {
+    if ((i = e->ops->read(e, buf, sizeof(buf))) < 0)
+      die(EXIT_FAILURE, "error reading input: %s", strerror(errno));
+    if (fwrite(buf, 1, i, ofp) < i)
+      die(EXIT_FAILURE, "error writing output: %s", strerror(errno));
+  } while (i == sizeof(buf));
+  e->ops->decdone(e);
+  freeenc(e);
+  return (0);
+
+#undef f_bogus
+}
+
 /*----- That's all, folks -------------------------------------------------*/
diff --git a/cc.h b/cc.h
index 24fc205..5574e9e 100644 (file)
--- a/cc.h
+++ b/cc.h
@@ -114,7 +114,8 @@ typedef struct encops {
   const char *name;
   const char *rmode, *wmode;
   enc *(*initenc)(FILE */*fp*/, const char */*msg*/);
-  enc *(*initdec)(FILE */*fp*/, const char */*msg*/);
+  enc *(*initdec)(FILE */*fp*/,
+                 int (*/*func*/)(const char *, void *), void */*p*/);
   int (*read)(enc */*e*/, void */*p*/, size_t /*sz*/);
   int (*write)(enc */*e*/, const void */*p*/, size_t /*sz*/);
   int (*encdone)(enc */*e*/);
@@ -201,20 +202,45 @@ extern void freesig(sig */*s*/);
 
 extern const encops *getenc(const char */*enc*/);
 
+/* --- @checkbdry@ --- *
+ *
+ * Arguments:  @const char *b@ = boundary string found
+ *             @void *p@ = boundary string wanted
+ *
+ * Returns:    Nonzero if the boundary string is the one we wanted.
+ *
+ * Use:                Pass as @func@ to @initdec@ if you just want a simple life.
+ */
+
+extern int checkbdry(const char */*b*/, void */*p*/);
+
 /* --- @initenc@ --- *
  *
  * Arguments:  @const encops *eo@ = operations (from @getenc@)
  *             @FILE *fp@ = file handle to attach
  *             @const char *msg@ = banner message
- *             @int wantenc@ = nonzero if we want to encode
  *
  * Returns:    The encoder object.
  *
  * Use:                Initializes an encoder.
  */
 
-extern enc *initenc(const encops */*eo*/, FILE */*fp*/,
-                   const char */*msg*/, int /*wantenc*/);
+extern enc *initenc(const encops */*eo*/, FILE */*fp*/, const char */*msg*/);
+
+/* --- @initdec@ --- *
+ *
+ * Arguments:  @const encops *eo@ = operations (from @getenc@)
+ *             @FILE *fp@ = file handle to attach
+ *             @int (*func)(const char *, void *)@ = banner check function
+ *             @void *p@ = argument for @func@
+ *
+ * Returns:    The encoder object.
+ *
+ * Use:                Initializes an encoder.
+ */
+
+extern enc *initdec(const encops */*eo*/, FILE */*fp*/,
+                   int (*/*func*/)(const char *, void *), void */*p*/);
 
 /* --- @freeenc@ --- *
  *
@@ -227,6 +253,33 @@ extern enc *initenc(const encops */*eo*/, FILE */*fp*/,
 
 extern void freeenc(enc */*e*/);
 
+/* --- @cmd_encode@, @cmd_decode@ --- */
+
+#define CMD_ENCODE {                                                   \
+  "encode", cmd_encode,                                                        \
+    "encode [-f FORMAT] [-b LABEL] [-o OUTPUT] [FILE]",                        \
+    "\
+Options:\n\
+\n\
+-f, --format=FORMAT    Encode to FORMAT.\n\
+-b, --boundary=LABEL   PEM boundary is LABEL.\n\
+-o, --output=FILE      Write output to FILE.\n\
+" }
+
+#define CMD_DECODE {                                                   \
+  "decode", cmd_decode,                                                        \
+    "decode [-f FORMAT] [-b LABEL] [-o OUTPUT] [FILE]",                        \
+    "\
+Options:\n\
+\n\
+-f, --format=FORMAT    Decode from FORMAT.\n\
+-b, --boundary=LABEL   PEM boundary is LABEL.\n\
+-o, --output=FILE      Write output to FILE.\n\
+" }
+
+extern int cmd_encode(int /*argc*/, char */*argv*/[]);
+extern int cmd_decode(int /*argc*/, char */*argv*/[]);
+
 /* --- @LIST(STRING, FP, END-TEST, NAME-EXPR)@ --- *
  *
  * Produce list of things.  Requires @i@ and @w@ variables in scope.
diff --git a/dsig.1 b/dsig.1
index 0d7c83b..3b4223c 100644 (file)
--- a/dsig.1
+++ b/dsig.1
@@ -507,6 +507,7 @@ blocks.
 .BR key (1),
 .BR hashsum (1),
 .BR catcrypt (1),
+.BR catsign (1),
 .BR keyring (5).
 .SH AUTHOR
 Mark Wooding, <mdw@nsict.org>
index 5eb280b..0084b04 100644 (file)
--- a/hashsum.1
+++ b/hashsum.1
@@ -292,6 +292,9 @@ will emit a
 .RB ` #hash '
 directive in its output.
 .SH "SEE ALSO"
-.BR md5sum (1).
+.BR md5sum (1),
+.BR dsig (1),
+.BR catsign (1),
+.BR catcrypt (1).
 .SH "AUTHOR"
 Mark Wooding, <mdw@nsict.org>