dstr_reset(&d);
key_fulltag(k, &d);
- e = initenc(eo, ofp, "CATCRYPT ENCRYPTED MESSAGE", 1);
+ e = initenc(eo, ofp, "CATCRYPT ENCRYPTED MESSAGE");
km = getkem(k, "cckem", 0);
if ((err = km->ops->check(km)) != 0)
- moan("key `%s' fails check: %s", d.buf, err);
+ moan("key %s fails check: %s", d.buf, err);
if (sk) {
dstr_reset(&d);
key_fulltag(sk, &d);
s = getsig(sk, "ccsig", 1);
if ((err = s->ops->check(s)) != 0)
- moan("key `%s' fails check: %s", d.buf, err);
+ moan("key %s fails check: %s", d.buf, err);
}
/* --- Build the header chunk --- */
static int decrypt(int argc, char *argv[])
{
const char *of = 0;
- FILE *ofp = 0;
+ FILE *ofp = 0, *rfp = 0;
FILE *fp = 0;
const char *ef = "binary";
int i;
+ size_t n;
dstr d = DSTR_INIT;
buf b;
key_file kf;
enc *e;
#define f_bogus 1u
+#define f_buffer 2u
for (;;) {
static const struct option opt[] = {
{ "armour", 0, 0, 'a' },
{ "armor", 0, 0, 'a' },
+ { "buffer", 0, 0, 'b' },
{ "verbose", 0, 0, 'v' },
{ "quiet", 0, 0, 'q' },
{ "format", OPTF_ARGREQ, 0, 'f' },
if (i < 0) break;
switch (i) {
case 'a': ef = "pem"; break;
+ case 'b': f |= f_buffer; break;
case 'v': verb++; break;
case 'q': if (verb) verb--; break;
case 'f': ef = optarg; break;
if (key_open(&kf, keyring, KOPEN_READ, keyreport, 0))
die(EXIT_FAILURE, "can't open keyring `%s'", keyring);
- e = initenc(eo, fp, "CATCRYPT ENCRYPTED MESSAGE", 0);
+ e = initdec(eo, fp, checkbdry, "CATCRYPT ENCRYPTED MESSAGE");
/* --- Read the header chunk --- */
if (verb) {
dstr_reset(&d);
key_fulltag(sk, &d);
- printf("INFO good signature from %s\n", d.buf);
+ printf("INFO good-signature %s\n", d.buf);
}
} else if (verb)
- printf("INFO no signature packet\n");
+ printf("INFO no-signature\n");
/* --- Now decrypt the main body --- */
if (!of || strcmp(of, "-") == 0) {
ofp = stdout;
- if (verb) printf("DATA\n");
- } else if ((ofp = fopen(of, "wb")) == 0) {
- die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
- ofp, strerror(errno));
+ f |= f_buffer;
}
+ if (!(f & f_buffer)) {
+ if ((ofp = fopen(of, "wb")) == 0) {
+ die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
+ ofp, strerror(errno));
+ }
+ rfp = ofp;
+ } else if ((rfp = tmpfile()) == 0)
+ die(EXIT_FAILURE, "couldn't create temporary file: %s", strerror(errno));
seq = 0;
dstr_ensure(&d, GC_CLASS(c)->blksz);
seq++;
chunk_read(e, &d, &b);
if ((tag = buf_get(&b, GM_CLASS(m)->hashsz)) == 0) {
- if (verb) printf("%sFAIL bad ciphertext chunk: no tag\n",
- ofp == stdout ? "\n" : "");
+ if (verb) printf("FAIL bad ciphertext chunk: no tag\n");
exit(EXIT_FAILURE);
}
GH_HASH(h, BCUR(&b), BLEFT(&b));
if (memcmp(tag, GH_DONE(h, 0), GM_CLASS(m)->hashsz) != 0) {
if (verb)
- printf("%sFAIL bad ciphertext chunk: authentication failure\n",
- ofp == stdout ? "\n" : "");
+ printf("FAIL bad ciphertext chunk: authentication failure\n");
exit(EXIT_FAILURE);
}
if (!BLEFT(&b))
break;
GC_DECRYPT(c, BCUR(&b), BCUR(&b), BLEFT(&b));
- if (fwrite(BCUR(&b), 1, BLEFT(&b), ofp) != BLEFT(&b)) {
- if (verb) printf("%sFAIL error writing output: %s\n",
- ofp == stdout ? "\n" : "", strerror(errno));
+ if (fwrite(BCUR(&b), 1, BLEFT(&b), rfp) != BLEFT(&b)) {
+ if (verb) printf("FAIL error writing output: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
}
- if (fflush(ofp) || ferror(ofp)) {
- if (verb) printf("%sFAIL error writing output: %s\n",
- ofp == stdout ? "\n" : "", strerror(errno));
+ if (fflush(rfp) || ferror(rfp)) {
+ if (verb) printf("FAIL error writing output: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
+ if (f & f_buffer) {
+ if (!ofp && (ofp = fopen(of, "wb")) == 0) {
+ die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
+ of, strerror(errno));
+ }
+ rewind(rfp);
+ dstr_reset(&d);
+ dstr_ensure(&d, 65536);
+ if (ofp == stdout) printf("DATA\n");
+ for (;;) {
+ n = fread(d.buf, 1, d.sz, rfp);
+ if (!n) break;
+ if (fwrite(d.buf, 1, n, ofp) < n)
+ die(EXIT_FAILURE, "error writing output: %s", strerror(errno));
+ }
+ if (ferror(rfp) || fclose(rfp))
+ die(EXIT_FAILURE, "error unbuffering output: %s", strerror(errno));
+ }
+ if (ofp && (fflush(ofp) || ferror(ofp) || fclose(ofp)))
+ die(EXIT_FAILURE, "error writing output: %s", strerror(errno));
e->ops->decdone(e);
if (verb && ofp != stdout)
return (0);
#undef f_bogus
-}
-
-/*----- Test code ---------------------------------------------------------*/
-
-static int encode(int argc, char *argv[])
-{
- const char *of = 0;
- FILE *ofp = 0;
- FILE *fp = 0;
- const char *ef = "binary";
- const char *bd = "MESSAGE";
- int i;
- size_t n;
- char buf[4096];
- unsigned f = 0;
- const encops *eo;
- enc *e;
-
-#define f_bogus 1u
-
- for (;;) {
- static const struct option opt[] = {
- { "format", OPTF_ARGREQ, 0, 'f' },
- { "boundary", OPTF_ARGREQ, 0, 'b' },
- { "output", OPTF_ARGREQ, 0, 'o' },
- { 0, 0, 0, 0 }
- };
- i = mdwopt(argc, argv, "f:b:o:", opt, 0, 0, 0);
- if (i < 0) break;
- switch (i) {
- case 'f': ef = optarg; break;
- case 'b': bd = optarg; break;
- case 'o': of = optarg; break;
- default: f |= f_bogus; break;
- }
- }
- if (argc - optind > 1 || (f & f_bogus))
- die(EXIT_FAILURE, "Usage: encode [-OPTIONS] [FILE]");
-
- if ((eo = getenc(ef)) == 0)
- die(EXIT_FAILURE, "encoding `%s' not found", ef);
-
- if (optind == argc)
- fp = stdin;
- else if (strcmp(argv[optind], "-") == 0) {
- fp = stdin;
- optind++;
- } else if ((fp = fopen(argv[optind], "rb")) == 0) {
- die(EXIT_FAILURE, "couldn't open file `%s': %s",
- argv[optind], strerror(errno));
- } else
- optind++;
-
- if (!of || strcmp(of, "-") == 0)
- ofp = stdout;
- else if ((ofp = fopen(of, eo->wmode)) == 0) {
- die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
- ofp, strerror(errno));
- }
-
- e = initenc(eo, ofp, bd, 1);
-
- do {
- n = fread(buf, 1, sizeof(buf), fp);
- if (e->ops->write(e, buf, n))
- die(EXIT_FAILURE, "error writing output: %s", strerror(errno));
- } while (n == sizeof(buf));
- e->ops->encdone(e);
- freeenc(e);
- return (0);
-
-#undef f_bogus
-}
-
-static int decode(int argc, char *argv[])
-{
- const char *of = 0;
- FILE *ofp = 0;
- FILE *fp = 0;
- const char *ef = "binary";
- const char *bd = 0;
- int i;
- char buf[4096];
- unsigned f = 0;
- const encops *eo;
- enc *e;
-
-#define f_bogus 1u
-
- for (;;) {
- static const struct option opt[] = {
- { "format", OPTF_ARGREQ, 0, 'f' },
- { "boundary", OPTF_ARGREQ, 0, 'b' },
- { "output", OPTF_ARGREQ, 0, 'o' },
- { 0, 0, 0, 0 }
- };
- i = mdwopt(argc, argv, "f:b:o:", opt, 0, 0, 0);
- if (i < 0) break;
- switch (i) {
- case 'f': ef = optarg; break;
- case 'b': bd = optarg; break;
- case 'o': of = optarg; break;
- default: f |= f_bogus; break;
- }
- }
- if (argc - optind > 1 || (f & f_bogus))
- die(EXIT_FAILURE, "Usage: decode [-OPTIONS] [FILE]");
-
- if ((eo = getenc(ef)) == 0)
- die(EXIT_FAILURE, "encoding `%s' not found", ef);
-
- if (optind == argc)
- fp = stdin;
- else if (strcmp(argv[optind], "-") == 0) {
- fp = stdin;
- optind++;
- } else if ((fp = fopen(argv[optind], eo->rmode)) == 0) {
- die(EXIT_FAILURE, "couldn't open file `%s': %s",
- argv[optind], strerror(errno));
- } else
- optind++;
-
- if (!of || strcmp(of, "-") == 0)
- ofp = stdout;
- else if ((ofp = fopen(of, "wb")) == 0) {
- die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
- ofp, strerror(errno));
- }
-
- e = initenc(eo, fp, bd, 0);
-
- do {
- if ((i = e->ops->read(e, buf, sizeof(buf))) < 0)
- die(EXIT_FAILURE, "error reading input: %s", strerror(errno));
- if (fwrite(buf, 1, i, ofp) < i)
- die(EXIT_FAILURE, "error writing output: %s", strerror(errno));
- } while (i == sizeof(buf));
- e->ops->decdone(e);
- freeenc(e);
- return (0);
-
-#undef f_bogus
+#undef f_buffer
}
/*----- Main code ---------------------------------------------------------*/
static cmd cmdtab[] = {
{ "help", cmd_help, "help [COMMAND...]" },
{ "show", cmd_show, "show [ITEM...]" },
- { "encode", encode,
- "encode [-f FORMAT] [-b LABEL] [-o OUTPUT] [FILE]",
- "\
-Options:\n\
-\n\
--f, --format=FORMAT Encode to FORMAT.\n\
--b, --boundary=LABEL PEM boundary is LABEL.\n\
--o, --output=FILE Write output to FILE.\n\
-" },
- { "decode", decode,
- "decode [-f FORMAT] [-b LABEL] [-o OUTPUT] [FILE]",
- "\
-Options:\n\
-\n\
--f, --format=FORMAT Decode from FORMAT.\n\
--b, --boundary=LABEL PEM boundary is LABEL.\n\
--o, --output=FILE Write output to FILE.\n\
-" },
+ CMD_ENCODE,
+ CMD_DECODE,
{ "encrypt", encrypt,
"encrypt [-a] [-k TAG] [-s TAG] [-f FORMAT]\n\t\
[-o OUTPUT] [FILE]", "\
-o, --output=FILE Write output to FILE.\n\
" },
{ "decrypt", decrypt,
- "decrypt [-aqv] [-f FORMAT] [-o OUTPUT] [FILE]", "\
+ "decrypt [-abqv] [-f FORMAT] [-o OUTPUT] [FILE]", "\
Options:\n\
\n\
-a, --armour Same as `-f pem'.\n\
--v, --verbose Produce more verbose messages.\n\
--q, --quiet Produce fewer messages.\n\
+-b, --buffer Buffer output until we're sure we have it all.\n\
-f, --format=FORMAT Decode as FORMAT.\n\
-o, --output=FILE Write output to FILE.\n\
-" },
+-q, --quiet Produce fewer messages.\n\
+-v, --verbose Produce more verbose messages.\n\
+" }, /* ' emacs is confused */
{ 0, 0, 0 }
};
--- /dev/null
+.\" -*-nroff-*-
+.de VS
+.sp 1
+.RS
+.nf
+.ft B
+..
+.de VE
+.ft R
+.fi
+.RE
+.sp 1
+..
+.ie t \{\
+. if \n(.g \{\
+. fam P
+. \}
+.\}
+.de hP
+.IP
+.ft B
+\h'-\w'\\$1\ 'u'\\$1\ \c
+.ft P
+..
+.ie t .ds o \(bu
+.el .ds o o
+.TH catsign 1 "17 March 2005" "Straylight/Edgeware" "Catacomb cryptographic library"
+.SH NAME
+catsign \- sign and verify messages
+.SH SYNOPSIS
+.B catsign
+.RB [ \-k
+.IR keyring ]
+.I command
+.PP
+where
+.I command
+is one of:
+.PP
+.B help
+.RI [ command ...]
+.br
+.B show
+.RI [ item ...]
+.br
+.B sign
+.RB [ \-adt ]
+.RB [ \-k
+.IR tag ]
+.RB [ \-f
+.IR format ]
+.RB [ \-o
+.IR output ]
+.RI [ file ]
+.br
+.B verify
+.RB [ \-aquv ]
+.RB [ \-k
+.IR tag ]
+.RB [ \-f
+.IR format ]
+.br
+
+.RB [ \-o
+.IR output ]
+.RI [ file
+.RI [ message ]]
+.br
+.B info
+.RB [ \-a ]
+.RB [ \-f
+.IR format ]
+.RI [ file ]
+.br
+.B format
+.RB [ \-auABDET ]
+.RB [ \-f
+.IR format ]
+.RB [ \-F
+.IR format ]
+.br
+
+.RB [ \-m
+.IR file ]
+.RB [ \-o
+.IR output ]
+.RI [ file
+.RI [ message ]]
+.br
+.B encode
+.RB [ \-f
+.IR format ]
+.RB [ \-b
+.IR boundary ]
+.RB [ \-o
+.IR output ]
+.RI [ file ]
+.br
+.B decode
+.RB [ \-f
+.IR format ]
+.RB [ \-b
+.IR boundary ]
+.RB [ \-o
+.IR output ]
+.RI [ file ]
+.SH "DESCRIPTION"
+The
+.B catsign
+command signs and verifies messages. It also works as a simple PEM
+encoder and decoder. It provides a number of subcommands, by which the
+various operations may be carried out.
+.SS "Global options"
+Before the command name,
+.I "global options"
+may be given. The following global options are supported:
+.TP
+.BR "\-h, \-\-help " [ \fIcommand ...]
+Writes a brief summary of
+.BR catsign 's
+various options to standard output, and returns a successful exit
+status. With command names, gives help on those commands.
+.TP
+.B "\-v, \-\-version"
+Writes the program's version number to standard output, and returns a
+successful exit status.
+.TP
+.B "\-u, \-\-usage"
+Writes a very terse command line summary to standard output, and returns
+a successful exit status.
+.TP
+.BI "\-k, \-\-keyring " file
+Names the keyring file which
+.B key
+is to process. The default keyring, used if this option doesn't specify
+one, is the file named
+.B keyring
+in the current directory. See
+.BR key (1)
+and
+.BR keyring (5)
+for more details about keyring files.
+.SH "KEY SETUP"
+Algorithms to be used with a particular key are described by attributes
+on the key, or its type. The
+.B catsign
+command deals with signing keys. (Note that
+.B catsign
+uses signing keys in the same way as
+.BR catcrypt (1).)
+.PP
+A
+.I sigalgspec
+has the form
+.IR sig \c
+.RB [ / \c
+.IR hash ].
+If a
+.B sig
+attribute is present on the key, then it must have this form; otherwise,
+the key's type must have the form
+.BI ccsig- \c
+.IR sigalgspec .
+Algorithm selections are taken from appropriately-named attributes, or,
+failing that, from the
+.IR sigalgspec .
+.PP
+The signature algorithm is chosen according to the setting of
+.I sig
+as follows. Run
+.B catsign show sig
+for a list of supported signature algorithms.
+.TP
+.B rsapkcs1
+This is almost the same as the RSASSA-PKCS1-v1_5 algorithm described in
+RFC3447; the difference is that the hash is left bare rather than being
+wrapped in a DER-encoded
+.B DigestInfo
+structure. This doesn't affect security since the key can only be used
+with the one hash function anyway, and dropping the DER wrapping permits
+rapid adoption of new hash functions. Regardless, use of this algorithm
+is not recommended, since the padding method has been shown vulnerable
+to attack. Use the
+.B rsa
+algorithm of the
+.B key add
+command (see
+.BR key (1))
+to generate the key.
+.TP
+.B rsapss
+This is the RSASSA-PSS algorithm described in RFC3447. It is the
+preferred RSA-based signature scheme. Use the
+.B rsa
+algorithm of the
+.B key add
+command (see
+.BR key (1))
+to generate the key.
+.TP
+.B dsa
+This is the DSA algorithm described in FIPS180-1 and FIPS180-2. Use the
+.B dsa
+algorithm of the
+.B key add
+command (see
+.BR key (1))
+to generate the key.
+.TP
+.B ecdsa
+This is the ECDSA algorithm described in ANSI X9.62 and FIPS180-2. Use
+the
+.B ec
+algorithm of the
+.B key add
+command (see
+.BR key (1))
+to generate the key.
+.TP
+.B kcdsa
+This is the revised KCDSA (Korean Certificate-based Digital Signature
+Algorithm) described in
+.I The Revised Version of KCDSA
+.RB ( http://dasan.sejong.ac.kr/~chlim/pub/kcdsa1.ps ).
+Use the
+.B dh
+algorithm of the
+.B key add
+command with the
+.B \-LS
+options (see
+.BR key (1))
+to generate the key.
+.TP
+.B eckcdsa
+This is an unofficial elliptic-curve analogue of the KCDSA algorithm.
+Use the
+.B ec
+algorithm of the
+.B key add
+command (see
+.BR key (1))
+to generate the key.
+.PP
+As well as the signature algorithm itself, a hash function is used.
+This is taken from the
+.B hash
+attribute on the key, or, failing that, from the
+.I hash
+specified in the
+.IR sigalgspec ,
+or, if that is absent, determined by the signature algorithm as follows.
+.hP \*o
+For
+.BR rsapkcs1 ,
+.BR rsapss ,
+.BR dsa ,
+and
+.BR ecdsa ,
+the default hash function is
+.BR sha .
+.hP \*o
+For
+.BR kcdsa
+and
+.BR eckcdsa ,
+the default hash function is
+.BR has160 .
+.PP
+Run
+.B catsign show hash
+for a list of supported hash functions.
+.SH "ENCODINGS"
+Two encodings for the ciphertext are supported.
+.TP
+.B binary
+The raw format, which has the benefit of being smaller, but needs to be
+attached to mail messages and generally handled with care.
+.TP
+.B pem
+PEM-encapsulated Base-64 encoded text. This format can be included
+directly in email and picked out again automatically; but there is a
+4-to-3 data expansion as a result.
+.SH "SIGNATURE FORMATS"
+There are two basic signature formats understood by
+.BR catsign .
+.hP \*o
+Embedded signatures include (embed) the message they sign; hence they're
+complete in and of themselves. The
+.B catsign
+program extracts the message during signature verification.
+.hP \*o
+Detached signatures are separate from the messages they sign, and both
+the original file and the signature are required for a successful
+verification.
+.PP
+Another important distinction is whether the message data is considered
+to be plain text or raw binary data.
+.hP \*o
+When dealing with plain text,
+.B catsign
+allows a limited quantity of leeway in the messages it processes. It
+ignores trailing whitespace on a line, including stray carriage-returns,
+which may appear if Windows boxes have had their way with the data. It
+also appends a final newline if there wasn't one before. In embedded
+signatures, the text is left unencoded, so that the message is readable.
+.hP \*o
+Binary files are preserved completely, and no variation whatever is
+permitted.
+.PP
+The
+.VS
+catsign format
+.VE
+command can convert between detached and embedded signatures; it cannot
+convert between binary and text mode signatures. (The data actually
+signed includes a flag saying whether the message is textual. The
+rationale here is that what looks like an ASCII space before a newline
+may be devastatingly significant in a binary data file, and if a message
+is signed as raw binary then no changes whatever should be allowed.)
+.SH "COMMAND REFERENCE"
+.SS help
+The
+.B help
+command behaves exactly as the
+.B \-\-help
+option. With no arguments, it shows an overview of
+.BR catsign 's
+options; with arguments, it describes the named subcommands.
+.SS show
+The
+.B show
+command prints various lists of tokens understood by
+.BR catsign .
+With no arguments, it prints all of the lists; with arguments, it prints
+just the named lists, in order. The recognized lists can be enumerated
+using the
+.VS
+catsign show list
+.VE
+command. The lists are as follows.
+.TP
+.B list
+The lists which can be enumerated by the
+.B show
+command.
+.TP
+.B sig
+The signature algorithms which can be used in a signing key's
+.B sig
+attribute.
+.TP
+.B hash
+The hash functions which can be used in a key's
+.B hash
+attribute.
+.TP
+.B enc
+The encodings which can be applied to encrypted messages; see
+.B ENCODINGS
+above.
+.SS sign
+The
+.B sign
+command signs a message and writes out an appropriately-encoded
+signature. By default, it reads a message from standard input and
+writes the signature to standard output. If a filename argument is
+given, this file is read instead.
+.PP
+The following options are recognized.
+.TP
+.B "\-a, \-\-armour"
+Produce ASCII-armoured output. This is equivalent to specifying
+.BR "\-f pem" .
+The variant spelling
+.B "\-\-armor"
+is also accepted.
+.TP
+.B "\-b, \-\-binary"
+Read and sign the input as binary data. The default is to treat the
+input as text.
+.TP
+.B "\-d, \-\-detach"
+Produce a detached signature. The default is to produce a signature
+with embedded message.
+.TP
+.BI "\-f, \-\-format " format
+Produce output encoded according to
+.IR format .
+.TP
+.BI "\-k, \-\-key " tag
+Use the signing key named
+.I tag
+in the current keyring; the default key is
+.BR ccsig .
+.TP
+.BI "\-o, \-\-ouptut " file
+Write output to
+.I file
+rather than to standard output.
+.TP
+.B "\-t, \-\-text"
+Read and sign the input as text. This is the default.
+.SS verify
+The
+.B verify
+command checks a signature's validity, producing as output information
+about the signature and the signed message.
+.PP
+The first non-option argument is the name of the file containing the
+signature data; this may be omitted or
+.RB ` \- '
+to indicate that the signature be read from standard input. The second
+non-option argument, if any, is the name of the file to read the message
+from, if the signature is detached. An error is reported if a message
+file is specified but the signature contains an embedded message
+already; if the signature is detached but no filename is given, then the
+message is expected on stdin (immediately after the signature, if any).
+.TP
+.B "\-a, \-\-armour"
+Read ASCII-armoured input. This is equivalent to specifying
+.BR "\-f pem" .
+The variant spelling
+.B "\-\-armor"
+is also accepted.
+.TP
+.B "\-b, \-\-buffer"
+Buffer the message until the signature is verified. This is forced on
+if output is to stdout, but is always available as an option.
+.TP
+.BI "\-f, \-\-format " format
+Read input encoded according to
+.IR format .
+.TP
+.B "\-v, \-\-verbose"
+Produce more verbose messages. See below for the messages produced
+during decryption. The default verbosity level is 1. (Currently this
+is the most verbose setting. This might not be the case always.)
+.B "\-q, \-\-quiet"
+Produce fewer messages.
+.TP
+.BI "\-k, \-\-key " tag
+Usually
+.B catsign
+uses the signature header to work out which key to use to verify a
+signature. Using this option causes verification to fail unless the
+signature header specifies the key named
+.IR tag .
+.TP
+.B "\-u, \-\-utc"
+Show the datestamp in the signature in UTC rather than (your) local
+time. The synonym
+.B \-\-gmt
+is also accepted.
+.TP
+.BI "\-o, \-\-output " file
+Write the verified message to
+.IR file .
+The file is written in text or binary
+mode as appropriate. The default is to write the message to standard
+output unless verifying a detached signature, in which case nothing is
+written.
+.PP
+Output is written to standard output in a machine-readable format.
+Major problems cause the program to write a diagnostic to standard error
+and exit nonzero as usual. The quantity of output varies depending on
+the verbosity level and whether the message is also being written to
+standard output. Output lines begin with a keyword:
+.TP
+.BI "FAIL " reason
+An error prevented verification. The program will exit nonzero.
+.TP
+.BI "WARN " reason
+.B catsign
+encountered a situation which may or may not invalidate the
+verification.
+.TP
+.BI "OK " message
+Verification was successful. This is only produced if the message is
+being sent somewhere other than standard output.
+.TP
+.B "DATA"
+The message follows, starting just after the next newline character or
+sequence. This is only produced if the message is being written to
+standard output.
+.TP
+.BI "INFO " note
+Any other information.
+.PP
+The information written at the various verbosity levels is as follows.
+.hP 0.
+No output. Watch the exit status.
+.hP 1.
+All messages.
+.PP
+.B Warning!
+All output written has been checked for authenticity. However, output
+can fail madway through for many reasons, and the resulting message may
+therefore be truncated. Don't rely on the output being complete until
+.B OK is printed or
+.B catsign verify
+exits successfully.
+.SS info
+The
+.B info
+command analyses a signature without verifying it, and prints
+interesting information about it. This might be useful for diagnostic
+purposes. No keys are needed for this operation, though you get more
+useful information if you have them.
+.PP
+If a non-option argument is given, and it is not
+.RB ` \- ',
+then it is taken to name the file containing the signature to parse;
+otherwise a signature is read from standard input.
+.PP
+The following options are recognized.
+.TP
+.B "\-a, \-\-armour"
+Read ASCII-armoured input. This is equivalent to specifying
+.BR "\-f pem" .
+The variant spelling
+.B "\-\-armor"
+is also accepted.
+.TP
+.BI "\-f, \-\-format " format
+Read input encoded according to
+.IR format .
+.TP
+.B "\-u, \-\-utc"
+Show the datestamp in the signature in UTC rather than (your) local
+time. The synonym
+.B \-\-gmt
+is also accepted.
+.PP
+A description of the signature block is produced on standard output; it
+is mostly machine-readable. The first word on each line explains what
+kind of output it is.
+.TP
+.BI "BAD " message
+The signature data is invalid and cannot be parsed.
+.TP
+.BI "WARN " message
+Something is wrong with the data, but isn't fatal.
+.TP
+.BI "NOTE " message
+An environmental problem means that the information isn't as helpful as
+it might be. For example, the keyring file can't be opened, so we don't
+know whether the verification key is there.
+.TP
+.BI "INFO flags " flags
+Describes the flags set in the signature header. The
+.I flags
+are a list of flags, one per word, preceded by a
+.RB ` ! '
+if the flag is clear.
+.TP
+.BI "INFO expected-flags " flags
+If the PEM boundary string didn't match the actual signature data then
+this line is output, listing the expected flags and their settings.
+Problems with boundary mismatches can be resolved using the
+.B format
+command.
+.TP
+.BI "INFO date " yyyy "\-" mm "\-" dd " " hh ":" mm ":" ss " " tz
+Signature was (allegedly!) made at the given time and date. If the
+.B \-u
+option was given, this will be in UTC.
+.TP
+.BI "INFO key " tag
+Signature was (allegedly!) made using the key
+.IR tag ,
+which is present in the current keyring.
+.TP
+.BI "INFO unknown-key " keyid
+Signature was (allegedly!) made using the key with id
+.IR keyid
+which is not in the current keyring (or the keyring wasn't found).
+.SS format
+The
+.B format
+command translates signatures between the various supported formats.
+This is a (slightly) more complex operation than re-encoding, though it
+does not require any cryptographic operations.
+.PP
+The first non-option argument is the name of the file containing the
+signature data; this may be omitted or
+.RB ` \- '
+to indicate that the signature be read from standard input. The second
+non-option argument, if any, is the name of the file to read the message
+from, if the signature is detached. An error is reported if a message
+file is specified but the signature contains an embedded message
+already; if the signature is detached but no filename is given, then the
+message is expected on stdin (immediately after the signature, if any).
+.PP
+The options follow a rough convention: options describing the input
+format are lower-case and options specifying the output format are
+upper-case. The following options are recognized.
+.TP "\-a, \-\-armour-in"
+Read ASCII-armoured input. This is equivalent to specifying
+.BR "\-f pem" .
+The variant spelling
+.B "\-\-armor"
+is also accepted.
+.TP "\-A, \-\-armour-out"
+Produce ASCII-armoured output. This is equivalent to specifying
+.BR "\-F pem" .
+The variant spelling
+.B "\-\-armor-out"
+is also accepted.
+.TP
+.B "\-D, \-\-detach"
+Produce a detached signature. This may be used to detach a signature
+from an embedded message.
+.TP
+.B "\-E, \-\-embed"
+Produce a signature with embedded message. This may be used to
+reattach a message to its detached signature.
+.TP
+.BI "\-f, \-\-format-in " format
+Read input encoded according to
+.IR format .
+.TP
+.BI "\-F, \-\-format-out " format
+Produce output encoded according to
+.IR format .
+.TP
+.BI "\-m, \-\-message " file
+Write the message to
+.IR file .
+If
+.I file
+is
+.RB ` \- '
+then write the message to standard output. Don't send the message and
+signature to the same place because it doesn't work.
+.TP
+.BI "\-o, \-\-output " file
+Write the signature to
+.IR file .
+If no
+.B \-m
+or
+.B \-o
+option is given, a signature is written to standard output.
+.SH "BUGS"
+The trailing-whitespace deletion doesn't work for more than 32K of
+whitespace. I don't think this is a big problem, really.
+.PP
+The
+.B format
+command does something unhelpful if message and signature are sent to
+the same file.
+.SH "SEE ALSO"
+.BR key (1),
+.BR catcrypt (1),
+.BR dsig (1),
+.BR hashsum (1),
+.BR keyring (5).
+.SH AUTHOR
+Mark Wooding, <mdw@nsict.org>
--- /dev/null
+/* -*-c-*-
+ *
+ * $Id$
+ *
+ * Sign files
+ *
+ * (c) 2005 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of Catacomb.
+ *
+ * Catacomb is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * Catacomb is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with Catacomb; if not, write to the Free
+ * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "config.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <mLib/base64.h>
+#include <mLib/dstr.h>
+#include <mLib/mdwopt.h>
+#include <mLib/quis.h>
+#include <mLib/report.h>
+#include <mLib/sub.h>
+
+#include "buf.h"
+#include "rand.h"
+#include "noise.h"
+#include "mprand.h"
+#include "key.h"
+#include "cc.h"
+
+#include "ectab.h"
+#include "ptab.h"
+
+/*----- Utilities ---------------------------------------------------------*/
+
+/* --- @keyreport@ --- *
+ *
+ * Arguments: @const char *file@ = filename containing the error
+ * @int line@ = line number in file
+ * @const char *err@ = error text message
+ * @void *p@ = unimportant pointer
+ *
+ * Returns: ---
+ *
+ * Use: Reports errors during the opening of a key file.
+ */
+
+static void keyreport(const char *file, int line, const char *err, void *p)
+{
+ moan("error in keyring `%s' at line `%s': %s", file, line, err);
+}
+
+/*----- Static variables --------------------------------------------------*/
+
+static const char *keyring = "keyring";
+
+/*----- Data formats ------------------------------------------------------*
+ *
+ * Our crypto stuff is split into three parts: a header describing the key,
+ * the message itself, and the signature. In a detached signature, the
+ * message is separate, and the header and signature are combined into one
+ * encoded chunk. In an attached signature, the message is included. There
+ * are two forms of message: binary and text. Binary messages are divided
+ * into chunks, each preceded by a 2-octet length, and terminated by a
+ * zero-length chunk. Text messages are byte-stuffed, as for RFC821, and
+ * trailing whitespace before a newline is ignored.
+ */
+
+typedef struct sigmsg {
+ unsigned f; /* Flags */
+#define F_DETACH 1u
+#define F_BINARY 2u
+#define F_SIGMASK 3u
+#define F_HASHMASK (F_BINARY)
+ uint32 keyid; /* Key identifier */
+ time_t t; /* When the signature was made */
+ dstr kh; /* Key fingerprint (sanity check) */
+ dstr sig; /* Signature */
+ sig *s; /* Signature algorithm */
+} sigmsg;
+
+#define F_MIDLINE 16u
+#define F_EOF 32u
+#define F_NOCLOSE 64u
+#define F_BOGUS 128u
+#define F_BUFFER 256u
+#define F_UTC 512u
+
+/*----- Chunk I/O ---------------------------------------------------------*/
+
+static void chunk_write(enc *e, const void *p, size_t n)
+{
+ octet b[2];
+
+ assert(n <= 0xffff);
+ STORE16(b, n);
+ if (e->ops->write(e, b, 2) || e->ops->write(e, p, n))
+ die(EXIT_FAILURE, "error writing output: %s", strerror(errno));
+}
+
+static size_t chunk_read(enc *e, void *p)
+{
+ octet b[2];
+ size_t n;
+
+ if (e->ops->read(e, b, 2) != 2)
+ goto err;
+ n = LOAD16(b);
+ if (n && e->ops->read(e, p, n) != n)
+ goto err;
+ return (n);
+
+err:
+ if (!errno) die(EXIT_FAILURE, "unexpected end-of-file on input");
+ else die(EXIT_FAILURE, "error reading input: %s", strerror(errno));
+ return (0);
+}
+
+/*----- Message canonification --------------------------------------------*/
+
+#define MSGBUFSZ 65536
+#define MSGBUFTHRESH 32768
+
+typedef struct msgcanon {
+ unsigned f;
+ FILE *fp;
+ enc *e;
+ size_t (*read)(struct msgcanon *, void *);
+ void (*write)(struct msgcanon *, const void *, size_t);
+ void (*close)(struct msgcanon *);
+} msgcanon;
+
+#define MC_INIT { 0 }
+
+static size_t textread(msgcanon *m, void *bp)
+{
+ size_t n = 0, nsp = 0;
+ int ch;
+ char *b = bp;
+ unsigned f = m->f;
+
+ if (f & F_EOF) return (0);
+ for (;;) {
+ if ((ch = getc(m->fp)) == EOF) goto eof;
+ if (!(f & (F_DETACH | F_MIDLINE)) && ch == '.') {
+ ch = getc(m->fp);
+ if (ch == '\n') goto eof;
+ }
+ if (ch == '\n') {
+ n -= nsp; nsp = 0;
+ if (n >= MSGBUFTHRESH) goto full;
+ b[n++] = ch;
+ f &= ~F_MIDLINE;
+ } else if (isspace(ch)) {
+ f |= F_MIDLINE;
+ if (n >= MSGBUFSZ) goto full;
+ b[n++] = ch; nsp++;
+ } else {
+ f |= F_MIDLINE;
+ nsp = 0;
+ if (n >= MSGBUFTHRESH) goto full;
+ b[n++] = ch;
+ }
+ }
+eof:
+ f |= F_EOF;
+ goto done;
+full:
+ ungetc(ch, m->fp);
+done:
+ m->f = f;
+ return (n);
+}
+
+static void textwrite(msgcanon *m, const void *bp, size_t n)
+{
+ const char *p = bp, *l = p + n;
+ unsigned f = m->f;
+
+ while (p < l) {
+ if (!(f & (F_MIDLINE | F_DETACH)) &&
+ (*p == '.' || *p == '-'))
+ putc('.', m->fp);
+ if (*p == '\n') f &= ~F_MIDLINE;
+ else f |= F_MIDLINE;
+ putc(*p, m->fp);
+ p++;
+ }
+ m->f = f;
+}
+
+static size_t binreadembed(msgcanon *m, void *bp)
+ { return (chunk_read(m->e, bp)); }
+static size_t binreaddetach(msgcanon *m, void *bp)
+ { return (fread(bp, 1, MSGBUFSZ, m->fp)); }
+
+static void binwriteembed(msgcanon *m, const void *bp, size_t n)
+ { chunk_write(m->e, bp, n); }
+
+static void nullwrite(msgcanon *m, const void *bp, size_t n) { ; }
+
+static void mcsetup_readfile(msgcanon *m, unsigned f, const char *fn)
+{
+ m->f = F_DETACH | (f & F_BINARY);
+ if (!fn || strcmp(fn, "-") == 0) {
+ m->fp = stdin;
+ m->f |= F_NOCLOSE;
+ } else if ((m->fp = fopen(fn, (f & F_BINARY) ? "rb" : "r")) == 0)
+ die(EXIT_FAILURE, "couldn't open file `%s': %s", fn, strerror(errno));
+ m->read = (f & F_BINARY) ? binreaddetach : textread;
+}
+
+static void mcsetup_writenull(msgcanon *m) { m->write = nullwrite; }
+
+static void mcsetup_read(msgcanon *m, unsigned f, enc **ee, const char *dfn)
+{
+ enc *e = *ee;
+
+ m->f = f | F_NOCLOSE;
+
+ if (dfn && !(f & F_DETACH)) die(EXIT_FAILURE, "signature is not detached");
+ if (f & F_BINARY) {
+ if (!(f & F_DETACH)) {
+ m->e = e;
+ *ee = 0;
+ m->fp = e->fp;
+ m->read = binreadembed;
+ } else {
+ m->read = binreaddetach;
+ if (!dfn || strcmp(dfn, "-") == 0)
+ m->fp = stdin;
+ else if ((m->fp = fopen(dfn, "rb")) == 0)
+ die(EXIT_FAILURE, "can't open `%s': %s", dfn, strerror(errno));
+ else
+ m->f &= ~F_NOCLOSE;
+ }
+ } else {
+ m->read = textread;
+ if (!(f & F_DETACH)) {
+ if (e->ops->decdone(e) || ferror(e->fp))
+ die(EXIT_FAILURE, "error at end of signature header");
+ m->fp = e->fp;
+ e->ops->destroy(e);
+ *ee = 0;
+ } else if (!dfn || strcmp(dfn, "-") == 0)
+ m->fp = stdin;
+ else if ((m->fp = fopen(dfn, "r")) == 0)
+ die(EXIT_FAILURE, "can't read file `%s': %s", dfn, strerror(errno));
+ else
+ m->f &= ~F_NOCLOSE;
+ }
+}
+
+static void mcsetup_write(msgcanon *m, unsigned f, enc **ee)
+{
+ enc *e = *ee;
+
+ m->f = f | F_NOCLOSE;
+
+ if (f & F_DETACH)
+ m->write = nullwrite;
+ else if (f & F_BINARY) {
+ m->e = e;
+ *ee = 0;
+ m->fp = e->fp;
+ m->write = binwriteembed;
+ } else {
+ if (e->ops->encdone(e) || ferror(e->fp))
+ die(EXIT_FAILURE, "error at end of signature header");
+ m->fp = e->fp;
+ e->ops->destroy(e);
+ *ee = 0;
+ m->write = textwrite;
+ }
+}
+
+static void mc_endread(msgcanon *m, const encops *eops, enc **ee)
+{
+ if (!(m->f & F_DETACH)) {
+ if (m->f & F_BINARY)
+ *ee = m->e;
+ else
+ *ee = initdec(eops, m->fp, checkbdry, "CATSIGN SIGNATURE");
+ }
+ if (m->fp && !(m->f & F_NOCLOSE)) {
+ if (ferror(m->fp) || fclose(m->fp))
+ die(EXIT_FAILURE, "error closing message file: %s", strerror(errno));
+ }
+}
+
+static void mc_endwrite(msgcanon *m, const encops *eops, enc **ee)
+{
+ if (!(m->f & F_BINARY)) {
+ if (m->f & F_MIDLINE) putc('\n', m->fp);
+ if (!(m->f & F_DETACH)) {
+ putc('.', m->fp); putc('\n', m->fp);
+ *ee = initenc(eops, m->fp, "CATSIGN SIGNATURE");
+ }
+ } else if (!(m->f & F_DETACH)) {
+ chunk_write(m->e, 0, 0);
+ *ee = m->e;
+ }
+ if (m->fp && !(m->f & F_NOCLOSE)) {
+ if (fflush(m->fp) || ferror(m->fp) || fclose(m->fp))
+ die(EXIT_FAILURE, "error closing message file: %s", strerror(errno));
+ }
+}
+
+/*----- Signature reading and writing -------------------------------------*/
+
+static void sig_init(sigmsg *s, unsigned f, uint32 keyid)
+{
+ s->f = f & F_SIGMASK;
+ s->keyid = keyid;
+ time(&s->t);
+ s->s = 0;
+ dstr_create(&s->kh);
+ dstr_create(&s->sig);
+}
+
+static void sig_destroy(sigmsg *s)
+{
+ dstr_destroy(&s->kh);
+ dstr_destroy(&s->sig);
+ if (s->s) freesig(s->s);
+}
+
+static void sigtobuffer(sigmsg *s, buf *b, int hashp)
+{
+ kludge64 t;
+
+ ASSIGN64(t, s->t);
+ if (hashp) buf_putu16(b, s->f & F_HASHMASK);
+ else buf_putu16(b, s->f & F_SIGMASK);
+ buf_putu32(b, s->keyid);
+ buf_putu32(b, HI64(t));
+ buf_putu32(b, LO64(t));
+ buf_putstr16(b, &s->kh);
+ assert(BOK(b));
+}
+
+static void dohash(ghash *h, const void *p, size_t n)
+{
+/* trace_block(1, "hashing", p, n); */
+ GH_HASH(h, p, n);
+}
+
+static void sig_hash(sigmsg *s)
+{
+ octet bb[16384];
+ buf b;
+
+ buf_init(&b, bb, sizeof(bb));
+ sigtobuffer(s, &b, 1);
+ dohash(s->s->h, BBASE(&b), BLEN(&b));
+}
+
+static void keyhash(key *k, sig *s, dstr *d)
+{
+ ghash *h;
+ key_filter kf;
+
+ h = GH_INIT(GH_CLASS(s->h));
+ kf.f = KCAT_PUB;
+ kf.m = KF_CATMASK;
+ key_fingerprint(k, h, &kf);
+ dstr_ensure(d, GH_CLASS(h)->hashsz);
+ GH_DONE(h, d->buf + d->len);
+ d->len += GH_CLASS(h)->hashsz;
+ GH_DESTROY(h);
+}
+
+static void sig_writeheader(enc *e, sigmsg *s)
+{
+ octet bb[16384];
+ buf b;
+
+ buf_init(&b, bb, sizeof(bb));
+ sigtobuffer(s, &b, 0);
+ chunk_write(e, BBASE(&b), BLEN(&b));
+}
+
+static void sig_writesig(enc *e, sigmsg *s)
+ { chunk_write(e, s->sig.buf, s->sig.len); }
+
+static void diechoke(const char *m, void *p)
+ { die(EXIT_FAILURE, "%s%s%s", p, p ? ": " : "", m); }
+
+static void sig_readheader(enc *e, sigmsg *s,
+ void (*choke)(const char *, void *), void *p)
+{
+ uint16 f;
+ octet bb[MSGBUFSZ];
+ uint32 x, y;
+ kludge64 t;
+ buf b;
+ size_t n;
+
+ n = chunk_read(e, bb);
+ buf_init(&b, bb, n);
+ if (buf_getu16(&b, &f)) choke("missing flags", p);
+ if (buf_getu32(&b, &x)) choke("missing keyid", p);
+ sig_init(s, f, x);
+ if (buf_getu32(&b, &x) || buf_getu32(&b, &y))
+ choke("missing datestamp", p);
+ SET64(t, x, y); s->t = GET64(time_t, t);
+ if (buf_getstr16(&b, &s->kh))
+ choke("missing key hash", p);
+ if (BLEFT(&b))
+ choke("junk at end", p);
+}
+
+static void sig_readsig(enc *e, sigmsg *s)
+{
+ octet bb[MSGBUFSZ];
+ size_t n;
+
+ n = chunk_read(e, bb);
+ dstr_putm(&s->sig, bb, n);
+}
+
+/*----- Signing -----------------------------------------------------------*/
+
+static int sign(int argc, char *argv[])
+{
+ const char *ef = "binary", *fn = 0, *of = 0, *kn = "ccsig", *err;
+ unsigned f = 0;
+ key_file kf;
+ key *k;
+ sigmsg s;
+ FILE *ofp = 0;
+ int i;
+ char bb[MSGBUFSZ];
+ size_t n;
+ dstr d = DSTR_INIT;
+ const encops *eo;
+ msgcanon mc_in = MC_INIT, mc_out = MC_INIT;
+ enc *e;
+
+ for (;;) {
+ static const struct option opt[] = {
+ { "armour", 0, 0, 'a' },
+ { "armor", 0, 0, 'a' },
+ { "binary", 0, 0, 'b' },
+ { "detach", 0, 0, 'd' },
+ { "key", OPTF_ARGREQ, 0, 'k' },
+ { "format", OPTF_ARGREQ, 0, 'f' },
+ { "output", OPTF_ARGREQ, 0, 'o' },
+ { "text", 0, 0, 't' },
+ { 0, 0, 0, 0 }
+ };
+ i = mdwopt(argc, argv, "k:f:o:abdt", opt, 0, 0, 0);
+ if (i < 0) break;
+ switch (i) {
+ case 'k': kn = optarg; break;
+ case 'f': ef = optarg; break;
+ case 'o': of = optarg; break;
+ case 'a': ef = "pem"; break;
+ case 't': f &= ~F_BINARY; break;
+ case 'b': f |= F_BINARY; break;
+ case 'd': f |= F_DETACH; break;
+ default: f |= F_BOGUS; break;
+ }
+ }
+ if (argc - optind > 1 || (f & F_BOGUS))
+ die(EXIT_FAILURE, "Usage: sign [-OPTIONS] [FILE]");
+
+ if (key_open(&kf, keyring, KOPEN_READ, keyreport, 0))
+ die(EXIT_FAILURE, "can't open keyring `%s'", keyring);
+ if ((k = key_bytag(&kf, kn)) == 0)
+ die(EXIT_FAILURE, "key `%s' not found", kn);
+
+ if ((eo = getenc(ef)) == 0)
+ die(EXIT_FAILURE, "encoding `%s' not found", ef);
+
+ fn = (optind >= argc) ? 0 : argv[optind++];
+
+ if (!of || strcmp(of, "-") == 0)
+ ofp = stdout;
+ else if ((ofp = fopen(of, eo->wmode)) == 0) {
+ die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
+ ofp, strerror(errno));
+ }
+
+ /* --- Start the work --- */
+
+ sig_init(&s, f, k->id);
+ dstr_reset(&d);
+ key_fulltag(k, &d);
+ s.s = getsig(k, "ccsig", 1);
+ if ((err = s.s->ops->check(s.s)) != 0)
+ moan("key %s fails check: %s", d.buf, err);
+ keyhash(k, s.s, &s.kh);
+ e = initenc(eo, ofp,
+ (f & F_DETACH) ? "CATSIGN SIGNATURE" :
+ (f & F_BINARY) ? "CATSIGN MESSAGE" :
+ "CATSIGN MESSAGE HEADER");
+ sig_writeheader(e, &s);
+
+ /* --- Hash the message --- */
+
+ mcsetup_readfile(&mc_in, f, fn);
+ mcsetup_write(&mc_out, f, &e);
+ sig_hash(&s);
+ for (;;) {
+ n = mc_in.read(&mc_in, bb);
+ if (!n) break;
+ dohash(s.s->h, bb, n);
+ mc_out.write(&mc_out, bb, n);
+ }
+ mc_endread(&mc_in, 0, 0);
+ mc_endwrite(&mc_out, eo, &e);
+
+ /* --- Write the signature --- */
+
+ if (s.s->ops->doit(s.s, &s.sig))
+ die(EXIT_FAILURE, "signature failed");
+ sig_writesig(e, &s);
+ e->ops->encdone(e);
+ if (fflush(ofp) || ferror(ofp) || fclose(ofp))
+ die(EXIT_FAILURE, "error writing signature: %s", strerror(errno));
+
+ /* --- All done --- */
+
+ freeenc(e);
+ key_close(&kf);
+ sig_destroy(&s);
+ dstr_destroy(&d);
+ return (0);
+}
+
+/*----- Verifying ---------------------------------------------------------*/
+
+typedef struct vrfctx {
+ unsigned f, m;
+ int verb;
+ const char *what;
+} vrfctx;
+
+static int vrfbdry(const char *b, void *p)
+{
+ vrfctx *v = p;
+
+ if (strcmp(b, "CATSIGN MESSAGE") == 0) {
+ v->f |= F_BINARY;
+ v->m |= F_BINARY | F_DETACH;
+ return (1);
+ } else if (strcmp(b, "CATSIGN MESSAGE HEADER") == 0) {
+ v->m |= F_BINARY | F_DETACH;
+ return (1);
+ } else if (strcmp(b, "CATSIGN SIGNATURE") == 0) {
+ v->f |= F_DETACH;
+ v->m |= F_DETACH;
+ return (1);
+ } else
+ return (0);
+}
+
+static void vrfchoke(const char *m, void *p)
+{
+ vrfctx *v = p;
+ if (v->verb) printf("FAIL %s: %s\n", v->what, m);
+ exit(EXIT_FAILURE);
+}
+
+static int verify(int argc, char *argv[])
+{
+ const char *ef = "binary", *of = 0, *dfn = 0, *kn = 0, *err;
+ vrfctx v = { 0, 0, 1 };
+ key_file kf;
+ key *k, *kk = 0;
+ sigmsg s;
+ FILE *fp, *ofp = 0, *rfp = 0;
+ struct tm *tm;
+ int i;
+ char bb[MSGBUFSZ];
+ size_t n;
+ dstr d = DSTR_INIT, dd = DSTR_INIT;
+ const encops *eo;
+ msgcanon mc_in = MC_INIT;
+ enc *e;
+
+ for (;;) {
+ static const struct option opt[] = {
+ { "armour", 0, 0, 'a' },
+ { "armor", 0, 0, 'a' },
+ { "buffer", 0, 0, 'b' },
+ { "key", OPTF_ARGREQ, 0, 'k' },
+ { "format", OPTF_ARGREQ, 0, 'f' },
+ { "output", OPTF_ARGREQ, 0, 'o' },
+ { "quiet", 0, 0, 'q' },
+ { "utc", 0, 0, 'u' },
+ { "gmt", 0, 0, 'u' },
+ { "verbose", 0, 0, 'v' },
+ { 0, 0, 0, 0 }
+ };
+ i = mdwopt(argc, argv, "k:f:o:abquv", opt, 0, 0, 0);
+ if (i < 0) break;
+ switch (i) {
+ case 'a': ef = "pem"; break;
+ case 'b': v.f |= F_BUFFER; break;
+ case 'k': kn = optarg; break;
+ case 'f': ef = optarg; break;
+ case 'o': of = optarg; break;
+ case 'u': v.f |= F_UTC; break;
+ case 'q': if (v.verb > 0) v.verb--; break;
+ case 'v': if (v.verb < 10) v.verb++; break;
+ default: v.f |= F_BOGUS; break;
+ }
+ }
+ if (argc - optind > 2 || (v.f & F_BOGUS))
+ die(EXIT_FAILURE, "Usage: verify [-OPTIONS] [FILE [MESSAGE]]");
+
+ if ((eo = getenc(ef)) == 0)
+ die(EXIT_FAILURE, "encoding `%s' not found", ef);
+
+ if (optind >= argc)
+ fp = stdin;
+ else if (strcmp(argv[optind], "-") == 0) {
+ fp = stdin;
+ optind++;
+ } else if ((fp = fopen(argv[optind], eo->rmode)) == 0) {
+ die(EXIT_FAILURE, "couldn't open file `%s': %s",
+ argv[optind], strerror(errno));
+ } else
+ optind++;
+
+ if (key_open(&kf, keyring, KOPEN_READ, keyreport, 0))
+ die(EXIT_FAILURE, "can't open keyring `%s'", keyring);
+ if (kn && (kk = key_bytag(&kf, kn)) == 0)
+ die(EXIT_FAILURE, "key `%s' not found", kn);
+
+ e = initdec(eo, fp, vrfbdry, &v);
+
+ /* --- Read the header chunk --- */
+
+ v.what = "malformed header";
+ sig_readheader(e, &s, vrfchoke, &v);
+
+ if (((s.f ^ v.f) & v.m) != 0) {
+ if (v.verb) printf("FAIL boundary string inconsistent with contents\n");
+ exit(EXIT_FAILURE);
+ }
+ v.f |= s.f;
+
+ if ((k = key_byid(&kf, s.keyid)) == 0) {
+ if (v.verb) printf("FAIL key id %08lx not found\n",
+ (unsigned long)s.keyid);
+ exit(EXIT_FAILURE);
+ }
+ if (kk && k->id != kk->id) {
+ if (v.verb) {
+ dstr_reset(&d); key_fulltag(k, &d);
+ dstr_reset(&dd); key_fulltag(kk, &dd);
+ printf("FAIL signing key is %s; expected key %s\n", d.buf, dd.buf);
+ }
+ exit(EXIT_FAILURE);
+ }
+
+ s.s = getsig(k, "ccsig", 0);
+ dstr_reset(&d); key_fulltag(k, &d);
+ if (v.verb && (err = s.s->ops->check(s.s)) != 0)
+ printf("WARN verification key %s fails check: %s\n", d.buf, err);
+
+ dstr_reset(&dd); keyhash(k, s.s, &dd);
+ if (dd.len != s.kh.len || memcmp(dd.buf, s.kh.buf, dd.len) != 0) {
+ if (v.verb) printf("FAIL key hash mismatch\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* --- Now a merry dance --- */
+
+ if (v.f & F_DETACH)
+ sig_readsig(e, &s);
+ if (optind < argc)
+ dfn = argv[optind++];
+ mcsetup_read(&mc_in, v.f, &e, dfn);
+
+ if (!of && (v.f & F_DETACH)) {
+ rfp = ofp = 0;
+ v.f &= ~F_BUFFER;
+ } else if (!of || strcmp(of, "-") == 0) {
+ v.f |= F_BUFFER;
+ ofp = stdout;
+ }
+ if (of && !(v.f & F_BUFFER)) {
+ if ((ofp = fopen(of, (v.f & F_BINARY) ? "wb" : "w")) == 0) {
+ die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
+ of, strerror(errno));
+ }
+ rfp = ofp;
+ } else if ((rfp = tmpfile()) == 0)
+ die(EXIT_FAILURE, "couldn't create temporary file: %s", strerror(errno));
+
+ /* --- Read the message and verify the signature --- */
+
+ sig_hash(&s);
+ for (;;) {
+ n = mc_in.read(&mc_in, bb);
+ if (!n) break;
+ dohash(s.s->h, bb, n);
+ if (rfp) fwrite(bb, 1, n, rfp);
+ }
+ mc_endread(&mc_in, eo, &e);
+ if (!(v.f & F_DETACH))
+ sig_readsig(e, &s);
+ if (rfp && (ferror(rfp) || fflush(rfp))) {
+ if (v.verb) printf("FAIL error writing message: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ /* --- Check the signature --- */
+
+ if (s.s->ops->doit(s.s, &s.sig)) {
+ if (v.verb) printf("FAIL signature verification failed\n");
+ exit(EXIT_FAILURE);
+ }
+ if (v.verb) {
+ tm = (v.f & F_UTC) ? gmtime(&s.t) : localtime(&s.t);
+ strftime(bb, sizeof(bb), "%Y-%m-%d %H:%M:%S %Z", tm);
+ printf("INFO good-signature %s\n", d.buf);
+ printf("INFO date %s\n", bb);
+ }
+
+ /* --- Unbuffer buffered input --- */
+
+ if (v.f & F_BUFFER) {
+ if (!ofp && (ofp = fopen(of, "wb")) == 0) {
+ die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
+ of, strerror(errno));
+ }
+ rewind(rfp);
+ if (v.verb && ofp == stdout) printf("DATA\n");
+ for (;;) {
+ n = fread(bb, 1, sizeof(bb), rfp);
+ if (!n) break;
+ if (fwrite(bb, 1, n, ofp) < n)
+ die(EXIT_FAILURE, "error writing output: %s", strerror(errno));
+ }
+ if (ferror(rfp) || fclose(rfp))
+ die(EXIT_FAILURE, "error unbuffering output: %s", strerror(errno));
+ }
+ if (ofp && (fflush(ofp) || ferror(ofp) || fclose(ofp)))
+ die(EXIT_FAILURE, "error writing output: %s", strerror(errno));
+
+ /* --- Tidy up --- */
+
+ e->ops->decdone(e);
+ if (v.verb && ofp != stdout)
+ printf("OK verified successfully\n");
+ freeenc(e);
+ key_close(&kf);
+ sig_destroy(&s);
+ dstr_destroy(&d);
+ dstr_destroy(&dd);
+ return (0);
+}
+
+/*----- Reformatting ------------------------------------------------------*/
+
+static int format(int argc, char *argv[])
+{
+ const char *ief = "binary", *oef = "binary";
+ const char *dfn = 0, *of = 0, *mf = 0;
+ sigmsg s;
+ FILE *fp, *ofp = 0, *mfp = 0;
+ int i;
+ size_t n;
+ msgcanon mc_in = MC_INIT, mc_out = MC_INIT;
+ char bb[MSGBUFSZ];
+ vrfctx v = { 0, 0, 1 };
+ unsigned f = 0, fm = ~F_SIGMASK, sf;
+ const encops *ieo, *oeo;
+ enc *ie, *oe;
+
+ for (;;) {
+ static const struct option opt[] = {
+ { "armour-in", 0, 0, 'a' },
+ { "armor-in", 0, 0, 'a' },
+ { "armour-out", 0, 0, 'A' },
+ { "armor-out", 0, 0, 'A' },
+ { "detach", 0, 0, 'D' },
+ { "embed", 0, 0, 'E' },
+ { "format-in", OPTF_ARGREQ, 0, 'f' },
+ { "format-out", OPTF_ARGREQ, 0, 'F' },
+ { "message", OPTF_ARGREQ, 0, 'm' },
+ { "output", OPTF_ARGREQ, 0, 'o' },
+ { 0, 0, 0, 0 }
+ };
+ i = mdwopt(argc, argv, "f:F:m:o:aADE", opt, 0, 0, 0);
+ if (i < 0) break;
+ switch (i) {
+ case 'a': ief = "pem"; break;
+ case 'A': oef = "pem"; break;
+ case 'f': ief = optarg; break;
+ case 'F': oef = optarg; break;
+ case 'D': f |= F_DETACH; fm |= F_DETACH; break;
+ case 'E': f &= ~F_DETACH; fm |= F_DETACH; break;
+ case 'm': mf = optarg; break;
+ case 'o': of = optarg; break;
+ default: f |= F_BOGUS; break;
+ }
+ }
+
+ if (argc - optind > 2 || (f & F_BOGUS))
+ die(EXIT_FAILURE, "Usage: format [-OPTIONS] [FILE [MESSAGE]]");
+
+ if ((ieo = getenc(ief)) == 0)
+ die(EXIT_FAILURE, "encoding `%s' not found", ief);
+ if ((oeo = getenc(oef)) == 0)
+ die(EXIT_FAILURE, "encoding `%s' not found", oef);
+
+ if (optind >= argc)
+ fp = stdin;
+ else if (strcmp(argv[optind], "-") == 0) {
+ fp = stdin;
+ optind++;
+ } else if ((fp = fopen(argv[optind], ieo->rmode)) == 0) {
+ die(EXIT_FAILURE, "couldn't open file `%s': %s",
+ argv[optind], strerror(errno));
+ } else
+ optind++;
+
+ if (optind < argc)
+ dfn = argv[optind++];
+
+ ie = initdec(ieo, fp, vrfbdry, &v);
+
+ /* --- Read the header chunk --- */
+
+ sig_readheader(ie, &s, diechoke, "malformed header");
+
+ if (((s.f ^ v.f) & v.m) != 0)
+ moan("boundary string inconsistent with contents (ignoring)");
+
+ mcsetup_read(&mc_in, s.f, &ie, dfn);
+
+ /* --- Prepare the output stuff --- */
+
+ if (!of && !mf) of = "-";
+ sf = s.f;
+ f = (f & fm) | (s.f & ~fm);
+ s.f = f & F_SIGMASK;
+
+ if (sf & F_DETACH)
+ sig_readsig(ie, &s);
+
+ if (!of)
+ mcsetup_writenull(&mc_out);
+ else {
+ if (strcmp(of, "-") == 0)
+ ofp = stdout;
+ else if ((ofp = fopen(of, oeo->wmode)) == 0) {
+ die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
+ of, strerror(errno));
+ }
+ oe = initenc(oeo, ofp,
+ (f & F_DETACH) ? "CATSIGN SIGNATURE" :
+ (f & F_BINARY) ? "CATSIGN MESSAGE" :
+ "CATSIGN MESSAGE HEADER");
+ sig_writeheader(oe, &s);
+ mcsetup_write(&mc_out, f, &oe);
+ }
+
+ if (mf) {
+ if (strcmp(mf, "-") == 0)
+ mfp = stdout;
+ else if ((mfp = fopen(mf, (f & F_BINARY) ? "wb" : "w")) == 0) {
+ die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
+ mf, strerror(errno));
+ }
+ }
+
+ /* --- Wade through the message body --- */
+
+ for (;;) {
+ n = mc_in.read(&mc_in, bb);
+ if (!n) break;
+ mc_out.write(&mc_out, bb, n);
+ if (mfp) fwrite(bb, 1, n, mfp);
+ }
+ mc_endread(&mc_in, ieo, &ie);
+ if (of) mc_endwrite(&mc_out, oeo, &oe);
+
+ /* --- Write the signature --- */
+
+ if (!(sf & F_DETACH))
+ sig_readsig(ie, &s);
+ if (of) {
+ sig_writesig(oe, &s);
+ oe->ops->encdone(oe);
+ }
+
+ /* --- All done --- */
+
+ ie->ops->decdone(ie);
+ if (ferror(fp) || fclose(fp))
+ die(EXIT_FAILURE, "error reading input signature: %s", strerror(errno));
+ if (ofp && (fflush(ofp) || ferror(ofp) || fclose(ofp)))
+ die(EXIT_FAILURE, "error writing output signature: %s", strerror(errno));
+ if (mfp && (fflush(mfp) || ferror(mfp) || fclose(mfp)))
+ die(EXIT_FAILURE, "error writing output message: %s", strerror(errno));
+ freeenc(ie);
+ if (of) freeenc(oe);
+ sig_destroy(&s);
+ return (0);
+}
+
+static void infochoke(const char *m, void *p)
+{
+ vrfctx *v = p;
+ printf("BAD %s: %s\n", v->what, m);
+ exit(EXIT_FAILURE);
+}
+
+static void infokeyreport(const char *file, int line,
+ const char *err, void *p)
+{ /*whatever*/; }
+
+static int info(int argc, char *argv[])
+{
+ const char *ef = "binary";
+ vrfctx v = { 0, 0, 1 };
+ key_file kf;
+ key *k;
+ sigmsg s;
+ FILE *fp;
+ int i;
+ struct tm *tm;
+ char bb[256];
+ dstr d = DSTR_INIT;
+ const encops *eo;
+ enc *e;
+
+ for (;;) {
+ static const struct option opt[] = {
+ { "armour", 0, 0, 'a' },
+ { "armor", 0, 0, 'a' },
+ { "format", OPTF_ARGREQ, 0, 'f' },
+ { "gmt", 0, 0, 'u' },
+ { "utc", 0, 0, 'u' },
+ { 0, 0, 0, 0 }
+ };
+ i = mdwopt(argc, argv, "f:au", opt, 0, 0, 0);
+ if (i < 0) break;
+ switch (i) {
+ case 'a': ef = "pem"; break;
+ case 'f': ef = optarg; break;
+ case 'u': v.f |= F_UTC; break;
+ default: v.f |= F_BOGUS; break;
+ }
+ }
+ if (argc - optind > 1 || (v.f & F_BOGUS))
+ die(EXIT_FAILURE, "Usage: info [-OPTIONS] [FILE]");
+
+ if ((eo = getenc(ef)) == 0)
+ die(EXIT_FAILURE, "encoding `%s' not found", ef);
+
+ if (optind >= argc)
+ fp = stdin;
+ else if (strcmp(argv[optind], "-") == 0) {
+ fp = stdin;
+ optind++;
+ } else if ((fp = fopen(argv[optind], eo->rmode)) == 0) {
+ die(EXIT_FAILURE, "couldn't open file `%s': %s",
+ argv[optind], strerror(errno));
+ } else
+ optind++;
+
+ if (key_open(&kf, keyring, KOPEN_READ, infokeyreport, 0)) {
+ printf("NOTE can't open keyring `%s'\n", keyring);
+ keyring = 0;
+ }
+ e = initdec(eo, fp, vrfbdry, &v);
+
+ v.what = "malformed header";
+ sig_readheader(e, &s, infochoke, &v);
+
+ printf("INFO flags %sdetach %sbinary\n",
+ (s.f & F_DETACH) ? "" : "!",
+ (s.f & F_BINARY) ? "" : "!");
+
+ if (((s.f ^ v.f) & v.m) != 0) {
+ printf("WARN boundary string inconsistent with contents\n");
+ printf("INFO expected-flags");
+ if (v.m & F_DETACH) printf(" %sdetach", (v.f & F_DETACH) ? "" : "!");
+ if (v.m & F_BINARY) printf(" %sbinary", (v.f & F_BINARY) ? "" : "!");
+ putchar('\n');
+ }
+ v.f |= s.f;
+
+ tm = (v.f & F_UTC) ? gmtime(&s.t) : localtime(&s.t);
+ strftime(bb, sizeof(bb), "%Y-%m-%d %H:%M:%S %Z", tm);
+ printf("INFO date %s\n", bb);
+
+ if (keyring && (k = key_byid(&kf, s.keyid)) != 0) {
+ dstr_reset(&d); key_fulltag(k, &d);
+ printf("INFO key %s\n", d.buf);
+ } else
+ printf("INFO unknown-key %08lx\n", (unsigned long)s.keyid);
+
+ if (keyring) key_close(&kf);
+ dstr_destroy(&d);
+ sig_destroy(&s);
+ return (0);
+}
+
+/*----- Main code ---------------------------------------------------------*/
+
+#define LISTS(LI) \
+ LI("Lists", list, \
+ listtab[i].name, listtab[i].name) \
+ LI("Signature schemes", sig, \
+ sigtab[i].name, sigtab[i].name) \
+ LI("Encodings", enc, \
+ enctab[i].name, enctab[i].name) \
+ LI("Hash functions", hash, \
+ ghashtab[i], ghashtab[i]->name)
+
+MAKELISTTAB(listtab, LISTS)
+
+int cmd_show(int argc, char *argv[])
+{
+ return (displaylists(listtab, argv + 1));
+}
+
+static int cmd_help(int, char **);
+
+static cmd cmdtab[] = {
+ { "help", cmd_help, "help [COMMAND...]" },
+ { "show", cmd_show, "show [ITEM...]" },
+ CMD_ENCODE,
+ CMD_DECODE,
+ { "sign", sign,
+ "sign [-adt] [-k TAG] [-f FORMAT] [-o OUTPUT] [FILE]", "\
+Options:\n\
+\n\
+-a, --armour Same as `-f pem'.\n\
+-b, --binary Treat the input message as binary data.\n\
+-d, --detach Produce a detached signature.\n\
+-f, --format=FORMAT Encode as FORMAT.\n\
+-k, --key=TAG Use public encryption key named by TAG.\n\
+-o, --output=FILE Write output to FILE.\n\
+-t, --text Canonify input message as a text file.\n\
+" },
+ { "verify", verify,
+ "verify [-abquv] [-f FORMAT] [-k TAG] [-o OUTPUT]\n\t\
+[FILE [MESSAGE]]", "\
+Options:\n\
+\n\
+-a, --armour Same as `-f pem'.\n\
+-b, --buffer Buffer message until signature is verified.\n\
+-f, --format=FORMAT Decode as FORMAT.\n\
+-k, --key=TAG Require that the message be signed by key TAG.\n\
+-o, --output=FILE Write message to FILE.\n\
+-q, --quiet Produce fewer messages.\n\
+-u, --utc Show dates in UTC rather than local time.\n\
+-v, --verbose Produce more verbose messages.\n\
+" },
+ { "info", info,
+ "info [-au] [-f FORMAT] [FILE]", "\
+Options:\n\
+\n\
+-a, --armour Same as `-f pem'.\n\
+-f, --format=FORMAT Decode as FORMAT.\n\
+-u, --utc Show dates in UTC rather than local time.\n\
+"},
+ { "format", format,
+ "format [-auADE] [-f FORMAT] [-F format] [-m FILE] [-o FILE]\n\t\
+[FILE [MESSAGE]]", "\
+Options:\n\
+\n\
+-a, --armour-in Same as `-f pem'.\n\
+-A, --armour-out Same as `-F pem'.\n\
+-D, --detach Create detached signature.\n\
+-E, --embed Create signature with embedded message.\n\
+-f, --format-in=FORMAT Decode input as FORMAT.\n\
+-F, --format-out=FORMAT Encode output as FORMAT.\n\
+-m, --message=FILE Write message to FILE.\n\
+-o, --output=FILE Write new signature to FILE.\n\
+"},
+ { 0, 0, 0 }
+}; /* " Emacs seems confused. */
+
+static int cmd_help(int argc, char **argv)
+{
+ sc_help(cmdtab, stdout, argv + 1);
+ return (0);
+}
+
+void version(FILE *fp)
+{
+ pquis(fp, "$, Catacomb version " VERSION "\n");
+}
+
+static void usage(FILE *fp)
+{
+ pquis(fp, "Usage: $ [-k KEYRING] COMMAND [ARGS]\n");
+}
+
+void help_global(FILE *fp)
+{
+ usage(fp);
+ fputs("\n\
+Sign and verify data.\n\
+\n\
+Global command-line options:\n\
+\n\
+-h, --help [COMMAND...] Show this help message, or help for COMMANDs.\n\
+-v, --version Show program version number.\n\
+-u, --usage Show a terse usage message.\n\
+\n\
+-k, --keyring=FILE Read keys from FILE.\n",
+ fp);
+}
+
+/* --- @main@ --- *
+ *
+ * Arguments: @int argc@ = number of command line arguments
+ * @char *argv[]@ = vector of command line arguments
+ *
+ * Returns: Zero if successful, nonzero otherwise.
+ *
+ * Use: Encrypts or decrypts files.
+ */
+
+int main(int argc, char *argv[])
+{
+ unsigned f = 0;
+
+#define f_bogus 1u
+
+ /* --- Initialize the library --- */
+
+ ego(argv[0]);
+ sub_init();
+ rand_noisesrc(RAND_GLOBAL, &noise_source);
+ rand_seed(RAND_GLOBAL, 160);
+/* trace_on(stderr, 1); */
+
+ /* --- Parse options --- */
+
+ for (;;) {
+ static struct option opts[] = {
+ { "help", 0, 0, 'h' },
+ { "version", 0, 0, 'v' },
+ { "usage", 0, 0, 'u' },
+ { "keyring", OPTF_ARGREQ, 0, 'k' },
+ { 0, 0, 0, 0 }
+ };
+ int i = mdwopt(argc, argv, "+hvu k:", opts, 0, 0, 0);
+ if (i < 0)
+ break;
+ switch (i) {
+ case 'h':
+ sc_help(cmdtab, stdout, argv + optind);
+ exit(0);
+ break;
+ case 'v':
+ version(stdout);
+ exit(0);
+ break;
+ case 'u':
+ usage(stdout);
+ exit(0);
+ case 'k':
+ keyring = optarg;
+ break;
+ default:
+ f |= f_bogus;
+ break;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+ optind = 0;
+ if (f & f_bogus || argc < 1) {
+ usage(stderr);
+ exit(EXIT_FAILURE);
+ }
+
+ /* --- Dispatch to the correct subcommand handler --- */
+
+ return (findcmd(cmdtab, argv[0])->cmd(argc, argv));
+
+#undef f_bogus
+}
+
+/*----- That's all, folks -------------------------------------------------*/