From 16810bbd0d4c7256f1f91971d4919145d7d7612e Mon Sep 17 00:00:00 2001 Message-Id: <16810bbd0d4c7256f1f91971d4919145d7d7612e.1718752416.git.mdw@distorted.org.uk> From: Mark Wooding Date: Fri, 29 May 2015 18:54:29 +0100 Subject: [PATCH] progs/key.c: Multiple output presentations for fingerprints. Organization: Straylight/Edgeware From: Mark Wooding Add a new presentation for base32 output, separated into groups of six characters. Also document the presentation styles better; include some other missing options in usage messages; and patch an irrelevant memory leak. --- progs/key.1 | 48 +++++++++++++++-- progs/key.c | 145 ++++++++++++++++++++++++++++++---------------------- 2 files changed, 129 insertions(+), 64 deletions(-) diff --git a/progs/key.1 b/progs/key.1 index f301391f..a2ac3376 100644 --- a/progs/key.1 +++ b/progs/key.1 @@ -117,6 +117,8 @@ is one of: .B fingerprint .RB [ \-f .IR filter ] +.RB [ \-p +.IR style ] .RB [ \-a .IR hash ] .RI [ tag ...] @@ -124,6 +126,8 @@ is one of: .B verify .RB [ \-f .IR filter ] +.RB [ \-p +.IR style ] .RB [ \-a .IR hash ] .I tag @@ -340,6 +344,13 @@ The pseudorandom generators which are acceptable to the option of the .B add command. +.TP +.B fpres +Fingerprint presentation styles, as used by the +.B fingerprint +and +.B verify +commands. .SS add The .B add @@ -919,6 +930,11 @@ Specifies a filter. Only keys and key components which match the filter are fingerprinted. The default is to only fingerprint nonsecret components. .TP +.BI "\-p, \-\-presentation " style +Write fingerprints in the given +.IR style . +See below for a list of presentation styles. +.TP .BI "\-a, \-\-algorithm " hash Names the hashing algorithm. Run .B key show hash @@ -930,6 +946,18 @@ command line arguments. If no key tags are given, all keys which match the filter are fingerprinted. See .BR keyring (5) for a description of how key fingerprints are computed. +.PP +The fingerprint may be shown in the following styles. +.TP +.B hex +Lowercase hexadecimal, with groups of eight digits separated by hyphens +(`\-'). This is the default presentation style. (On input, colons are +also permitted as separators.) +.TP +.B base32 +Lowercase Base32 encoding, without `=' padding, with groups of six +digits separated by colons (`:'). (On input, padding characters are +ignored.) .SS "verify" Check a key's fingerprint against a reference copy. The following options are supported: @@ -939,15 +967,29 @@ Specifies a filter. Only key components which match the filter are hashed. The default is to only fingerprint nonsecret components. An error is reported if no part of the key matches. .TP +.BI "\-p, \-\-presentation " style +Expect the +.I fingerprint +to be in the given presentation +.IR style . +These match the styles produced by the +.B fingerprint +command described above. +.TP .BI "\-a, \-\-algorithm " hash Names the hashing algorithm. Run .B key show hash for a list of hashing algorithms. The default is .BR rmd160 . .PP -The reference fingerprint is given as hex, in upper or lower case. The -hash may contain hyphens, colons and whitespace. Other characters are -not permitted. +The fingerprint should be provided in the form printed by the +.B fingerprint +command, using the same presentation +.IR style . +A little flexibility is permitted: separators may be placed anywhere (or +not at all) and are ignored; whitespace is permitted and ignored; and +case is ignored in presentation styles which don't make use of both +upper- and lower-case characters. .SS "tidy" Simply reads the keyring from file and writes it back again. This has the effect of removing any deleted keys from the file. diff --git a/progs/key.c b/progs/key.c index 4530d00e..05494691 100644 --- a/progs/key.c +++ b/progs/key.c @@ -39,7 +39,9 @@ #include #include +#include #include +#include #include #include #include @@ -1682,42 +1684,67 @@ static int cmd_getattr(int argc, char *argv[]) /* --- @cmd_finger@ --- */ -static void fingerprint(key *k, const gchash *ch, const key_filter *kf) +static const struct fpres { + const char *name; + const codec_class *cdc; + unsigned short ival; + const char *sep; +} fprestab[] = { + { "hex", &hex_class, 8, "-:" }, + { "base32", &base32_class, 6, ":" }, + { 0, 0 } +}; + +static void fingerprint(key *k, const struct fpres *fpres, + const gchash *ch, const key_filter *kf) { ghash *h; - dstr d = DSTR_INIT; + dstr d = DSTR_INIT, dd = DSTR_INIT; const octet *p; size_t i; + codec *c; h = GH_INIT(ch); if (key_fingerprint(k, h, kf)) { p = GH_DONE(h, 0); - key_fulltag(k, &d); - for (i = 0; i < ch->hashsz; i++) { - if (i && i % 4 == 0) - putchar('-'); - printf("%02x", p[i]); + c = fpres->cdc->encoder(CDCF_LOWERC | CDCF_NOEQPAD, "", 0); + c->ops->code(c, p, ch->hashsz, &dd); c->ops->code(c, 0, 0, &dd); + c->ops->destroy(c); + for (i = 0; i < dd.len; i++) { + if (i && i%fpres->ival == 0) dstr_putc(&d, fpres->sep[0]); + dstr_putc(&d, dd.buf[i]); } - printf(" %s\n", d.buf); + dstr_putc(&d, ' '); key_fulltag(k, &d); dstr_putc(&d, '\n'); + dstr_write(&d, stdout); } - dstr_destroy(&d); + dstr_destroy(&d); dstr_destroy(&dd); GH_DESTROY(h); } +static const struct fpres *lookup_fpres(const char *name) +{ + const struct fpres *fpres; + for (fpres = fprestab; fpres->name; fpres++) + if (strcmp(fpres->name, name) == 0) return (fpres); + die(EXIT_FAILURE, "unknown presentation syle `%s'", name); +} + static int cmd_finger(int argc, char *argv[]) { key_file f; int rc = 0; + const struct fpres *fpres = fprestab; const gchash *ch = &rmd160; key_filter kf = { KF_NONSECRET, KF_NONSECRET }; for (;;) { static struct option opt[] = { { "filter", OPTF_ARGREQ, 0, 'f' }, + { "presentation", OPTF_ARGREQ, 0, 'p' }, { "algorithm", OPTF_ARGREQ, 0, 'a' }, { 0, 0, 0, 0 } }; - int i = mdwopt(argc, argv, "+f:a:", opt, 0, 0, 0); + int i = mdwopt(argc, argv, "+f:a:p:", opt, 0, 0, 0); if (i < 0) break; switch (i) { @@ -1727,6 +1754,9 @@ static int cmd_finger(int argc, char *argv[]) if (err || *p) die(EXIT_FAILURE, "bad filter string `%s'", optarg); } break; + case 'p': + fpres = lookup_fpres(optarg); + break; case 'a': if ((ch = ghash_byname(optarg)) == 0) die(EXIT_FAILURE, "unknown hash algorithm `%s'", optarg); @@ -1738,8 +1768,10 @@ static int cmd_finger(int argc, char *argv[]) } argv += optind; argc -= optind; - if (rc) - die(EXIT_FAILURE, "Usage: fingerprint [-f FILTER] [TAG...]"); + if (rc) { + die(EXIT_FAILURE, + "Usage: fingerprint [-a HASHALG] [-p STYLE] [-f FILTER] [TAG...]"); + } doopen(&f, KOPEN_READ); @@ -1748,7 +1780,7 @@ static int cmd_finger(int argc, char *argv[]) for (i = 0; i < argc; i++) { key *k = key_bytag(&f, argv[i]); if (k) - fingerprint(k, ch, &kf); + fingerprint(k, fpres, ch, &kf); else { rc = 1; moan("key `%s' not found", argv[i]); @@ -1758,50 +1790,13 @@ static int cmd_finger(int argc, char *argv[]) key_iter i; key *k; for (key_mkiter(&i, &f); (k = key_next(&i)) != 0; ) - fingerprint(k, ch, &kf); + fingerprint(k, fpres, ch, &kf); } return (rc); } /* --- @cmd_verify@ --- */ -static unsigned xdigit(char c) -{ - if ('A' <= c && c <= 'Z') return (c + 10 - 'A'); - if ('a' <= c && c <= 'z') return (c + 10 - 'a'); - if ('0' <= c && c <= '9') return (c - '0'); - return (~0u); -} - -static void unhexify(octet *q, char *p, size_t n) -{ - unsigned a = 0; - int i = 0; - - for (;;) { - if (*p == '-' || *p == ':' || isspace((unsigned char)*p)) { - p++; - continue; - } - if (!n && !*p) - break; - if (!*p) - die(EXIT_FAILURE, "hex string too short"); - if (!isxdigit((unsigned char)*p)) - die(EXIT_FAILURE, "bad hex string"); - if (!n) - die(EXIT_FAILURE, "hex string too long"); - a = (a << 4) | xdigit(*p++); - i++; - if (i == 2) { - *q++ = U8(a); - a = 0; - i = 0; - n--; - } - } -} - static int cmd_verify(int argc, char *argv[]) { key_file f; @@ -1809,17 +1804,21 @@ static int cmd_verify(int argc, char *argv[]) const gchash *ch = &rmd160; ghash *h; key *k; - octet *buf; const octet *fpr; + dstr d = DSTR_INIT, dd = DSTR_INIT; + codec *c; + const char *p; + const struct fpres *fpres = fprestab; key_filter kf = { KF_NONSECRET, KF_NONSECRET }; for (;;) { static struct option opt[] = { { "filter", OPTF_ARGREQ, 0, 'f' }, + { "presentation", OPTF_ARGREQ, 0, 'p' }, { "algorithm", OPTF_ARGREQ, 0, 'a' }, { 0, 0, 0, 0 } }; - int i = mdwopt(argc, argv, "+f:a:", opt, 0, 0, 0); + int i = mdwopt(argc, argv, "+f:a:p:", opt, 0, 0, 0); if (i < 0) break; switch (i) { @@ -1829,6 +1828,9 @@ static int cmd_verify(int argc, char *argv[]) if (err || *p) die(EXIT_FAILURE, "bad filter string `%s'", optarg); } break; + case 'p': + fpres = lookup_fpres(optarg); + break; case 'a': if ((ch = ghash_byname(optarg)) == 0) die(EXIT_FAILURE, "unknown hash algorithm `%s'", optarg); @@ -1840,21 +1842,36 @@ static int cmd_verify(int argc, char *argv[]) } argv += optind; argc -= optind; - if (rc || argc != 2) - die(EXIT_FAILURE, "Usage: verify [-f FILTER] TAG FINGERPRINT"); + if (rc || argc != 2) { + die(EXIT_FAILURE, + "Usage: verify [-a HASHALG] [-p STYLE] [-f FILTER] TAG FINGERPRINT"); + } doopen(&f, KOPEN_READ); if ((k = key_bytag(&f, argv[0])) == 0) die(EXIT_FAILURE, "key `%s' not found", argv[0]); - buf = xmalloc(ch->hashsz); - unhexify(buf, argv[1], ch->hashsz); + for (p = argv[1]; *p; p++) { + if (strchr(fpres->sep, *p)) continue; + dstr_putc(&dd, *p); + } + c = fpres->cdc->decoder(CDCF_IGNCASE | CDCF_IGNEQPAD | + CDCF_IGNSPC | CDCF_IGNNEWL); + if ((rc = c->ops->code(c, dd.buf, dd.len, &d)) != 0 || + (rc = c->ops->code(c, 0, 0, &d)) != 0) + die(EXIT_FAILURE, "invalid fingerprint: %s", codec_strerror(rc)); + c->ops->destroy(c); + if (d.len != ch->hashsz) { + die(EXIT_FAILURE, "incorrect fingerprint length (%lu != %lu)", + (unsigned long)d.len, (unsigned long)ch->hashsz); + } h = GH_INIT(ch); if (!key_fingerprint(k, h, &kf)) die(EXIT_FAILURE, "key has no fingerprintable components (as filtered)"); fpr = GH_DONE(h, 0); - if (memcmp(fpr, buf, ch->hashsz) != 0) + if (memcmp(fpr, d.buf, ch->hashsz) != 0) die(EXIT_FAILURE, "key fingerprint mismatch"); + dstr_destroy(&d); dstr_destroy(&dd); doclose(&f); return (0); } @@ -2090,7 +2107,9 @@ static int cmd_merge(int argc, char *argv[]) LI("Key-generation algorithms", keygen, \ algtab[i].name, algtab[i].name) \ LI("Random seeding algorithms", seed, \ - seedtab[i].p, seedtab[i].p) + seedtab[i].p, seedtab[i].p) \ + LI("Fingerprint presentation styles", fpres, \ + fprestab[i].name, fprestab[i].name) MAKELISTTAB(listtab, LISTS) @@ -2113,17 +2132,21 @@ Options:\n\ -q, --quiet Show less information.\n\ -v, --verbose Show more information.\n\ " }, - { "fingerprint", cmd_finger, "fingerprint [-f FILTER] [TAG...]", "\ + { "fingerprint", cmd_finger, + "fingerprint [-a HASHALG] [-p STYLE] [-f FILTER] [TAG...]", "\ Options:\n\ \n\ -f, --filter=FILT Only hash key components matching FILT.\n\ +-p, --presentation=STYLE Use STYLE for presenting fingerprints.\n\ -a, --algorithm=HASH Use the named HASH algorithm.\n\ ($ show hash for list.)\n\ " }, - { "verify", cmd_verify, "verify [-f FILTER] TAG FINGERPRINT", "\ + { "verify", cmd_verify, + "verify [-a HASH] [-p STYLE] [-f FILTER] TAG FINGERPRINT", "\ Options:\n\ \n\ -f, --filter=FILT Only hash key components matching FILT.\n\ +-p, --presentation=STYLE Expect FINGERPRINT in the given STYLE.\n\ -a, --algorithm=HASH Use the named HASH algorithm.\n\ ($ show hash for list.)\n\ " }, -- [mdw]