From 252c122db9653732c758e08c41bedd08096f0749 Mon Sep 17 00:00:00 2001 From: mdw Date: Wed, 22 Dec 1999 15:48:10 +0000 Subject: [PATCH] Track new key-management changes. Support new key generation algorithms. --- keyutil.c | 1383 ++++++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 1147 insertions(+), 236 deletions(-) diff --git a/keyutil.c b/keyutil.c index e47c401..b6b1bf9 100644 --- a/keyutil.c +++ b/keyutil.c @@ -1,10 +1,10 @@ /* -*-c-*- * - * $Id: keyutil.c,v 1.3 1999/11/02 15:23:24 mdw Exp $ + * $Id: keyutil.c,v 1.4 1999/12/22 15:48:10 mdw Exp $ * * Simple key manager program * - * (c) 1999 Mark Wooding + * (c) 1999 Straylight/Edgeware */ /*----- Licensing notice --------------------------------------------------* @@ -30,6 +30,10 @@ /*----- Revision history --------------------------------------------------* * * $Log: keyutil.c,v $ + * Revision 1.4 1999/12/22 15:48:10 mdw + * Track new key-management changes. Support new key generation + * algorithms. + * * Revision 1.3 1999/11/02 15:23:24 mdw * Fix newlines in keyring list. * @@ -52,6 +56,7 @@ #include #include +#include #include #include #include @@ -60,8 +65,17 @@ #include #include +#include "bbs.h" +#include "dsa.h" +#include "fibrand.h" #include "getdate.h" #include "key.h" +#include "mp.h" +#include "mpmont.h" +#include "mprand.h" +#include "mptext.h" +#include "pgen.h" +#include "rsa.h" /*----- Handy global state ------------------------------------------------*/ @@ -82,7 +96,7 @@ static const char *keyfile = "keyring"; static void doopen(key_file *f, unsigned how) { - if (key_open(f, keyfile, how)) + if (key_open(f, keyfile, how, key_moan, 0)) die(1, "couldn't open file `%s': %s", keyfile, strerror(errno)); } @@ -122,48 +136,503 @@ static void doclose(key_file *f) static void setattr(key_file *f, key *k, char *v[]) { while (*v) { + int err; char *p = *v; size_t eq = strcspn(p, "="); if (p[eq] == 0) moan("invalid assignment: `%s'", p); p[eq] = 0; p += eq + 1; - key_putattr(f, k, *v, *p ? p : 0); + if ((err = key_putattr(f, k, *v, *p ? p : 0)) != 0) + die(EXIT_FAILURE, "couldn't set attributes: %s", key_strerror(err)); v++; } } -/*----- Command implementation --------------------------------------------*/ +/*----- Key generation ----------------------------------------------------*/ + +/* --- Key generation parameters --- */ + +typedef struct keyopts { + key_file *kf; /* Pointer to key file */ + key *k; /* Pointer to the actual key */ + dstr tag; /* Full tag name for the key */ + unsigned f; /* Flags for the new key */ + unsigned bits, qbits; /* Bit length for the new key */ + key *p; /* Parameters key-data */ +} keyopts; + +enum { + f_bogus = 1, /* Error in parsing */ + f_lock = 2, /* Passphrase-lock private key */ + f_quiet = 4 /* Don't show a progress indicator */ +}; + +/* --- @dolock@ --- * + * + * Arguments: @keyopts *k@ = key generation options + * @key_data *kd@ = pointer to key data to lock + * @const char *t@ = tag suffix or null + * + * Returns: --- + * + * Use: Does passphrase locking on new keys. + */ + +static void dolock(keyopts *k, key_data *kd, const char *t) +{ + if (!(k->f & f_lock)) + return; + if (t) + dstr_putf(&k->tag, ".%s", t); + if (key_plock(k->tag.buf, kd, kd)) + die(EXIT_FAILURE, "couldn't lock key"); +} + +/* --- @mpkey@ --- * + * + * Arguments: @key_data *kd@ = pointer to parent key block + * @const char *tag@ = pointer to tag string + * @mp *m@ = integer to store + * @unsigned f@ = flags to set + * + * Returns: --- + * + * Use: Sets a multiprecision integer subkey. + */ + +static void mpkey(key_data *kd, const char *tag, mp *m, unsigned f) +{ + key_data *kkd = key_structcreate(kd, tag); + key_mp(kkd, m); + kkd->e |= f; +} + +/* --- @copyparam@ --- * + * + * Arguments: @keyopts *k@ = pointer to key options + * @const char **pp@ = checklist of parameters + * + * Returns: Nonzero if parameters copied; zero if you have to generate + * them. + * + * Use: Copies parameters from a source key to the current one. + */ + +static int copyparam(keyopts *k, const char **pp) +{ + key_filter kf; + + /* --- Quick check if no parameters supplied --- */ + + if (!k->p) + return (0); + + /* --- Run through the checklist --- */ + + while (*pp) { + key_data *kd = key_structfind(&k->p->k, *pp); + if (!kd) + die(EXIT_FAILURE, "bad parameter key: parameter `%s' not found", *pp); + if ((kd->e & KF_CATMASK) != KCAT_SHARE) + die(EXIT_FAILURE, "bad parameter key: subkey `%s' is not shared", *pp); + pp++; + } + + /* --- Copy over the parameters --- */ + + kf.f = KCAT_SHARE; + kf.m = KF_CATMASK; + if (!key_copy(&k->k->k, &k->p->k, &kf)) + die(EXIT_FAILURE, "unexpected failure while copying parameters"); + return (1); +} + +/* --- @getmp@ --- * + * + * Arguments: @key_data *k@ = pointer to key data block + * @const char *tag@ = tag string to use + * + * Returns: Pointer to multiprecision integer key item. + * + * Use: Fetches an MP key component. + */ + +static mp *getmp(key_data *k, const char *tag) +{ + k = key_structfind(k, tag); + if (!k) + die(EXIT_FAILURE, "unexpected failure looking up subkey `%s'", tag); + if ((k->e & KF_ENCMASK) != KENC_MP) + die(EXIT_FAILURE, "subkey `%s' has an incompatible type"); + return (k->u.m); +} + +/* --- Key generation algorithms --- */ + +static void alg_binary(keyopts *k) +{ + unsigned sz; + unsigned m; + octet *p; + + if (!k->bits) + k->bits = 128; + if (k->p) + die(EXIT_FAILURE, "no shared parameters for binary keys"); + + sz = (k->bits + 7) >> 3; + p = sub_alloc(sz); + m = (1 << (((k->bits - 1) & 7) + 1)) - 1; + rand_getgood(RAND_GLOBAL, p, sz); + *p &= m; + key_binary(&k->k->k, p, sz); + k->k->k.e |= KCAT_SYMM | KF_BURN; + memset(p, 0, sz); + sub_free(p, sz); + dolock(k, &k->k->k, 0); +} + +static void alg_des(keyopts *k) +{ + unsigned sz; + octet *p; + int i; + + if (!k->bits) + k->bits = 112; + if (k->p) + die(EXIT_FAILURE, "no shared parameters for DES keys"); + if (k->bits % 56 || k->bits > 168) + die(EXIT_FAILURE, "DES keys must be 56, 112 or 168 bits long"); + + sz = k->bits / 7; + p = sub_alloc(sz); + rand_getgood(RAND_GLOBAL, p, sz); /* Too much work done here! */ + for (i = 0; i < sz; i++) { + octet x = p[i] & 0xfe; + x = x ^ (x >> 4); + x = x ^ (x >> 2); + x = x ^ (x >> 1) ^ 1; + p[i] = (p[i] & 0xfe) | (x & 0x01); + } + key_binary(&k->k->k, p, sz); + k->k->k.e |= KCAT_SYMM | KF_BURN; + memset(p, 0, sz); + sub_free(p, sz); + dolock(k, &k->k->k, 0); +} + +static void alg_rsa(keyopts *k) +{ + rsa_param rp; + key_data *kd; + + /* --- Sanity checking --- */ + + if (k->p) + die(EXIT_FAILURE, "no shared parameters for RSA keys"); + if (!k->bits) + k->bits = 1024; + + /* --- Generate the RSA parameters --- */ + + if (rsa_gen(&rp, k->bits, &rand_global, 0, + (k->f & f_quiet) ? 0 : pgen_ev, 0)) + die(EXIT_FAILURE, "RSA key generation failed"); + + /* --- Run a test encryption --- */ + + { + grand *g = fibrand_create(rand_global.ops->word(&rand_global)); + mpmont mm; + mp *m = mprand_range(MP_NEW, rp.n, g, 0); + mp *c; + + /* --- Encrypt the plaintext --- */ + + mpmont_create(&mm, rp.n); + c = mpmont_exp(&mm, MP_NEW, m, rp.e); + mpmont_destroy(&mm); + + /* --- Decrypt the ciphertext --- */ + + c = rsa_decrypt(&rp, c, c, g); + + /* --- Check everything went OK --- */ + + if (MP_CMP(c, !=, m)) + die(EXIT_FAILURE, "test encryption failed"); + mp_drop(c); + mp_drop(m); + g->ops->destroy(g); + } + + /* --- Allrighty then --- */ + + kd = &k->k->k; + key_structure(kd); + mpkey(kd, "n", rp.n, KCAT_PUB); + mpkey(kd, "e", rp.e, KCAT_PUB); + + kd = key_structcreate(kd, "private"); + key_structure(kd); + mpkey(kd, "d", rp.d, KCAT_PRIV | KF_BURN); + mpkey(kd, "p", rp.p, KCAT_PRIV | KF_BURN); + mpkey(kd, "q", rp.q, KCAT_PRIV | KF_BURN); + mpkey(kd, "q-inv", rp.q_inv, KCAT_PRIV | KF_BURN); + mpkey(kd, "d-mod-p", rp.dp, KCAT_PRIV | KF_BURN); + mpkey(kd, "d-mod-q", rp.dq, KCAT_PRIV | KF_BURN); + dolock(k, kd, "private"); + + mp_drop(rp.p); mp_drop(rp.q); mp_drop(rp.n); mp_drop(rp.q_inv); + mp_drop(rp.e); mp_drop(rp.d); mp_drop(rp.dp); mp_drop(rp.dq); +} + +static void alg_dsaparam(keyopts *k) +{ + static const char *pl[] = { "q", "p", "g", 0 }; + if (!copyparam(k, pl)) { + dsa_param dp; + octet *p; + size_t sz; + dstr d = DSTR_INIT; + base64_ctx c; + key_data *kd = &k->k->k; + + /* --- Choose appropriate bit lengths if necessary --- */ + + if (!k->qbits) + k->qbits = 160; + if (!k->bits) + k->bits = 768; + + /* --- Allocate a seed block --- */ + + sz = (k->qbits + 7) >> 3; + p = sub_alloc(sz); + rand_getgood(RAND_GLOBAL, p, sz); + + /* --- Allocate the parameters --- */ + + if (dsa_seed(&dp, k->qbits, k->bits, 0, p, sz, + (k->f & f_quiet) ? 0 : pgen_ev, 0)) + die(EXIT_FAILURE, "DSA parameter generation failed"); + + /* --- Store the parameters --- */ + + key_structure(kd); + mpkey(kd, "q", dp.q, KCAT_SHARE); + mpkey(kd, "p", dp.p, KCAT_SHARE); + mpkey(kd, "g", dp.g, KCAT_SHARE); + mp_drop(dp.q); + mp_drop(dp.p); + mp_drop(dp.g); + + /* --- Store the seed for future verification --- */ + + base64_init(&c); + c.maxline = 0; + c.indent = ""; + base64_encode(&c, p, sz, &d); + base64_encode(&c, 0, 0, &d); + key_putattr(k->kf, k->k, "seed", d.buf); + sub_free(p, sz); + dstr_destroy(&d); + } +} + +static void alg_dsa(keyopts *k) +{ + mp *q, *p, *g; + mp *x, *y; + mpmont mm; + key_data *kd = &k->k->k; + + /* --- Get the shared parameters --- */ + + alg_dsaparam(k); + q = getmp(kd, "q"); + p = getmp(kd, "p"); + g = getmp(kd, "g"); + + /* --- Choose a private key --- */ + + x = mprand_range(MP_NEW, q, &rand_global, 0); + mp_burn(x); + mpmont_create(&mm, p); + y = mpmont_exp(&mm, MP_NEW, g, x); + + /* --- Store everything away --- */ + + mpkey(kd, "y", y, KCAT_PUB); + + kd = key_structcreate(kd, "private"); + key_structure(kd); + mpkey(kd, "x", x, KCAT_PRIV | KF_BURN); + dolock(k, kd, "private"); +} + +static void alg_dhparam(keyopts *k) +{ + static const char *pl[] = { "p", "g", 0 }; + if (!copyparam(k, pl)) { + pgen_safetestctx c; + mp *p, *q; + key_data *kd = &k->k->k; + + if (!k->bits) + k->bits = 1024; + + /* --- Choose a large safe prime number --- */ + + q = MP_NEW; + q = mprand(q, k->bits, &rand_global, 3); + p = pgen("p", MP_NEW, q, (k->f & f_quiet) ? 0 : pgen_ev, 0, + 0, pgen_safestep, &c.c, + rabin_iters(k->bits), pgen_safetest, &c); + if (!p) + die(EXIT_FAILURE, "Diffie-Hellman parameter generation failed"); + + key_structure(kd); + mpkey(kd, "p", p, KCAT_SHARE); + mp_drop(q); + mp_drop(p); + + /* --- The generator 4 is good --- * + * + * Since 4 is clearly a quadratic residue, and %$p = 2q + 1$% for prime + * %$q$%, the number 4 has order %$q$%. This is better than choosing a + * real primitive element, because it could conceivably be trapped in an + * order-2 subgroup. (Not very likely, I'll admit, but possible.) + */ + + mpkey(kd, "g", MP_FOUR, KCAT_SHARE); + } +} + +static void alg_dh(keyopts *k) +{ + mp *x, *y; + mp *p, *g; + mpmont mm; + key_data *kd = &k->k->k; + + /* --- Get the shared parameters --- */ + + alg_dhparam(k); + p = getmp(kd, "p"); + g = getmp(kd, "g"); + + /* --- Choose a suitable private key --- * + * + * Since %$g$% has order %$q$%, choose %$x < q$%. + */ + + y = mp_lsr(MP_NEW, p, 1); + x = mprand_range(MP_NEW, y, &rand_global, 0); + mp_burn(x); + + /* --- Compute the public key %$y = g^x \bmod p$% --- */ + + mpmont_create(&mm, p); + y = mpmont_exp(&mm, y, g, x); + mpmont_destroy(&mm); + + /* --- Store everything away --- */ + + mpkey(kd, "y", y, KCAT_PUB); + + kd = key_structcreate(kd, "private"); + key_structure(kd); + mpkey(kd, "x", x, KCAT_PRIV | KF_BURN); + dolock(k, kd, "private"); +} + +static void alg_bbs(keyopts *k) +{ + bbs_param bp; + key_data *kd; + mp *p, *q; + + /* --- Sanity checking --- */ + + if (k->p) + die(EXIT_FAILURE, "no shared parameters for Blum-Blum-Shub keys"); + if (!k->bits) + k->bits = 1024; + + /* --- Generate the BBS parameters --- */ + + p = mprand(MP_NEW, k->bits / 2, &rand_global, 3); + q = mprand(MP_NEW, k->bits - k->bits / 2, &rand_global, 3); + mp_burn(p); mp_burn(q); + if (bbs_gen(&bp, p, q, 0, (k->f & f_quiet) ? 0 : pgen_ev, 0)) + die(EXIT_FAILURE, "Blum-Blum-Shub key generation failed"); + mp_drop(p); mp_drop(q); + + /* --- Allrighty then --- */ + + kd = &k->k->k; + key_structure(kd); + mpkey(kd, "n", bp.n, KCAT_PUB); + + kd = key_structcreate(kd, "private"); + key_structure(kd); + mpkey(kd, "p", bp.p, KCAT_PRIV | KF_BURN); + mpkey(kd, "q", bp.q, KCAT_PRIV | KF_BURN); + dolock(k, kd, "private"); + + mp_drop(bp.p); mp_drop(bp.q); mp_drop(bp.n); +} + +/* --- The algorithm tables --- */ + +typedef struct keyalg { + const char *name; + void (*proc)(keyopts *o); + const char *help; +} keyalg; + +static keyalg algtab[] = { + { "binary", alg_binary, "Plain binary data" }, + { "des", alg_des, "Binary with DES-style parity" }, + { "rsa", alg_rsa, "RSA public-key encryption" }, + { "dsa", alg_dsa, "DSA digital signatures" }, + { "dsa-param", alg_dsaparam, "DSA shared parameters" }, + { "dh", alg_dh, "Diffie-Hellman key exchange" }, + { "dh-param", alg_dhparam, "Diffie-Hellman parameters" }, + { "bbs", alg_bbs, "Blum-Blum-Shub generator" }, + { 0, 0 } +}; /* --- @cmd_add@ --- */ static int cmd_add(int argc, char *argv[]) { key_file f; - key *k; - int bits = 128; time_t exp = KEXP_EXPIRE; - unsigned fl = 0; - unsigned char *p; - size_t sz; + const char *tag = 0, *ptag = 0; const char *c = 0; - - /* --- Various useful flag bits --- */ - - enum { - f_bogus = 1 - }; + keyalg *alg = algtab; + keyopts k = { 0, 0, DSTR_INIT, 0, 0, 0, 0 }; /* --- Parse options for the subcommand --- */ for (;;) { static struct option opt[] = { + { "algorithm", OPTF_ARGREQ, 0, 'a' }, { "bits", OPTF_ARGREQ, 0, 'b' }, + { "qbits", OPTF_ARGREQ, 0, 'B' }, + { "parameters", OPTF_ARGREQ, 0, 'p' }, { "expire", OPTF_ARGREQ, 0, 'e' }, { "comment", OPTF_ARGREQ, 0, 'c' }, + { "tag", OPTF_ARGREQ, 0, 't' }, + { "lock", 0, 0, 'l' }, + { "quiet", 0, 0, 'q' }, { 0, 0, 0, 0 } }; - int i = mdwopt(argc, argv, "+b:e:c:", opt, 0, 0, 0); + int i = mdwopt(argc, argv, "+a:b:B:p:e:c:t:l", opt, 0, 0, 0); if (i < 0) break; @@ -171,92 +640,508 @@ static int cmd_add(int argc, char *argv[]) switch (i) { + /* --- Read an algorithm name --- */ + + case 'a': { + keyalg *a; + size_t sz = strlen(optarg); + + if (strcmp(optarg, "list") == 0) { + for (a = algtab; a->name; a++) + printf("%-10s %s\n", a->name, a->help); + return (0); + } + + alg = 0; + for (a = algtab; a->name; a++) { + if (strncmp(optarg, a->name, sz) == 0) { + if (a->name[sz] == 0) { + alg = a; + break; + } else if (alg) + die(EXIT_FAILURE, "ambiguous algorithm name `%s'", optarg); + else + alg = a; + } + } + if (!alg) + die(EXIT_FAILURE, "unknown algorithm name `%s'", optarg); + } break; + /* --- Bits must be nonzero and a multiple of 8 --- */ - case 'b': - if (!(bits = atoi(optarg)) || bits % 8) - die(EXIT_FAILURE, "bad number of bits: `%s'", optarg); + case 'b': { + char *p; + k.bits = strtoul(optarg, &p, 0); + if (k.bits == 0 || *p != 0) + die(EXIT_FAILURE, "bad bitlength `%s'", optarg); + } break; + + case 'B': { + char *p; + k.qbits = strtoul(optarg, &p, 0); + if (k.qbits == 0 || *p != 0) + die(EXIT_FAILURE, "bad bitlength `%s'", optarg); + } break; + + /* --- Parameter selection --- */ + + case 'p': + ptag = optarg; break; /* --- Expiry dates get passed to @get_date@ for parsing --- */ case 'e': - if (strcmp(optarg, "forever") == 0) + if (strncmp(optarg, "forever", strlen(optarg)) == 0) exp = KEXP_FOREVER; else { exp = get_date(optarg, 0); if (exp == -1) - die(EXIT_FAILURE, "bad expiry date: `%s'", optarg); + die(EXIT_FAILURE, "bad expiry date `%s'", optarg); } break; /* --- Store comments without interpretation --- */ case 'c': - if (key_chkcomment(c)) - die(EXIT_FAILURE, "bad comment string: `%s'", optarg); + if (key_chkcomment(optarg)) + die(EXIT_FAILURE, "bad comment string `%s'", optarg); c = optarg; break; + /* --- Store tags --- */ + + case 't': + if (key_chkident(optarg)) + die(EXIT_FAILURE, "bad tag string `%s'", optarg); + tag = optarg; + break; + + /* --- Other flags --- */ + + case 'l': + k.f |= f_lock; + break; + case 'q': + k.f |= f_quiet; + break; + /* --- Other things are bogus --- */ default: - fl |= f_bogus; + k.f |= f_bogus; break; } } - /* --- Various sorts of bogusity --- */ + /* --- Various sorts of bogosity --- */ - if (fl & f_bogus || optind + 1 > argc) { + if ((k.f & f_bogus) || optind + 1 > argc) { die(EXIT_FAILURE, - "Usage: add [-b BITS] [-e EXPIRE] [-c COMMENT] TYPE [ATTR...]"); + "Usage: add [options] type [attr...]"); } - if (key_chktype(argv[optind])) - die(EXIT_FAILURE, "bad key type: `%s'", argv[optind]); + if (key_chkident(argv[optind])) + die(EXIT_FAILURE, "bad key type `%s'", argv[optind]); + + /* --- Set up various bits of the state --- */ + if (exp == KEXP_EXPIRE) exp = time(0) + 14 * 24 * 60 * 60; - /* --- Initialize the Catacomb random number generator --- */ + /* --- Open the file and create the basic key block --- * + * + * Keep on generating keyids until one of them doesn't collide. + */ - rand_init(RAND_GLOBAL); - rand_noisesrc(RAND_GLOBAL, &noise_source); + doopen(&f, KOPEN_WRITE); + k.kf = &f; + + for (;;) { + uint32 id = rand_global.ops->word(&rand_global); + int err; + k.k = key_new(&f, id, argv[optind], exp, &err); + if (k.k) + break; + if (err != KERR_DUPID) + die(EXIT_FAILURE, "error adding new key: %s", key_strerror(err)); + } - /* --- Extract the key data from the generator --- */ + /* --- Set various simple attributes --- */ - sz = bits / 8; - p = xmalloc(sz); - rand_getgood(RAND_GLOBAL, p, sz); + if (tag) { + int err = key_settag(&f, k.k, tag); + if (err) + die(EXIT_FAILURE, "error setting key tag: %s", key_strerror(err)); + } - /* --- Open the file, add the key, set attributes, close, return --- */ + if (c) { + int err = key_setcomment(&f, k.k, c); + if (err) + die(EXIT_FAILURE, "error setting key comment: %s", key_strerror(err)); + } + + setattr(&f, k.k, argv + optind + 1); + + key_fulltag(k.k, &k.tag); + + /* --- Find the parameter key --- */ + + if (ptag) { + if ((k.p = key_bytag(&f, ptag)) == 0) + die(EXIT_FAILURE, "parameter key `%s' not found", ptag); + if ((k.p->k.e & KF_ENCMASK) != KENC_STRUCT) + die(EXIT_FAILURE, "parameter key `%s' is not structured", ptag); + } + + /* --- Now generate the actual key data --- */ + + alg->proc(&k); + + /* --- Done --- */ - doopen(&f, KOPEN_WRITE); - if (!(k = key_new(&f, argv[optind], p, sz, exp, c))) - moan("key not added: expiry date in past?"); - setattr(&f, k, argv + optind + 1); doclose(&f); return (0); } +/*----- Key listing -------------------------------------------------------*/ + +/* --- Listing options --- */ + +typedef struct listopts { + const char *tfmt; /* Date format (@strftime@-style) */ + int v; /* Verbosity level */ + unsigned f; /* Various flags */ + time_t t; /* Time now (for key expiry) */ + key_filter kf; /* Filter for matching keys */ +} listopts; + +/* --- Listing flags --- */ + +enum { + f_newline = 2, /* Write newline before next entry */ + f_attr = 4, /* Written at least one attribute */ + f_utc = 8 /* Emit UTC time, not local time */ +}; + +/* --- @showkeydata@ --- * + * + * Arguments: @key_data *k@ = pointer to key to write + * @int ind@ = indentation level + * @listopts *o@ = listing options + * @dstr *d@ = tag string for this subkey + * + * Returns: --- + * + * Use: Emits a piece of key data in a human-readable format. + */ + +static void showkeydata(key_data *k, int ind, listopts *o, dstr *d) +{ +#define INDENT(i) do { \ + int _i; \ + for (_i = 0; _i < (i); _i++) { \ + putchar(' '); \ + } \ +} while (0) + + switch (k->e & KF_ENCMASK) { + + /* --- Binary key data --- * + * + * Emit as a simple hex dump. + */ + + case KENC_BINARY: { + const octet *p = k->u.k.k; + const octet *l = p + k->u.k.sz; + size_t sz = 0; + + fputs(" {", stdout); + while (p < l) { + if (sz % 16 == 0) { + putchar('\n'); + INDENT(ind + 2); + } else if (sz % 8 == 0) + fputs(" ", stdout); + else + putc(' ', stdout); + printf("%02x", *p++); + sz++; + } + putchar('\n'); + INDENT(ind); + fputs("}\n", stdout); + } break; + + /* --- Encrypted data --- * + * + * If the user is sufficiently keen, ask for a passphrase and decrypt the + * key. Otherwise just say that it's encrypted and move on. + */ + + case KENC_ENCRYPT: + if (o->v <= 3) + fputs(" encrypted\n", stdout); + else { + key_data kd; + if (key_punlock(d->buf, k, &kd)) + printf(" \n", d->buf); + else { + fputs(" encrypted", stdout); + showkeydata(&kd, ind, o, d); + key_destroy(&kd); + } + } + break; + + /* --- Integer keys --- * + * + * Emit as a large integer in decimal. This makes using the key in + * `calc' or whatever easier. + */ + + case KENC_MP: + putchar(' '); + mp_writefile(k->u.m, stdout, 10); + putchar('\n'); + break; + + /* --- Structured keys --- * + * + * Just iterate over the subkeys. + */ + + case KENC_STRUCT: { + sym_iter i; + key_struct *ks; + size_t n = d->len; + + fputs(" {\n", stdout); + for (sym_mkiter(&i, &k->u.s); (ks = sym_next(&i)) != 0; ) { + if (!key_match(&ks->k, &o->kf)) + continue; + INDENT(ind + 2); + printf("%s =", SYM_NAME(ks)); + d->len = n; + dstr_putf(d, ".%s", SYM_NAME(ks)); + showkeydata(&ks->k, ind + 2, o, d); + } + INDENT(ind); + fputs("}\n", stdout); + } break; + } + +#undef INDENT +} + +/* --- @showkey@ --- * + * + * Arguments: @key *k@ = pointer to key to show + * @listopts *o@ = pointer to listing options + * + * Returns: --- + * + * Use: Emits a listing of a particular key. + */ + +static void showkey(key *k, listopts *o) +{ + char ebuf[24], dbuf[24]; + struct tm *tm; + + /* --- Skip the key if the filter doesn't match --- */ + + if (!key_match(&k->k, &o->kf)) + return; + + /* --- Sort out the expiry and deletion times --- */ + + if (KEY_EXPIRED(o->t, k->exp)) + strcpy(ebuf, "expired"); + else if (k->exp == KEXP_FOREVER) + strcpy(ebuf, "forever"); + else { + tm = (o->f & f_utc) ? gmtime(&k->exp) : localtime(&k->exp); + strftime(ebuf, sizeof(ebuf), o->tfmt, tm); + } + + if (KEY_EXPIRED(o->t, k->del)) + strcpy(dbuf, "deleted"); + else if (k->del == KEXP_FOREVER) + strcpy(dbuf, "forever"); + else { + tm = (o->f & f_utc) ? gmtime(&k->del) : localtime(&k->del); + strftime(dbuf, sizeof(dbuf), o->tfmt, tm); + } + + /* --- If in compact format, just display and quit --- */ + + if (!o->v) { + if (!(o->f & f_newline)) { + printf("%8s %-20s %-20s %-10s %-10s\n", + "Id", "Tag", "Type", "Expire", "Delete"); + } + printf("%08lx %-20s %-20s %-10s %-10s\n", + (unsigned long)k->id, k->tag ? k->tag : "", + k->type, ebuf, dbuf); + o->f |= f_newline; + return; + } + + /* --- Display the standard header --- */ + + if (o->f & f_newline) + fputc('\n', stdout); + printf("keyid: %08lx\n", (unsigned long)k->id); + printf("tag: %s\n", k->tag ? k->tag : ""); + printf("type: %s\n", k->type); + printf("expiry: %s\n", ebuf); + printf("delete: %s\n", dbuf); + printf("comment: %s\n", k->c ? k->c : ""); + + /* --- Display the attributes --- */ + + if (o->v > 1) { + key_attriter i; + const char *av, *an; + + o->f &= ~f_attr; + printf("attributes:"); + for (key_mkattriter(&i, k); key_nextattr(&i, &an, &av); ) { + printf("\n\t%s = %s", an, av); + o->f |= f_attr; + } + if (o->f & f_attr) + fputc('\n', stdout); + else + puts(" "); + } + + /* --- If dumping requested, dump the raw key data --- */ + + if (o->v > 2) { + dstr d = DSTR_INIT; + fputs("key:", stdout); + key_fulltag(k, &d); + showkeydata(&k->k, 0, o, &d); + dstr_destroy(&d); + } + + o->f |= f_newline; +} + +/* --- @cmd_list@ --- */ + +static int cmd_list(int argc, char *argv[]) +{ + key_file f; + key *k; + listopts o = { 0, 0, 0, 0, { 0, 0 } }; + + /* --- Parse subcommand options --- */ + + for (;;) { + static struct option opt[] = { + { "quiet", 0, 0, 'q' }, + { "verbose", 0, 0, 'v' }, + { "utc", 0, 0, 'u' }, + { "filter", OPTF_ARGREQ, 0, 'f' }, + { 0, 0, 0, 0 } + }; + int i = mdwopt(argc, argv, "+uqvf:", opt, 0, 0, 0); + if (i < 0) + break; + + switch (i) { + case 'u': + o.f |= f_utc; + break; + case 'q': + if (o.v) + o.v--; + break; + case 'v': + o.v++; + break; + case 'f': { + char *p; + int e = key_readflags(optarg, &p, &o.kf.f, &o.kf.m); + if (e || *p) + die(EXIT_FAILURE, "bad filter string `%s'", optarg); + } break; + default: + o.f |= f_bogus; + break; + } + } + + if (o.f & f_bogus) + die(EXIT_FAILURE, "Usage: list [-uqv] [-f filter] [tag...]"); + + /* --- Open the key file --- */ + + doopen(&f, KOPEN_READ); + o.t = time(0); + + /* --- Set up the time format --- */ + + if (!o.v) + o.tfmt = "%Y-%m-%d"; + else if (o.f & f_utc) + o.tfmt = "%Y-%m-%d %H:%M:%S UTC"; + else + o.tfmt = "%Y-%m-%d %H:%M:%S %Z"; + + /* --- If specific keys were requested use them, otherwise do all --- * + * + * Some day, this might turn into a wildcard match. + */ + + if (optind < argc) { + do { + if ((k = key_bytag(&f, argv[optind])) != 0) + showkey(k, &o); + else { + moan("key `%s' not found", argv[optind]); + o.f |= f_bogus; + } + optind++; + } while (optind < argc); + } else { + key_iter i; + for (key_mkiter(&i, &f); (k = key_next(&i)) != 0; ) + showkey(k, &o); + } + + /* --- Done --- */ + + doclose(&f); + if (o.f & f_bogus) + return (EXIT_FAILURE); + else + return (0); +} + +/*----- Command implementation --------------------------------------------*/ + /* --- @cmd_expire@ --- */ static int cmd_expire(int argc, char *argv[]) { key_file f; key *k; - uint32 id; int i; int rc = 0; if (argc < 2) - die(EXIT_FAILURE, "Usage: expire KEYID..."); + die(EXIT_FAILURE, "Usage: expire tag..."); doopen(&f, KOPEN_WRITE); for (i = 1; i < argc; i++) { - id = (uint32)strtoul(argv[i], 0, 16); - if ((k = key_byid(&f, id)) != 0) + if ((k = key_bytag(&f, argv[i])) != 0) key_expire(&f, k); else { - moan("keyid %lx not found", (unsigned long)id); + moan("key `%s' not found", argv[i]); rc = 1; } } @@ -270,19 +1155,17 @@ static int cmd_delete(int argc, char *argv[]) { key_file f; key *k; - uint32 id; int i; int rc = 0; if (argc < 2) - die(EXIT_FAILURE, "Usage: delete KEYID..."); + die(EXIT_FAILURE, "Usage: delete tag..."); doopen(&f, KOPEN_WRITE); for (i = 1; i < argc; i++) { - id = (uint32)strtoul(argv[i], 0, 16); - if ((k = key_byid(&f, id)) != 0) + if ((k = key_bytag(&f, argv[i])) != 0) key_delete(&f, k); else { - moan("keyid %lx not found", (unsigned long)id); + moan("key `%s' not found", argv[i]); rc = 1; } } @@ -296,209 +1179,195 @@ static int cmd_setattr(int argc, char *argv[]) { key_file f; key *k; - uint32 id; if (argc < 3) - die(EXIT_FAILURE, "Usage: setattr KEYID ATTR..."); + die(EXIT_FAILURE, "Usage: setattr tag attr..."); doopen(&f, KOPEN_WRITE); - id = (uint32)strtoul(argv[1], 0, 16); - if ((k = key_byid(&f, id)) == 0) - die(EXIT_FAILURE, "keyid %lx not found", (unsigned long)id); + if ((k = key_bytag(&f, argv[1])) == 0) + die(EXIT_FAILURE, "key `%s' not found", argv[1]); setattr(&f, k, argv + 2); doclose(&f); return (0); } -/* --- @cmd_comment@ --- */ +/* --- @cmd_finger@ --- */ -static int cmd_comment(int argc, char *argv[]) +static int fpkey(key_data *kd, dstr *d, void *p) { - uint32 id; - key_file f; - key *k; - - if (argc < 2 || argc > 3) - die(EXIT_FAILURE, "Usage: comment KEYID [COMMENT]"); - doopen(&f, KOPEN_WRITE); - id = (uint32)strtoul(argv[1], 0, 16); - if ((k = key_byid(&f, id)) == 0) - die(EXIT_FAILURE, "keyid %lx not found", (unsigned long)id); - if (key_chkcomment(argv[2])) - die(EXIT_FAILURE, "bad comment: `%s'", argv[2]); - key_setcomment(&f, k, argv[2]); - doclose(&f); + rmd160_ctx *r = p; + switch (kd->e & KF_ENCMASK) { + case KENC_BINARY: + case KENC_ENCRYPT: + rmd160_hash(r, kd->u.k.k, kd->u.k.sz); + break; + case KENC_MP: { + size_t sz = mp_octets(kd->u.m); + octet *q = sub_alloc(sz); + mp_storeb(kd->u.m, q, sz); + rmd160_hash(r, q, sz); + memset(q, 0, sz); + sub_free(q, sz); + } break; + } return (0); } -/* --- @cmd_list@ --- */ - -static int cmd_list(int argc, char *argv[]) +static void fingerprint(key *k, const key_filter *kf) { - key_file f; - key *k; - key_iter i; - unsigned fl = 0; - const char *tfmt; - int v = 0; - time_t t; + rmd160_ctx r; + octet hash[RMD160_HASHSZ]; + dstr d = DSTR_INIT; + int i; - enum { - f_bogus = 1, - f_newline = 2, - f_attr = 4, - f_utc = 8 - }; + if (!key_match(&k->k, kf)) + return; + rmd160_init(&r); + key_do(&k->k, kf, 0, fpkey, &r); + rmd160_done(&r, hash); + + key_fulltag(k, &d); + for (i = 0; i < sizeof(hash); i++) { + if (i && i % 4 == 0) + putchar('-'); + printf("%02x", hash[i]); + } + printf(" %s\n", d.buf); + dstr_destroy(&d); +} - /* --- Parse subcommand options --- */ +static int cmd_finger(int argc, char *argv[]) +{ + key_file f; + int rc = 0; + key_filter kf = { KF_NONSECRET, KF_NONSECRET }; for (;;) { static struct option opt[] = { - { "quiet", 0, 0, 'q' }, - { "verbose", 0, 0, 'v' }, - { "utc", 0, 0, 'u' }, - { 0, 0, 0, 0 } + { "filter", OPTF_ARGREQ, 0, 'f' }, + { 0, 0, 0, 0 } }; - int i = mdwopt(argc, argv, "+uqv", opt, 0, 0, 0); + int i = mdwopt(argc, argv, "f:", opt, 0, 0, 0); if (i < 0) break; - switch (i) { - case 'u': - fl |= f_utc; - break; - case 'q': - if (v) - v--; - break; - case 'v': - v++; - break; + case 'f': { + char *p; + int err = key_readflags(optarg, &p, &kf.f, &kf.m); + if (err || *p) + die(EXIT_FAILURE, "bad filter string `%s'", optarg); + } break; default: - fl |= f_bogus; + rc = 1; break; } } - if (fl & f_bogus || optind != argc) - die(EXIT_FAILURE, "Usage: list [-uqv]"); - - /* --- Open the key file --- */ + argv += optind; argc -= optind; + if (rc) + die(EXIT_FAILURE, "Usage: fingerprint [-f filter] [tag...]"); doopen(&f, KOPEN_READ); - t = time(0); - /* --- Write the header --- */ - - if (!v) - tfmt = "%Y-%m-%d"; - else if (fl & f_utc) - tfmt = "%Y-%m-%d %H:%M:%S UTC"; - else - tfmt = "%Y-%m-%d %H:%M:%S %Z"; + if (argc) { + int i; + for (i = 0; i < argc; i++) { + key *k = key_bytag(&f, argv[i]); + if (k) + fingerprint(k, &kf); + else { + rc = 1; + moan("key `%s' not found", argv[i]); + } + } + } else { + key_iter i; + key *k; + for (key_mkiter(&i, &f); (k = key_next(&i)) != 0; ) + fingerprint(k, &kf); + } + return (rc); +} - /* --- Now iterate through the keys --- */ +/* --- @cmd_comment@ --- */ - for (key_mkiter(&i, &f); (k = key_next(&i)) != 0; ) { - char ebuf[24], dbuf[24]; - struct tm *tm; +static int cmd_comment(int argc, char *argv[]) +{ + key_file f; + key *k; + int err; - /* --- Sort out the expiry times --- */ + if (argc < 2 || argc > 3) + 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]); + if ((err = key_setcomment(&f, k, argv[2])) != 0) + die(EXIT_FAILURE, "bad comment `%s': %s", argv[2], key_strerror(err)); + doclose(&f); + return (0); +} - if (KEY_EXPIRED(t, k->exp)) { - strcpy(ebuf, "expired"); - if (KEY_DELETED(t, k->del)) { - strcpy(dbuf, "deleted"); - goto donetime; - } else - goto deltime; - } +/* --- @cmd_tag@ --- */ - if (k->exp == KEXP_FOREVER) - strcpy(ebuf, "forever"); - else { - tm = (fl & f_utc) ? gmtime(&k->exp) : localtime(&k->exp); - strftime(ebuf, sizeof(ebuf), tfmt, tm); - } +static int cmd_tag(int argc, char *argv[]) +{ + key_file f; + key *k; + int err; - /* --- Sort out the delete times --- */ + if (argc < 2 || argc > 3) + die(EXIT_FAILURE, "Usage: tag tag [new-tag]"); + doopen(&f, KOPEN_WRITE); + if ((k = key_bytag(&f, argv[1])) == 0) + die(EXIT_FAILURE, "key `%s' not found", argv[1]); + if ((err = key_settag(&f, k, argv[2])) != 0) + die(EXIT_FAILURE, "bad tag `%s': %s", argv[2], key_strerror(err)); + doclose(&f); + return (0); +} - deltime: - if (k->del == KEXP_UNUSED) - strcpy(dbuf, "on expiry"); - else if (k->del == KEXP_FOREVER) - strcpy(dbuf, "forever"); - else { - tm = localtime(&k->del); - strftime(dbuf, sizeof(dbuf), tfmt, tm); - } +/* --- @cmd_lock@ --- */ - donetime:; +static int cmd_lock(int argc, char *argv[]) +{ + key_file f; + key *k; + key_data *kd; + dstr d = DSTR_INIT; - /* --- Display the data obtained so far --- */ + if (argc != 2) + 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]); + if (kd->e == KENC_ENCRYPT && key_punlock(d.buf, kd, kd)) + die(EXIT_FAILURE, "couldn't unlock key `%s'", d.buf); + if (key_plock(d.buf, kd, kd)) + die(EXIT_FAILURE, "failed to lock key `%s'", d.buf); + f.f |= KF_MODIFIED; + doclose(&f); + return (0); +} - if (!v) { - if (!(fl & f_newline)) { - printf("%8s %-20s %-10s %-10s %-23s\n", - "Id", "Type", "Expire", "Delete", "Comment"); - } - printf("%08lx %-20s %-10s %-10s %-23s\n", - (unsigned long)k->id, k->type, ebuf, dbuf, - k->c ? k->c : ""); - fl |= f_newline; - } else { - - /* --- Display the standard header --- */ - - if (fl & f_newline) - fputc('\n', stdout); - printf("keyid: %08lx\n", (unsigned long)k->id); - printf("type: %s\n", k->type); - printf("expiry: %s\n", ebuf); - printf("delete: %s\n", dbuf); - printf("comment: %s\n", k->c ? k->c : ""); - - /* --- Display the attributes --- */ - - if (v > 1) { - key_attriter i; - const char *av, *an; - - fl &= ~f_attr; - printf("attributes:"); - for (key_mkattriter(&i, &f, k); key_nextattr(&i, &an, &av); ) { - printf("\n\t%s = %s", an, av); - fl |= f_attr; - } - if (fl & f_attr) - fputc('\n', stdout); - else - puts(" "); - } +/* --- @cmd_unlock@ --- */ - /* --- If dumping requested, dump the raw key data in hex --- */ - - if (v > 2) { - unsigned char *p = k->k; - unsigned char *l = p + k->ksz; - size_t sz = 0; - - fputs("key:", stdout); - while (p < l) { - if (sz % 16 == 0) - fputs("\n\t", stdout); - else if (sz % 8 == 0) - fputs(" ", stdout); - else - fputc(' ', stdout); - printf("%02x", *p++); - sz++; - } - fputc('\n', stdout); - } - fl |= f_newline; - } - } +static int cmd_unlock(int argc, char *argv[]) +{ + key_file f; + key *k; + key_data *kd; + dstr d = DSTR_INIT; + if (argc != 2) + 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]); + if (kd->e != KENC_ENCRYPT) + die(EXIT_FAILURE, "key `%s' is not encrypted", d.buf); + if (kd->e == KENC_ENCRYPT && key_punlock(d.buf, kd, kd)) + die(EXIT_FAILURE, "couldn't unlock key `%s'", d.buf); + f.f |= KF_MODIFIED; doclose(&f); return (0); } @@ -509,30 +1378,60 @@ static int cmd_extract(int argc, char *argv[]) { key_file f; key *k; - uint32 id; int i; int rc = 0; + key_filter kf = { 0, 0 }; FILE *fp; - if (argc < 3) - die(EXIT_FAILURE, "Usage: extract FILE KEYID..."); - if (strcmp(argv[1], "-") == 0) + for (;;) { + static struct option opt[] = { + { "filter", OPTF_ARGREQ, 0, 'f' }, + { 0, 0, 0, 0 } + }; + int i = mdwopt(argc, argv, "f:", opt, 0, 0, 0); + if (i < 0) + break; + switch (i) { + case 'f': { + char *p; + int err = key_readflags(optarg, &p, &kf.f, &kf.m); + if (err || *p) + die(EXIT_FAILURE, "bad filter string `%s'", optarg); + } break; + default: + rc = 1; + break; + } + } + + argv += optind; argc -= optind; + if (rc || argc < 1) + die(EXIT_FAILURE, "Usage: extract [-f filter] file [tag...]"); + if (strcmp(*argv, "-") == 0) fp = stdout; - else if (!(fp = fopen(argv[1], "w"))) { + else if (!(fp = fopen(*argv, "w"))) { die(EXIT_FAILURE, "couldn't open `%s' for writing: %s", - argv[1], strerror(errno)); + *argv, strerror(errno)); } - doopen(&f, KOPEN_WRITE); - for (i = 2; i < argc; i++) { - id = (uint32)strtoul(argv[i], 0, 16); - if ((k = key_byid(&f, id)) != 0) - key_extract(&f, k, fp); - else { - moan("keyid %lx not found", (unsigned long)id); - rc = 1; + doopen(&f, KOPEN_READ); + if (argc < 2) { + key_iter i; + key *k; + for (key_mkiter(&i, &f); (k = key_next(&i)) != 0; ) + key_extract(&f, k, fp, &kf); + } else { + for (i = 1; i < argc; i++) { + if ((k = key_bytag(&f, argv[i])) != 0) + key_extract(&f, k, fp, &kf); + else { + moan("key `%s' not found", argv[i]); + rc = 1; + } } } + if (fclose(fp)) + die(EXIT_FAILURE, "error writing file: %s", strerror(errno)); doclose(&f); return (rc); } @@ -558,7 +1457,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"))) { @@ -567,7 +1466,7 @@ static int cmd_merge(int argc, char *argv[]) } doopen(&f, KOPEN_WRITE); - key_merge(&f, argv[1], fp); + key_merge(&f, argv[1], fp, key_moan, 0); doclose(&f); return (0); } @@ -580,15 +1479,22 @@ static struct cmd { const char *help; } cmds[] = { { "add", cmd_add, - "add [-b BITS] [-e EXPIRE] [-c COMMENT] TYPE [ATTR...]" }, - { "expire", cmd_expire, "expire KEYID..." }, - { "delete", cmd_delete, "delete KEYID..." }, - { "setattr", cmd_setattr, "setattr KEYID ATTR..." }, - { "comment", cmd_comment, "comment KEYID [COMMENT]" }, - { "list", cmd_list, "list [-uqv]" }, + "add [options] type [attr...]\n\ + Options: [-l] [-a alg] [-b bits] [-p param]\n\ + [-e expire] [-t tag] [-c comment]" + }, + { "expire", cmd_expire, "expire tag..." }, + { "delete", cmd_delete, "delete tag..." }, + { "tag", cmd_tag, "tag tag [new-tag]" }, + { "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...]" }, + { "fingerprint", cmd_finger, "fingerprint [-f filter] [tag...]" }, { "tidy", cmd_tidy, "tidy" }, - { "extract", cmd_extract, "extract FILE KEYID..." }, - { "merge", cmd_merge, "merge FILE" }, + { "extract", cmd_extract, "extract file qtag..." }, + { "merge", cmd_merge, "merge file" }, { 0, 0, 0 } }; @@ -653,6 +1559,11 @@ int main(int argc, char *argv[]) ego(argv[0]); sub_init(); + /* --- Initialize the Catacomb random number generator --- */ + + rand_init(RAND_GLOBAL); + rand_noisesrc(RAND_GLOBAL, &noise_source); + /* --- Parse command line options --- */ for (;;) { -- 2.11.0