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 | |
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>
-rw-r--r-- | NEWS | 10 | ||||
-rw-r--r-- | devel/libgnutls.abignore | 14 | ||||
-rw-r--r-- | devel/symbols.last | 6 | ||||
-rw-r--r-- | doc/Makefile.am | 10 | ||||
-rw-r--r-- | doc/cha-config.texi | 82 | ||||
-rw-r--r-- | doc/manpages/Makefile.am | 5 | ||||
-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 | ||||
-rw-r--r-- | tests/Makefile.am | 6 | ||||
-rwxr-xr-x | tests/system-override-curves-allowlist.sh | 113 | ||||
-rwxr-xr-x | tests/system-override-hash-allowlist.sh | 41 | ||||
-rwxr-xr-x | tests/system-override-sig-allowlist.sh | 43 | ||||
-rwxr-xr-x | tests/system-override-special-allowlist.sh | 177 | ||||
-rwxr-xr-x | tests/system-override-versions-allowlist.sh | 109 |
22 files changed, 1548 insertions, 201 deletions
@@ -7,6 +7,11 @@ See the end for copying conditions. * Version 3.7.3 (unreleased) +** libgnutls: The allowlisting configuration mode has been added to the system-wide + settings. In this mode, all the algorithms are initially marked as insecure + or disabled, while the applications can re-enable them either through the + [overrides] section of the configuration file or the new API (#1172). + ** certtool: Certtool can now generate, manipulate, and evaluate x25519 and x448 public keys, private keys, and certificates. ** libgnutls: disabling a hashing algorithm through "insecure-hash" @@ -22,6 +27,11 @@ See the end for copying conditions. ** API and ABI modifications: GNUTLS_PRIVKEY_FLAG_RSA_PSS_FIXED_SALT_LENGTH: new flag in gnutls_privkey_flags_t GNUTLS_VERIFY_RSA_PSS_FIXED_SALT_LENGTH: new flag in gnutls_certificate_verify_flags +gnutls_ecc_curve_set_enabled: Added. +gnutls_sign_set_secure: Added. +gnutls_sign_set_secure_for_certs: Added. +gnutls_digest_set_secure: Added. +gnutls_protocol_set_enabled: Added. * Version 3.7.2 (released 2021-05-29) diff --git a/devel/libgnutls.abignore b/devel/libgnutls.abignore index e43e5472f3..8afa94148a 100644 --- a/devel/libgnutls.abignore +++ b/devel/libgnutls.abignore @@ -57,3 +57,17 @@ return_type_name = unsigned int # The following should be removed in the new release, after updating the # abi-dump repository: +[suppress_function] +name = gnutls_digest_set_secure + +[suppress_function] +name = gnutls_ecc_curve_set_enabled + +[suppress_function] +name = gnutls_protocol_set_enabled + +[suppress_function] +name = gnutls_sign_set_secure + +[suppress_function] +name = gnutls_sign_set_secure_for_certs diff --git a/devel/symbols.last b/devel/symbols.last index 963287687b..7bef666350 100644 --- a/devel/symbols.last +++ b/devel/symbols.last @@ -13,6 +13,7 @@ GNUTLS_3_6_8@GNUTLS_3_6_8 GNUTLS_3_6_9@GNUTLS_3_6_9 GNUTLS_3_7_0@GNUTLS_3_7_0 GNUTLS_3_7_2@GNUTLS_3_7_2 +GNUTLS_3_7_3@GNUTLS_3_7_3 _gnutls_global_init_skip@GNUTLS_3_4 gnutls_aead_cipher_decrypt@GNUTLS_3_4 gnutls_aead_cipher_decryptv2@GNUTLS_3_6_10 @@ -195,6 +196,7 @@ gnutls_digest_get_id@GNUTLS_3_4 gnutls_digest_get_name@GNUTLS_3_4 gnutls_digest_get_oid@GNUTLS_3_4 gnutls_digest_list@GNUTLS_3_4 +gnutls_digest_set_secure@GNUTLS_3_7_3 gnutls_dtls_cookie_send@GNUTLS_3_4 gnutls_dtls_cookie_verify@GNUTLS_3_4 gnutls_dtls_get_data_mtu@GNUTLS_3_4 @@ -213,6 +215,7 @@ gnutls_ecc_curve_get_oid@GNUTLS_3_4 gnutls_ecc_curve_get_pk@GNUTLS_3_4 gnutls_ecc_curve_get_size@GNUTLS_3_4 gnutls_ecc_curve_list@GNUTLS_3_4 +gnutls_ecc_curve_set_enabled@GNUTLS_3_7_3 gnutls_encode_ber_digest_info@GNUTLS_3_4 gnutls_encode_gost_rs_value@GNUTLS_3_6_3 gnutls_encode_rs_value@GNUTLS_3_6_0 @@ -657,6 +660,7 @@ gnutls_protocol_get_id@GNUTLS_3_4 gnutls_protocol_get_name@GNUTLS_3_4 gnutls_protocol_get_version@GNUTLS_3_4 gnutls_protocol_list@GNUTLS_3_4 +gnutls_protocol_set_enabled@GNUTLS_3_7_3 gnutls_psk_allocate_client_credentials@GNUTLS_3_4 gnutls_psk_allocate_server_credentials@GNUTLS_3_4 gnutls_psk_client_get_hint@GNUTLS_3_4 @@ -806,6 +810,8 @@ gnutls_sign_get_pk_algorithm@GNUTLS_3_4 gnutls_sign_is_secure2@GNUTLS_3_6_0 gnutls_sign_is_secure@GNUTLS_3_4 gnutls_sign_list@GNUTLS_3_4 +gnutls_sign_set_secure@GNUTLS_3_7_3 +gnutls_sign_set_secure_for_certs@GNUTLS_3_7_3 gnutls_sign_supports_pk_algorithm@GNUTLS_3_6_0 gnutls_srp_1024_group_generator@GNUTLS_3_4 gnutls_srp_1024_group_prime@GNUTLS_3_4 diff --git a/doc/Makefile.am b/doc/Makefile.am index 562a85fa87..4f25bf0d5e 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -974,6 +974,8 @@ FUNCS += functions/gnutls_digest_get_oid FUNCS += functions/gnutls_digest_get_oid.short FUNCS += functions/gnutls_digest_list FUNCS += functions/gnutls_digest_list.short +FUNCS += functions/gnutls_digest_set_secure +FUNCS += functions/gnutls_digest_set_secure.short FUNCS += functions/gnutls_dtls_cookie_send FUNCS += functions/gnutls_dtls_cookie_send.short FUNCS += functions/gnutls_dtls_cookie_verify @@ -1010,6 +1012,8 @@ FUNCS += functions/gnutls_ecc_curve_get_size FUNCS += functions/gnutls_ecc_curve_get_size.short FUNCS += functions/gnutls_ecc_curve_list FUNCS += functions/gnutls_ecc_curve_list.short +FUNCS += functions/gnutls_ecc_curve_set_enabled +FUNCS += functions/gnutls_ecc_curve_set_enabled.short FUNCS += functions/gnutls_encode_ber_digest_info FUNCS += functions/gnutls_encode_ber_digest_info.short FUNCS += functions/gnutls_encode_gost_rs_value @@ -1730,6 +1734,8 @@ FUNCS += functions/gnutls_protocol_get_version FUNCS += functions/gnutls_protocol_get_version.short FUNCS += functions/gnutls_protocol_list FUNCS += functions/gnutls_protocol_list.short +FUNCS += functions/gnutls_protocol_set_enabled +FUNCS += functions/gnutls_protocol_set_enabled.short FUNCS += functions/gnutls_psk_allocate_client_credentials FUNCS += functions/gnutls_psk_allocate_client_credentials.short FUNCS += functions/gnutls_psk_allocate_server_credentials @@ -2024,6 +2030,10 @@ FUNCS += functions/gnutls_sign_is_secure2 FUNCS += functions/gnutls_sign_is_secure2.short FUNCS += functions/gnutls_sign_list FUNCS += functions/gnutls_sign_list.short +FUNCS += functions/gnutls_sign_set_secure +FUNCS += functions/gnutls_sign_set_secure.short +FUNCS += functions/gnutls_sign_set_secure_for_certs +FUNCS += functions/gnutls_sign_set_secure_for_certs.short FUNCS += functions/gnutls_sign_supports_pk_algorithm FUNCS += functions/gnutls_sign_supports_pk_algorithm.short FUNCS += functions/gnutls_srp_allocate_client_credentials diff --git a/doc/cha-config.texi b/doc/cha-config.texi index c0f7048fc2..62def65796 100644 --- a/doc/cha-config.texi +++ b/doc/cha-config.texi @@ -74,6 +74,7 @@ configuration file and can be @item @code{insecure-sig-for-cert}: to mark the signature algorithm as insecure when used in certificates. @item @code{insecure-sig}: to mark the signature algorithm as insecure for any use. @item @code{insecure-hash}: to mark the hash algorithm as insecure for digital signature use (provides a more generic way to disable digital signatures for broken hash algorithms). +@item @code{disabled-curve}: to disable the specified elliptic curve. @item @code{disabled-version}: to disable the specified TLS versions. @item @code{tls-disabled-cipher}: to disable the specified ciphers for use in the TLS or DTLS protocols. @item @code{tls-disabled-mac}: to disable the specified MAC algorithms for use in the TLS or DTLS protocols. @@ -82,10 +83,54 @@ configuration file and can be @end itemize Each of the options can be repeated multiple times when multiple values need -to be disabled. +to be disabled or enabled. The valid values for the options above can be found in the 'Protocols', 'Digests' -'PK-signatures', 'Protocols', 'Ciphrers', and 'MACs' fields of the output of @code{gnutls-cli --list}. +'PK-signatures', 'Protocols', 'Ciphers', and 'MACs' fields of the output of @code{gnutls-cli --list}. + +Sometimes the system administrator wants to enable only specific +algorithms, despite the library defaults. GnuTLS provides an +alternative mode of overriding: allowlisting. + +As shown below in the examples, it is hard to use this mode correctly, +as it requires understanding of how algorithms are used underneath by +the protocols. Allowlisting configuration mode is intended to be used +by the operating system vendors that prefer laying out the library +defaults exhaustively from scratch instead on depending on gnutls +presets, such as @code{NORMAL}. Applications are then expected to +optionally disable or enable only a subset algorithms on top of the +vendor-provided configuration. + +In the allowlisting mode, all the algorithms are initially marked as +insecure or disabled, and shall be explicitly turned on by the options +listed below in the @code{[overrides]} section. As the allowlisting +mode is mutually exclusive to the blocklisting mode, the options +listed above for the blocklisting mode are forbidden in the +allowlisting mode, and vice versa. + +@itemize +@item @code{secure-sig-for-cert}: to mark the signature algorithm as secure when used in certificates. +@item @code{secure-sig}: to mark the signature algorithm as secure for any use. +@item @code{secure-hash}: to mark the hash algorithm as secure for digital signature use (provides a more generic way to enable digital signatures for broken hash algorithms). +@item @code{enabled-curve}: to enable the specified elliptic curve. +@item @code{enabled-version}: to enable the specified TLS versions. +@item @code{tls-enabled-cipher}: to enable the specified ciphers for use in the TLS or DTLS protocols. +@item @code{tls-enabled-mac}: to enable the specified MAC algorithms for use in the TLS or DTLS protocols. +@item @code{tls-enabled-group}: to enable the specified group for use in the TLS or DTLS protocols. +@item @code{tls-enabled-kx}: to enable the specified key exchange algorithms for use in the TLS or DTLS protocols (applies to TLS1.2 or earlier). +@end itemize + +The allowlisting mode can be enabled by adding @code{override-mode = +allowlist} in the @code{[global]} section. + +The following functions allow the applications to modify the setting. + +@showfuncE{gnutls_ecc_curve_set_enabled,gnutls_sign_set_secure,gnutls_sign_set_secure_for_certs,gnutls_digest_set_secure,gnutls_protocol_set_enabled} + +When the allowlisting mode is in effect, a @code{@@SYSTEM} priority +string is automatically constructed from the options in the +@code{[overrides]} section. For this reason, the above functions +should be called before the @code{@@SYSTEM} priority is used. @subsection Examples @@ -120,6 +165,39 @@ tls-disabled-mac = sha1 tls-disabled-group = group-ffdhe8192 @end example +The following example demonstrates the use of the allowlisting +mode. All the signature algorithms are disabled by default but +@code{RSA-SHA256}. Note that the hash algorithm @code{SHA256} also +needs to be explicitly enabled. + +@example +[global] +override-mode = allowlist + +[overrides] +secure-hash = sha256 +secure-sig = rsa-sha256 +@end example + +To enable a TLS ciphersuite in the allowlist mode requires a more +verbose configuration, explicitly listing algorithm dependencies. The +following example enables TLS_AES_128_GCM_SHA256, using the SECP256R1 +curve for signing and key exchange. + +@example +[global] +override-mode = allowlist + +[overrides] +secure-hash = sha256 +enabled-curve = secp256r1 +secure-sig = ecdsa-secp256r1-sha256 +enabled-version = tls1.3 +tls-enabled-cipher = aes-128-gcm +tls-enabled-mac = aead +tls-enabled-group = secp256r1 +@end example + @node Querying for disabled algorithms and protocols @section Querying for disabled algorithms and protocols diff --git a/doc/manpages/Makefile.am b/doc/manpages/Makefile.am index cee5a9644a..4f39adf0cc 100644 --- a/doc/manpages/Makefile.am +++ b/doc/manpages/Makefile.am @@ -289,6 +289,7 @@ APIMANS += gnutls_digest_get_id.3 APIMANS += gnutls_digest_get_name.3 APIMANS += gnutls_digest_get_oid.3 APIMANS += gnutls_digest_list.3 +APIMANS += gnutls_digest_set_secure.3 APIMANS += gnutls_dtls_cookie_send.3 APIMANS += gnutls_dtls_cookie_verify.3 APIMANS += gnutls_dtls_get_data_mtu.3 @@ -307,6 +308,7 @@ APIMANS += gnutls_ecc_curve_get_oid.3 APIMANS += gnutls_ecc_curve_get_pk.3 APIMANS += gnutls_ecc_curve_get_size.3 APIMANS += gnutls_ecc_curve_list.3 +APIMANS += gnutls_ecc_curve_set_enabled.3 APIMANS += gnutls_encode_ber_digest_info.3 APIMANS += gnutls_encode_gost_rs_value.3 APIMANS += gnutls_encode_rs_value.3 @@ -667,6 +669,7 @@ APIMANS += gnutls_protocol_get_id.3 APIMANS += gnutls_protocol_get_name.3 APIMANS += gnutls_protocol_get_version.3 APIMANS += gnutls_protocol_list.3 +APIMANS += gnutls_protocol_set_enabled.3 APIMANS += gnutls_psk_allocate_client_credentials.3 APIMANS += gnutls_psk_allocate_server_credentials.3 APIMANS += gnutls_psk_client_get_hint.3 @@ -814,6 +817,8 @@ APIMANS += gnutls_sign_get_pk_algorithm.3 APIMANS += gnutls_sign_is_secure.3 APIMANS += gnutls_sign_is_secure2.3 APIMANS += gnutls_sign_list.3 +APIMANS += gnutls_sign_set_secure.3 +APIMANS += gnutls_sign_set_secure_for_certs.3 APIMANS += gnutls_sign_supports_pk_algorithm.3 APIMANS += gnutls_srp_allocate_client_credentials.3 APIMANS += gnutls_srp_allocate_server_credentials.3 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)-> diff --git a/tests/Makefile.am b/tests/Makefile.am index e9ee9e9de2..ba16f4de3f 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -523,6 +523,12 @@ dist_check_SCRIPTS += system-override-sig.sh system-override-hash.sh \ system-override-curves.sh system-override-profiles.sh system-override-tls.sh \ system-override-kx.sh system-override-default-priority-string.sh \ system-override-sig-tls.sh system-override-hash-filters-prf.sh + +dist_check_SCRIPTS += system-override-sig-allowlist.sh \ + system-override-hash-allowlist.sh \ + system-override-versions-allowlist.sh \ + system-override-curves-allowlist.sh \ + system-override-special-allowlist.sh endif dist_check_SCRIPTS += gnutls-cli-self-signed.sh gnutls-cli-invalid-crl.sh gnutls-cli-rawpk.sh diff --git a/tests/system-override-curves-allowlist.sh b/tests/system-override-curves-allowlist.sh new file mode 100755 index 0000000000..08f3e2ca94 --- /dev/null +++ b/tests/system-override-curves-allowlist.sh @@ -0,0 +1,113 @@ +#!/bin/sh + +# Copyright (C) 2019 Red Hat, Inc. +# +# Author: Nikos Mavrogiannopoulos +# +# This file is part of GnuTLS. +# +# GnuTLS is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at +# your option) any later version. +# +# GnuTLS is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/> + +: ${srcdir=.} +: ${SERV=../src/gnutls-serv${EXEEXT}} +: ${CLI=../src/gnutls-cli${EXEEXT}} +TMPFILE=config.$$.tmp +TMPFILE2=log.$$.tmp +export GNUTLS_SYSTEM_PRIORITY_FAIL_ON_INVALID=1 + +if ! test -x "${SERV}"; then + exit 77 +fi + +if ! test -x "${CLI}"; then + exit 77 +fi + +if test "${WINDIR}" != ""; then + exit 77 +fi + +. "${srcdir}/scripts/common.sh" + +# This test doesn't work in FIPS mode +if test -n "${GNUTLS_FORCE_FIPS_MODE}" && test "${GNUTLS_FORCE_FIPS_MODE}" != 0; then + exit 77 +fi + +# We intentionally add stray spaces and tabs to check our parser +cat <<_EOF_ > ${TMPFILE} +[global] +override-mode = allowlist + +[overrides] +enabled-curve = secp384r1 +_EOF_ + +export GNUTLS_SYSTEM_PRIORITY_FILE="${TMPFILE}" +export GNUTLS_DEBUG_LEVEL=3 + +"${CLI}" --list|grep ^Groups >${TMPFILE2} +cat ${TMPFILE2} +if grep -i "SECP256R1" ${TMPFILE2} || grep -i "SECP521R1" ${TMPFILE2};then + echo "Found disabled curve with --list" + exit 1 +fi + +if ! grep -i "SECP384R1" ${TMPFILE2};then + echo "Could not found secp384r1" + exit 1 +fi + +# Try whether a client connection with a disabled curve will succeed. + +KEY1=${srcdir}/../doc/credentials/x509/key-rsa.pem +CERT1=${srcdir}/../doc/credentials/x509/cert-rsa.pem + +unset GNUTLS_SYSTEM_PRIORITY_FILE + +eval "${GETPORT}" +launch_server --echo --priority "NORMAL:-VERS-ALL:+VERS-TLS1.2:+VERS-TLS1.3" --x509keyfile ${KEY1} --x509certfile ${CERT1} +PID=$! +wait_server ${PID} + +"${CLI}" -p "${PORT}" 127.0.0.1 --priority NORMAL:-CURVE-ALL:+CURVE-SECP256R1:+CURVE-SECP521R1 --insecure --logfile ${TMPFILE2} </dev/null >/dev/null || + fail "expected connection to succeed (1)" + +export GNUTLS_SYSTEM_PRIORITY_FILE="${TMPFILE}" + +"${CLI}" -p "${PORT}" 127.0.0.1 --priority NORMAL:-CURVE-ALL:+CURVE-SECP256R1:+CURVE-SECP521R1 --insecure --logfile ${TMPFILE2} </dev/null >/dev/null && + fail "expected connection to fail (2)" + +kill ${PID} +wait + +# Try whether a server connection with a disabled curve will succeed. + +KEY1=${srcdir}/../doc/credentials/x509/key-rsa.pem +CERT1=${srcdir}/../doc/credentials/x509/cert-rsa.pem + +eval "${GETPORT}" +launch_server --echo --priority "NORMAL" --x509keyfile ${KEY1} --x509certfile ${CERT1} +PID=$! +wait_server ${PID} + +unset GNUTLS_SYSTEM_PRIORITY_FILE + +"${CLI}" -p "${PORT}" 127.0.0.1 --priority "NORMAL:-CURVE-ALL:+CURVE-SECP256R1:+CURVE-SECP521R1" --insecure --logfile ${TMPFILE2} </dev/null >/dev/null && + fail "expected connection to fail (2)" + +kill ${PID} +wait + +exit 0 diff --git a/tests/system-override-hash-allowlist.sh b/tests/system-override-hash-allowlist.sh new file mode 100755 index 0000000000..651a596461 --- /dev/null +++ b/tests/system-override-hash-allowlist.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +# Copyright (C) 2019 Nikos Mavrogiannopoulos +# +# Author: Nikos Mavrogiannopoulos +# +# This file is part of GnuTLS. +# +# GnuTLS is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at +# your option) any later version. +# +# GnuTLS is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GnuTLS; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +: ${builddir=.} +TMPFILE=c.$$.tmp +export GNUTLS_SYSTEM_PRIORITY_FAIL_ON_INVALID=1 + +cat <<_EOF_ > ${TMPFILE} +[global] +override-mode = allowlist + +[overrides] +secure-hash = sha384 +secure-sig = rsa-pss-sha384 +_EOF_ + +export GNUTLS_SYSTEM_PRIORITY_FILE="${TMPFILE}" + +"${builddir}/system-override-hash" +rc=$? +rm ${TMPFILE} +exit $rc diff --git a/tests/system-override-sig-allowlist.sh b/tests/system-override-sig-allowlist.sh new file mode 100755 index 0000000000..70b02f14cb --- /dev/null +++ b/tests/system-override-sig-allowlist.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +# Copyright (C) 2019 Nikos Mavrogiannopoulos +# +# Author: Nikos Mavrogiannopoulos +# +# This file is part of GnuTLS. +# +# GnuTLS is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at +# your option) any later version. +# +# GnuTLS is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GnuTLS; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +: ${builddir=.} +TMPFILE=c.$$.tmp +export GNUTLS_SYSTEM_PRIORITY_FAIL_ON_INVALID=1 + +cat <<_EOF_ > ${TMPFILE} +[global] +override-mode = allowlist + +[overrides] +secure-hash = sha256 +secure-sig = rsa-sha256 +secure-hash = sha384 +secure-sig = rsa-pss-sha384 +_EOF_ + +export GNUTLS_SYSTEM_PRIORITY_FILE="${TMPFILE}" + +"${builddir}/system-override-sig" +rc=$? +rm ${TMPFILE} +exit $rc diff --git a/tests/system-override-special-allowlist.sh b/tests/system-override-special-allowlist.sh new file mode 100755 index 0000000000..47e838e580 --- /dev/null +++ b/tests/system-override-special-allowlist.sh @@ -0,0 +1,177 @@ +#!/bin/sh + +# Copyright (C) 2021 Red Hat, Inc. +# +# Author: Alexander Sosedkin +# +# This file is part of GnuTLS. +# +# GnuTLS is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at +# your option) any later version. +# +# GnuTLS is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GnuTLS; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +: ${srcdir=.} +: ${CLI=../src/gnutls-cli${EXEEXT}} +: ${GREP=grep} +: ${DIFF=diff} +: ${SED=sed} + +if ! test -x "${CLI}"; then + exit 77 +fi + +TMPCFGFILE=cfg.$$.tmp +TMPREFFILE=ref.$$.tmp +TMPCMPFILE=cmp.$$.tmp +TMPOUTFILE=out.$$.tmp +TMPSPECIAL=spc.$$.tmp + +# extract the list of %SPECIALs from the sources + +< ${srcdir}/../lib/priority_options.gperf \ + ${SED} -ne '/\([A-Z_0-9]\{1,\}\), .*/p' | \ + ${SED} -e 's/\([A-Z_0-9]\{1,\}\), .*/\1/' > "${TMPSPECIAL}" + +if ! ${GREP} -Fqx STATELESS_COMPRESSION "${TMPSPECIAL}"; then + cat "${TMPSPECIAL}" + echo 'source-extracted list of %SPECIALs has no %STATELESS_COMPRESSION' + exit 1 +fi + +# Set up a configuration file using allowlisting +# allowing for both TLS 1.2 and TLS 1.3 +# (so that %NO_EXTENSIONS later caps that just TLS 1.2) + +cat <<_EOF_ > ${TMPCFGFILE} +[global] +override-mode = allowlist + +[overrides] +secure-hash = SHA256 +tls-enabled-mac = AEAD +tls-enabled-group = GROUP-FFDHE3072 +secure-sig = RSA-SHA256 +tls-enabled-cipher = AES-128-GCM +tls-enabled-kx = RSA +enabled-version = TLS1.3 +enabled-version = TLS1.2 +_EOF_ +export GNUTLS_SYSTEM_PRIORITY_FILE="${TMPCFGFILE}" +export GNUTLS_SYSTEM_PRIORITY_FAIL_ON_INVALID=1 + +# Smoke --list, @SYSTEM + +${CLI} --list -d 4 --priority @SYSTEM &>"${TMPOUTFILE}" +if test $? != 0; then + cat "${TMPOUTFILE}" + echo 'fails with just @SYSTEM' + exit 1 +fi +if ! ${GREP} -Fqx 'Protocols: VERS-TLS1.3, VERS-TLS1.2' \ + "${TMPOUTFILE}"; then + cat "${TMPOUTFILE}" + echo 'unexpected protocol list with @SYSTEM' + exit 1 +fi +if ! ${GREP} -Fq TLS_AES_128_GCM_SHA256 "${TMPOUTFILE}"; then + cat "${TMPOUTFILE}" + echo 'no TLS_AES_128_GCM_SHA256 with just @SYSTEM' + exit 1 +fi +if ! ${GREP} -q TLS_RSA_AES_128_GCM_SHA256 "${TMPOUTFILE}"; then + cat "${TMPOUTFILE}" + echo 'no TLS_RSA_AES_128_GCM_SHA256 with just @SYSTEM' + exit 1 +fi +${SED} 's/for @SYSTEM/for ---PRIORITY---/' "${TMPOUTFILE}" > "${TMPREFFILE}" + +# Smoke-test a no-op %STATELESS_COMPRESSION, expect --list to stay the same + +${CLI} --list -d 4 --priority @SYSTEM:%STATELESS_COMPRESSION &>"${TMPOUTFILE}" +if test $? != 0; then + cat "${TMPOUTFILE}" + echo 'fails with %STATELESS_COMPRESSION' + exit 1 +fi +${SED} 's/for @SYSTEM:%STATELESS_COMPRESSION/for ---PRIORITY---/' \ + "${TMPOUTFILE}" > "${TMPCMPFILE}" +if ! ${DIFF} "${TMPCMPFILE}" "${TMPREFFILE}"; then + echo '%STATELESS_COMPRESSION has changed the output' + exit 1 +fi + +# Smoke-test %NONEXISTING_OPTION, expect a syntax error + +${CLI} --list -d 4 --priority @SYSTEM:%NONEXISTING_OPTION &>"${TMPOUTFILE}" +if test $? = 0; then + cat "${TMPOUTFILE}" + echo 'unknown option was not caught' + exit 1 +fi +if ! ${GREP} -Fq 'Syntax error at: @SYSTEM:%NONEXISTING_OPTION' "${TMPOUTFILE}" +then + cat "${TMPOUTFILE}" + echo 'unknown option was not errored upon' + exit 1 +fi + +# Test impact-less %SPECIALs, expect --list to stay the same + +while read special; do + if test "$special" = NO_EXTENSIONS; then + continue # see below + fi + prio="@SYSTEM:%$special" + ${CLI} --list -d 4 --priority "$prio" &>"${TMPOUTFILE}" + if test $? != 0; then + cat "${TMPOUTFILE}" + echo "fails with $prio" + exit 1 + fi + ${SED} "s/for $prio/for ---PRIORITY---/" "${TMPOUTFILE}" \ + > "${TMPCMPFILE}" + if ! ${DIFF} "${TMPCMPFILE}" "${TMPREFFILE}"; then + echo "$special has changed the output" + exit 1 + fi +done < "${TMPSPECIAL}" + +# Check that %NO_EXTENSIONS changes the output, capping it to TLS 1.2 + +${CLI} --list -d 4 --priority @SYSTEM:%NO_EXTENSIONS &>"${TMPOUTFILE}" +if test $? != 0; then + cat "${TMPOUTFILE}" + echo 'fails with just @SYSTEM' + exit 1 +fi +if ! ${GREP} -Fqx 'Protocols: VERS-TLS1.2' \ + "${TMPOUTFILE}"; then + cat "${TMPOUTFILE}" + echo 'unexpected protocol list with @SYSTEM:%NO_EXTENSIONS' + exit 1 +fi +if ${GREP} -Fq TLS_AES_128_GCM_SHA256 "${TMPOUTFILE}"; then + cat "${TMPOUTFILE}" + echo 'TLS_AES_128_GCM_SHA256 present with @SYSTEM:%NO_EXTENSIONS' + exit 1 +fi +if ! ${GREP} -q TLS_RSA_AES_128_GCM_SHA256 "${TMPOUTFILE}"; then + cat "${TMPOUTFILE}" + echo 'no TLS_RSA_AES_128_GCM_SHA256 with @SYSTEM:%NO_EXTENSIONS' + exit 1 +fi + +rm "${TMPCFGFILE}" "${TMPREFFILE}" "${TMPCMPFILE}" "${TMPOUTFILE}" +rm "${TMPSPECIAL}" + +exit 0 diff --git a/tests/system-override-versions-allowlist.sh b/tests/system-override-versions-allowlist.sh new file mode 100755 index 0000000000..b708c8c7e4 --- /dev/null +++ b/tests/system-override-versions-allowlist.sh @@ -0,0 +1,109 @@ +#!/bin/sh + +# Copyright (C) 2019 Red Hat, Inc. +# +# Author: Nikos Mavrogiannopoulos +# +# This file is part of GnuTLS. +# +# GnuTLS is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at +# your option) any later version. +# +# GnuTLS is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GnuTLS; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +: ${srcdir=.} +: ${SERV=../src/gnutls-serv${EXEEXT}} +: ${CLI=../src/gnutls-cli${EXEEXT}} +TMPFILE=config.$$.tmp +TMPFILE2=log.$$.tmp +export GNUTLS_SYSTEM_PRIORITY_FAIL_ON_INVALID=1 + +if ! test -x "${SERV}"; then + exit 77 +fi + +if ! test -x "${CLI}"; then + exit 77 +fi + +if test "${WINDIR}" != ""; then + exit 77 +fi + +. "${srcdir}/scripts/common.sh" + +cat <<_EOF_ > ${TMPFILE} +[global] +override-mode = allowlist + +[overrides] +enabled-version = tls1.1 +_EOF_ + +export GNUTLS_SYSTEM_PRIORITY_FILE="${TMPFILE}" +export GNUTLS_DEBUG_LEVEL=3 + +"${CLI}" --list --priority=@SYSTEM | grep Protocols >${TMPFILE2} +cat ${TMPFILE2} +if grep 'VERS-TLS1\.[23]' ${TMPFILE2}; then + echo "Found disabled protocol with --list" + exit 1 +fi + +PRIO=@SYSTEM:+CIPHER-ALL:+MAC-ALL:+GROUP-ALL + +"${CLI}" --priority "$PRIO" --list | grep Protocols >${TMPFILE2} +cat ${TMPFILE2} +if grep 'VERS-TLS1\.[23]' ${TMPFILE2}; then + echo "Found disabled protocol with --list --priority $PRIO" + exit 1 +fi + +# Try whether a client connection with these protocols will succeed. + +KEY1=${srcdir}/../doc/credentials/x509/key-rsa.pem +CERT1=${srcdir}/../doc/credentials/x509/cert-rsa.pem + +unset GNUTLS_SYSTEM_PRIORITY_FILE + +eval "${GETPORT}" +launch_server --echo --priority "NORMAL:-VERS-ALL:+VERS-TLS1.2:+VERS-TLS1.3" --x509keyfile ${KEY1} --x509certfile ${CERT1} +PID=$! +wait_server ${PID} + +export GNUTLS_SYSTEM_PRIORITY_FILE="${TMPFILE}" + +"${CLI}" -p "${PORT}" 127.0.0.1 --priority "$PRIO" --insecure --logfile ${TMPFILE2} </dev/null >/dev/null && + fail "expected connection to fail (1)" + +kill ${PID} +wait + +# Try whether a server connection with these protocols will succeed. + +KEY1=${srcdir}/../doc/credentials/x509/key-rsa.pem +CERT1=${srcdir}/../doc/credentials/x509/cert-rsa.pem + +eval "${GETPORT}" +launch_server --echo --priority "$PRIO" --x509keyfile ${KEY1} --x509certfile ${CERT1} +PID=$! +wait_server ${PID} + +unset GNUTLS_SYSTEM_PRIORITY_FILE + +"${CLI}" -p "${PORT}" 127.0.0.1 --priority "NORMAL:-VERS-ALL:+VERS-TLS1.2:+VERS-TLS1.3" --insecure --logfile ${TMPFILE2} </dev/null >/dev/null && + fail "expected connection to fail (2)" + +kill ${PID} +wait + +exit 0 |