Files
resiprocate/rutil/Random.cxx
Scott Godin 495defc80f Adopt size_t in more places and remove compiler warnings
- change Data::size_type from uint32_t to size_t to more inline with C++ API standard - also helps remove 64-bit compilation warnings
- ares.h - fix include order issue that was causing redefinition errors or NO_ERROR on windows builds
2024-12-10 17:32:19 -05:00

550 lines
15 KiB
C++

#if defined(HAVE_CONFIG_H)
#include "config.h"
#endif
#include "rutil/ResipAssert.h"
#include <stdlib.h>
#ifdef WIN32
#include "rutil/Socket.hxx"
#include "rutil/DataStream.hxx"
#include "rutil/Data.hxx"
#else
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#endif
#include "rutil/Random.hxx"
#include "rutil/Timer.hxx"
#include "rutil/Mutex.hxx"
#include "rutil/Lock.hxx"
#include "rutil/Logger.hxx"
#ifdef USE_SSL
#ifdef WIN32
//hack for name collision of OCSP_RESPONSE and wincrypt.h in latest openssl release 0.9.8h
//http://www.google.com/search?q=OCSP%5fRESPONSE+wincrypt%2eh
//continue to watch this issue for a real fix.
#undef OCSP_RESPONSE
#endif
#include "rutil/ssl/OpenSSLInit.hxx"
# define USE_OPENSSL 1
#else
# define USE_OPENSSL 0
#endif
#if ( USE_OPENSSL == 1 )
# include <openssl/opensslv.h>
#if !defined(LIBRESSL_VERSION_NUMBER)
# include <openssl/e_os2.h>
#endif
# include <openssl/rand.h>
# include <openssl/err.h>
#endif
using namespace resip;
#define RESIPROCATE_SUBSYSTEM Subsystem::SIP
Mutex Random::mMutex;
bool Random::mIsInitialized = false;
#ifdef WIN32
Random::Initializer Random::mInitializer;
#ifdef RESIP_RANDOM_WIN32_RTL
BOOLEAN (APIENTRY *Random::RtlGenRandom)(void*, ULONG) = 0;
#endif
#endif //WIN32
#ifdef RESIP_RANDOM_THREAD_MUTEX
struct random_data* Random::sRandomState = 0;
#endif
#ifdef RESIP_RANDOM_THREAD_LOCAL
ThreadIf::TlsKey Random::sRandomStateKey = 0;
#endif
#define RANDOM_STATE_SIZE 128
const char*
Random::getImplName()
{
#ifdef WIN32
#if defined(RESIP_RANDOM_WIN32_RTL)
return "win32_rtl";
#else
return "win32_rand";
#endif
#else // WIN32
#if defined(RESIP_RANDOM_THREAD_LOCAL)
return "posix_thread_local";
#elif defined(RESIP_RANDOM_THREAD_MUTEX)
return "posix_thread_mutex";
#else
return "posix_random";
#endif
#endif // not WIN32
}
/**
Key goal is to make sure that each thread has distinct seed.
**/
unsigned
Random::getSimpleSeed()
{
// !cj! need to find a better way - use pentium random commands?
Data buffer;
{
DataStream strm(buffer);
#ifdef WIN32
strm << GetTickCount() << ":";
strm << GetCurrentProcessId() << ":";
strm << GetCurrentThreadId();
#else
// .kw. previously just used the lower 32bits of getTimeMs()
strm << ResipClock::getTimeMicroSec() << ":";
strm << getpid();
#if defined(RESIP_RANDOM_THREAD_LOCAL)
strm << ":" << ThreadIf::selfId();
#endif
#endif
}
return (unsigned int)buffer.hash();
}
void
Random::initialize()
{
#ifdef WIN32
//#if defined(USE_SSL)
#if 0 //!dcm! - this shouldn't be per thread for win32, and this is slow. Going
//to re-work openssl initialization
if ( !Random::mIsInitialized)
{
Lock lock(mMutex);
if (!Random::mIsInitialized)
{
mIsInitialized = true;
RAND_screen ();
}
}
#else
if (!Random::mInitializer.isInitialized())
{
Lock lock(mMutex);
if (!Random::mInitializer.isInitialized())
{
Random::mInitializer.setInitialized();
unsigned seed = getSimpleSeed();
srand(seed);
#ifdef RESIP_RANDOM_WIN32_RTL
// .jjg. from http://blogs.msdn.com/michael_howard/archive/2005/01/14/353379.aspx
// srand(..) and rand() have proven to be insufficient sources of randomness,
// leading to transaction id collisions in resip.
// SystemFunction036 maps to RtlGenRandom, which is used by rand_s() (which is available
// only with the VC 8.0 runtime or later) and is the Microsoft-recommended way of getting
// a random number. This code allows that functionality to be accessed even from VC 7.1.
// However, SystemFunction036 only exists in Windows XP and later, so we may need to fallback
// to the old method using rand().
HMODULE hLib = LoadLibrary("ADVAPI32.DLL");
if (hLib)
{
Random::RtlGenRandom =
(BOOLEAN (APIENTRY *)(void*,ULONG))GetProcAddress(hLib,"SystemFunction036");
if (!Random::RtlGenRandom)
{
WarningLog(<< "Using srand(..) and rand() for random numbers");
}
}
#endif // RESIP_RANDOM_WIN32_RTL
mIsInitialized = true;
}
}
#endif // not dead code
#else // WIN32
// ?dcm? -- OpenSSL will transparently initialize PRNG if /dev/urandom is
// present. In any case, will move into OpenSSLInit
if ( !Random::mIsInitialized)
{
Lock lock(mMutex);
if (!Random::mIsInitialized)
{
mIsInitialized = true;
Timer::setupTimeOffsets();
unsigned seed = getSimpleSeed();
#if defined(RESIP_RANDOM_THREAD_LOCAL)
ThreadIf::tlsKeyCreate(sRandomStateKey, ::free);
#elif defined(RESIP_RANDOM_THREAD_MUTEX)
struct random_data *buf;
size_t sz = sizeof(*buf)+RANDOM_STATE_SIZE;
buf = (struct random_data*) ::malloc(sz);
memset( buf, 0, sz); // .kw. strange segfaults without this
initstate_r(seed, ((char*)buf)+sizeof(*buf), RANDOM_STATE_SIZE, buf);
sRandomState = buf;
#else
srandom(seed);
#endif
int fd = open("/dev/urandom", O_RDONLY);
// !ah! blocks on embedded devices -- not enough entropy.
if ( fd != -1 )
{
int s = read( fd,&seed,sizeof(seed) ); //!ah! blocks if /dev/random on embedded sys
if ( s != sizeof(seed) )
{
ErrLog( << "System is short of randomness" ); // !ah! never prints
}
}
else
{
ErrLog( << "Could not open /dev/urandom" );
}
#if defined(USE_SSL)
if (fd == -1 )
{
// really bad sign - /dev/random does not exist so need to intialize
// OpenSSL some other way
// !cj! need to fix assert(0);
}
else
{
char buf[1024/8]; // size is number byes used for OpenSSL init
int s = read( fd,&buf,sizeof(buf) );
if ( s != sizeof(buf) )
{
ErrLog( << "System is short of randomness" );
}
RAND_add(buf,sizeof(buf),double(s*8));
}
#endif // SSL
if (fd != -1 )
{
::close(fd);
}
}
}
#endif // not WIN32
}
int
Random::getRandom()
{
initialize();
#ifdef WIN32
int ret = 0;
#ifdef RESIP_RANDOM_WIN32_RTL
// see comment in initialize()
if (Random::RtlGenRandom)
{
unsigned long buff[1];
ULONG ulCbBuff = sizeof(buff);
if (Random::RtlGenRandom(buff,ulCbBuff))
{
// .kw. all other impls here return positive number, so do the same...
ret = buff[0] & (~(1<<31));
return ret;
}
}
// fallback to using rand() if this is a Windows version previous to XP
#endif // RESIP_RANDOM_WIN32_RTL
{
// rand() returns [0,RAND_MAX], which on Windows is 15 bits and positive
// code below gets 30bits of randomness; with bit31 and bit15
// always zero; result is always positive
resip_assert( RAND_MAX == 0x7fff );
// WATCHOUT: on Linux, rand() returns 31bits, and assert above will fail
int r1 = rand();
int r2 = rand();
ret = (r1<<16) + r2;
}
return ret;
#else // WIN32
#if defined(RESIP_RANDOM_THREAD_LOCAL)
struct random_data *buf = (struct random_data*) ThreadIf::tlsGetValue(sRandomStateKey);
if ( buf==NULL ) {
size_t sz = sizeof(*buf)+RANDOM_STATE_SIZE;
buf = (struct random_data*) ::malloc(sz);
memset( buf, 0, sz); // .kw. strange segfaults without this
unsigned seed = getSimpleSeed();
initstate_r(seed, ((char*)buf)+sizeof(*buf), RANDOM_STATE_SIZE, buf);
ThreadIf::tlsSetValue(sRandomStateKey, buf);
}
int32_t ret;
random_r(buf, &ret);
return ret;
#elif defined(RESIP_RANDOM_THREAD_MUTEX)
int32_t ret;
{
Lock statelock(mMutex);
random_r(sRandomState, &ret);
}
return ret;
#else
// random returns [0,RAN_MAX]. On Linux, this is 31 bits and positive.
// On some platforms it might be on 15 bits, and will need to do something.
// assert( RAND_MAX == ((1<<31)-1) ); // ?slg? commented out assert since, RAND_MAX is not used in random(), it applies to rand() only
return random();
#endif // THREAD_LOCAL
#endif // WIN32
}
int
Random::getCryptoRandom()
{
initialize();
#if USE_OPENSSL
int ret;
int e = RAND_bytes( (unsigned char*)&ret , sizeof(ret) );
if ( e < 0 )
{
// error of some type - likely not enough rendomness to dod this
long err = ERR_get_error();
char buf[1024];
ERR_error_string_n(err,buf,sizeof(buf));
ErrLog( << buf );
resip_assert(0);
}
return ret;
#else
return getRandom();
#endif
}
Data
Random::getRandom(unsigned int len)
{
initialize();
resip_assert(len < Random::maxLength+1);
union
{
char cbuf[Random::maxLength+1];
unsigned int ibuf[(Random::maxLength+1)/sizeof(int)];
};
for (unsigned int count=0; count<(len+sizeof(int)-1)/sizeof(int); ++count)
{
ibuf[count] = Random::getRandom();
}
return Data(cbuf, len);
}
Data
Random::getCryptoRandom(unsigned int len)
{
unsigned char* buf = new unsigned char[len];
getCryptoRandom(buf, len); // USE_SSL check is in here
return Data(Data::Take, (char*)buf, len);
}
Data
Random::getRandomHex(unsigned int numBytes)
{
return Random::getRandom(numBytes).hex();
}
Data
Random::getRandomBase64(unsigned int numBytes)
{
return Random::getRandom(numBytes).base64encode();
}
Data
Random::getCryptoRandomHex(unsigned int numBytes)
{
return Random::getCryptoRandom(numBytes).hex();
}
Data
Random::getCryptoRandomBase64(unsigned int numBytes)
{
return Random::getCryptoRandom(numBytes).base64encode();
}
/*
[From RFC 4122]
The version 4 UUID is meant for generating UUIDs from truly-random or
pseudo-random numbers.
The algorithm is as follows:
o Set the two most significant bits (bits 6 and 7) of the
clock_seq_hi_and_reserved to zero and one, respectively.
o Set the four most significant bits (bits 12 through 15) of the
time_hi_and_version field to the 4-bit version number from
Section 4.1.3. (0 1 0 0)
o Set all the other bits to randomly (or pseudo-randomly) chosen
values.
UUID = time-low "-" time-mid "-"
time-high-and-version "-"
clock-seq-and-reserved
clock-seq-low "-" node
time-low = 4hexOctet
time-mid = 2hexOctet
time-high-and-version = 2hexOctet
clock-seq-and-reserved = hexOctet
clock-seq-low = hexOctet
node = 6hexOctet
hexOctet = hexDigit hexDigit
*/
Data
Random::getVersion4UuidUrn()
{
Data urn ("urn:uuid:");
urn += getCryptoRandomHex(4); // time-low
urn += "-";
urn += getCryptoRandomHex(2); // time-mid
urn += "-";
Data time_hi_and_version = Random::getCryptoRandom(2);
time_hi_and_version[0] &= 0x0f;
time_hi_and_version[0] |= 0x40;
urn += time_hi_and_version.hex();
urn += "-";
Data clock_seq_hi_and_reserved = Random::getCryptoRandom(1);
clock_seq_hi_and_reserved[0] &= 0x3f;
clock_seq_hi_and_reserved[0] |= 0x40;
urn += clock_seq_hi_and_reserved.hex();
urn += getCryptoRandomHex(1); // clock-seq-low
urn += "-";
urn += getCryptoRandomHex(6); // node
return urn;
}
void
Random::getCryptoRandom(unsigned char* buf, unsigned int numBytes)
{
resip_assert(numBytes < Random::maxLength+1);
#if USE_OPENSSL
initialize();
int e = RAND_bytes( (unsigned char*)buf , numBytes );
if ( e < 0 )
{
// error of some type - likely not enough rendomness to dod this
long err = ERR_get_error();
char buf[1024];
ERR_error_string_n(err,buf,sizeof(buf));
ErrLog( << buf );
resip_assert(0);
}
#else
// !bwc! Should optimize this.
Data temp=Random::getRandom(numBytes);
memcpy(buf, temp.data(), numBytes);
#endif
}
#ifdef WIN32
Random::Initializer::Initializer() : mThreadStorage(::TlsAlloc())
{
resip_assert(mThreadStorage != TLS_OUT_OF_INDEXES);
}
Random::Initializer::~Initializer()
{
::TlsFree(mThreadStorage);
}
void
Random::Initializer::setInitialized()
{
::TlsSetValue(mThreadStorage, (LPVOID) TRUE);
}
bool
Random::Initializer::isInitialized()
{
#pragma warning ( disable:4311)
#pragma warning ( disable:4302)
// Note: if value is not set yet then 0 (false) is returned
return (BOOL) ::TlsGetValue(mThreadStorage) == TRUE;
}
#endif
/* ====================================================================
* The Vovida Software License, Version 1.0
*
* Copyright (c) 2005. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The names "VOCAL", "Vovida Open Communication Application Library",
* and "Vovida Open Communication Application Library (VOCAL)" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact vocal@vovida.org.
*
* 4. Products derived from this software may not be called "VOCAL", nor
* may "VOCAL" appear in their name, without prior written
* permission of Vovida Networks, Inc.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
* NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL VOVIDA
* NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
* IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
* ====================================================================
*
* This software consists of voluntary contributions made by Vovida
* Networks, Inc. and many individuals on behalf of Vovida Networks,
* Inc. For more information on Vovida Networks, Inc., please see
* <http://www.vovida.org/>.
*
* vi: set shiftwidth=3 expandtab:
*/