From: Jonathan McDowell Date: Mon, 15 Apr 2019 18:19:22 +0000 (+0100) Subject: Add support for v5 keys X-Git-Tag: onak-0.6.0~60 X-Git-Url: https://the.earth.li/gitweb/?a=commitdiff_plain;h=85187675424f3854869f1607afd8a1e84e536946;p=onak.git Add support for v5 keys v5 is being specified in RFC4880bis. It uses a slightly different packet format for public key packets and uses SHA2-256 for fingerprints. Add basic support for parsing and storing these days, and some new unit tests using the v5 test key. --- diff --git a/decodekey.c b/decodekey.c index 8e7d544..28f8cee 100644 --- a/decodekey.c +++ b/decodekey.c @@ -229,6 +229,7 @@ onak_status_t sig_info(struct openpgp_packet *packet, uint64_t *keyid, } break; case 4: + case 5: res = parse_subpackets(&packet->data[4], packet->length - 4, &length, keyid, creation); diff --git a/keydb.c b/keydb.c index f92c252..f8d5a0b 100644 --- a/keydb.c +++ b/keydb.c @@ -273,13 +273,23 @@ static int generic_fetch_key_fp(struct onak_dbctx *dbctx, /* * We assume if the backend is using this function it's not storing * anything bigger than the 64 bit key ID and just truncate the - * fingerprint to get that value. This doesn't work for v3 keys, + * fingerprint to get that value. v4 keys want the lowest 64 bits, v5 + * keys need the top 64 bits. This doesn't work for v3 keys, * but there's no way to map from v3 fingerprint to v3 key ID so * if the backend can't do it we're going to fail anyway. */ keyid = 0; - for (i = (fingerprint->length - 8); i < fingerprint->length; i++) { - keyid = (keyid << 8) + fingerprint->fp[i]; + if (fingerprint->length == 20) { + /* v4 */ + for (i = (fingerprint->length - 8); i < fingerprint->length; + i++) { + keyid = (keyid << 8) + fingerprint->fp[i]; + } + } else { + /* v5 */ + for (i = 0; i < 8; i++) { + keyid = (keyid << 8) + fingerprint->fp[i]; + } } return dbctx->fetch_key_id(dbctx, keyid, publickey, intrans); diff --git a/keyid.c b/keyid.c index 9ac97b1..d03d47a 100644 --- a/keyid.c +++ b/keyid.c @@ -42,9 +42,23 @@ uint64_t fingerprint2keyid(struct openpgp_fingerprint *fingerprint) uint64_t keyid; int i; - for (keyid = 0, i = 12; i < 20; i++) { - keyid <<= 8; - keyid += fingerprint->fp[i]; + switch (fingerprint->length) { + case 20: + /* v4, keyid is last 64 bits */ + for (keyid = 0, i = 12; i < 20; i++) { + keyid <<= 8; + keyid += fingerprint->fp[i]; + } + break; + case 32: + /* v5, keyid is first 64 bits */ + for (keyid = 0, i = 0; i < 8; i++) { + keyid <<= 8; + keyid += fingerprint->fp[i]; + } + break; + default: + keyid = (uint64_t) -1; } return keyid; @@ -73,6 +87,7 @@ onak_status_t get_keyid(struct openpgp_publickey *publickey, uint64_t *keyid) onak_status_t get_fingerprint(struct openpgp_packet *packet, struct openpgp_fingerprint *fingerprint) { + struct sha256_ctx sha2_ctx; struct sha1_ctx sha_ctx; struct md5_ctx md5_context; unsigned char c; @@ -103,7 +118,6 @@ onak_status_t get_fingerprint(struct openpgp_packet *packet, md5_digest(&md5_context, fingerprint->length, fingerprint->fp); break; - case 4: sha1_init(&sha_ctx); /* @@ -121,6 +135,25 @@ onak_status_t get_fingerprint(struct openpgp_packet *packet, fingerprint->length = 20; sha1_digest(&sha_ctx, fingerprint->length, fingerprint->fp); + break; + case 5: + sha256_init(&sha2_ctx); + /* RFC4880bis 12.2 */ + c = 0x9A; + sha256_update(&sha2_ctx, sizeof(c), &c); + c = packet->length >> 24; + sha256_update(&sha2_ctx, sizeof(c), &c); + c = packet->length >> 16; + sha256_update(&sha2_ctx, sizeof(c), &c); + c = packet->length >> 8; + sha256_update(&sha2_ctx, sizeof(c), &c); + c = packet->length & 0xFF; + sha256_update(&sha2_ctx, sizeof(c), &c); + sha256_update(&sha2_ctx, packet->length, + packet->data); + fingerprint->length = 32; + sha256_digest(&sha2_ctx, fingerprint->length, fingerprint->fp); + break; default: return ONAK_E_UNKNOWN_VER; @@ -203,10 +236,9 @@ onak_status_t get_packetid(struct openpgp_packet *packet, uint64_t *keyid) } break; case 4: + case 5: get_fingerprint(packet, &fingerprint); - *keyid = fingerprint2keyid(&fingerprint); - break; default: return ONAK_E_UNKNOWN_VER; diff --git a/keyindex.c b/keyindex.c index c656bd1..94f52f7 100644 --- a/keyindex.c +++ b/keyindex.c @@ -80,6 +80,7 @@ char pkalgo2char(uint8_t algo) unsigned int keylength(struct openpgp_packet *keydata) { unsigned int length; + uint8_t keyofs; switch (keydata->data[0]) { case 2: @@ -88,106 +89,109 @@ unsigned int keylength(struct openpgp_packet *keydata) keydata->data[9]; break; case 4: + case 5: + /* v5 has an additional 4 bytes of key length data */ + keyofs = (keydata->data[0] == 4) ? 6 : 10; switch (keydata->data[5]) { case OPENPGP_PKALGO_EC: case OPENPGP_PKALGO_ECDSA: case OPENPGP_PKALGO_EDDSA: /* Elliptic curve key size is based on OID */ /* Curve25519 / 1.3.6.1.4.1.3029.1.5.1 */ - if ((keydata->data[6] == 10) && - (keydata->data[7] == 0x2B) && - (keydata->data[8] == 0x06) && - (keydata->data[9] == 0x01) && - (keydata->data[10] == 0x04) && - (keydata->data[11] == 0x01) && - (keydata->data[12] == 0x97) && - (keydata->data[13] == 0x55) && - (keydata->data[14] == 0x01) && - (keydata->data[15] == 0x05) && - (keydata->data[16] == 0x01)) { + if ((keydata->data[keyofs] == 10) && + (keydata->data[keyofs + 1] == 0x2B) && + (keydata->data[keyofs + 2] == 0x06) && + (keydata->data[keyofs + 3] == 0x01) && + (keydata->data[keyofs + 4] == 0x04) && + (keydata->data[keyofs + 5] == 0x01) && + (keydata->data[keyofs + 6] == 0x97) && + (keydata->data[keyofs + 7] == 0x55) && + (keydata->data[keyofs + 8] == 0x01) && + (keydata->data[keyofs + 9] == 0x05) && + (keydata->data[keyofs + 10] == 0x01)) { length = 255; /* Ed25519 / 1.3.6.1.4.1.11591.15.1 */ - } else if ((keydata->data[6] == 9) && - (keydata->data[7] == 0x2B) && - (keydata->data[8] == 0x06) && - (keydata->data[9] == 0x01) && - (keydata->data[10] == 0x04) && - (keydata->data[11] == 0x01) && - (keydata->data[12] == 0xDA) && - (keydata->data[13] == 0x47) && - (keydata->data[14] == 0x0F) && - (keydata->data[15] == 0x01)) { + } else if ((keydata->data[keyofs] == 9) && + (keydata->data[keyofs + 1] == 0x2B) && + (keydata->data[keyofs + 2] == 0x06) && + (keydata->data[keyofs + 3] == 0x01) && + (keydata->data[keyofs + 4] == 0x04) && + (keydata->data[keyofs + 5] == 0x01) && + (keydata->data[keyofs + 6] == 0xDA) && + (keydata->data[keyofs + 7] == 0x47) && + (keydata->data[keyofs + 8] == 0x0F) && + (keydata->data[keyofs + 9] == 0x01)) { length = 255; /* nistp256 / 1.2.840.10045.3.1.7 */ - } else if ((keydata->data[6] == 8) && - (keydata->data[7] == 0x2A) && - (keydata->data[8] == 0x86) && - (keydata->data[9] == 0x48) && - (keydata->data[10] == 0xCE) && - (keydata->data[11] == 0x3D) && - (keydata->data[12] == 0x03) && - (keydata->data[13] == 0x01) && - (keydata->data[14] == 0x07)) { + } else if ((keydata->data[keyofs] == 8) && + (keydata->data[keyofs + 1] == 0x2A) && + (keydata->data[keyofs + 2] == 0x86) && + (keydata->data[keyofs + 3] == 0x48) && + (keydata->data[keyofs + 4] == 0xCE) && + (keydata->data[keyofs + 5] == 0x3D) && + (keydata->data[keyofs + 6] == 0x03) && + (keydata->data[keyofs + 7] == 0x01) && + (keydata->data[keyofs + 8] == 0x07)) { length = 256; /* nistp384 / 1.3.132.0.34 */ - } else if ((keydata->data[6] == 5) && - (keydata->data[7] == 0x2B) && - (keydata->data[8] == 0x81) && - (keydata->data[9] == 0x04) && - (keydata->data[10] == 0x00) && - (keydata->data[11] == 0x22)) { + } else if ((keydata->data[keyofs] == 5) && + (keydata->data[keyofs + 1] == 0x2B) && + (keydata->data[keyofs + 2] == 0x81) && + (keydata->data[keyofs + 3] == 0x04) && + (keydata->data[keyofs + 4] == 0x00) && + (keydata->data[keyofs + 5] == 0x22)) { length = 384; /* nistp521 / 1.3.132.0.35 */ - } else if ((keydata->data[6] == 5) && - (keydata->data[7] == 0x2B) && - (keydata->data[8] == 0x81) && - (keydata->data[9] == 0x04) && - (keydata->data[10] == 0x00) && - (keydata->data[11] == 0x23)) { + } else if ((keydata->data[keyofs] == 5) && + (keydata->data[keyofs + 1] == 0x2B) && + (keydata->data[keyofs + 2] == 0x81) && + (keydata->data[keyofs + 3] == 0x04) && + (keydata->data[keyofs + 4] == 0x00) && + (keydata->data[keyofs + 5] == 0x23)) { length = 521; /* brainpoolP256r1 / 1.3.36.3.3.2.8.1.1.7 */ - } else if ((keydata->data[6] == 9) && - (keydata->data[7] == 0x2B) && - (keydata->data[8] == 0x24) && - (keydata->data[9] == 0x03) && - (keydata->data[10] == 0x03) && - (keydata->data[11] == 0x02) && - (keydata->data[12] == 0x08) && - (keydata->data[13] == 0x01) && - (keydata->data[14] == 0x01) && - (keydata->data[15] == 0x07)) { + } else if ((keydata->data[keyofs] == 9) && + (keydata->data[keyofs + 1] == 0x2B) && + (keydata->data[keyofs + 2] == 0x24) && + (keydata->data[keyofs + 3] == 0x03) && + (keydata->data[keyofs + 4] == 0x03) && + (keydata->data[keyofs + 5] == 0x02) && + (keydata->data[keyofs + 6] == 0x08) && + (keydata->data[keyofs + 7] == 0x01) && + (keydata->data[keyofs + 8] == 0x01) && + (keydata->data[keyofs + 9] == 0x07)) { length = 256; /* brainpoolP384r1 / 1.3.36.3.3.2.8.1.1.11 */ - } else if ((keydata->data[6] == 9) && - (keydata->data[7] == 0x2B) && - (keydata->data[8] == 0x24) && - (keydata->data[9] == 0x03) && - (keydata->data[10] == 0x03) && - (keydata->data[11] == 0x02) && - (keydata->data[12] == 0x08) && - (keydata->data[13] == 0x01) && - (keydata->data[14] == 0x01) && - (keydata->data[15] == 0x0B)) { + } else if ((keydata->data[keyofs] == 9) && + (keydata->data[keyofs + 1] == 0x2B) && + (keydata->data[keyofs + 2] == 0x24) && + (keydata->data[keyofs + 3] == 0x03) && + (keydata->data[keyofs + 4] == 0x03) && + (keydata->data[keyofs + 5] == 0x02) && + (keydata->data[keyofs + 6] == 0x08) && + (keydata->data[keyofs + 7] == 0x01) && + (keydata->data[keyofs + 8] == 0x01) && + (keydata->data[keyofs + 9] == 0x0B)) { length = 384; /* brainpoolP512r1 / 1.3.36.3.3.2.8.1.1.13 */ - } else if ((keydata->data[6] == 9) && - (keydata->data[7] == 0x2B) && - (keydata->data[8] == 0x24) && - (keydata->data[9] == 0x03) && - (keydata->data[10] == 0x03) && - (keydata->data[11] == 0x02) && - (keydata->data[12] == 0x08) && - (keydata->data[13] == 0x01) && - (keydata->data[14] == 0x01) && - (keydata->data[15] == 0x0D)) { + } else if ((keydata->data[keyofs] == 9) && + (keydata->data[keyofs + 1] == 0x2B) && + (keydata->data[keyofs + 2] == 0x24) && + (keydata->data[keyofs + 3] == 0x03) && + (keydata->data[keyofs + 4] == 0x03) && + (keydata->data[keyofs + 5] == 0x02) && + (keydata->data[keyofs + 6] == 0x08) && + (keydata->data[keyofs + 7] == 0x01) && + (keydata->data[keyofs + 8] == 0x01) && + (keydata->data[keyofs + 9] == 0x0D)) { length = 512; /* secp256k1 / 1.3.132.0.10 */ - } else if ((keydata->data[6] == 5) && - (keydata->data[7] == 0x2B) && - (keydata->data[8] == 0x81) && - (keydata->data[9] == 0x04) && - (keydata->data[10] == 0x00) && - (keydata->data[11] == 0x0A)) { + } else if ((keydata->data[keyofs] == 5) && + (keydata->data[keyofs + 1] == 0x2B) && + (keydata->data[keyofs + 2] == 0x81) && + (keydata->data[keyofs + 3] == 0x04) && + (keydata->data[keyofs + 4] == 0x00) && + (keydata->data[keyofs + 5] == 0x0A)) { length = 256; } else { logthing(LOGTHING_ERROR, @@ -196,8 +200,8 @@ unsigned int keylength(struct openpgp_packet *keydata) } break; default: - length = (keydata->data[6] << 8) + - keydata->data[7]; + length = (keydata->data[keyofs] << 8) + + keydata->data[keyofs + 1]; } break; default: @@ -321,11 +325,12 @@ int list_subkeys(struct onak_dbctx *dbctx, type = subkeys->packet->data[7]; break; case 4: + case 5: type = subkeys->packet->data[5]; break; default: logthing(LOGTHING_ERROR, - "Unknown key type: %d", + "Unknown key version: %d", subkeys->packet->data[0]); } length = keylength(subkeys->packet); @@ -440,10 +445,11 @@ int key_index(struct onak_dbctx *dbctx, type = keys->publickey->data[7]; break; case 4: + case 5: type = keys->publickey->data[5]; break; default: - logthing(LOGTHING_ERROR, "Unknown key type: %d", + logthing(LOGTHING_ERROR, "Unknown key version: %d", keys->publickey->data[0]); } length = keylength(keys->publickey); @@ -557,6 +563,7 @@ int mrkey_index(struct openpgp_publickey *keys) type = keys->publickey->data[7]; break; case 4: + case 5: (void) get_fingerprint(keys->publickey, &fingerprint); for (i = 0; i < fingerprint.length; i++) { @@ -566,7 +573,7 @@ int mrkey_index(struct openpgp_publickey *keys) type = keys->publickey->data[5]; break; default: - logthing(LOGTHING_ERROR, "Unknown key type: %d", + logthing(LOGTHING_ERROR, "Unknown key version: %d", keys->publickey->data[0]); } length = keylength(keys->publickey); diff --git a/keys/README b/keys/README index bbd8206..ac64056 100644 --- a/keys/README +++ b/keys/README @@ -22,3 +22,5 @@ huggie-rev.key A revoked v4 key. elgv3.key A v3 key using Elgamal (generated by older version of GnuPG) +test-v5.key + A v5 sample key as provided by Werner Koch on the OpenPGP mailing list diff --git a/keys/v5-test.key b/keys/v5-test.key new file mode 100644 index 0000000..eed8ccc Binary files /dev/null and b/keys/v5-test.key differ diff --git a/keystructs.h b/keystructs.h index 9ec6ebf..af909f7 100644 --- a/keystructs.h +++ b/keystructs.h @@ -26,8 +26,12 @@ #include "ll.h" -/* v3 MD5 fingerprint is 16 characters, v4 SHA-1 fingerprint is 20 */ -#define MAX_FINGERPRINT_LEN 20 +/* + * v3 MD5 fingerprint is 16 bytes + * v4 SHA-1 fingerprint is 20 + * v5 SHA2-256 fingerprint is 32 + */ +#define MAX_FINGERPRINT_LEN 32 /** * @brief Stores the fingerprint of an OpenPGP key diff --git a/lookup.c b/lookup.c index 91371b4..35c039b 100644 --- a/lookup.c +++ b/lookup.c @@ -147,8 +147,20 @@ int main(int argc, char *argv[]) params[i+1] = NULL; if (search != NULL && strlen(search) == 42 && search[0] == '0' && search[1] == 'x') { - fingerprint.length = MAX_FINGERPRINT_LEN; - for (j = 0; j < MAX_FINGERPRINT_LEN; j++) { + /* v4 fingerprint */ + fingerprint.length = 20; + for (j = 0; j < 20; j++) { + fingerprint.fp[j] = (hex2bin( + search[2 + j * 2]) + << 4) + + hex2bin(search[3 + j * 2]); + } + isfp = true; + } else if (search != NULL && strlen(search) == 66 && + search[0] == '0' && search[1] == 'x') { + /* v5 fingerprint */ + fingerprint.length = 32; + for (j = 0; j < 32; j++) { fingerprint.fp[j] = (hex2bin( search[2 + j * 2]) << 4) + diff --git a/onak.c b/onak.c index 63f19d7..b7cfc80 100644 --- a/onak.c +++ b/onak.c @@ -330,8 +330,19 @@ int main(int argc, char *argv[]) search = argv[optind+1]; if (search != NULL && strlen(search) == 42 && search[0] == '0' && search[1] == 'x') { - fingerprint.length = MAX_FINGERPRINT_LEN; - for (i = 0; i < MAX_FINGERPRINT_LEN; i++) { + /* v4 fingerprint */ + fingerprint.length = 20; + for (i = 0; i < 20; i++) { + fingerprint.fp[i] = + (hex2bin(search[2 + i * 2]) << 4) + + hex2bin(search[3 + i * 2]); + } + isfp = true; + } else if (search != NULL && strlen(search) == 66 && + search[0] == '0' && search[1] == 'x') { + /* v5 fingerprint */ + fingerprint.length = 32; + for (i = 0; i < 32; i++) { fingerprint.fp[i] = (hex2bin(search[2 + i * 2]) << 4) + hex2bin(search[3 + i * 2]); diff --git a/parsekey.c b/parsekey.c index 618492b..49b91bf 100644 --- a/parsekey.c +++ b/parsekey.c @@ -377,9 +377,9 @@ onak_status_t read_openpgp_stream(int (*getchar_func)(void *ctx, size_t count, case OPENPGP_PACKET_SIGNATURE: case OPENPGP_PACKET_SECRETKEY: case OPENPGP_PACKET_PUBLICKEY: - /* Must be v2 -> v4 */ + /* Must be v2 -> v5 */ if (curpacket->packet->data[0] < 2 || - curpacket->packet->data[0] > 4) { + curpacket->packet->data[0] > 5) { rc = ONAK_E_INVALID_PKT; } break; diff --git a/t/all-025-get-v5.t b/t/all-025-get-v5.t new file mode 100755 index 0000000..846ba0e --- /dev/null +++ b/t/all-025-get-v5.t @@ -0,0 +1,14 @@ +#!/bin/sh +# Check we can retrieve a key by keyid + +set -e + +cd ${WORKDIR} +${BUILDDIR}/onak -b -c $1 add < ${TESTSDIR}/../keys/v5-test.key +if ! ${BUILDDIR}/onak -c $1 get 0x19347BC987246402 2> /dev/null | \ + grep -q -- '-----BEGIN PGP PUBLIC KEY BLOCK-----'; then + echo "* Did not correctly retrieve key by keyid." + exit 1 +fi + +exit 0 diff --git a/t/all-045-index-text-v5.t b/t/all-045-index-text-v5.t new file mode 100755 index 0000000..0cf8bcc --- /dev/null +++ b/t/all-045-index-text-v5.t @@ -0,0 +1,21 @@ +#!/bin/sh +# Check we can index a key by some uid text + +set -e + +# Backends should really support this, but the file one is as simple as +# possible, so doesn't. Skip the test for it. +if [ "$2" = "file" ]; then + exit 0 +fi + +cd ${WORKDIR} +${BUILDDIR}/onak -b -c $1 add < ${TESTSDIR}/../keys/v5-test.key +if ! ${BUILDDIR}/onak -c $1 index emma 2> /dev/null | \ + grep -q -- \ + 'pub 255E/87246402 2019/03/20 emma.goldman@example.net'; then + echo "* Did not correctly retrieve key by text" + exit 1 +fi + +exit 0 diff --git a/t/all-065-get-fingerprint-v5.t b/t/all-065-get-fingerprint-v5.t new file mode 100755 index 0000000..e6d3439 --- /dev/null +++ b/t/all-065-get-fingerprint-v5.t @@ -0,0 +1,14 @@ +#!/bin/sh +# Check we can retrieve a key by key fingerprint + +set -e + +cd ${WORKDIR} +${BUILDDIR}/onak -b -c $1 add < ${TESTSDIR}/../keys/v5-test.key +if ! ${BUILDDIR}/onak -c $1 get 0x19347BC9872464025F99DF3EC2E0000ED9884892E1F7B3EA4C94009159569B54 2> /dev/null | \ + grep -q -- '-----BEGIN PGP PUBLIC KEY BLOCK-----'; then + echo "* Did not correctly retrieve key by fingerprint." + exit 1 +fi + +exit 0 diff --git a/t/all-085-get-subkey-v5.t b/t/all-085-get-subkey-v5.t new file mode 100755 index 0000000..e6076f1 --- /dev/null +++ b/t/all-085-get-subkey-v5.t @@ -0,0 +1,20 @@ +#!/bin/sh +# Check we can retrieve a key by subkeyid + +set -e + +# Backends should really support this, but the file one is as simple as +# possible, so doesn't. Skip the test for it. +if [ "$2" = "file" ]; then + exit 0 +fi + +cd ${WORKDIR} +${BUILDDIR}/onak -b -c $1 add < ${TESTSDIR}/../keys/v5-test.key +if ! ${BUILDDIR}/onak -c $1 get 0xE4557C2B02FFBF4B 2> /dev/null | \ + grep -q -- '-----BEGIN PGP PUBLIC KEY BLOCK-----'; then + echo "* Did not correctly retrieve key by subkey id." + exit 1 +fi + +exit 0