3 * $Id: catcrypt.c,v 1.2 2004/05/09 13:03:46 mdw Exp $
5 * Command-line encryption tool
7 * (c) 2004 Straylight/Edgeware
10 /*----- Licensing notice --------------------------------------------------*
12 * This file is part of Catacomb.
14 * Catacomb is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU Library General Public License as
16 * published by the Free Software Foundation; either version 2 of the
17 * License, or (at your option) any later version.
19 * Catacomb is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU Library General Public License for more details.
24 * You should have received a copy of the GNU Library General Public
25 * License along with Catacomb; if not, write to the Free
26 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
30 /*----- Header files ------------------------------------------------------*/
39 #include <mLib/base64.h>
40 #include <mLib/dstr.h>
41 #include <mLib/mdwopt.h>
42 #include <mLib/quis.h>
43 #include <mLib/report.h>
53 /*----- Utilities ---------------------------------------------------------*/
55 /* --- @keyreport@ --- *
57 * Arguments: @const char *file@ = filename containing the error
58 * @int line@ = line number in file
59 * @const char *err@ = error text message
60 * @void *p@ = unimportant pointer
64 * Use: Reports errors during the opening of a key file.
67 static void keyreport(const char *file
, int line
, const char *err
, void *p
)
69 moan("error in keyring `%s' at line `%s': %s", file
, line
, err
);
72 /*----- Static variables --------------------------------------------------*/
74 static const char *keyring
= "keyring";
76 /*----- Data format -------------------------------------------------------*/
80 * The encrypted message is divided into chunks, each preceded by a two-octet
81 * length. The chunks don't need to be large -- the idea is that we can
82 * stream the chunks in and out.
84 * The first chunk is a header. It contains the decryption key-id, and maybe
85 * the verification key-id if the message is signed.
87 * Next comes the key-encapsulation chunk. This is decrypted in some
88 * KEM-specific way to yield a secret hash. This hash is what is signed if
89 * the message is signed. The hash is expanded using an MGF (or similar) to
90 * make a symmetric encryption and MAC key.
92 * If the message is signed, there comes a signature chunk. The signature is
93 * on the secret hash. This means that the recipient can modify the message
94 * and still have a valid signature, so it's not useful for proving things to
95 * other people; but it also means that the recipient knows that the message
96 * is from someone who knows the hash, which limits the possiblities to (a)
97 * whoever encrypted the message (good!) and (b) whoever knows the
98 * recipient's private key.
100 * Then come message chunks. Each one begins with a MAC over an implicit
101 * sequence number and the ciphertext. The final chunk's ciphertext is
102 * empty; no other chunk is empty. Thus can the correct end-of-file be
106 /*----- Chunk I/O ---------------------------------------------------------*/
108 static void chunk_write(enc
*e
, buf
*b
)
114 if (e
->ops
->write(e
, l
, 2) ||
115 e
->ops
->write(e
, BBASE(b
), BLEN(b
)))
116 die(EXIT_FAILURE
, "error writing output: %s", strerror(errno
));
119 static void chunk_read(enc
*e
, dstr
*d
, buf
*b
)
126 if (e
->ops
->read(e
, l
, 2) != 2)
130 if (e
->ops
->read(e
, d
->buf
, n
) != n
)
133 buf_init(b
, d
->buf
, d
->len
);
137 if (!errno
) die(EXIT_FAILURE
, "unexpected end-of-file on input");
138 else die(EXIT_FAILURE
, "error reading input: %s", strerror(errno
));
141 /*----- Encryption --------------------------------------------------------*/
143 static int encrypt(int argc
, char *argv
[])
145 const char *of
= 0, *kn
= "ccrypt";
148 const char *ef
= "binary";
170 static const struct option opt
[] = {
171 { "key", OPTF_ARGREQ
, 0, 'k' },
172 { "armour", 0, 0, 'a' },
173 { "armor", 0, 0, 'a' },
174 { "format", OPTF_ARGREQ
, 0, 'f' },
175 { "output", OPTF_ARGREQ
, 0, 'o' },
178 i
= mdwopt(argc
, argv
, "k:af:o:", opt
, 0, 0, 0);
181 case 'k': kn
= optarg
; break;
182 case 'a': ef
= "pem"; break;
183 case 'f': ef
= optarg
; break;
184 case 'o': of
= optarg
; break;
185 default: f
|= f_bogus
; break;
188 if (argc
- optind
> 1 || (f
& f_bogus
))
189 die(EXIT_FAILURE
, "Usage: encrypt [-options] [file]");
191 if (key_open(&kf
, keyring
, KOPEN_READ
, keyreport
, 0))
192 die(EXIT_FAILURE
, "can't open keyring `%s'", keyring
);
193 if ((k
= key_bytag(&kf
, kn
)) == 0)
194 die(EXIT_FAILURE
, "key `%s' not found", kn
);
196 if ((eo
= getenc(ef
)) == 0)
197 die(EXIT_FAILURE
, "encoding `%s' not found", ef
);
201 else if (strcmp(argv
[optind
], "-") == 0) {
204 } else if ((fp
= fopen(argv
[optind
], "rb")) == 0) {
205 die(EXIT_FAILURE
, "couldn't open file `%s': %s",
206 argv
[optind
], strerror(errno
));
210 if (!of
|| strcmp(of
, "-") == 0)
212 else if ((ofp
= fopen(of
, eo
->wmode
)) == 0) {
213 die(EXIT_FAILURE
, "couldn't open file `%s' for output: %s",
214 ofp
, strerror(errno
));
218 e
= initenc(eo
, ofp
, "CATCRYPT ENCRYPTED MESSAGE", 1);
219 km
= getkem(k
, "ccrypt", 0);
220 if ((err
= km
->ops
->check(km
)) != 0)
221 moan("key `%s' fails check: %s", d
.buf
, err
);
223 /* --- Build the header chunk --- */
226 dstr_ensure(&d
, 256);
227 buf_init(&b
, d
.buf
, 256);
228 buf_putu32(&b
, k
->id
);
232 /* --- Build the KEM chunk --- */
235 if (setupkem(km
, &d
, &cx
, &c
, &m
))
236 die(EXIT_FAILURE
, "failed to encapsulate key");
237 buf_init(&b
, d
.buf
, d
.len
);
241 /* --- Now do the main crypto --- */
243 assert(GC_CLASS(c
)->blksz
<= sizeof(bb
));
244 dstr_ensure(&d
, sizeof(bb
) + GM_CLASS(m
)->hashsz
);
251 if (GC_CLASS(c
)->blksz
) {
252 GC_ENCRYPT(cx
, 0, bb
, GC_CLASS(c
)->blksz
);
255 n
= fread(bb
, 1, sizeof(bb
), fp
);
257 buf_init(&b
, d
.buf
, d
.sz
);
258 tag
= buf_get(&b
, GM_CLASS(m
)->hashsz
);
260 assert(tag
); assert(ct
);
261 GC_ENCRYPT(c
, bb
, ct
, n
);
268 /* --- Final terminator packet --- */
270 buf_init(&b
, d
.buf
, d
.sz
);
271 tag
= buf_get(&b
, GM_CLASS(m
)->hashsz
);
277 /* --- All done --- */
293 /*---- Decryption ---------------------------------------------------------*/
295 static int decrypt(int argc
, char *argv
[])
300 const char *ef
= "binary";
321 static const struct option opt
[] = {
322 { "armour", 0, 0, 'a' },
323 { "armor", 0, 0, 'a' },
324 { "format", OPTF_ARGREQ
, 0, 'f' },
325 { "output", OPTF_ARGREQ
, 0, 'o' },
328 i
= mdwopt(argc
, argv
, "af:o:", opt
, 0, 0, 0);
331 case 'a': ef
= "pem"; break;
332 case 'f': ef
= optarg
; break;
333 case 'o': of
= optarg
; break;
334 default: f
|= f_bogus
; break;
337 if (argc
- optind
> 1 || (f
& f_bogus
))
338 die(EXIT_FAILURE
, "Usage: decrypt [-options] [file]");
340 if ((eo
= getenc(ef
)) == 0)
341 die(EXIT_FAILURE
, "encoding `%s' not found", ef
);
345 else if (strcmp(argv
[optind
], "-") == 0) {
348 } else if ((fp
= fopen(argv
[optind
], eo
->rmode
)) == 0) {
349 die(EXIT_FAILURE
, "couldn't open file `%s': %s",
350 argv
[optind
], strerror(errno
));
354 if (key_open(&kf
, keyring
, KOPEN_READ
, keyreport
, 0))
355 die(EXIT_FAILURE
, "can't open keyring `%s'", keyring
);
357 e
= initenc(eo
, fp
, "CATCRYPT ENCRYPTED MESSAGE", 0);
359 /* --- Read the header chunk --- */
361 chunk_read(e
, &d
, &b
);
362 if (buf_getu32(&b
, &id
))
363 die(EXIT_FAILURE
, "malformed header: missing keyid");
365 die(EXIT_FAILURE
, "malformed header: junk at end");
367 /* --- Find the key --- */
369 if ((k
= key_byid(&kf
, id
)) == 0)
370 die(EXIT_FAILURE
, "key id %08lx not found", (unsigned long)id
);
371 km
= getkem(k
, "ccrypt", 1);
373 /* --- Read the KEM chunk --- */
375 chunk_read(e
, &d
, &b
);
376 if (setupkem(km
, &d
, &cx
, &c
, &m
))
377 die(EXIT_FAILURE
, "failed to decapsulate key");
379 /* --- Now decrypt the main body --- */
381 if (!of
|| strcmp(of
, "-") == 0)
383 else if ((ofp
= fopen(of
, "wb")) == 0) {
384 die(EXIT_FAILURE
, "couldn't open file `%s' for output: %s",
385 ofp
, strerror(errno
));
389 dstr_ensure(&d
, GC_CLASS(c
)->blksz
);
392 if (GC_CLASS(c
)->blksz
) {
393 GC_ENCRYPT(cx
, 0, d
.buf
, GC_CLASS(c
)->blksz
);
398 GH_HASH(h
, d
.buf
, 4);
400 chunk_read(e
, &d
, &b
);
401 if ((tag
= buf_get(&b
, GM_CLASS(m
)->hashsz
)) == 0)
402 die(EXIT_FAILURE
, "bad ciphertext chunk: no tag");
403 GH_HASH(h
, BCUR(&b
), BLEFT(&b
));
404 if (memcmp(tag
, GH_DONE(h
, 0), GM_CLASS(m
)->hashsz
) != 0)
405 die(EXIT_FAILURE
, "bad ciphertext chunk: authentication failure");
408 GC_DECRYPT(c
, BCUR(&b
), BCUR(&b
), BLEFT(&b
));
409 if (fwrite(BCUR(&b
), 1, BLEFT(&b
), ofp
) != BLEFT(&b
))
410 die(EXIT_FAILURE
, "error writing output: %s", strerror(errno
));
413 if (fflush(ofp
) || ferror(ofp
))
414 die(EXIT_FAILURE
, "error writing output: %s", strerror(errno
));
430 /*----- Test code ---------------------------------------------------------*/
432 static int encode(int argc
, char *argv
[])
437 const char *ef
= "binary";
438 const char *bd
= "MESSAGE";
449 static const struct option opt
[] = {
450 { "format", OPTF_ARGREQ
, 0, 'f' },
451 { "boundary", OPTF_ARGREQ
, 0, 'b' },
452 { "output", OPTF_ARGREQ
, 0, 'o' },
455 i
= mdwopt(argc
, argv
, "f:b:o:", opt
, 0, 0, 0);
458 case 'f': ef
= optarg
; break;
459 case 'b': bd
= optarg
; break;
460 case 'o': of
= optarg
; break;
461 default: f
|= f_bogus
; break;
464 if (argc
- optind
> 1 || (f
& f_bogus
))
465 die(EXIT_FAILURE
, "Usage: encode [-options] [file]");
467 if ((eo
= getenc(ef
)) == 0)
468 die(EXIT_FAILURE
, "encoding `%s' not found", ef
);
472 else if (strcmp(argv
[optind
], "-") == 0) {
475 } else if ((fp
= fopen(argv
[optind
], "rb")) == 0) {
476 die(EXIT_FAILURE
, "couldn't open file `%s': %s",
477 argv
[optind
], strerror(errno
));
481 if (!of
|| strcmp(of
, "-") == 0)
483 else if ((ofp
= fopen(of
, eo
->wmode
)) == 0) {
484 die(EXIT_FAILURE
, "couldn't open file `%s' for output: %s",
485 ofp
, strerror(errno
));
488 e
= initenc(eo
, ofp
, bd
, 1);
491 n
= fread(buf
, 1, sizeof(buf
), fp
);
492 if (e
->ops
->write(e
, buf
, n
))
493 die(EXIT_FAILURE
, "error writing output: %s", strerror(errno
));
494 } while (n
== sizeof(buf
));
502 static int decode(int argc
, char *argv
[])
507 const char *ef
= "binary";
518 static const struct option opt
[] = {
519 { "format", OPTF_ARGREQ
, 0, 'f' },
520 { "boundary", OPTF_ARGREQ
, 0, 'b' },
521 { "output", OPTF_ARGREQ
, 0, 'o' },
524 i
= mdwopt(argc
, argv
, "f:b:o:", opt
, 0, 0, 0);
527 case 'f': ef
= optarg
; break;
528 case 'b': bd
= optarg
; break;
529 case 'o': of
= optarg
; break;
530 default: f
|= f_bogus
; break;
533 if (argc
- optind
> 1 || (f
& f_bogus
))
534 die(EXIT_FAILURE
, "Usage: decode [-options] [file]");
536 if ((eo
= getenc(ef
)) == 0)
537 die(EXIT_FAILURE
, "encoding `%s' not found", ef
);
541 else if (strcmp(argv
[optind
], "-") == 0) {
544 } else if ((fp
= fopen(argv
[optind
], eo
->rmode
)) == 0) {
545 die(EXIT_FAILURE
, "couldn't open file `%s': %s",
546 argv
[optind
], strerror(errno
));
550 if (!of
|| strcmp(of
, "-") == 0)
552 else if ((ofp
= fopen(of
, "wb")) == 0) {
553 die(EXIT_FAILURE
, "couldn't open file `%s' for output: %s",
554 ofp
, strerror(errno
));
557 e
= initenc(eo
, fp
, bd
, 0);
560 if ((i
= e
->ops
->read(e
, buf
, sizeof(buf
))) < 0)
561 die(EXIT_FAILURE
, "error reading input: %s", strerror(errno
));
562 if (fwrite(buf
, 1, i
, ofp
) < i
)
563 die(EXIT_FAILURE
, "error writing output: %s", strerror(errno
));
564 } while (i
== sizeof(buf
));
572 /*----- Main code ---------------------------------------------------------*/
576 int (*func
)(int /*argc*/, char */
*argv*/
[]);
581 static cmd cmdtab
[] = {
583 "encode [-f format] [-b label] [-o output] [file]",
587 -f, --format=FORMAT Encode to FORMAT.\n\
588 -b, --boundary=LABEL PEM boundary is LABEL.\n\
589 -o, --output=FILE Write output to FILE.\n\
592 "decode [-f format] [-b label] [-o output] [file]",
596 -f, --format=FORMAT Decode from FORMAT.\n\
597 -b, --boundary=LABEL PEM boundary is LABEL.\n\
598 -o, --output=FILE Write output to FILE.\n\
600 { "encrypt", encrypt
,
601 "encrypt [-a] [-k tag] [f format]] [-o output] [file]",
605 -a, --armour Same as `-f pem'.\n\
606 -f, --format=FORMAT Encode as FORMAT.\n\
607 -k, --key=TAG Use public key named by TAG.\n\
608 -o, --output=FILE Write output to FILE.\n\
610 { "decrypt", decrypt
,
611 "decrypt [-t] [-o output] [file]", "\
614 -t, --text Read PEM-encoded input.\n\
615 -o, --output=FILE Write output to FILE.\n\
620 /* --- @findcmd@ --- *
622 * Arguments: @const char *name@ = a command name
624 * Returns: Pointer to the command structure.
626 * Use: Looks up a command by name. If the command isn't found, an
627 * error is reported and the program is terminated.
630 static cmd
*findcmd(const char *name
)
633 size_t sz
= strlen(name
);
635 for (c
= cmdtab
; c
->name
; c
++) {
636 if (strncmp(name
, c
->name
, sz
) == 0) {
637 if (c
->name
[sz
] == 0) {
641 die(EXIT_FAILURE
, "ambiguous command name `%s'", name
);
647 die(EXIT_FAILURE
, "unknown command name `%s'", name
);
651 static void version(FILE *fp
)
653 pquis(fp
, "$, Catacomb version " VERSION
"\n");
656 static void usage(FILE *fp
)
658 pquis(fp
, "Usage: $ [-k keyring] command [args]\n");
661 static void help(FILE *fp
, char **argv
)
667 fprintf(fp
, "Usage: %s [-k keyring] %s\n", QUIS
, c
->usage
);
677 Encrypt and decrypt files.\n\
679 for (c
= cmdtab
; c
->name
; c
++)
680 fprintf(fp
, "%s\n", c
->usage
);
686 * Arguments: @int argc@ = number of command line arguments
687 * @char *argv[]@ = vector of command line arguments
689 * Returns: Zero if successful, nonzero otherwise.
691 * Use: Signs or verifies signatures on lists of files. Useful for
692 * ensuring that a distribution is unmolested.
695 int main(int argc
, char *argv
[])
701 /* --- Initialize the library --- */
705 rand_noisesrc(RAND_GLOBAL
, &noise_source
);
706 rand_seed(RAND_GLOBAL
, 160);
708 /* --- Parse options --- */
711 static struct option opts
[] = {
712 { "help", 0, 0, 'h' },
713 { "version", 0, 0, 'v' },
714 { "usage", 0, 0, 'u' },
715 { "keyring", OPTF_ARGREQ
, 0, 'k' },
718 int i
= mdwopt(argc
, argv
, "+hvu k:", opts
, 0, 0, 0);
723 help(stdout
, argv
+ optind
);
745 if (f
& f_bogus
|| argc
< 1) {
750 /* --- Dispatch to the correct subcommand handler --- */
752 return (findcmd(argv
[0])->func(argc
, argv
));
757 /*----- That's all, folks -------------------------------------------------*/