diff options
author | Nikos Mavrogiannopoulos <nmav@gnutls.org> | 2018-01-21 15:07:00 +0100 |
---|---|---|
committer | Nikos Mavrogiannopoulos <nmav@redhat.com> | 2018-02-19 08:39:36 +0100 |
commit | dc3aa56779581b661311f1c4603f3383f252b3b3 (patch) | |
tree | 6f7c19ca00afb58b1f988a5b8b7e6333c4d29575 | |
parent | e89e981a2c6fab9ea5419207710b0f716df1c148 (diff) | |
download | gnutls-dc3aa56779581b661311f1c4603f3383f252b3b3.tar.gz |
fips140: added function for applications to switch the FIPS140-2 mode
That would allow FIPS140-2 compliant applications to use forbidden
algorithms by switching to a lax FIPS140-2 mode.
Resolves #352
Resolves #353
Signed-off-by: Nikos Mavrogiannopoulos <nmav@gnutls.org>
-rw-r--r-- | lib/Makefile.am | 2 | ||||
-rw-r--r-- | lib/fips.c | 139 | ||||
-rw-r--r-- | lib/fips.h | 43 | ||||
-rw-r--r-- | lib/gthreads.h | 31 | ||||
-rw-r--r-- | lib/includes/gnutls/gnutls.h.in | 28 | ||||
-rw-r--r-- | lib/libgnutls.map | 8 | ||||
-rw-r--r-- | lib/nettle/cipher.c | 3 | ||||
-rw-r--r-- | lib/nettle/int/dsa-keygen-fips186.c | 8 | ||||
-rw-r--r-- | lib/nettle/int/rsa-keygen-fips186.c | 26 | ||||
-rw-r--r-- | lib/random.c | 8 |
10 files changed, 225 insertions, 71 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am index 3ea1c6cebb..1b4f4fd6aa 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -65,7 +65,7 @@ SRP_COBJECTS = srp.c PSK_COBJECTS = psk.c -COBJECTS = range.c record.c compress.c debug.c cipher.c \ +COBJECTS = range.c record.c compress.c debug.c cipher.c gthreads.h \ mbuffers.c buffers.c handshake.c num.c errors.c dh.c kx.c \ priority.c hash_int.c cipher_int.c session.c db.c x509_b64.c \ extensions.c auth.c sslv2_compat.c datum.c session_pack.c mpi.c \ diff --git a/lib/fips.c b/lib/fips.c index 9d0d9977ca..2715af599f 100644 --- a/lib/fips.c +++ b/lib/fips.c @@ -30,7 +30,9 @@ #include <extras/hex.h> #include <random.h> -unsigned int _gnutls_lib_mode = LIB_STATE_POWERON; +#include "gthreads.h" + +unsigned int _gnutls_lib_state = LIB_STATE_POWERON; #ifdef ENABLE_FIPS140 #include <dlfcn.h> @@ -38,22 +40,37 @@ unsigned int _gnutls_lib_mode = LIB_STATE_POWERON; #define FIPS_KERNEL_FILE "/proc/sys/crypto/fips_enabled" #define FIPS_SYSTEM_FILE "/etc/system-fips" -static int _fips_mode = -1; +/* We provide a per-thread FIPS-mode so that an application + * can use gnutls_fips140_set_mode() to override a specific + * operation on a thread */ +static gnutls_fips_mode_t _global_fips_mode = -1; +static _Thread_local gnutls_fips_mode_t _tfips_mode = -1; + static int _skip_integrity_checks = 0; /* Returns: - * 0 - FIPS mode disabled - * 1 - FIPS mode enabled and enforced - * 2 - FIPS in testing mode + * a gnutls_fips_mode_t value */ unsigned _gnutls_fips_mode_enabled(void) { -unsigned f1p = 0, f2p; -FILE* fd; -const char *p; + unsigned f1p = 0, f2p; + FILE* fd; + const char *p; + unsigned ret; + + /* We initialize this threads' mode, and + * the global mode if not already initialized. + * When the global mode is initialized, then + * the thread mode is copied from it. As this + * is called on library initialization, the + * _global_fips_mode is always set during app run. + */ + if (_tfips_mode != (gnutls_fips_mode_t)-1) + return _tfips_mode; - if (_fips_mode != -1) - return _fips_mode; + if (_global_fips_mode != (gnutls_fips_mode_t)-1) { + return _global_fips_mode; + } p = secure_getenv("GNUTLS_SKIP_FIPS_INTEGRITY_CHECKS"); if (p && p[0] == '1') { @@ -63,12 +80,17 @@ const char *p; p = secure_getenv("GNUTLS_FORCE_FIPS_MODE"); if (p) { if (p[0] == '1') - _fips_mode = 1; + ret = 1; else if (p[0] == '2') - _fips_mode = 2; + ret = GNUTLS_FIPS140_SELFTESTS; + else if (p[0] == '3') + ret = GNUTLS_FIPS140_LAX; + else if (p[0] == '4') + ret = GNUTLS_FIPS140_LOG; else - _fips_mode = 0; - return _fips_mode; + ret = GNUTLS_FIPS140_DISABLED; + + goto exit; } fd = fopen(FIPS_KERNEL_FILE, "r"); @@ -84,28 +106,32 @@ const char *p; if (f1p != 0 && f2p != 0) { _gnutls_debug_log("FIPS140-2 mode enabled\n"); - _fips_mode = 1; - return _fips_mode; + ret = GNUTLS_FIPS140_STRICT; + goto exit; } if (f2p != 0) { /* a funny state where self tests are performed * and ignored */ _gnutls_debug_log("FIPS140-2 ZOMBIE mode enabled\n"); - _fips_mode = 2; - return _fips_mode; + ret = GNUTLS_FIPS140_SELFTESTS; + goto exit; } - _fips_mode = 0; - return _fips_mode; + ret = GNUTLS_FIPS140_DISABLED; + goto exit; + + exit: + _global_fips_mode = ret; + return ret; } /* This _fips_mode == 2 is a strange mode where checks are being * performed, but its output is ignored. */ void _gnutls_fips_mode_reset_zombie(void) { - if (_fips_mode == 2) { - _fips_mode = 0; + if (_global_fips_mode == GNUTLS_FIPS140_SELFTESTS) { + _global_fips_mode = GNUTLS_FIPS140_DISABLED; } } @@ -397,7 +423,13 @@ error: /** * gnutls_fips140_mode_enabled: * - * Checks whether this library is in FIPS140 mode. + * Checks whether this library is in FIPS140 mode. The returned + * value corresponds to the library mode as set with + * gnutls_fips140_set_mode(). + * + * If gnutls_fips140_set_mode() was called with %GNUTLS_FIPS140_SET_MODE_THREAD + * then this function will return the current thread's FIPS140 mode, otherwise + * the global value is returned. * * Returns: return non-zero if true or zero if false. * @@ -406,14 +438,71 @@ error: unsigned gnutls_fips140_mode_enabled(void) { #ifdef ENABLE_FIPS140 -int ret = _gnutls_fips_mode_enabled(); + unsigned ret = _gnutls_fips_mode_enabled(); - if (ret == 1) + if (ret > GNUTLS_FIPS140_DISABLED) return ret; #endif return 0; } +/** + * gnutls_fips140_set_mode: + * @mode: the FIPS140-2 mode to switch to + * @flags: should be zero or %GNUTLS_FIPS140_SET_MODE_THREAD + * + * That function is not thread-safe when changing the mode with no flags + * (globally), and should be called prior to creating any threads. Its + * behavior with no flags after threads are created is undefined. + * + * When the flag %GNUTLS_FIPS140_SET_MODE_THREAD is specified + * then this call will change the FIPS140-2 mode for this particular + * thread and not for the whole process. That way an application + * can utilize this function to set and reset mode for specific + * operations. + * + * This function never fails but will be a no-op if used when + * the library is not in FIPS140-2 mode. When asked to switch to unknown + * values for @mode or to %GNUTLS_FIPS140_SELFTESTS mode, the library + * switches to %GNUTLS_FIPS140_STRICT mode. + * + * Since: 3.6.2 + **/ +void gnutls_fips140_set_mode(gnutls_fips_mode_t mode, unsigned flags) +{ +#ifdef ENABLE_FIPS140 + gnutls_fips_mode_t prev = _gnutls_fips_mode_enabled(); + if (prev == GNUTLS_FIPS140_DISABLED || prev == GNUTLS_FIPS140_SELFTESTS) { + /* we need to run self-tests first to be in FIPS140-2 mode */ + _gnutls_audit_log(NULL, "The library should be initialized in FIPS140-2 mode to do that operation\n"); + return; + } + + switch (mode) { + case GNUTLS_FIPS140_STRICT: + case GNUTLS_FIPS140_LAX: + case GNUTLS_FIPS140_LOG: + case GNUTLS_FIPS140_DISABLED: + break; + case GNUTLS_FIPS140_SELFTESTS: + _gnutls_audit_log(NULL, "Cannot switch library to FIPS140-2 self-tests mode; defaulting to strict\n"); + mode = GNUTLS_FIPS140_STRICT; + break; + default: + _gnutls_audit_log(NULL, "Cannot switch library to mode %u; defaulting to strict\n", (unsigned)mode); + mode = GNUTLS_FIPS140_STRICT; + break; + } + + if (flags & GNUTLS_FIPS140_SET_MODE_THREAD) + _tfips_mode = mode; + else { + _global_fips_mode = mode; + _tfips_mode = -1; + } +#endif +} + void _gnutls_lib_simulate_error(void) { _gnutls_switch_lib_state(LIB_STATE_ERROR); diff --git a/lib/fips.h b/lib/fips.h index ea9b5b3399..7d3f3cfd39 100644 --- a/lib/fips.h +++ b/lib/fips.h @@ -38,19 +38,19 @@ typedef enum { } gnutls_lib_state_t; /* do not access directly */ -extern unsigned int _gnutls_lib_mode; +extern unsigned int _gnutls_lib_state; extern gnutls_crypto_rnd_st _gnutls_fips_rnd_ops; inline static void _gnutls_switch_lib_state(gnutls_lib_state_t state) { /* Once into zombie state no errors can change us */ - _gnutls_lib_mode = state; + _gnutls_lib_state = state; } inline static gnutls_lib_state_t _gnutls_get_lib_state(void) { - return _gnutls_lib_mode; + return _gnutls_lib_state; } int _gnutls_fips_perform_self_checks1(void); @@ -74,12 +74,27 @@ void _gnutls_lib_simulate_error(void); void _gnutls_lib_force_operational(void); #ifdef ENABLE_FIPS140 +/* This will test the condition when in FIPS140-2 mode + * and return an error if necessary or ignore */ +# define FIPS_RULE(condition, ret_error, ...) { \ + gnutls_fips_mode_t _mode = _gnutls_fips_mode_enabled(); \ + if (_mode != GNUTLS_FIPS140_DISABLED) { \ + if (condition) { \ + if (_mode == GNUTLS_FIPS140_LOG) { \ + _gnutls_audit_log(NULL, "fips140-2: allowing "__VA_ARGS__); \ + } else if (_mode != GNUTLS_FIPS140_LAX) { \ + _gnutls_debug_log("fips140-2: disallowing "__VA_ARGS__); \ + return ret_error; \ + } \ + } \ + }} + inline static unsigned is_mac_algo_forbidden(gnutls_mac_algorithm_t algo) { - if (_gnutls_fips_mode_enabled() != 0 && + gnutls_fips_mode_t mode = _gnutls_fips_mode_enabled(); + if (mode != GNUTLS_FIPS140_DISABLED && _gnutls_get_lib_state() != LIB_STATE_SELFTEST) { - switch(algo) { case GNUTLS_MAC_SHA1: case GNUTLS_MAC_SHA256: @@ -92,6 +107,13 @@ static unsigned is_mac_algo_forbidden(gnutls_mac_algorithm_t algo) case GNUTLS_MAC_SHA3_512: return 0; default: + if (mode == GNUTLS_FIPS140_LAX) + return 0; + else if (mode == GNUTLS_FIPS140_LOG) { + _gnutls_audit_log(NULL, "fips140-2: allowing access to %s\n", + gnutls_mac_get_name(algo)); + return 0; + } return 1; } } @@ -102,7 +124,8 @@ static unsigned is_mac_algo_forbidden(gnutls_mac_algorithm_t algo) inline static unsigned is_cipher_algo_forbidden(gnutls_cipher_algorithm_t algo) { - if (_gnutls_fips_mode_enabled() != 0 && + gnutls_fips_mode_t mode = _gnutls_fips_mode_enabled(); + if (mode != GNUTLS_FIPS140_DISABLED && _gnutls_get_lib_state() != LIB_STATE_SELFTEST) { switch(algo) { @@ -118,6 +141,13 @@ static unsigned is_cipher_algo_forbidden(gnutls_cipher_algorithm_t algo) case GNUTLS_CIPHER_AES_256_CCM_8: return 0; default: + if (mode == GNUTLS_FIPS140_LAX) + return 0; + else if (mode == GNUTLS_FIPS140_LOG) { + _gnutls_audit_log(NULL, "fips140-2: allowing access to %s\n", + gnutls_cipher_get_name(algo)); + return 0; + } return 1; } } @@ -127,6 +157,7 @@ static unsigned is_cipher_algo_forbidden(gnutls_cipher_algorithm_t algo) #else # define is_mac_algo_forbidden(x) 0 # define is_cipher_algo_forbidden(x) 0 +# define FIPS_RULE(condition, ret_error, ...) #endif #endif /* FIPS_H */ diff --git a/lib/gthreads.h b/lib/gthreads.h new file mode 100644 index 0000000000..5887d46ba5 --- /dev/null +++ b/lib/gthreads.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2018 Red Hat, Inc. + * + * Author: Nikos Mavrogiannopoulos + * + * This file is part of GnuTLS. + * + * The GnuTLS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +#include <config.h> + +#ifdef HAVE_THREADS_H +# include <threads.h> +#elif defined(__GNUC__) +# define _Thread_local __thread +#else +# error Unsupported platform +#endif diff --git a/lib/includes/gnutls/gnutls.h.in b/lib/includes/gnutls/gnutls.h.in index 93cddb98c0..a199e420bc 100644 --- a/lib/includes/gnutls/gnutls.h.in +++ b/lib/includes/gnutls/gnutls.h.in @@ -2701,6 +2701,34 @@ void gnutls_supplemental_send(gnutls_session_t session, unsigned do_send_supplem /* FIPS140-2 related functions */ unsigned gnutls_fips140_mode_enabled(void); +/** + * gnutls_fips_mode_t: + * @GNUTLS_FIPS140_DISABLED: The FIPS140-2 mode is disabled. + * @GNUTLS_FIPS140_STRICT: The default mode; all forbidden operations will cause an + * operation failure via error code. + * @GNUTLS_FIPS140_LAX: The library still uses the FIPS140-2 relevant algorithms but all + * forbidden by FIPS140-2 operations are allowed; this is useful when the + * application is aware of the followed security policy, and needs + * to utilize disallowed operations for other reasons (e.g., compatibility). + * @GNUTLS_FIPS140_LOG: Similarly to %GNUTLS_FIPS140_LAX, it allows forbidden operations; any use of them results + * to a message to the audit callback functions. + * @GNUTLS_FIPS140_SELFTESTS: A transient state during library initialization. That state + * cannot be set or seen by applications. + * + * Enumeration of different operational modes under FIPS140-2. + */ +typedef enum gnutls_fips_mode_t { + GNUTLS_FIPS140_DISABLED = 0, + GNUTLS_FIPS140_STRICT = 1, + GNUTLS_FIPS140_SELFTESTS = 2, + GNUTLS_FIPS140_LAX = 3, + GNUTLS_FIPS140_LOG = 4 +} gnutls_fips_mode_t; + +#define GNUTLS_FIPS140_SET_MODE_THREAD 1 + +void gnutls_fips140_set_mode(gnutls_fips_mode_t mode, unsigned flags); + /* Gnutls error codes. The mapping to a TLS alert is also shown in * comments. */ diff --git a/lib/libgnutls.map b/lib/libgnutls.map index 72764ebab1..fd99091767 100644 --- a/lib/libgnutls.map +++ b/lib/libgnutls.map @@ -1196,10 +1196,14 @@ GNUTLS_3_6_2 global: gnutls_srp_8192_group_generator; gnutls_srp_8192_group_prime; - local: - *; } GNUTLS_3_6_0; +GNUTLS_3_6_3 +{ + global: + gnutls_fips140_set_mode; +} GNUTLS_3_6_2; + GNUTLS_FIPS140_3_4 { gnutls_cipher_self_test; gnutls_pk_self_test; diff --git a/lib/nettle/cipher.c b/lib/nettle/cipher.c index 68215c50cf..4bcbe42269 100644 --- a/lib/nettle/cipher.c +++ b/lib/nettle/cipher.c @@ -562,8 +562,7 @@ wrap_nettle_cipher_setiv(void *_ctx, const void *iv, size_t iv_size) switch (ctx->cipher->algo) { case GNUTLS_CIPHER_AES_128_GCM: case GNUTLS_CIPHER_AES_256_GCM: - if (_gnutls_fips_mode_enabled() != 0 && iv_size < GCM_IV_SIZE) - return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + FIPS_RULE(iv_size < GCM_IV_SIZE, GNUTLS_E_INVALID_REQUEST, "access to short GCM nonce size\n"); break; case GNUTLS_CIPHER_SALSA20_256: case GNUTLS_CIPHER_ESTREAM_SALSA20_256: diff --git a/lib/nettle/int/dsa-keygen-fips186.c b/lib/nettle/int/dsa-keygen-fips186.c index 7328f7761e..30ba004e0f 100644 --- a/lib/nettle/int/dsa-keygen-fips186.c +++ b/lib/nettle/int/dsa-keygen-fips186.c @@ -39,8 +39,7 @@ unsigned _dsa_check_qp_sizes(unsigned q_bits, unsigned p_bits, unsigned generate { switch (q_bits) { case 160: - if (_gnutls_fips_mode_enabled() != 0 && generate != 0) - return 0; + FIPS_RULE(generate != 0, 0, "DSA 160-bit generation\n"); if (p_bits != 1024) return 0; @@ -422,10 +421,7 @@ _dsa_generate_dss_pqg(struct dsa_params *params, if (_gnutls_fips_mode_enabled() != 0) { cert->seed_length = 2 * (q_bits / 8) + 1; - if (cert->seed_length != seed_size) { - _gnutls_debug_log("Seed length must be %d bytes (it is %d)\n", cert->seed_length, seed_size); - return 0; - } + FIPS_RULE(cert->seed_length != seed_size, 0, "unsupported DSA seed length (is %d, shoudl be %d)\n", seed_size, cert->seed_length); } else { cert->seed_length = seed_size; } diff --git a/lib/nettle/int/rsa-keygen-fips186.c b/lib/nettle/int/rsa-keygen-fips186.c index 506f758dd1..a76e5eaa00 100644 --- a/lib/nettle/int/rsa-keygen-fips186.c +++ b/lib/nettle/int/rsa-keygen-fips186.c @@ -267,22 +267,9 @@ _rsa_generate_fips186_4_keypair(struct rsa_public_key *pub, struct dss_params_validation_seeds cert; unsigned l = n_size / 2; - if (_gnutls_fips_mode_enabled() != 0) { - if (n_size == 2048) { - if (seed_length != 14 * 2) { - _gnutls_debug_log("Seed length must be 28 bytes (it is %d)\n", seed_length); - return 0; - } - } else if (n_size == 3072) { - if (seed_length != 16 * 2) { - _gnutls_debug_log("Seed length must be 32 bytes (it is %d)\n", seed_length); - return 0; - } - } else { - _gnutls_debug_log("Unsupported size for modulus\n"); - return 0; - } - } + FIPS_RULE(n_size == 2048 && seed_length != 14 * 2, 0, "seed length other than 28 bytes\n"); + FIPS_RULE(n_size == 3072 && seed_length != 16 * 2, 0, "seed length other than 32 bytes\n"); + FIPS_RULE(n_size != 2048 && n_size != 3072, 0, "unsupported size for modulus\n"); if (!mpz_tstbit(pub->e, 0)) { _gnutls_debug_log("Unacceptable e (it is even)\n"); @@ -420,12 +407,7 @@ rsa_generate_fips186_4_keypair(struct rsa_public_key *pub, unsigned seed_length; int ret; - if (_gnutls_fips_mode_enabled() != 0) { - if (n_size != 2048 && n_size != 3072) { - _gnutls_debug_log("The size of a prime can only be 2048 or 3072\n"); - return 0; - } - } + FIPS_RULE(n_size != 2048 && n_size != 3072, 0, "size of prime of other than 2048 or 3072\n"); seed_length = SEED_LENGTH(n_size); if (seed_length > sizeof(seed)) diff --git a/lib/random.c b/lib/random.c index 0c05dbe187..469fc4b9a6 100644 --- a/lib/random.c +++ b/lib/random.c @@ -29,13 +29,7 @@ #include "locks.h" #include <fips.h> -#ifdef HAVE_THREADS_H -# include <threads.h> -#elif defined(__GNUC__) -# define _Thread_local __thread -#else -# error Unsupported platform -#endif +#include "gthreads.h" #if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) extern gnutls_crypto_rnd_st _gnutls_fuzz_rnd_ops; |