From: Jonathan McDowell Date: Sat, 7 Sep 2019 12:04:14 +0000 (+0000) Subject: Add support for full signature verification X-Git-Tag: onak-0.6.0~11 X-Git-Url: https://the.earth.li/gitweb/?p=onak.git;a=commitdiff_plain;h=c981a80699901eb3d513a4cc9355574a69016037 Add support for full signature verification For a long time it has been known that attacks against the keyserver network were easy due to the lack of cryptographic verification on signatures, as well as the lack of other checks and balances. Add the ability to actually verify signatures, allowing those are that are not valid (or that come from non-present keys) to be removed. --- diff --git a/CMakeLists.txt b/CMakeLists.txt index bf819d7..eebafdd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,8 @@ if ("x${CMAKE_INSTALL_FULL_RUNSTATEDIR}" STREQUAL "x") ${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/run) endif() +list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) + # Configuration options from the user set(DBTYPE "dynamic" CACHE STRING "Configure the default database backend to use" ) @@ -34,7 +36,7 @@ endif() # Core objects add_library(libonak STATIC armor.c charfuncs.c cleankey.c cleanup.c decodekey.c getcgi.c hash.c keyarray.c keyid.c keyindex.c ll.c log.c marshal.c - mem.c merge.c onak-conf.c parsekey.c photoid.c sigcheck.c sendsync.c + mem.c merge.c onak-conf.c parsekey.c photoid.c rsa.c sigcheck.c sendsync.c sha1x.c wordlist.c) set(LIBONAK_LIBRARIES "") @@ -48,6 +50,18 @@ else() target_sources(libonak PRIVATE md5.c sha1.c) endif() +# We need libhogweed and libgmp to be able to do more than hash calculations +pkg_check_modules(HOGWEED hogweed) +if (HOGWEED_FOUND) + find_package(GMP) +endif() +if (GMP_FOUND) + set(HAVE_CRYPTO true) + target_include_directories(libonak SYSTEM PUBLIC + ${GMP_INCLUDE_DIRS} ${HOGWEED_INCLUDE_DIRS}) + LIST(APPEND LIBONAK_LIBRARIES ${GMP_LIBRARY} ${HOGWEED_LIBRARIES}) +endif() + # Build files that have substitutions in them include_directories(${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}) configure_file("${CMAKE_SOURCE_DIR}/build-config.h.in" diff --git a/build-config.h.in b/build-config.h.in index 15abe12..48e4c87 100644 --- a/build-config.h.in +++ b/build-config.h.in @@ -3,6 +3,7 @@ #define ONAK_VERSION "@VERSION@" +#cmakedefine HAVE_CRYPTO 1 #cmakedefine HAVE_NETTLE 1 #cmakedefine HAVE_SYSTEMD 1 #cmakedefine WORDS_BIGENDIAN 1 diff --git a/cleankey.c b/cleankey.c index 55e3161..51274fe 100644 --- a/cleankey.c +++ b/cleankey.c @@ -17,10 +17,11 @@ */ #include -#include #include +#include "build-config.h" #include "cleankey.h" +#include "decodekey.h" #include "keyid.h" #include "keystructs.h" #include "log.h" @@ -132,19 +133,32 @@ int dedupsubkeys(struct openpgp_publickey *key) * hash cannot be checked (eg we don't support that hash type) we err * on the side of caution and keep it. */ -int clean_sighashes(struct openpgp_publickey *key, +int clean_sighashes(struct onak_dbctx *dbctx, + struct openpgp_publickey *key, struct openpgp_packet *sigdata, - struct openpgp_packet_list **sigs) + struct openpgp_packet_list **sigs, + bool fullverify, + bool *selfsig, bool *othersig) { struct openpgp_packet_list *tmpsig; + struct openpgp_publickey *sigkey = NULL; onak_status_t ret; uint8_t hashtype; uint8_t hash[64]; uint8_t *sighash; int removed = 0; - uint64_t keyid; + uint64_t keyid, sigid; + bool remove; + get_keyid(key, &keyid); + if (othersig != NULL) { + *othersig = false; + } + if (selfsig != NULL) { + *selfsig = false; + } while (*sigs != NULL) { + remove = false; ret = calculate_packet_sighash(key, sigdata, (*sigs)->packet, &hashtype, hash, &sighash); @@ -155,10 +169,57 @@ int clean_sighashes(struct openpgp_publickey *key, PRIX64, hashtype, keyid); - sigs = &(*sigs)->next; - } else if (ret != ONAK_E_OK || + if (fullverify) { + remove = true; + } + } else if (ret != ONAK_E_OK || (!fullverify && !(hash[0] == sighash[0] && - hash[1] == sighash[1])) { + hash[1] == sighash[1]))) { + remove = true; + } + +#if HAVE_CRYPTO + if (fullverify && !remove) { + sig_info((*sigs)->packet, &sigid, NULL); + + /* Start by assuming it's a bad sig */ + + remove = true; + if (sigid == keyid) { + ret = onak_check_hash_sig(key, (*sigs)->packet, + hash, hashtype); + + /* We have a valid self signature */ + if (ret == ONAK_E_OK) { + remove = false; + if (selfsig != NULL) { + *selfsig = true; + } + } + } + + if (remove && dbctx->fetch_key_id(dbctx, sigid, + &sigkey, false)) { + + ret = onak_check_hash_sig(sigkey, + (*sigs)->packet, + hash, hashtype); + + /* Got a valid signature */ + if (ret == ONAK_E_OK) { + remove = false; + if (othersig != NULL) { + *othersig = true; + } + } + + free_publickey(sigkey); + sigkey = NULL; + } + } +#endif + + if (remove) { tmpsig = *sigs; *sigs = (*sigs)->next; tmpsig->next = NULL; @@ -172,27 +233,46 @@ int clean_sighashes(struct openpgp_publickey *key, return removed; } -int clean_list_sighashes(struct openpgp_publickey *key, - struct openpgp_signedpacket_list *siglist) +int clean_list_sighashes(struct onak_dbctx *dbctx, + struct openpgp_publickey *key, + struct openpgp_signedpacket_list **siglist, + bool fullverify, bool needother) { + struct openpgp_signedpacket_list *tmp = NULL; + bool selfsig, othersig; int removed = 0; - while (siglist != NULL) { - removed += clean_sighashes(key, siglist->packet, - &siglist->sigs); - siglist = siglist->next; + while (siglist != NULL && *siglist != NULL) { + selfsig = othersig = false; + + removed += clean_sighashes(dbctx, key, (*siglist)->packet, + &(*siglist)->sigs, fullverify, &selfsig, &othersig); + + if (fullverify && (!selfsig || (needother && !othersig))) { + /* Remove the UID/subkey if there's no selfsig */ + tmp = *siglist; + *siglist = (*siglist)->next; + tmp->next = NULL; + free_signedpacket_list(tmp); + } else { + siglist = &(*siglist)->next; + } } return removed; } -int clean_key_sighashes(struct openpgp_publickey *key) +int clean_key_signatures(struct onak_dbctx *dbctx, + struct openpgp_publickey *key, bool fullverify, bool needother) { int removed; - removed = clean_sighashes(key, NULL, &key->sigs); - removed += clean_list_sighashes(key, key->uids); - removed += clean_list_sighashes(key, key->subkeys); + removed = clean_sighashes(dbctx, key, NULL, &key->sigs, fullverify, + NULL, NULL); + removed += clean_list_sighashes(dbctx, key, &key->uids, fullverify, + needother); + removed += clean_list_sighashes(dbctx, key, &key->subkeys, fullverify, + false); return removed; } @@ -268,7 +348,7 @@ int cleankeys(struct onak_dbctx *dbctx, struct openpgp_publickey **keys, while (*curkey != NULL) { if (policies & ONAK_CLEAN_DROP_V3_KEYS) { if ((*curkey)->publickey->data[0] < 4) { - /* Remove the key from the list */ + /* Remove the key from the list if it's < v4 */ tmp = *curkey; *curkey = tmp->next; tmp->next = NULL; @@ -282,13 +362,24 @@ int cleankeys(struct onak_dbctx *dbctx, struct openpgp_publickey **keys, } count += dedupuids(*curkey); count += dedupsubkeys(*curkey); - if (policies & ONAK_CLEAN_CHECK_SIGHASH) { - count += clean_key_sighashes(*curkey); + if (policies & (ONAK_CLEAN_CHECK_SIGHASH | + ONAK_CLEAN_VERIFY_SIGNATURES)) { + count += clean_key_signatures(dbctx, *curkey, + policies & ONAK_CLEAN_VERIFY_SIGNATURES, + policies & ONAK_CLEAN_NEED_OTHER_SIG); } if (count > 0) { changed++; } - curkey = &(*curkey)->next; + if ((*curkey)->uids == NULL) { + /* No valid UIDS so remove the key from the list */ + tmp = *curkey; + *curkey = tmp->next; + tmp->next = NULL; + free_publickey(tmp); + } else { + curkey = &(*curkey)->next; + } } return changed; diff --git a/cleankey.h b/cleankey.h index 4671d20..204d6d1 100644 --- a/cleankey.h +++ b/cleankey.h @@ -26,6 +26,8 @@ #define ONAK_CLEAN_LARGE_PACKETS (1 << 1) #define ONAK_CLEAN_DROP_V3_KEYS (1 << 2) #define ONAK_CLEAN_UPDATE_ONLY (1 << 3) +#define ONAK_CLEAN_VERIFY_SIGNATURES (1 << 4) +#define ONAK_CLEAN_NEED_OTHER_SIG (1 << 5) #define ONAK_CLEAN_ALL (uint64_t) -1 /** diff --git a/cmake/FindGMP.cmake b/cmake/FindGMP.cmake new file mode 100644 index 0000000..f129e18 --- /dev/null +++ b/cmake/FindGMP.cmake @@ -0,0 +1,24 @@ +# No pkg-config support in GMP, so try and find it manually + +set(GMP_PREFIX "" CACHE PATH "path ") + +find_path(GMP_INCLUDE_DIR gmp.h gmpxx.h + PATHS ${GMP_PREFIX}/include /usr/include /usr/local/include) + +find_library(GMP_LIBRARY NAMES gmp libgmp + PATHS ${GMP_PREFIX}/lib /usr/lib /usr/local/lib) + +if(GMP_INCLUDE_DIR AND GMP_LIBRARY) + get_filename_component(GMP_LIBRARY_DIR ${GMP_LIBRARY} PATH) + set(GMP_FOUND TRUE) +endif() + +if(GMP_FOUND) + if(NOT GMP_FIND_QUIETLY) + MESSAGE(STATUS "Found GMP: ${GMP_LIBRARY}") + endif() +elseif(GMP_FOUND) + if(GMP_FIND_REQUIRED) + message(FATAL_ERROR "Could not find GMP") + endif() +endif() diff --git a/onak-conf.c b/onak-conf.c index a67c950..6d67a74 100644 --- a/onak-conf.c +++ b/onak-conf.c @@ -313,6 +313,21 @@ static bool parseconfigline(char *line) config.clean_policies &= ~ONAK_CLEAN_LARGE_PACKETS; } + } else if (MATCH("verification", "require_other_sig")) { +#if HAVE_CRYPTO + if (parsebool(value, config.clean_policies & + ONAK_CLEAN_NEED_OTHER_SIG)) { + config.clean_policies |= + ONAK_CLEAN_NEED_OTHER_SIG; + } else { + config.clean_policies &= + ~ONAK_CLEAN_NEED_OTHER_SIG; + } +#else + logthing(LOGTHING_ERROR, + "Compiled without crypto support, " + "require_other_sig not available."); +#endif } else if (MATCH("verification", "update_only")) { if (parsebool(value, config.clean_policies & ONAK_CLEAN_UPDATE_ONLY)) { @@ -322,6 +337,21 @@ static bool parseconfigline(char *line) config.clean_policies &= ~ONAK_CLEAN_UPDATE_ONLY; } + } else if (MATCH("verification", "verify_signatures")) { +#if HAVE_CRYPTO + if (parsebool(value, config.clean_policies & + ONAK_CLEAN_VERIFY_SIGNATURES)) { + config.clean_policies |= + ONAK_CLEAN_VERIFY_SIGNATURES; + } else { + config.clean_policies &= + ~ONAK_CLEAN_VERIFY_SIGNATURES; + } +#else + logthing(LOGTHING_ERROR, + "Compiled without crypto support, " + "verify_signatures not available."); +#endif } else { return false; } diff --git a/onak.h b/onak.h index 7193bf2..7856dbf 100644 --- a/onak.h +++ b/onak.h @@ -27,6 +27,8 @@ typedef enum { ONAK_E_INVALID_PKT, ONAK_E_UNKNOWN_VER, ONAK_E_UNSUPPORTED_FEATURE, + ONAK_E_BAD_SIGNATURE, + ONAK_E_WEAK_SIGNATURE, } onak_status_t; #endif /* __ONAK_H__ */ diff --git a/onak.ini.in b/onak.ini.in index dc68e1f..7149b5a 100644 --- a/onak.ini.in +++ b/onak.ini.in @@ -25,10 +25,19 @@ check_sighash=true ; Drop v3 (and older) keys. These are long considered insecure, so unless there ; is a good reason you should accept this default. drop_v3=true +; Specify that a key must have a certificate from another key in order for it +; to be accepted. Only valid when verify_signatures is set, meaning new keys +; can only be added if they are certified by keys already present. +;require_other_sig=false ; Only allow keys that already exist to be update; silently drop the addition ; of any key we don't already know about. Useful for allowing updates to ; curated keys without the addition of new keys. ;update_only=false +; Verify signatures, dropping those that cannot or do not validate. Keys/UIDS +; that lack valid self signatures will also be dropped. Note that in order to +; valid a signature the signing key must be present in the key database, so +; multiple passes may be required to import new keyrings fully. +;verify_signatures=false ; Settings related to the email interface to onak. [mail] diff --git a/rsa.c b/rsa.c new file mode 100644 index 0000000..ceb81e1 --- /dev/null +++ b/rsa.c @@ -0,0 +1,79 @@ +/* + * rsa.c - routines to check RSA hash signature combos not present in libnettle + * + * Copyright 2019 Jonathan McDowell + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; version 2 of the License. + * + * This program 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 General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include + +#include +#include +#include + +#include "rsa.h" + +const uint8_t ripemd160_prefix[] = { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, + 0x2B, 0x24, 0x03, 0x02, 0x01, + 0x05, 0x00, + 0x04, 0x14 +}; + +const uint8_t sha224_prefix[] = { 0x30, 0x2D, 0x30, 0x0d, 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, + 0x05, 0x00, + 0x04, 0x1c +}; + +const uint8_t sha384_prefix[] = { 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, + 0x05, 0x00, + 0x04, 0x3 +}; + +int rsa_ripemd160_verify_digest(const struct rsa_public_key *key, + const uint8_t *digest, + const mpz_t s) +{ + uint8_t buf[sizeof(ripemd160_prefix) + RIPEMD160_DIGEST_SIZE]; + + memcpy(buf, ripemd160_prefix, sizeof(ripemd160_prefix)); + memcpy(buf + sizeof(ripemd160_prefix), digest, RIPEMD160_DIGEST_SIZE); + + return rsa_pkcs1_verify(key, sizeof(buf), buf, s); +} + +int rsa_sha224_verify_digest(const struct rsa_public_key *key, + const uint8_t *digest, + const mpz_t s) +{ + uint8_t buf[sizeof(sha224_prefix) + SHA224_DIGEST_SIZE]; + + memcpy(buf, sha224_prefix, sizeof(sha224_prefix)); + memcpy(buf + sizeof(sha224_prefix), digest, SHA224_DIGEST_SIZE); + + return rsa_pkcs1_verify(key, sizeof(buf), buf, s); +} + +int rsa_sha384_verify_digest(const struct rsa_public_key *key, + const uint8_t *digest, + const mpz_t s) +{ + uint8_t buf[sizeof(sha384_prefix) + SHA384_DIGEST_SIZE]; + + memcpy(buf, sha384_prefix, sizeof(sha384_prefix)); + memcpy(buf + sizeof(sha384_prefix), digest, SHA384_DIGEST_SIZE); + + return rsa_pkcs1_verify(key, sizeof(buf), buf, s); +} diff --git a/rsa.h b/rsa.h new file mode 100644 index 0000000..a204f45 --- /dev/null +++ b/rsa.h @@ -0,0 +1,42 @@ +/* + * rsa.h - routines to check RSA hash signature combos not present in libnettle + * + * Copyright 2019 Jonathan McDowell + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; version 2 of the License. + * + * This program 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 General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#ifndef __RSA_H__ +#define __RSA_H__ + +#ifdef HAVE_NETTLE +#include + +#include +#include +#include + +int rsa_ripemd160_verify_digest(const struct rsa_public_key *key, + const uint8_t *digest, + const mpz_t s); + +int rsa_sha224_verify_digest(const struct rsa_public_key *key, + const uint8_t *digest, + const mpz_t s); + +int rsa_sha384_verify_digest(const struct rsa_public_key *key, + const uint8_t *digest, + const mpz_t s); +#endif + +#endif /* __RSA_H__ */ diff --git a/sigcheck.c b/sigcheck.c index 89b3f92..2f72a49 100644 --- a/sigcheck.c +++ b/sigcheck.c @@ -17,12 +17,14 @@ */ #include +#include #include "build-config.h" #include "decodekey.h" #include "keyid.h" #include "keystructs.h" #include "log.h" +#include "onak.h" #include "openpgp.h" #include "sigcheck.h" @@ -34,8 +36,457 @@ #include "md5.h" #include "sha1.h" #endif + #include "sha1x.h" +#ifdef HAVE_CRYPTO +#include +#include +#include +#include +#include +#include +#include +#include "rsa.h" +#endif + +/* Take an MPI from a buffer and import it into a GMP mpz_t */ +#define MPI_TO_MPZ(pk, v) \ +{ \ + /* MPI length is stored in bits, convert it to bytes */ \ + if (pk->length < (ofs + 2)) { \ + ret = ONAK_E_INVALID_PKT; \ + } else { \ + len = pk->data[ofs] << 8 | pk->data[ofs + 1]; \ + len += 7; \ + len = len >> 3; \ + if (pk->length < (ofs + len + 2)) { \ + ret = ONAK_E_INVALID_PKT; \ + } else { \ + mpz_import(v, len, 1, 1, 0, 0, &pk->data[ofs + 2]); \ + ofs += len + 2; \ + } \ + } \ +} + +#if HAVE_CRYPTO + +/* + * Hold the crypto material for a public key. + * May want to move to a header at some point. + */ +struct onak_key_material { + uint8_t type; + union { + struct dsa_params dsa; + struct ecc_point ecc; + struct rsa_public_key rsa; + uint8_t ed25519[32]; + }; + mpz_t y; +}; + +static void onak_free_key_material(struct onak_key_material *key) +{ + switch (key->type) { + case OPENPGP_PKALGO_ECDSA: + ecc_point_clear(&key->ecc); + break; + case OPENPGP_PKALGO_DSA: + mpz_clear(key->dsa.p); + mpz_clear(key->dsa.q); + mpz_clear(key->dsa.g); + mpz_clear(key->y); + break; + case OPENPGP_PKALGO_RSA: + case OPENPGP_PKALGO_RSA_ENC: + case OPENPGP_PKALGO_RSA_SIGN: + mpz_clear(key->rsa.n); + mpz_clear(key->rsa.e); + break; + } + + /* Set the key type back to 0 to indicate we cleared it */ + key->type = 0; + + return; +} + +static onak_status_t onak_parse_key_material(struct openpgp_packet *pk, + struct onak_key_material *key) +{ + int i, len, ofs; + enum onak_oid oid; + mpz_t x, y; + onak_status_t ret = ONAK_E_OK; + + /* Clear the key type; only set it when fully parsed */ + key->type = 0; + + /* + * Shortest valid key is v4 Ed25519, which takes 51 bytes, so do a + * quick sanity check which will ensure we have enough data to check + * the packet header and OID info. + */ + if (pk->length < 51) + return ONAK_E_INVALID_PKT; + + if (pk->data[0] != 4 && pk->data[0] != 5) + return ONAK_E_UNSUPPORTED_FEATURE; + + /* + * MPIs are after version byte, 4 byte creation time + + * type byte plus length for v5. + */ + ofs = (pk->data[0] == 4) ? 6 : 10; + switch (pk->data[5]) { + case OPENPGP_PKALGO_ECDSA: + oid = onak_parse_oid(&pk->data[ofs], pk->length - ofs); + if (oid == ONAK_OID_INVALID) + return ONAK_E_INVALID_PKT; + if (oid == ONAK_OID_UNKNOWN) + return ONAK_E_UNSUPPORTED_FEATURE; + + if (oid == ONAK_OID_NISTP256) { + if (pk->length - ofs != 76) + return ONAK_E_INVALID_PKT; + /* Move past the OID to the key data MPI */ + ofs += pk->data[ofs] + 1; + len = pk->data[ofs] << 8 | pk->data[ofs + 1]; + if (len != 515) + return ONAK_E_INVALID_PKT; + if (pk->data[ofs + 2] != 4) + return ONAK_E_INVALID_PKT; + mpz_init(x); + mpz_init(y); + ecc_point_init(&key->ecc, nettle_get_secp_256r1()); + ofs += 3; + mpz_import(x, 32, 1, 1, 0, 0, &pk->data[ofs]); + ofs += 32; + mpz_import(y, 32, 1, 1, 0, 0, &pk->data[ofs]); + ofs += 32; + ecc_point_set(&key->ecc, x, y); + } else if (oid == ONAK_OID_NISTP384) { + if (pk->length - ofs != 105) + return ONAK_E_INVALID_PKT; + /* Move past the OID to the key data MPI */ + ofs += pk->data[ofs] + 1; + len = pk->data[ofs] << 8 | pk->data[ofs + 1]; + if (len != 771) + return ONAK_E_INVALID_PKT; + if (pk->data[ofs + 2] != 4) + return ONAK_E_INVALID_PKT; + mpz_init(x); + mpz_init(y); + ecc_point_init(&key->ecc, nettle_get_secp_384r1()); + ofs += 3; + mpz_import(x, 48, 1, 1, 0, 0, &pk->data[ofs]); + ofs += 48; + mpz_import(y, 48, 1, 1, 0, 0, &pk->data[ofs]); + ofs += 48; + ecc_point_set(&key->ecc, x, y); + } else if (oid == ONAK_OID_NISTP521) { + if (pk->length - ofs != 141) + return ONAK_E_INVALID_PKT; + /* Move past the OID to the key data MPI */ + ofs += pk->data[ofs] + 1; + len = pk->data[ofs] << 8 | pk->data[ofs + 1]; + if (len != 1059) + return ONAK_E_INVALID_PKT; + if (pk->data[ofs + 2] != 4) + return ONAK_E_INVALID_PKT; + mpz_init(x); + mpz_init(y); + ecc_point_init(&key->ecc, nettle_get_secp_521r1()); + ofs += 3; + mpz_import(x, 66, 1, 1, 0, 0, &pk->data[ofs]); + ofs += 66; + mpz_import(y, 66, 1, 1, 0, 0, &pk->data[ofs]); + ofs += 66; + ecc_point_set(&key->ecc, x, y); + } else { + return ONAK_E_UNSUPPORTED_FEATURE; + } + mpz_clear(y); + mpz_clear(x); + break; + case OPENPGP_PKALGO_EDDSA: + if (pk->length - ofs != 45) + return ONAK_E_INVALID_PKT; + oid = onak_parse_oid(&pk->data[ofs], pk->length - ofs); + if (oid == ONAK_OID_INVALID) + return ONAK_E_INVALID_PKT; + if (oid == ONAK_OID_UNKNOWN) + return ONAK_E_UNSUPPORTED_FEATURE; + + /* Move past the OID to the key data MPI */ + ofs += pk->data[ofs] + 1; + + if (oid == ONAK_OID_ED25519) { + len = pk->data[ofs] << 8 | pk->data[ofs + 1]; + if (len != 263) + return ONAK_E_INVALID_PKT; + if (pk->data[ofs + 2] != 0x40) + return ONAK_E_INVALID_PKT; + ofs += 3; + memcpy(key->ed25519, &pk->data[ofs], 32); + ofs += 32; + } else { + return ONAK_E_UNSUPPORTED_FEATURE; + } + break; + case OPENPGP_PKALGO_DSA: + mpz_init(key->dsa.p); + mpz_init(key->dsa.q); + mpz_init(key->dsa.g); + mpz_init(key->y); + MPI_TO_MPZ(pk, key->dsa.p); + if (ret == ONAK_E_OK) + MPI_TO_MPZ(pk, key->dsa.q); + if (ret == ONAK_E_OK) + MPI_TO_MPZ(pk, key->dsa.g); + if (ret == ONAK_E_OK) + MPI_TO_MPZ(pk, key->y); + break; + case OPENPGP_PKALGO_RSA: + case OPENPGP_PKALGO_RSA_ENC: + case OPENPGP_PKALGO_RSA_SIGN: + mpz_init(key->rsa.n); + mpz_init(key->rsa.e); + key->rsa.size = ((pk->data[6] << 8) + pk->data[7] + 7) >> 3; + MPI_TO_MPZ(pk, key->rsa.n); + if (ret == ONAK_E_OK) + MPI_TO_MPZ(pk, key->rsa.e); + break; + default: + return ONAK_E_UNSUPPORTED_FEATURE; + } + + key->type = pk->data[5]; + + if (ret != ONAK_E_OK) { + onak_free_key_material(key); + } + + return ret; +} + +onak_status_t onak_check_hash_sig(struct openpgp_publickey *sigkey, + struct openpgp_packet *sig, + uint8_t *hash, + uint8_t hashtype) +{ + onak_status_t ret; + struct onak_key_material pubkey; + struct dsa_signature dsasig; + uint8_t edsig[64]; + uint64_t keyid; + int len, ofs; + mpz_t s; + + ret = onak_parse_key_material(sigkey->publickey, &pubkey); + if (ret != ONAK_E_OK) { + return ret; + } + + /* Sanity check the length of the signature packet */ + if (sig->length < 8) { + ret = ONAK_E_INVALID_PKT; + goto out; + } + + /* Is the key the same type as the signature we're checking? */ + if (pubkey.type != sig->data[2]) { + ret = ONAK_E_INVALID_PARAM; + goto out; + } + + /* Skip the hashed data */ + ofs = (sig->data[4] << 8) + sig->data[5] + 6; + if (sig->length < ofs + 2) { + ret = ONAK_E_INVALID_PKT; + goto out; + } + /* Skip the unhashed data */ + ofs += (sig->data[ofs] << 8) + sig->data[ofs + 1] + 2; + if (sig->length < ofs + 2) { + ret = ONAK_E_INVALID_PKT; + goto out; + } + /* Skip the sig hash bytes */ + ofs += 2; + + /* Parse the actual signature values */ + switch (sig->data[2]) { + case OPENPGP_PKALGO_ECDSA: + case OPENPGP_PKALGO_DSA: + mpz_init(dsasig.r); + mpz_init(dsasig.s); + MPI_TO_MPZ(sig, dsasig.r); + if (ret == ONAK_E_OK) + MPI_TO_MPZ(sig, dsasig.s); + break; + case OPENPGP_PKALGO_EDDSA: + mpz_init(dsasig.r); + mpz_init(dsasig.s); + MPI_TO_MPZ(sig, dsasig.r); + if (ret == ONAK_E_OK) + MPI_TO_MPZ(sig, dsasig.s); + mpz_export(edsig, NULL, 1, 1, 0, 0, dsasig.r); + mpz_export(&edsig[32], NULL, 1, 1, 0, 0, dsasig.s); + break; + case OPENPGP_PKALGO_RSA: + case OPENPGP_PKALGO_RSA_SIGN: + mpz_init(s); + MPI_TO_MPZ(sig, s); + break; + } + + /* If we didn't parse the signature properly then do clean-up */ + if (ret != ONAK_E_OK) + goto sigerr; + + /* Squash a signing only RSA key to a standard RSA key for below */ + if (pubkey.type == OPENPGP_PKALGO_RSA_SIGN) { + pubkey.type = OPENPGP_PKALGO_RSA; + } + +#define KEYHASH(key, hash) ((key << 8) | hash) + + switch KEYHASH(pubkey.type, hashtype) { + case KEYHASH(OPENPGP_PKALGO_DSA, OPENPGP_HASH_MD5): + ret = dsa_verify(&pubkey.dsa, pubkey.y, + MD5_DIGEST_SIZE, hash, &dsasig) ? + ONAK_E_WEAK_SIGNATURE : ONAK_E_BAD_SIGNATURE; + break; + case KEYHASH(OPENPGP_PKALGO_DSA, OPENPGP_HASH_RIPEMD160): + ret = dsa_verify(&pubkey.dsa, pubkey.y, + RIPEMD160_DIGEST_SIZE, hash, &dsasig) ? + ONAK_E_OK : ONAK_E_BAD_SIGNATURE; + break; + case KEYHASH(OPENPGP_PKALGO_DSA, OPENPGP_HASH_SHA1): + ret = dsa_verify(&pubkey.dsa, pubkey.y, + SHA1_DIGEST_SIZE, hash, &dsasig) ? + ONAK_E_OK : ONAK_E_BAD_SIGNATURE; + break; + case KEYHASH(OPENPGP_PKALGO_DSA, OPENPGP_HASH_SHA1X): + ret = dsa_verify(&pubkey.dsa, pubkey.y, + SHA1X_DIGEST_SIZE, hash, &dsasig) ? + ONAK_E_OK : ONAK_E_BAD_SIGNATURE; + break; + case KEYHASH(OPENPGP_PKALGO_DSA, OPENPGP_HASH_SHA224): + ret = dsa_verify(&pubkey.dsa, pubkey.y, + SHA224_DIGEST_SIZE, hash, &dsasig) ? + ONAK_E_OK : ONAK_E_BAD_SIGNATURE; + break; + case KEYHASH(OPENPGP_PKALGO_DSA, OPENPGP_HASH_SHA256): + ret = dsa_verify(&pubkey.dsa, pubkey.y, + SHA256_DIGEST_SIZE, hash, &dsasig) ? + ONAK_E_OK : ONAK_E_BAD_SIGNATURE; + break; + case KEYHASH(OPENPGP_PKALGO_DSA, OPENPGP_HASH_SHA384): + ret = dsa_verify(&pubkey.dsa, pubkey.y, + SHA384_DIGEST_SIZE, hash, &dsasig) ? + ONAK_E_OK : ONAK_E_BAD_SIGNATURE; + break; + case KEYHASH(OPENPGP_PKALGO_DSA, OPENPGP_HASH_SHA512): + ret = dsa_verify(&pubkey.dsa, pubkey.y, + SHA512_DIGEST_SIZE, hash, &dsasig) ? + ONAK_E_OK : ONAK_E_BAD_SIGNATURE; + break; + case KEYHASH(OPENPGP_PKALGO_ECDSA, OPENPGP_HASH_SHA1): + ret = ecdsa_verify(&pubkey.ecc, + SHA1_DIGEST_SIZE, hash, &dsasig) ? + ONAK_E_OK : ONAK_E_BAD_SIGNATURE; + case KEYHASH(OPENPGP_PKALGO_ECDSA, OPENPGP_HASH_SHA256): + ret = ecdsa_verify(&pubkey.ecc, + SHA256_DIGEST_SIZE, hash, &dsasig) ? + ONAK_E_OK : ONAK_E_BAD_SIGNATURE; + break; + case KEYHASH(OPENPGP_PKALGO_ECDSA, OPENPGP_HASH_SHA384): + ret = ecdsa_verify(&pubkey.ecc, + SHA384_DIGEST_SIZE, hash, &dsasig) ? + ONAK_E_OK : ONAK_E_BAD_SIGNATURE; + break; + case KEYHASH(OPENPGP_PKALGO_ECDSA, OPENPGP_HASH_SHA512): + ret = ecdsa_verify(&pubkey.ecc, + SHA512_DIGEST_SIZE, hash, &dsasig) ? + ONAK_E_OK : ONAK_E_BAD_SIGNATURE; + break; + case KEYHASH(OPENPGP_PKALGO_EDDSA, OPENPGP_HASH_RIPEMD160): + ret = ed25519_sha512_verify(pubkey.ed25519, + RIPEMD160_DIGEST_SIZE, hash, edsig) ? + ONAK_E_OK : ONAK_E_BAD_SIGNATURE; + break; + case KEYHASH(OPENPGP_PKALGO_EDDSA, OPENPGP_HASH_SHA256): + ret = ed25519_sha512_verify(pubkey.ed25519, + SHA256_DIGEST_SIZE, hash, edsig) ? + ONAK_E_OK : ONAK_E_BAD_SIGNATURE; + break; + case KEYHASH(OPENPGP_PKALGO_EDDSA, OPENPGP_HASH_SHA384): + ret = ed25519_sha512_verify(pubkey.ed25519, + SHA384_DIGEST_SIZE, hash, edsig) ? + ONAK_E_OK : ONAK_E_BAD_SIGNATURE; + break; + case KEYHASH(OPENPGP_PKALGO_EDDSA, OPENPGP_HASH_SHA512): + ret = ed25519_sha512_verify(pubkey.ed25519, + SHA512_DIGEST_SIZE, hash, edsig) ? + ONAK_E_OK : ONAK_E_BAD_SIGNATURE; + break; + case KEYHASH(OPENPGP_PKALGO_RSA, OPENPGP_HASH_MD5): + ret = rsa_md5_verify_digest(&pubkey.rsa, hash, s) ? + ONAK_E_WEAK_SIGNATURE : ONAK_E_BAD_SIGNATURE; + break; + case KEYHASH(OPENPGP_PKALGO_RSA, OPENPGP_HASH_RIPEMD160): + ret = rsa_ripemd160_verify_digest(&pubkey.rsa, hash, s) ? + ONAK_E_OK : ONAK_E_BAD_SIGNATURE; + break; + case KEYHASH(OPENPGP_PKALGO_RSA, OPENPGP_HASH_SHA1): + ret = rsa_sha1_verify_digest(&pubkey.rsa, hash, s) ? + ONAK_E_OK : ONAK_E_BAD_SIGNATURE; + break; + case KEYHASH(OPENPGP_PKALGO_RSA, OPENPGP_HASH_SHA224): + ret = rsa_sha224_verify_digest(&pubkey.rsa, hash, s) ? + ONAK_E_OK : ONAK_E_BAD_SIGNATURE; + break; + case KEYHASH(OPENPGP_PKALGO_RSA, OPENPGP_HASH_SHA256): + ret = rsa_sha256_verify_digest(&pubkey.rsa, hash, s) ? + ONAK_E_OK : ONAK_E_BAD_SIGNATURE; + break; + case KEYHASH(OPENPGP_PKALGO_RSA, OPENPGP_HASH_SHA384): + ret = rsa_sha384_verify_digest(&pubkey.rsa, hash, s) ? + ONAK_E_OK : ONAK_E_BAD_SIGNATURE; + break; + case KEYHASH(OPENPGP_PKALGO_RSA, OPENPGP_HASH_SHA512): + ret = rsa_sha512_verify_digest(&pubkey.rsa, hash, s) ? + ONAK_E_OK : ONAK_E_BAD_SIGNATURE; + break; + default: + ret = ONAK_E_UNSUPPORTED_FEATURE; + } + +sigerr: + switch (sig->data[2]) { + case OPENPGP_PKALGO_ECDSA: + case OPENPGP_PKALGO_EDDSA: + case OPENPGP_PKALGO_DSA: + mpz_clear(dsasig.r); + mpz_clear(dsasig.s); + break; + case OPENPGP_PKALGO_RSA: + case OPENPGP_PKALGO_RSA_SIGN: + mpz_clear(s); + break; + } + +out: + onak_free_key_material(&pubkey); + + return ret; +} + +#endif /* HAVE_CRYPTO */ + onak_status_t calculate_packet_sighash(struct openpgp_publickey *key, struct openpgp_packet *packet, struct openpgp_packet *sig, diff --git a/sigcheck.h b/sigcheck.h index 756987e..20dcb13 100644 --- a/sigcheck.h +++ b/sigcheck.h @@ -9,4 +9,16 @@ onak_status_t calculate_packet_sighash(struct openpgp_publickey *key, uint8_t *hash, uint8_t **sighash); +/** + * onak_check_hash_sig - check the signature on a hash is valid + * @sigkey: The public key that made the signature + * @sig: The signature packet + * @hash: Hash digest the signature is over + * @hashtype: Type of hash (OPENPGP_HASH_*) + */ +onak_status_t onak_check_hash_sig(struct openpgp_publickey *sigkey, + struct openpgp_packet *sig, + uint8_t *hash, + uint8_t hashtype); + #endif /* __SIGCHECK_H__ */