diff --git a/.github/configs/os-check-linux.json b/.github/configs/os-check-linux.json index 9fb37b775d..95690318d0 100644 --- a/.github/configs/os-check-linux.json +++ b/.github/configs/os-check-linux.json @@ -268,6 +268,12 @@ {"name": "no-verify-oid-fpki", "minutes": 1.2, "configure": ["CPPFLAGS=-DNO_VERIFY_OID -DWOLFSSL_FPKI"]}, {"name": "no-verify-oid", "minutes": 1.1, "configure": ["CPPFLAGS=-DNO_VERIFY_OID"]}, +{"name": "rng-seed-device", "minutes": 1.1, + "comment": "Seed the RNG from a nominated device. /dev/urandom stands in for a hardware RNG so the WC_RNG_SEED_DEVICE read path is actually exercised on a runner.", + "configure": ["--with-rng-seed-device=/dev/urandom"]}, +{"name": "rng-seed-device-fallback", "minutes": 1.1, + "comment": "Nominated seed device that cannot be opened, so every test runs through the fallback to the default seed sources.", + "configure": ["--with-rng-seed-device=/nonexistent/hwrng"]}, {"name": "no-server-no-client-auth", "minutes": 1.0, "configure": ["CPPFLAGS=-DNO_WOLFSSL_SERVER -DWOLFSSL_NO_CLIENT_AUTH"]}, {"name": "no-wolfssl-client", "minutes": 1.0, diff --git a/CMakeLists.txt b/CMakeLists.txt index f35291e03d..72bac1f02f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3768,6 +3768,14 @@ if(WOLFSSL_ECC AND WOLFSSL_ECC_MIN_KEY_SZ) list(APPEND WOLFSSL_DEFINITIONS "-DECC_MIN_KEY_SZ=${WOLFSSL_ECC_MIN_KEY_SZ}") endif() +# RNG seed device (best effort, falls back to the default sources) +set(WOLFSSL_RNG_SEED_DEVICE "" CACHE STRING + "Seed the RNG from this device before the default sources, e.g. /dev/hwrng (default: disabled)") +if(WOLFSSL_RNG_SEED_DEVICE) + list(APPEND WOLFSSL_DEFINITIONS + "-DWC_RNG_SEED_DEVICE=\"${WOLFSSL_RNG_SEED_DEVICE}\"") +endif() + #################################################### # Additional crypto/TLS feature options (parity with configure.ac) diff --git a/cmake/options.h.in b/cmake/options.h.in index c7ecfd7c4c..78f4ce3c55 100644 --- a/cmake/options.h.in +++ b/cmake/options.h.in @@ -611,6 +611,8 @@ extern "C" { #cmakedefine MAX_EX_DATA @MAX_EX_DATA@ #undef ECC_MIN_KEY_SZ #cmakedefine ECC_MIN_KEY_SZ @ECC_MIN_KEY_SZ@ +#undef WC_RNG_SEED_DEVICE +#cmakedefine WC_RNG_SEED_DEVICE @WC_RNG_SEED_DEVICE@ /* parity: crypto/TLS feature options (wave B) */ #undef WOLFSSL_MD2 diff --git a/configure.ac b/configure.ac index 3b997f801b..3fe834e63d 100644 --- a/configure.ac +++ b/configure.ac @@ -8749,6 +8749,23 @@ then fi +# RNG seed device (best effort, falls back to the default sources) +AC_ARG_WITH([rng-seed-device], + [AS_HELP_STRING([--with-rng-seed-device=PATH],[Seed the RNG from PATH before the default sources, e.g. /dev/hwrng (default: disabled)])], + [ RNG_SEED_DEVICE=$withval ], + [ RNG_SEED_DEVICE=no ] + ) + +if test "$RNG_SEED_DEVICE" != "no" +then + if test "$RNG_SEED_DEVICE" = "yes" + then + RNG_SEED_DEVICE="/dev/hwrng" + fi + AM_CFLAGS="$AM_CFLAGS -DWC_RNG_SEED_DEVICE=\\\"$RNG_SEED_DEVICE\\\"" +fi + + # Filesystem Build if test "$HAVE_KERNEL_MODE" = "yes" then diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index a600356d9e..b5a0a24055 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -58,6 +58,9 @@ This library contains implementation for the random number generator. * FORCE_FAILURE_GETRANDOM: Force getrandom failure (testing) default: off * NO_DEV_RANDOM: Don't use /dev/random for seeding default: off * NO_DEV_URANDOM: Don't use /dev/urandom for seeding default: off + * WC_RNG_SEED_DEVICE: Device tried before the usual seed default: off + * sources. Must be a quoted string, + * e.g. -DWC_RNG_SEED_DEVICE='"/dev/hwrng"' * HAVE_INTEL_RDRAND: Use Intel RDRAND instruction default: off * HAVE_INTEL_RDSEED: Use Intel RDSEED instruction default: off * HAVE_AMD_RDSEED: Use AMD RDSEED instruction default: off @@ -352,6 +355,11 @@ enum { #error RNG_MAX_BLOCK_LEN is larger than NIST DBRG max request length #endif +/* the seed device is read through the filesystem API */ +#if defined(WC_RNG_SEED_DEVICE) && defined(NO_FILESYSTEM) + #error WC_RNG_SEED_DEVICE requires filesystem support +#endif + enum { drbgInitC = 0, drbgReseed = 1, @@ -5933,6 +5941,12 @@ int wc_GenerateSeed(OS_Seed* os, byte* output, word32 sz) int wc_GenerateSeed(OS_Seed* os, byte* output, word32 sz) { int ret = 0; + #ifdef WC_RNG_SEED_DEVICE + byte* devOut; + word32 devSz; + int devFd; + int devLen; + #endif /* Validate output before any entropy backend dereferences it: some * (e.g. glibc's vDSO getrandom()) fault on a NULL buffer rather than @@ -5971,6 +5985,43 @@ int wc_GenerateSeed(OS_Seed* os, byte* output, word32 sz) #if !defined(HAVE_ENTROPY_MEMUSE) || !defined(ENTROPY_MEMUSE_FORCE_FAILURE) + #ifdef WC_RNG_SEED_DEVICE + /* Nominated entropy source, usually a hardware RNG. Best effort: on + * any failure fall through to the default sources below. Those refill + * the whole request from the start, so bytes a partial read left in + * output are overwritten rather than mixed in. */ + devOut = output; + devSz = sz; + devFd = wc_open_cloexec(WC_RNG_SEED_DEVICE, O_RDONLY); + if (devFd != XBADFD) { + while (devSz > 0) { + errno = 0; + devLen = (int)read(devFd, devOut, devSz); + if (devLen < 0) { + if (errno == EINTR) + continue; /* interrupted, read again */ + break; + } + if (devLen == 0) + break; /* at EOF, will never fill the request */ + + devSz -= (word32)devLen; + devOut += devLen; + } + close(devFd); + } + #if defined(DEBUG_WOLFSSL) + if (devSz == 0) + WOLFSSL_MSG("seeded from WC_RNG_SEED_DEVICE."); + else + WOLFSSL_MSG("WC_RNG_SEED_DEVICE unusable, using default."); + #endif /* DEBUG_WOLFSSL */ + if (devSz == 0) { + /* success, we're done */ + return 0; + } + #endif /* WC_RNG_SEED_DEVICE */ + #if defined(HAVE_INTEL_RDSEED) || defined(HAVE_AMD_RDSEED) if (IS_INTEL_RDSEED(intel_flags)) { ret = wc_GenerateSeed_IntelRD(NULL, output, sz); @@ -6038,25 +6089,27 @@ int wc_GenerateSeed(OS_Seed* os, byte* output, word32 sz) #ifdef WOLFSSL_KEEP_RNG_SEED_FD_OPEN if (!os->seedFdOpen) { + os->fd = XBADFD; #ifndef NO_DEV_URANDOM /* way to disable use of /dev/urandom */ - os->fd = wc_open_cloexec("/dev/urandom", O_RDONLY); + if (os->fd == XBADFD) { + os->fd = wc_open_cloexec("/dev/urandom", O_RDONLY); #if defined(DEBUG_WOLFSSL) - WOLFSSL_MSG("opened /dev/urandom."); + if (os->fd != XBADFD) + WOLFSSL_MSG("opened /dev/urandom."); #endif /* DEBUG_WOLFSSL */ - if (os->fd == XBADFD) + } #endif /* NO_DEV_URANDOM */ - { + if (os->fd == XBADFD) { /* may still have /dev/random */ os->fd = wc_open_cloexec("/dev/random", O_RDONLY); #if defined(DEBUG_WOLFSSL) - WOLFSSL_MSG("opened /dev/random."); + if (os->fd != XBADFD) + WOLFSSL_MSG("opened /dev/random."); #endif /* DEBUG_WOLFSSL */ if (os->fd == XBADFD) return OPEN_RAN_E; - else { - os->keepSeedFdOpen = 0; - os->seedFdOpen = 1; - } + os->keepSeedFdOpen = 0; + os->seedFdOpen = 1; } else { os->keepSeedFdOpen = 1; @@ -6064,18 +6117,22 @@ int wc_GenerateSeed(OS_Seed* os, byte* output, word32 sz) } } #else /* WOLFSSL_KEEP_RNG_SEED_FD_OPEN */ - #ifndef NO_DEV_URANDOM /* way to disable use of /dev/urandom */ - os->fd = wc_open_cloexec("/dev/urandom", O_RDONLY); + os->fd = XBADFD; + #ifndef NO_DEV_URANDOM /* way to disable use of /dev/urandom */ + if (os->fd == XBADFD) { + os->fd = wc_open_cloexec("/dev/urandom", O_RDONLY); #if defined(DEBUG_WOLFSSL) - WOLFSSL_MSG("opened /dev/urandom."); + if (os->fd != XBADFD) + WOLFSSL_MSG("opened /dev/urandom."); #endif /* DEBUG_WOLFSSL */ - if (os->fd == XBADFD) - #endif /* !NO_DEV_URANDOM */ - { + } + #endif /* !NO_DEV_URANDOM */ + if (os->fd == XBADFD) { /* may still have /dev/random */ os->fd = wc_open_cloexec("/dev/random", O_RDONLY); #if defined(DEBUG_WOLFSSL) - WOLFSSL_MSG("opened /dev/random."); + if (os->fd != XBADFD) + WOLFSSL_MSG("opened /dev/random."); #endif /* DEBUG_WOLFSSL */ if (os->fd == XBADFD) return OPEN_RAN_E; @@ -6086,7 +6143,8 @@ int wc_GenerateSeed(OS_Seed* os, byte* output, word32 sz) #endif /* DEBUG_WOLFSSL */ while (sz) { int len = (int)read(os->fd, output, sz); - if (len == -1) { + /* EOF never fills the request, don't retry forever */ + if (len <= 0) { ret = READ_RAN_E; break; }