TLS "security profiles" define which minimum cipher suites and other parameter values are allowed in a specific usage scenario for TLS, for example WPA3 in theory requires a specific security profile for its TLS-based EAP method. This will also allow the unit tests to test individual cipher suites. --- ell/ell.sym | 1 + ell/tls-private.h | 2 + ell/tls.c | 101 ++++++++++++++++++++++++++++++++++++++++++---- ell/tls.h | 3 ++ 4 files changed, 99 insertions(+), 8 deletions(-) diff --git a/ell/ell.sym b/ell/ell.sym index 23d2349..e4f157e 100644 --- a/ell/ell.sym +++ b/ell/ell.sym @@ -417,6 +417,7 @@ global: l_tls_set_cacert; l_tls_set_auth_data; l_tls_set_version_range; + l_tls_set_cipher_suites; l_tls_alert_to_str; l_tls_set_debug; /* uintset */ diff --git a/ell/tls-private.h b/ell/tls-private.h index 3fda4b5..29ce8f9 100644 --- a/ell/tls-private.h +++ b/ell/tls-private.h @@ -147,6 +147,8 @@ struct l_tls { struct l_key *priv_key; size_t priv_key_size; + struct tls_cipher_suite **cipher_suite_pref_list; + /* Record layer */ uint8_t *record_buf; diff --git a/ell/tls.c b/ell/tls.c index 59d7f5a..61bcc19 100644 --- a/ell/tls.c +++ b/ell/tls.c @@ -41,6 +41,7 @@ #include "tls-private.h" #include "key.h" #include "asn1-private.h" +#include "strv.h" bool tls10_prf(const void *secret, size_t secret_len, const char *label, @@ -840,6 +841,7 @@ static bool tls_send_client_hello(struct l_tls *tls) uint8_t *ptr = buf + TLS_HANDSHAKE_HEADER_SIZE; uint8_t *len_ptr; int i; + struct tls_cipher_suite **suite; /* Fill in the Client Hello body */ @@ -855,14 +857,16 @@ static bool tls_send_client_hello(struct l_tls *tls) len_ptr = ptr; ptr += 2; - for (i = 0; i < (int) L_ARRAY_SIZE(tls_cipher_suite_pref); i++) { - if (!tls_cipher_suite_is_compatible(tls, - &tls_cipher_suite_pref[i], - NULL)) + for (suite = tls->cipher_suite_pref_list; *suite; suite++) { + const char *error; + + if (!tls_cipher_suite_is_compatible(tls, *suite, &error)) { + TLS_DEBUG("non-fatal: %s", error); continue; + } - *ptr++ = tls_cipher_suite_pref[i].id[0]; - *ptr++ = tls_cipher_suite_pref[i].id[1]; + *ptr++ = (*suite)->id[0]; + *ptr++ = (*suite)->id[1]; } if (ptr == len_ptr + 2) { @@ -1524,6 +1528,7 @@ static void tls_handle_client_hello(struct l_tls *tls, const uint8_t *cipher_suites; const uint8_t *compression_methods; int i; + enum l_tls_alert_desc alert_desc = TLS_ALERT_HANDSHAKE_FAIL; /* Do we have enough for ProtocolVersion + Random + SessionID size? */ if (len < 2 + 32 + 1) @@ -1618,12 +1623,39 @@ static void tls_handle_client_hello(struct l_tls *tls, TLS_DEBUG("Negotiated TLS " TLS_VER_FMT, TLS_VER_ARGS(tls->negotiated_version)); + if (!tls->cipher_suite_pref_list[0]) { + TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0, + "No usable cipher suites"); + return; + } + /* Select a cipher suite according to client's preference list */ while (cipher_suites_size) { struct tls_cipher_suite *suite = tls_find_cipher_suite(cipher_suites); + struct tls_cipher_suite **iter; + const char *error; + + for (iter = tls->cipher_suite_pref_list; *iter; iter++) + if (*iter == suite) + break; - if (suite && tls_cipher_suite_is_compatible(tls, suite, NULL)) { + if (!suite) + TLS_DEBUG("non-fatal: Cipher suite %04x unknown", + l_get_be16(cipher_suites)); + else if (!tls_cipher_suite_is_compatible(tls, suite, &error)) + TLS_DEBUG("non-fatal: %s", error); + else if (!*iter) { + /* + * We have at least one matching compatible suite but + * it is not allowed in this security profile. If the + * handshake ends up failing then we blame the security + * profile. + */ + alert_desc = TLS_ALERT_INSUFFICIENT_SECURITY; + TLS_DEBUG("non-fatal: Cipher suite %s disallowed " + "by config", suite->name); + } else { tls->pending.cipher_suite = suite; break; } @@ -1633,7 +1665,7 @@ static void tls_handle_client_hello(struct l_tls *tls, } if (!cipher_suites_size) { - TLS_DISCONNECT(TLS_ALERT_HANDSHAKE_FAIL, 0, + TLS_DISCONNECT(alert_desc, 0, "No common cipher suites matching negotiated " "TLS version and our certificate's key type"); return; @@ -1701,6 +1733,7 @@ static void tls_handle_server_hello(struct l_tls *tls, { uint8_t session_id_size, cipher_suite_id[2], compression_method_id; const char *error; + struct tls_cipher_suite **iter; int i; /* Do we have enough for ProtocolVersion + Random + SessionID len ? */ @@ -1756,6 +1789,16 @@ static void tls_handle_server_hello(struct l_tls *tls, return; } + for (iter = tls->cipher_suite_pref_list; *iter; iter++) + if (*iter == tls->pending.cipher_suite) + break; + if (!*iter) { + TLS_DISCONNECT(TLS_ALERT_INSUFFICIENT_SECURITY, 0, + "Selected cipher suite %s disallowed by config", + tls->pending.cipher_suite->name); + return; + } + if (!tls_cipher_suite_is_compatible(tls, tls->pending.cipher_suite, &error)) { TLS_DISCONNECT(TLS_ALERT_HANDSHAKE_FAIL, 0, @@ -2485,6 +2528,7 @@ LIB_EXPORT struct l_tls *l_tls_new(bool server, tls->user_data = user_data; tls->min_version = TLS_MIN_VERSION; tls->max_version = TLS_MAX_VERSION; + l_tls_set_cipher_suites(tls, NULL); tls->signature_hash = HANDSHAKE_HASH_SHA256; @@ -2525,6 +2569,8 @@ LIB_EXPORT void l_tls_free(struct l_tls *tls) if (tls->debug_destroy) tls->debug_destroy(tls->debug_data); + l_free(tls->cipher_suite_pref_list); + l_free(tls); } @@ -2790,6 +2836,45 @@ LIB_EXPORT void l_tls_set_version_range(struct l_tls *tls, max_version : TLS_MAX_VERSION; } +LIB_EXPORT void l_tls_set_cipher_suites(struct l_tls *tls, + const char **suite_list) +{ + unsigned int i, len; + struct tls_cipher_suite **suite; + + l_free(tls->cipher_suite_pref_list); + + if (suite_list) + len = l_strv_length((char **) suite_list); + else + len = L_ARRAY_SIZE(tls_cipher_suite_pref); + + tls->cipher_suite_pref_list = l_new(struct tls_cipher_suite *, len + 1); + + if (!suite_list) { + /* Use our default cipher suite preference list */ + for (i = 0; i < L_ARRAY_SIZE(tls_cipher_suite_pref); i++) + tls->cipher_suite_pref_list[i] = + &tls_cipher_suite_pref[i]; + + return; + } + + suite = tls->cipher_suite_pref_list; + + for (; *suite_list; suite_list++) { + for (i = 0; i < L_ARRAY_SIZE(tls_cipher_suite_pref); i++) + if (!strcmp(tls_cipher_suite_pref[i].name, *suite_list)) + break; + + if (i < L_ARRAY_SIZE(tls_cipher_suite_pref)) + *suite++ = &tls_cipher_suite_pref[i]; + else + TLS_DEBUG("Cipher suite %s is not supported", + *suite_list); + } +} + LIB_EXPORT const char *l_tls_alert_to_str(enum l_tls_alert_desc desc) { switch (desc) { diff --git a/ell/tls.h b/ell/tls.h index 5b2f398..c684dc2 100644 --- a/ell/tls.h +++ b/ell/tls.h @@ -113,6 +113,9 @@ bool l_tls_set_auth_data(struct l_tls *tls, const char *cert_path, void l_tls_set_version_range(struct l_tls *tls, uint16_t min_version, uint16_t max_version); +/* Optionally limit allowed cipher suites to a custom set */ +void l_tls_set_cipher_suites(struct l_tls *tls, const char **suite_list); + const char *l_tls_alert_to_str(enum l_tls_alert_desc desc); enum l_checksum_type; -- 2.19.1