/*
* Copyright (C) 2013 Red Hat
*
* 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
*
*/
#include "gnutls_int.h"
#include
#include
#include
#include "dirname.h"
#include "errors.h"
#include "file.h"
#include "inih/ini.h"
#include "str.h"
#include
#include
#include
#include
#include
#include "gthreads.h"
#ifdef HAVE_DL_ITERATE_PHDR
#include
#endif
unsigned int _gnutls_lib_state = LIB_STATE_POWERON;
struct gnutls_fips140_context_st {
gnutls_fips140_operation_state_t state;
struct gnutls_fips140_context_st *next;
};
#ifdef ENABLE_FIPS140
#include
#define FIPS_KERNEL_FILE "/proc/sys/crypto/fips_enabled"
#define FIPS_SYSTEM_FILE "/etc/system-fips"
/* 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 _Thread_local gnutls_fips140_context_t _tfips_context = NULL;
static int _skip_integrity_checks = 0;
/* Returns:
* a gnutls_fips_mode_t value
*/
unsigned _gnutls_fips_mode_enabled(void)
{
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 (_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') {
_skip_integrity_checks = 1;
}
p = secure_getenv("GNUTLS_FORCE_FIPS_MODE");
if (p) {
if (p[0] == '1')
ret = GNUTLS_FIPS140_STRICT;
else if (p[0] == '2')
ret = GNUTLS_FIPS140_SELFTESTS;
else if (p[0] == '3')
ret = GNUTLS_FIPS140_LAX;
else if (p[0] == '4')
ret = GNUTLS_FIPS140_LOG;
else
ret = GNUTLS_FIPS140_DISABLED;
goto exit;
}
fd = fopen(FIPS_KERNEL_FILE, "re");
if (fd != NULL) {
f1p = fgetc(fd);
fclose(fd);
if (f1p == '1')
f1p = 1;
else
f1p = 0;
}
if (f1p != 0) {
_gnutls_debug_log("FIPS140-2 mode enabled\n");
ret = GNUTLS_FIPS140_STRICT;
goto exit;
}
f2p = !access(FIPS_SYSTEM_FILE, F_OK);
if (f2p != 0) {
/* a funny state where self tests are performed
* and ignored */
_gnutls_debug_log("FIPS140-2 ZOMBIE mode enabled\n");
ret = GNUTLS_FIPS140_SELFTESTS;
goto exit;
}
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 (_global_fips_mode == GNUTLS_FIPS140_SELFTESTS) {
_global_fips_mode = GNUTLS_FIPS140_DISABLED;
}
}
/* These only works with the platform where SONAME is part of the ABI.
* For example, *_SONAME will be set to "none" on Windows platforms. */
#define GNUTLS_LIBRARY_NAME GNUTLS_LIBRARY_SONAME
#define NETTLE_LIBRARY_NAME NETTLE_LIBRARY_SONAME
#define HOGWEED_LIBRARY_NAME HOGWEED_LIBRARY_SONAME
#define GMP_LIBRARY_NAME GMP_LIBRARY_SONAME
#define HMAC_SIZE 32
#define HMAC_ALGO GNUTLS_MAC_SHA256
#define HMAC_FORMAT_VERSION 1
struct hmac_entry {
char path[GNUTLS_PATH_MAX];
uint8_t hmac[HMAC_SIZE];
};
struct hmac_file {
int version;
struct hmac_entry gnutls;
struct hmac_entry nettle;
struct hmac_entry hogweed;
struct hmac_entry gmp;
};
struct lib_paths {
char gnutls[GNUTLS_PATH_MAX];
char nettle[GNUTLS_PATH_MAX];
char hogweed[GNUTLS_PATH_MAX];
char gmp[GNUTLS_PATH_MAX];
};
/*
* get_hmac:
* @dest: buffer for the hex value
* @value: hmac value
*
* Parses hmac data and copies hex value into dest.
* dest must point to at least HMAC_SIZE amount of memory
*
* Returns: 0 on success, a negative error code otherwise
*/
static int get_hmac(uint8_t *dest, const char *value)
{
int ret;
size_t hmac_size;
gnutls_datum_t data;
data.size = strlen(value);
data.data = (unsigned char *)value;
hmac_size = HMAC_SIZE;
ret = gnutls_hex_decode(&data, dest, &hmac_size);
if (ret < 0)
return gnutls_assert_val(GNUTLS_E_PARSING_ERROR);
if (hmac_size != HMAC_SIZE)
return gnutls_assert_val(GNUTLS_E_PARSING_ERROR);
return 0;
}
static int lib_handler(struct hmac_entry *entry, const char *section,
const char *name, const char *value)
{
if (!strcmp(name, "path")) {
snprintf(entry->path, GNUTLS_PATH_MAX, "%s", value);
} else if (!strcmp(name, "hmac")) {
if (get_hmac(entry->hmac, value) < 0)
return 0;
} else {
return 0;
}
return 1;
}
static int handler(void *user, const char *section, const char *name,
const char *value)
{
struct hmac_file *p = (struct hmac_file *)user;
if (!strcmp(section, "global")) {
if (!strcmp(name, "format-version")) {
p->version = strtol(value, NULL, 10);
} else {
return 0;
}
} else if (!strcmp(section, GNUTLS_LIBRARY_NAME)) {
return lib_handler(&p->gnutls, section, name, value);
} else if (!strcmp(section, NETTLE_LIBRARY_NAME)) {
return lib_handler(&p->nettle, section, name, value);
} else if (!strcmp(section, HOGWEED_LIBRARY_NAME)) {
return lib_handler(&p->hogweed, section, name, value);
} else if (!strcmp(section, GMP_LIBRARY_NAME)) {
return lib_handler(&p->gmp, section, name, value);
} else {
return 0;
}
return 1;
}
/*
* get_hmac_path:
* @mac_file: buffer where the hmac file path will be written to
* @mac_file_size: size of the mac_file buffer
* @gnutls_path: path to the gnutls library, used to deduce hmac file path
*
* Deduces hmac file path from the gnutls library path.
*
* Returns: 0 on success, a negative error code otherwise
*/
static int get_hmac_path(char *mac_file, size_t mac_file_size,
const char *gnutls_path)
{
int ret;
char *p;
p = strrchr(gnutls_path, '/');
if (p == NULL)
ret = snprintf(mac_file, mac_file_size, ".%s.hmac",
gnutls_path);
else
ret = snprintf(mac_file, mac_file_size, "%.*s/.%s.hmac",
(int)(p - gnutls_path), gnutls_path, p + 1);
if ((size_t)ret >= mac_file_size)
return gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER);
ret = _gnutls_file_exists(mac_file);
if (ret == 0)
return GNUTLS_E_SUCCESS;
if (p == NULL)
ret = snprintf(mac_file, mac_file_size, "fipscheck/.%s.hmac",
gnutls_path);
else
ret = snprintf(mac_file, mac_file_size,
"%.*s/fipscheck/.%s.hmac",
(int)(p - gnutls_path), gnutls_path, p + 1);
if ((size_t)ret >= mac_file_size)
return gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER);
ret = _gnutls_file_exists(mac_file);
if (ret == 0)
return GNUTLS_E_SUCCESS;
return GNUTLS_E_FILE_ERROR;
}
/*
* load_hmac_file:
* @hmac_file: hmac file structure
* @hmac_path: path to the hmac file
*
* Loads the hmac file into the hmac file structure.
*
* Returns: 0 on success, a negative error code otherwise
*/
static int load_hmac_file(struct hmac_file *hmac_file, const char *hmac_path)
{
int ret;
FILE *stream;
stream = fopen(hmac_path, "r");
if (stream == NULL)
return gnutls_assert_val(GNUTLS_E_FILE_ERROR);
gnutls_memset(hmac_file, 0, sizeof(*hmac_file));
ret = ini_parse_file(stream, handler, hmac_file);
fclose(stream);
if (ret < 0)
return gnutls_assert_val(GNUTLS_E_PARSING_ERROR);
if (hmac_file->version != HMAC_FORMAT_VERSION)
return gnutls_assert_val(GNUTLS_E_PARSING_ERROR);
return 0;
}
/*
* check_lib_hmac:
* @entry: hmac file entry
* @path: path to the library which hmac should be compared
*
* Verify that HMAC from hmac file entry matches HMAC of given library.
*
* Returns: 0 on successful HMAC verification, a negative error code otherwise
*/
static int check_lib_hmac(struct hmac_entry *entry, const char *path)
{
int ret;
unsigned prev;
uint8_t hmac[HMAC_SIZE];
gnutls_datum_t data;
_gnutls_debug_log("Loading: %s\n", path);
ret = gnutls_load_file(path, &data);
if (ret < 0) {
_gnutls_debug_log("Could not load %s: %s\n", path,
gnutls_strerror(ret));
return gnutls_assert_val(ret);
}
prev = _gnutls_get_lib_state();
_gnutls_switch_lib_state(LIB_STATE_OPERATIONAL);
ret = gnutls_hmac_fast(HMAC_ALGO, FIPS_KEY, sizeof(FIPS_KEY) - 1,
data.data, data.size, hmac);
_gnutls_switch_lib_state(prev);
gnutls_free(data.data);
if (ret < 0) {
_gnutls_debug_log("Could not calculate HMAC for %s: %s\n", path,
gnutls_strerror(ret));
return gnutls_assert_val(ret);
}
if (gnutls_memcmp(entry->hmac, hmac, HMAC_SIZE)) {
_gnutls_debug_log("Calculated MAC for %s does not match\n",
path);
return gnutls_assert_val(GNUTLS_E_PARSING_ERROR);
}
_gnutls_debug_log("Successfully verified MAC for %s\n", path);
return 0;
}
#ifdef HAVE_DL_ITERATE_PHDR
static int callback(struct dl_phdr_info *info, size_t size, void *data)
{
const char *path = info->dlpi_name;
const char *soname = last_component(path);
struct lib_paths *paths = (struct lib_paths *)data;
if (!strcmp(soname, GNUTLS_LIBRARY_SONAME))
_gnutls_str_cpy(paths->gnutls, GNUTLS_PATH_MAX, path);
else if (!strcmp(soname, NETTLE_LIBRARY_SONAME))
_gnutls_str_cpy(paths->nettle, GNUTLS_PATH_MAX, path);
else if (!strcmp(soname, HOGWEED_LIBRARY_SONAME))
_gnutls_str_cpy(paths->hogweed, GNUTLS_PATH_MAX, path);
else if (!strcmp(soname, GMP_LIBRARY_SONAME))
_gnutls_str_cpy(paths->gmp, GNUTLS_PATH_MAX, path);
return 0;
}
static int load_lib_paths(struct lib_paths *paths)
{
memset(paths, 0, sizeof(*paths));
dl_iterate_phdr(callback, paths);
if (paths->gnutls[0] == '\0') {
_gnutls_debug_log("Gnutls library path was not found\n");
return gnutls_assert_val(GNUTLS_E_FILE_ERROR);
}
if (paths->nettle[0] == '\0') {
_gnutls_debug_log("Nettle library path was not found\n");
return gnutls_assert_val(GNUTLS_E_FILE_ERROR);
}
if (paths->hogweed[0] == '\0') {
_gnutls_debug_log("Hogweed library path was not found\n");
return gnutls_assert_val(GNUTLS_E_FILE_ERROR);
}
if (paths->gmp[0] == '\0') {
_gnutls_debug_log("Gmp library path was not found\n");
return gnutls_assert_val(GNUTLS_E_FILE_ERROR);
}
return GNUTLS_E_SUCCESS;
}
#else
static int load_lib_paths(struct lib_paths *paths)
{
(void)paths;
_gnutls_debug_log("Function dl_iterate_phdr is missing\n");
return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR);
}
#endif /* HAVE_DL_ITERATE_PHDR */
static int check_binary_integrity(void)
{
int ret;
struct lib_paths paths;
struct hmac_file hmac;
char hmac_path[GNUTLS_PATH_MAX];
ret = load_lib_paths(&paths);
if (ret < 0) {
_gnutls_debug_log("Could not load library paths: %s\n",
gnutls_strerror(ret));
return ret;
}
ret = get_hmac_path(hmac_path, sizeof(hmac_path), paths.gnutls);
if (ret < 0) {
_gnutls_debug_log("Could not get hmac file path: %s\n",
gnutls_strerror(ret));
return ret;
}
ret = load_hmac_file(&hmac, hmac_path);
if (ret < 0) {
_gnutls_debug_log("Could not load hmac file: %s\n",
gnutls_strerror(ret));
return ret;
}
ret = check_lib_hmac(&hmac.gnutls, paths.gnutls);
if (ret < 0)
return ret;
ret = check_lib_hmac(&hmac.nettle, paths.nettle);
if (ret < 0)
return ret;
ret = check_lib_hmac(&hmac.hogweed, paths.hogweed);
if (ret < 0)
return ret;
ret = check_lib_hmac(&hmac.gmp, paths.gmp);
if (ret < 0)
return ret;
return 0;
}
int _gnutls_fips_perform_self_checks1(void)
{
int ret;
/* Tests the FIPS algorithms used by nettle internally.
* In our case we test AES-CBC since nettle's AES is used by
* the DRBG-AES.
*/
/* ciphers - one test per cipher */
ret = gnutls_cipher_self_test(0, GNUTLS_CIPHER_AES_128_CBC);
if (ret < 0) {
return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
}
return 0;
}
int _gnutls_fips_perform_self_checks2(void)
{
int ret;
/* Tests the FIPS algorithms */
/* ciphers - one test per cipher */
ret = gnutls_cipher_self_test(0, GNUTLS_CIPHER_AES_256_CBC);
if (ret < 0) {
return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
}
ret = gnutls_cipher_self_test(0, GNUTLS_CIPHER_AES_256_GCM);
if (ret < 0) {
return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
}
ret = gnutls_cipher_self_test(0, GNUTLS_CIPHER_AES_256_XTS);
if (ret < 0) {
return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
}
ret = gnutls_cipher_self_test(0, GNUTLS_CIPHER_AES_256_CFB8);
if (ret < 0) {
return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
}
/* Digest tests */
ret = gnutls_digest_self_test(0, GNUTLS_DIG_SHA3_224);
if (ret < 0) {
return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
}
ret = gnutls_digest_self_test(0, GNUTLS_DIG_SHA3_256);
if (ret < 0) {
return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
}
ret = gnutls_digest_self_test(0, GNUTLS_DIG_SHA3_384);
if (ret < 0) {
return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
}
ret = gnutls_digest_self_test(0, GNUTLS_DIG_SHA3_512);
if (ret < 0) {
return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
}
/* MAC (includes message digest test) */
ret = gnutls_mac_self_test(0, GNUTLS_MAC_SHA1);
if (ret < 0) {
return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
}
ret = gnutls_mac_self_test(0, GNUTLS_MAC_SHA224);
if (ret < 0) {
return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
}
ret = gnutls_mac_self_test(0, GNUTLS_MAC_SHA256);
if (ret < 0) {
return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
}
ret = gnutls_mac_self_test(0, GNUTLS_MAC_SHA384);
if (ret < 0) {
return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
}
ret = gnutls_mac_self_test(0, GNUTLS_MAC_SHA512);
if (ret < 0) {
return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
}
ret = gnutls_mac_self_test(0, GNUTLS_MAC_AES_CMAC_256);
if (ret < 0) {
return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
}
/* PK */
ret = gnutls_pk_self_test(0, GNUTLS_PK_RSA);
if (ret < 0) {
return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
}
ret = gnutls_pk_self_test(0, GNUTLS_PK_DSA);
if (ret < 0) {
return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
}
ret = gnutls_pk_self_test(0, GNUTLS_PK_EC);
if (ret < 0) {
return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
}
ret = gnutls_pk_self_test(0, GNUTLS_PK_DH);
if (ret < 0) {
return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
}
/* HKDF */
ret = gnutls_hkdf_self_test(0, GNUTLS_MAC_SHA256);
if (ret < 0) {
return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
}
/* PBKDF2 */
ret = gnutls_pbkdf2_self_test(0, GNUTLS_MAC_SHA256);
if (ret < 0) {
return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
}
/* TLS-PRF */
ret = gnutls_tlsprf_self_test(0, GNUTLS_MAC_SHA256);
if (ret < 0) {
return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
}
if (_gnutls_rnd_ops.self_test == NULL) {
return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
}
/* this does not require rng initialization */
ret = _gnutls_rnd_ops.self_test();
if (ret < 0) {
return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
}
if (_skip_integrity_checks == 0) {
ret = check_binary_integrity();
if (ret < 0) {
return gnutls_assert_val(GNUTLS_E_SELF_TEST_ERROR);
}
}
return 0;
}
#endif
/**
* gnutls_fips140_mode_enabled:
*
* 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.
*
* Since: 3.3.0
**/
unsigned gnutls_fips140_mode_enabled(void)
{
#ifdef ENABLE_FIPS140
unsigned ret = _gnutls_fips_mode_enabled();
if (ret > GNUTLS_FIPS140_DISABLED) {
/* If the previous run of selftests has failed, return as if
* the FIPS mode is disabled. We could use HAVE_LIB_ERROR, if
* we can assume that all the selftests run atomically from
* the ELF constructor.
*/
if (_gnutls_get_lib_state() == LIB_STATE_ERROR)
return 0;
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);
}
void _gnutls_lib_force_operational(void)
{
_gnutls_switch_lib_state(LIB_STATE_OPERATIONAL);
}
/**
* gnutls_fips140_context_init:
* @context: location to store @gnutls_fips140_context_t
*
* Create and initialize the FIPS context object.
*
* Returns: 0 upon success, a negative error code otherwise
*
* Since: 3.7.3
*/
int gnutls_fips140_context_init(gnutls_fips140_context_t *context)
{
*context = gnutls_malloc(sizeof(struct gnutls_fips140_context_st));
if (!*context) {
return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR);
}
(*context)->state = GNUTLS_FIPS140_OP_INITIAL;
return 0;
}
/**
* gnutls_fips140_context_deinit:
* @context: a #gnutls_fips140_context_t
*
* Uninitialize and release the FIPS context @context.
*
* Since: 3.7.3
*/
void gnutls_fips140_context_deinit(gnutls_fips140_context_t context)
{
gnutls_free(context);
}
/**
* gnutls_fips140_get_operation_state:
* @context: a #gnutls_fips140_context_t
*
* Get the previous operation state of @context in terms of FIPS.
*
* Returns: a #gnutls_fips140_operation_state_t
*
* Since: 3.7.3
*/
gnutls_fips140_operation_state_t
gnutls_fips140_get_operation_state(gnutls_fips140_context_t context)
{
return context->state;
}
/**
* gnutls_fips140_push_context:
* @context: a #gnutls_fips140_context_t
*
* Associate the FIPS @context to the current thread, diverting the
* currently active context. If a cryptographic operation is ongoing
* in the current thread, e.g., gnutls_aead_cipher_init() is called
* but gnutls_aead_cipher_deinit() is not yet called, it returns an
* error %GNUTLS_E_INVALID_REQUEST.
*
* The operation state of @context will be reset to
* %GNUTLS_FIPS140_OP_INITIAL.
*
* This function is no-op if FIPS140 is not compiled in nor enabled
* at run-time.
*
* Returns: 0 upon success, a negative error code otherwise
*
* Since: 3.7.3
*/
int gnutls_fips140_push_context(gnutls_fips140_context_t context)
{
#ifdef ENABLE_FIPS140
if (_gnutls_fips_mode_enabled() != GNUTLS_FIPS140_DISABLED) {
context->next = _tfips_context;
_tfips_context = context;
context->state = GNUTLS_FIPS140_OP_INITIAL;
}
return 0;
#else
return GNUTLS_E_INVALID_REQUEST;
#endif
}
/**
* gnutls_fips140_pop_context:
*
* Dissociate the FIPS context currently
* active on the current thread, reverting to the previously active
* context. If a cryptographic operation is ongoing in the current
* thread, e.g., gnutls_aead_cipher_init() is called but
* gnutls_aead_cipher_deinit() is not yet called, it returns an error
* %GNUTLS_E_INVALID_REQUEST.
*
* This function is no-op if FIPS140 is not compiled in nor enabled
* at run-time.
*
* Returns: 0 upon success, a negative error code otherwise
*
* Since: 3.7.3
*/
int gnutls_fips140_pop_context(void)
{
#ifdef ENABLE_FIPS140
if (_gnutls_fips_mode_enabled() != GNUTLS_FIPS140_DISABLED) {
if (!_tfips_context) {
return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST);
}
_tfips_context = _tfips_context->next;
}
return 0;
#else
return GNUTLS_E_INVALID_REQUEST;
#endif
}
#ifdef ENABLE_FIPS140
static inline const char *
operation_state_to_string(gnutls_fips140_operation_state_t state)
{
switch (state) {
case GNUTLS_FIPS140_OP_INITIAL:
return "initial";
case GNUTLS_FIPS140_OP_APPROVED:
return "approved";
case GNUTLS_FIPS140_OP_NOT_APPROVED:
return "not-approved";
case GNUTLS_FIPS140_OP_ERROR:
return "error";
default:
/*NOTREACHED*/ assert(0);
return NULL;
}
}
void _gnutls_switch_fips_state(gnutls_fips140_operation_state_t state)
{
gnutls_fips_mode_t mode = _gnutls_fips_mode_enabled();
if (mode == GNUTLS_FIPS140_DISABLED) {
return;
}
if (!_tfips_context) {
_gnutls_debug_log("FIPS140-2 context is not set\n");
return;
}
if (_tfips_context->state == state) {
return;
}
switch (_tfips_context->state) {
case GNUTLS_FIPS140_OP_INITIAL:
/* initial can be transitioned to any state */
if (mode != GNUTLS_FIPS140_LAX) {
_gnutls_audit_log(
NULL,
"FIPS140-2 operation mode switched from initial to %s\n",
operation_state_to_string(state));
}
_tfips_context->state = state;
break;
case GNUTLS_FIPS140_OP_APPROVED:
/* approved can only be transitioned to not-approved */
if (likely(state == GNUTLS_FIPS140_OP_NOT_APPROVED)) {
if (mode != GNUTLS_FIPS140_LAX) {
_gnutls_audit_log(
NULL,
"FIPS140-2 operation mode switched from approved to %s\n",
operation_state_to_string(state));
}
_tfips_context->state = state;
return;
}
FALLTHROUGH;
default:
/* other transitions are prohibited */
if (mode != GNUTLS_FIPS140_LAX) {
_gnutls_audit_log(
NULL,
"FIPS140-2 operation mode cannot be switched from %s to %s\n",
operation_state_to_string(
_tfips_context->state),
operation_state_to_string(state));
}
break;
}
}
#else
void _gnutls_switch_fips_state(gnutls_fips140_operation_state_t state)
{
(void)state;
}
#endif
/**
* gnutls_fips140_run_self_tests:
*
* Manually perform the second round of the FIPS140 self-tests,
* including:
*
* - Known answer tests (KAT) for the selected set of symmetric
* cipher, MAC, public key, KDF, and DRBG
* - Library integrity checks
*
* Upon failure with FIPS140 mode enabled, it makes the library
* unusable. This function is not thread-safe.
*
* Returns: 0 upon success, a negative error code otherwise
*
* Since: 3.7.7
*/
int gnutls_fips140_run_self_tests(void)
{
#ifdef ENABLE_FIPS140
int ret;
unsigned prev_lib_state;
gnutls_fips140_context_t fips_context = NULL;
/* Save the FIPS context, because self tests change it */
if (gnutls_fips140_mode_enabled() != GNUTLS_FIPS140_DISABLED) {
if (gnutls_fips140_context_init(&fips_context) < 0 ||
gnutls_fips140_push_context(fips_context) < 0) {
gnutls_fips140_context_deinit(fips_context);
fips_context = NULL;
}
}
/* Temporarily switch to LIB_STATE_SELFTEST as some of the
* algorithms are implemented using special constructs in
* self-tests (such as deterministic variants) */
prev_lib_state = _gnutls_get_lib_state();
_gnutls_switch_lib_state(LIB_STATE_SELFTEST);
ret = _gnutls_fips_perform_self_checks2();
if (gnutls_fips140_mode_enabled() != GNUTLS_FIPS140_DISABLED &&
ret < 0) {
_gnutls_switch_lib_state(LIB_STATE_ERROR);
_gnutls_audit_log(NULL,
"FIPS140-2 self testing part 2 failed\n");
} else {
/* Restore the previous library state */
_gnutls_switch_lib_state(prev_lib_state);
}
/* Restore the previous FIPS context */
if (gnutls_fips140_mode_enabled() != GNUTLS_FIPS140_DISABLED &&
fips_context) {
if (gnutls_fips140_pop_context() < 0) {
_gnutls_switch_lib_state(LIB_STATE_ERROR);
_gnutls_audit_log(
NULL, "FIPS140-2 context restoration failed\n");
}
gnutls_fips140_context_deinit(fips_context);
}
return ret;
#else
return 0;
#endif
}