]> the.earth.li Git - onak.git/commitdiff
Add support for full signature verification
authorJonathan McDowell <noodles@earth.li>
Sat, 7 Sep 2019 12:04:14 +0000 (12:04 +0000)
committerJonathan McDowell <noodles@earth.li>
Sat, 7 Sep 2019 12:04:14 +0000 (12:04 +0000)
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.

12 files changed:
CMakeLists.txt
build-config.h.in
cleankey.c
cleankey.h
cmake/FindGMP.cmake [new file with mode: 0644]
onak-conf.c
onak.h
onak.ini.in
rsa.c [new file with mode: 0644]
rsa.h [new file with mode: 0644]
sigcheck.c
sigcheck.h

index bf819d7e718453d6a106c73e69ef1b12b3c0d74f..eebafdd4ebfb99d1f9494325fe743eaa0b96d38a 100644 (file)
@@ -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"
index 15abe12687eb6992f36994f38e19534e57277250..48e4c87776714cb4e4a369f3fd86c8fdba98c4d2 100644 (file)
@@ -3,6 +3,7 @@
 
 #define ONAK_VERSION "@VERSION@"
 
+#cmakedefine HAVE_CRYPTO 1
 #cmakedefine HAVE_NETTLE 1
 #cmakedefine HAVE_SYSTEMD 1
 #cmakedefine WORDS_BIGENDIAN 1
index 55e3161d0d87bb35ebb7d6be2826c919a6571fff..51274fe874b25898a1405e006ddd20296deebc9d 100644 (file)
  */
 
 #include <stdbool.h>
-#include <stdio.h>
 #include <stdlib.h>
 
+#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;
index 4671d207b15cbfa15fac511386d93626e9d2c7ae..204d6d1f65b8cd2c77f197daf5c7703084a16ab0 100644 (file)
@@ -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 (file)
index 0000000..f129e18
--- /dev/null
@@ -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()
index a67c950432e2c05322eff4de91f43a37b01059ad..6d67a74610c81b06f1677badca51a10e1f1a7378 100644 (file)
@@ -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 7193bf280e0a5b401000f60bf243ffc3eb1bc632..7856dbf1452c73ccd42e03f5428c7bcf1c4980e8 100644 (file)
--- 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__ */
index dc68e1f0173f6df9a8d8aea404313787d66ef93d..7149b5a7e56f6c8c8edc4a401e6a64a5b3474a7f 100644 (file)
@@ -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 (file)
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 <noodles@earth.li>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+
+#include <nettle/ripemd160.h>
+#include <nettle/rsa.h>
+#include <nettle/sha.h>
+
+#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 (file)
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 <noodles@earth.li>
+ *
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __RSA_H__
+#define __RSA_H__
+
+#ifdef HAVE_NETTLE
+#include <string.h>
+
+#include <nettle/ripemd160.h>
+#include <nettle/rsa.h>
+#include <nettle/sha.h>
+
+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__ */
index 89b3f92970205df3ff70f629ef201f6bb839c705..2f72a492af2599859efc1ae8079c3eae0a4d2a4d 100644 (file)
  */
 
 #include <stdint.h>
+#include <string.h>
 
 #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"
 
 #include "md5.h"
 #include "sha1.h"
 #endif
+
 #include "sha1x.h"
 
+#ifdef HAVE_CRYPTO
+#include <gmp.h>
+#include <nettle/dsa.h>
+#include <nettle/ecc.h>
+#include <nettle/ecc-curve.h>
+#include <nettle/ecdsa.h>
+#include <nettle/eddsa.h>
+#include <nettle/rsa.h>
+#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,
index 756987e2b72745410769be0694f824e26cfd5c55..20dcb13951257b6344a870e7e26feeec2fb7bb2a 100644 (file)
@@ -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__ */