ext-cryptopp/osrng.cpp
Jeffrey Walton b41f1f033b
Add O_NOFOLLOW when available in OS rngs
This should help prevent symlink race attacks
2021-09-25 15:32:27 -04:00

309 lines
7.9 KiB
C++

// osrng.cpp - originally written and placed in the public domain by Wei Dai
// Thanks to Leonard Janke for the suggestion for AutoSeededRandomPool.
#include "pch.h"
#include "config.h"
#ifndef CRYPTOPP_IMPORTS
// Win32 has CryptoAPI and <wincrypt.h>. Windows 10 and Windows Store 10 have CNG and <bcrypt.h>.
// There's a hole for Windows Phone 8 and Windows Store 8. There is no userland RNG available.
// Also see http://www.drdobbs.com/windows/using-c-and-com-with-winrt/240168150 and
// http://stackoverflow.com/questions/36974545/random-numbers-for-windows-phone-8-and-windows-store-8 and
// https://social.msdn.microsoft.com/Forums/vstudio/en-US/25b83e13-c85f-4aa1-a057-88a279ea3fd6/what-crypto-random-generator-c-code-could-use-on-wp81
#if defined(CRYPTOPP_WIN32_AVAILABLE) && !defined(OS_RNG_AVAILABLE)
# pragma message("WARNING: Compiling for Windows but an OS RNG is not available. This is likely a Windows Phone 8 or Windows Store 8 app.")
#endif
#if !defined(NO_OS_DEPENDENCE) && defined(OS_RNG_AVAILABLE)
#include "osrng.h"
#include "rng.h"
#ifdef CRYPTOPP_WIN32_AVAILABLE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#ifndef ERROR_INCORRECT_SIZE
# define ERROR_INCORRECT_SIZE 0x000005B6
#endif
#if defined(USE_MS_CRYPTOAPI)
#include <wincrypt.h>
#ifndef CRYPT_NEWKEYSET
# define CRYPT_NEWKEYSET 0x00000008
#endif
#ifndef CRYPT_MACHINE_KEYSET
# define CRYPT_MACHINE_KEYSET 0x00000020
#endif
#elif defined(USE_MS_CNGAPI)
#include <bcrypt.h>
#ifndef BCRYPT_SUCCESS
# define BCRYPT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#endif
#ifndef STATUS_INVALID_PARAMETER
# define STATUS_INVALID_PARAMETER 0xC000000D
#endif
#ifndef STATUS_INVALID_HANDLE
# define STATUS_INVALID_HANDLE 0xC0000008
#endif
#endif
#endif // Win32
#ifdef CRYPTOPP_UNIX_AVAILABLE
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#endif
NAMESPACE_BEGIN(CryptoPP)
#if defined(NONBLOCKING_RNG_AVAILABLE) || defined(BLOCKING_RNG_AVAILABLE)
OS_RNG_Err::OS_RNG_Err(const std::string &operation)
: Exception(OTHER_ERROR, "OS_Rng: " + operation + " operation failed with error " +
#ifdef CRYPTOPP_WIN32_AVAILABLE
"0x" + IntToString(GetLastError(), 16)
#else
IntToString(errno)
#endif
)
{
}
#endif
#ifdef NONBLOCKING_RNG_AVAILABLE
#ifdef CRYPTOPP_WIN32_AVAILABLE
#if defined(USE_MS_CNGAPI)
inline DWORD NtStatusToErrorCode(NTSTATUS status)
{
if (status == STATUS_INVALID_PARAMETER)
return ERROR_INVALID_PARAMETER;
else if (status == STATUS_INVALID_HANDLE)
return ERROR_INVALID_HANDLE;
else
return (DWORD)status;
}
#endif
#if defined(UNICODE) || defined(_UNICODE)
# define CRYPTOPP_CONTAINER L"Crypto++ RNG"
#else
# define CRYPTOPP_CONTAINER "Crypto++ RNG"
#endif
MicrosoftCryptoProvider::MicrosoftCryptoProvider() : m_hProvider(0)
{
#if defined(USE_MS_CRYPTOAPI)
// See http://support.microsoft.com/en-us/kb/238187 for CRYPT_NEWKEYSET fallback strategy
if (!CryptAcquireContext(&m_hProvider, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
{
const DWORD firstErr = GetLastError();
if (!CryptAcquireContext(&m_hProvider, CRYPTOPP_CONTAINER, 0, PROV_RSA_FULL, CRYPT_NEWKEYSET /*user*/) &&
!CryptAcquireContext(&m_hProvider, CRYPTOPP_CONTAINER, 0, PROV_RSA_FULL, CRYPT_MACHINE_KEYSET|CRYPT_NEWKEYSET))
{
// Set original error with original code
SetLastError(firstErr);
throw OS_RNG_Err("CryptAcquireContext");
}
}
#elif defined(USE_MS_CNGAPI)
NTSTATUS ret = BCryptOpenAlgorithmProvider(&m_hProvider, BCRYPT_RNG_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0);
if (!(BCRYPT_SUCCESS(ret)))
{
// Hack... OS_RNG_Err calls GetLastError()
SetLastError(NtStatusToErrorCode(ret));
throw OS_RNG_Err("BCryptOpenAlgorithmProvider");
}
#endif
}
MicrosoftCryptoProvider::~MicrosoftCryptoProvider()
{
#if defined(USE_MS_CRYPTOAPI)
if (m_hProvider)
CryptReleaseContext(m_hProvider, 0);
#elif defined(USE_MS_CNGAPI)
if (m_hProvider)
BCryptCloseAlgorithmProvider(m_hProvider, 0);
#endif
}
#endif // CRYPTOPP_WIN32_AVAILABLE
NonblockingRng::NonblockingRng()
{
#ifndef CRYPTOPP_WIN32_AVAILABLE
# ifdef O_NOFOLLOW
const int flags = O_RDONLY|O_NOFOLLOW;
# else
const int flags = O_RDONLY;
# endif
m_fd = open("/dev/urandom", flags);
if (m_fd == -1)
throw OS_RNG_Err("open /dev/urandom");
#endif
}
NonblockingRng::~NonblockingRng()
{
#ifndef CRYPTOPP_WIN32_AVAILABLE
close(m_fd);
#endif
}
void NonblockingRng::GenerateBlock(byte *output, size_t size)
{
#ifdef CRYPTOPP_WIN32_AVAILABLE
// Acquiring a provider is expensive. Do it once and retain the reference.
# if defined(CRYPTOPP_CXX11_STATIC_INIT)
static const MicrosoftCryptoProvider hProvider = MicrosoftCryptoProvider();
# else
const MicrosoftCryptoProvider &hProvider = Singleton<MicrosoftCryptoProvider>().Ref();
# endif
# if defined(USE_MS_CRYPTOAPI)
DWORD dwSize;
CRYPTOPP_ASSERT(SafeConvert(size, dwSize));
if (!SafeConvert(size, dwSize))
{
SetLastError(ERROR_INCORRECT_SIZE);
throw OS_RNG_Err("GenerateBlock size");
}
BOOL ret = CryptGenRandom(hProvider.GetProviderHandle(), dwSize, output);
CRYPTOPP_ASSERT(ret != FALSE);
if (ret == FALSE)
throw OS_RNG_Err("CryptGenRandom");
# elif defined(USE_MS_CNGAPI)
ULONG ulSize;
CRYPTOPP_ASSERT(SafeConvert(size, ulSize));
if (!SafeConvert(size, ulSize))
{
SetLastError(ERROR_INCORRECT_SIZE);
throw OS_RNG_Err("GenerateBlock size");
}
NTSTATUS ret = BCryptGenRandom(hProvider.GetProviderHandle(), output, ulSize, 0);
CRYPTOPP_ASSERT(BCRYPT_SUCCESS(ret));
if (!(BCRYPT_SUCCESS(ret)))
{
// Hack... OS_RNG_Err calls GetLastError()
SetLastError(NtStatusToErrorCode(ret));
throw OS_RNG_Err("BCryptGenRandom");
}
# endif
#else
while (size)
{
ssize_t len = read(m_fd, output, size);
if (len < 0)
{
// /dev/urandom reads CAN give EAGAIN errors! (maybe EINTR as well)
if (errno != EINTR && errno != EAGAIN)
throw OS_RNG_Err("read /dev/urandom");
continue;
}
output += len;
size -= len;
}
#endif // CRYPTOPP_WIN32_AVAILABLE
}
#endif // NONBLOCKING_RNG_AVAILABLE
// *************************************************************
#ifdef BLOCKING_RNG_AVAILABLE
#ifndef CRYPTOPP_BLOCKING_RNG_FILENAME
#ifdef __OpenBSD__
#define CRYPTOPP_BLOCKING_RNG_FILENAME "/dev/srandom"
#else
#define CRYPTOPP_BLOCKING_RNG_FILENAME "/dev/random"
#endif
#endif
BlockingRng::BlockingRng()
{
# ifdef O_NOFOLLOW
const int flags = O_RDONLY|O_NOFOLLOW;
# else
const int flags = O_RDONLY;
# endif
m_fd = open(CRYPTOPP_BLOCKING_RNG_FILENAME, flags);
if (m_fd == -1)
throw OS_RNG_Err("open " CRYPTOPP_BLOCKING_RNG_FILENAME);
}
BlockingRng::~BlockingRng()
{
close(m_fd);
}
void BlockingRng::GenerateBlock(byte *output, size_t size)
{
while (size)
{
// on some systems /dev/random will block until all bytes
// are available, on others it returns immediately
ssize_t len = read(m_fd, output, size);
if (len < 0)
{
// /dev/random reads CAN give EAGAIN errors! (maybe EINTR as well)
if (errno != EINTR && errno != EAGAIN)
throw OS_RNG_Err("read " CRYPTOPP_BLOCKING_RNG_FILENAME);
continue;
}
size -= len;
output += len;
if (size)
sleep(1);
}
}
#endif // BLOCKING_RNG_AVAILABLE
// *************************************************************
void OS_GenerateRandomBlock(bool blocking, byte *output, size_t size)
{
#ifdef NONBLOCKING_RNG_AVAILABLE
if (blocking)
#endif
{
#ifdef BLOCKING_RNG_AVAILABLE
BlockingRng rng;
rng.GenerateBlock(output, size);
#endif
}
#ifdef BLOCKING_RNG_AVAILABLE
if (!blocking)
#endif
{
#ifdef NONBLOCKING_RNG_AVAILABLE
NonblockingRng rng;
rng.GenerateBlock(output, size);
#endif
}
}
void AutoSeededRandomPool::Reseed(bool blocking, unsigned int seedSize)
{
SecByteBlock seed(seedSize);
OS_GenerateRandomBlock(blocking, seed, seedSize);
IncorporateEntropy(seed, seedSize);
}
NAMESPACE_END
#endif // OS_RNG_AVAILABLE
#endif // CRYPTOPP_IMPORTS