#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#ifdef ENABLE_HMAC_BINARY_CHECK
# include <dlfcn.h>
#endif
#ifdef HAVE_SYSLOG
# include <syslog.h>
#endif
#include "g10lib.h"
#include "cipher-proto.h"
#include "hmac256.h"
#define FIPS_FORCE_FILE "/etc/gcrypt/fips_enabled"
enum module_states
{
STATE_POWERON = 0,
STATE_INIT,
STATE_SELFTEST,
STATE_OPERATIONAL,
STATE_ERROR,
STATE_FATALERROR,
STATE_SHUTDOWN
};
static int no_fips_mode_required;
static int enforced_fips_mode;
static int inactive_fips_mode;
GPGRT_LOCK_DEFINE (fsm_lock);
static enum module_states current_state;
static void fips_new_state (enum module_states new_state);
#define loxtoi_1(p) (*(p) <= '9'? (*(p)- '0'): (*(p)-'a'+10))
#define loxtoi_2(p) ((loxtoi_1(p) * 16) + loxtoi_1((p)+1))
#define loxdigit_p(p) !!strchr ("01234567890abcdef", *(p))
void
_gcry_initialize_fips_mode (int force)
{
static int done;
gpg_error_t err;
if (done)
{
if ( fips_mode () )
{
fips_new_state (STATE_FATALERROR);
fips_noreturn ();
}
gcry_assert (!done);
}
done = 1;
if (force)
{
gcry_assert (!no_fips_mode_required);
goto leave;
}
if ( !access (FIPS_FORCE_FILE, F_OK) )
{
gcry_assert (!no_fips_mode_required);
goto leave;
}
{
static const char procfname[] = "/proc/sys/crypto/fips_enabled";
FILE *fp;
int saved_errno;
fp = fopen (procfname, "r");
if (fp)
{
char line[256];
if (fgets (line, sizeof line, fp) && atoi (line))
{
fclose (fp);
gcry_assert (!no_fips_mode_required);
goto leave;
}
fclose (fp);
}
else if ((saved_errno = errno) != ENOENT
&& saved_errno != EACCES
&& !access ("/proc/version", F_OK) )
{
log_info ("FATAL: error reading `%s' in libgcrypt: %s\n",
procfname, strerror (saved_errno));
#ifdef HAVE_SYSLOG
syslog (LOG_USER|LOG_ERR, "Libgcrypt error: "
"reading `%s' failed: %s - abort",
procfname, strerror (saved_errno));
#endif
abort ();
}
}
no_fips_mode_required = 1;
leave:
if (!no_fips_mode_required)
{
FILE *fp;
err = gpgrt_lock_init (&fsm_lock);
if (err)
{
log_info ("FATAL: failed to create the FSM lock in libgcrypt: %s\n",
gpg_strerror (err));
#ifdef HAVE_SYSLOG
syslog (LOG_USER|LOG_ERR, "Libgcrypt error: "
"creating FSM lock failed: %s - abort",
gpg_strerror (err));
#endif
abort ();
}
fp = fopen (FIPS_FORCE_FILE, "r");
if (fp)
{
char line[256];
if (fgets (line, sizeof line, fp) && atoi (line))
enforced_fips_mode = 1;
fclose (fp);
}
fips_new_state (STATE_INIT);
}
return;
}
static void
lock_fsm (void)
{
gpg_error_t err;
err = gpgrt_lock_lock (&fsm_lock);
if (err)
{
log_info ("FATAL: failed to acquire the FSM lock in libgrypt: %s\n",
gpg_strerror (err));
#ifdef HAVE_SYSLOG
syslog (LOG_USER|LOG_ERR, "Libgcrypt error: "
"acquiring FSM lock failed: %s - abort",
gpg_strerror (err));
#endif
abort ();
}
}
static void
unlock_fsm (void)
{
gpg_error_t err;
err = gpgrt_lock_unlock (&fsm_lock);
if (err)
{
log_info ("FATAL: failed to release the FSM lock in libgrypt: %s\n",
gpg_strerror (err));
#ifdef HAVE_SYSLOG
syslog (LOG_USER|LOG_ERR, "Libgcrypt error: "
"releasing FSM lock failed: %s - abort",
gpg_strerror (err));
#endif
abort ();
}
}
int
_gcry_fips_mode (void)
{
return !no_fips_mode_required;
}
int
_gcry_enforced_fips_mode (void)
{
if (!_gcry_fips_mode ())
return 0;
return enforced_fips_mode;
}
void
_gcry_set_enforced_fips_mode (void)
{
enforced_fips_mode = 1;
}
void
_gcry_inactivate_fips_mode (const char *text)
{
gcry_assert (_gcry_fips_mode ());
if (_gcry_enforced_fips_mode () )
{
fips_signal_error (text);
return;
}
lock_fsm ();
if (!inactive_fips_mode)
{
inactive_fips_mode = 1;
unlock_fsm ();
#ifdef HAVE_SYSLOG
syslog (LOG_USER|LOG_WARNING, "Libgcrypt warning: "
"%s - FIPS mode inactivated", text);
#endif
}
else
unlock_fsm ();
}
int
_gcry_is_fips_mode_inactive (void)
{
int flag;
if (!_gcry_fips_mode ())
return 0;
lock_fsm ();
flag = inactive_fips_mode;
unlock_fsm ();
return flag;
}
static const char *
state2str (enum module_states state)
{
const char *s;
switch (state)
{
case STATE_POWERON: s = "Power-On"; break;
case STATE_INIT: s = "Init"; break;
case STATE_SELFTEST: s = "Self-Test"; break;
case STATE_OPERATIONAL: s = "Operational"; break;
case STATE_ERROR: s = "Error"; break;
case STATE_FATALERROR: s = "Fatal-Error"; break;
case STATE_SHUTDOWN: s = "Shutdown"; break;
default: s = "?"; break;
}
return s;
}
int
_gcry_fips_is_operational (void)
{
int result;
if (!fips_mode ())
result = 1;
else
{
lock_fsm ();
if (current_state == STATE_INIT)
{
unlock_fsm ();
_gcry_fips_run_selftests (0);
lock_fsm ();
}
result = (current_state == STATE_OPERATIONAL);
unlock_fsm ();
}
return result;
}
int
_gcry_fips_test_operational (void)
{
int result;
if (!fips_mode ())
result = 1;
else
{
lock_fsm ();
result = (current_state == STATE_OPERATIONAL);
unlock_fsm ();
}
return result;
}
int
_gcry_fips_test_error_or_operational (void)
{
int result;
if (!fips_mode ())
result = 1;
else
{
lock_fsm ();
result = (current_state == STATE_OPERATIONAL
|| current_state == STATE_ERROR);
unlock_fsm ();
}
return result;
}
static void
reporter (const char *domain, int algo, const char *what, const char *errtxt)
{
if (!errtxt && !_gcry_log_verbosity (2))
return;
log_info ("libgcrypt selftest: %s %s%s (%d): %s%s%s%s\n",
!strcmp (domain, "hmac")? "digest":domain,
!strcmp (domain, "hmac")? "HMAC-":"",
!strcmp (domain, "cipher")? _gcry_cipher_algo_name (algo) :
!strcmp (domain, "digest")? _gcry_md_algo_name (algo) :
!strcmp (domain, "hmac")? _gcry_md_algo_name (algo) :
!strcmp (domain, "pubkey")? _gcry_pk_algo_name (algo) : "",
algo, errtxt? errtxt:"Okay",
what?" (":"", what? what:"", what?")":"");
}
static int
run_cipher_selftests (int extended)
{
static int algos[] =
{
GCRY_CIPHER_3DES,
GCRY_CIPHER_AES128,
GCRY_CIPHER_AES192,
GCRY_CIPHER_AES256,
0
};
int idx;
gpg_error_t err;
int anyerr = 0;
for (idx=0; algos[idx]; idx++)
{
err = _gcry_cipher_selftest (algos[idx], extended, reporter);
reporter ("cipher", algos[idx], NULL,
err? gpg_strerror (err):NULL);
if (err)
anyerr = 1;
}
return anyerr;
}
static int
run_digest_selftests (int extended)
{
static int algos[] =
{
GCRY_MD_SHA1,
GCRY_MD_SHA224,
GCRY_MD_SHA256,
GCRY_MD_SHA384,
GCRY_MD_SHA512,
0
};
int idx;
gpg_error_t err;
int anyerr = 0;
for (idx=0; algos[idx]; idx++)
{
err = _gcry_md_selftest (algos[idx], extended, reporter);
reporter ("digest", algos[idx], NULL,
err? gpg_strerror (err):NULL);
if (err)
anyerr = 1;
}
return anyerr;
}
static int
run_hmac_selftests (int extended)
{
static int algos[] =
{
GCRY_MD_SHA1,
GCRY_MD_SHA224,
GCRY_MD_SHA256,
GCRY_MD_SHA384,
GCRY_MD_SHA512,
GCRY_MD_SHA3_224,
GCRY_MD_SHA3_256,
GCRY_MD_SHA3_384,
GCRY_MD_SHA3_512,
0
};
int idx;
gpg_error_t err;
int anyerr = 0;
for (idx=0; algos[idx]; idx++)
{
err = _gcry_hmac_selftest (algos[idx], extended, reporter);
reporter ("hmac", algos[idx], NULL,
err? gpg_strerror (err):NULL);
if (err)
anyerr = 1;
}
return anyerr;
}
static int
run_pubkey_selftests (int extended)
{
static int algos[] =
{
GCRY_PK_RSA,
GCRY_PK_DSA,
GCRY_PK_ECC,
0
};
int idx;
gpg_error_t err;
int anyerr = 0;
for (idx=0; algos[idx]; idx++)
{
err = _gcry_pk_selftest (algos[idx], extended, reporter);
reporter ("pubkey", algos[idx], NULL,
err? gpg_strerror (err):NULL);
if (err)
anyerr = 1;
}
return anyerr;
}
static int
run_random_selftests (void)
{
gpg_error_t err;
err = _gcry_random_selftest (reporter);
reporter ("random", 0, NULL, err? gpg_strerror (err):NULL);
return !!err;
}
static int
check_binary_integrity (void)
{
#ifdef ENABLE_HMAC_BINARY_CHECK
gpg_error_t err;
Dl_info info;
unsigned char digest[32];
int dlen;
char *fname = NULL;
const char key[] = "What am I, a doctor or a moonshuttle conductor?";
if (!dladdr ("gcry_check_version", &info))
err = gpg_error_from_syserror ();
else
{
dlen = _gcry_hmac256_file (digest, sizeof digest, info.dli_fname,
key, strlen (key));
if (dlen < 0)
err = gpg_error_from_syserror ();
else if (dlen != 32)
err = gpg_error (GPG_ERR_INTERNAL);
else
{
fname = xtrymalloc (strlen (info.dli_fname) + 1 + 5 + 1 );
if (!fname)
err = gpg_error_from_syserror ();
else
{
FILE *fp;
char *p;
strcpy (fname, info.dli_fname);
p = strrchr (fname, '/');
if (p)
p++;
else
p = fname;
memmove (p+1, p, strlen (p)+1);
*p = '.';
strcat (fname, ".hmac");
fp = fopen (fname, "r");
if (!fp)
err = gpg_error_from_syserror ();
else
{
unsigned char buffer[64+1+1];
const unsigned char *s;
int n;
err = gpg_error (GPG_ERR_SELFTEST_FAILED);
n = fread (buffer, 1, sizeof buffer, fp);
if (n == 64
|| (n == 65 && buffer[64] == '\n')
|| (n == 66 && buffer[64] == ' ' && buffer[65] == ' '))
{
buffer[64] = 0;
for (n=0, s= buffer;
n < 32 && loxdigit_p (s) && loxdigit_p (s+1);
n++, s += 2)
buffer[n] = loxtoi_2 (s);
if ( n == 32 && !memcmp (digest, buffer, 32) )
err = 0;
}
fclose (fp);
}
}
}
}
reporter ("binary", 0, fname, err? gpg_strerror (err):NULL);
#ifdef HAVE_SYSLOG
if (err)
syslog (LOG_USER|LOG_ERR, "Libgcrypt error: "
"integrity check using `%s' failed: %s",
fname? fname:"[?]", gpg_strerror (err));
#endif
xfree (fname);
return !!err;
#else
return 0;
#endif
}
gpg_err_code_t
_gcry_fips_run_selftests (int extended)
{
enum module_states result = STATE_ERROR;
gcry_err_code_t ec = GPG_ERR_SELFTEST_FAILED;
if (fips_mode ())
fips_new_state (STATE_SELFTEST);
if (run_cipher_selftests (extended))
goto leave;
if (run_digest_selftests (extended))
goto leave;
if (run_hmac_selftests (extended))
goto leave;
if (run_random_selftests ())
goto leave;
if (run_pubkey_selftests (extended))
goto leave;
if (check_binary_integrity ())
goto leave;
result = STATE_OPERATIONAL;
ec = 0;
leave:
if (fips_mode ())
fips_new_state (result);
return ec;
}
void
_gcry_fips_signal_error (const char *srcfile, int srcline, const char *srcfunc,
int is_fatal, const char *description)
{
if (!fips_mode ())
return;
fips_new_state (is_fatal? STATE_FATALERROR : STATE_ERROR);
log_info ("%serror in libgcrypt, file %s, line %d%s%s: %s\n",
is_fatal? "fatal ":"",
srcfile, srcline,
srcfunc? ", function ":"", srcfunc? srcfunc:"",
description? description : "no description available");
#ifdef HAVE_SYSLOG
syslog (LOG_USER|LOG_ERR, "Libgcrypt error: "
"%serror in file %s, line %d%s%s: %s",
is_fatal? "fatal ":"",
srcfile, srcline,
srcfunc? ", function ":"", srcfunc? srcfunc:"",
description? description : "no description available");
#endif
}
static void
fips_new_state (enum module_states new_state)
{
int ok = 0;
enum module_states last_state;
lock_fsm ();
last_state = current_state;
switch (current_state)
{
case STATE_POWERON:
if (new_state == STATE_INIT
|| new_state == STATE_ERROR
|| new_state == STATE_FATALERROR)
ok = 1;
break;
case STATE_INIT:
if (new_state == STATE_SELFTEST
|| new_state == STATE_ERROR
|| new_state == STATE_FATALERROR)
ok = 1;
break;
case STATE_SELFTEST:
if (new_state == STATE_OPERATIONAL
|| new_state == STATE_ERROR
|| new_state == STATE_FATALERROR)
ok = 1;
break;
case STATE_OPERATIONAL:
if (new_state == STATE_SHUTDOWN
|| new_state == STATE_SELFTEST
|| new_state == STATE_ERROR
|| new_state == STATE_FATALERROR)
ok = 1;
break;
case STATE_ERROR:
if (new_state == STATE_SHUTDOWN
|| new_state == STATE_ERROR
|| new_state == STATE_FATALERROR
|| new_state == STATE_SELFTEST)
ok = 1;
break;
case STATE_FATALERROR:
if (new_state == STATE_SHUTDOWN )
ok = 1;
break;
case STATE_SHUTDOWN:
break;
}
if (ok)
{
current_state = new_state;
}
unlock_fsm ();
if (!ok || _gcry_log_verbosity (2))
log_info ("libgcrypt state transition %s => %s %s\n",
state2str (last_state), state2str (new_state),
ok? "granted":"denied");
if (!ok)
{
#ifdef HAVE_SYSLOG
syslog (LOG_USER|LOG_ERR,
"Libgcrypt error: invalid state transition %s => %s",
state2str (last_state), state2str (new_state));
#endif
fips_noreturn ();
}
else if (new_state == STATE_ERROR || new_state == STATE_FATALERROR)
{
#ifdef HAVE_SYSLOG
syslog (LOG_USER|LOG_WARNING,
"Libgcrypt notice: state transition %s => %s",
state2str (last_state), state2str (new_state));
#endif
}
}
void
_gcry_fips_noreturn (void)
{
#ifdef HAVE_SYSLOG
syslog (LOG_USER|LOG_ERR, "Libgcrypt terminated the application");
#endif
fflush (NULL);
abort ();
}