diff options
author | Daiki Ueno <ueno@gnu.org> | 2021-05-06 12:41:40 +0200 |
---|---|---|
committer | Daiki Ueno <ueno@gnu.org> | 2021-11-29 13:21:53 +0100 |
commit | 0ecce7191dfd78387f2994253d37ed1df50d563d (patch) | |
tree | fb1d66e9329cdad3ef617c02b96c77aca1c8dd3e /lib | |
parent | ee3af8d6e863bd958cbe7468f9cbe09d803f4e92 (diff) | |
download | gnutls-0ecce7191dfd78387f2994253d37ed1df50d563d.tar.gz |
priority: support allowlisting in configuration file
This adds a new mode of interpreting the [overrides] section. If
"override-mode" is set to "allowlisting" in the [global] section, all
the algorithms (hashes, signature algorithms, curves, and versions)
are initially marked as insecure/disabled. Then the user can enable
them by specifying allowlisting keywords such as "secure-hash" in the
[overrides] section.
Signed-off-by: Daiki Ueno <ueno@gnu.org>
Co-authored-by: Alexander Sosedkin <asosedkin@redhat.com>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/algorithms.h | 14 | ||||
-rw-r--r-- | lib/algorithms/ecc.c | 47 | ||||
-rw-r--r-- | lib/algorithms/groups.c | 18 | ||||
-rw-r--r-- | lib/algorithms/mac.c | 67 | ||||
-rw-r--r-- | lib/algorithms/protocols.c | 69 | ||||
-rw-r--r-- | lib/algorithms/sign.c | 120 | ||||
-rw-r--r-- | lib/gnutls_int.h | 3 | ||||
-rw-r--r-- | lib/includes/gnutls/gnutls.h.in | 11 | ||||
-rw-r--r-- | lib/libgnutls.map | 12 | ||||
-rw-r--r-- | lib/priority.c | 772 |
10 files changed, 934 insertions, 199 deletions
diff --git a/lib/algorithms.h b/lib/algorithms.h index 2f5366db6b..da72403fba 100644 --- a/lib/algorithms.h +++ b/lib/algorithms.h @@ -349,11 +349,23 @@ int _gnutls_ecc_curve_mark_disabled(gnutls_ecc_curve_t curve); int _gnutls_sign_mark_insecure(gnutls_sign_algorithm_t, hash_security_level_t); int _gnutls_digest_mark_insecure(gnutls_digest_algorithm_t dig); unsigned _gnutls_digest_is_insecure(gnutls_digest_algorithm_t dig); +bool _gnutls_digest_is_insecure2(gnutls_digest_algorithm_t dig, unsigned flags); +const gnutls_protocol_t *_gnutls_protocol_list(void); int _gnutls_version_mark_disabled(gnutls_protocol_t version); gnutls_protocol_t _gnutls_protocol_get_id_if_supported(const char *name); +/* these functions are for revertible settings, meaning that algorithms marked + * as disabled/insecure with mark_*_all functions can be re-enabled with + * mark_{enabled,secure} functions */ +void _gnutls_ecc_curve_mark_disabled_all(void); +void _gnutls_sign_mark_insecure_all(hash_security_level_t level); +void _gnutls_digest_mark_insecure_all(void); +void _gnutls_version_mark_revertible_all(void); + #define GNUTLS_SIGN_FLAG_TLS13_OK 1 /* if it is ok to use under TLS1.3 */ #define GNUTLS_SIGN_FLAG_CRT_VRFY_REVERSE (1 << 1) /* reverse order of bytes in CrtVrfy signature */ +#define GNUTLS_SIGN_FLAG_INSECURE_REVERTIBLE (1 << 2) +#define GNUTLS_SIGN_FLAG_ALLOW_INSECURE_REVERTIBLE (1 << 3) struct gnutls_sign_entry_st { const char *name; const char *oid; @@ -448,6 +460,7 @@ typedef struct gnutls_ecc_curve_entry_st { unsigned sig_size; /* the size of curve signatures in bytes (EdDSA) */ unsigned gost_curve; bool supported; + bool supported_revertible; gnutls_group_t group; } gnutls_ecc_curve_entry_st; @@ -459,6 +472,7 @@ unsigned _gnutls_ecc_curve_is_supported(gnutls_ecc_curve_t); gnutls_group_t _gnutls_ecc_curve_get_group(gnutls_ecc_curve_t); const gnutls_group_entry_st *_gnutls_tls_id_to_group(unsigned num); const gnutls_group_entry_st * _gnutls_id_to_group(unsigned id); +gnutls_group_t _gnutls_group_get_id(const char *name); gnutls_ecc_curve_t _gnutls_ecc_bits_to_curve(gnutls_pk_algorithm_t pk, int bits); #define MAX_ECC_CURVE_SIZE 66 diff --git a/lib/algorithms/ecc.c b/lib/algorithms/ecc.c index f5fb8ac76f..d2c0b3585b 100644 --- a/lib/algorithms/ecc.c +++ b/lib/algorithms/ecc.c @@ -353,13 +353,58 @@ gnutls_ecc_curve_t gnutls_ecc_curve_get_id(const char *name) return ret; } +/* This is only called by cfg_apply in priority.c, in blocklisting mode. */ int _gnutls_ecc_curve_mark_disabled(gnutls_ecc_curve_t curve) { gnutls_ecc_curve_entry_st *p; for(p = ecc_curves; p->name != NULL; p++) { if (p->id == curve) { - p->supported = 0; + p->supported = false; + return 0; + } + } + + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); +} + +/* This is only called by cfg_apply in priority.c, in allowlisting mode. */ +void _gnutls_ecc_curve_mark_disabled_all(void) +{ + gnutls_ecc_curve_entry_st *p; + + for(p = ecc_curves; p->name != NULL; p++) { + p->supported = false; + p->supported_revertible = true; + } +} + +/** + * 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. This only has effect when the curve is + * enabled through the allowlisting mode in the configuration file, or + * when the setting is modified with a prior call to this function. + * + * 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) +{ + gnutls_ecc_curve_entry_st *p; + + for(p = ecc_curves; p->name != NULL; p++) { + if (p->id == curve) { + if (!p->supported_revertible) { + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + p->supported = enabled; return 0; } } diff --git a/lib/algorithms/groups.c b/lib/algorithms/groups.c index d4b77beb2a..d8bf95824f 100644 --- a/lib/algorithms/groups.c +++ b/lib/algorithms/groups.c @@ -276,6 +276,24 @@ gnutls_group_t gnutls_group_get_id(const char *name) return ret; } + +/* Similar to gnutls_group_get_id, except that it does not check if + * the curve is supported. + */ +gnutls_group_t _gnutls_group_get_id(const char *name) +{ + gnutls_group_t ret = GNUTLS_GROUP_INVALID; + + GNUTLS_GROUP_LOOP( + if (c_strcasecmp(p->name, name) == 0) { + ret = p->id; + break; + } + ); + + return ret; +} + /** * gnutls_group_get_name: * @group: is an element from %gnutls_group_t diff --git a/lib/algorithms/mac.c b/lib/algorithms/mac.c index 57dfca95de..a2c66e76bb 100644 --- a/lib/algorithms/mac.c +++ b/lib/algorithms/mac.c @@ -291,6 +291,7 @@ gnutls_digest_algorithm_t gnutls_digest_get_id(const char *name) return ret; } +/* This is only called by cfg_apply in priority.c, in blocklisting mode. */ int _gnutls_digest_mark_insecure(gnutls_digest_algorithm_t dig) { #ifndef DISABLE_SYSTEM_CONFIG @@ -307,6 +308,57 @@ int _gnutls_digest_mark_insecure(gnutls_digest_algorithm_t dig) return GNUTLS_E_INVALID_REQUEST; } +/* This is only called by cfg_apply in priority.c, in allowlisting mode. */ +void _gnutls_digest_mark_insecure_all(void) +{ +#ifndef DISABLE_SYSTEM_CONFIG + mac_entry_st *p; + + for(p = hash_algorithms; p->name != NULL; p++) { + p->flags |= GNUTLS_MAC_FLAG_PREIMAGE_INSECURE_REVERTIBLE | + GNUTLS_MAC_FLAG_PREIMAGE_INSECURE; + } + +#endif +} + +/** + * 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 + mac_entry_st *p; + + for(p = hash_algorithms; p->name != NULL; p++) { + if (p->oid != NULL && p->id == (gnutls_mac_algorithm_t)dig) { + if (!(p->flags & GNUTLS_MAC_FLAG_PREIMAGE_INSECURE_REVERTIBLE)) { + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + if (secure) { + p->flags &= ~GNUTLS_MAC_FLAG_PREIMAGE_INSECURE; + } else { + p->flags |= GNUTLS_MAC_FLAG_PREIMAGE_INSECURE; + } + return 0; + } + } + +#endif + return GNUTLS_E_INVALID_REQUEST; +} + unsigned _gnutls_digest_is_insecure(gnutls_digest_algorithm_t dig) { const mac_entry_st *p; @@ -320,6 +372,21 @@ unsigned _gnutls_digest_is_insecure(gnutls_digest_algorithm_t dig) return 1; } +bool _gnutls_digest_is_insecure2(gnutls_digest_algorithm_t dig, unsigned flags) +{ + const mac_entry_st *p; + + for(p = hash_algorithms; p->name != NULL; p++) { + if (p->oid != NULL && p->id == (gnutls_mac_algorithm_t)dig) { + return (p->flags & GNUTLS_MAC_FLAG_PREIMAGE_INSECURE && + !(flags & GNUTLS_MAC_FLAG_ALLOW_INSECURE_REVERTIBLE && + p->flags & GNUTLS_MAC_FLAG_PREIMAGE_INSECURE_REVERTIBLE)); + } + } + + return true; +} + /** * gnutls_mac_get_id: * @name: is a MAC algorithm name diff --git a/lib/algorithms/protocols.c b/lib/algorithms/protocols.c index 4283cd0388..b0f3e0bc30 100644 --- a/lib/algorithms/protocols.c +++ b/lib/algorithms/protocols.c @@ -198,6 +198,7 @@ version_is_valid_for_session(gnutls_session_t session, return 0; } +/* This is only called by cfg_apply in priority.c, in blocklisting mode. */ int _gnutls_version_mark_disabled(gnutls_protocol_t version) { #ifndef DISABLE_SYSTEM_CONFIG @@ -205,7 +206,54 @@ int _gnutls_version_mark_disabled(gnutls_protocol_t version) for (p = sup_versions; p->name != NULL; p++) if (p->id == version) { - p->supported = 0; + p->supported = false; + return 0; + } + +#endif + return GNUTLS_E_INVALID_REQUEST; +} + +/* This is only called by cfg_apply in priority.c, in allowlisting mode. */ +void _gnutls_version_mark_revertible_all(void) +{ +#ifndef DISABLE_SYSTEM_CONFIG + version_entry_st *p; + + for (p = sup_versions; p->name != NULL; p++) { + p->supported_revertible = true; + } + +#endif +} + +/** + * gnutls_protocol_set_enabled: + * @version: is a (gnutls) version number + * @enabled: whether to enable the protocol + * + * Mark the previous system wide setting that marked @version as + * enabled or disabled. This only has effect when the version is + * enabled through the allowlisting mode in the configuration file, or + * when the setting is modified with a prior call to this function. + * + * Returns: 0 on success or negative error code otherwise. + * + * Since: 3.7.3 + */ +int +gnutls_protocol_set_enabled(gnutls_protocol_t version, + unsigned int enabled) +{ +#ifndef DISABLE_SYSTEM_CONFIG + version_entry_st *p; + + for (p = sup_versions; p->name != NULL; p++) + if (p->id == version) { + if (!p->supported_revertible) { + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + p->supported = enabled; return 0; } @@ -469,6 +517,25 @@ const gnutls_protocol_t *gnutls_protocol_list(void) return supported_protocols; } +/* Return all versions, including non-supported ones. + */ +const gnutls_protocol_t *_gnutls_protocol_list(void) +{ + const version_entry_st *p; + static gnutls_protocol_t protocols[MAX_ALGOS] = { 0 }; + + if (protocols[0] == 0) { + int i = 0; + + for (p = sup_versions; p->name != NULL; p++) { + protocols[i++] = p->id; + } + protocols[i++] = 0; + } + + return protocols; +} + /* Returns a version number given the major and minor numbers. */ gnutls_protocol_t _gnutls_version_get(uint8_t major, uint8_t minor) diff --git a/lib/algorithms/sign.c b/lib/algorithms/sign.c index 4c5619454f..543bd19bb5 100644 --- a/lib/algorithms/sign.c +++ b/lib/algorithms/sign.c @@ -453,15 +453,22 @@ unsigned gnutls_sign_is_secure(gnutls_sign_algorithm_t algorithm) bool _gnutls_sign_is_secure2(const gnutls_sign_entry_st *se, unsigned int flags) { - if (se->hash != GNUTLS_DIG_UNKNOWN && _gnutls_digest_is_insecure(se->hash)) - return gnutls_assert_val(0); + if (se->hash != GNUTLS_DIG_UNKNOWN && + _gnutls_digest_is_insecure2(se->hash, + flags & GNUTLS_SIGN_FLAG_ALLOW_INSECURE_REVERTIBLE ? + GNUTLS_MAC_FLAG_ALLOW_INSECURE_REVERTIBLE : + 0)) { + return gnutls_assert_val(false); + } - if (flags & GNUTLS_SIGN_FLAG_SECURE_FOR_CERTS) - return (se->slevel==_SECURE)?1:0; - else - return (se->slevel==_SECURE || se->slevel == _INSECURE_FOR_CERTS)?1:0; + return (flags & GNUTLS_SIGN_FLAG_SECURE_FOR_CERTS ? + se->slevel == _SECURE : + (se->slevel == _SECURE || se->slevel == _INSECURE_FOR_CERTS)) || + (flags & GNUTLS_SIGN_FLAG_ALLOW_INSECURE_REVERTIBLE && + se->flags & GNUTLS_SIGN_FLAG_INSECURE_REVERTIBLE); } +/* This is only called by cfg_apply in priority.c, in blocklisting mode. */ int _gnutls_sign_mark_insecure(gnutls_sign_algorithm_t sign, hash_security_level_t level) { #ifndef DISABLE_SYSTEM_CONFIG @@ -472,6 +479,7 @@ int _gnutls_sign_mark_insecure(gnutls_sign_algorithm_t sign, hash_security_level for(p = sign_algorithms; p->name != NULL; p++) { if (p->id && p->id == sign) { + if (p->slevel < level) p->slevel = level; return 0; } @@ -480,6 +488,106 @@ int _gnutls_sign_mark_insecure(gnutls_sign_algorithm_t sign, hash_security_level return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); } +/* This is only called by cfg_apply in priority.c, in allowlisting mode. */ +void _gnutls_sign_mark_insecure_all(hash_security_level_t level) +{ +#ifndef DISABLE_SYSTEM_CONFIG + gnutls_sign_entry_st *p; + + for(p = sign_algorithms; p->name != NULL; p++) { + if (p->slevel < level) + p->slevel = level; + p->flags |= GNUTLS_SIGN_FLAG_INSECURE_REVERTIBLE; + } +#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. This only has effect when the algorithm is marked as + * secure through the allowlisting mode in the configuration file, or + * when the setting is modified with a prior call to this function. + * + * 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. + * + * Since: 3.7.3 + */ +int +gnutls_sign_set_secure(gnutls_sign_algorithm_t sign, + unsigned int secure) +{ +#ifndef DISABLE_SYSTEM_CONFIG + gnutls_sign_entry_st *p; + + for(p = sign_algorithms; p->name != NULL; p++) { + if (p->id && p->id == sign) { + if (!(p->flags & GNUTLS_SIGN_FLAG_INSECURE_REVERTIBLE)) { + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + if (secure) { + if (p->slevel > _INSECURE_FOR_CERTS) { + p->slevel = _INSECURE_FOR_CERTS; + } + } else { + p->slevel = _INSECURE; + } + return 0; + } + } +#endif + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); +} + +/** + * 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. This only has effect when + * the algorithm is marked as secure through the allowlisting mode in + * the configuration file, or when the setting is modified with a + * prior call to this function. + * + * 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. + * + * Since: 3.7.3 + */ +int +gnutls_sign_set_secure_for_certs(gnutls_sign_algorithm_t sign, + unsigned int secure) +{ +#ifndef DISABLE_SYSTEM_CONFIG + gnutls_sign_entry_st *p; + + for(p = sign_algorithms; p->name != NULL; p++) { + if (p->id && p->id == sign) { + if (!(p->flags & GNUTLS_SIGN_FLAG_INSECURE_REVERTIBLE)) { + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + if (secure) { + p->slevel = _SECURE; + } else { + if (p->slevel < _INSECURE_FOR_CERTS) { + p->slevel = _INSECURE_FOR_CERTS; + } + } + return 0; + } + } +#endif + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); +} + /** * gnutls_sign_is_secure2: * @algorithm: is a sign algorithm diff --git a/lib/gnutls_int.h b/lib/gnutls_int.h index 88f0c28a00..1dbe404857 100644 --- a/lib/gnutls_int.h +++ b/lib/gnutls_int.h @@ -665,6 +665,8 @@ typedef struct gnutls_group_entry_st { #define GNUTLS_MAC_FLAG_PREIMAGE_INSECURE 1 /* if this algorithm should not be trusted for pre-image attacks */ #define GNUTLS_MAC_FLAG_CONTINUOUS_MAC (1 << 1) /* if this MAC should be used in a 'continuous' way in TLS */ +#define GNUTLS_MAC_FLAG_PREIMAGE_INSECURE_REVERTIBLE (1 << 2) /* if this algorithm should not be trusted for pre-image attacks, but can be enabled through API */ +#define GNUTLS_MAC_FLAG_ALLOW_INSECURE_REVERTIBLE (1 << 3) /* when checking with _gnutls_digest_is_insecure2, don't treat revertible setting as fatal */ /* This structure is used both for MACs and digests */ typedef struct mac_entry_st { @@ -688,6 +690,7 @@ typedef struct { uint8_t minor; /* defined by the protocol */ transport_t transport; /* Type of transport, stream or datagram */ bool supported; /* 0 not supported, > 0 is supported */ + bool supported_revertible; bool explicit_iv; bool extensions; /* whether it supports extensions */ bool selectable_sighash; /* whether signatures can be selected */ diff --git a/lib/includes/gnutls/gnutls.h.in b/lib/includes/gnutls/gnutls.h.in index d69b29b443..1e883aa8eb 100644 --- a/lib/includes/gnutls/gnutls.h.in +++ b/lib/includes/gnutls/gnutls.h.in @@ -1438,6 +1438,17 @@ const char * gnutls_mac_algorithm_t * mac, gnutls_protocol_t * min_version); + /* functions for run-time enablement of algorithms */ +int gnutls_ecc_curve_set_enabled(gnutls_ecc_curve_t curve, + unsigned int enabled); +int gnutls_sign_set_secure(gnutls_sign_algorithm_t sign, unsigned int secure); +int gnutls_sign_set_secure_for_certs(gnutls_sign_algorithm_t sign, + unsigned int secure); +int gnutls_digest_set_secure(gnutls_digest_algorithm_t dig, + unsigned int secure); +int gnutls_protocol_set_enabled(gnutls_protocol_t version, + unsigned int enabled); + /* error functions */ int gnutls_error_is_fatal(int error) __GNUTLS_CONST__; int gnutls_error_to_alert(int err, int *level); diff --git a/lib/libgnutls.map b/lib/libgnutls.map index 0e6b379314..dc50c6dba9 100644 --- a/lib/libgnutls.map +++ b/lib/libgnutls.map @@ -1355,6 +1355,18 @@ GNUTLS_3_7_2 *; } GNUTLS_3_7_0; +GNUTLS_3_7_3 +{ + global: + gnutls_ecc_curve_set_enabled; + gnutls_sign_set_secure; + gnutls_sign_set_secure_for_certs; + gnutls_digest_set_secure; + gnutls_protocol_set_enabled; + local: + *; +} GNUTLS_3_7_2; + GNUTLS_FIPS140_3_4 { global: gnutls_cipher_self_test; diff --git a/lib/priority.c b/lib/priority.c index 4ad5c90462..54d7b1bb45 100644 --- a/lib/priority.c +++ b/lib/priority.c @@ -53,6 +53,13 @@ /* 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); @@ -701,6 +708,7 @@ gnutls_priority_set(gnutls_session_t session, gnutls_priority_t priority) #define LEVEL_SUITEB128 "SUITEB128" #define LEVEL_SUITEB192 "SUITEB192" #define LEVEL_LEGACY "LEGACY" +#define LEVEL_SYSTEM "SYSTEM" struct priority_groups_st { const char *name; @@ -1000,17 +1008,32 @@ static void dummy_func(gnutls_priority_t c) #include <priority_options.h> -/* Configuration read from the config file */ struct cfg { - gnutls_certificate_verification_profiles_t verification_profile; + bool allowlisting; + name_val_array_t priority_strings; - unsigned default_priority_string; - unsigned disabled_ciphers[MAX_ALGOS+1]; - unsigned disabled_macs[MAX_ALGOS+1]; - unsigned disabled_groups[MAX_ALGOS+1]; - unsigned disabled_kxs[MAX_ALGOS+1]; + 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]; }; +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; @@ -1020,18 +1043,17 @@ 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 static void _clear_default_system_priority(void) { - if (system_wide_config.default_priority_string) { - gnutls_free(_gnutls_default_priority_string); - _gnutls_default_priority_string = DEFAULT_PRIORITY_STRING; - system_wide_config.default_priority_string = 0; - } + 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) @@ -1059,15 +1081,8 @@ static char *clear_spaces(const char *str, char out[MAX_ALGO_NAME]) return out; } -struct cfg { - name_val_array_t priority_strings; - 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]; +struct ini_ctx { + struct cfg cfg; gnutls_digest_algorithm_t *hashes; size_t hashes_size; @@ -1082,89 +1097,207 @@ struct cfg { }; static inline void -cfg_deinit(struct cfg *cfg) +ini_ctx_deinit(struct ini_ctx *ctx) { - if (cfg->priority_strings) { - _name_val_array_clear(&cfg->priority_strings); - } - gnutls_free(cfg->default_priority_string); - gnutls_free(cfg->hashes); - gnutls_free(cfg->sigs); - gnutls_free(cfg->sigs_for_cert); - gnutls_free(cfg->versions); - gnutls_free(cfg->curves); + 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; + 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)); } static inline int -cfg_apply(struct cfg *cfg) +cfg_apply(struct cfg *cfg, struct ini_ctx *ctx) { size_t i; - system_wide_verification_profile = cfg->verification_profile; - - system_wide_priority_strings = cfg->priority_strings; - cfg->priority_strings = NULL; + cfg_steal(cfg, &ctx->cfg); if (cfg->default_priority_string) { - _clear_default_system_priority(); _gnutls_default_priority_string = cfg->default_priority_string; - cfg->default_priority_string = NULL; - system_wide_default_priority_string = 1; } - memcpy(system_wide_disabled_ciphers, cfg->ciphers, sizeof(cfg->ciphers)); - memcpy(system_wide_disabled_macs, cfg->macs, sizeof(cfg->macs)); - memcpy(system_wide_disabled_groups, cfg->groups, sizeof(cfg->groups)); - memcpy(system_wide_disabled_kxs, cfg->kxs, sizeof(cfg->kxs)); + if (cfg->allowlisting) { + unsigned tls_sig_sem = 0; + size_t j; - for (i = 0; i < cfg->hashes_size; i++) { - int ret = _gnutls_digest_mark_insecure(cfg->hashes[i]); - if (unlikely(ret < 0)) { - return ret; + _gnutls_digest_mark_insecure_all(); + for (i = 0; i < ctx->hashes_size; i++) { + int ret = gnutls_digest_set_secure(ctx->hashes[i], 1); + if (unlikely(ret < 0)) { + return ret; + } } - } - - for (i = 0; i < cfg->sigs_size; i++) { - int ret = _gnutls_sign_mark_insecure(cfg->sigs[i], _INSECURE); - if (unlikely(ret < 0)) { - return ret; + _gnutls_sign_mark_insecure_all(_INSECURE); + for (i = 0; i < ctx->sigs_size; i++) { + int ret = gnutls_sign_set_secure(ctx->sigs[i], 1); + if (unlikely(ret < 0)) { + return ret; + } } - } + for (i = 0; i < ctx->sigs_for_cert_size; i++) { + int ret = gnutls_sign_set_secure_for_certs(ctx->sigs_for_cert[i], + 1); + if (unlikely(ret < 0)) { + return ret; + } + } + _gnutls_version_mark_revertible_all(); + for (i = 0, j = 0; i < ctx->versions_size; i++) { + const version_entry_st *vers; + vers = version_to_entry(ctx->versions[i]); + if (vers && vers->supported) { + tls_sig_sem |= vers->tls_sig_sem; + cfg->versions[j++] = vers->id; + } + } + _gnutls_ecc_curve_mark_disabled_all(); + for (i = 0; i < ctx->curves_size; i++) { + int ret = gnutls_ecc_curve_set_enabled(ctx->curves[i], 1); + if (unlikely(ret < 0)) { + return ret; + } + } + for (i = 0, j = 0; i < ctx->sigs_size; i++) { + const gnutls_sign_entry_st *se; - for (i = 0; i < cfg->sigs_for_cert_size; i++) { - int ret = _gnutls_sign_mark_insecure(cfg->sigs_for_cert[i], _INSECURE_FOR_CERTS); - if (unlikely(ret < 0)) { - return ret; + se = _gnutls_sign_to_entry(ctx->sigs[i]); + if (se != NULL && se->aid.tls_sem & tls_sig_sem && + _gnutls_sign_is_secure2(se, 0)) { + cfg->sigs[j++] = se->id; + } + } + } else { + for (i = 0; i < ctx->hashes_size; i++) { + int ret = _gnutls_digest_mark_insecure(ctx->hashes[i]); + if (unlikely(ret < 0)) { + return ret; + } + } + for (i = 0; i < ctx->sigs_size; i++) { + int 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++) { + int 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++) { + int ret = _gnutls_version_mark_disabled(ctx->versions[i]); + if (unlikely(ret < 0)) { + return ret; + } + } + for (i = 0; i < ctx->curves_size; i++) { + int ret = _gnutls_ecc_curve_mark_disabled(ctx->curves[i]); + if (unlikely(ret < 0)) { + return ret; + } } } - for (i = 0; i < cfg->versions_size; i++) { - int ret = _gnutls_version_mark_disabled(cfg->versions[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(value, "allowlist") == 0) { + cfg->allowlisting = true; + } else if (c_strcasecmp(value, "blocklist") == 0) { + cfg->allowlisting = false; + } else { + _gnutls_debug_log("cfg: unknown override 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; } } - for (i = 0; i < cfg->curves_size; i++) { - int ret = _gnutls_ecc_curve_mark_disabled(cfg->curves[i]); - if (unlikely(ret < 0)) { - return ret; - } + 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 0; + 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) +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 cfg *cfg = ctx; + 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. */ @@ -1177,7 +1310,12 @@ static int cfg_ini_handler(void *ctx, const char *section, const char *name, con if (ret < 0) return 0; } else if (c_strcasecmp(section, OVERRIDES_SECTION)==0) { - if (c_strcasecmp(name, "default-priority-string")==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; @@ -1195,13 +1333,19 @@ static int cfg_ini_handler(void *ctx, const char *section, const char *name, con if (fail_on_invalid_config) return 0; } - } else if (c_strcasecmp(name, "insecure-hash")==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); - _gnutls_debug_log("cfg: marking hash %s as insecure\n", - p); + 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) { @@ -1211,27 +1355,38 @@ static int cfg_ini_handler(void *ctx, const char *section, const char *name, con return 0; goto exit; } - tmp = _gnutls_reallocarray(cfg->hashes, - cfg->hashes_size + 1, + tmp = _gnutls_reallocarray(ctx->hashes, + ctx->hashes_size + 1, sizeof(gnutls_digest_algorithm_t)); if (!tmp) { - _gnutls_debug_log("cfg: failed marking hash %s as insecure\n", - p); + 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; } - cfg->hashes = tmp; - cfg->hashes[cfg->hashes_size] = dig; - cfg->hashes_size++; - } else if (c_strcasecmp(name, "insecure-sig")==0) { + 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); - _gnutls_debug_log("cfg: marking signature %s as insecure\n", - p); + 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) { @@ -1241,27 +1396,38 @@ static int cfg_ini_handler(void *ctx, const char *section, const char *name, con return 0; goto exit; } - tmp = _gnutls_reallocarray(cfg->sigs, - cfg->sigs_size + 1, + tmp = _gnutls_reallocarray(ctx->sigs, + ctx->sigs_size + 1, sizeof(gnutls_sign_algorithm_t)); if (!tmp) { - _gnutls_debug_log("cfg: failed marking signature %s as insecure\n", - p); + 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; } - cfg->sigs = tmp; - cfg->sigs[cfg->sigs_size] = sig; - cfg->sigs_size++; - } else if (c_strcasecmp(name, "insecure-sig-for-cert")==0) { + 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); - _gnutls_debug_log("cfg: marking signature %s as insecure for certs\n", - p); + 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) { @@ -1271,27 +1437,38 @@ static int cfg_ini_handler(void *ctx, const char *section, const char *name, con return 0; goto exit; } - tmp = _gnutls_reallocarray(cfg->sigs_for_cert, - cfg->sigs_for_cert_size + 1, + tmp = _gnutls_reallocarray(ctx->sigs_for_cert, + ctx->sigs_for_cert_size + 1, sizeof(gnutls_sign_algorithm_t)); if (!tmp) { - _gnutls_debug_log("cfg: failed marking signature %s as insecure for certs\n", - p); + 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; } - cfg->sigs_for_cert = tmp; - cfg->sigs_for_cert[cfg->sigs_for_cert_size] = sig; - cfg->sigs_for_cert_size++; - } else if (c_strcasecmp(name, "disabled-version")==0) { + 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); - _gnutls_debug_log("cfg: disabling version %s\n", - p); + 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) { @@ -1301,27 +1478,38 @@ static int cfg_ini_handler(void *ctx, const char *section, const char *name, con return 0; goto exit; } - tmp = _gnutls_reallocarray(cfg->versions, - cfg->versions_size + 1, + tmp = _gnutls_reallocarray(ctx->versions, + ctx->versions_size + 1, sizeof(gnutls_protocol_t)); if (!tmp) { - _gnutls_debug_log("cfg: failed disabling version %s\n", - p); + 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; } - cfg->versions = tmp; - cfg->versions[cfg->versions_size] = prot; - cfg->versions_size++; - } else if (c_strcasecmp(name, "disabled-curve")==0) { + 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); - _gnutls_debug_log("cfg: disabling curve %s\n", - p); + 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) { @@ -1331,20 +1519,25 @@ static int cfg_ini_handler(void *ctx, const char *section, const char *name, con return 0; goto exit; } - tmp = _gnutls_reallocarray(cfg->curves, - cfg->curves_size + 1, + tmp = _gnutls_reallocarray(ctx->curves, + ctx->curves_size + 1, sizeof(gnutls_ecc_curve_t)); if (!tmp) { - _gnutls_debug_log("cfg: failed disabling curve %s\n", - p); + 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; } - cfg->curves = tmp; - cfg->curves[cfg->curves_size] = curve; - cfg->curves_size++; + 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); @@ -1358,13 +1551,19 @@ static int cfg_ini_handler(void *ctx, const char *section, const char *name, con } cfg->verification_profile = profile; - } else if (c_strcasecmp(name, "tls-disabled-cipher")==0) { + } 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); - _gnutls_debug_log("cfg: disabling cipher %s for TLS\n", - p); + 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) { @@ -1380,8 +1579,13 @@ static int cfg_ini_handler(void *ctx, const char *section, const char *name, con i++; if (i > MAX_ALGOS-1) { - _gnutls_debug_log("cfg: too many (%d) disabled ciphers from %s\n", - i, name); + 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; @@ -1389,13 +1593,19 @@ static int cfg_ini_handler(void *ctx, const char *section, const char *name, con cfg->ciphers[i] = algo; cfg->ciphers[i+1] = 0; - } else if (c_strcasecmp(name, "tls-disabled-mac")==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); - _gnutls_debug_log("cfg: disabling MAC %s for TLS\n", - p); + 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) { @@ -1411,26 +1621,37 @@ static int cfg_ini_handler(void *ctx, const char *section, const char *name, con i++; if (i > MAX_ALGOS-1) { - _gnutls_debug_log("cfg: too many (%d) disabled MACs from %s\n", - i, name); + 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) { + } 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 (strlen(p) > 6) - p += 6; // skip GROUP- + if (c_strncasecmp(p, "GROUP-", 6) == 0) + p += 6; - _gnutls_debug_log("cfg: disabling group %s for TLS\n", - p); + 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); + algo = _gnutls_group_get_id(p); if (algo == 0) { _gnutls_debug_log("cfg: unknown group %s listed at %s\n", p, name); @@ -1444,21 +1665,32 @@ static int cfg_ini_handler(void *ctx, const char *section, const char *name, con i++; if (i > MAX_ALGOS-1) { - _gnutls_debug_log("cfg: too many (%d) disabled groups from %s\n", - i, name); + 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) { + } else if (c_strcasecmp(name, "tls-disabled-kx") == 0 || + c_strcasecmp(name, "tls-enabled-kx") == 0) { unsigned algo; p = clear_spaces(value, str); - _gnutls_debug_log("cfg: disabling key exchange %s for TLS\n", - p); + 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) { @@ -1474,8 +1706,13 @@ static int cfg_ini_handler(void *ctx, const char *section, const char *name, con i++; if (i > MAX_ALGOS-1) { - _gnutls_debug_log("cfg: too many (%d) disabled key exchanges from %s\n", - i, name); + 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; @@ -1487,7 +1724,7 @@ static int cfg_ini_handler(void *ctx, const char *section, const char *name, con if (fail_on_invalid_config) return 0; } - } else { + } else if (c_strcasecmp(section, GLOBAL_SECTION) != 0) { _gnutls_debug_log("cfg: unknown section %s\n", section); if (fail_on_invalid_config) @@ -1498,12 +1735,124 @@ static int cfg_ini_handler(void *ctx, const char *section, const char *name, con return 1; } +static int +update_system_wide_priority_string(void) +{ + 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; + } + } + + 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(void) { int ret, err = 0; struct stat sb; FILE *fp; - struct cfg cfg; + struct ini_ctx ctx; ret = gnutls_rwlock_rdlock(&system_wide_config_rwlock); if (ret < 0) { @@ -1542,23 +1891,50 @@ static int _gnutls_update_system_priorities(void) system_priority_file_loaded = 0; _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; } - memset(&cfg, 0, sizeof(cfg)); - err = ini_parse_file(fp, cfg_ini_handler, &cfg); + /* 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) { - cfg_deinit(&cfg); + ini_ctx_deinit(&ctx); _gnutls_debug_log("cfg: unable to parse: %s: %d\n", system_priority_file, err); goto out; } - cfg_apply(&cfg); - cfg_deinit(&cfg); + cfg_apply(&system_wide_config, &ctx); + ini_ctx_deinit(&ctx); + + if (system_wide_config.allowlisting) { + ret = update_system_wide_priority_string(); + 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; + } + } _gnutls_debug_log("cfg: loaded system priority %s mtime %lld\n", system_priority_file, @@ -1600,6 +1976,7 @@ void _gnutls_load_system_priorities(void) 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; } @@ -1689,9 +2066,13 @@ char *_gnutls_resolve_priorities(const char* priorities) gnutls_strerror(ret)); break; } - - p = _name_val_array_value(system_wide_config.priority_strings, - ss, ss_len); + 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)); @@ -1799,48 +2180,52 @@ static int set_ciphersuite_list(gnutls_priority_t priority_cache) return gnutls_assert_val(ret); } - /* disable key exchanges which are globally disabled */ - z = 0; - while (system_wide_config.disabled_kxs[z] != 0) { - for (i = j = 0; i < priority_cache->_kx.num_priorities; i++) { - if (priority_cache->_kx.priorities[i] != system_wide_config.disabled_kxs[z]) - priority_cache->_kx.priorities[j++] = priority_cache->_kx.priorities[i]; + /* 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++; } - priority_cache->_kx.num_priorities = j; - z++; - } - /* disable groups which are globally disabled */ - z = 0; - while (system_wide_config.disabled_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.disabled_groups[z]) - priority_cache->_supported_ecc.priorities[j++] = priority_cache->_supported_ecc.priorities[i]; + /* 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++; } - priority_cache->_supported_ecc.num_priorities = j; - z++; - } - /* disable ciphers which are globally disabled */ - z = 0; - while (system_wide_config.disabled_ciphers[z] != 0) { - for (i = j = 0; i < priority_cache->_cipher.num_priorities; i++) { - if (priority_cache->_cipher.priorities[i] != system_wide_config.disabled_ciphers[z]) - priority_cache->_cipher.priorities[j++] = priority_cache->_cipher.priorities[i]; + /* 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++; } - priority_cache->_cipher.num_priorities = j; - z++; - } - /* disable MACs which are globally disabled */ - z = 0; - while (system_wide_config.disabled_macs[z] != 0) { - for (i = j = 0; i < priority_cache->_mac.num_priorities; i++) { - if (priority_cache->_mac.priorities[i] != system_wide_config.disabled_macs[z]) - priority_cache->_mac.priorities[j++] = priority_cache->_mac.priorities[i]; + /* 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++; } - priority_cache->_mac.num_priorities = j; - z++; } for (j=0;j<priority_cache->_cipher.num_priorities;j++) { @@ -2008,7 +2393,9 @@ static int set_ciphersuite_list(gnutls_priority_t priority_cache) * 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, 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; @@ -2295,6 +2682,9 @@ gnutls_priority_init(gnutls_priority_t * priority_cache, (*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; @@ -2428,7 +2818,7 @@ gnutls_priority_init(gnutls_priority_t * priority_cache, _supported_groups_gost); } else { if ((algo = - gnutls_group_get_id + _gnutls_group_get_id (&broken_list[i][7])) != GNUTLS_GROUP_INVALID) fn(&(*priority_cache)-> |