General utilities cleanup. Add signature support to catcrypt. Throw in
authormdw <mdw>
Fri, 1 Oct 2004 21:08:29 +0000 (21:08 +0000)
committermdw <mdw>
Fri, 1 Oct 2004 21:08:29 +0000 (21:08 +0000)
cookie.  Add manual pages.

18 files changed:
Makefile.m4
catcrypt.1 [new file with mode: 0644]
catcrypt.c
cc-enc.c
cc-kem.c
cc-list.c [new file with mode: 0644]
cc-sig.c
cc-subcmd.c [new file with mode: 0644]
cc.h
cookie.1 [new file with mode: 0644]
cookie.c [new file with mode: 0644]
dsig.1 [new file with mode: 0644]
dsig.c
hashsum.1
hashsum.c
key.1
keyutil.c
perftest.c

index 04d03f6..1cccc9b 100644 (file)
@@ -1,6 +1,6 @@
 ## -*-m4-*-
 ##
-## $Id: Makefile.m4,v 1.83 2004/04/21 00:37:32 mdw Exp $
+## $Id$
 ##
 ## Makefile for Catacomb
 ##
@@ -246,17 +246,24 @@ patsubst(MP_SOURCES, `\.c\>', `.lo') dsig.o keyutil.o rspit.o: \
 ## --- Utility programs ---
 
 bin_PROGRAMS = \
-       dsig key pixie rspit factorial hashsum mkphrase catcrypt
+       dsig key pixie cookie rspit factorial hashsum mkphrase catcrypt
+noinst_LIBRARIES = libcatcrypt.a
 bin_SCRIPTS = catacomb-config xpixie
 noinst_PROGRAMS = \
        genprimes mptypes serpent-check bittest mpdump \
        perftest \
        addsuffix(`gen_tables', `-mktab')
-LDADD = libcatacomb.la
-
-dsig_SOURCES = dsig.c cc.h cc-sig.c getdate.y getdate.h
-catcrypt_SOURCES = catcrypt.c cc.h cc-sig.c cc-kem.c cc-enc.c
-key_SOURCES = keyutil.c getdate.y getdate.h
+LDADD = libcatacomb.la libcatcrypt.a
+
+libcatcrypt_a_SOURCES = \
+       cc.h getdate.h \
+       cc-sig.c cc-subcmd.c cc-enc.c cc-kem.c cc-list.c \
+       getdate.y
+       
+dsig_SOURCES = dsig.c
+cookie_SOURCES = cookie.c
+catcrypt_SOURCES = catcrypt.c
+key_SOURCES = keyutil.c
 hashsum_SOURCES = hashsum.c
 rspit_SOURCES = rspit.c
 factorial_SOURCES = factorial.c
@@ -309,7 +316,7 @@ changequote(`, ')
 
 ## --- Documentation ---
 
-man_MANS = key.1 hashsum.1 keyring.5 pixie.1
+man_MANS = key.1 dsig.1 cookie.1 catcrypt.1 hashsum.1 keyring.5 pixie.1
 
 ## --- Other handy definitions ---
 
diff --git a/catcrypt.1 b/catcrypt.1
new file mode 100644 (file)
index 0000000..603d1d1
--- /dev/null
@@ -0,0 +1,676 @@
+.\" -*-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 catcrypt 1 "30 September 2004" "Straylight/Edgeware" "Catacomb cryptographic library"
+.SH NAME
+catcrypt \- encrypt and decrypt messages
+.SH SYNOPSIS
+.B catcrypt
+.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 encrypt
+.RB [ \-a ]
+.RB [ \-k
+.IR tag ]
+.RB [ \-f
+.IR format ]
+.RB [ \-o
+.IR output ]
+.RI [ file ]
+.br
+.B decrypt
+.RB [ \-aqv ]
+.RB [ \-f
+.IR format ]
+.RB [ \-o
+.IR output ]
+.RI [ file ]
+.br
+.B encode
+.RB [ \-f
+.IR format ]
+.RB [ \-b
+.IR boundary ]
+.RB [ \-o
+.IR output ]
+.RI [ file ]
+.br
+.B encode
+.RB [ \-f
+.IR format ]
+.RB [ \-b
+.IR boundary ]
+.RB [ \-o
+.IR output ]
+.RI [ file ]
+.SH "DESCRIPTION"
+The
+.B catcrypt
+command encrypts and decrypts 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 catcrypt '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 catcrypt
+command deals with both signing and key-encapsulation keys.
+.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
+which are largely uninteresting at this point.)
+.PP
+A
+.I kemalgspec
+has the syntax
+.IR kem \c
+.RB [ / \c
+.IR cipher \c
+.RB [ / \c
+.IR hash ]].
+If a
+.B kem
+attribute is present on the key, then it must have this form; otherwise,
+the key's type must have the form
+.BR cckem- \c
+.IR kemalgspec .
+Algorithm selections are taken from appropriately-named attributes, or,
+failing that, from the
+.IR kemalgspec .
+.PP
+The key-encapsulation mechanism is chosen according to the setting of
+.I kem
+as follows.  Run
+.B catcrypt show kem
+for a list of supported KEMs.
+.TP
+.B rsa
+This is Shoup's RSA-KEM (formerly Simple RSA); see
+.I
+A proposal for an ISO standard for public key encryption (version 2.0)
+available at
+.BR http://eprint.iacr.org/2000/060/ .
+Use the
+.B rsa
+algorithm of the
+.B key add
+command (see
+.BR key (1))
+to generate the key.
+.TP
+.B dh
+This is standard Diffie-Hellman key exchange, hashing the resulting
+shared secret to form the key, as used in, e.g., DLIES (P1363a).
+Use the
+.B dh
+algorithm of the
+.B key add
+command, preferably with the
+.B \-LS
+options, to generate the key.
+.TP
+.B ec
+This is the elliptic-curve analogue of
+.BR dh .  Use the
+.B ec
+algorithm of the
+.BR key (1))
+command to generate the key.
+.PP
+As well as the KEM itself, a number of supporting algorithms are used.
+These are taken from appropriately named attributes on the key or,
+failing that, derived from other attributes as described below.
+.TP
+.B cipher
+This is the symmetric encryption algorithm used for bulk data
+encryption.  If there is no
+.B cipher
+attribute then the
+.I cipher
+in the
+.I kemalgspec
+is used; if that it absent, then the default of
+.B blowfish-cbc
+is used.  Run
+.B catcrypt show cipher
+for a list of supported symmetric encryption algorithms.
+.TP
+.B hash
+This is the hash function used to distil entropy from the shared secret
+constructed by the raw KEM.  If there is no
+.B hash
+attribute then the
+.I hash
+in the
+.I kemalgspec is used; if that is absent then the default of
+.B rmd160
+is used.  Run
+.B catcrypt show hash
+for a list of supported symmetric encryption algorithms.
+.TP
+.B mac
+This is the message authentication algorithm used during bulk data
+encryption to ensure integrity of the encrypted message and defend
+against chosen-ciphertext attacks.  If there is no
+.B mac
+attribute then
+.IB hash -hmac
+is chosen as a default.  Run
+.B catcrypt show mac
+for a list of supported message authentication algorithms.
+.TP
+.B kdf
+This is the key derivation function used to stretch the hashed shared
+secret to a sufficient length to select symmetric encryption and
+authentication keys, initialization vectors and other necessary
+pseudorandom quantities.  If there is no
+.B kdf
+attribute then
+.IB hash -mgf
+is chosen as a default.  Run
+.B catcrypt show kdf
+for a list of supported key derivation functions.
+.B Caution!
+Not all supported functions have the required security features: don't
+override the default choice unless you know what you're doing.
+.SS "Signing keys"
+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 catcrypt 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 catcrypt 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 "COMMAND REFERENCE"
+.SS help
+The
+.B help
+command behaves exactly as the
+.B \-\-help
+option.  With no arguments, it shows an overview of
+.BR catcrypt 's
+options; with arguments, it describes the named subcommands.
+.SS show
+The
+.B show
+command prints various lists of tokens understood by
+.BR catcrypt .
+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
+catcrypt 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 kem
+The key-encapsulation algorithms which can be used in a
+key-encapsulation key's
+.B kem
+attribute.
+.TP
+.B cipher
+The symmetric encryption algorithms which can be used in a
+key-encapsulation key's
+.B cipher
+attribute.
+.TP
+.B mac
+The message authentication algorithms which can be used in a
+key-encapsulation key's
+.B mac
+attribute.
+.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 encrypt
+The
+.B encrypt
+command encrypts a file and writes out the appropriately-encoded
+ciphertext.  By default, it reads from standard input and writes to
+standard output.  If a filename argument is given, this file is read
+instead (as binary data).
+.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
+.BI "\-f, \-\-format " format
+Produce output encoded according to
+.IR format .
+.TP
+.BI "\-k, \-\-key " tag
+Use the key-encapsulation key named
+.I tag
+in the current keyring; the default key is
+.BR ccrypt .
+.TP
+.BI "\-s, \-\-sign-key " tag
+Use the signature key named
+.I tag
+in the current keyring; the default is not to sign the ciphertext.
+.TP
+.BI "\-o, \-\-ouptut " file
+Write output to
+.I file
+rather than to standard output.
+.SS decrypt
+The
+.B decrypt
+command decrypts a ciphertext and writes out the plaintext.  By default,
+it reads from standard input and writes to standard output.  If a
+filename argument is given, this file is read instead.
+.PP
+The following options are recognized.
+.TP
+.B "\-a, \-\-armour"
+Read ASCII-armoured output.  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 "\-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.)
+.TP
+.B "\-q, \-\-quiet"
+Produce fewer messages.
+.TP
+.BI "\-o, \-\-output " file
+Write output to
+.I file
+instead of to standard output.  The file is written in binary mode.
+Fixing line-end conventions is your problem; there are lots of good
+tools for dealing with it.
+.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 plaintext is also being written to
+standard output.  Output lines begin with a keyword.:
+.TP
+.BI "FAIL " reason
+An error prevented decryption.  The program will exit nonzero.
+.TP
+.BI "WARN " reason
+.B catcrypt
+encountered a situation which may or may not invalidate the decryption.
+.TP 
+.BI "OK " message
+Decryption was successful.  This is only produced if main output is
+being sent somewhere other than standard output.
+.TP
+.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.
+.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, 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
+.B catcrypt decrypt
+prints
+.B OK
+and/or exits successfully.
+.SS "encode"
+The
+.B encode
+command encodes an input file according to one of the encodings
+described above in
+.BR ENCODINGS .
+The input is read from the 
+.I file
+given on the command line, or from standard input if none is specified.
+Options provided are:
+.TP
+.BI "\-f, \-\-format " format
+Produce output in
+.IR format .
+Run
+.B catcrypt show enc
+for a list of encoding formats.
+.TP
+.BI "\-b, \-\-boundary " label
+Set the PEM boundary string to
+.IR label ;
+i.e., assuming we're encoding in PEM format, the output will have
+.BI "\-\-\-\-\-BEGIN " label "\-\-\-\-\-"
+at the top and
+.BI "\-\-\-\-\-END " label "\-\-\-\-\-"
+at the bottom.  The default
+.I label
+is
+.BR MESSAGE .
+.TP
+.BI "\-o, \-\-output " file
+Write output to
+.I file
+instead of to standard output.
+.SS "decode"
+The
+.B decode
+command decodes an input file encoded according to one of the encodings
+described above in
+.BR ENCODINGS .
+The input is read from the 
+.I file
+given on the command line, or from standard input if none is specified.
+Options provided are:
+.TP
+.BI "\-f, \-\-format " format
+Decode input in
+.IR format .
+Run
+.B catcrypt show enc
+for a list of encoding formats.
+.TP
+.BI "\-b, \-\-boundary " label
+Set the PEM boundary string to
+.IR label ;
+i.e., assuming we're encoding in PEM format, start processing input
+between
+.BI "\-\-\-\-\-BEGIN " label "\-\-\-\-\-"
+and 
+.BI "\-\-\-\-\-END " label "\-\-\-\-\-"
+lines.  Without this option,
+.B catcrypt
+will start reading at the first plausible boundary string, and continue
+processing until it reaches the matching end boundary.
+.TP
+.BI "\-o, \-\-output " file
+Write output to
+.I file
+instead of to standard output.
+.SH "SECURITY PROPERTIES"
+Assuming the security of the underlying primitive algorithms, the
+following security properties of the ciphertext hold.
+.hP \*o
+An adversary given the public key-encapsulation key and capable of
+requesting encryption of arbitrary plaintexts of his own devising is
+unable to decide whether he is given ciphertexts corresponding to his
+chosen plaintexts or random plaintexts of the same length.  This holds
+even if the adversary is permitted to request decryption of any
+ciphertext other than one produced as a result of an encryption request.
+This property is called
+.BR IND-CCA2 .
+.hP \*o
+An adversary given the public key-encapsulation and verification keys,
+and capable of requesting encryption of arbitrary plaintext of his own
+devising is unable to produce a new ciphertext which will be accepted as
+genuine.  This property is called
+.BR INT-CTXT .
+.hP \*o
+An adversary given the public key-encapsulation and verification keys,
+and capable of requesting encryption of arbitrary plaintext of his own
+devising is unable to decide whether the ciphertexts he is given are
+correctly signed.  This property doesn't seem to have a name.
+.PP
+Not all is rosy.  If you leak intermediate values during decryption then
+an adversary can construct a new correctly-signed message.  Don't do
+that, then \(en leaking intermediate values often voids security
+warranties.  But it does avoid the usual problem with separate signing
+and encryption that a careful leak by the recipient can produce evidence
+that you signed some incriminating message.
+.SH "CRYPTOGRAPHIC THEORY"
+Encryption of a message proceeds as follows.
+.hP 0.
+Emit a header packet containing the key-ids for the key-encapsulation
+key, and signature key if any.
+.hP 1.
+Use the KEM to produce a public value and a shared secret the recipient
+will be able to extract from the public value using his private key.
+Emit a packet containing the public value.
+.hP 2.
+Hash the shared secret.  Use the KDF to produce a pseudorandom keystream
+of indefinite length.
+.hP 3.
+Use the first bits of the keystream to key a symmetric encryption
+scheme; use the next bits to key a message authentication code.
+.hP 4.
+If we're signing the message then extract 1024 bytes from the keystream,
+sign them, and emit a packet containing the signature.  The signature
+packet doesn't contain the signed message, just the signature.
+.hP 5.
+Split the message into blocks.  For each block, pick a random IV from
+the keystream, encrypt the block and emit a packet containing the
+IV, ciphertext and a MAC tag.
+.PP
+That's it.  Nothing terribly controversial, really.
+.SH "SEE ALSO"
+.BR key (1),
+.BR dsig (1),
+.BR hashsum (1),
+.BR keyring (5).
+.SH AUTHOR
+Mark Wooding, <mdw@nsict.org>
index 546b6c3..cc1ab39 100644 (file)
@@ -1,6 +1,6 @@
 /* -*-c-*-
  *
- * $Id: catcrypt.c,v 1.2 2004/05/09 13:03:46 mdw Exp $
+ * $Id$
  *
  * Command-line encryption tool
  *
@@ -50,6 +50,9 @@
 #include "key.h"
 #include "cc.h"
 
+#include "ectab.h"
+#include "ptab.h"
+
 /*----- Utilities ---------------------------------------------------------*/
 
 /* --- @keyreport@ --- *
@@ -142,12 +145,13 @@ err:
 
 static int encrypt(int argc, char *argv[])
 {
-  const char *of = 0, *kn = "ccrypt";
+  const char *of = 0, *kn = "ccrypt", *skn = 0;
   FILE *ofp = 0;
   FILE *fp = 0;
   const char *ef = "binary";
   const char *err;
   int i;
+  int en;
   size_t n;
   dstr d = DSTR_INIT;
   octet *tag, *ct;
@@ -157,7 +161,9 @@ static int encrypt(int argc, char *argv[])
   unsigned f = 0;
   key_file kf;
   key *k;
+  key *sk = 0;
   kem *km;
+  sig *s = 0;
   gcipher *cx, *c;
   gmac *m;
   ghash *h;
@@ -169,16 +175,18 @@ static int encrypt(int argc, char *argv[])
   for (;;) {
     static const struct option opt[] = {
       { "key",         OPTF_ARGREQ,    0,      'k' },
+      { "sign-key",    OPTF_ARGREQ,    0,      's' },
       { "armour",      0,              0,      'a' },
       { "armor",       0,              0,      'a' },
       { "format",      OPTF_ARGREQ,    0,      'f' },
       { "output",      OPTF_ARGREQ,    0,      'o' },
       { 0,             0,              0,      0 }
     };
-    i = mdwopt(argc, argv, "k:af:o:", opt, 0, 0, 0);
+    i = mdwopt(argc, argv, "k:s:af:o:", opt, 0, 0, 0);
     if (i < 0) break;
     switch (i) {
       case 'k': kn = optarg; break;
+      case 's': skn = optarg; break;
       case 'a': ef = "pem"; break;
       case 'f': ef = optarg; break;
       case 'o': of = optarg; break;
@@ -186,12 +194,14 @@ static int encrypt(int argc, char *argv[])
     }
   }
   if (argc - optind > 1 || (f & f_bogus))
-    die(EXIT_FAILURE, "Usage: encrypt [-options] [file]");
+    die(EXIT_FAILURE, "Usage: encrypt [-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 (skn && (sk = key_bytag(&kf, skn)) == 0)
+    die(EXIT_FAILURE, "key `%s' not found", skn);
 
   if ((eo = getenc(ef)) == 0)
     die(EXIT_FAILURE, "encoding `%s' not found", ef);
@@ -214,11 +224,19 @@ static int encrypt(int argc, char *argv[])
        ofp, strerror(errno));
   }
 
+  dstr_reset(&d);
   key_fulltag(k, &d);
   e = initenc(eo, ofp, "CATCRYPT ENCRYPTED MESSAGE", 1);
-  km = getkem(k, "ccrypt", 0);
+  km = getkem(k, "cckem", 0);
   if ((err = km->ops->check(km)) != 0)
     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);
+  }
 
   /* --- Build the header chunk --- */
 
@@ -226,6 +244,7 @@ static int encrypt(int argc, char *argv[])
   dstr_ensure(&d, 256);
   buf_init(&b, d.buf, 256);
   buf_putu32(&b, k->id);
+  if (sk) buf_putu32(&b, sk->id);
   assert(BOK(&b));
   chunk_write(e, &b);
 
@@ -238,6 +257,19 @@ static int encrypt(int argc, char *argv[])
   BSTEP(&b, d.len);
   chunk_write(e, &b);
 
+  /* --- Write the signature chunk --- */
+
+  if (s) {
+    GC_ENCRYPT(cx, 0, bb, 1024);
+    GH_HASH(s->h, bb, 1024);
+    dstr_reset(&d);
+    if ((en = s->ops->doit(s, &d)) != 0)
+      die(EXIT_FAILURE, "error creating signature: %s", key_strerror(en));
+    buf_init(&b, d.buf, d.len);
+    BSTEP(&b, d.len);
+    chunk_write(e, &b);
+  }  
+
   /* --- Now do the main crypto --- */
 
   assert(GC_CLASS(c)->blksz <= sizeof(bb));
@@ -281,6 +313,7 @@ static int encrypt(int argc, char *argv[])
   GC_DESTROY(c);
   GC_DESTROY(cx);
   freeenc(e);
+  if (s) freesig(s);
   freekem(km);
   if (of) fclose(ofp);
   key_close(&kf);
@@ -305,7 +338,9 @@ static int decrypt(int argc, char *argv[])
   size_t seq;
   uint32 id;
   key *k;
+  key *sk = 0;
   kem *km;
+  sig *s = 0;
   gcipher *cx;
   gcipher *c;
   ghash *h;
@@ -313,6 +348,8 @@ static int decrypt(int argc, char *argv[])
   octet *tag;
   unsigned f = 0;
   const encops *eo;
+  const char *err;
+  int verb = 1;
   enc *e;
 
 #define f_bogus 1u
@@ -321,21 +358,25 @@ static int decrypt(int argc, char *argv[])
     static const struct option opt[] = {
       { "armour",      0,              0,      'a' },
       { "armor",       0,              0,      'a' },
+      { "verbose",     0,              0,      'v' },
+      { "quiet",       0,              0,      'q' },
       { "format",      OPTF_ARGREQ,    0,      'f' },
       { "output",      OPTF_ARGREQ,    0,      'o' },
       { 0,             0,              0,      0 }
     };
-    i = mdwopt(argc, argv, "af:o:", opt, 0, 0, 0);
+    i = mdwopt(argc, argv, "af:o:qv", opt, 0, 0, 0);
     if (i < 0) break;
     switch (i) {
       case 'a': ef = "pem"; break;
+      case 'v': verb++; break;
+      case 'q': if (verb) verb--; break;
       case 'f': ef = optarg; break;
       case 'o': of = optarg; break;
       default: f |= f_bogus; break;
     }
   }
   if (argc - optind > 1 || (f & f_bogus))
-    die(EXIT_FAILURE, "Usage: decrypt [-options] [file]");
+    die(EXIT_FAILURE, "Usage: decrypt [-OPTIONS] [FILE]");
 
   if ((eo = getenc(ef)) == 0)
     die(EXIT_FAILURE, "encoding `%s' not found", ef);
@@ -359,28 +400,72 @@ static int decrypt(int argc, char *argv[])
   /* --- Read the header chunk --- */
 
   chunk_read(e, &d, &b);
-  if (buf_getu32(&b, &id))
-    die(EXIT_FAILURE, "malformed header: missing keyid");
-  if (BLEFT(&b))
-    die(EXIT_FAILURE, "malformed header: junk at end");
+  if (buf_getu32(&b, &id)) {
+    if (verb) printf("FAIL malformed header: missing keyid\n");
+    exit(EXIT_FAILURE);
+  }
+  if ((k = key_byid(&kf, id)) == 0) {
+    if (verb) printf("FAIL key id %08lx not found\n", (unsigned long)id);
+    exit(EXIT_FAILURE);
+  }
+  if (BLEFT(&b)) {
+    if (buf_getu32(&b, &id)) {
+      if (verb) printf("FAIL malformed header: missing signature keyid\n");
+      exit(EXIT_FAILURE);
+    }
+    if ((sk = key_byid(&kf, id)) == 0) {
+      if (verb) printf("FAIL key id %08lx not found\n", (unsigned long)id);
+      exit(EXIT_FAILURE);
+    }
+  }
+  if (BLEFT(&b)) {
+    if (verb) printf("FAIL malformed header: junk at end\n");
+    exit(EXIT_FAILURE);
+  }
 
   /* --- Find the key --- */
 
-  if ((k = key_byid(&kf, id)) == 0)
-    die(EXIT_FAILURE, "key id %08lx not found", (unsigned long)id);
-  km = getkem(k, "ccrypt", 1);
+  km = getkem(k, "cckem", 1);
 
   /* --- Read the KEM chunk --- */
 
   chunk_read(e, &d, &b);
-  if (setupkem(km, &d, &cx, &c, &m))
-    die(EXIT_FAILURE, "failed to decapsulate key");
+  if (setupkem(km, &d, &cx, &c, &m)) {
+    if (verb) printf("FAIL failed to decapsulate key\n");
+    exit(EXIT_FAILURE);
+  }
+
+  /* --- Verify the signature, if there is one --- */
+
+  if (sk) {
+    s = getsig(sk, "ccsig", 0);
+    dstr_reset(&d);
+    key_fulltag(sk, &d);
+    if (verb && (err = s->ops->check(s)) != 0)
+      printf("WARN verification key %s fails check: %s\n", d.buf, err);
+    dstr_reset(&d);
+    dstr_ensure(&d, 1024);
+    GC_ENCRYPT(cx, 0, d.buf, 1024);
+    GH_HASH(s->h, d.buf, 1024);
+    chunk_read(e, &d, &b);
+    if (s->ops->doit(s, &d)) {
+      if (verb) printf("FAIL signature verification failed\n");
+      exit(EXIT_FAILURE);
+    }
+    if (verb) {
+      dstr_reset(&d);
+      key_fulltag(sk, &d);
+      printf("INFO good signature from %s\n", d.buf);
+    }
+  } else if (verb)
+    printf("INFO no signature packet\n");
 
   /* --- Now decrypt the main body --- */
 
-  if (!of || strcmp(of, "-") == 0)
+  if (!of || strcmp(of, "-") == 0) {
     ofp = stdout;
-  else if ((ofp = fopen(of, "wb")) == 0) {
+    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));
   }
@@ -398,22 +483,37 @@ static int decrypt(int argc, char *argv[])
     GH_HASH(h, d.buf, 4);
     seq++;
     chunk_read(e, &d, &b);
-    if ((tag = buf_get(&b, GM_CLASS(m)->hashsz)) == 0)
-      die(EXIT_FAILURE, "bad ciphertext chunk: no tag");
+    if ((tag = buf_get(&b, GM_CLASS(m)->hashsz)) == 0) {
+      if (verb) printf("%sFAIL bad ciphertext chunk: no tag\n",
+                      ofp == stdout ? "\n" : "");
+      exit(EXIT_FAILURE);
+    }
     GH_HASH(h, BCUR(&b), BLEFT(&b));
-    if (memcmp(tag, GH_DONE(h, 0), GM_CLASS(m)->hashsz) != 0)
-      die(EXIT_FAILURE, "bad ciphertext chunk: authentication failure");
+    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" : "");
+      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))
-      die(EXIT_FAILURE, "error writing output: %s", strerror(errno));
+    if (fwrite(BCUR(&b), 1, BLEFT(&b), ofp) != BLEFT(&b)) {
+      if (verb) printf("%sFAIL error writing output: %s\n",
+                      ofp == stdout ? "\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));
+    exit(EXIT_FAILURE);
   }
 
-  if (fflush(ofp) || ferror(ofp))
-    die(EXIT_FAILURE, "error writing output: %s", strerror(errno));
-    
   e->ops->decdone(e);
+  if (verb && ofp != stdout)
+    printf("OK decrypted successfully\n");
   freeenc(e);
   GC_DESTROY(c);
   GC_DESTROY(cx);
@@ -462,7 +562,7 @@ static int encode(int argc, char *argv[])
     }
   }
   if (argc - optind > 1 || (f & f_bogus))
-    die(EXIT_FAILURE, "Usage: encode [-options] [file]");
+    die(EXIT_FAILURE, "Usage: encode [-OPTIONS] [FILE]");
 
   if ((eo = getenc(ef)) == 0)
     die(EXIT_FAILURE, "encoding `%s' not found", ef);
@@ -531,7 +631,7 @@ static int decode(int argc, char *argv[])
     }
   }
   if (argc - optind > 1 || (f & f_bogus))
-    die(EXIT_FAILURE, "Usage: decode [-options] [file]");
+    die(EXIT_FAILURE, "Usage: decode [-OPTIONS] [FILE]");
 
   if ((eo = getenc(ef)) == 0)
     die(EXIT_FAILURE, "encoding `%s' not found", ef);
@@ -571,16 +671,36 @@ static int decode(int argc, char *argv[])
 
 /*----- Main code ---------------------------------------------------------*/
 
-typedef struct cmd {
-  const char *name;
-  int (*func)(int /*argc*/, char */*argv*/[]);
-  const char *usage;
-  const char *help;
-} cmd;
+#define LISTS(LI)                                                      \
+  LI("Lists", list,                                                    \
+     listtab[i].name, listtab[i].name)                                 \
+  LI("Key-encapsulation mechanisms", kem,                              \
+     kemtab[i].name, kemtab[i].name)                                   \
+  LI("Signature schemes", sig,                                         \
+     sigtab[i].name, sigtab[i].name)                                   \
+  LI("Encodings", enc,                                                 \
+     enctab[i].name, enctab[i].name)                                   \
+  LI("Symmetric encryption algorithms", cipher,                                \
+     gciphertab[i], gciphertab[i]->name)                               \
+  LI("Hash functions", hash,                                           \
+     ghashtab[i], ghashtab[i]->name)                                   \
+  LI("Message authentication codes", mac,                              \
+     gmactab[i], gmactab[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...]" },
   { "encode", encode,
-    "encode [-f format] [-b label] [-o output] [file]",
+    "encode [-f FORMAT] [-b LABEL] [-o OUTPUT] [FILE]",
     "\
 Options:\n\
 \n\
@@ -589,7 +709,7 @@ Options:\n\
 -o, --output=FILE      Write output to FILE.\n\
 " },
   { "decode", decode,
-    "decode [-f format] [-b label] [-o output] [file]",
+    "decode [-f FORMAT] [-b LABEL] [-o OUTPUT] [FILE]",
     "\
 Options:\n\
 \n\
@@ -598,87 +718,59 @@ Options:\n\
 -o, --output=FILE      Write output to FILE.\n\
 " },
   { "encrypt", encrypt,
-    "encrypt [-a] [-k tag] [f format]] [-o output] [file]",
-    "\
+    "encrypt [-a] [-k TAG] [-s TAG] [-f FORMAT]\n\t\
+[-o OUTPUT] [FILE]", "\
 Options:\n\
 \n\
 -a, --armour           Same as `-f pem'.\n\
 -f, --format=FORMAT    Encode as FORMAT.\n\
--k, --key=TAG          Use public key named by TAG.\n\
+-k, --key=TAG          Use public encryption key named by TAG.\n\
+-s, --sign-key=TAG     Use private signature key named by TAG.\n\
 -o, --output=FILE      Write output to FILE.\n\
 " },
   { "decrypt", decrypt,
-    "decrypt [-t] [-o output] [file]", "\
+    "decrypt [-aqv] [-f FORMAT] [-o OUTPUT] [FILE]", "\
 Options:\n\
 \n\
--t, --text             Read PEM-encoded input.\n\
+-a, --armour           Same as `-f pem'.\n\
+-v, --verbose          Produce more verbose messages.\n\
+-q, --quiet            Produce fewer messages.\n\
+-f, --format=FORMAT    Decode as FORMAT.\n\
 -o, --output=FILE      Write output to FILE.\n\
 " },
   { 0, 0, 0 }
 };
 
-/* --- @findcmd@ --- *
- *
- * Arguments:  @const char *name@ = a command name
- *
- * Returns:    Pointer to the command structure.
- *
- * Use:                Looks up a command by name.  If the command isn't found, an
- *             error is reported and the program is terminated.
- */
-
-static cmd *findcmd(const char *name)
+static int cmd_help(int argc, char **argv)
 {
-  cmd *c, *chosen = 0;
-  size_t sz = strlen(name);
-
-  for (c = cmdtab; c->name; c++) {
-    if (strncmp(name, c->name, sz) == 0) {
-      if (c->name[sz] == 0) {
-       chosen = c;
-       break;
-      } else if (chosen)
-       die(EXIT_FAILURE, "ambiguous command name `%s'", name);
-      else
-       chosen = c;
-    }
-  }
-  if (!chosen)
-    die(EXIT_FAILURE, "unknown command name `%s'", name);
-  return (chosen);
+  sc_help(cmdtab, stdout, argv + 1);
+  return (0);
 }
 
-static void version(FILE *fp)
+void version(FILE *fp)
 {
   pquis(fp, "$, Catacomb version " VERSION "\n");
 }
 
 static void usage(FILE *fp)
 {
-  pquis(fp, "Usage: $ [-k keyring] command [args]\n");
+  pquis(fp, "Usage: $ [-k KEYRING] COMMAND [ARGS]\n");
 }
 
-static void help(FILE *fp, char **argv)
+void help_global(FILE *fp)
 {
-  cmd *c;
-
-  if (*argv) {
-    c = findcmd(*argv);
-    fprintf(fp, "Usage: %s [-k keyring] %s\n", QUIS, c->usage);
-    if (c->help) {
-      fputc('\n', fp); 
-      fputs(c->help, fp);
-    }
-  } else {
-    version(fp);
-    fputc('\n', fp);
-    usage(fp);
-    fputs("\n\
+  usage(fp);
+  fputs("\n\
 Encrypt and decrypt files.\n\
-\n", fp);
-    for (c = cmdtab; c->name; c++)
-      fprintf(fp, "%s\n", c->usage);
-  }
+\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@ --- *
@@ -688,8 +780,7 @@ Encrypt and decrypt files.\n\
  *
  * Returns:    Zero if successful, nonzero otherwise.
  *
- * Use:                Signs or verifies signatures on lists of files.  Useful for
- *             ensuring that a distribution is unmolested.
+ * Use:                Encrypts or decrypts files.
  */
 
 int main(int argc, char *argv[])
@@ -720,7 +811,7 @@ int main(int argc, char *argv[])
       break;
     switch (i) {
       case 'h':
-       help(stdout, argv + optind);
+       sc_help(cmdtab, stdout, argv + optind);
        exit(0);
        break;
       case 'v':
@@ -749,7 +840,7 @@ int main(int argc, char *argv[])
 
   /* --- Dispatch to the correct subcommand handler --- */
 
-  return (findcmd(argv[0])->func(argc, argv));
+  return (findcmd(cmdtab, argv[0])->cmd(argc, argv));
 
 #undef f_bogus
 }
index f44bb33..c3da95b 100644 (file)
--- a/cc-enc.c
+++ b/cc-enc.c
@@ -1,6 +1,6 @@
 /* -*-c-*-
  *
- * $Id: cc-enc.c,v 1.1 2004/04/17 09:58:37 mdw Exp $
+ * $Id$
  *
  * Catcrypt data encoding
  *
@@ -260,7 +260,7 @@ static void pem_destroy(enc *e)
 
 /* --- Encoder table --- */
 
-static const encops enctab[] = {
+const encops enctab[] = {
   { "binary", "rb", "wb",
     bin_init, bin_init,
     bin_read, bin_write,
index 4f71d0d..8ba29fd 100644 (file)
--- a/cc-kem.c
+++ b/cc-kem.c
@@ -1,6 +1,6 @@
 /* -*-c-*-
  *
- * $Id: cc-kem.c,v 1.1 2004/04/17 09:58:37 mdw Exp $
+ * $Id$
  *
  * Catcrypt key-encapsulation
  *
@@ -324,11 +324,7 @@ static const kemops ec_decops = {
 
 /* --- The switch table --- */
 
-static const struct kemtab {
-  const char *name;
-  const kemops *encops;
-  const kemops *decops;
-} kemtab[] = {
+const struct kemtab kemtab[] = {
   { "rsa",     &rsa_encops,    &rsa_decops },
   { "dh",      &dh_encops,     &dh_decops },
   { "ec",      &ec_encops,     &ec_decops },
@@ -483,7 +479,7 @@ int setupkem(kem *k, dstr *d, gcipher **cx, gcipher **c, gmac **m)
   octet *kd;
   size_t n, cn, mn;
   ghash *h;
-  int rc = 0;
+  int rc = -1;
 
   h = GH_INIT(k->h);
   if (k->ops->doit(k, d, h))
diff --git a/cc-list.c b/cc-list.c
new file mode 100644 (file)
index 0000000..f5aef42
--- /dev/null
+++ b/cc-list.c
@@ -0,0 +1,73 @@
+/* -*-c-*-
+ *
+ * $Id$
+ *
+ * Emit lists of things in tables
+ *
+ * (c) 2004 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 <mLib/report.h>
+
+#include "cc.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @displaylists@ --- *
+ *
+ * Arguments:  @const struct listent *listtab@ = table of lists to show
+ *             @char *const argv[]@ = list of lists to show
+ *
+ * Returns:    Nonzero if anything failed.
+ *
+ * Use:                Dumps the named lists, or all of them.
+ */
+
+int displaylists(const struct listent *listtab, char *const argv[])
+{
+  const struct listent *li;
+  int i;
+  int rc = 0;
+
+  if (!argv || !*argv) {
+    for (li = listtab; li->name; li++)
+      li->list();
+  } else {
+    for (i = 0; argv[i]; i++) {
+      for (li = listtab; li->name; li++) {
+       if (strcmp(li->name, argv[i]) == 0) {
+         li->list();
+         goto cool;
+       }
+      }
+      moan("unknown list `%s'", argv[i]);
+      rc = 1;
+    cool:;
+    }
+  }
+  return (rc);
+}
+
+/*----- That's all, folks -------------------------------------------------*/
index 2bfe7e9..b7ce0b7 100644 (file)
--- a/cc-sig.c
+++ b/cc-sig.c
@@ -1,6 +1,6 @@
 /* -*-c-*-
  *
- * $Id: cc-sig.c,v 1.1 2004/04/17 09:58:37 mdw Exp $
+ * $Id$
  *
  * Catcrypt signatures
  *
@@ -517,12 +517,7 @@ static const sigops eckcdsa_vrf = {
 
 /* --- The switch table --- */
 
-static const struct sigtab {
-  const char *name;
-  const sigops *signops;
-  const sigops *verifyops;
-  const gchash *ch;
-} sigtab[] = {
+const struct sigtab sigtab[] = {
   { "rsapkcs1",        &rsap1_sig,     &rsap1_vrf,     &sha },
   { "rsapss",  &rsapss_sig,    &rsapss_vrf,    &sha },
   { "dsa",     &dsa_sig,       &dsa_vrf,       &sha },
diff --git a/cc-subcmd.c b/cc-subcmd.c
new file mode 100644 (file)
index 0000000..9af70e1
--- /dev/null
@@ -0,0 +1,109 @@
+/* -*-c-*-
+ *
+ * $Id$
+ *
+ * Subcommand infrastructure
+ *
+ * (c) 2004 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 <mLib/quis.h>
+#include <mLib/report.h>
+
+#include "cc.h"
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- @findcmd@ --- *
+ *
+ * Arguments:  @const cmd *cmds@ = pointer to command table
+ *             @const char *name@ = a command name
+ *
+ * Returns:    Pointer to the command structure.
+ *
+ * Use:                Looks up a command by name.  If the command isn't found, an
+ *             error is reported and the program is terminated.
+ */
+
+const cmd *findcmd(const cmd *cmds, const char *name)
+{
+  const cmd *c, *chosen = 0;
+  size_t sz = strlen(name);
+
+  for (c = cmds; c->name; c++) {
+    if (strncmp(name, c->name, sz) == 0) {
+      if (c->name[sz] == 0) {
+       chosen = c;
+       break;
+      } else if (chosen)
+       die(EXIT_FAILURE, "ambiguous command name `%s'", name);
+      else
+       chosen = c;
+    }
+  }
+  if (!chosen)
+    die(EXIT_FAILURE, "unknown command name `%s'", name);
+  return (chosen);
+}
+
+/* --- @sc_help@ --- *
+ *
+ * Arguments:  @const cmd *cmds@ = pointer to command table
+ *             @FILE *fp@ = output file handle
+ *             @char *const *argv@ = remaining arguments
+ *
+ * Returns:    ---
+ *
+ * Use:                Prints a help message, maybe with help about subcommands.
+ */
+
+void sc_help(const cmd *cmds, FILE *fp, char *const *argv)
+{
+  const cmd *c;
+
+  version(fp);
+  fputc('\n', fp);
+  if (!*argv) {
+    help_global(fp);
+    fputs("\n\
+The following commands are understood:\n\n",
+         fp);
+    for (c = cmds; c->name; c++)
+      fprintf(fp, "%s\n", c->usage);
+  } else {
+    while (*argv) {
+      c = findcmd(cmds, *argv);
+      fprintf(fp, "Usage: %s [-OPTIONS] %s\n", QUIS, c->usage);
+      if (c->help) {
+       fputc('\n', fp);        
+       pquis(fp, c->help);
+      }
+      argv++;
+      if (*argv) fputc('\n', fp);
+    }
+  }
+}
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/cc.h b/cc.h
index 7c2c092..24fc205 100644 (file)
--- a/cc.h
+++ b/cc.h
@@ -1,6 +1,6 @@
 /* -*-c-*-
  *
- * $Id: cc.h,v 1.1 2004/04/17 09:58:37 mdw Exp $
+ * $Id$
  *
  * Catcrypt common stuff
  *
@@ -37,6 +37,7 @@
 /*----- Header files ------------------------------------------------------*/
 
 #include <stdio.h>
+#include <string.h>
 
 #include <mLib/dstr.h>
 
@@ -67,6 +68,14 @@ typedef struct kemops {
   void (*destroy)(kem */*k*/);
 } kemops;
 
+struct kemtab {
+  const char *name;
+  const kemops *encops;
+  const kemops *decops;
+};
+
+extern const struct kemtab kemtab[];
+
 /* --- Signing --- */
 
 typedef struct sig {
@@ -85,6 +94,15 @@ typedef struct sigops {
   void (*destroy)(sig */*s*/);
 } sigops;
 
+struct sigtab {
+  const char *name;
+  const sigops *signops;
+  const sigops *verifyops;
+  const gchash *ch;
+};
+    
+extern const struct sigtab sigtab[];
+
 /* --- Data encoding --- */
 
 typedef struct enc {
@@ -104,6 +122,8 @@ typedef struct encops {
   void (*destroy)(enc */*e*/);
 } encops;
 
+extern const encops enctab[];
+
 /*----- Functions provided ------------------------------------------------*/
 
 /* --- @getkem@ --- *
@@ -207,6 +227,112 @@ extern enc *initenc(const encops */*eo*/, FILE */*fp*/,
 
 extern void freeenc(enc */*e*/);
 
+/* --- @LIST(STRING, FP, END-TEST, NAME-EXPR)@ --- *
+ *
+ * Produce list of things.  Requires @i@ and @w@ variables in scope.
+ * END-TEST and NAME-EXPR are in terms of @i@.
+ */
+
+#define LIST(what, fp, end, name) do {                                 \
+  fputs(what ":\n  ", fp);                                             \
+  w = 2;                                                               \
+  for (i = 0; end; i++) {                                              \
+    if (w == 2)                                                                \
+      w += strlen(name);                                               \
+    else {                                                             \
+      if (strlen(name) + w > 76) {                                     \
+       fputs("\n  ", fp);                                              \
+       w = 2 + strlen(name);                                           \
+      } else {                                                         \
+       fputc(' ', fp);                                                 \
+       w += strlen(name) + 1;                                          \
+      }                                                                        \
+    }                                                                  \
+    fputs(name, fp);                                                   \
+  }                                                                    \
+  fputc('\n', fp);                                                     \
+} while (0)
+
+#define STDLISTS(LI)                                                   \
+  LI("Hash functions", hash,                                           \
+     ghashtab[i], ghashtab[i]->name)                                   \
+  LI("Encryption schemes", enc,                                                \
+     gciphertab[i], gciphertab[i]->name)                               \
+  LI("Message authentication schemes", mac,                            \
+     gmactab[i], gmactab[i]->name)                                     \
+  LI("Elliptic curves", ec,                                            \
+     ectab[i].name, ectab[i].name)                                     \
+  LI("Diffie-Hellman groups", dh,                                      \
+     ptab[i].name, ptab[i].name)
+
+#define LIDECL(text, tag, test, name)                                  \
+  static void show_##tag(void);
+
+#define LIDEF(text, tag, test, name)                                   \
+  static void show_##tag(void)                                         \
+  {                                                                    \
+    unsigned i, w;                                                     \
+    LIST(text, stdout, test, name);                                    \
+  }
+
+#define LIENT(text, tag, test, name)                                   \
+  { #tag, show_##tag },
+
+struct listent {
+  const char *name;
+  void (*list)(void);
+};
+
+#define MAKELISTTAB(listtab, LISTS)                                    \
+  LISTS(LIDECL)                                                                \
+  static const struct listent listtab[] = {                            \
+    LISTS(LIENT)                                                       \
+    { 0, 0 }                                                           \
+  };                                                                   \
+  LISTS(LIDEF)
+
+extern int displaylists(const struct listent */*listtab*/,
+                       char *const /*argv*/[]);
+
+/*----- Subcommand dispatch -----------------------------------------------*/
+
+typedef struct cmd {
+  const char *name;
+  int (*cmd)(int /*argc*/, char */*argv*/[]);
+  const char *usage;
+  const char *help;
+} cmd;
+
+extern void version(FILE */*fp*/);
+extern void help_global(FILE */*fp*/);
+
+/* --- @findcmd@ --- *
+ *
+ * Arguments:  @const cmd *cmds@ = pointer to command table
+ *             @const char *name@ = a command name
+ *
+ * Returns:    Pointer to the command structure.
+ *
+ * Use:                Looks up a command by name.  If the command isn't found, an
+ *             error is reported and the program is terminated.
+ */
+
+const cmd *findcmd(const cmd */*cmds*/, const char */*name*/);
+
+/* --- @sc_help@ --- *
+ *
+ * Arguments:  @const cmd *cmds@ = pointer to command table
+ *             @FILE *fp@ = output file handle
+ *             @char *const *argv@ = remaining arguments
+ *
+ * Returns:    ---
+ *
+ * Use:                Prints a help message, maybe with help about subcommands.
+ */
+
+extern void sc_help(const cmd */*cmds*/, FILE */*fp*/,
+                   char *const */*argv*/);
+
 /*----- That's all, folks -------------------------------------------------*/
 
 #ifdef __cplusplus
diff --git a/cookie.1 b/cookie.1
new file mode 100644 (file)
index 0000000..61d9726
--- /dev/null
+++ b/cookie.1
@@ -0,0 +1,269 @@
+.\" -*-nroff-*-
+.de hP
+.IP
+.ft B
+\h'-\w'\\$1\ 'u'\\$1\ \c
+.ft P
+..
+.ie t \{\
+.  if \n(.g \{\
+.    fam P
+.  \}
+.\}
+.ie t .ds o \(bu
+.el .ds o o
+.
+.TH cookie 1 "5 June 1999" "Straylight/Edgeware" "Catacomb cryptographic library"
+.SH NAME
+cookie \- generate and validate cryptographic cookies
+.SH SYNOPSIS
+.B cookie
+.RB [ \-k
+.IR keyring ]
+.I command
+.PP
+where command is one of:
+.PP
+.B help
+.RI [ command ...]
+.br
+.B show
+.RI [ item ...]
+.br
+.B generate
+.RB [ \-b
+.IR bits ]
+.RB [ \-e
+.IR expire ]
+.RB [ \-k
+.IR tag ]
+.I data
+.br
+.B verify
+.RB [ \-fquv ]
+.RB [ \-b
+.IR bits ]
+.RB [ \-m
+.IR bits ]
+.I cookie
+.RI [ data ]
+.SH DESCRIPTION
+The
+.B cookie
+program generates timestamped cryptographic cookies which can be handed
+out to clients and later validated.  It provides two subcommands: one to
+create a new cookie, and one to ensure that a particular cookie is
+valid.
+.SS "Global options"
+Before the command name,
+.I "global options"
+may be given.  The following global options are supported:
+.TP
+.BR "\-h, \-\-help " [ \fIcommand ...]
+Displays help text for
+.B cookie
+on standard output and exits successfullly.  With command names, gives
+help on each command.
+.TP
+.B "\-v, \-\-version"
+Displays
+.BR cookie 's
+version number on standard output and exits successfullly.
+.TP
+.B "\-u, \-\-usage"
+Displays an unhelpfully terse usage summary on standard output and exits
+successfully.
+.TP
+.BI "\-k, \-\-keyring " file
+Use
+.I file
+as the keyring file rather than the default, which 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"
+The message authentication algorithm used to tag and verify cookies is
+described by an attribute on the key, or its type.  If the key has a
+.B mac
+attribute, then that is taken to be the name of the MAC algorithm to
+use; otherwise the key must have the type
+.BR cookie- \c
+.IR mac .
+.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 cookie 's
+options; with arguments, it describes the named subcommands.
+.SS show
+The
+.B show
+command prints various lists of tokens understood by
+.BR cookie .
+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
+cookie 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 mac
+The message authentication algorithms which can be used in a key's
+.B mac
+attribute.
+.SS generate
+The
+.B generate
+command creates a new cookie.  The command understands the following
+options:
+.TP
+.BI "\-b, \-\-bits " bits
+Specifies the size of the cryptographic token to generate.  The default
+token size is 32 bits.
+.TP
+.BI "\-e, \-\-expire " expire
+The expiry date for the cookie.  This may be the string
+.RB ` forever '
+if the cookie should never expire, or any date acceptable to the
+.BR getdate (3)
+library function, e.g.,
+.RB ` "+2 weeks" '.
+It is an error to ask for a non-expiring cookie to be created using a
+key which will expire.
+.TP
+.BI "\-k, \-\-key " tag
+Use the key named
+.I tag
+to authenticate the cookie; the default key is named
+.BR cookie .
+.PP
+If a
+.I data
+argument is supplied, then an identical argument must be supplied to the
+.B verify
+command if the cookie is to be accepted as valid.  The data will usually
+be some way of identifying the cookie's recipient, e.g., an email
+address. 
+.PP
+The generated cookie is written to standard output, followed by a
+newline.  Cookies are not architecture-specific.
+.SS verify
+The
+.B verify
+command checks a cookie for validity.  It accepts the following
+options:
+.TP
+.BI "\-b, \-\-bits " bits
+Only accept a cookie with a cryptographic token exactly
+.I bits
+bits long.
+.TP
+.BI "\-m, \-\-min\-bits " bits
+Only accept a cookie with a cryptographic token at least
+.I bits
+bits long.  The default is to accept any token with size 32 bits or
+more.
+.TP
+.B "\-f, \-\-forever"
+Allow non-expiring cookies.  Without this option, cookies which never
+expire are automatically rejected by
+.BR cookie .
+.TP
+.B "\-u, \-\-utc"
+Display expiry time as UTC rather than using the local time zone.
+.TP
+.B "\-q, \-\-quiet"
+Make
+.B cookie
+more quiet.  Repeat for a greater effect.
+.TP
+.B "\-v, \-\-verbose"
+Make
+.B cookie
+more verbose.  Repeat for a greater effect.
+.PP
+The argument
+.I cookie
+is the cookie to check.  The cookie may have whitespace before, after
+or within it.
+.PP
+If
+.I data
+is specified, it must exactly match the data passed to
+.B generate
+if the cookie is to be accepted.
+.PP
+Each line of text emitted by the
+.B verify
+command begins with a status indicator, which is one of the following:
+.TP
+.B INFO
+This line provides possibly useful information about the cookie.
+.B INFO
+lines are displayed while
+.B cookie
+is working, before it's made its mind up about whether to reject the
+cookie.  The remainder of the line contains the information gleaned.
+.TP
+.B FAIL
+The cookie is invalid.  The remainder of the line is a human-readable
+error message giving a reason for rejecting the cookie.
+.TP
+.B OK
+The cookie is valid.
+.PP
+The
+.B \-q
+and
+.B \-v
+flags control the amount of output that
+.B cookie
+generates.  By default, only
+.B OK
+and
+.B FAIL
+lines are generated.  One
+.B \-v
+option shows the cookie's keyid and expiry time.  Two
+.B \-v
+options also show the authentication token width.  A
+.B \-q
+option suppresses all output, or cancels out a previous
+.BR \-v .
+.PP
+Regardless of how much output is generated,
+.B cookie
+exits with return code 0 if the cookie was accepted and return code 1 if
+validation failed for some reason.
+.SH "COOKIE FORMAT"
+Cookies are Base64-encoded binary objects.  The binary data consists of:
+.hP \*o
+A 32-bit keyid referring to the key with which the cookie was created.
+.hP \*o
+An expiry date, expressed as a 64-bit integer in the format returned by
+the
+.BR time (2)
+system call.
+.hP \*o
+The `authentication token', which is the leftmost bits of a MAC over the
+keyid and date and any extra data supplied to the
+.B generate
+command
+.PP
+The keyid and expiry date are stored in network (big-endian) byte order.
+.SH "SEE ALSO"
+.BR key (1).
+.SH "AUTHOR"
+Mark Wooding, <mdw@nsict.org>
diff --git a/cookie.c b/cookie.c
new file mode 100644 (file)
index 0000000..9b32389
--- /dev/null
+++ b/cookie.c
@@ -0,0 +1,692 @@
+/* -*-c-*-
+ *
+ * $Id$
+ *
+ * Generate and validate cryptographic cookies
+ *
+ * (c) 1999 Mark Wooding
+ */
+
+/*----- 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 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 General Public License for more details.
+ * 
+ * You should have received a copy of the GNU 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 <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <mLib/base64.h>
+#include <mLib/bits.h>
+#include <mLib/dstr.h>
+#include <mLib/mdwopt.h>
+#include <mLib/quis.h>
+#include <mLib/report.h>
+#include <mLib/sub.h>
+
+#include "cc.h"
+#include "key.h"
+#include "gmac.h"
+#include "getdate.h"
+
+/*----- Handy global state ------------------------------------------------*/
+
+static const char *keyfile = "keyring";
+
+/*----- Cookie format -----------------------------------------------------*/
+
+/* --- Cookie header structure (unpacked) --- */
+
+typedef struct cookie {
+  uint32 k;
+  time_t exp;
+} cookie;
+
+/* --- Size of a cookie header (packed) --- */
+
+#define COOKIE_SZ (4 + 8)
+
+/* --- @COOKIE_PACK@ --- *
+ *
+ * Arguments:  @p@ = pointer to destination buffer
+ *             @c@ = pointer to source cookie header block
+ *
+ * Use:                Packs a cookie header into an octet buffer in a machine-
+ *             independent way.
+ */
+
+#define COOKIE_PACK(p, c) do {                                         \
+  octet *_p = (octet *)(p);                                            \
+  const cookie *_c = (c);                                              \
+  STORE32(_p + 0, _c->k);                                              \
+  STORE32(_p + 4, ((_c->exp & ~MASK32) >> 16) >> 16);                  \
+  STORE32(_p + 8, _c->exp);                                            \
+} while (0)
+
+/* --- @COOKIE_UNPACK@ --- *
+ *
+ * Arguments:  @c@ = pointer to destination cookie header
+ *             @p@ = pointer to source buffer
+ *
+ * Use:                Unpacks a cookie header from an octet buffer into a
+ *             machine-specific but comprehensible structure.
+ */
+
+#define COOKIE_UNPACK(c, p) do {                                       \
+  cookie *_c = (c);                                                    \
+  const octet *_p = (const octet *)(p);                                        \
+  _c->k = LOAD32(_p + 0);                                              \
+  _c->exp = ((time_t)(((LOAD32(_p + 4) << 16) << 16) & ~MASK32) |      \
+            (time_t)LOAD32(_p + 8));                                   \
+} while (0)
+
+/*----- Useful shared functions -------------------------------------------*/
+
+/* --- @doopen@ --- *
+ *
+ * Arguments:  @key_file *f@ = pointer to key file block
+ *             @unsigned how@ = method to open file with
+ *
+ * Returns:    ---
+ *
+ * Use:                Opens a key file and handles errors by panicking
+ *             appropriately.
+ */
+
+static void doopen(key_file *f, unsigned how)
+{
+  if (key_open(f, keyfile, how, key_moan, 0)) {
+    die(EXIT_FAILURE, "couldn't open file `%s': %s",
+       keyfile, strerror(errno));
+  }
+}
+
+/* --- @doclose@ --- *
+ *
+ * Arguments:  @key_file *f@ = pointer to key file block
+ *
+ * Returns:    ---
+ *
+ * Use:                Closes a key file and handles errors by panicking
+ *             appropriately.
+ */
+
+static void doclose(key_file *f)
+{
+  switch (key_close(f)) {
+    case KWRITE_FAIL:
+      die(EXIT_FAILURE, "couldn't write file `%s': %s",
+         keyfile, strerror(errno));
+    case KWRITE_BROKEN:
+      die(EXIT_FAILURE, "keyring file `%s' broken: %s (repair manually)",
+         keyfile, strerror(errno));
+  }
+}
+
+/* --- @getmac@ --- *
+ *
+ * Arguments:  @key *k@ = key to use
+ *             @const char *app@ = application name
+ *
+ * Returns:    The MAC to use.
+ *
+ * Use:                Finds the right MAC for the given key.
+ */
+
+static gmac *getmac(key *k, const char *app)
+{
+  dstr t = DSTR_INIT;
+  dstr d = DSTR_INIT;
+  char *p = 0;
+  const char *q;
+  size_t n;
+  key_bin kb;
+  key_packdef kp;
+  const gcmac *cm;
+  int e;
+  gmac *m;
+
+  /* --- Set up --- */
+
+  key_fulltag(k, &t);
+
+  /* --- Pick out the right MAC --- */
+
+  n = strlen(app);
+  if ((q = key_getattr(0, k, "mac")) != 0) {
+    dstr_puts(&d, q);
+    p = d.buf;
+  } else if (strncmp(k->type, app, n) == 0 && k->type[n] == '-') {
+    dstr_puts(&d, k->type);
+    p = d.buf + n + 1;
+  } else
+    die(EXIT_FAILURE, "no MAC algorithm for key `%s'", t.buf);
+  if ((cm = gmac_byname(p)) == 0) {
+    die(EXIT_FAILURE, "MAC algorithm `%s' not found in key `%s'",
+       p, t.buf);
+  }
+
+  /* --- Unlock the key --- */
+
+  kp.kd.e = KENC_BINARY;
+  kp.p = &kb;
+  if ((e = key_unpack(&kp, &k->k, &t)) != 0) {
+    die(EXIT_FAILURE, "error unpacking key `%s': %s",
+       t.buf, key_strerror(e));
+  }
+
+  /* --- Make the MAC object --- */
+
+  if (keysz(kb.sz, cm->keysz) != kb.sz)
+    die(EXIT_FAILURE, "key %s has bad length (%lu) for MAC %s",
+       t.buf, (unsigned long)kb.sz, cm->name);
+  m = cm->key(kb.k, kb.sz);
+  key_unpackdone(&kp);
+  return (m);
+}
+
+/*----- Command implementation --------------------------------------------*/
+
+/* --- @cmd_gen@ --- */
+
+static int cmd_gen(int argc, char *argv[])
+{
+  key_file f;
+  key *k;
+  gmac *m;
+  ghash *h;
+  const char *tag = "cookie";
+  int err;
+  cookie c = { 0, KEXP_EXPIRE };
+  unsigned fl = 0;
+  int bits = 32;
+  const octet *t;
+  dstr d = DSTR_INIT;
+  octet buf[COOKIE_SZ];
+  base64_ctx b;
+  
+  /* --- Various useful flag bits --- */
+
+#define f_bogus 1u
+
+  /* --- Parse options for the subcommand --- */
+
+  for (;;) {
+    static struct option opt[] = {
+      { "bits",                OPTF_ARGREQ,    0,      'b' },
+      { "expire",      OPTF_ARGREQ,    0,      'e' },
+      { "key",         OPTF_ARGREQ,    0,      'k' },
+      { 0,             0,              0,      0 }
+    };
+    int i = mdwopt(argc, argv, "+b:e:i:t:", opt, 0, 0, 0);
+    if (i < 0)
+      break;
+
+    /* --- Handle the various options --- */
+
+    switch (i) {
+
+      /* --- Fetch a size in bits --- */
+
+      case 'b':
+       if (!(bits = atoi(optarg)) || bits % 8)
+         die(EXIT_FAILURE, "bad number of bits: `%s'", optarg);
+       break;
+
+      /* --- Fetch an expiry time --- */
+
+      case 'e':
+       if (strcmp(optarg, "forever") == 0)
+         c.exp = KEXP_FOREVER;
+       else if ((c.exp = get_date(optarg, 0)) == -1)
+         die(EXIT_FAILURE, "bad expiry date: `%s'", optarg);
+       break;
+
+      /* --- Fetch a key type --- */
+
+      case 'k':
+       tag = optarg;
+       break;
+
+      /* --- Other things are bogus --- */
+
+      default:
+       fl |= f_bogus;
+       break;
+    }
+  }
+
+  /* --- Various sorts of bogosity --- */
+
+  if (fl & f_bogus || optind + 1 < argc)
+    die(EXIT_FAILURE,
+       "Usage: generate [-b BITS] [-e TIME] [-k TAG] [DATA]");
+
+  /* --- Choose a default expiry time --- */
+
+  if (c.exp == KEXP_EXPIRE)
+    c.exp = time(0) + 7 * 24 * 60 * 60;
+
+  /* --- Open the key file and get the key --- */
+
+  doopen(&f, KOPEN_WRITE);
+  if ((k = key_bytag(&f, tag)) == 0) {
+    die(EXIT_FAILURE, "no key with tag `%s' in keyring `%s'",
+       tag, keyfile);
+  }
+
+  c.k = k->id;
+  if ((err = key_used(&f, k, c.exp)) != 0)
+    die(EXIT_FAILURE, "can't generate cookie: %s", key_strerror(err));
+  m = getmac(k, "cookie");
+  if (bits/8 > GM_CLASS(m)->hashsz) {
+    die(EXIT_FAILURE, "inapproriate bit length for `%s' MACs",
+       GM_CLASS(m)->name);
+  }
+
+  /* --- Store and MAC the cookie --- */
+
+  COOKIE_PACK(buf, &c);
+
+  h = GM_INIT(m);
+  GH_HASH(h, buf, sizeof(buf));
+  if (argv[optind])
+    GH_HASH(h, argv[optind], strlen(argv[optind]));
+  t = GH_DONE(h, 0);
+
+  /* --- Encode and emit the finished cookie --- */
+
+  base64_init(&b);
+  b.indent = "";
+  base64_encode(&b, buf, sizeof(buf), &d);
+  base64_encode(&b, t, bits/8, &d);
+  base64_encode(&b, 0, 0, &d);
+  DWRITE(&d, stdout);
+  fputc('\n', stdout);
+  DDESTROY(&d);
+  GH_DESTROY(h);
+  GM_DESTROY(m);
+
+  doclose(&f);
+  return (0);
+
+#undef f_bogus
+}
+
+/* --- @cmd_verify@ --- */
+
+static int cmd_verify(int argc, char *argv[])
+{
+  key_file f;
+  dstr d = DSTR_INIT;
+  unsigned fl = 0;
+  int bits = -1, minbits = 32;
+  int v = 1;
+  base64_ctx b;
+  gmac *m;
+  ghash *h;
+  cookie c;
+  key *k;
+  int cbits;
+  const octet *t;
+  time_t now = time(0);
+
+  /* --- Various useful flag bits --- */
+
+#define f_bogus 1u
+#define f_forever 2u
+#define f_utc 4u
+
+  /* --- Parse options for the subcommand --- */
+
+  for (;;) {
+    static struct option opt[] = {
+      { "bits",                OPTF_ARGREQ,    0,      'b' },
+      { "min-bits",    OPTF_ARGREQ,    0,      'm' },
+      { "forever",     0,              0,      'f' },
+      { "quiet",       0,              0,      'q' },
+      { "verbose",     0,              0,      'v' },
+      { "utc",         0,              0,      'u' },
+      { 0,             0,              0,      0 }
+    };
+    int i = mdwopt(argc, argv, "+b:m:fqvu", opt, 0, 0, 0);
+    if (i < 0)
+      break;
+
+    /* --- Handle the various options --- */
+
+    switch (i) {
+
+      /* --- Fetch a size in bits --- */
+
+      case 'b':
+       if (!(bits = atoi(optarg)) || bits % 8)
+         die(EXIT_FAILURE, "bad number of bits: `%s'", optarg);
+       break;
+      case 'm':
+       if (!(minbits = atoi(optarg)) || minbits % 8)
+         die(EXIT_FAILURE, "bad number of bits: `%s'", optarg);
+       break;  
+
+      /* --- Miscellaneous flags --- */
+
+      case 'f':
+       fl |= f_forever;
+       break;
+      case 'u':
+       fl |= f_utc;
+       break;
+      case 'q':
+       if (v > 0) v--;
+       break;
+      case 'v':
+       v++;
+       break;
+       
+      /* --- Other things are bogus --- */
+
+      default:
+       fl |= f_bogus;
+       break;
+    }
+  }
+
+  /* --- Various sorts of bogosity --- */
+
+  if (fl & f_bogus || optind == argc || optind + 2 < argc) {
+    die(EXIT_FAILURE,
+       "Usage: verify [-fuqv] [-b BITS] [-m BITS] COOKIE [DATA]");
+  }
+  doopen(&f, KOPEN_READ);
+
+  /* --- Decode the base64 wrapping --- */
+
+  base64_init(&b);
+  base64_decode(&b, argv[optind], strlen(argv[optind]), &d);
+  base64_decode(&b, 0, 0, &d);
+
+  if (d.len < COOKIE_SZ + 1) {
+    if (v) printf("FAIL cookie too small\n");
+    goto fail;
+  }
+
+  /* --- Extract the relevant details --- */
+
+  COOKIE_UNPACK(&c, d.buf);
+
+  if (v > 1) {
+    char buf[64];
+    if (c.exp == KEXP_FOREVER)
+      strcpy(buf, "forever");
+    else {
+      struct tm *tm;
+      const char *fmt;
+
+      if (fl & f_utc) {
+       tm = gmtime(&c.exp);
+       fmt = "%Y-%m-%d %H:%M:%S UTC";
+      } else {
+       tm = localtime(&c.exp);
+       fmt = "%Y-%m-%d %H:%M:%S %Z";
+      }
+      strftime(buf, sizeof(buf), fmt, tm);
+    }
+    printf("INFO keyid = %08lx; expiry = %s\n", (unsigned long)c.k, buf);
+  }
+
+  /* --- Check the authentication token width --- */
+
+  cbits = (d.len - COOKIE_SZ) * 8;
+  if (v > 2) printf("INFO authentication token width = %i bits\n", cbits);
+  if (bits == -1) {
+    if (cbits < minbits) {
+      if (v) printf("FAIL authentication token too narrow\n");
+      goto fail;
+    }
+  } else {
+    if (cbits != bits) {
+      if (v) printf("FAIL authentication token width doesn't match\n");
+      goto fail;
+    }
+  }
+  /* --- Get the key --- */
+
+  if ((k = key_byid(&f, c.k)) == 0) {
+    if (v) printf("FAIL keyid %08lx unavailable\n", (unsigned long)c.k);
+    goto fail;
+  }
+
+  /* --- Check that the cookie authenticates OK --- */
+
+  m = getmac(k, "cookie");
+  h = GM_INIT(m);
+  GH_HASH(h, d.buf, COOKIE_SZ);
+  if (argv[optind + 1])
+    GH_HASH(h, argv[optind + 1], strlen(argv[optind + 1]));
+  t = GH_DONE(h, 0);
+
+  if (memcmp(t, d.buf + COOKIE_SZ, cbits / 8) != 0) {
+    if (v) printf("FAIL bad authentication token\n");
+    goto fail;
+  }
+
+  /* --- See whether the cookie has expired --- */
+
+  if (c.exp == KEXP_FOREVER) {
+    if (!(fl & f_forever)) {
+      if (v) printf("FAIL forever cookies not allowed\n");
+      goto fail;
+    }
+    if (k->exp != KEXP_FOREVER) {
+      if (v) printf("FAIL cookie lasts forever but key will expire\n");
+      goto fail;
+    }
+  } else if (c.exp < now) {
+    if (v) printf("FAIL cookie has expired\n");
+    goto fail;
+  }
+
+  if (v) printf("OK\n");
+  key_close(&f);
+  GM_DESTROY(m);
+  GH_DESTROY(h);
+  dstr_destroy(&d);
+  return (0);
+
+fail:
+  key_close(&f);
+  dstr_destroy(&d);
+  return (1);
+
+#undef f_bogus
+#undef f_forever
+#undef f_utc
+}
+
+/*----- Main command table ------------------------------------------------*/
+
+static int cmd_help(int, char **);
+
+#define LISTS(LI)                                                      \
+  LI("Lists", list,                                                    \
+     listtab[i].name, listtab[i].name)                                 \
+  LI("Message authentication algorithms", mac,                         \
+     gmactab[i], gmactab[i]->name)
+
+MAKELISTTAB(listtab, LISTS)
+
+static int cmd_show(int argc, char *argv[])
+{
+  return (displaylists(listtab, argv + 1));
+}
+
+static cmd cmds[] = {
+  { "help", cmd_help, "help [COMMAND...]" },
+  { "show", cmd_show, "show [ITEM...]" },
+  { "generate", cmd_gen,
+    "generate [-b BITS] [-e TIME] [-k TAG] [DATA]", "\
+Options:\n\
+\n\
+-b, --bits=N           Use an N-bit token in the cookie.\n\
+-e, --expire=TIME      Make the cookie expire after TIME.\n\
+-k, --key=TAG          Use key TAG to create the token.\n\
+" },
+  { "verify", cmd_verify,
+    "verify [-fuqv] [-b BITS] [-m BITS] COOKIE [DATA]", "\
+Options:\n\
+\n\
+-b, --bits=N           Accept tokens exactly N bits long only.\n\
+-m, --min-bits=N       Accept tokens N bits long or more.\n\
+-f, --forever          Accept cookies which never expire.\n\
+-u, --utc              Output cookie expiry dates in UTC.\n\
+-q, --quiet            Produce less output while checking cookies.\n\
+-v, --verbose          Produce more output while checking cookies.\n\
+" },
+  { 0, 0, 0 }
+};
+
+static int cmd_help(int argc, char *argv[])
+{
+  sc_help(cmds, stdout, argv + 1);
+  return (0);
+}
+
+/*----- Main code ---------------------------------------------------------*/
+
+/* --- Helpful GNUy functions --- */
+
+static void usage(FILE *fp)
+{
+  fprintf(fp, "Usage: %s [-k KEYRING] COMMAND [ARGS]\n", QUIS);
+}
+
+void version(FILE *fp)
+{
+  fprintf(fp, "%s, Catacomb version " VERSION "\n", QUIS);
+}
+
+void help_global(FILE *fp)
+{
+  usage(fp);
+  fputs("\n\
+Generates and validates cryptographic cookies.  Command line options\n\
+recognized are:\n\
+\n\
+-h, --help [COMMAND]   Display this help text (or help for COMMAND).\n\
+-v, --version          Display version number.\n\
+-u, --usage            Display short usage summary.\n\
+\n\
+-k, --key-file=FILE    Read and write keys in FILE.\n",
+       fp);
+}
+
+/* --- @main@ --- *
+ *
+ * Arguments:  @int argc@ = number of command line arguments
+ *             @char *argv[]@ = array of arguments
+ *
+ * Returns:    Zero if OK, nonzero if not.
+ *
+ * Use:                Generates and validates cryptographic cookies.
+ */
+
+int main(int argc, char *argv[])
+{
+  unsigned f = 0;
+
+#define f_bogus 1u
+#define f_forever 2u
+
+  /* --- Initialize the library --- */
+
+  ego(argv[0]);
+  sub_init();
+
+  /* --- Options parsing --- */
+
+  for (;;) {
+    static struct option opt[] = {
+
+      /* --- Standard GNUy help options --- */
+
+      { "help",                0,              0,      'h' },
+      { "version",     0,              0,      'v' },
+      { "usage",       0,              0,      'u' },
+
+      /* --- Actual relevant options --- */
+
+      { "keyring",     OPTF_ARGREQ,    0,      'k' },
+
+      /* --- Magic terminator --- */
+
+      { 0,             0,              0,      0 }
+    };
+    int i = mdwopt(argc, argv, "+hvu k:", opt, 0, 0, 0);
+
+    if (i < 0)
+      break;
+    switch (i) {
+
+      /* --- Helpful GNUs --- */
+
+      case 'u':
+       usage(stdout);
+       exit(0);
+      case 'v':
+       version(stdout);
+       exit(0);
+      case 'h':
+       sc_help(cmds, stdout, argv + optind);
+       exit(0);
+
+      /* --- Real genuine useful options --- */
+
+      case 'k':
+       keyfile = optarg;
+       break;
+
+      /* --- Bogus things --- */
+
+      default:
+       f |= f_bogus;
+       break;
+    }
+  }
+
+  if ((f & f_bogus) || optind == argc) {
+    usage(stderr);
+    exit(EXIT_FAILURE);
+  }
+
+  /* --- Dispatch to appropriate command handler --- */
+
+  argc -= optind;
+  argv += optind;
+  optind = 0;
+  return (findcmd(cmds, argv[0])->cmd(argc, argv));
+
+#undef f_bogus
+#undef f_forever
+}    
+
+/*----- That's all, folks -------------------------------------------------*/
diff --git a/dsig.1 b/dsig.1
new file mode 100644 (file)
index 0000000..0d7c83b
--- /dev/null
+++ b/dsig.1
@@ -0,0 +1,512 @@
+.\" -*-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 dsig 1 "30 September 2004" "Straylight/Edgeware" "Catacomb cryptographic library"
+.SH NAME
+dsig \- compute and verify signatures on collections of files
+.SH SYNOPSIS
+.B dsig
+.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 [ \-0bqv ]
+.RB [ \-c
+.IR comment ]
+.RB [ \-k
+.IR tag ]
+.RB [ \-e
+.IR expire ]
+.br
+\h'8n'
+.RB [ \-f
+.IR file ]
+.RB [ \-o
+.IR output ]
+.br
+.B verify
+.RB [ \-qv ]
+.RI [ file ]
+.SH DESCRIPTION
+The
+.B dsig
+command signs and verifies signatures on a collection of files.  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 dsig '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"
+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 dsig- \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 dsig 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 dsig show hash
+for a list of supported hash functions.
+.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 dsig 's
+options; with arguments, it describes the named subcommands.
+.SS show
+The
+.B show
+command prints various lists of tokens understood by
+.BR dsig .
+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
+dsig 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 key's
+.B sig
+attribute.
+.TP
+.B hash
+The hash functions which can be used in a key's
+.B hash
+attribute.
+.SS sign
+The
+.B sign
+command creates a signature for a collection of files.  The default
+behaviour is to read a list of whitespace-separated file names (see
+below for the precise format) from standard input and write the
+an output file, containing hashes of the files and a digital signature
+made by the key
+.B dsig
+in the current keyring, to standard output, in plain text with binary
+values Base64-encoded.  It is intended to be used in conjunction with
+.BR find (1).
+This behaviour can be modified by specifying command-line options.
+.TP
+.B "\-0, \-\-null"
+Read null-terminated filenames, rather than whitespace-separated names.
+This is the recommended mode of operation if you have a
+.BR find (1)
+which understands the
+.B \-print0
+option.
+.TP
+.B "\-b, \-\-binary"
+Produce output in raw binary rather than the textual output.  This isn't
+a useful thing to do unless you're trying to debug
+.BR dsig .
+.TP
+.B "\-v, \-\-verbose"
+Makes
+.B dsig
+more verbose.  At present, this just means that it'll print the hashes
+of files that it comes across in hex.  (Use
+.BR hashsum (1)
+if this is the output you actually wanted.)
+.TP
+.B "\-q, \-\-quiet"
+Makes
+.B dsig
+less verbose.
+.TP
+.BI "\-c, \-\-comment " string
+Writes
+.I string
+as a comment in the output file.  The comment's integrity is protected
+by the signature.
+.TP
+.BI "\-f, \-\-file " name
+Read filenames from
+.I name
+instead of from standard input.
+.TP
+.BI "\-o, \-\-output " name
+Write output to
+.I name
+instead of to standard output.
+.TP
+.BI "\-k, \-\-key " tag
+Use the key named
+.I tag
+rather than the default
+.BR dsig .
+.TP
+.BI "\-e, \-\-expire " date
+Set the signature to expire at
+.IR date .
+The default is to expire 28 days from creation.  Use
+.B forever
+to make the signature not expire.
+.PP
+The whitespace-separated format for filenames allows quoting and
+escaping of strange characters.  The backslash
+.RB ` \e '
+can be used to escape whitespace, quotes, or other special characters
+(including itself), and to represent special characters using the
+standard C escape sequences
+.RB ` \ea ',
+.RB ` \eb ',
+.RB ` \ef ',
+.RB ` \en ',
+.RB ` \et ',
+and
+.RB ` \eb '.
+A filename can be quoted in
+.BR ` ... ',
+.BR ' ... '
+or
+.BR """" ... """".
+Whitespace within quotes is part of the filename.  The quotes must be at
+the beginning and end of the name.
+.SS verify
+The
+.B verify
+command will verify signatures made by the
+.B sign
+command.  With no arguments, it expects to read a text-format signature
+file from standard input; with an argument, it examines the file it
+names to see whether it's text or binary.
+.PP
+Command-line options provided are:
+.TP
+.B "\-v, \-\-verbose"
+Produce more informational output.  The default verbosity level is 1.
+.TP
+.B "\-q, \-\-quiet"
+Produce less information output.
+.PP
+Output is written to standard output in a machine-readable format.
+Formatting errors cause the program to write a diagnostic to standard
+error and exit nonzero as usual.  Lines begin with a keyword:
+.TP
+.BI "FAIL " reason
+An error prevented verification.
+.TP
+.BI "BAD " reason
+The signature is bad: some file had the wrong hash or the signature is
+invalid. 
+.TP
+.BI "WARN " reason
+.B dsig
+encountered a situation which may or may not invalidate the signature.
+.TP
+.BI "OK " message
+The signature verified correctly.
+.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.
+.B dsig
+exits zero if the signature was good.
+.hP 1.
+All
+.BR OK ,
+.B FAIL
+and
+.B WARN
+messages are printed.
+.hP 2.
+As for level 1; also
+.B BAD
+messages are printed describing reasons why the signature verification
+failed, and an
+.B INFO
+message is printed showing the signature file's comment if any.
+.hP 3.
+As for level 2; also
+.B INFO
+messages are shown listing the signing program's identification string,
+the signing key, the signature and expiry dates, and actual signature
+verification.
+.hP 4.
+As for level 3; also
+.B INFO
+messages are printed for each file covered, showing its name and hash.
+.SH "OUTPUT FORMAT"
+There are two output formats: textual and binary.  The hash used in the
+digital signature is always computed on the
+.I binary
+version of the data, regardless of the external representation.
+.SS "Textual format"
+Within the file, whitespace and comments between strings are ignored.  A
+comment begins with a hash
+.RB (` # ')
+and extends until the next newline.
+.PP
+Strings are either quoted or whitespace-delimited.  A string may be
+quoted by
+.BR ` ... ',
+.BR ' ... '
+or
+.BR """" ... """".
+The end-quote character can be backslash-escaped within the string.  An
+occurrence of the unescaped end-quote character terminates the string.
+A whitespace-delimited string is terminated by any unescaped whitespace
+character.  The C-language escape sequences
+.RB ` \ea ',
+.RB ` \eb ',
+.RB ` \ef ',
+.RB ` \en ',
+.RB ` \et ',
+and
+.RB ` \eb '
+are recognized within either kind of string.
+.PP
+Blocks within the file consist of sequences of strings.  The first
+string is a
+.I tag
+\(en a simple string ending in a colon
+.RB (` : ')
+\(en which describes the format of the remaining strings.
+.SS "Binary format"
+The file consists of a sequence of blocks, each of which begins with a
+tag byte.  The format of the test of the block depends on the tag.
+Strings are null-terminated; all integers are in network byte order.
+.PP
+A binary file always begins with an ident block, which has a tag of 0.
+.SS "Block types"
+The following block types are known.  They must appear in the order
+given, and except where noted must appear exactly once each.
+.TP
+.BR "ident: " (0)
+Identification string of the generating program.
+.BR "keyid: " (1)
+The signing key's id, as eight hex digits (text) or a 32-bit integer
+(binary).
+.TP
+.BR "comment: " (2)
+The comment string set with the
+.B \-c
+option to the
+.B sign
+command.  This block need not appear.
+.TP
+.BR "date: " (3)
+The date the signature was made.  In a text file, this has the form
+.IB yyyy-mm-dd 
+.IB hh:mm:ss
+.IR timezone ;
+in a binary file, it's a 64-bit integer representing the POSIX time.
+.TP
+.BR "expires: " (4)
+The expiry time of the signature, expressed as for
+.BR date: .
+A non-expiring signature is represented by the string
+.B forever
+in text files, or all-bits-set in binary.
+.TP
+.BR "file: " (5)
+A file hash.  In text, this is two strings which are the Base-64-encoded
+hash and the file name; in binary, this is a 16-bit hash length, the raw
+hash, and the null-terminated filename.  There can be any number of
+.B file:
+blocks.
+.TP
+.BR "signature: " (6)
+The signature.  In text, this is the Base-64-encoded signature; in
+binary, it is a 16-bit length followed by the binary signature.
+.PP
+The signature covers the
+.I binary
+representations of the file's
+.BR date: ,
+.B expires:
+and
+.B file:
+blocks.
+.SH "SEE ALSO"
+.BR key (1),
+.BR hashsum (1),
+.BR catcrypt (1),
+.BR keyring (5).
+.SH AUTHOR
+Mark Wooding, <mdw@nsict.org>
diff --git a/dsig.c b/dsig.c
index 2aeb9ef..c45d204 100644 (file)
--- a/dsig.c
+++ b/dsig.c
@@ -1,6 +1,6 @@
 /* -*-c-*-
  *
- * $Id: dsig.c,v 1.12 2004/04/17 09:58:37 mdw Exp $
+ * $Id$
  *
  * Verify signatures on distribuitions of files
  *
@@ -485,8 +485,13 @@ static void blob(block *b, dstr *d)
     case T_DATE:
     case T_EXPIRE:
       DENSURE(d, 8);
-      STORE32(d->buf + d->len, ((b->t & ~MASK32) >> 16) >> 16);
-      STORE32(d->buf + d->len + 4, b->t);
+      if (b->t == KEXP_FOREVER) {
+       STORE32(d->buf + d->len, 0xffffffff);
+       STORE32(d->buf + d->len + 4, 0xffffffff);
+      } else {
+       STORE32(d->buf + d->len, ((b->t & ~MASK32) >> 16) >> 16);
+       STORE32(d->buf + d->len + 4, b->t);
+      }
       d->len += 8;
       break;
     case T_KEYID:
@@ -737,7 +742,7 @@ static int sign(int argc, char *argv[])
     }
   }
   if (optind != argc || (f & f_bogus))
-    die(EXIT_FAILURE, "Usage: sign [-options]");
+    die(EXIT_FAILURE, "Usage: sign [-OPTIONS]");
 
   /* --- Locate the signing key --- */
 
@@ -924,7 +929,7 @@ static int verify(int argc, char *argv[])
   argc -= optind;
   argv += optind;
   if ((f & f_bogus) || argc > 1)
-    die(EXIT_FAILURE, "Usage: verify [-qv] [file]");
+    die(EXIT_FAILURE, "Usage: verify [-qv] [FILE]");
 
   /* --- Open the key file, and start reading the input file --- */
 
@@ -1011,7 +1016,7 @@ static int verify(int argc, char *argv[])
        break;
       case T_EXPIRE: {
        time_t now = time(0);
-       if (b.t < now) {
+       if (b.t != KEXP_FOREVER && b.t < now) {
          if (verb > 1)
            puts("BAD signature has expired");
          f |= f_bogus;
@@ -1088,16 +1093,29 @@ done:
 
 /*----- Main code ---------------------------------------------------------*/
 
-typedef struct cmd {
-  const char *name;
-  int (*func)(int /*argc*/, char */*argv*/[]);
-  const char *usage;
-  const char *help;
-} cmd;
+#define LISTS(LI)                                                      \
+  LI("Lists", list,                                                    \
+     listtab[i].name, listtab[i].name)                                 \
+  LI("Signature schemes", sig,                                         \
+     sigtab[i].name, sigtab[i].name)                                   \
+  LI("Hash functions", hash,                                           \
+     ghashtab[i], ghashtab[i]->name)
+
+MAKELISTTAB(listtab, LISTS)
+
+int cmd_show(int argc, char *argv[])
+{
+  return (displaylists(listtab, argv + 1));
+}
+
+static int cmd_help(int, char **);
 
 static cmd cmdtab[] = {
+  { "help", cmd_help, "help [COMMAND...]" },
+  { "show", cmd_show, "show [ITEM...]" },
   { "sign", sign,
-    "sign [-0bqv] [-c comment] [-k tag] [-e expire] [-f file] [-o output]",
+    "sign [-0bqv] [-c COMMENT] [-k TAG] [-e EXPIRE]\n\t\
+[-f FILE] [-o OUTPUT]",
     "\
 Options:\n\
 \n\
@@ -1112,7 +1130,7 @@ Options:\n\
 -e, --expire=TIME      The signature should expire after TIME.\n\
 " },
   { "verify", verify,
-    "verify [-qv] [file]", "\
+    "verify [-qv] [FILE]", "\
 Options:\n\
 \n\
 -q, --quiet            Produce fewer messages while working.\n\
@@ -1121,68 +1139,36 @@ Options:\n\
   { 0, 0, 0 }
 };
 
-/* --- @findcmd@ --- *
- *
- * Arguments:  @const char *name@ = a command name
- *
- * Returns:    Pointer to the command structure.
- *
- * Use:                Looks up a command by name.  If the command isn't found, an
- *             error is reported and the program is terminated.
- */
-
-static cmd *findcmd(const char *name)
+static int cmd_help(int argc, char **argv)
 {
-  cmd *c, *chosen = 0;
-  size_t sz = strlen(name);
-
-  for (c = cmdtab; c->name; c++) {
-    if (strncmp(name, c->name, sz) == 0) {
-      if (c->name[sz] == 0) {
-       chosen = c;
-       break;
-      } else if (chosen)
-       die(EXIT_FAILURE, "ambiguous command name `%s'", name);
-      else
-       chosen = c;
-    }
-  }
-  if (!chosen)
-    die(EXIT_FAILURE, "unknown command name `%s'", name);
-  return (chosen);
+  sc_help(cmdtab, stdout, argv + 1);
+  return (0);
 }
 
-static void version(FILE *fp)
+void version(FILE *fp)
 {
   pquis(fp, "$, Catacomb version " VERSION "\n");
 }
 
 static void usage(FILE *fp)
 {
-  pquis(fp, "Usage: $ [-k keyring] command [args]\n");
+  pquis(fp, "Usage: $ [-k KEYRING] COMMAND [ARGS]\n");
 }
 
-static void help(FILE *fp, char **argv)
+void help_global(FILE *fp)
 {
-  cmd *c;
-
-  if (*argv) {
-    c = findcmd(*argv);
-    fprintf(fp, "Usage: %s [-k keyring] %s\n", QUIS, c->usage);
-    if (c->help) {
-      fputc('\n', fp); 
-      fputs(c->help, fp);
-    }
-  } else {
-    version(fp);
-    fputc('\n', fp);
-    usage(fp);
-    fputs("\n\
+  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->usage);
-  }
+\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@ --- *
@@ -1224,7 +1210,7 @@ int main(int argc, char *argv[])
       break;
     switch (i) {
       case 'h':
-       help(stdout, argv + optind);
+       sc_help(cmdtab, stdout, argv + optind);
        exit(0);
        break;
       case 'v':
@@ -1253,7 +1239,7 @@ int main(int argc, char *argv[])
 
   /* --- Dispatch to the correct subcommand handler --- */
 
-  return (findcmd(argv[0])->func(argc, argv));
+  return (findcmd(cmdtab, argv[0])->cmd(argc, argv));
 
 #undef f_bogus
 }
index f500d25..5eb280b 100644 (file)
--- a/hashsum.1
+++ b/hashsum.1
@@ -15,6 +15,8 @@ hashsum \- compute and verify cryptographic checksums of files
 .RB [ \-f0ecbv ]
 .RB [ \-a
 .IR algorithm ]
+.RB [ \-E
+.IR encoding ]
 .IR files ...
 .SH DESCRIPTION
 The
@@ -52,6 +54,9 @@ successfully.
 .B "\-u, \-\-usage"
 Prints a brief usage summary to standard output and exits successfully.
 .TP
+.BR "\-l, \-\-list " [ \fIitem ...]
+Show lists of hash functions and encodings supported.
+.TP
 .BI "\-a, \-\-algorithm=" alg
 Use the hash algorithm
 .IR alg .
@@ -60,9 +65,20 @@ see
 .B "Hashing algorithms"
 below.
 .TP
-.B "\-l, \-\-list"
-Prints a space-separated list of available hashing algorithms to
-standard output and exits successfully.
+.BI "\-E, \-\-encoding=" encoding
+Use the given
+.I encoding
+to represent hashes in the output.  This is not interoperable with other
+programs, but it's handy, e.g., for building sha1 URNs.  The encodings
+recognized are
+.B hex 
+(the default),
+.B base64
+and
+.BR base32 .
+Type
+.B hashsum \-\-list enc
+for a list of supported encodings.
 .TP
 .B "\-f, \-\-files"
 Each input file is considered to be a list of filenames which should be
@@ -132,19 +148,19 @@ character.  Two directives are currently understood:
 Subsequent hashes in this file were generated using the algorithm
 .IR alg .
 .TP
+.BI "#encoding " encoding
+Subsequent hashes in this file are represented using the named
+.IR encoding .
+.TP
 .BI "#escape"
 Filenames in subsequence lines are written using the `escaped' format,
 described below.
 .PP
 A
 .I "file line"
-consists of a hash, in hexadecimal, followed by a space, a
+consists of a hash, in the requested encoding, followed by a space, a
 .IR flag ,
-and the filename.  If the current hash algorithm produces
-.IR n -bit
-output, there must be
-.IR n /4
-hex digits of hash in a file line.  The
+and the filename.  The
 .I flag
 is either a star
 .RB (` * ')
index 492eb9b..c9bb3ec 100644 (file)
--- a/hashsum.c
+++ b/hashsum.c
@@ -51,6 +51,7 @@
 #include <mLib/base64.h>
 
 #include "ghash.h"
+#include "cc.h"
 
 /*----- Static variables --------------------------------------------------*/
 
@@ -164,24 +165,24 @@ static size_t getb32(const char *p, octet *q, size_t sz, char **pp)
 
 /* --- Table --- */
 
-typedef struct encops {
+typedef struct encodeops {
   const char *name;
   void (*put)(const octet *, size_t, FILE *);
   size_t (*get)(const char *, octet *, size_t, char **);
-} encops;
+} encodeops;
 
-static const encops enctab[] = {
+static const encodeops encodingtab[] = {
   { "hex", puthex, gethex },
   { "base64", putb64, getb64 },
   { "base32", putb32, getb32 },
   { 0, 0, 0 }
 };
 
-static const encops *getenc(const char *ename)
+static const encodeops *getencoding(const char *ename)
 {
-  const encops *e;
+  const encodeops *e;
 
-  for (e = enctab; e->name; e++) {
+  for (e = encodingtab; e->name; e++) {
     if (strcmp(ename, e->name) == 0)
       return (e);
   }
@@ -431,7 +432,7 @@ static void putstring(FILE *fp, const char *p, unsigned raw)
 /*----- Guts --------------------------------------------------------------*/
 
 static int checkhash(const char *file, unsigned f,
-                    const gchash *gch, const encops *e)
+                    const gchash *gch, const encodeops *e)
 {
   int rc;
   FILE *fp;
@@ -468,10 +469,10 @@ static int checkhash(const char *file, unsigned f,
        xfree(buf);
        buf = xmalloc(2 * gch->hashsz);
       } else if (strcmp(q, "encoding") == 0) {
-       const encops *ee;
+       const encodeops *ee;
        if ((q = str_getword(&p)) == 0)
          continue;
-       if ((ee = getenc(q)) == 0)
+       if ((ee = getencoding(q)) == 0)
          continue;
        e = ee;
       } else if (strcmp(q, "escape") == 0)
@@ -531,7 +532,7 @@ static int checkhash(const char *file, unsigned f,
 }
 
 static int dohash(const char *file, unsigned f,
-                 const gchash *gch, const encops *e)
+                 const gchash *gch, const encodeops *e)
 {
   int rc = 0;
   octet *p = xmalloc(gch->hashsz);
@@ -557,13 +558,13 @@ static int dohash(const char *file, unsigned f,
 }
 
 static int dofile(const char *file, unsigned f,
-                 const gchash *gch, const encops *e)
+                 const gchash *gch, const encodeops *e)
 {
   return (f & f_check ? checkhash : dohash)(file, f, gch, e);
 }
 
 static int hashfiles(const char *file, unsigned f,
-                    const gchash *gch, const encops *e)
+                    const gchash *gch, const encodeops *e)
 {
   FILE *fp;
   dstr d = DSTR_INIT;
@@ -589,21 +590,21 @@ static int hashfiles(const char *file, unsigned f,
 }
 
 static int hashsum(const char *file, unsigned f,
-                  const gchash *gch, const encops *e)
+                  const gchash *gch, const encodeops *e)
 {
   return (f & f_files ? hashfiles : dofile)(file, f, gch, e);
 }
 
 /*----- Main driver -------------------------------------------------------*/
 
-static void version(FILE *fp)
+void version(FILE *fp)
 {
   pquis(fp, "$, Catacomb version " VERSION "\n");
 }
 
 static void usage(FILE *fp)
 {
-  pquis(fp, "Usage: $ [-f0ebcv] [-a algorithm] [files...]\n");
+  pquis(fp, "Usage: $ [-f0ebcv] [-a ALGORITHM] [-E ENC] [FILES...]\n");
 }
 
 static void help(FILE *fp, const gchash *gch)
@@ -617,6 +618,7 @@ Generates or checks message digests on files.  Options available:\n\
 -h, --help             Display this help message.\n\
 -V, --version          Display program's version number.\n\
 -u, --usage            Display a terse usage message.\n\
+-l, --list [ITEM...]   Show known hash functions and/or encodings.\n\
 \n\
 -a, --algorithm=ALG    Use the message digest algorithm ALG.\n\
 -E, --encoding=ENC     Represent hashes using encoding ENC.\n\
@@ -635,11 +637,18 @@ For a list of hashing algorithms and encodings, type `$ --list'.\n\
     fprintf(fp, "The default message digest algorithm is %s.\n", gch->name);
 }
 
+#define LISTS(LI)                                                      \
+  LI("Lists", list, listtab[i].name, listtab[i].name)                  \
+  LI("Hash functions", hash, ghashtab[i], ghashtab[i]->name)           \
+  LI("Encodings", enc, encodingtab[i].name, encodingtab[i].name)
+
+MAKELISTTAB(listtab, LISTS)
+
 int main(int argc, char *argv[])
 {
   unsigned f = 0;
   const gchash *gch = 0;
-  const encops *e = &enctab[0];
+  const encodeops *e = &encodingtab[0];
   int rc;
 
   /* --- Initialization --- */
@@ -699,29 +708,15 @@ int main(int argc, char *argv[])
       case 'u':
        usage(stdout);
        exit(0);
+      case 'l':
+       exit(displaylists(listtab, argv + optind));
       case 'a':
        if ((gch = gethash(optarg)) == 0)
          die(EXIT_FAILURE, "unknown hash algorithm `%s'", optarg);
        f |= f_oddhash;
        break;
-      case 'l': {
-       unsigned j;
-       printf("Algorithms: ");
-       for (j = 0; ghashtab[j]; j++) {
-         if (j) fputc(' ', stdout);
-         printf("%s", ghashtab[j]->name);
-       }
-       fputc('\n', stdout);
-       printf("Encodings: ");
-       for (j = 0; enctab[j].name; j++) {
-         if (j) fputc(' ', stdout);
-         printf("%s", enctab[j].name);
-       }
-       fputc('\n', stdout);
-       exit(0);
-      } break;
       case 'E':
-       if ((e = getenc(optarg)) == 0)
+       if ((e = getencoding(optarg)) == 0)
          die(EXIT_FAILURE, "unknown encoding `%s'", optarg);
        f |= f_oddenc;
        break;
diff --git a/key.1 b/key.1
index 5c7bee6..07237a7 100644 (file)
--- a/key.1
+++ b/key.1
 .  ds ue
 .  ds *b \fIbeta\fP
 .\}
+.de VS
+.sp 1
+.RS
+.nf
+.ft B
+..
+.de VE
+.ft R
+.fi
+.RE
+.sp 1
+..
 .TH key 1 "5 June 1999" "Straylight/Edgeware" "Catacomb cryptographic library"
 .SH NAME
 key \- simple key management system
@@ -29,6 +41,12 @@ where
 .I command
 is one of:
 .PP
+.B help
+.RI [ command ...]
+.br
+.B show
+.RI [ item ...]
+.br
 .B add
 .RB [ \-lqrLS ]
 .RB [ \-a
@@ -41,6 +59,14 @@ is one of:
 .IR tag ]
 .br
 \h'8n'
+.RB [ \-A
+.IR seed-alg ]
+.RB [ \-s
+.IR seed ]
+.RB [ \-n
+.IR bits ]
+.br
+\h'8n'
 .RB [ \-e
 .IR expire ]
 .RB [ \-t
@@ -112,12 +138,12 @@ Before the command name,
 .I "global options"
 may be given.  The following global options are supported:
 .TP
-.BR "\-h, \-\-help " [ \fIcommand ]
+.BR "\-h, \-\-help " [ \fIcommand ...]
 Writes a brief summary of
 .BR key 's
 various options to standard output, and
-returns a successful exit status.  With a command name, gives help on
-that command.
+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
@@ -246,6 +272,60 @@ arbitrary strings, except they may not contain null bytes.  Some
 attributes may have meaning for particular applications or key types;
 others may be assigned global meanings in future.
 .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 key 's
+options; with arguments, it describes the named subcommands.
+.SS show
+The
+.B show
+command prints various lists of tokens understood by
+.BR key .
+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
+key 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 hash
+The hash functions which can be used with the
+.B fingerprint
+command.
+.TP
+.B ec
+The built-in elliptic curves which can be used with the
+.B add \-a ec
+command.
+.TP
+.B dh
+The built-in Diffie-Hellman groups which can be used with the
+.B add \-a dh
+command.
+.TP
+.B keygen
+The key-generation algorithms which are acceptable to the
+.B \-a
+option of the
+.B add
+command.
+.TP
+.B seed
+The pseudorandom generators which are acceptable to the
+.B \-s
+option of the
+.B add
+command.
 .SS add
 The
 .B add
@@ -255,7 +335,9 @@ accepts the following options:
 .BI "\-a, \-\-algorithm " alg
 Selects a key generation algorithm.  The default algorithm is
 .BR binary ;
-the different algorithms are described below.
+the different algorithms are described below.  The command
+.B key show keygen
+lists the recognized key-generation algorithms.
 .TP
 .BI "\-b, \-\-bits " bits
 The length of the key to generate, in bits.  The default, if this option
@@ -270,6 +352,51 @@ Selects a key containing parameter values to copy.  Not all
 key-generation algorithms allow the use of shared parameters.  A new key
 also inherits attributes from its parameter key.
 .TP
+.BI "\-A, \-\-seedalg " seed-alg
+Use the deterministic random number generator algorithm
+.I seed-alg
+to generate the key.  Use
+.I before
+the
+.B \-s
+or
+.B \-n
+options; without one of these,
+.B \-A
+has no effect.  The default algorithm is
+.BR rmd160-mgf .
+The command
+.B key show seed
+shows a list of recognized seeding algorithms.  The seeding algorithm
+used to generate a key is recorded as the key's
+.B seedalg
+attribute.
+.TP
+.BI "\-s, \-\-seed " seed
+Generate the key deterministically using the given
+.IR seed ,
+which should be a Base64-encoded binary string.  This is mainly useful
+for parameters keys (types
+.BR dsa-param
+and
+.BR dh-param ),
+to demonstrate that a set of parameters has been generated in an honest
+fashion.  The
+.B dsarand
+generation algorithm can be used to generate
+.B dsa-param
+keys as required by FIPS186.  The requested seed is recorded,
+Base64-encoded, as the new key's
+.B seed
+attribute.
+.TP
+.BI "\-n, \-\-newseed " bits
+Generate a new seed, with the given length in
+.IR bits .
+The generated seed is recorded, Base64-encoded, as the new key's
+.B seed
+attribute.
+.TP
 .BI "\-e, \-\-expire " expire
 The expiry date for the generated key.  This may be the string
 .RB ` forever '
index 13b8784..0b54df2 100644 (file)
--- a/keyutil.c
+++ b/keyutil.c
@@ -1,6 +1,6 @@
 /* -*-c-*-
  *
- * $Id: keyutil.c,v 1.25 2004/04/21 00:38:22 mdw Exp $
+ * $Id$
  *
  * Simple key manager program
  *
@@ -64,6 +64,7 @@
 #include "ptab.h"
 #include "rsa.h"
 
+#include "cc.h"
 #include "sha-mgf.h"
 #include "sha256-mgf.h"
 #include "sha224-mgf.h"
@@ -571,10 +572,8 @@ static void alg_dhparam(keyopts *k)
       qd_parse qd;
       
       if (strcmp(k->curve, "list") == 0) {
-       const pentry *pe;
-       printf("Built-in prime groups:\n");
-       for (pe = ptab; pe->name; pe++)
-         printf("  %s\n", pe->name);
+       unsigned i, w;
+       LIST("Built-in prime groups", stdout, ptab[i].name, ptab[i].name);
        exit(0);
       }
       qd.p = k->curve;
@@ -711,10 +710,9 @@ static void alg_ecparam(keyopts *k)
 
     if (!k->bits) k->bits = 256;
     if (k->curve && strcmp(k->curve, "list") == 0) {
-      const ecentry *ee;
-      printf("Built-in elliptic curves:\n");
-      for (ee = ectab; ee->name; ee++)
-       printf("  %s\n", ee->name);
+      unsigned i, w;
+      LIST("Built-in elliptic curves", stdout,
+          ectab[i].name, ectab[i].name);
       exit(0);
     }
     if (!k->curve) {
@@ -1022,7 +1020,7 @@ static int cmd_add(int argc, char *argv[])
 
   if ((k.f & f_bogus) || optind + 1 > argc) {
     die(EXIT_FAILURE,
-       "Usage: add [options] type [attr...]");
+       "Usage: add [OPTIONS] TYPE [ATTR...]");
   }
   if (key_chkident(argv[optind]))
     die(EXIT_FAILURE, "bad key type `%s'", argv[optind]);
@@ -1389,7 +1387,7 @@ static int cmd_list(int argc, char *argv[])
   }
 
   if (o.f & f_bogus)
-    die(EXIT_FAILURE, "Usage: list [-uqv] [-f filter] [tag...]");
+    die(EXIT_FAILURE, "Usage: list [-uqv] [-f FILTER] [TAG...]");
 
   /* --- Open the key file --- */
 
@@ -1447,7 +1445,7 @@ static int cmd_expire(int argc, char *argv[])
   int rc = 0;
 
   if (argc < 2)
-    die(EXIT_FAILURE, "Usage: expire tag...");
+    die(EXIT_FAILURE, "Usage: expire TAG...");
   doopen(&f, KOPEN_WRITE);
   for (i = 1; i < argc; i++) {
     if ((k = key_bytag(&f, argv[i])) != 0)
@@ -1471,7 +1469,7 @@ static int cmd_delete(int argc, char *argv[])
   int rc = 0;
 
   if (argc < 2)
-    die(EXIT_FAILURE, "Usage: delete tag...");
+    die(EXIT_FAILURE, "Usage: delete TAG...");
   doopen(&f, KOPEN_WRITE);
   for (i = 1; i < argc; i++) {
     if ((k = key_bytag(&f, argv[i])) != 0)
@@ -1493,7 +1491,7 @@ static int cmd_setattr(int argc, char *argv[])
   key *k;
 
   if (argc < 3)
-    die(EXIT_FAILURE, "Usage: setattr tag attr...");
+    die(EXIT_FAILURE, "Usage: setattr TAG ATTR...");
   doopen(&f, KOPEN_WRITE);
   if ((k = key_bytag(&f, argv[1])) == 0)
     die(EXIT_FAILURE, "key `%s' not found", argv[1]);
@@ -1561,7 +1559,7 @@ static int cmd_finger(int argc, char *argv[])
 
   argv += optind; argc -= optind;
   if (rc)
-    die(EXIT_FAILURE, "Usage: fingerprint [-f filter] [tag...]");
+    die(EXIT_FAILURE, "Usage: fingerprint [-f FILTER] [TAG...]");
 
   doopen(&f, KOPEN_READ);
 
@@ -1594,7 +1592,7 @@ static int cmd_comment(int argc, char *argv[])
   int err;
 
   if (argc < 2 || argc > 3)
-    die(EXIT_FAILURE, "Usage: comment tag [comment]");
+    die(EXIT_FAILURE, "Usage: comment TAG [COMMENT]");
   doopen(&f, KOPEN_WRITE);
   if ((k = key_bytag(&f, argv[1])) == 0)
     die(EXIT_FAILURE, "key `%s' not found", argv[1]);
@@ -1634,7 +1632,7 @@ static int cmd_tag(int argc, char *argv[])
 
   argv += optind; argc -= optind;
   if (argc < 1 || argc > 2 || rc)
-    die(EXIT_FAILURE, "Usage: tag [-r] tag [new-tag]");
+    die(EXIT_FAILURE, "Usage: tag [-r] TAG [NEW-TAG]");
   doopen(&f, KOPEN_WRITE);
   if (flags & f_retag) {
     if ((k = key_bytag(&f, argv[1])) != 0 && strcmp(k->tag, argv[1]) == 0)
@@ -1658,7 +1656,7 @@ static int cmd_lock(int argc, char *argv[])
   dstr d = DSTR_INIT;
 
   if (argc != 2)
-    die(EXIT_FAILURE, "Usage: lock qtag");
+    die(EXIT_FAILURE, "Usage: lock QTAG");
   doopen(&f, KOPEN_WRITE);
   if (key_qtag(&f, argv[1], &d, &k, &kd))
     die(EXIT_FAILURE, "key `%s' not found", argv[1]);
@@ -1681,7 +1679,7 @@ static int cmd_unlock(int argc, char *argv[])
   dstr d = DSTR_INIT;
 
   if (argc != 2)
-    die(EXIT_FAILURE, "Usage: unlock qtag");
+    die(EXIT_FAILURE, "Usage: unlock QTAG");
   doopen(&f, KOPEN_WRITE);
   if (key_qtag(&f, argv[1], &d, &k, &kd))
     die(EXIT_FAILURE, "key `%s' not found", argv[1]);
@@ -1728,7 +1726,7 @@ static int cmd_extract(int argc, char *argv[])
 
   argv += optind; argc -= optind;
   if (rc || argc < 1)
-    die(EXIT_FAILURE, "Usage: extract [-f filter] file [tag...]");
+    die(EXIT_FAILURE, "Usage: extract [-f FILTER] FILE [TAG...]");
   if (strcmp(*argv, "-") == 0)
     fp = stdout;
   else if (!(fp = fopen(*argv, "w"))) {
@@ -1764,7 +1762,7 @@ static int cmd_tidy(int argc, char *argv[])
 {
   key_file f;
   if (argc != 1)
-    die(EXIT_FAILURE, "usage: tidy");
+    die(EXIT_FAILURE, "Usage: tidy");
   doopen(&f, KOPEN_WRITE);
   f.f |= KF_MODIFIED; /* Nasty hack */
   doclose(&f);
@@ -1779,7 +1777,7 @@ static int cmd_merge(int argc, char *argv[])
   FILE *fp;
 
   if (argc != 2)
-    die(EXIT_FAILURE, "Usage: merge file");
+    die(EXIT_FAILURE, "Usage: merge FILE");
   if (strcmp(argv[1], "-") == 0)
     fp = stdin;
   else if (!(fp = fopen(argv[1], "r"))) {
@@ -1793,25 +1791,86 @@ static int cmd_merge(int argc, char *argv[])
   return (0);
 }
 
+/* --- @cmd_show@ --- */
+
+#define LISTS(LI)                                                      \
+  LI("Lists", list,                                                    \
+     listtab[i].name, listtab[i].name)                                 \
+  LI("Hash functions", hash,                                           \
+     ghashtab[i], ghashtab[i]->name)                                   \
+  LI("Elliptic curves", ec,                                            \
+     ectab[i].name, ectab[i].name)                                     \
+  LI("Diffie-Hellman groups", dh,                                      \
+     ptab[i].name, ptab[i].name)                                       \
+  LI("Key-generation algorithms", keygen,                              \
+     algtab[i].name, algtab[i].name)                                   \
+  LI("Random seeding algorithms", seed,                                        \
+     seedtab[i].p, seedtab[i].p)
+
+MAKELISTTAB(listtab, LISTS)
+
+static int cmd_show(int argc, char *argv[])
+{
+  return (displaylists(listtab, argv + 1));
+}
+
 /*----- Main command table ------------------------------------------------*/
 
-static struct cmd {
-  const char *name;
-  int (*cmd)(int /*argc*/, char */*argv*/[]);
-  const char *usage;
-  const char *help;
-} cmds[] = {
+static int cmd_help(int argc, char *argv[]);
+
+static cmd cmds[] = {
+  { "help", cmd_help, "help [COMMAND...]" },
+  { "show", cmd_show, "show [ITEM...]" },
+  { "list", cmd_list, "list [-uqv] [-f FILTER] [TAG...]", "\
+Options:\n\
+\n\
+-u, --utc              Display expiry times etc. in UTC, not local time.\n\
+-q, --quiet            Show less information.\n\
+-v, --verbose          Show more information.\n\
+" },
+  { "fingerprint", cmd_finger, "fingerprint [-f FILTER] [TAG...]", "\
+Options:\n\
+\n\
+-f, --filter=FILT      Only hash key components matching FILT.\n\
+-a, --algorithm=HASH   Use the named HASH algorithm.\n\
+                         ($ show hash for list.)\n\
+" },
+  { "extract", cmd_extract, "extract [-f FILTER] FILE [TAG...]", "\
+Options:\n\
+\n\
+-f, --filter=FILT      Only extract key components matching FILT.\n\
+" },
+  { "merge", cmd_merge, "merge FILE" },
+  { "expire", cmd_expire, "expire TAG..." },
+  { "delete", cmd_delete, "delete TAG..." },
+  { "setattr", cmd_setattr, "setattr TAG ATTR..." },
+  { "comment", cmd_comment, "comment TAG [COMMENT]" },
+  { "lock", cmd_lock, "lock QTAG" },
+  { "unlock", cmd_unlock, "unlock QTAG" },
+  { "tag", cmd_tag, "tag [-r] TAG [NEW-TAG]", "\
+Options:\n\
+\n\
+-r, --retag            Untag any key currently called new-tag.\n\
+" },
+  { "tidy", cmd_tidy, "tidy" },
   { "add", cmd_add,
-    "add [options] type [attr...]\n\
-       Options: [-lqrLS] [-a alg] [-bB bits] [-p param] [-R tag]\n\
-                [-e expire] [-t tag] [-c comment]", "\
+    "add [-OPTIONS] TYPE [ATTR...]\n\
+       Options: [-lqrLS] [-a ALG] [-bB BITS] [-p PARAM] [-R TAG]\n\
+                [-A SEEDALG] [-s SEED] [-n BITS]\n\
+                [-e EXPIRE] [-t TAG] [-c COMMENT]", "\
 Options:\n\
 \n\
 -a, --algorithm=ALG    Generate keys suitable for ALG.\n\
+                         ($ show keygen for list.)\n\
 -b, --bits=N           Generate an N-bit key.\n\
 -B, --qbits=N          Use an N-bit subgroup or factors.\n\
 -p, --parameters=TAG   Get group parameters from TAG.\n\
--C, --curve=CURVE      Use elliptic curve CURVE.\n\
+-C, --curve=NAME       Use elliptic curve or DH group NAME.\n\
+                         ($ show ec or $ show dh for list.)\n\
+-A, --seedalg=ALG      Use pseudorandom generator ALG to generate key.\n\
+                         ($ show seed for list.)\n\
+-s, --seed=BASE64      Use Base64-encoded string BASE64 as seed.\n\
+-n, --newseed=COUNT    Generate new COUNT-bit seed.\n\
 -e, --expire=TIME      Make the key expire after TIME.\n\
 -c, --comment=STRING   Attach the command STRING to the key.\n\
 -t, --tag=TAG          Tag the key with the name TAG.\n\
@@ -1822,80 +1881,22 @@ Options:\n\
 -L, --lim-lee          Generate Lim-Lee primes for Diffie-Hellman groups.\n\
 -S, --subgroup         Use a prime-order subgroup for Diffie-Hellman.\n\
 " },
-  { "expire", cmd_expire, "expire tag..." },
-  { "delete", cmd_delete, "delete tag..." },
-  { "tag", cmd_tag, "tag [-r] tag [new-tag]", "\
-Options:\n\
-\n\
--r, --retag            Untag any key currently called new-tag.\n\
-" },
-  { "setattr", cmd_setattr, "setattr tag attr..." },
-  { "comment", cmd_comment, "comment tag [comment]" },
-  { "lock", cmd_lock, "lock qtag" },
-  { "unlock", cmd_unlock, "unlock qtag" },
-  { "list", cmd_list, "list [-uqv] [-f filter] [tag...]", "\
-Options:\n\
-\n\
--u, --utc              Display expiry times etc. in UTC, not local time.\n\
--q, --quiet            Show less information.\n\
--v, --verbose          Show more information.\n\
-" },
-  { "fingerprint", cmd_finger, "fingerprint [-f filter] [tag...]", "\
-Options:\n\
-\n\
--f, --filter=FILT      Only hash key components matching FILT.\n\
--a, --algorithm=HASH   Use the named HASH algorithm.\n\
-" },
-  { "tidy", cmd_tidy, "tidy" },
-  { "extract", cmd_extract, "extract [-f filter] file [tag...]", "\
-Options:\n\
-\n\
--f, --filter=FILT      Only extract key components matching FILT.\n\
-" },
-  { "merge", cmd_merge, "merge file" },
   { 0, 0, 0 }
 };
 
-typedef struct cmd cmd;
-
-/*----- Main code ---------------------------------------------------------*/
-
-/* --- @findcmd@ --- *
- *
- * Arguments:  @const char *name@ = a command name
- *
- * Returns:    Pointer to the command structure.
- *
- * Use:                Looks up a command by name.  If the command isn't found, an
- *             error is reported and the program is terminated.
- */
-
-static cmd *findcmd(const char *name)
+static int cmd_help(int argc, char *argv[])
 {
-  cmd *c, *chosen = 0;
-  size_t sz = strlen(name);
-
-  for (c = cmds; c->name; c++) {
-    if (strncmp(name, c->name, sz) == 0) {
-      if (c->name[sz] == 0) {
-       chosen = c;
-       break;
-      } else if (chosen)
-       die(EXIT_FAILURE, "ambiguous command name `%s'", name);
-      else
-       chosen = c;
-    }
-  }
-  if (!chosen)
-    die(EXIT_FAILURE, "unknown command name `%s'", name);
-  return (chosen);
+  sc_help(cmds, stdout, argv + 1);
+  return (0);
 }
 
+/*----- Main code ---------------------------------------------------------*/
+
 /* --- Helpful GNUy functions --- */
 
-void usage(FILE *fp)
+static void usage(FILE *fp)
 {
-  pquis(fp, "Usage: $ [-k keyring] command [args]\n");
+  pquis(fp, "Usage: $ [-k KEYRING] COMMAND [ARGS]\n");
 }
 
 void version(FILE *fp)
@@ -1903,38 +1904,20 @@ void version(FILE *fp)
   pquis(fp, "$, Catacomb version " VERSION "\n");
 }
 
-void help(FILE *fp, char **argv)
+void help_global(FILE *fp)
 {
-  cmd *c;
-
-  version(fp);
-  fputc('\n', fp);
-  if (*argv) {
-    c = findcmd(*argv);
-    fprintf(fp, "Usage: %s [-k keyring] %s\n", QUIS, c->usage);
-    if (c->help) {
-      fputc('\n', fp); 
-      fputs(c->help, fp);
-    }
-  } else {
-    usage(fp);
-    fputs("\n\
-Performs various simple key management operations.  Command line options\n\
-recognized are:\n\
+  usage(fp);
+  fputs("\n\
+Performs various simple key management operations.\n\
+\n\
+Global command line options:\n\
 \n\
--h, --help [COMMAND]   Display this help text (or help for COMMAND).\n\
+-h, --help [COMMAND...]        Display this help text (or help for COMMANDs).\n\
 -v, --version          Display version number.\n\
 -u, --usage            Display short usage summary.\n\
 \n\
--k, --keyring=FILE     Read and write keys in FILE.\n\
--i, --id=TAG           Use key TAG for random number generator.\n\
--t, --type=TYPE                Use key TYPE for random number generator.\n\
-\n\
-The following commands are understood:\n\n",
-         fp);
-    for (c = cmds; c->name; c++)
-      fprintf(fp, "%s\n", c->usage);
-  }
+-k, --keyring=FILE     Read and write keys in FILE.\n",
+       fp);
 }
 
 /* --- @main@ --- *
@@ -1986,7 +1969,7 @@ int main(int argc, char *argv[])
       /* --- GNU help options --- */
 
       case 'h':
-       help(stdout, argv + optind);
+       sc_help(cmds, stdout, argv + optind);
        exit(0);
       case 'v':
        version(stdout);
@@ -2026,7 +2009,7 @@ int main(int argc, char *argv[])
   argc -= optind;
   argv += optind;
   optind = 0;
-  return (findcmd(argv[0])->cmd(argc, argv));
+  return (findcmd(cmds, argv[0])->cmd(argc, argv));
 }
 
 /*----- That's all, folks -------------------------------------------------*/
index 4b8d158..ad1f182 100644 (file)
@@ -1,6 +1,6 @@
 /* -*-c-*-
  *
- * $Id: perftest.c,v 1.1 2004/04/21 00:37:32 mdw Exp $
+ * $Id$
  *
  * Measure performance of various operations (Unix-specific)
  *
 #include "ec.h"
 #include "group.h"
 
+#include "cc.h"
 #include "gcipher.h"
 #include "ghash.h"
 #include "gmac.h"
+#include "ectab.h"
+#include "ptab.h"
 
 /*----- Options -----------------------------------------------------------*/
 
@@ -426,7 +429,7 @@ static void hash_run(void *cc)
 
 /* --- Job table --- */
 
-typedef struct jobobs {
+typedef struct jobops {
   const char *name;
   void *(*init)(opts *);
   void (*run)(void *);
@@ -452,7 +455,7 @@ static const jobops jobtab[] = {
 
 /*----- Main code ---------------------------------------------------------*/
 
-static void version(FILE *fp)
+void version(FILE *fp)
 {
   pquis(fp, "$, Catacomb " VERSION "\n");
 }
@@ -469,9 +472,40 @@ static void help(FILE *fp)
   usage(fp);
   pquis(fp, "\n\
 Various performance tests.\n\
+\n\
+Options:\n\
+\n\
+-h, --help             Show this help text.\n\
+-v, --version          Show program version number.\n\
+-u, --usage            Show terse usage message.\n\
+-l, --list [ITEM...]   List all the various names of things.\n\
+\n\
+-C, --name=NAME                Select curve/DH-group/enc/hash name.\n\
+-b, --field-bits       Field size for g-prime and rsa.\n\
+-B, --group-bits       Group size for g-prime; key size for ksched;\n\
+                         data size for enc and hash.\n\
+-n, --factors=COUNT    Number of factors for {exp,mul}-sim.\n\
+-i, --intervals=COUNT  Number of intervals to run for.  [0; forever]\n\
+-t, --time=TIME                Length of an interval in seconds.  [1]\n\
 ");
 }
 
+#define LISTS(LI)                                                      \
+  LI("Lists", list,                                                    \
+     listtab[i].name, listtab[i].name)                                 \
+  LI("Jobs", job,                                                      \
+     jobtab[i].name, jobtab[i].name)                                   \
+  LI("Elliptic curves", ec,                                            \
+     ectab[i].name, ectab[i].name)                                     \
+  LI("Diffie-Hellman groups", dh,                                      \
+     ptab[i].name, ptab[i].name)                                       \
+  LI("Encryption algorithms", cipher,                                  \
+     gciphertab[i], gciphertab[i]->name)                               \
+  LI("Hash functions", hash,                                           \
+     ghashtab[i], ghashtab[i]->name)
+
+MAKELISTTAB(listtab, LISTS)
+
 static unsigned uarg(const char *what, const char *p)
 {
   char *q;
@@ -514,6 +548,7 @@ int main(int argc, char *argv[])
       { "help",                0,              0,      'h' },
       { "version",     0,              0,      'v' },
       { "usage",       0,              0,      'u' },
+      { "list",                0,              0,      'l' },
       { "name",                OPTF_ARGREQ,    0,      'C' },
       { "field-bits",  OPTF_ARGREQ,    0,      'b' },
       { "group-bits",  OPTF_ARGREQ,    0,      'B' },
@@ -523,12 +558,13 @@ int main(int argc, char *argv[])
       { 0,             0,              0,      0 }
     };
 
-    i = mdwopt(argc, argv, "hvuC:b:B:n:i:t:", opts, 0, 0, 0);
+    i = mdwopt(argc, argv, "hvulC:b:B:n:i:t:", opts, 0, 0, 0);
     if (i < 0) break;
     switch (i) {
       case 'h': help(stdout); exit(0);
       case 'v': version(stdout); exit(0);
       case 'u': usage(stdout); exit(0);
+      case 'l': exit(displaylists(listtab, argv + optind));
       case 'C': o.name = optarg; break;
       case 'b': o.fbits = uarg("field bits", optarg); break;
       case 'B': o.gbits = uarg("subgroup bits", optarg); break;