]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
tls-server: Consider supported signature algorithms when selecting key/certificate
authorPascal Knecht <pascal.knecht@hsr.ch>
Mon, 12 Oct 2020 16:58:53 +0000 (18:58 +0200)
committerTobias Brunner <tobias@strongswan.org>
Fri, 12 Feb 2021 13:35:23 +0000 (14:35 +0100)
This won't work if the client doesn't send a `signature_algorithms`
extension.  But since the default is SHA1/RSA, most will send it to at
least announce stronger hash algorithms if not ECDSA.

src/libtls/tls_crypto.c
src/libtls/tls_crypto.h
src/libtls/tls_server.c

index fc74d31f2e62a672780f107caf9fb32b7903d462..cb4ed9c530d9ad4873bcfe8eba101c4b77d46db6 100644 (file)
@@ -23,6 +23,7 @@
 
 #include <utils/debug.h>
 #include <plugins/plugin_feature.h>
+#include <collections/hashtable.h>
 
 ENUM_BEGIN(tls_cipher_suite_names, TLS_NULL_WITH_NULL_NULL,
                                                                   TLS_DH_anon_WITH_3DES_EDE_CBC_SHA,
@@ -2423,3 +2424,87 @@ tls_named_group_t tls_ec_group_to_curve(diffie_hellman_group_t group)
        }
        return 0;
 }
+
+/**
+ * See header.
+ */
+key_type_t tls_signature_scheme_to_key_type(tls_signature_scheme_t sig)
+{
+       int i;
+
+       for (i = 0; i < countof(schemes); i++)
+       {
+               if (schemes[i].sig == sig)
+               {
+                       return key_type_from_signature_scheme(schemes[i].params.scheme);
+               }
+       }
+       return 0;
+}
+
+/**
+ * Hashtable hash function
+ */
+static u_int hash_key_type(key_type_t *type)
+{
+       return chunk_hash(chunk_from_thing(*type));
+}
+
+/**
+ * Hashtable equals function
+ */
+static bool equals_key_type(key_type_t *key1, key_type_t *key2)
+{
+       return *key1 == *key2;
+}
+
+CALLBACK(filter_key_types, bool,
+       void *data, enumerator_t *orig, va_list args)
+{
+       key_type_t *key_type, *out;
+
+       VA_ARGS_VGET(args, out);
+
+       if (orig->enumerate(orig, NULL, &key_type))
+       {
+               *out = *key_type;
+               return TRUE;
+       }
+       return FALSE;
+}
+
+CALLBACK(destroy_key_types, void,
+       hashtable_t *ht)
+{
+       ht->destroy_function(ht, (void*)free);
+}
+
+/*
+ * See header.
+ */
+enumerator_t *tls_get_supported_key_types(tls_version_t min_version,
+                                                                                 tls_version_t max_version)
+{
+       hashtable_t *ht;
+       key_type_t *type, lookup;
+       int i;
+
+       ht = hashtable_create((hashtable_hash_t)hash_key_type,
+                                                 (hashtable_equals_t)equals_key_type, 4);
+       for (i = 0; i < countof(schemes); i++)
+       {
+               if (schemes[i].min_version <= max_version &&
+                       schemes[i].max_version >= min_version)
+               {
+                       lookup = key_type_from_signature_scheme(schemes[i].params.scheme);
+                       if (!ht->get(ht, &lookup))
+                       {
+                               type = malloc_thing(key_type_t);
+                               *type = lookup;
+                               ht->put(ht, type, type);
+                       }
+               }
+       }
+       return enumerator_create_filter(ht->create_enumerator(ht),
+                                                                       filter_key_types, ht, destroy_key_types);
+}
index 9da07ce7509840659b51b7fa8fb966153d228961..0e9d454f7364bf1da84bd64cb68597495d1dd0df 100644 (file)
@@ -691,4 +691,24 @@ int tls_crypto_get_supported_groups(diffie_hellman_group_t **groups);
  */
 tls_named_group_t tls_ec_group_to_curve(diffie_hellman_group_t group);
 
+/**
+ * Get the key type from a TLS signature scheme
+ *
+ * @param sig                  TLS signature algorithm scheme
+ * @return                             type of a key
+ */
+key_type_t tls_signature_scheme_to_key_type(tls_signature_scheme_t sig);
+
+/**
+ * Create an enumerator over supported key types within a specific TLS version range
+ *
+ * Enumerates over key_type_t
+ *
+ * @param min_version  minimum negotiated TLS version
+ * @param max_version  maximum negotiated TLS version
+ * @return                             hashtable of key types
+ */
+enumerator_t *tls_get_supported_key_types(tls_version_t min_version,
+                                                                                 tls_version_t max_version);
+
 #endif /** TLS_CRYPTO_H_ @}*/
index 4957cba898b5aa76867386b1f78e4e7b08766a3a..f5e15615bbe01daabd332aabe6e01f6828ce6f4e 100644 (file)
@@ -169,71 +169,109 @@ struct private_tls_server_t {
        bool curves_received;
 };
 
+/**
+ * Create an array of an intersection of server and peer supported key types
+ */
+static array_t *create_common_key_types(chunk_t hashsig,
+                                                                               tls_version_t version_min,
+                                                                               tls_version_t version_max)
+{
+       array_t *key_types;
+       enumerator_t *enumerator;
+       key_type_t v, lookup;
+       uint16_t sig_scheme;
+
+       key_types = array_create(sizeof(key_type_t), 8);
+       enumerator = tls_get_supported_key_types(version_min, version_max);
+       while (enumerator->enumerate(enumerator, &v))
+       {
+               bio_reader_t *reader;
+
+               reader = bio_reader_create(hashsig);
+               while (reader->remaining(reader) &&
+                          reader->read_uint16(reader, &sig_scheme))
+               {
+                       lookup = tls_signature_scheme_to_key_type(sig_scheme);
+                       if (v == lookup)
+                       {
+                               array_insert(key_types, ARRAY_TAIL, &lookup);
+                               break;
+                       }
+               }
+               reader->destroy(reader);
+       }
+       enumerator->destroy(enumerator);
+       return key_types;
+}
+
 /**
  * Find a cipher suite and a server key
  */
 static bool select_suite_and_key(private_tls_server_t *this,
                                                                 tls_cipher_suite_t *suites, int count)
 {
+       array_t *key_types;
+       tls_version_t version_min, version_max;
        private_key_t *key;
        key_type_t type;
 
-       key = lib->credmgr->get_private(lib->credmgr, KEY_ANY, this->server,
-                                                                       this->server_auth);
+       version_min = this->tls->get_version_min(this->tls);
+       version_max = this->tls->get_version_max(this->tls);
+       key_types = create_common_key_types(this->hashsig, version_min, version_max);
+       if (!array_count(key_types))
+       {
+               DBG1(DBG_TLS, "no common signature algorithms found");
+               array_destroy(key_types);
+               return FALSE;
+       }
+       while (array_remove(key_types, ARRAY_HEAD, &type))
+       {
+               key = lib->credmgr->get_private(lib->credmgr, type, this->server,
+                                                                               this->server_auth);
+               if (key)
+               {
+                       break;
+               }
+       }
        if (!key)
        {
                DBG1(DBG_TLS, "no usable TLS server certificate found for '%Y'",
                         this->server);
+               array_destroy(key_types);
                return FALSE;
        }
 
-       if (this->tls->get_version_max(this->tls) >= TLS_1_3)
+       if (version_max >= TLS_1_3)
        {
-               /* currently no support to derive key based on client supported
-                * signature schemes */
                this->suite = this->crypto->select_cipher_suite(this->crypto, suites,
                                                                                                                count, KEY_ANY);
-               if (!this->suite)
-               {
-                       DBG1(DBG_TLS, "received cipher suites unacceptable");
-                       return FALSE;
-               }
        }
        else
        {
                this->suite = this->crypto->select_cipher_suite(this->crypto, suites,
-                                                                                                               count,
-                                                                                                               key->get_type(key));
-               if (!this->suite)
-               {       /* no match for this key, try to find another type */
-                       if (key->get_type(key) == KEY_ECDSA)
-                       {
-                               type = KEY_RSA;
-                       }
-                       else
-                       {
-                               type = KEY_ECDSA;
-                       }
-                       key->destroy(key);
-
-                       this->suite = this->crypto->select_cipher_suite(this->crypto, suites,
-                                                                                                                       count, type);
-                       if (!this->suite)
-                       {
-                               DBG1(DBG_TLS, "received cipher suites unacceptable");
-                               return FALSE;
-                       }
+                                                                                                               count, type);
+               while (!this->suite && array_remove(key_types, ARRAY_HEAD, &type))
+               {       /* find a key and cipher suite for one of the remaining key types */
+                       DESTROY_IF(key);
                        this->server_auth->destroy(this->server_auth);
                        this->server_auth = auth_cfg_create();
                        key = lib->credmgr->get_private(lib->credmgr, type, this->server,
                                                                                        this->server_auth);
-                       if (!key)
+                       if (key)
                        {
-                               DBG1(DBG_TLS, "received cipher suites unacceptable");
-                               return FALSE;
+                               this->suite = this->crypto->select_cipher_suite(this->crypto,
+                                                                                                                               suites, count,
+                                                                                                                               type);
                        }
                }
        }
+       array_destroy(key_types);
+       if (!this->suite || !key)
+       {
+               DBG1(DBG_TLS, "received cipher suites or signature schemes unacceptable");
+               return FALSE;
+       }
+       DBG1(DBG_TLS, "using key of type %N", key_type_names, key->get_type(key));
        this->private = key;
        return TRUE;
 }