diff options
Diffstat (limited to '')
-rw-r--r-- | lib/priority.c | 4053 |
1 files changed, 4053 insertions, 0 deletions
diff --git a/lib/priority.c b/lib/priority.c new file mode 100644 index 0000000..efa4d90 --- /dev/null +++ b/lib/priority.c @@ -0,0 +1,4053 @@ +/* + * Copyright (C) 2004-2015 Free Software Foundation, Inc. + * Copyright (C) 2015-2019 Red Hat, Inc. + * + * Author: Nikos Mavrogiannopoulos + * + * This file is part of GnuTLS. + * + * The GnuTLS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/> + * + */ + +/* Here lies the code of the gnutls_*_set_priority() functions. + */ + +#include "gnutls_int.h" +#include "algorithms.h" +#include "errors.h" +#include <num.h> +#include <gnutls/x509.h> +#include <c-ctype.h> +#include <hello_ext.h> +#include <c-strcase.h> +#include "fips.h" +#include "errno.h" +#include "ext/srp.h" +#include <gnutls/gnutls.h> +#include "profiles.h" +#include "c-strcase.h" +#include "inih/ini.h" +#include "locks.h" +#include "profiles.h" +#include "name_val_array.h" + +#define MAX_ELEMENTS GNUTLS_MAX_ALGORITHM_NUM + +#define ENABLE_PROFILE(c, profile) do { \ + c->additional_verify_flags &= 0x00ffffff; \ + c->additional_verify_flags |= GNUTLS_PROFILE_TO_VFLAGS(profile); \ + c->level = _gnutls_profile_to_sec_level(profile); \ + } while(0) + +/* This function is used by the test suite */ +char *_gnutls_resolve_priorities(const char* priorities); + +/* This variable points to either a constant value (DEFAULT_PRIORITY_STRING or + * externally assigned) or heap-allocated + * system_wide_config.default_priority_string. We can't move this to the + * system_wide_config struct, because this variable is part of (private) ABI + * exported for testing. + */ +const char *_gnutls_default_priority_string = DEFAULT_PRIORITY_STRING; + +static void prio_remove(priority_st * priority_list, unsigned int algo); +static void prio_add(priority_st * priority_list, unsigned int algo); +static void +break_list(char *etag, + char *broken_etag[MAX_ELEMENTS], int *size); + +typedef void (bulk_rmadd_func) (priority_st * priority_list, const int *); + +inline static void _set_priority(priority_st * st, const int *list) +{ + int num = 0, i; + + while (list[num] != 0) + num++; + if (num > MAX_ALGOS) + num = MAX_ALGOS; + st->num_priorities = num; + + for (i = 0; i < num; i++) { + st->priorities[i] = list[i]; + } + + return; +} + +inline static void _add_priority(priority_st * st, const int *list) +{ + int num, i, j, init; + + init = i = st->num_priorities; + + for (num = 0; list[num] != 0; ++num) { + if (i + 1 > MAX_ALGOS) { + return; + } + + for (j = 0; j < init; j++) { + if (st->priorities[j] == (unsigned) list[num]) { + break; + } + } + + if (j == init) { + st->priorities[i++] = list[num]; + st->num_priorities++; + } + } + + return; +} + +static void _clear_priorities(priority_st * st, const int *list) +{ + memset(st, 0, sizeof(*st)); +} + +static void _clear_given_priorities(priority_st * st, const int *list) +{ + unsigned i; + + for (i=0;list[i]!=0;i++) { + prio_remove(st, list[i]); + } +} + +static const int _supported_groups_dh[] = { + GNUTLS_GROUP_FFDHE2048, + GNUTLS_GROUP_FFDHE3072, + GNUTLS_GROUP_FFDHE4096, + GNUTLS_GROUP_FFDHE6144, + GNUTLS_GROUP_FFDHE8192, + 0 +}; + +static const int _supported_groups_ecdh[] = { + GNUTLS_GROUP_SECP256R1, + GNUTLS_GROUP_SECP384R1, + GNUTLS_GROUP_SECP521R1, + GNUTLS_GROUP_X25519, /* RFC 8422 */ + GNUTLS_GROUP_X448, /* RFC 8422 */ + 0 +}; + +static const int _supported_groups_gost[] = { +#ifdef ENABLE_GOST + GNUTLS_GROUP_GC256A, + GNUTLS_GROUP_GC256B, + GNUTLS_GROUP_GC256C, + GNUTLS_GROUP_GC256D, + GNUTLS_GROUP_GC512A, + GNUTLS_GROUP_GC512B, + GNUTLS_GROUP_GC512C, +#endif + 0 +}; + +static const int _supported_groups_normal[] = { + GNUTLS_GROUP_SECP256R1, + GNUTLS_GROUP_SECP384R1, + GNUTLS_GROUP_SECP521R1, + GNUTLS_GROUP_X25519, /* RFC 8422 */ + GNUTLS_GROUP_X448, /* RFC 8422 */ + + /* These should stay last as our default behavior + * is to send key shares for two top types (GNUTLS_KEY_SHARE_TOP2) + * and we wouldn't want to have these sent by all clients + * by default as they are quite expensive CPU-wise. */ + GNUTLS_GROUP_FFDHE2048, + GNUTLS_GROUP_FFDHE3072, + GNUTLS_GROUP_FFDHE4096, + GNUTLS_GROUP_FFDHE6144, + GNUTLS_GROUP_FFDHE8192, + 0 +}; +static const int* supported_groups_normal = _supported_groups_normal; + +static const int _supported_groups_secure128[] = { + GNUTLS_GROUP_SECP256R1, + GNUTLS_GROUP_SECP384R1, + GNUTLS_GROUP_SECP521R1, + GNUTLS_GROUP_X25519, /* RFC 8422 */ + GNUTLS_GROUP_X448, /* RFC 8422 */ + GNUTLS_GROUP_FFDHE2048, + GNUTLS_GROUP_FFDHE3072, + GNUTLS_GROUP_FFDHE4096, + GNUTLS_GROUP_FFDHE6144, + GNUTLS_GROUP_FFDHE8192, + 0 +}; +static const int* supported_groups_secure128 = _supported_groups_secure128; + +static const int _supported_groups_suiteb128[] = { + GNUTLS_GROUP_SECP256R1, + GNUTLS_GROUP_SECP384R1, + 0 +}; +static const int* supported_groups_suiteb128 = _supported_groups_suiteb128; + +static const int _supported_groups_suiteb192[] = { + GNUTLS_GROUP_SECP384R1, + 0 +}; +static const int* supported_groups_suiteb192 = _supported_groups_suiteb192; + +static const int _supported_groups_secure192[] = { + GNUTLS_GROUP_SECP384R1, + GNUTLS_GROUP_SECP521R1, + GNUTLS_GROUP_FFDHE8192, + 0 +}; +static const int* supported_groups_secure192 = _supported_groups_secure192; + +static const int protocol_priority[] = { + GNUTLS_TLS1_3, + GNUTLS_TLS1_2, + GNUTLS_TLS1_1, + GNUTLS_TLS1_0, + GNUTLS_DTLS1_2, + GNUTLS_DTLS1_0, + 0 +}; + +/* contains all the supported TLS protocols, intended to be used for eliminating them + */ +static const int stream_protocol_priority[] = { + GNUTLS_TLS1_3, + GNUTLS_TLS1_2, + GNUTLS_TLS1_1, + GNUTLS_TLS1_0, + 0 +}; + +/* contains all the supported DTLS protocols, intended to be used for eliminating them + */ +static const int dgram_protocol_priority[] = { + GNUTLS_DTLS1_2, + GNUTLS_DTLS1_0, + GNUTLS_DTLS0_9, + 0 +}; + +static const int dtls_protocol_priority[] = { + GNUTLS_DTLS1_2, + GNUTLS_DTLS1_0, + 0 +}; + +static const int _protocol_priority_suiteb[] = { + GNUTLS_TLS1_2, + 0 +}; +static const int* protocol_priority_suiteb = _protocol_priority_suiteb; + +static const int _kx_priority_performance[] = { + GNUTLS_KX_RSA, +#ifdef ENABLE_ECDHE + GNUTLS_KX_ECDHE_ECDSA, + GNUTLS_KX_ECDHE_RSA, +#endif +#ifdef ENABLE_DHE + GNUTLS_KX_DHE_RSA, +#endif + 0 +}; +static const int* kx_priority_performance = _kx_priority_performance; + +static const int _kx_priority_pfs[] = { +#ifdef ENABLE_ECDHE + GNUTLS_KX_ECDHE_ECDSA, + GNUTLS_KX_ECDHE_RSA, +#endif +#ifdef ENABLE_DHE + GNUTLS_KX_DHE_RSA, +#endif + 0 +}; +static const int* kx_priority_pfs = _kx_priority_pfs; + +static const int _kx_priority_suiteb[] = { + GNUTLS_KX_ECDHE_ECDSA, + 0 +}; +static const int* kx_priority_suiteb = _kx_priority_suiteb; + +static const int _kx_priority_secure[] = { + /* The ciphersuites that offer forward secrecy take + * precedence + */ +#ifdef ENABLE_ECDHE + GNUTLS_KX_ECDHE_ECDSA, + GNUTLS_KX_ECDHE_RSA, +#endif + GNUTLS_KX_RSA, + /* KX-RSA is now ahead of DHE-RSA and DHE-DSS due to the compatibility + * issues the DHE ciphersuites have. That is, one cannot enforce a specific + * security level without dropping the connection. + */ +#ifdef ENABLE_DHE + GNUTLS_KX_DHE_RSA, +#endif + /* GNUTLS_KX_ANON_DH: Man-in-the-middle prone, don't add! + */ + 0 +}; +static const int* kx_priority_secure = _kx_priority_secure; + +static const int _kx_priority_gost[] = { +#ifdef ENABLE_GOST + GNUTLS_KX_VKO_GOST_12, +#endif + 0, +}; +static const int* kx_priority_gost = _kx_priority_gost; + +static const int _cipher_priority_performance_default[] = { + GNUTLS_CIPHER_AES_128_GCM, + GNUTLS_CIPHER_AES_256_GCM, + GNUTLS_CIPHER_CHACHA20_POLY1305, + GNUTLS_CIPHER_AES_128_CCM, + GNUTLS_CIPHER_AES_256_CCM, + GNUTLS_CIPHER_AES_128_CBC, + GNUTLS_CIPHER_AES_256_CBC, + 0 +}; + +static const int _cipher_priority_performance_no_aesni[] = { + GNUTLS_CIPHER_CHACHA20_POLY1305, + GNUTLS_CIPHER_AES_128_GCM, + GNUTLS_CIPHER_AES_256_GCM, + GNUTLS_CIPHER_AES_128_CCM, + GNUTLS_CIPHER_AES_256_CCM, + GNUTLS_CIPHER_AES_128_CBC, + GNUTLS_CIPHER_AES_256_CBC, + 0 +}; + +/* If GCM and AES acceleration is available then prefer + * them over anything else. Overall we prioritise AEAD + * over legacy ciphers, and 256-bit over 128 (for future + * proof). + */ +static const int _cipher_priority_normal_default[] = { + GNUTLS_CIPHER_AES_256_GCM, + GNUTLS_CIPHER_CHACHA20_POLY1305, + GNUTLS_CIPHER_AES_256_CCM, + + GNUTLS_CIPHER_AES_256_CBC, + + GNUTLS_CIPHER_AES_128_GCM, + GNUTLS_CIPHER_AES_128_CCM, + + GNUTLS_CIPHER_AES_128_CBC, + 0 +}; + +static const int cipher_priority_performance_fips[] = { + GNUTLS_CIPHER_AES_128_GCM, + GNUTLS_CIPHER_AES_128_CCM, + GNUTLS_CIPHER_AES_256_GCM, + GNUTLS_CIPHER_AES_256_CCM, + + GNUTLS_CIPHER_AES_128_CBC, + GNUTLS_CIPHER_AES_256_CBC, + 0 +}; + +static const int cipher_priority_normal_fips[] = { + GNUTLS_CIPHER_AES_256_GCM, + GNUTLS_CIPHER_AES_256_CCM, + GNUTLS_CIPHER_AES_256_CBC, + + GNUTLS_CIPHER_AES_128_GCM, + GNUTLS_CIPHER_AES_128_CBC, + GNUTLS_CIPHER_AES_128_CCM, + 0 +}; + + +static const int _cipher_priority_suiteb128[] = { + GNUTLS_CIPHER_AES_256_GCM, + GNUTLS_CIPHER_AES_128_GCM, + 0 +}; +static const int* cipher_priority_suiteb128 = _cipher_priority_suiteb128; + +static const int _cipher_priority_suiteb192[] = { + GNUTLS_CIPHER_AES_256_GCM, + 0 +}; +static const int* cipher_priority_suiteb192 = _cipher_priority_suiteb192; + + +static const int _cipher_priority_secure128[] = { + GNUTLS_CIPHER_AES_256_GCM, + GNUTLS_CIPHER_CHACHA20_POLY1305, + GNUTLS_CIPHER_AES_256_CBC, + GNUTLS_CIPHER_AES_256_CCM, + + GNUTLS_CIPHER_AES_128_GCM, + GNUTLS_CIPHER_AES_128_CBC, + GNUTLS_CIPHER_AES_128_CCM, + 0 +}; +static const int *cipher_priority_secure128 = _cipher_priority_secure128; + + +static const int _cipher_priority_secure192[] = { + GNUTLS_CIPHER_AES_256_GCM, + GNUTLS_CIPHER_CHACHA20_POLY1305, + GNUTLS_CIPHER_AES_256_CBC, + GNUTLS_CIPHER_AES_256_CCM, + 0 +}; +static const int* cipher_priority_secure192 = _cipher_priority_secure192; + +static const int _sign_priority_default[] = { + GNUTLS_SIGN_RSA_SHA256, + GNUTLS_SIGN_RSA_PSS_SHA256, + GNUTLS_SIGN_RSA_PSS_RSAE_SHA256, + GNUTLS_SIGN_ECDSA_SHA256, + GNUTLS_SIGN_ECDSA_SECP256R1_SHA256, + + GNUTLS_SIGN_EDDSA_ED25519, + + GNUTLS_SIGN_RSA_SHA384, + GNUTLS_SIGN_RSA_PSS_SHA384, + GNUTLS_SIGN_RSA_PSS_RSAE_SHA384, + GNUTLS_SIGN_ECDSA_SHA384, + GNUTLS_SIGN_ECDSA_SECP384R1_SHA384, + + GNUTLS_SIGN_EDDSA_ED448, + + GNUTLS_SIGN_RSA_SHA512, + GNUTLS_SIGN_RSA_PSS_SHA512, + GNUTLS_SIGN_RSA_PSS_RSAE_SHA512, + + GNUTLS_SIGN_ECDSA_SHA512, + GNUTLS_SIGN_ECDSA_SECP521R1_SHA512, + + GNUTLS_SIGN_RSA_SHA1, + GNUTLS_SIGN_ECDSA_SHA1, + + 0 +}; +static const int* sign_priority_default = _sign_priority_default; + +static const int _sign_priority_suiteb128[] = { + GNUTLS_SIGN_ECDSA_SHA256, + GNUTLS_SIGN_ECDSA_SECP256R1_SHA256, + GNUTLS_SIGN_ECDSA_SHA384, + GNUTLS_SIGN_ECDSA_SECP384R1_SHA384, + 0 +}; +static const int* sign_priority_suiteb128 = _sign_priority_suiteb128; + +static const int _sign_priority_suiteb192[] = { + GNUTLS_SIGN_ECDSA_SHA384, + GNUTLS_SIGN_ECDSA_SECP384R1_SHA384, + 0 +}; +static const int* sign_priority_suiteb192 = _sign_priority_suiteb192; + +static const int _sign_priority_secure128[] = { + GNUTLS_SIGN_RSA_SHA256, + GNUTLS_SIGN_RSA_PSS_SHA256, + GNUTLS_SIGN_RSA_PSS_RSAE_SHA256, + GNUTLS_SIGN_ECDSA_SHA256, + GNUTLS_SIGN_ECDSA_SECP256R1_SHA256, + + GNUTLS_SIGN_EDDSA_ED25519, + + GNUTLS_SIGN_RSA_SHA384, + GNUTLS_SIGN_RSA_PSS_SHA384, + GNUTLS_SIGN_RSA_PSS_RSAE_SHA384, + GNUTLS_SIGN_ECDSA_SHA384, + GNUTLS_SIGN_ECDSA_SECP384R1_SHA384, + + GNUTLS_SIGN_EDDSA_ED448, + + GNUTLS_SIGN_RSA_SHA512, + GNUTLS_SIGN_RSA_PSS_SHA512, + GNUTLS_SIGN_RSA_PSS_RSAE_SHA512, + GNUTLS_SIGN_ECDSA_SHA512, + GNUTLS_SIGN_ECDSA_SECP521R1_SHA512, + + 0 +}; +static const int* sign_priority_secure128 = _sign_priority_secure128; + +static const int _sign_priority_secure192[] = { + GNUTLS_SIGN_RSA_SHA384, + GNUTLS_SIGN_RSA_PSS_SHA384, + GNUTLS_SIGN_RSA_PSS_RSAE_SHA384, + GNUTLS_SIGN_ECDSA_SHA384, + GNUTLS_SIGN_ECDSA_SECP384R1_SHA384, + GNUTLS_SIGN_EDDSA_ED448, + GNUTLS_SIGN_RSA_SHA512, + GNUTLS_SIGN_RSA_PSS_SHA512, + GNUTLS_SIGN_RSA_PSS_RSAE_SHA512, + GNUTLS_SIGN_ECDSA_SHA512, + GNUTLS_SIGN_ECDSA_SECP521R1_SHA512, + + 0 +}; +static const int* sign_priority_secure192 = _sign_priority_secure192; + +static const int _sign_priority_gost[] = { +#ifdef ENABLE_GOST + GNUTLS_SIGN_GOST_256, + GNUTLS_SIGN_GOST_512, +#endif + 0 +}; +static const int* sign_priority_gost = _sign_priority_gost; + +static const int mac_priority_normal_default[] = { + GNUTLS_MAC_SHA1, + GNUTLS_MAC_AEAD, + 0 +}; + +static const int mac_priority_normal_fips[] = { + GNUTLS_MAC_SHA1, + GNUTLS_MAC_AEAD, + 0 +}; + +static const int *cipher_priority_performance = _cipher_priority_performance_default; +static const int *cipher_priority_normal = _cipher_priority_normal_default; +static const int *mac_priority_normal = mac_priority_normal_default; + +static const int _cipher_priority_gost[] = { +#ifdef ENABLE_GOST + GNUTLS_CIPHER_GOST28147_TC26Z_CNT, +#endif + 0 +}; +static const int *cipher_priority_gost = _cipher_priority_gost; + +static const int _mac_priority_gost[] = { +#ifdef ENABLE_GOST + GNUTLS_MAC_GOST28147_TC26Z_IMIT, +#endif + 0 +}; +static const int *mac_priority_gost = _mac_priority_gost; + +/* if called with replace the default priorities with the FIPS140 ones */ +void _gnutls_priority_update_fips(void) +{ + cipher_priority_performance = cipher_priority_performance_fips; + cipher_priority_normal = cipher_priority_normal_fips; + mac_priority_normal = mac_priority_normal_fips; +} + +void _gnutls_priority_update_non_aesni(void) +{ + /* if we have no AES acceleration in performance mode + * prefer fast stream ciphers */ + if (_gnutls_fips_mode_enabled() == 0) { + cipher_priority_performance = _cipher_priority_performance_no_aesni; + } +} + +static const int _mac_priority_suiteb[] = { + GNUTLS_MAC_AEAD, + 0 +}; +static const int* mac_priority_suiteb = _mac_priority_suiteb; + +static const int _mac_priority_secure128[] = { + GNUTLS_MAC_SHA1, + GNUTLS_MAC_AEAD, + 0 +}; +static const int* mac_priority_secure128 = _mac_priority_secure128; + +static const int _mac_priority_secure192[] = { + GNUTLS_MAC_AEAD, + 0 +}; +static const int* mac_priority_secure192 = _mac_priority_secure192; + +static const int cert_type_priority_default[] = { + GNUTLS_CRT_X509, + 0 +}; + +static const int cert_type_priority_all[] = { + GNUTLS_CRT_X509, + GNUTLS_CRT_RAWPK, + 0 +}; + +typedef void (rmadd_func) (priority_st * priority_list, unsigned int alg); + +static void prio_remove(priority_st * priority_list, unsigned int algo) +{ + unsigned int i; + + for (i = 0; i < priority_list->num_priorities; i++) { + if (priority_list->priorities[i] == algo) { + priority_list->num_priorities--; + if ((priority_list->num_priorities - i) > 0) + memmove(&priority_list->priorities[i], + &priority_list->priorities[i + 1], + (priority_list->num_priorities - + i) * + sizeof(priority_list-> + priorities[0])); + priority_list->priorities[priority_list-> + num_priorities] = 0; + break; + } + } + + return; +} + +static void prio_add(priority_st * priority_list, unsigned int algo) +{ + unsigned int i, l = priority_list->num_priorities; + + if (l >= MAX_ALGOS) + return; /* can't add it anyway */ + + for (i = 0; i < l; ++i) { + if (algo == priority_list->priorities[i]) + return; /* if it exists */ + } + + priority_list->priorities[l] = algo; + priority_list->num_priorities++; + + return; +} + + +/** + * gnutls_priority_set: + * @session: is a #gnutls_session_t type. + * @priority: is a #gnutls_priority_t type. + * + * Sets the priorities to use on the ciphers, key exchange methods, + * and macs. Note that this function is expected to be called once + * per session; when called multiple times (e.g., before a re-handshake, + * the caller should make sure that any new settings are not incompatible + * with the original session). + * + * Returns: %GNUTLS_E_SUCCESS on success, or an error code on error. + **/ +int +gnutls_priority_set(gnutls_session_t session, gnutls_priority_t priority) +{ + int ret; + + if (priority == NULL || priority->protocol.num_priorities == 0 || + priority->cs.size == 0) + return gnutls_assert_val(GNUTLS_E_NO_PRIORITIES_WERE_SET); + + /* set the current version to the first in the chain, if this is + * the call before the initial handshake. During a re-handshake + * we do not set the version to avoid overriding the currently + * negotiated version. */ + if (!session->internals.handshake_in_progress && + !session->internals.initial_negotiation_completed) { + ret = _gnutls_set_current_version(session, + priority->protocol.priorities[0]); + if (ret < 0) + return gnutls_assert_val(ret); + } + + /* At this point the provided priorities passed the sanity tests */ + + if (session->internals.priorities) + gnutls_priority_deinit(session->internals.priorities); + + gnutls_atomic_increment(&priority->usage_cnt); + session->internals.priorities = priority; + + if (priority->no_tickets != 0) { + session->internals.flags |= GNUTLS_NO_TICKETS; + } + + if (priority->no_tickets_tls12 != 0) { + /* when PFS is explicitly requested, disable session tickets for TLS 1.2 */ + session->internals.flags |= GNUTLS_NO_TICKETS_TLS12; + } + + ADD_PROFILE_VFLAGS(session, priority->additional_verify_flags); + + /* mirror variables */ +#undef COPY_TO_INTERNALS +#define COPY_TO_INTERNALS(xx) session->internals.xx = priority->_##xx + COPY_TO_INTERNALS(allow_large_records); + COPY_TO_INTERNALS(allow_small_records); + COPY_TO_INTERNALS(no_etm); + COPY_TO_INTERNALS(no_ext_master_secret); + COPY_TO_INTERNALS(allow_key_usage_violation); + COPY_TO_INTERNALS(allow_wrong_pms); + COPY_TO_INTERNALS(dumbfw); + COPY_TO_INTERNALS(dh_prime_bits); + + return 0; +} + + +#define LEVEL_NONE "NONE" +#define LEVEL_NORMAL "NORMAL" +#define LEVEL_PFS "PFS" +#define LEVEL_PERFORMANCE "PERFORMANCE" +#define LEVEL_SECURE128 "SECURE128" +#define LEVEL_SECURE192 "SECURE192" +#define LEVEL_SECURE256 "SECURE256" +#define LEVEL_SUITEB128 "SUITEB128" +#define LEVEL_SUITEB192 "SUITEB192" +#define LEVEL_LEGACY "LEGACY" +#define LEVEL_SYSTEM "SYSTEM" + +struct priority_groups_st { + const char *name; + const char *alias; + const int **proto_list; + const int **cipher_list; + const int **mac_list; + const int **kx_list; + const int **sign_list; + const int **group_list; + unsigned profile; + int sec_param; + bool no_tickets; + bool no_tickets_tls12; +}; + +static const struct priority_groups_st pgroups[] = +{ + {.name = LEVEL_NORMAL, + .cipher_list = &cipher_priority_normal, + .mac_list = &mac_priority_normal, + .kx_list = &kx_priority_secure, + .sign_list = &sign_priority_default, + .group_list = &supported_groups_normal, + .profile = GNUTLS_PROFILE_LOW, + .sec_param = GNUTLS_SEC_PARAM_WEAK + }, + {.name = LEVEL_PFS, + .cipher_list = &cipher_priority_normal, + .mac_list = &mac_priority_secure128, + .kx_list = &kx_priority_pfs, + .sign_list = &sign_priority_default, + .group_list = &supported_groups_normal, + .profile = GNUTLS_PROFILE_LOW, + .sec_param = GNUTLS_SEC_PARAM_WEAK, + .no_tickets_tls12 = 1 + }, + {.name = LEVEL_SECURE128, + .alias = "SECURE", + .cipher_list = &cipher_priority_secure128, + .mac_list = &mac_priority_secure128, + .kx_list = &kx_priority_secure, + .sign_list = &sign_priority_secure128, + .group_list = &supported_groups_secure128, + /* The profile should have been HIGH but if we don't allow + * SHA-1 (80-bits) as signature algorithm we are not able + * to connect anywhere with this level */ + .profile = GNUTLS_PROFILE_LOW, + .sec_param = GNUTLS_SEC_PARAM_LOW + }, + {.name = LEVEL_SECURE192, + .alias = LEVEL_SECURE256, + .cipher_list = &cipher_priority_secure192, + .mac_list = &mac_priority_secure192, + .kx_list = &kx_priority_secure, + .sign_list = &sign_priority_secure192, + .group_list = &supported_groups_secure192, + .profile = GNUTLS_PROFILE_HIGH, + .sec_param = GNUTLS_SEC_PARAM_HIGH + }, + {.name = LEVEL_SUITEB128, + .proto_list = &protocol_priority_suiteb, + .cipher_list = &cipher_priority_suiteb128, + .mac_list = &mac_priority_suiteb, + .kx_list = &kx_priority_suiteb, + .sign_list = &sign_priority_suiteb128, + .group_list = &supported_groups_suiteb128, + .profile = GNUTLS_PROFILE_SUITEB128, + .sec_param = GNUTLS_SEC_PARAM_HIGH + }, + {.name = LEVEL_SUITEB192, + .proto_list = &protocol_priority_suiteb, + .cipher_list = &cipher_priority_suiteb192, + .mac_list = &mac_priority_suiteb, + .kx_list = &kx_priority_suiteb, + .sign_list = &sign_priority_suiteb192, + .group_list = &supported_groups_suiteb192, + .profile = GNUTLS_PROFILE_SUITEB192, + .sec_param = GNUTLS_SEC_PARAM_ULTRA + }, + {.name = LEVEL_LEGACY, + .cipher_list = &cipher_priority_normal, + .mac_list = &mac_priority_normal, + .kx_list = &kx_priority_secure, + .sign_list = &sign_priority_default, + .group_list = &supported_groups_normal, + .sec_param = GNUTLS_SEC_PARAM_VERY_WEAK + }, + {.name = LEVEL_PERFORMANCE, + .cipher_list = &cipher_priority_performance, + .mac_list = &mac_priority_normal, + .kx_list = &kx_priority_performance, + .sign_list = &sign_priority_default, + .group_list = &supported_groups_normal, + .profile = GNUTLS_PROFILE_LOW, + .sec_param = GNUTLS_SEC_PARAM_WEAK + }, + { + .name = NULL, + } +}; + +#define SET_PROFILE(to_set) \ + profile = GNUTLS_VFLAGS_TO_PROFILE(priority_cache->additional_verify_flags); \ + if (profile == 0 || profile > to_set) { \ + priority_cache->additional_verify_flags &= ~GNUTLS_VFLAGS_PROFILE_MASK; \ + priority_cache->additional_verify_flags |= GNUTLS_PROFILE_TO_VFLAGS(to_set); \ + } + +#define SET_LEVEL(to_set) \ + if (priority_cache->level == 0 || (unsigned)priority_cache->level > (unsigned)to_set) \ + priority_cache->level = to_set + +static +int check_level(const char *level, gnutls_priority_t priority_cache, + int add) +{ + bulk_rmadd_func *func; + unsigned profile = 0; + unsigned i; + int j; + const cipher_entry_st *centry; + + if (add) + func = _add_priority; + else + func = _set_priority; + + for (i=0;;i++) { + if (pgroups[i].name == NULL) + return 0; + + if (c_strcasecmp(level, pgroups[i].name) == 0 || + (pgroups[i].alias != NULL && c_strcasecmp(level, pgroups[i].alias) == 0)) { + if (pgroups[i].proto_list != NULL) + func(&priority_cache->protocol, *pgroups[i].proto_list); + func(&priority_cache->_cipher, *pgroups[i].cipher_list); + func(&priority_cache->_kx, *pgroups[i].kx_list); + func(&priority_cache->_mac, *pgroups[i].mac_list); + func(&priority_cache->_sign_algo, *pgroups[i].sign_list); + func(&priority_cache->_supported_ecc, *pgroups[i].group_list); + + if (pgroups[i].profile != 0) { + SET_PROFILE(pgroups[i].profile); /* set certificate level */ + } + SET_LEVEL(pgroups[i].sec_param); /* set DH params level */ + priority_cache->no_tickets = pgroups[i].no_tickets; + priority_cache->no_tickets_tls12 = pgroups[i].no_tickets_tls12; + if (priority_cache->have_cbc == 0) { + for (j=0;(*pgroups[i].cipher_list)[j]!=0;j++) { + centry = cipher_to_entry((*pgroups[i].cipher_list)[j]); + if (centry != NULL && centry->type == CIPHER_BLOCK) { + priority_cache->have_cbc = 1; + break; + } + } + } + return 1; + } + } +} + +static void enable_compat(gnutls_priority_t c) +{ + ENABLE_PRIO_COMPAT(c); +} +static void enable_server_key_usage_violations(gnutls_priority_t c) +{ + c->allow_server_key_usage_violation = 1; +} +static void enable_allow_small_records(gnutls_priority_t c) +{ + c->_allow_small_records = 1; +} +static void enable_dumbfw(gnutls_priority_t c) +{ + c->_dumbfw = 1; +} +static void enable_no_extensions(gnutls_priority_t c) +{ + c->no_extensions = 1; +} +static void enable_no_ext_master_secret(gnutls_priority_t c) +{ + c->_no_ext_master_secret = 1; +} +static void enable_no_etm(gnutls_priority_t c) +{ + c->_no_etm = 1; +} +static void enable_force_etm(gnutls_priority_t c) +{ + c->force_etm = 1; +} +static void enable_no_tickets(gnutls_priority_t c) +{ + c->no_tickets = 1; +} +static void enable_no_tickets_tls12(gnutls_priority_t c) +{ + c->no_tickets_tls12 = 1; +} +static void disable_wildcards(gnutls_priority_t c) +{ + c->additional_verify_flags |= GNUTLS_VERIFY_DO_NOT_ALLOW_WILDCARDS; +} +static void enable_profile_very_weak(gnutls_priority_t c) +{ + ENABLE_PROFILE(c, GNUTLS_PROFILE_VERY_WEAK); +} +static void enable_profile_low(gnutls_priority_t c) +{ + ENABLE_PROFILE(c, GNUTLS_PROFILE_LOW); +} +static void enable_profile_legacy(gnutls_priority_t c) +{ + ENABLE_PROFILE(c, GNUTLS_PROFILE_LEGACY); +} +static void enable_profile_medium(gnutls_priority_t c) +{ + ENABLE_PROFILE(c, GNUTLS_PROFILE_MEDIUM); +} +static void enable_profile_high(gnutls_priority_t c) +{ + ENABLE_PROFILE(c, GNUTLS_PROFILE_HIGH); +} +static void enable_profile_ultra(gnutls_priority_t c) +{ + ENABLE_PROFILE(c, GNUTLS_PROFILE_ULTRA); +} +static void enable_profile_future(gnutls_priority_t c) +{ + ENABLE_PROFILE(c, GNUTLS_PROFILE_FUTURE); +} +static void enable_profile_suiteb128(gnutls_priority_t c) +{ + ENABLE_PROFILE(c, GNUTLS_PROFILE_SUITEB128); +} +static void enable_profile_suiteb192(gnutls_priority_t c) +{ + ENABLE_PROFILE(c, GNUTLS_PROFILE_SUITEB128); +} +static void enable_safe_renegotiation(gnutls_priority_t c) +{ + c->sr = SR_SAFE; + +} +static void enable_unsafe_renegotiation(gnutls_priority_t c) +{ + c->sr = SR_UNSAFE; +} +static void enable_partial_safe_renegotiation(gnutls_priority_t c) +{ + c->sr = SR_PARTIAL; +} +static void disable_safe_renegotiation(gnutls_priority_t c) +{ + c->sr = SR_DISABLED; +} +static void enable_fallback_scsv(gnutls_priority_t c) +{ + c->fallback = 1; +} +static void enable_latest_record_version(gnutls_priority_t c) +{ + c->min_record_version = 0; +} +static void enable_ssl3_record_version(gnutls_priority_t c) +{ + c->min_record_version = 1; +} +static void enable_verify_allow_rsa_md5(gnutls_priority_t c) +{ + c->additional_verify_flags |= + GNUTLS_VERIFY_ALLOW_SIGN_RSA_MD5; +} +static void enable_verify_allow_sha1(gnutls_priority_t c) +{ + c->additional_verify_flags |= + GNUTLS_VERIFY_ALLOW_SIGN_WITH_SHA1; +} +static void enable_verify_allow_broken(gnutls_priority_t c) +{ + c->additional_verify_flags |= + GNUTLS_VERIFY_ALLOW_BROKEN; +} +static void disable_crl_checks(gnutls_priority_t c) +{ + c->additional_verify_flags |= + GNUTLS_VERIFY_DISABLE_CRL_CHECKS; +} +static void enable_server_precedence(gnutls_priority_t c) +{ + c->server_precedence = 1; +} +static void disable_tls13_compat_mode(gnutls_priority_t c) +{ + c->tls13_compat_mode = false; +} +static void dummy_func(gnutls_priority_t c) +{ +} + +#include <priority_options.h> + +struct cfg { + bool allowlisting; + bool ktls_enabled; + + name_val_array_t priority_strings; + char *priority_string; + char *default_priority_string; + gnutls_certificate_verification_profiles_t verification_profile; + + gnutls_cipher_algorithm_t ciphers[MAX_ALGOS+1]; + gnutls_mac_algorithm_t macs[MAX_ALGOS+1]; + gnutls_group_t groups[MAX_ALGOS+1]; + gnutls_kx_algorithm_t kxs[MAX_ALGOS+1]; + gnutls_sign_algorithm_t sigs[MAX_ALGOS+1]; + gnutls_protocol_t versions[MAX_ALGOS+1]; + + gnutls_digest_algorithm_t hashes[MAX_ALGOS+1]; + gnutls_ecc_curve_t ecc_curves[MAX_ALGOS+1]; + gnutls_sign_algorithm_t sigs_for_cert[MAX_ALGOS+1]; +}; + +static inline void +cfg_deinit(struct cfg *cfg) +{ + if (cfg->priority_strings) { + _name_val_array_clear(&cfg->priority_strings); + } + gnutls_free(cfg->priority_string); + gnutls_free(cfg->default_priority_string); +} + +/* Lock for reading and writing system_wide_config */ +GNUTLS_RWLOCK(system_wide_config_rwlock); +static struct cfg system_wide_config; + +static unsigned fail_on_invalid_config = 0; +static const char *system_priority_file = SYSTEM_PRIORITY_FILE; +static time_t system_priority_last_mod = 0; +static unsigned system_priority_file_loaded = 0; + +#define GLOBAL_SECTION "global" +#define CUSTOM_PRIORITY_SECTION "priorities" +#define OVERRIDES_SECTION "overrides" +#define MAX_ALGO_NAME 2048 + +bool _gnutls_allowlisting_mode(void) +{ + return system_wide_config.allowlisting; +} + +static void _clear_default_system_priority(void) +{ + gnutls_free(system_wide_config.default_priority_string); + system_wide_config.default_priority_string = NULL; + + _gnutls_default_priority_string = DEFAULT_PRIORITY_STRING; +} + +gnutls_certificate_verification_profiles_t _gnutls_get_system_wide_verification_profile(void) +{ + return system_wide_config.verification_profile; +} + +/* removes spaces */ +static char *clear_spaces(const char *str, char out[MAX_ALGO_NAME]) +{ + const char *p = str; + unsigned i = 0; + + while (c_isspace(*p)) + p++; + + while (!c_isspace(*p) && *p != 0) { + out[i++] = *p; + p++; + + if (i >= MAX_ALGO_NAME-1) + break; + } + out[i] = 0; + return out; +} + +struct ini_ctx { + struct cfg cfg; + + gnutls_digest_algorithm_t *hashes; + size_t hashes_size; + gnutls_sign_algorithm_t *sigs; + size_t sigs_size; + gnutls_sign_algorithm_t *sigs_for_cert; + size_t sigs_for_cert_size; + gnutls_protocol_t *versions; + size_t versions_size; + gnutls_ecc_curve_t *curves; + size_t curves_size; +}; + +static inline void +ini_ctx_deinit(struct ini_ctx *ctx) +{ + cfg_deinit(&ctx->cfg); + gnutls_free(ctx->hashes); + gnutls_free(ctx->sigs); + gnutls_free(ctx->sigs_for_cert); + gnutls_free(ctx->versions); + gnutls_free(ctx->curves); +} + +static inline void +cfg_steal(struct cfg *dst, struct cfg *src) +{ + dst->verification_profile = src->verification_profile; + + dst->priority_strings = src->priority_strings; + src->priority_strings = NULL; + + dst->priority_string = src->priority_string; + src->priority_string = NULL; + + dst->default_priority_string = src->default_priority_string; + src->default_priority_string = NULL; + + dst->allowlisting = src->allowlisting; + dst->ktls_enabled = src->ktls_enabled; + memcpy(dst->ciphers, src->ciphers, sizeof(src->ciphers)); + memcpy(dst->macs, src->macs, sizeof(src->macs)); + memcpy(dst->groups, src->groups, sizeof(src->groups)); + memcpy(dst->kxs, src->kxs, sizeof(src->kxs)); + memcpy(dst->hashes, src->hashes, sizeof(src->hashes)); + memcpy(dst->ecc_curves, src->ecc_curves, sizeof(src->ecc_curves)); + memcpy(dst->sigs, src->sigs, sizeof(src->sigs)); + memcpy(dst->sigs_for_cert, src->sigs_for_cert, + sizeof(src->sigs_for_cert)); +} + +/* + * synchronizing changes from struct cfg to global `lib/algorithms` arrays + */ + +/* global side-effect! modifies `flags` in `hash_algorithms[]` */ +static inline int /* allowlisting-only */ +_cfg_hashes_remark(struct cfg* cfg) +{ + size_t i; + _gnutls_digest_mark_insecure_all(); + for (i = 0; cfg->hashes[i] != 0; i++) { + int ret = _gnutls_digest_set_secure(cfg->hashes[i], 1); + if (unlikely(ret < 0)) { + return gnutls_assert_val(ret); + } + } + return 0; +} + +/* global side-effect! modifies `flags` in `sign_algorithms[]` */ +static inline int /* allowlisting-only */ +_cfg_sigs_remark(struct cfg* cfg) +{ + size_t i; + _gnutls_sign_mark_insecure_all(_INSECURE); + for (i = 0; cfg->sigs[i] != 0; i++) { + int ret = _gnutls_sign_set_secure(cfg->sigs[i], + _INSECURE_FOR_CERTS); + if (unlikely(ret < 0)) { + return gnutls_assert_val(ret); + } + } + for (i = 0; cfg->sigs_for_cert[i] != 0; i++) { + int ret = _gnutls_sign_set_secure(cfg->sigs_for_cert[i], + _SECURE); + if (unlikely(ret < 0)) { + return gnutls_assert_val(ret); + } + } + return 0; +} + +/* global side-effect! modifies `supported` in `sup_versions[]` */ +static inline int /* allowlisting-only */ +_cfg_versions_remark(struct cfg* cfg) +{ + size_t i; + _gnutls_version_mark_disabled_all(); + for (i = 0; cfg->versions[i] != 0; i++) { + int ret = _gnutls_protocol_set_enabled(cfg->versions[i], 1); + if (unlikely(ret < 0)) { + return gnutls_assert_val(ret); + } + } + return 0; +} + +/* global side-effect! modifies `supported` in `ecc_curves[]` */ +static inline int /* allowlisting-only */ +_cfg_ecc_curves_remark(struct cfg* cfg) +{ + size_t i; + _gnutls_ecc_curve_mark_disabled_all(); + for (i = 0; cfg->ecc_curves[i] != 0; i++) { + int ret = _gnutls_ecc_curve_set_enabled(cfg->ecc_curves[i], 1); + if (unlikely(ret < 0)) { + return gnutls_assert_val(ret); + } + } + return 0; +} + +/* + * setting arrays of struct cfg: from other arrays + */ + +static inline int /* allowlisting-only */ +cfg_hashes_set_array(struct cfg* cfg, + gnutls_digest_algorithm_t* src, size_t len) +{ + if (unlikely(len >= MAX_ALGOS)) { + return gnutls_assert_val(GNUTLS_A_INTERNAL_ERROR); + } + if (len) { + memcpy(cfg->hashes, + src, sizeof(gnutls_digest_algorithm_t) * len); + } + cfg->hashes[len] = 0; + return _cfg_hashes_remark(cfg); +} + +static inline int /* allowlisting-only */ +cfg_sigs_set_arrays(struct cfg* cfg, + gnutls_sign_algorithm_t* src, size_t len, + gnutls_sign_algorithm_t* src_for_cert, size_t len_for_cert) +{ + if (unlikely(len >= MAX_ALGOS)) { + return gnutls_assert_val(GNUTLS_A_INTERNAL_ERROR); + } + if (unlikely(len_for_cert >= MAX_ALGOS)) { + return gnutls_assert_val(GNUTLS_A_INTERNAL_ERROR); + } + if (len) { + memcpy(cfg->sigs, src, sizeof(gnutls_sign_algorithm_t) * len); + } + if (len_for_cert) { + memcpy(cfg->sigs_for_cert, src_for_cert, + sizeof(gnutls_sign_algorithm_t) * len_for_cert); + } + cfg->sigs[len] = 0; + cfg->sigs_for_cert[len_for_cert] = 0; + return _cfg_sigs_remark(cfg); +} + +static inline int /* allowlisting-only */ +cfg_versions_set_array(struct cfg* cfg, gnutls_protocol_t* src, size_t len) +{ + if (unlikely(len >= MAX_ALGOS)) { + return gnutls_assert_val(GNUTLS_A_INTERNAL_ERROR); + } + if (len) { + memcpy(cfg->versions, src, sizeof(gnutls_protocol_t) * len); + } + cfg->versions[len] = 0; + return _cfg_versions_remark(cfg); +} + +static inline int /* allowlisting-only */ +cfg_ecc_curves_set_array(struct cfg* cfg, gnutls_ecc_curve_t* src, size_t len) +{ + if (unlikely(len >= MAX_ALGOS)) { + return gnutls_assert_val(GNUTLS_A_INTERNAL_ERROR); + } + if (len) { + memcpy(cfg->ecc_curves, src, sizeof(gnutls_ecc_curve_t) * len); + } + cfg->ecc_curves[len] = 0; + return _cfg_ecc_curves_remark(cfg); +} + +/* + * appending to arrays of struct cfg + */ + +/* polymorphic way to DRY this operation. other possible approaches: + * 1. just unmacro (long) + * 2. cast to ints and write a function operating on ints + * (hacky, every call is +4 lines, needs a portable static assert) + * 3. macro whole functions, not just this operation (harder to find/read) + */ +#define APPEND_TO_NULL_TERMINATED_ARRAY(dst, element) \ + do { \ + size_t i; \ + for (i = 0; dst[i] != 0; i++) { \ + if (dst[i] == element) { \ + return 0; \ + } \ + } \ + if (unlikely(i >= MAX_ALGOS)) { \ + return gnutls_assert_val(GNUTLS_A_INTERNAL_ERROR); \ + } \ + dst[i] = element; \ + dst[i + 1] = 0; \ + } while (0) + +static inline int /* allowlisting-only */ +cfg_hashes_add(struct cfg *cfg, gnutls_digest_algorithm_t dig) +{ + _gnutls_debug_log("cfg: enabling digest algorithm %s\n", + gnutls_digest_get_name(dig)); + APPEND_TO_NULL_TERMINATED_ARRAY(cfg->hashes, dig); + return _cfg_hashes_remark(cfg); +} + +static inline int /* allowlisting-only */ +cfg_sigs_add(struct cfg *cfg, gnutls_sign_algorithm_t sig) +{ + _gnutls_debug_log("cfg: enabling signature algorithm " + "(for non-certificate usage) " + "%s\n", gnutls_sign_get_name(sig)); + APPEND_TO_NULL_TERMINATED_ARRAY(cfg->sigs, sig); + return _cfg_sigs_remark(cfg); +} + +static inline int /* allowlisting-only */ +cfg_sigs_for_cert_add(struct cfg *cfg, gnutls_sign_algorithm_t sig) +{ + _gnutls_debug_log("cfg: enabling signature algorithm" + "(for certificate usage) " + "%s\n", gnutls_sign_get_name(sig)); + APPEND_TO_NULL_TERMINATED_ARRAY(cfg->sigs_for_cert, sig); + return _cfg_sigs_remark(cfg); +} + +static inline int /* allowlisting-only */ +cfg_versions_add(struct cfg *cfg, gnutls_protocol_t prot) +{ + _gnutls_debug_log("cfg: enabling version %s\n", + gnutls_protocol_get_name(prot)); + APPEND_TO_NULL_TERMINATED_ARRAY(cfg->versions, prot); + return _cfg_versions_remark(cfg); +} + +static inline int /* allowlisting-only */ +cfg_ecc_curves_add(struct cfg *cfg, gnutls_ecc_curve_t curve) +{ + _gnutls_debug_log("cfg: enabling curve %s\n", + gnutls_ecc_curve_get_name(curve)); + APPEND_TO_NULL_TERMINATED_ARRAY(cfg->ecc_curves, curve); + return _cfg_ecc_curves_remark(cfg); +} + +#undef APPEND_TO_NULL_TERMINATED_ARRAY + +/* + * removing from arrays of struct cfg + */ + +/* polymorphic way to DRY this removal, see APPEND_TO_NULL_TERMINATED_ARRAY */ +#define REMOVE_FROM_NULL_TERMINATED_ARRAY(dst, element) \ + do { \ + size_t i, j; \ + for (i = 0; dst[i] != 0; i++) { \ + if (dst[i] == element) { \ + for (j = i; dst[j] != 0; j++) { \ + dst[j] = dst[j + 1]; \ + } \ + } \ + } \ + } while (0) + +static inline int /* allowlisting-only */ +cfg_hashes_remove(struct cfg *cfg, gnutls_digest_algorithm_t dig) +{ + _gnutls_debug_log("cfg: disabling digest algorithm %s\n", + gnutls_digest_get_name(dig)); + REMOVE_FROM_NULL_TERMINATED_ARRAY(cfg->hashes, dig); + return _cfg_hashes_remark(cfg); +} + +static inline int /* allowlisting-only */ +cfg_sigs_remove(struct cfg *cfg, gnutls_sign_algorithm_t sig) +{ + _gnutls_debug_log("cfg: disabling signature algorithm " + "(for non-certificate usage) " + "%s\n", gnutls_sign_get_name(sig)); + REMOVE_FROM_NULL_TERMINATED_ARRAY(cfg->sigs, sig); + return _cfg_sigs_remark(cfg); +} + +static inline int /* allowlisting-only */ +cfg_sigs_for_cert_remove(struct cfg *cfg, gnutls_sign_algorithm_t sig) +{ + _gnutls_debug_log("cfg: disabling signature algorithm" + "(for certificate usage) " + "%s\n", gnutls_sign_get_name(sig)); + REMOVE_FROM_NULL_TERMINATED_ARRAY(cfg->sigs_for_cert, sig); + return _cfg_sigs_remark(cfg); +} + +static inline int /* allowlisting-only */ +cfg_versions_remove(struct cfg *cfg, gnutls_protocol_t prot) +{ + _gnutls_debug_log("cfg: disabling version %s\n", + gnutls_protocol_get_name(prot)); + REMOVE_FROM_NULL_TERMINATED_ARRAY(cfg->versions, prot); + return _cfg_versions_remark(cfg); +} + +static inline int /* allowlisting-only */ +cfg_ecc_curves_remove(struct cfg *cfg, gnutls_ecc_curve_t curve) +{ + _gnutls_debug_log("cfg: disabling curve %s\n", + gnutls_ecc_curve_get_name(curve)); + REMOVE_FROM_NULL_TERMINATED_ARRAY(cfg->ecc_curves, curve); + return _cfg_ecc_curves_remark(cfg); +} + +static inline int +cfg_apply(struct cfg *cfg, struct ini_ctx *ctx) +{ + size_t i; + int ret; + + cfg_steal(cfg, &ctx->cfg); + + if (cfg->default_priority_string) { + _gnutls_default_priority_string = cfg->default_priority_string; + } + + if (cfg->allowlisting) { + /* also updates `flags` of global `hash_algorithms[]` */ + ret = cfg_hashes_set_array(cfg, ctx->hashes, ctx->hashes_size); + if (unlikely(ret < 0)) { + return gnutls_assert_val(ret); + } + /* also updates `flags` of global `sign_algorithms[]` */ + ret = cfg_sigs_set_arrays(cfg, ctx->sigs, ctx->sigs_size, + ctx->sigs_for_cert, + ctx->sigs_for_cert_size); + if (unlikely(ret < 0)) { + return gnutls_assert_val(ret); + } + /* also updates `supported` field of global `sup_versions[]` */ + ret = cfg_versions_set_array(cfg, + ctx->versions, ctx->versions_size); + if (unlikely(ret < 0)) { + return gnutls_assert_val(ret); + } + /* also updates `supported` field of global `ecc_curves[]` */ + ret = cfg_ecc_curves_set_array(cfg, + ctx->curves, ctx->curves_size); + if (unlikely(ret < 0)) { + return gnutls_assert_val(ret); + } + } else { + /* updates same global arrays as above, but doesn't store + * the algorithms into the `struct cfg` as allowlisting does. + * blocklisting doesn't allow relaxing the restrictions */ + for (i = 0; i < ctx->hashes_size; i++) { + ret = _gnutls_digest_mark_insecure(ctx->hashes[i]); + if (unlikely(ret < 0)) { + return ret; + } + } + for (i = 0; i < ctx->sigs_size; i++) { + ret = _gnutls_sign_mark_insecure(ctx->sigs[i], + _INSECURE); + if (unlikely(ret < 0)) { + return ret; + } + } + for (i = 0; i < ctx->sigs_for_cert_size; i++) { + ret = _gnutls_sign_mark_insecure(ctx->sigs_for_cert[i], _INSECURE_FOR_CERTS); + if (unlikely(ret < 0)) { + return ret; + } + } + for (i = 0; i < ctx->versions_size; i++) { + ret = _gnutls_version_mark_disabled(ctx->versions[i]); + if (unlikely(ret < 0)) { + return ret; + } + } + for (i = 0; i < ctx->curves_size; i++) { + ret = _gnutls_ecc_curve_mark_disabled(ctx->curves[i]); + if (unlikely(ret < 0)) { + return ret; + } + } + } + + return 0; +} + +/* This function parses the global section of the configuration file. + */ +static int global_ini_handler(void *ctx, const char *section, const char *name, const char *value) +{ + char *p; + char str[MAX_ALGO_NAME]; + struct cfg *cfg = ctx; + + if (section != NULL && c_strcasecmp(section, GLOBAL_SECTION) == 0) { + if (c_strcasecmp(name, "override-mode") == 0) { + p = clear_spaces(value, str); + if (c_strcasecmp(p, "allowlist") == 0) { + cfg->allowlisting = true; + } else if (c_strcasecmp(p, "blocklist") == 0) { + cfg->allowlisting = false; + } else { + _gnutls_debug_log("cfg: unknown override mode %s\n", + p); + if (fail_on_invalid_config) + return 0; + } + } else if (c_strcasecmp(name, "ktls") == 0) { + p = clear_spaces(value, str); + if (c_strcasecmp(p, "true") == 0) { + cfg->ktls_enabled = true; + } else { + _gnutls_debug_log("cfg: unknown ktls mode %s\n", + p); + if (fail_on_invalid_config) + return 0; + } + } else { + _gnutls_debug_log("unknown parameter %s\n", name); + if (fail_on_invalid_config) + return 0; + } + } + + return 1; +} + +static bool +override_allowed(bool allowlisting, const char *name) +{ + static const struct { + const char *allowlist_name; + const char *blocklist_name; + } names[] = { + { "secure-hash", "insecure-hash" }, + { "secure-sig", "insecure-sig" }, + { "secure-sig-for-cert", "insecure-sig-for-cert" }, + { "enabled-version", "disabled-version" }, + { "enabled-curve", "disabled-curve" }, + { "tls-enabled-cipher", "tls-disabled-cipher" }, + { "tls-enabled-group", "tls-disabled-group" }, + { "tls-enabled-kx", "tls-disabled-kx" }, + { "tls-enabled-mac", "tls-disabled-mac" } + }; + size_t i; + + for (i = 0; i < sizeof(names) / sizeof(names[0]); i++) { + if (c_strcasecmp(name, + allowlisting ? + names[i].blocklist_name : + names[i].allowlist_name) == 0) + return false; + } + + return true; +} + +/* This function parses a gnutls configuration file. Updating internal settings + * according to the parsed configuration is done by cfg_apply. + */ +static int cfg_ini_handler(void *_ctx, const char *section, const char *name, const char *value) +{ + char *p; + int ret; + unsigned i; + char str[MAX_ALGO_NAME]; + struct ini_ctx *ctx = _ctx; + struct cfg *cfg = &ctx->cfg; + + /* Note that we intentionally overwrite the value above; inih does + * not use that value after we handle it. */ + + /* Parse sections */ + if (section == NULL || section[0] == 0 || c_strcasecmp(section, CUSTOM_PRIORITY_SECTION)==0) { + _gnutls_debug_log("cfg: adding priority: %s -> %s\n", name, value); + + ret = _name_val_array_append(&cfg->priority_strings, name, value); + if (ret < 0) + return 0; + } else if (c_strcasecmp(section, OVERRIDES_SECTION)==0) { + if (!override_allowed(cfg->allowlisting, name)) { + _gnutls_debug_log("cfg: %s is not allowed in this mode\n", + name); + if (fail_on_invalid_config) + return 0; + } else if (c_strcasecmp(name, "default-priority-string")==0) { + if (cfg->default_priority_string) { + gnutls_free(cfg->default_priority_string); + cfg->default_priority_string = NULL; + } + p = clear_spaces(value, str); + _gnutls_debug_log("cfg: setting default-priority-string to %s\n", p); + if (strlen(p) > 0) { + cfg->default_priority_string = gnutls_strdup(p); + if (!cfg->default_priority_string) { + _gnutls_debug_log("cfg: failed setting default-priority-string\n"); + return 0; + } + } else { + _gnutls_debug_log("cfg: empty default-priority-string, using default\n"); + if (fail_on_invalid_config) + return 0; + } + } else if (c_strcasecmp(name, "insecure-hash") == 0 || + c_strcasecmp(name, "secure-hash") == 0) { + gnutls_digest_algorithm_t dig, *tmp; + + p = clear_spaces(value, str); + + if (cfg->allowlisting) { + _gnutls_debug_log("cfg: marking hash %s as secure\n", + p); + } else { + _gnutls_debug_log("cfg: marking hash %s as insecure\n", + p); + } + + dig = gnutls_digest_get_id(p); + if (dig == GNUTLS_DIG_UNKNOWN) { + _gnutls_debug_log("cfg: found unknown hash %s in %s\n", + p, name); + if (fail_on_invalid_config) + return 0; + goto exit; + } + tmp = _gnutls_reallocarray(ctx->hashes, + ctx->hashes_size + 1, + sizeof(gnutls_digest_algorithm_t)); + if (!tmp) { + if (cfg->allowlisting) { + _gnutls_debug_log("cfg: failed marking hash %s as secure\n", + p); + } else { + _gnutls_debug_log("cfg: failed marking hash %s as insecure\n", + p); + } + if (fail_on_invalid_config) + return 0; + goto exit; + } + + ctx->hashes = tmp; + ctx->hashes[ctx->hashes_size] = dig; + ctx->hashes_size++; + } else if (c_strcasecmp(name, "insecure-sig") == 0 || + c_strcasecmp(name, "secure-sig") == 0) { + gnutls_sign_algorithm_t sig, *tmp; + + p = clear_spaces(value, str); + + if (cfg->allowlisting) { + _gnutls_debug_log("cfg: marking signature %s as secure\n", + p); + } else { + _gnutls_debug_log("cfg: marking signature %s as insecure\n", + p); + } + + sig = gnutls_sign_get_id(p); + if (sig == GNUTLS_SIGN_UNKNOWN) { + _gnutls_debug_log("cfg: found unknown signature algorithm %s in %s\n", + p, name); + if (fail_on_invalid_config) + return 0; + goto exit; + } + tmp = _gnutls_reallocarray(ctx->sigs, + ctx->sigs_size + 1, + sizeof(gnutls_sign_algorithm_t)); + if (!tmp) { + if (cfg->allowlisting) { + _gnutls_debug_log("cfg: failed marking signature %s as secure\n", + p); + } else { + _gnutls_debug_log("cfg: failed marking signature %s as insecure\n", + p); + } + if (fail_on_invalid_config) + return 0; + goto exit; + } + + ctx->sigs = tmp; + ctx->sigs[ctx->sigs_size] = sig; + ctx->sigs_size++; + } else if (c_strcasecmp(name, "insecure-sig-for-cert") == 0 || + c_strcasecmp(name, "secure-sig-for-cert") == 0) { + gnutls_sign_algorithm_t sig, *tmp; + + p = clear_spaces(value, str); + + if (cfg->allowlisting) { + _gnutls_debug_log("cfg: marking signature %s as secure for certs\n", + p); + } else { + _gnutls_debug_log("cfg: marking signature %s as insecure for certs\n", + p); + } + + sig = gnutls_sign_get_id(p); + if (sig == GNUTLS_SIGN_UNKNOWN) { + _gnutls_debug_log("cfg: found unknown signature algorithm %s in %s\n", + p, name); + if (fail_on_invalid_config) + return 0; + goto exit; + } + tmp = _gnutls_reallocarray(ctx->sigs_for_cert, + ctx->sigs_for_cert_size + 1, + sizeof(gnutls_sign_algorithm_t)); + if (!tmp) { + if (cfg->allowlisting) { + _gnutls_debug_log("cfg: failed marking signature %s as secure for certs\n", + p); + } else { + _gnutls_debug_log("cfg: failed marking signature %s as insecure for certs\n", + p); + } + if (fail_on_invalid_config) + return 0; + goto exit; + } + + ctx->sigs_for_cert = tmp; + ctx->sigs_for_cert[ctx->sigs_for_cert_size] = sig; + ctx->sigs_for_cert_size++; + } else if (c_strcasecmp(name, "disabled-version") == 0 || + c_strcasecmp(name, "enabled-version") == 0) { + gnutls_protocol_t prot, *tmp; + + p = clear_spaces(value, str); + + if (cfg->allowlisting) { + _gnutls_debug_log("cfg: enabling version %s\n", + p); + } else { + _gnutls_debug_log("cfg: disabling version %s\n", + p); + } + + prot = gnutls_protocol_get_id(p); + if (prot == GNUTLS_VERSION_UNKNOWN) { + _gnutls_debug_log("cfg: found unknown version %s in %s\n", + p, name); + if (fail_on_invalid_config) + return 0; + goto exit; + } + tmp = _gnutls_reallocarray(ctx->versions, + ctx->versions_size + 1, + sizeof(gnutls_protocol_t)); + if (!tmp) { + if (cfg->allowlisting) { + _gnutls_debug_log("cfg: failed enabling version %s\n", + p); + } else { + _gnutls_debug_log("cfg: failed disabling version %s\n", + p); + } + if (fail_on_invalid_config) + return 0; + goto exit; + } + + ctx->versions = tmp; + ctx->versions[ctx->versions_size] = prot; + ctx->versions_size++; + } else if (c_strcasecmp(name, "disabled-curve") == 0 || + c_strcasecmp(name, "enabled-curve") == 0) { + gnutls_ecc_curve_t curve, *tmp; + + p = clear_spaces(value, str); + + if (cfg->allowlisting) { + _gnutls_debug_log("cfg: enabling curve %s\n", + p); + } else { + _gnutls_debug_log("cfg: disabling curve %s\n", + p); + } + + curve = gnutls_ecc_curve_get_id(p); + if (curve == GNUTLS_ECC_CURVE_INVALID) { + _gnutls_debug_log("cfg: found unknown curve %s in %s\n", + p, name); + if (fail_on_invalid_config) + return 0; + goto exit; + } + tmp = _gnutls_reallocarray(ctx->curves, + ctx->curves_size + 1, + sizeof(gnutls_ecc_curve_t)); + if (!tmp) { + if (cfg->allowlisting) { + _gnutls_debug_log("cfg: failed enabling curve %s\n", + p); + } else { + _gnutls_debug_log("cfg: failed disabling curve %s\n", + p); + } + if (fail_on_invalid_config) + return 0; + goto exit; + } + + ctx->curves = tmp; + ctx->curves[ctx->curves_size] = curve; + ctx->curves_size++; + } else if (c_strcasecmp(name, "min-verification-profile")==0) { + gnutls_certificate_verification_profiles_t profile; + profile = gnutls_certificate_verification_profile_get_id(value); + + if (profile == GNUTLS_PROFILE_UNKNOWN) { + _gnutls_debug_log("cfg: found unknown profile %s in %s\n", + value, name); + if (fail_on_invalid_config) + return 0; + goto exit; + } + + cfg->verification_profile = profile; + } else if (c_strcasecmp(name, "tls-disabled-cipher") == 0 || + c_strcasecmp(name, "tls-enabled-cipher") == 0) { + gnutls_cipher_algorithm_t algo; + + p = clear_spaces(value, str); + + if (cfg->allowlisting) { + _gnutls_debug_log("cfg: enabling cipher %s for TLS\n", + p); + } else { + _gnutls_debug_log("cfg: disabling cipher %s for TLS\n", + p); + } + + algo = gnutls_cipher_get_id(p); + if (algo == GNUTLS_CIPHER_UNKNOWN) { + _gnutls_debug_log("cfg: unknown algorithm %s listed at %s\n", + p, name); + if (fail_on_invalid_config) + return 0; + goto exit; + } + + i = 0; + while (cfg->ciphers[i] != 0) + i++; + + if (i > MAX_ALGOS-1) { + if (cfg->allowlisting) { + _gnutls_debug_log("cfg: too many (%d) enabled ciphers from %s\n", + i, name); + } else { + _gnutls_debug_log("cfg: too many (%d) disabled ciphers from %s\n", + i, name); + } + if (fail_on_invalid_config) + return 0; + goto exit; + } + cfg->ciphers[i] = algo; + cfg->ciphers[i+1] = 0; + + } else if (c_strcasecmp(name, "tls-disabled-mac") == 0 || + c_strcasecmp(name, "tls-enabled-mac") == 0) { + gnutls_mac_algorithm_t algo; + + p = clear_spaces(value, str); + + if (cfg->allowlisting) { + _gnutls_debug_log("cfg: enabling MAC %s for TLS\n", + p); + } else { + _gnutls_debug_log("cfg: disabling MAC %s for TLS\n", + p); + } + + algo = gnutls_mac_get_id(p); + if (algo == 0) { + _gnutls_debug_log("cfg: unknown algorithm %s listed at %s\n", + p, name); + if (fail_on_invalid_config) + return 0; + goto exit; + } + + i = 0; + while (cfg->macs[i] != 0) + i++; + + if (i > MAX_ALGOS-1) { + if (cfg->allowlisting) { + _gnutls_debug_log("cfg: too many (%d) enabled MACs from %s\n", + i, name); + } else { + _gnutls_debug_log("cfg: too many (%d) disabled MACs from %s\n", + i, name); + } + if (fail_on_invalid_config) + return 0; + goto exit; + } + cfg->macs[i] = algo; + cfg->macs[i+1] = 0; + } else if (c_strcasecmp(name, "tls-disabled-group") == 0 || + c_strcasecmp(name, "tls-enabled-group") == 0) { + gnutls_group_t algo; + + p = clear_spaces(value, str); + + if (c_strncasecmp(p, "GROUP-", 6) == 0) + p += 6; + + if (cfg->allowlisting) { + _gnutls_debug_log("cfg: enabling group %s for TLS\n", + p); + } else { + _gnutls_debug_log("cfg: disabling group %s for TLS\n", + p); + } + + algo = _gnutls_group_get_id(p); + if (algo == 0) { + _gnutls_debug_log("cfg: unknown group %s listed at %s\n", + p, name); + if (fail_on_invalid_config) + return 0; + goto exit; + } + + i = 0; + while (cfg->groups[i] != 0) + i++; + + if (i > MAX_ALGOS-1) { + if (cfg->allowlisting) { + _gnutls_debug_log("cfg: too many (%d) enabled groups from %s\n", + i, name); + } else { + _gnutls_debug_log("cfg: too many (%d) disabled groups from %s\n", + i, name); + } + if (fail_on_invalid_config) + return 0; + goto exit; + } + cfg->groups[i] = algo; + cfg->groups[i+1] = 0; + } else if (c_strcasecmp(name, "tls-disabled-kx") == 0 || + c_strcasecmp(name, "tls-enabled-kx") == 0) { + unsigned algo; + + p = clear_spaces(value, str); + + if (cfg->allowlisting) { + _gnutls_debug_log("cfg: enabling key exchange %s for TLS\n", + p); + } else { + _gnutls_debug_log("cfg: disabling key exchange %s for TLS\n", + p); + } + + algo = gnutls_kx_get_id(p); + if (algo == 0) { + _gnutls_debug_log("cfg: unknown key exchange %s listed at %s\n", + p, name); + if (fail_on_invalid_config) + return 0; + goto exit; + } + + i = 0; + while (cfg->kxs[i] != 0) + i++; + + if (i > MAX_ALGOS-1) { + if (cfg->allowlisting) { + _gnutls_debug_log("cfg: too many (%d) enabled key exchanges from %s\n", + i, name); + } else { + _gnutls_debug_log("cfg: too many (%d) disabled key exchanges from %s\n", + i, name); + } + if (fail_on_invalid_config) + return 0; + goto exit; + } + cfg->kxs[i] = algo; + cfg->kxs[i+1] = 0; + } else { + _gnutls_debug_log("unknown parameter %s\n", name); + if (fail_on_invalid_config) + return 0; + } + } else if (c_strcasecmp(section, GLOBAL_SECTION) != 0) { + _gnutls_debug_log("cfg: unknown section %s\n", + section); + if (fail_on_invalid_config) + return 0; + } + + exit: + return 1; +} + +static int /* not locking system_wide_config */ +construct_system_wide_priority_string(gnutls_buffer_st* buf) +{ + int ret; + size_t i; + + _gnutls_buffer_init(buf); + + ret = _gnutls_buffer_append_str(buf, "NONE"); + if (ret < 0) { + _gnutls_buffer_clear(buf); + return ret; + } + + for (i = 0; system_wide_config.kxs[i] != 0; i++) { + ret = _gnutls_buffer_append_str(buf, ":+"); + if (ret < 0) { + _gnutls_buffer_clear(buf); + return ret; + } + + ret = _gnutls_buffer_append_str(buf, + gnutls_kx_get_name(system_wide_config.kxs[i])); + if (ret < 0) { + _gnutls_buffer_clear(buf); + return ret; + } + } + + for (i = 0; system_wide_config.groups[i] != 0; i++) { + ret = _gnutls_buffer_append_str(buf, ":+GROUP-"); + if (ret < 0) { + _gnutls_buffer_clear(buf); + return ret; + } + + ret = _gnutls_buffer_append_str(buf, + gnutls_group_get_name(system_wide_config.groups[i])); + if (ret < 0) { + _gnutls_buffer_clear(buf); + return ret; + } + } + + for (i = 0; system_wide_config.ciphers[i] != 0; i++) { + ret = _gnutls_buffer_append_str(buf, ":+"); + if (ret < 0) { + _gnutls_buffer_clear(buf); + return ret; + } + + ret = _gnutls_buffer_append_str(buf, + gnutls_cipher_get_name(system_wide_config.ciphers[i])); + if (ret < 0) { + _gnutls_buffer_clear(buf); + return ret; + } + } + + for (i = 0; system_wide_config.macs[i] != 0; i++) { + ret = _gnutls_buffer_append_str(buf, ":+"); + if (ret < 0) { + _gnutls_buffer_clear(buf); + return ret; + } + + ret = _gnutls_buffer_append_str(buf, + gnutls_mac_get_name(system_wide_config.macs[i])); + if (ret < 0) { + _gnutls_buffer_clear(buf); + return ret; + } + } + + for (i = 0; system_wide_config.sigs[i] != 0; i++) { + ret = _gnutls_buffer_append_str(buf, ":+SIGN-"); + if (ret < 0) { + _gnutls_buffer_clear(buf); + return ret; + } + + ret = _gnutls_buffer_append_str(buf, + gnutls_sign_get_name(system_wide_config.sigs[i])); + if (ret < 0) { + _gnutls_buffer_clear(buf); + return ret; + } + } + + for (i = 0; system_wide_config.versions[i] != 0; i++) { + ret = _gnutls_buffer_append_str(buf, ":+VERS-"); + if (ret < 0) { + _gnutls_buffer_clear(buf); + return ret; + } + + ret = _gnutls_buffer_append_str(buf, + gnutls_protocol_get_name(system_wide_config.versions[i])); + if (ret < 0) { + _gnutls_buffer_clear(buf); + return ret; + } + } + return 0; +} + +static int /* not locking system_wide_config */ +update_system_wide_priority_string(void) +{ + /* doesn't do locking, _gnutls_update_system_priorities does */ + gnutls_buffer_st buf; + int ret; + + ret = construct_system_wide_priority_string(&buf); + if (ret < 0) { + _gnutls_debug_log("cfg: unable to construct " + "system-wide priority string: %s", + gnutls_strerror(ret)); + _gnutls_buffer_clear(&buf); + return ret; + } + + gnutls_free(system_wide_config.priority_string); + system_wide_config.priority_string = gnutls_strdup((char *)buf.data); + _gnutls_buffer_clear(&buf); + + return 0; +} + +static int _gnutls_update_system_priorities(bool defer_system_wide) +{ + int ret, err = 0; + struct stat sb; + FILE *fp; + gnutls_buffer_st buf; + struct ini_ctx ctx; + + ret = gnutls_rwlock_rdlock(&system_wide_config_rwlock); + if (ret < 0) { + return gnutls_assert_val(ret); + } + + if (stat(system_priority_file, &sb) < 0) { + _gnutls_debug_log("cfg: unable to access: %s: %d\n", + system_priority_file, errno); + goto out; + } + + if (system_priority_file_loaded && + system_priority_last_mod == sb.st_mtime) { + _gnutls_debug_log("cfg: system priority %s has not changed\n", + system_priority_file); + if (system_wide_config.priority_string) { + goto out; /* nothing to do */ + } + } + + (void)gnutls_rwlock_unlock(&system_wide_config_rwlock); + + ret = gnutls_rwlock_wrlock(&system_wide_config_rwlock); + if (ret < 0) { + return gnutls_assert_val(ret); + } + + /* Another thread could have successfully re-read system-wide config, + * skip re-reading if the mtime it has used is exactly the same. + */ + if (system_priority_file_loaded) { + system_priority_file_loaded = + (system_priority_last_mod == sb.st_mtime); + } + + if (!system_priority_file_loaded) { + _name_val_array_clear(&system_wide_config.priority_strings); + + gnutls_free(system_wide_config.priority_string); + system_wide_config.priority_string = NULL; + + fp = fopen(system_priority_file, "re"); + if (fp == NULL) { + _gnutls_debug_log("cfg: unable to open: %s: %d\n", + system_priority_file, errno); + goto out; + } + /* Parsing the configuration file needs to be done in 2 phases: + * first parsing the [global] section + * and then the other sections, + * because the [global] section modifies the parsing behavior. + */ + memset(&ctx, 0, sizeof(ctx)); + err = ini_parse_file(fp, global_ini_handler, &ctx); + if (!err) { + if (fseek(fp, 0L, SEEK_SET) < 0) { + _gnutls_debug_log("cfg: unable to rewind: %s\n", + system_priority_file); + if (fail_on_invalid_config) + exit(1); + } + err = ini_parse_file(fp, cfg_ini_handler, &ctx); + } + fclose(fp); + if (err) { + ini_ctx_deinit(&ctx); + _gnutls_debug_log("cfg: unable to parse: %s: %d\n", + system_priority_file, err); + goto out; + } + cfg_apply(&system_wide_config, &ctx); + ini_ctx_deinit(&ctx); + _gnutls_debug_log("cfg: loaded system config %s mtime %lld\n", + system_priority_file, + (unsigned long long)sb.st_mtime); + + } + + if (system_wide_config.allowlisting) { + if (defer_system_wide) { + /* try constructing a priority string, + * but don't apply it yet, at this point + * we're only interested in whether we can */ + ret = construct_system_wide_priority_string(&buf); + _gnutls_buffer_clear(&buf); + _gnutls_debug_log("cfg: deferred setting " + "system-wide priority string\n"); + } else { + ret = update_system_wide_priority_string(); + _gnutls_debug_log("cfg: finalized " + "system-wide priority string\n"); + } + if (ret < 0) { + _gnutls_debug_log("cfg: unable to build priority string: %s\n", + gnutls_strerror(ret)); + if (fail_on_invalid_config) + exit(1); + goto out; + } + } + + system_priority_file_loaded = 1; + system_priority_last_mod = sb.st_mtime; + + out: + (void)gnutls_rwlock_unlock(&system_wide_config_rwlock); + + if (err && fail_on_invalid_config) { + exit(1); + } + + return ret; +} + +void _gnutls_prepare_to_load_system_priorities(void) +{ + const char *p; + int ret; + + p = secure_getenv("GNUTLS_SYSTEM_PRIORITY_FILE"); + if (p != NULL) + system_priority_file = p; + + p = secure_getenv("GNUTLS_SYSTEM_PRIORITY_FAIL_ON_INVALID"); + if (p != NULL && p[0] == '1' && p[1] == 0) + fail_on_invalid_config = 1; + + ret = _gnutls_update_system_priorities(true /* defer_system_wide */); + if (ret < 0) { + _gnutls_debug_log("failed to update system priorities: %s\n", + gnutls_strerror(ret)); + } +} + +void _gnutls_unload_system_priorities(void) +{ + _name_val_array_clear(&system_wide_config.priority_strings); + gnutls_free(system_wide_config.priority_string); + _clear_default_system_priority(); + system_priority_last_mod = 0; +} + +/** + * gnutls_get_system_config_file: + * + * Returns the filename of the system wide configuration + * file to be loaded by the library. + * + * Returns: a constant pointer to the config file path + * + * Since: 3.6.9 + **/ +const char *gnutls_get_system_config_file(void) +{ + return system_priority_file; +} + +#define S(str) ((str!=NULL)?str:"") + +/* Returns the new priorities if a priority string prefixed + * with '@' is provided, or just a copy of the provided + * priorities, appended with any additional present in + * the priorities string. + * + * The returned string must be released using gnutls_free(). + */ +char *_gnutls_resolve_priorities(const char* priorities) +{ + const char *p = priorities; + char *additional = NULL; + char *resolved = NULL; + const char *ss, *ss_next; + unsigned ss_len, ss_next_len; + size_t n, n2 = 0; + int ret; + + while (c_isspace(*p)) { + p++; + } + + /* Cannot reduce further. */ + if (*p != '@') { + return gnutls_strdup(p); + } + + ss = p+1; + additional = strchr(ss, ':'); + if (additional) { + additional++; + } + + /* Always try to refresh the cached data, to allow it to be + * updated without restarting all applications. + */ + ret = _gnutls_update_system_priorities(false /* defer_system_wide */); + if (ret < 0) { + _gnutls_debug_log("failed to update system priorities: %s\n", + gnutls_strerror(ret)); + } + + do { + ss_next = strchr(ss, ','); + if (ss_next) { + if (additional && ss_next > additional) { + ss_next = NULL; + } else { + ss_next++; + } + } + + if (ss_next) { + ss_len = ss_next - ss - 1; + ss_next_len = additional - ss_next - 1; + } else if (additional) { + ss_len = additional - ss - 1; + ss_next_len = 0; + } else { + ss_len = strlen(ss); + ss_next_len = 0; + } + + ret = gnutls_rwlock_rdlock(&system_wide_config_rwlock); + if (ret < 0) { + _gnutls_debug_log("cannot read system priority strings: %s\n", + gnutls_strerror(ret)); + break; + } + if (system_wide_config.allowlisting && + ss_len == sizeof(LEVEL_SYSTEM) - 1 && + strncmp(LEVEL_SYSTEM, ss, ss_len) == 0) { + p = system_wide_config.priority_string; + } else { + p = _name_val_array_value(system_wide_config.priority_strings, ss, ss_len); + } + + _gnutls_debug_log("resolved '%.*s' to '%s', next '%.*s'\n", + ss_len, ss, S(p), ss_next_len, S(ss_next)); + + if (p) { + n = strlen(p); + if (additional) { + n2 = strlen(additional); + } + + resolved = gnutls_malloc(n+n2+1+1); + if (resolved) { + memcpy(resolved, p, n); + if (additional) { + resolved[n] = ':'; + memcpy(&resolved[n+1], additional, n2); + resolved[n+n2+1] = 0; + } else { + resolved[n] = 0; + } + } + } + + (void)gnutls_rwlock_unlock(&system_wide_config_rwlock); + + ss = ss_next; + } while (ss && !resolved); + + if (resolved) { + _gnutls_debug_log("selected priority string: %s\n", resolved); + } else { + _gnutls_debug_log("unable to resolve %s\n", priorities); + } + + return resolved; +} + +static void add_ec(gnutls_priority_t priority_cache) +{ + const gnutls_group_entry_st *ge; + unsigned i; + + for (i = 0; i < priority_cache->_supported_ecc.num_priorities; i++) { + ge = _gnutls_id_to_group(priority_cache->_supported_ecc.priorities[i]); + if (ge != NULL && priority_cache->groups.size < sizeof(priority_cache->groups.entry)/sizeof(priority_cache->groups.entry[0])) { + /* do not add groups which do not correspond to enabled ciphersuites */ + if (!ge->curve) + continue; + priority_cache->groups.entry[priority_cache->groups.size++] = ge; + } + } +} + +static void add_dh(gnutls_priority_t priority_cache) +{ + const gnutls_group_entry_st *ge; + unsigned i; + + for (i = 0; i < priority_cache->_supported_ecc.num_priorities; i++) { + ge = _gnutls_id_to_group(priority_cache->_supported_ecc.priorities[i]); + if (ge != NULL && priority_cache->groups.size < sizeof(priority_cache->groups.entry)/sizeof(priority_cache->groups.entry[0])) { + /* do not add groups which do not correspond to enabled ciphersuites */ + if (!ge->prime) + continue; + priority_cache->groups.entry[priority_cache->groups.size++] = ge; + priority_cache->groups.have_ffdhe = 1; + } + } +} + +/* This function was originally precalculating ciphersuite-specific items, however + * it has now extended to much more than that. It provides a consistency check to + * set parameters, and in cases it applies policy specific items. + */ +static int set_ciphersuite_list(gnutls_priority_t priority_cache) +{ + unsigned i, j, z; + const gnutls_cipher_suite_entry_st *ce; + const gnutls_sign_entry_st *se; + unsigned have_ec = 0; + unsigned have_dh = 0; + unsigned tls_sig_sem = 0; + const version_entry_st *tlsmax = NULL, *vers; + const version_entry_st *dtlsmax = NULL; + const version_entry_st *tlsmin = NULL; + const version_entry_st *dtlsmin = NULL; + unsigned have_tls13 = 0, have_srp = 0; + unsigned have_pre_tls12 = 0, have_tls12 = 0; + unsigned have_psk = 0, have_null = 0, have_rsa_psk = 0; + gnutls_digest_algorithm_t prf_digest; + int ret = 0; + + /* have_psk indicates that a PSK key exchange compatible + * with TLS1.3 is enabled. */ + + priority_cache->cs.size = 0; + priority_cache->sigalg.size = 0; + priority_cache->groups.size = 0; + priority_cache->groups.have_ffdhe = 0; + + /* The following requires a lock so there are no inconsistencies in the + * members of system_wide_config loaded from the config file. */ + ret = gnutls_rwlock_rdlock(&system_wide_config_rwlock); + if (ret < 0) { + return gnutls_assert_val(ret); + } + + /* in blocklisting mode, apply system wide disablement of key exchanges, + * groups, MACs, and ciphers. */ + if (!system_wide_config.allowlisting) { + /* disable key exchanges which are globally disabled */ + z = 0; + while (system_wide_config.kxs[z] != 0) { + for (i = j = 0; i < priority_cache->_kx.num_priorities; i++) { + if (priority_cache->_kx.priorities[i] != system_wide_config.kxs[z]) + priority_cache->_kx.priorities[j++] = priority_cache->_kx.priorities[i]; + } + priority_cache->_kx.num_priorities = j; + z++; + } + + /* disable groups which are globally disabled */ + z = 0; + while (system_wide_config.groups[z] != 0) { + for (i = j = 0; i < priority_cache->_supported_ecc.num_priorities; i++) { + if (priority_cache->_supported_ecc.priorities[i] != system_wide_config.groups[z]) + priority_cache->_supported_ecc.priorities[j++] = priority_cache->_supported_ecc.priorities[i]; + } + priority_cache->_supported_ecc.num_priorities = j; + z++; + } + + /* disable ciphers which are globally disabled */ + z = 0; + while (system_wide_config.ciphers[z] != 0) { + for (i = j = 0; i < priority_cache->_cipher.num_priorities; i++) { + if (priority_cache->_cipher.priorities[i] != system_wide_config.ciphers[z]) + priority_cache->_cipher.priorities[j++] = priority_cache->_cipher.priorities[i]; + } + priority_cache->_cipher.num_priorities = j; + z++; + } + + /* disable MACs which are globally disabled */ + z = 0; + while (system_wide_config.macs[z] != 0) { + for (i = j = 0; i < priority_cache->_mac.num_priorities; i++) { + if (priority_cache->_mac.priorities[i] != system_wide_config.macs[z]) + priority_cache->_mac.priorities[j++] = priority_cache->_mac.priorities[i]; + } + priority_cache->_mac.num_priorities = j; + z++; + } + } + + for (j=0;j<priority_cache->_cipher.num_priorities;j++) { + if (priority_cache->_cipher.priorities[j] == GNUTLS_CIPHER_NULL) { + have_null = 1; + break; + } + } + + for (i = 0; i < priority_cache->_kx.num_priorities; i++) { + if (IS_SRP_KX(priority_cache->_kx.priorities[i])) { + have_srp = 1; + } else if (_gnutls_kx_is_psk(priority_cache->_kx.priorities[i])) { + if (priority_cache->_kx.priorities[i] == GNUTLS_KX_RSA_PSK) + have_rsa_psk = 1; + else + have_psk = 1; + } + } + + /* disable TLS versions which are added but are unsupported */ + for (i = j = 0; i < priority_cache->protocol.num_priorities; i++) { + vers = version_to_entry(priority_cache->protocol.priorities[i]); + if (!vers || vers->supported || + (system_wide_config.allowlisting && \ + vers->supported_revertible)) + priority_cache->protocol.priorities[j++] = priority_cache->protocol.priorities[i]; + } + priority_cache->protocol.num_priorities = j; + + + /* if we have NULL ciphersuites, SRP, or RSA-PSK enabled remove TLS1.3+ + * protocol versions; they cannot be negotiated under TLS1.3. */ + if (have_null || have_srp || have_rsa_psk || priority_cache->no_extensions) { + for (i = j = 0; i < priority_cache->protocol.num_priorities; i++) { + vers = version_to_entry(priority_cache->protocol.priorities[i]); + if (!vers || !vers->tls13_sem) + priority_cache->protocol.priorities[j++] = priority_cache->protocol.priorities[i]; + } + priority_cache->protocol.num_priorities = j; + } + + for (i = 0; i < priority_cache->protocol.num_priorities; i++) { + vers = version_to_entry(priority_cache->protocol.priorities[i]); + if (!vers) + continue; + + if (vers->transport == GNUTLS_STREAM) { /* TLS */ + tls_sig_sem |= vers->tls_sig_sem; + if (vers->tls13_sem) + have_tls13 = 1; + + if (vers->id == GNUTLS_TLS1_2) + have_tls12 = 1; + else if (vers->id < GNUTLS_TLS1_2) + have_pre_tls12 = 1; + + if (tlsmax == NULL || vers->age > tlsmax->age) + tlsmax = vers; + if (tlsmin == NULL || vers->age < tlsmin->age) + tlsmin = vers; + } else { /* dtls */ + tls_sig_sem |= vers->tls_sig_sem; + + /* we need to introduce similar handling to above + * when DTLS1.3 is supported */ + + if (dtlsmax == NULL || vers->age > dtlsmax->age) + dtlsmax = vers; + if (dtlsmin == NULL || vers->age < dtlsmin->age) + dtlsmin = vers; + } + } + + /* DTLS or TLS protocols must be present */ + if ((!tlsmax || !tlsmin) && (!dtlsmax || !dtlsmin)) { + ret = gnutls_assert_val(GNUTLS_E_NO_PRIORITIES_WERE_SET); + goto out; + } + + priority_cache->have_psk = have_psk; + + /* if we are have TLS1.3+ do not enable any key exchange algorithms, + * the protocol doesn't require any. */ + if (tlsmin && tlsmin->tls13_sem && !have_psk) { + if (!dtlsmin || (dtlsmin && dtlsmin->tls13_sem)) + priority_cache->_kx.num_priorities = 0; + } + + /* Add TLS 1.3 ciphersuites (no KX) */ + for (j=0;j<priority_cache->_cipher.num_priorities;j++) { + for (z=0;z<priority_cache->_mac.num_priorities;z++) { + ce = cipher_suite_get( + 0, priority_cache->_cipher.priorities[j], + priority_cache->_mac.priorities[z]); + if (ce == NULL) + continue; + + prf_digest = MAC_TO_DIG(ce->prf); + if (prf_digest == GNUTLS_DIG_UNKNOWN) + continue; + if (_gnutls_digest_is_insecure(prf_digest)) + continue; + + if (priority_cache->cs.size < MAX_CIPHERSUITE_SIZE) + priority_cache->cs.entry[priority_cache->cs.size++] = ce; + } + } + + for (i = 0; i < priority_cache->_kx.num_priorities; i++) { + for (j=0;j<priority_cache->_cipher.num_priorities;j++) { + for (z=0;z<priority_cache->_mac.num_priorities;z++) { + ce = cipher_suite_get( + priority_cache->_kx.priorities[i], + priority_cache->_cipher.priorities[j], + priority_cache->_mac.priorities[z]); + if (ce == NULL) + continue; + + prf_digest = MAC_TO_DIG(ce->prf); + if (prf_digest == GNUTLS_DIG_UNKNOWN) + continue; + if (_gnutls_digest_is_insecure(prf_digest)) + continue; + + if (priority_cache->cs.size == MAX_CIPHERSUITE_SIZE) + continue; + priority_cache->cs.entry[priority_cache->cs.size++] = ce; + if (!have_ec && (_gnutls_kx_is_ecc(ce->kx_algorithm) || + _gnutls_kx_is_vko_gost(ce->kx_algorithm))) { + have_ec = 1; + add_ec(priority_cache); + } + if (!have_dh && _gnutls_kx_is_dhe(ce->kx_algorithm)) { + have_dh = 1; + add_dh(priority_cache); + } + } + } + } + + if (have_tls13 && (!have_ec || !have_dh)) { + /* scan groups to determine have_ec and have_dh */ + for (i=0; i < priority_cache->_supported_ecc.num_priorities; i++) { + const gnutls_group_entry_st *ge; + ge = _gnutls_id_to_group(priority_cache->_supported_ecc.priorities[i]); + if (ge) { + if (ge->curve && !have_ec) { + add_ec(priority_cache); + have_ec = 1; + } else if (ge->prime && !have_dh) { + add_dh(priority_cache); + have_dh = 1; + } + + if (have_dh && have_ec) + break; + } + } + + } + + for (i = 0; i < priority_cache->_sign_algo.num_priorities; i++) { + se = _gnutls_sign_to_entry(priority_cache->_sign_algo.priorities[i]); + if (se != NULL && priority_cache->sigalg.size < sizeof(priority_cache->sigalg.entry)/sizeof(priority_cache->sigalg.entry[0])) { + /* if the signature algorithm semantics is not + * compatible with the protocol's, or the algorithm is + * marked as insecure, then skip. */ + if ((se->aid.tls_sem & tls_sig_sem) == 0 || + !_gnutls_sign_is_secure2(se, system_wide_config.allowlisting ? + GNUTLS_SIGN_FLAG_ALLOW_INSECURE_REVERTIBLE : + 0)) { + continue; + } + priority_cache->sigalg.entry[priority_cache->sigalg.size++] = se; + } + } + + _gnutls_debug_log("added %d protocols, %d ciphersuites, %d sig algos and %d groups into priority list\n", + priority_cache->protocol.num_priorities, + priority_cache->cs.size, priority_cache->sigalg.size, + priority_cache->groups.size); + + if (priority_cache->sigalg.size == 0) { + /* no signature algorithms; eliminate TLS 1.2 or DTLS 1.2 and later */ + priority_st newp; + newp.num_priorities = 0; + + /* we need to eliminate TLS 1.2 or DTLS 1.2 and later protocols */ + for (i = 0; i < priority_cache->protocol.num_priorities; i++) { + if (priority_cache->protocol.priorities[i] < GNUTLS_TLS1_2) { + newp.priorities[newp.num_priorities++] = priority_cache->protocol.priorities[i]; + } else if (priority_cache->protocol.priorities[i] >= GNUTLS_DTLS_VERSION_MIN && + priority_cache->protocol.priorities[i] < GNUTLS_DTLS1_2) { + newp.priorities[newp.num_priorities++] = priority_cache->protocol.priorities[i]; + } + } + memcpy(&priority_cache->protocol, &newp, sizeof(newp)); + } + + if (unlikely(priority_cache->protocol.num_priorities == 0)) { + ret = gnutls_assert_val(GNUTLS_E_NO_PRIORITIES_WERE_SET); + goto out; + } +#ifndef ENABLE_SSL3 + else if (unlikely(priority_cache->protocol.num_priorities == 1 && priority_cache->protocol.priorities[0] == GNUTLS_SSL3)) { + ret = gnutls_assert_val(GNUTLS_E_NO_PRIORITIES_WERE_SET); + goto out; + } +#endif + + if (unlikely(priority_cache->cs.size == 0)) { + ret = gnutls_assert_val(GNUTLS_E_NO_PRIORITIES_WERE_SET); + goto out; + } + + /* when TLS 1.3 is available we must have groups set; additionally + * we require TLS1.2 to be enabled if TLS1.3 is asked for, and + * a pre-TLS1.2 protocol is there; that is because servers which + * do not support TLS1.3 will negotiate TLS1.2 if seen a TLS1.3 handshake */ + if (unlikely((!have_psk && tlsmax && tlsmax->id >= GNUTLS_TLS1_3 && priority_cache->groups.size == 0)) || + (!have_tls12 && have_pre_tls12 && have_tls13)) { + for (i = j = 0; i < priority_cache->protocol.num_priorities; i++) { + vers = version_to_entry(priority_cache->protocol.priorities[i]); + if (!vers || vers->transport != GNUTLS_STREAM || !vers->tls13_sem) + priority_cache->protocol.priorities[j++] = priority_cache->protocol.priorities[i]; + } + priority_cache->protocol.num_priorities = j; + } + + /* ensure that the verification profile is not lower from the configured */ + if (system_wide_config.verification_profile) { + gnutls_sec_param_t level = priority_cache->level; + gnutls_sec_param_t system_wide_level = _gnutls_profile_to_sec_level(system_wide_config.verification_profile); + + if (level < system_wide_level) { + ENABLE_PROFILE(priority_cache, system_wide_config.verification_profile); + } + } + + out: + (void)gnutls_rwlock_unlock(&system_wide_config_rwlock); + return ret; +} + +/** + * gnutls_priority_init2: + * @priority_cache: is a #gnutls_priority_t type. + * @priorities: is a string describing priorities (may be %NULL) + * @err_pos: In case of an error this will have the position in the string the error occurred + * @flags: zero or %GNUTLS_PRIORITY_INIT_DEF_APPEND + * + * Sets priorities for the ciphers, key exchange methods, and macs. + * The @priority_cache should be deinitialized + * using gnutls_priority_deinit(). + * + * The #priorities option allows you to specify a colon + * separated list of the cipher priorities to enable. + * Some keywords are defined to provide quick access + * to common preferences. + * + * When @flags is set to %GNUTLS_PRIORITY_INIT_DEF_APPEND then the @priorities + * specified will be appended to the default options. + * + * Unless there is a special need, use the "NORMAL" keyword to + * apply a reasonable security level, or "NORMAL:%%COMPAT" for compatibility. + * + * "PERFORMANCE" means all the "secure" ciphersuites are enabled, + * limited to 128 bit ciphers and sorted by terms of speed + * performance. + * + * "LEGACY" the NORMAL settings for GnuTLS 3.2.x or earlier. There is + * no verification profile set, and the allowed DH primes are considered + * weak today. + * + * "NORMAL" means all "secure" ciphersuites. The 256-bit ciphers are + * included as a fallback only. The ciphers are sorted by security + * margin. + * + * "PFS" means all "secure" ciphersuites that support perfect forward secrecy. + * The 256-bit ciphers are included as a fallback only. + * The ciphers are sorted by security margin. + * + * "SECURE128" means all "secure" ciphersuites of security level 128-bit + * or more. + * + * "SECURE192" means all "secure" ciphersuites of security level 192-bit + * or more. + * + * "SUITEB128" means all the NSA SuiteB ciphersuites with security level + * of 128. + * + * "SUITEB192" means all the NSA SuiteB ciphersuites with security level + * of 192. + * + * "NONE" means nothing is enabled. This disables everything, including protocols. + * + * "@@KEYWORD1,KEYWORD2,..." The system administrator imposed settings. + * The provided keyword(s) will be expanded from a configuration-time + * provided file - default is: /etc/gnutls/config. + * Any attributes that follow it, will be appended to the expanded + * string. If multiple keywords are provided, separated by commas, + * then the first keyword that exists in the configuration file + * will be used. At least one of the keywords must exist, or this + * function will return an error. Typical usage would be to specify + * an application specified keyword first, followed by "SYSTEM" as + * a default fallback. e.g., "@LIBVIRT,SYSTEM:!-VERS-SSL3.0" will + * first try to find a config file entry matching "LIBVIRT", but if + * that does not exist will use the entry for "SYSTEM". If "SYSTEM" + * does not exist either, an error will be returned. In all cases, + * the SSL3.0 protocol will be disabled. The system priority file + * entries should be formatted as "KEYWORD=VALUE", e.g., + * "SYSTEM=NORMAL:+ARCFOUR-128". + * + * Special keywords are "!", "-" and "+". + * "!" or "-" appended with an algorithm will remove this algorithm. + * "+" appended with an algorithm will add this algorithm. + * + * Check the GnuTLS manual section "Priority strings" for detailed + * information. + * + * Examples: + * + * "NONE:+VERS-TLS-ALL:+MAC-ALL:+RSA:+AES-128-CBC:+SIGN-ALL:+COMP-NULL" + * + * "NORMAL:+ARCFOUR-128" means normal ciphers plus ARCFOUR-128. + * + * "SECURE128:-VERS-SSL3.0" means that only secure ciphers are + * and enabled, SSL3.0 is disabled. + * + * "NONE:+VERS-TLS-ALL:+AES-128-CBC:+RSA:+SHA1:+COMP-NULL:+SIGN-RSA-SHA1", + * + * "NONE:+VERS-TLS-ALL:+AES-128-CBC:+ECDHE-RSA:+SHA1:+COMP-NULL:+SIGN-RSA-SHA1:+CURVE-SECP256R1", + * + * "SECURE256:+SECURE128", + * + * Note that "NORMAL:%%COMPAT" is the most compatible mode. + * + * A %NULL @priorities string indicates the default priorities to be + * used (this is available since GnuTLS 3.3.0). + * + * Returns: On syntax error %GNUTLS_E_INVALID_REQUEST is returned, + * %GNUTLS_E_SUCCESS on success, or an error code. + * + * Since: 3.6.3 + **/ +int +gnutls_priority_init2(gnutls_priority_t * priority_cache, + const char *priorities, const char **err_pos, + unsigned flags) +{ + gnutls_buffer_st buf; + const char *ep; + int ret; + + if (flags & GNUTLS_PRIORITY_INIT_DEF_APPEND) { + if (priorities == NULL) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + if (err_pos) + *err_pos = priorities; + + _gnutls_buffer_init(&buf); + + ret = _gnutls_buffer_append_str(&buf, _gnutls_default_priority_string); + if (ret < 0) { + _gnutls_buffer_clear(&buf); + return gnutls_assert_val(ret); + } + + ret = _gnutls_buffer_append_str(&buf, ":"); + if (ret < 0) { + _gnutls_buffer_clear(&buf); + return gnutls_assert_val(ret); + } + + ret = _gnutls_buffer_append_str(&buf, priorities); + if (ret < 0) { + _gnutls_buffer_clear(&buf); + return gnutls_assert_val(ret); + } + + ret = gnutls_priority_init(priority_cache, (const char*)buf.data, &ep); + if (ret < 0 && ep != (const char*)buf.data && ep != NULL) { + ptrdiff_t diff = (ptrdiff_t)ep-(ptrdiff_t)buf.data; + unsigned hlen = strlen(_gnutls_default_priority_string)+1; + + if (err_pos && diff > hlen) { + *err_pos = priorities + diff - hlen; + } + } + _gnutls_buffer_clear(&buf); + return ret; + } else { + return gnutls_priority_init(priority_cache, priorities, err_pos); + } +} + +#define PRIO_MATCH(name) c_strncasecmp(&broken_list[i][1], name, sizeof(name) - 1) + +/** + * gnutls_priority_init: + * @priority_cache: is a #gnutls_priority_t type. + * @priorities: is a string describing priorities (may be %NULL) + * @err_pos: In case of an error this will have the position in the string the error occurred + * + * For applications that do not modify their crypto settings per release, consider + * using gnutls_priority_init2() with %GNUTLS_PRIORITY_INIT_DEF_APPEND flag + * instead. We suggest to use centralized crypto settings handled by the GnuTLS + * library, and applications modifying the default settings to their needs. + * + * This function is identical to gnutls_priority_init2() with zero + * flags. + * + * A %NULL @priorities string indicates the default priorities to be + * used (this is available since GnuTLS 3.3.0). + * + * Returns: On syntax error %GNUTLS_E_INVALID_REQUEST is returned, + * %GNUTLS_E_SUCCESS on success, or an error code. + **/ +int +gnutls_priority_init(gnutls_priority_t * priority_cache, + const char *priorities, const char **err_pos) +{ + char *broken_list[MAX_ELEMENTS]; + int broken_list_size = 0, i = 0, j; + char *darg = NULL; + unsigned ikeyword_set = 0; + int algo; + int ret; + rmadd_func *fn; + bulk_rmadd_func *bulk_fn; + bulk_rmadd_func *bulk_given_fn; + const cipher_entry_st *centry; + unsigned resolved_match = 1; + + if (err_pos) + *err_pos = priorities; + + *priority_cache = + gnutls_calloc(1, sizeof(struct gnutls_priority_st)); + if (*priority_cache == NULL) { + gnutls_assert(); + return GNUTLS_E_MEMORY_ERROR; + } + + /* for now unsafe renegotiation is default on everyone. To be removed + * when we make it the default. + */ + (*priority_cache)->sr = SR_PARTIAL; + /* For now TLS 1.3 middlebox compatibility mode is enabled by default. + * This will eventually be disabled by default and moved to the %COMPAT + * setting. + */ + (*priority_cache)->tls13_compat_mode = true; + (*priority_cache)->min_record_version = 1; + gnutls_atomic_init(&(*priority_cache)->usage_cnt); + + if (system_wide_config.allowlisting && !priorities) { + priorities = "@" LEVEL_SYSTEM; + } + if (priorities == NULL) { + priorities = _gnutls_default_priority_string; + resolved_match = 0; + } + + darg = _gnutls_resolve_priorities(priorities); + if (darg == NULL) { + gnutls_assert(); + goto error; + } + + if (strcmp(darg, priorities) != 0) + resolved_match = 0; + + break_list(darg, broken_list, &broken_list_size); + /* This is our default set of protocol version, certificate types. + */ + if (c_strcasecmp(broken_list[0], LEVEL_NONE) != 0) { + _set_priority(&(*priority_cache)->protocol, + protocol_priority); + _set_priority(&(*priority_cache)->client_ctype, + cert_type_priority_default); + _set_priority(&(*priority_cache)->server_ctype, + cert_type_priority_default); + _set_priority(&(*priority_cache)->_sign_algo, + sign_priority_default); + _set_priority(&(*priority_cache)->_supported_ecc, + supported_groups_normal); + i = 0; + } else { + ikeyword_set = 1; + i = 1; + } + + for (; i < broken_list_size; i++) { + if (check_level(broken_list[i], *priority_cache, ikeyword_set) != 0) { + ikeyword_set = 1; + continue; + } else if (broken_list[i][0] == '!' + || broken_list[i][0] == '+' + || broken_list[i][0] == '-') { + if (broken_list[i][0] == '+') { + fn = prio_add; + bulk_fn = _add_priority; + bulk_given_fn = _add_priority; + } else { + fn = prio_remove; + bulk_fn = _clear_priorities; + bulk_given_fn = _clear_given_priorities; + } + + if (broken_list[i][0] == '+' + && check_level(&broken_list[i][1], + *priority_cache, 1) != 0) { + continue; + } else if ((algo = + gnutls_mac_get_id(&broken_list[i][1])) + != GNUTLS_MAC_UNKNOWN) { + fn(&(*priority_cache)->_mac, algo); + } else if ((centry = cipher_name_to_entry(&broken_list[i][1])) != NULL) { + if (_gnutls_cipher_exists(centry->id)) { + fn(&(*priority_cache)->_cipher, centry->id); + if (centry->type == CIPHER_BLOCK) + (*priority_cache)->have_cbc = 1; + } + } else if ((algo = + _gnutls_kx_get_id(&broken_list[i][1])) != + GNUTLS_KX_UNKNOWN) { + if (algo != GNUTLS_KX_INVALID) + fn(&(*priority_cache)->_kx, algo); + } else if (PRIO_MATCH("VERS-") == 0) { + if (PRIO_MATCH("VERS-TLS-ALL") == 0) { + bulk_given_fn(&(*priority_cache)-> + protocol, + stream_protocol_priority); + } else if (PRIO_MATCH("VERS-DTLS-ALL") == 0) { + bulk_given_fn(&(*priority_cache)-> + protocol, + (bulk_given_fn==_add_priority)?dtls_protocol_priority:dgram_protocol_priority); + } else if (PRIO_MATCH("VERS-ALL") == 0) { + bulk_fn(&(*priority_cache)-> + protocol, + protocol_priority); + } else { + if ((algo = + gnutls_protocol_get_id + (&broken_list[i][6])) != + GNUTLS_VERSION_UNKNOWN) { + fn(&(*priority_cache)-> + protocol, algo); + } else + goto error; + + } + } /* now check if the element is something like -ALGO */ + else if (PRIO_MATCH("COMP-") == 0) { + /* ignore all compression methods */ + continue; + } /* now check if the element is something like -ALGO */ + else if (PRIO_MATCH("CURVE-") == 0) { + if (PRIO_MATCH("CURVE-ALL") == 0) { + bulk_fn(&(*priority_cache)-> + _supported_ecc, + supported_groups_normal); + } else { + if ((algo = + gnutls_ecc_curve_get_id + (&broken_list[i][7])) != + GNUTLS_ECC_CURVE_INVALID) + fn(&(*priority_cache)-> + _supported_ecc, algo); + else + goto error; + } + } else if (PRIO_MATCH("GROUP-") == 0) { + if (PRIO_MATCH("GROUP-ALL") == 0) { + bulk_fn(&(*priority_cache)-> + _supported_ecc, + supported_groups_normal); + } else if (PRIO_MATCH("GROUP-DH-ALL") == 0) { + bulk_given_fn(&(*priority_cache)-> + _supported_ecc, + _supported_groups_dh); + } else if (PRIO_MATCH("GROUP-EC-ALL") == 0) { + bulk_given_fn(&(*priority_cache)-> + _supported_ecc, + _supported_groups_ecdh); + } else if (PRIO_MATCH("GROUP-GOST-ALL") == 0) { + bulk_given_fn(&(*priority_cache)-> + _supported_ecc, + _supported_groups_gost); + } else { + if ((algo = + _gnutls_group_get_id + (&broken_list[i][7])) != + GNUTLS_GROUP_INVALID) + fn(&(*priority_cache)-> + _supported_ecc, algo); + else + goto error; + } + } else if (PRIO_MATCH("CTYPE-") == 0) { + // Certificate types + if (PRIO_MATCH("CTYPE-ALL") == 0) { + // Symmetric cert types, all types allowed + bulk_fn(&(*priority_cache)->client_ctype, + cert_type_priority_all); + bulk_fn(&(*priority_cache)->server_ctype, + cert_type_priority_all); + } else if (PRIO_MATCH("CTYPE-CLI-") == 0) { + // Client certificate types + if (PRIO_MATCH("CTYPE-CLI-ALL") == 0) { + // All client cert types allowed + bulk_fn(&(*priority_cache)->client_ctype, + cert_type_priority_all); + } else if ((algo = gnutls_certificate_type_get_id + (&broken_list[i][11])) != GNUTLS_CRT_UNKNOWN) { + // Specific client cert type allowed + fn(&(*priority_cache)->client_ctype, algo); + } else goto error; + } else if (PRIO_MATCH("CTYPE-SRV-") == 0) { + // Server certificate types + if (PRIO_MATCH("CTYPE-SRV-ALL") == 0) { + // All server cert types allowed + bulk_fn(&(*priority_cache)->server_ctype, + cert_type_priority_all); + } else if ((algo = gnutls_certificate_type_get_id + (&broken_list[i][11])) != GNUTLS_CRT_UNKNOWN) { + // Specific server cert type allowed + fn(&(*priority_cache)->server_ctype, algo); + } else goto error; + } else { // Symmetric certificate type + if ((algo = gnutls_certificate_type_get_id + (&broken_list[i][7])) != GNUTLS_CRT_UNKNOWN) { + fn(&(*priority_cache)->client_ctype, algo); + fn(&(*priority_cache)->server_ctype, algo); + } else if (PRIO_MATCH("CTYPE-OPENPGP") == 0) { + /* legacy openpgp option - ignore */ + continue; + } else goto error; + } + } else if (PRIO_MATCH("SIGN-") == 0) { + if (PRIO_MATCH("SIGN-ALL") == 0) { + bulk_fn(&(*priority_cache)-> + _sign_algo, + sign_priority_default); + } else if (PRIO_MATCH("SIGN-GOST-ALL") == 0) { + bulk_fn(&(*priority_cache)-> + _sign_algo, + sign_priority_gost); + } else { + if ((algo = + gnutls_sign_get_id + (&broken_list[i][6])) != + GNUTLS_SIGN_UNKNOWN) + fn(&(*priority_cache)-> + _sign_algo, algo); + else + goto error; + } + } else if (PRIO_MATCH("MAC-") == 0) { + if (PRIO_MATCH("MAC-ALL") == 0) { + bulk_fn(&(*priority_cache)->_mac, + mac_priority_normal); + } else if (PRIO_MATCH("MAC-GOST-ALL") == 0) { + bulk_fn(&(*priority_cache)->_mac, + mac_priority_gost); + } + } else if (PRIO_MATCH("CIPHER-") == 0) { + if (PRIO_MATCH("CIPHER-ALL") == 0) { + bulk_fn(&(*priority_cache)->_cipher, + cipher_priority_normal); + } else if (PRIO_MATCH("CIPHER-GOST-ALL") == 0) { + bulk_fn(&(*priority_cache)->_cipher, + cipher_priority_gost); + } + } else if (PRIO_MATCH("KX-") == 0) { + if (PRIO_MATCH("KX-ALL") == 0) { + bulk_fn(&(*priority_cache)->_kx, + kx_priority_secure); + } else if (PRIO_MATCH("KX-GOST-ALL") == 0) { + bulk_fn(&(*priority_cache)->_kx, + kx_priority_gost); + } + } else if (PRIO_MATCH("GOST") == 0) { + bulk_given_fn(&(*priority_cache)->_supported_ecc, + _supported_groups_gost); + bulk_fn(&(*priority_cache)->_sign_algo, + sign_priority_gost); + bulk_fn(&(*priority_cache)->_mac, + mac_priority_gost); + bulk_fn(&(*priority_cache)->_cipher, + cipher_priority_gost); + bulk_fn(&(*priority_cache)->_kx, + kx_priority_gost); + } else + goto error; + } else if (broken_list[i][0] == '%') { + const struct priority_options_st * o; + /* to add a new option modify + * priority_options.gperf */ + o = in_word_set(&broken_list[i][1], strlen(&broken_list[i][1])); + if (o == NULL) { + goto error; + } + o->func(*priority_cache); + } else + goto error; + } + + ret = set_ciphersuite_list(*priority_cache); + if (ret < 0) { + if (err_pos) + *err_pos = priorities; + goto error_cleanup; + } + + gnutls_free(darg); + + return 0; + + error: + if (err_pos != NULL && i < broken_list_size && resolved_match) { + *err_pos = priorities; + for (j = 0; j < i; j++) { + (*err_pos) += strlen(broken_list[j]) + 1; + } + } + ret = GNUTLS_E_INVALID_REQUEST; + + error_cleanup: + free(darg); + gnutls_priority_deinit(*priority_cache); + *priority_cache = NULL; + + return ret; +} + +/** + * gnutls_priority_deinit: + * @priority_cache: is a #gnutls_priority_t type. + * + * Deinitializes the priority cache. + **/ +void gnutls_priority_deinit(gnutls_priority_t priority_cache) +{ + if (priority_cache == NULL) + return; + + /* Note that here we care about the following two cases: + * 1. Multiple sessions or different threads holding a reference + a global reference + * 2. One session holding a reference with a possible global reference + * + * As such, it will never be that two threads reach the + * zero state at the same time, unless the global reference + * is cleared too, which is invalid state. + */ + if (gnutls_atomic_val(&priority_cache->usage_cnt) == 0) { + gnutls_atomic_deinit(&priority_cache->usage_cnt); + gnutls_free(priority_cache); + return; + } else { + gnutls_atomic_decrement(&priority_cache->usage_cnt); + } +} + + +/** + * gnutls_priority_set_direct: + * @session: is a #gnutls_session_t type. + * @priorities: is a string describing priorities + * @err_pos: In case of an error this will have the position in the string the error occurred + * + * Sets the priorities to use on the ciphers, key exchange methods, + * and macs. This function avoids keeping a + * priority cache and is used to directly set string priorities to a + * TLS session. For documentation check the gnutls_priority_init(). + * + * To use a reasonable default, consider using gnutls_set_default_priority(), + * or gnutls_set_default_priority_append() instead of this function. + * + * Returns: On syntax error %GNUTLS_E_INVALID_REQUEST is returned, + * %GNUTLS_E_SUCCESS on success, or an error code. + **/ +int +gnutls_priority_set_direct(gnutls_session_t session, + const char *priorities, const char **err_pos) +{ + gnutls_priority_t prio; + int ret; + + ret = gnutls_priority_init(&prio, priorities, err_pos); + if (ret < 0) { + gnutls_assert(); + return ret; + } + + ret = gnutls_priority_set(session, prio); + if (ret < 0) { + gnutls_assert(); + return ret; + } + + /* ensure that the session holds the only reference for the struct */ + gnutls_priority_deinit(prio); + + return 0; +} + +/* Breaks a list of "xxx", "yyy", to a character array, of + * MAX_COMMA_SEP_ELEMENTS size; Note that the given string is modified. + */ +static void +break_list(char *list, + char *broken_list[MAX_ELEMENTS], int *size) +{ + char *p = list; + + *size = 0; + + do { + broken_list[*size] = p; + + (*size)++; + + p = strchr(p, ':'); + if (p) { + *p = 0; + p++; /* move to next entry and skip white + * space. + */ + while (*p == ' ') + p++; + } + } + while (p != NULL && *size < MAX_ELEMENTS); +} + +/** + * gnutls_set_default_priority: + * @session: is a #gnutls_session_t type. + * + * Sets the default priority on the ciphers, key exchange methods, + * and macs. This is the recommended method of + * setting the defaults, in order to promote consistency between applications + * using GnuTLS, and to allow GnuTLS using applications to update settings + * in par with the library. For client applications which require + * maximum compatibility consider calling gnutls_session_enable_compatibility_mode() + * after this function. + * + * For an application to specify additional options to priority string + * consider using gnutls_set_default_priority_append(). + * + * To allow a user to override the defaults (e.g., when a user interface + * or configuration file is available), the functions + * gnutls_priority_set_direct() or gnutls_priority_set() can + * be used. + * + * Returns: %GNUTLS_E_SUCCESS on success, or an error code. + * + * Since: 2.1.4 + **/ +int gnutls_set_default_priority(gnutls_session_t session) +{ + return gnutls_priority_set_direct(session, NULL, NULL); +} + +/** + * gnutls_set_default_priority_append: + * @session: is a #gnutls_session_t type. + * @add_prio: is a string describing priorities to be appended to default + * @err_pos: In case of an error this will have the position in the string the error occurred + * @flags: must be zero + * + * Sets the default priority on the ciphers, key exchange methods, + * and macs with the additional options in @add_prio. This is the recommended method of + * setting the defaults when only few additional options are to be added. This promotes + * consistency between applications using GnuTLS, and allows GnuTLS using applications + * to update settings in par with the library. + * + * The @add_prio string should start as a normal priority string, e.g., + * '-VERS-TLS-ALL:+VERS-TLS1.3:%%COMPAT' or '%%FORCE_ETM'. That is, it must not start + * with ':'. + * + * To allow a user to override the defaults (e.g., when a user interface + * or configuration file is available), the functions + * gnutls_priority_set_direct() or gnutls_priority_set() can + * be used. + * + * Returns: %GNUTLS_E_SUCCESS on success, or an error code. + * + * Since: 3.6.3 + **/ +int gnutls_set_default_priority_append(gnutls_session_t session, + const char *add_prio, + const char **err_pos, + unsigned flags) +{ + gnutls_priority_t prio; + int ret; + + ret = gnutls_priority_init2(&prio, add_prio, err_pos, GNUTLS_PRIORITY_INIT_DEF_APPEND); + if (ret < 0) { + gnutls_assert(); + return ret; + } + + ret = gnutls_priority_set(session, prio); + if (ret < 0) { + gnutls_assert(); + return ret; + } + + /* ensure that the session holds the only reference for the struct */ + gnutls_priority_deinit(prio); + + return 0; +} + +/** + * gnutls_priority_ecc_curve_list: + * @pcache: is a #gnutls_priority_t type. + * @list: will point to an integer list + * + * Get a list of available elliptic curves in the priority + * structure. + * + * Deprecated: This function has been replaced by + * gnutls_priority_group_list() since 3.6.0. + * + * Returns: the number of items, or an error code. + * + * Since: 3.0 + **/ +int +gnutls_priority_ecc_curve_list(gnutls_priority_t pcache, + const unsigned int **list) +{ + unsigned i; + + if (pcache->_supported_ecc.num_priorities == 0) + return 0; + + *list = pcache->_supported_ecc.priorities; + + /* to ensure we don't confuse the caller, we do not include + * any FFDHE groups. This may return an incomplete list. */ + for (i=0;i<pcache->_supported_ecc.num_priorities;i++) + if (pcache->_supported_ecc.priorities[i] > GNUTLS_ECC_CURVE_MAX) + return i; + + return pcache->_supported_ecc.num_priorities; +} + +/** + * gnutls_priority_group_list: + * @pcache: is a #gnutls_priority_t type. + * @list: will point to an integer list + * + * Get a list of available groups in the priority + * structure. + * + * Returns: the number of items, or an error code. + * + * Since: 3.6.0 + **/ +int +gnutls_priority_group_list(gnutls_priority_t pcache, + const unsigned int **list) +{ + if (pcache->_supported_ecc.num_priorities == 0) + return 0; + + *list = pcache->_supported_ecc.priorities; + return pcache->_supported_ecc.num_priorities; +} + +/** + * gnutls_priority_kx_list: + * @pcache: is a #gnutls_priority_t type. + * @list: will point to an integer list + * + * Get a list of available key exchange methods in the priority + * structure. + * + * Returns: the number of items, or an error code. + * Since: 3.2.3 + **/ +int +gnutls_priority_kx_list(gnutls_priority_t pcache, + const unsigned int **list) +{ + if (pcache->_kx.num_priorities == 0) + return 0; + + *list = pcache->_kx.priorities; + return pcache->_kx.num_priorities; +} + +/** + * gnutls_priority_cipher_list: + * @pcache: is a #gnutls_priority_t type. + * @list: will point to an integer list + * + * Get a list of available ciphers in the priority + * structure. + * + * Returns: the number of items, or an error code. + * Since: 3.2.3 + **/ +int +gnutls_priority_cipher_list(gnutls_priority_t pcache, + const unsigned int **list) +{ + if (pcache->_cipher.num_priorities == 0) + return 0; + + *list = pcache->_cipher.priorities; + return pcache->_cipher.num_priorities; +} + +/** + * gnutls_priority_mac_list: + * @pcache: is a #gnutls_priority_t type. + * @list: will point to an integer list + * + * Get a list of available MAC algorithms in the priority + * structure. + * + * Returns: the number of items, or an error code. + * Since: 3.2.3 + **/ +int +gnutls_priority_mac_list(gnutls_priority_t pcache, + const unsigned int **list) +{ + if (pcache->_mac.num_priorities == 0) + return 0; + + *list = pcache->_mac.priorities; + return pcache->_mac.num_priorities; +} + +/** + * gnutls_priority_compression_list: + * @pcache: is a #gnutls_priority_t type. + * @list: will point to an integer list + * + * Get a list of available compression method in the priority + * structure. + * + * Returns: the number of methods, or an error code. + * Since: 3.0 + **/ +int +gnutls_priority_compression_list(gnutls_priority_t pcache, + const unsigned int **list) +{ + static const unsigned int priority[1] = {GNUTLS_COMP_NULL}; + + *list = priority; + return 1; +} + +/** + * gnutls_priority_protocol_list: + * @pcache: is a #gnutls_priority_t type. + * @list: will point to an integer list + * + * Get a list of available TLS version numbers in the priority + * structure. + * + * Returns: the number of protocols, or an error code. + * Since: 3.0 + **/ +int +gnutls_priority_protocol_list(gnutls_priority_t pcache, + const unsigned int **list) +{ + if (pcache->protocol.num_priorities == 0) + return 0; + + *list = pcache->protocol.priorities; + return pcache->protocol.num_priorities; +} + +/** + * gnutls_priority_sign_list: + * @pcache: is a #gnutls_priority_t type. + * @list: will point to an integer list + * + * Get a list of available signature algorithms in the priority + * structure. + * + * Returns: the number of algorithms, or an error code. + * Since: 3.0 + **/ +int +gnutls_priority_sign_list(gnutls_priority_t pcache, + const unsigned int **list) +{ + if (pcache->_sign_algo.num_priorities == 0) + return 0; + + *list = pcache->_sign_algo.priorities; + return pcache->_sign_algo.num_priorities; +} + +/** + * gnutls_priority_certificate_type_list: + * @pcache: is a #gnutls_priority_t type. + * @list: will point to an integer list + * + * Get a list of available certificate types in the priority + * structure. + * + * As of version 3.6.4 this function is an alias for + * gnutls_priority_certificate_type_list2 with the target parameter + * set to: + * - GNUTLS_CTYPE_SERVER, if the %SERVER_PRECEDENCE option is set + * - GNUTLS_CTYPE_CLIENT, otherwise. + * + * Returns: the number of certificate types, or an error code. + * Since: 3.0 + **/ +int +gnutls_priority_certificate_type_list(gnutls_priority_t pcache, + const unsigned int **list) +{ + gnutls_ctype_target_t target = + pcache->server_precedence ? GNUTLS_CTYPE_SERVER : GNUTLS_CTYPE_CLIENT; + + return gnutls_priority_certificate_type_list2(pcache, list, target); +} + +/** + * gnutls_priority_certificate_type_list2: + * @pcache: is a #gnutls_priority_t type. + * @list: will point to an integer list. + * @target: is a #gnutls_ctype_target_t type. Valid arguments are + * GNUTLS_CTYPE_CLIENT and GNUTLS_CTYPE_SERVER + * + * Get a list of available certificate types for the given target + * in the priority structure. + * + * Returns: the number of certificate types, or an error code. + * + * Since: 3.6.4 + **/ +int +gnutls_priority_certificate_type_list2(gnutls_priority_t pcache, + const unsigned int **list, gnutls_ctype_target_t target) +{ + switch (target) { + case GNUTLS_CTYPE_CLIENT: + if(pcache->client_ctype.num_priorities > 0) { + *list = pcache->client_ctype.priorities; + return pcache->client_ctype.num_priorities; + } + break; + case GNUTLS_CTYPE_SERVER: + if(pcache->server_ctype.num_priorities > 0) { + *list = pcache->server_ctype.priorities; + return pcache->server_ctype.num_priorities; + } + break; + default: + // Invalid target given + gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + + // Found a matching target but non of them had any ctypes set + return 0; +} + +/** + * gnutls_priority_string_list: + * @iter: an integer counter starting from zero + * @flags: one of %GNUTLS_PRIORITY_LIST_INIT_KEYWORDS, %GNUTLS_PRIORITY_LIST_SPECIAL + * + * Can be used to iterate all available priority strings. + * Due to internal implementation details, there are cases where this + * function can return the empty string. In that case that string should be ignored. + * When no strings are available it returns %NULL. + * + * Returns: a priority string + * Since: 3.4.0 + **/ +const char * +gnutls_priority_string_list(unsigned iter, unsigned int flags) +{ + if (flags & GNUTLS_PRIORITY_LIST_INIT_KEYWORDS) { + if (iter >= (sizeof(pgroups)/sizeof(pgroups[0]))-1) + return NULL; + return pgroups[iter].name; + } else if (flags & GNUTLS_PRIORITY_LIST_SPECIAL) { + if (iter >= (sizeof(wordlist)/sizeof(wordlist[0]))-1) + return NULL; + return wordlist[iter].name; + } + return NULL; +} + +bool _gnutls_config_is_ktls_enabled(void){ + return system_wide_config.ktls_enabled; +} + +/* + * high-level interface for overriding configuration files + */ + +static inline bool /* not locking system_wide_config */ +system_wide_config_is_malleable(void) { + if (!system_wide_config.allowlisting) { + _gnutls_audit_log(NULL, "allowlisting is not enabled!\n"); + return false; + } + if (system_wide_config.priority_string) { + _gnutls_audit_log(NULL, "priority strings have already been " + "initialized!\n"); + return false; + } + return true; +} + +/** + * gnutls_digest_set_secure: + * @dig: is a digest algorithm + * @secure: whether to mark the digest algorithm secure + * + * Modify the previous system wide setting that marked @dig as secure + * or insecure. This only has effect when the algorithm is enabled + * through the allowlisting mode in the configuration file, or when + * the setting is modified with a prior call to this function. + * + * Since: 3.7.3 + */ +int +gnutls_digest_set_secure(gnutls_digest_algorithm_t dig, unsigned int secure) +{ +#ifndef DISABLE_SYSTEM_CONFIG + int ret; + ret = gnutls_rwlock_wrlock(&system_wide_config_rwlock); + if (ret < 0) { + (void)gnutls_rwlock_unlock(&system_wide_config_rwlock); + return gnutls_assert_val(ret); + } + if (!system_wide_config_is_malleable()) { + (void)gnutls_rwlock_unlock(&system_wide_config_rwlock); + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + + if (secure) { + ret = cfg_hashes_add(&system_wide_config, dig); + } else { + ret = cfg_hashes_remove(&system_wide_config, dig); + } + + (void)gnutls_rwlock_unlock(&system_wide_config_rwlock); + return ret; +#else + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); +#endif +} + +/** + * gnutls_sign_set_secure: + * @sign: the sign algorithm + * @secure: whether to mark the sign algorithm secure + * + * Modify the previous system wide setting that marked @sign as secure + * or insecure. Calling this function is allowed + * only if allowlisting mode is set in the configuration file, + * and only if the system-wide TLS priority string + * has not been initialized yet. + * The intended usage is to provide applications with a way + * to expressly deviate from the distribution or site defaults + * inherited from the configuration file. + * The modification is composable with further modifications + * performed through the priority string mechanism. + * + * This function is not thread-safe and is intended to be called + * in the main thread at the beginning of the process execution. + * + * Even when @secure is true, @sign is not marked as secure for the + * use in certificates. Use gnutls_sign_set_secure_for_certs() to + * mark it secure as well for certificates. + * + * Returns: 0 on success or negative error code otherwise. + * + * Since: 3.7.3 + */ +int +gnutls_sign_set_secure(gnutls_sign_algorithm_t sign, unsigned int secure) +{ +#ifndef DISABLE_SYSTEM_CONFIG + int ret; + ret = gnutls_rwlock_wrlock(&system_wide_config_rwlock); + if (ret < 0) { + (void)gnutls_rwlock_unlock(&system_wide_config_rwlock); + return gnutls_assert_val(ret); + } + if (!system_wide_config_is_malleable()) { + (void)gnutls_rwlock_unlock(&system_wide_config_rwlock); + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + + if (secure) { + ret = cfg_sigs_add(&system_wide_config, sign); + } else { + ret = cfg_sigs_remove(&system_wide_config, sign); + if (ret < 0) { + (void)gnutls_rwlock_unlock(&system_wide_config_rwlock); + return ret; + } + /* irregularity, distrusting also means distrusting for certs */ + ret = cfg_sigs_for_cert_remove(&system_wide_config, sign); + } + + (void)gnutls_rwlock_unlock(&system_wide_config_rwlock); + return ret; +#else + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); +#endif +} + + +/** + * gnutls_sign_set_secure_for_certs: + * @sign: the sign algorithm + * @secure: whether to mark the sign algorithm secure for certificates + * + * Modify the previous system wide setting that marked @sign as secure + * or insecure for the use in certificates. Calling this fuction is allowed + * only if allowlisting mode is set in the configuration file, + * and only if the system-wide TLS priority string + * has not been initialized yet. + * The intended usage is to provide applications with a way + * to expressly deviate from the distribution or site defaults + * inherited from the configuration file. + * The modification is composable with further modifications + * performed through the priority string mechanism. + * + * This function is not thread-safe and is intended to be called + * in the main thread at the beginning of the process execution. + + * When @secure is true, @sign is marked as secure for any use unlike + * gnutls_sign_set_secure(). Otherwise, it is marked as insecure only + * for the use in certificates. Use gnutls_sign_set_secure() to mark + * it insecure for any uses. + * + * Returns: 0 on success or negative error code otherwise. + * + * Since: 3.7.3 + */ +int +gnutls_sign_set_secure_for_certs(gnutls_sign_algorithm_t sign, + unsigned int secure) +{ +#ifndef DISABLE_SYSTEM_CONFIG + int ret; + ret = gnutls_rwlock_wrlock(&system_wide_config_rwlock); + if (ret < 0) { + (void)gnutls_rwlock_unlock(&system_wide_config_rwlock); + return gnutls_assert_val(ret); + } + if (!system_wide_config_is_malleable()) { + (void)gnutls_rwlock_unlock(&system_wide_config_rwlock); + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + + if (secure) { + /* irregularity, trusting for certs means trusting in general */ + ret = cfg_sigs_add(&system_wide_config, sign); + if (ret < 0) { + (void)gnutls_rwlock_unlock(&system_wide_config_rwlock); + return ret; + } + ret = cfg_sigs_for_cert_add(&system_wide_config, sign); + } else { + ret = cfg_sigs_for_cert_remove(&system_wide_config, sign); + } + + (void)gnutls_rwlock_unlock(&system_wide_config_rwlock); + return ret; +#else + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); +#endif +} + +/** + * gnutls_protocol_set_enabled: + * @version: is a (gnutls) version number + * @enabled: whether to enable the protocol + * + * Control the previous system-wide setting that marked @version as + * enabled or disabled. Calling this fuction is allowed + * only if allowlisting mode is set in the configuration file, + * and only if the system-wide TLS priority string + * has not been initialized yet. + * The intended usage is to provide applications with a way + * to expressly deviate from the distribution or site defaults + * inherited from the configuration file. + * The modification is composable with further modifications + * performed through the priority string mechanism. + * + * This function is not thread-safe and is intended to be called + * in the main thread at the beginning of the process execution. + * + * Returns: 0 on success or negative error code otherwise. + * + * Since: 3.7.3 + */ +int /* allowlisting-only */ /* not thread-safe */ +gnutls_protocol_set_enabled(gnutls_protocol_t version, unsigned int enabled) +{ +#ifndef DISABLE_SYSTEM_CONFIG + int ret; + ret = gnutls_rwlock_wrlock(&system_wide_config_rwlock); + if (ret < 0) { + (void)gnutls_rwlock_unlock(&system_wide_config_rwlock); + return gnutls_assert_val(ret); + } + if (!system_wide_config_is_malleable()) { + (void)gnutls_rwlock_unlock(&system_wide_config_rwlock); + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + + if (enabled) { + ret = cfg_versions_add(&system_wide_config, version); + } else { + ret = cfg_versions_remove(&system_wide_config, version); + } + + (void)gnutls_rwlock_unlock(&system_wide_config_rwlock); + return ret; +#else + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); +#endif +} + +/** + * gnutls_ecc_curve_set_enabled: + * @curve: is an ECC curve + * @enabled: whether to enable the curve + * + * Modify the previous system wide setting that marked @curve as + * enabled or disabled. Calling this fuction is allowed + * only if allowlisting mode is set in the configuration file, + * and only if the system-wide TLS priority string + * has not been initialized yet. + * The intended usage is to provide applications with a way + * to expressly deviate from the distribution or site defaults + * inherited from the configuration file. + * The modification is composable with further modifications + * performed through the priority string mechanism. + * + * This function is not thread-safe and is intended to be called + * in the main thread at the beginning of the process execution. + * + * Returns: 0 on success or negative error code otherwise. + * + * Since: 3.7.3 + */ +int +gnutls_ecc_curve_set_enabled(gnutls_ecc_curve_t curve, unsigned int enabled) +{ +#ifndef DISABLE_SYSTEM_CONFIG + int ret; + ret = gnutls_rwlock_wrlock(&system_wide_config_rwlock); + if (ret < 0) { + (void)gnutls_rwlock_unlock(&system_wide_config_rwlock); + return gnutls_assert_val(ret); + } + if (!system_wide_config_is_malleable()) { + (void)gnutls_rwlock_unlock(&system_wide_config_rwlock); + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + + if (enabled) { + ret = cfg_ecc_curves_add(&system_wide_config, curve); + } else { + ret = cfg_ecc_curves_remove(&system_wide_config, curve); + } + + (void)gnutls_rwlock_unlock(&system_wide_config_rwlock); + return ret; +#else + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); +#endif +} |