]> git.ipfire.org Git - thirdparty/gnutls.git/commitdiff
priority: support allowlisting in configuration file
authorDaiki Ueno <ueno@gnu.org>
Thu, 6 May 2021 10:41:40 +0000 (12:41 +0200)
committerDaiki Ueno <ueno@gnu.org>
Mon, 29 Nov 2021 12:21:53 +0000 (13:21 +0100)
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>
22 files changed:
NEWS
devel/libgnutls.abignore
devel/symbols.last
doc/Makefile.am
doc/cha-config.texi
doc/manpages/Makefile.am
lib/algorithms.h
lib/algorithms/ecc.c
lib/algorithms/groups.c
lib/algorithms/mac.c
lib/algorithms/protocols.c
lib/algorithms/sign.c
lib/gnutls_int.h
lib/includes/gnutls/gnutls.h.in
lib/libgnutls.map
lib/priority.c
tests/Makefile.am
tests/system-override-curves-allowlist.sh [new file with mode: 0755]
tests/system-override-hash-allowlist.sh [new file with mode: 0755]
tests/system-override-sig-allowlist.sh [new file with mode: 0755]
tests/system-override-special-allowlist.sh [new file with mode: 0755]
tests/system-override-versions-allowlist.sh [new file with mode: 0755]

diff --git a/NEWS b/NEWS
index 42c2be012409c19f34d9601c62bb91f7009a05ba..638b3b51c2176219faab5d2fedd493070ed05e4f 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -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)
 
index e43e5472f3abca7a8a3c8ec655078dfff80213d1..8afa94148ab88a0df91b4b88455ef5b8b49807a3 100644 (file)
@@ -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
index 963287687bbf26b60325d57cd803fb61bcbd9a40..7bef6663505337d7dcb7cc5a881839aab1dde1a7 100644 (file)
@@ -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
index 562a85fa872edec9d87196c4e64c70d060beaf37..4f25bf0d5ea8c303e1ffc733ac1f0aa1ae06cfd1 100644 (file)
@@ -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
index c0f7048fc200d5cbe5aeafb004e4f189a8e4a9c8..62def65796fa6a48e6718840c712f9d53349134d 100644 (file)
@@ -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
 
index cee5a9644a8ee1f0ed96de24ff77c72abb1c8bef..4f39adf0ccbd6d31204ebac030510f33a48638c8 100644 (file)
@@ -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
index 2f5366db6bc647031356a1c6ce1999b26129e34d..da72403fbacb546bfffbd207ec77cf779016bef7 100644 (file)
@@ -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
index f5fb8ac76f5afa78c0f91c4e2b3fa56a3f54f134..d2c0b3585bd67109d61229df83e93772dd88f3c4 100644 (file)
@@ -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;
                }
        }
index d4b77beb2a577c3702f390399ed1f47d57b1445a..d8bf95824f381d6dfe9ca39f172c051b2381895d 100644 (file)
@@ -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
index 57dfca95dee9046d652251787b576f15ab252a9c..a2c66e76bbaffdaa3d5c34ca79f3a02f670e2401 100644 (file)
@@ -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
index 4283cd0388a2e36736e021525380b45b02add96a..b0f3e0bc30765708b3c51742737a1de7dc2846be 100644 (file)
@@ -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)
index 4c5619454fd6443779bcb0a6ca8da07c9132d2eb..543bd19bb5c9f63c2d4e98e1a2960b16e963ed0a 100644 (file)
@@ -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
index 88f0c28a00c56748dba80a4734944b1e2d0267c7..1dbe40485723a4b9428f311e5ea9b7c8e6637690 100644 (file)
@@ -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 */
index d69b29b443c7dc6c267df0325b354e718670e62f..1e883aa8eb21c0be022a864a02cc126d664a5b2e 100644 (file)
@@ -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);
index 0e6b3793144605d142c944f8e6c66ca0e74b618a..dc50c6dba96692749c848ce2780acc8a53eaf99e 100644 (file)
@@ -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;
index 4ad5c9046245a0c67e152c01901d88788b3576d8..54d7b1bb45a59009515730d0be90c5726ff1cf8c 100644 (file)
 
 /* 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)->
index e9ee9e9de20c7cf1792b7cdbd2bcc7b0f55326e9..ba16f4de3f66090647689ab32622f54862f57059 100644 (file)
@@ -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 (executable)
index 0000000..08f3e2c
--- /dev/null
@@ -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 (executable)
index 0000000..651a596
--- /dev/null
@@ -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 (executable)
index 0000000..70b02f1
--- /dev/null
@@ -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 (executable)
index 0000000..47e838e
--- /dev/null
@@ -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 (executable)
index 0000000..b708c8c
--- /dev/null
@@ -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