Commit 4333f4c5 authored by Greg Zaverucha's avatar Greg Zaverucha

[IOT-1313, IOT-1300] Improve random number generation

Update ocrandom.c to use platform specific, secure random number
generators instead of rand(). Update the API in ocrandom.h, and make
corresponding updates to callers. Avoid using FP arithmetic in
OCGetRandomRange.

Change-Id: Ic0476dc69f1c649c4a07a46b99643cf7206f83af
Signed-off-by: default avatarGreg Zaverucha <gregz@microsoft.com>
Reviewed-on: https://gerrit.iotivity.org/gerrit/15243Tested-by: default avatarjenkins-iotivity <jenkins-iotivity@opendaylight.org>
Reviewed-by: default avatarKevin Kane <kkane@microsoft.com>
Reviewed-by: default avatarDan Mihai <Daniel.Mihai@microsoft.com>
parent 15fb62fd
......@@ -24,104 +24,76 @@
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifndef ARDUINO
#include <time.h>
#else
#ifdef ARDUINO
// MEGA has 16 input pins whereas Due has only 12 input pins
#define ANALOG_IN (10)
#endif
/* Number of bytes in a UUID. */
#define UUID_SIZE (16)
// The characters are 36 long, 37 for the null-term
#define UUID_STRING_SIZE (37)
typedef enum
{
RAND_UUID_OK = 0,
RAND_UUID_INVALID_PARAM = -1,
RAND_UUID_READ_ERROR = -2,
RAND_UUID_CONVERT_ERROR = -3
} OCRandomUuidResult;
/**
* Seed the random number generator. Seeding depends on platform.
* Android and Linux uses current time. Arduino uses Analog reading on pin ANALOG_IN
* @retval 0 for Success, otherwise some error value
/*
* Size of a UUID string.
* IoTivity formats UUIDs as strings following RFC 4122, Section 3.
* For example, "f81d4fae-7dec-11d0-a765-00a0c91e6bf6".
* This requires 36 characters, plus one for the null terminator.
*/
int8_t OCSeedRandom();
#define UUID_STRING_SIZE (37)
/**
* Generate a uniformly [0,2^32] distributed random number
* @retval On Success, it returns the random value.
* Generate a uniformly distributed 32-bit random number.
* @retval On success, it returns the random value.
*/
uint32_t OCGetRandom();
/**
* Generate a uniformly [0,2^8] distributed random number
* @retval On Success, it returns the random value, otherwise -1 for error.
*/
uint8_t OCGetRandomByte(void);
/**
* Generate a uniformly distributed 8-bit (byte) array random numbers
* @param[out] location
* memory location to start filling with random bytes
* Generate an array of uniformly distributed random bytes.
* @param[out] output
* Array to fill with random bytes
* @param[in] len
* length of array to be filled with random bytes
* Length of array
* @retval true for success, otherwise false and an error is logged
*/
void OCFillRandomMem(uint8_t * location, uint16_t len);
bool OCGetRandomBytes(uint8_t * output, size_t len);
/*
* Generate a uniformly distributed number on the defined bounded range
/**
* Generate a uniformly distributed number in a given range.
* @param[in] firstBound
* the first bound of the range
* The output is greater than or equal to firstBound
* @param[in] secondBound
* the second bound of the range
* The output is less than or equal to secondBound
*/
uint32_t OCGetRandomRange(uint32_t firstBound, uint32_t secondBound);
/**
* Generate a Uniformly Unique Identifier based on RFC4122 and
* provide it as a 16 byte byte-array
* provide it as UUID_SIZE bytes.
*
* @param[out] uuid
* the 16 byte array to fill with the UUID data
* of a new UUID
* UUID_SIZE array to hold the new UUID
*
* @retval RAND_UUID_OK for success, otherwise an error value
* @retval true for success, otherwise false and an error is logged
*/
OCRandomUuidResult OCGenerateUuid(uint8_t uuid[UUID_SIZE]);
bool OCGenerateUuid(uint8_t uuid[UUID_SIZE]);
/**
* Generate a Uniformly Unique Identifier based on RFC4122 and
* provide it as a C style string.
*
* @param[out] uuidString
* a 37-byte length string to fill with the string
* representation of a new UUID. Size is 32 chars
* for the hex data, 4 for '-' characters, and 1
* for the NULL terminator
*
* @retval RAND_UUID_OK for success, otherwise an error value
*/
OCRandomUuidResult OCGenerateUuidString(char uuidString[UUID_STRING_SIZE]);
/**
* Convert a UUID generated by OCGenerateUuid to a C style string
* based on RFC 4122
* Convert a UUID generated by OCGenerateUuid to a C string
* based on RFC 4122.
*
* @param[in] uuid
* The 16 byte array filled with UUID data by OCGenerateUuid
* Array of length UUID_SIZE bytes with output of OCGenerateUuid
* @param[out] uuidString
* a 37 byte length string to fill with the string
* representation of the passed UUID.
* @retval RAND_UUID_OK for success, otherwise an error value
* A UUID_STRING_SIZE length string to hold the string
* representation of the input UUID.
* @retval true for success, otherwise false and an error is logged
*/
OCRandomUuidResult OCConvertUuidToString(const uint8_t uuid[UUID_SIZE],
bool OCConvertUuidToString(const uint8_t uuid[UUID_SIZE],
char uuidString[UUID_STRING_SIZE]);
/**
......@@ -132,9 +104,9 @@ OCRandomUuidResult OCConvertUuidToString(const uint8_t uuid[UUID_SIZE],
* representation of the passed UUID.
* @param[out] uuid
* The 16 byte array filled with UUID data
* @retval RAND_UUID_OK for success, otherwise an error value
* @retval true for success, otherwise false and an error is logged
*/
OCRandomUuidResult OCConvertStringToUuid(const char uuidString[UUID_STRING_SIZE],
bool OCConvertStringToUuid(const char uuidString[UUID_STRING_SIZE],
uint8_t uuid[UUID_SIZE]);
#ifdef __cplusplus
......
......@@ -31,6 +31,7 @@
#endif
#include "iotivity_config.h"
#include "logger.h"
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
......@@ -46,34 +47,39 @@
#elif defined(HAVE_STRINGS_H)
#include <strings.h>
#endif
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_TIME_H
#include <time.h>
#endif
#if defined(__ANDROID__)
#include <ctype.h>
#include <linux/time.h>
#endif
#ifdef HAVE_WINDOWS_H
#include <windows.h>
#endif
#include "ocrandom.h"
#include <stdio.h>
#include <stdbool.h>
#include <assert.h>
#ifdef HAVE_UUID_UUID_H
#include <uuid/uuid.h>
#endif
#define OC_MIN(A,B) ((A)<(B)?(A):(B))
#define NANO_SEC 1000000000
/**
* @def OCRANDOM_TAG
* @brief Logging tag for module name
*/
#define OCRANDOM_TAG "OIC_OCRANDOM"
#ifdef ARDUINO
#include "Arduino.h"
// ARM GCC compiler doesnt define srandom function.
#if defined(ARDUINO) && !defined(ARDUINO_ARCH_SAM)
#define HAVE_SRANDOM 1
/*
* ARM GCC compiler doesnt define random/srandom functions, fallback to
* rand/srand.
*/
#if !defined(ARDUINO_ARCH_SAM)
#define OC_arduino_srandom_function srandom
#define OC_arduino_random_function random
#elif
#define OC_arduino_srandom_function srand
#define OC_arduino_random_function rand
#endif
uint8_t GetRandomBitRaw()
......@@ -116,252 +122,180 @@ uint8_t GetRandomBit()
// For other cases, try again.
}
}
#endif
int8_t OCSeedRandom()
/*
* Currently, only the Arduino platform requires seeding. It's done
* automatically on the first call to OCGetRandomBytes.
*/
uint8_t g_isSeeded = 0;
static void OCSeedRandom()
{
#ifndef ARDUINO
// Get current time to Seed.
uint64_t currentTime = 0;
#ifdef __ANDROID__
struct timespec getTs;
clock_gettime(CLOCK_MONOTONIC, &getTs);
currentTime = (getTs.tv_sec * (uint64_t)NANO_SEC + getTs.tv_nsec)/1000;
#elif _WIN32
LARGE_INTEGER count;
if (QueryPerformanceCounter(&count)) {
currentTime = count.QuadPart;
}
#elif _POSIX_TIMERS > 0
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
currentTime = (ts.tv_sec * (uint64_t)NANO_SEC + ts.tv_nsec)/ 1000;
#else
struct timeval tv;
gettimeofday(&tv, NULL);
currentTime = tv.tv_sec * (uint64_t)1000000 + tv.tv_usec;
#endif
#if defined(__unix__) || defined(__APPLE__)
int32_t fd = open("/dev/urandom", O_RDONLY);
if (fd >= 0)
{
uint32_t randomSeed = 0;
uint32_t totalRead = 0; //how many integers were read
int32_t currentRead = 0;
while (totalRead < sizeof(randomSeed))
{
currentRead = read(fd, (uint8_t*) &randomSeed + totalRead,
sizeof(randomSeed) - totalRead);
if (currentRead > 0)
{
totalRead += currentRead;
}
}
close(fd);
srand(randomSeed | currentTime);
}
else
#endif
if (g_isSeeded)
{
// Do time based seed when problem in accessing "/dev/urandom"
srand(currentTime);
return;
}
return 0;
#elif defined ARDUINO
uint32_t result =0;
uint8_t i;
for (i=32; i--;)
{
result += result + GetRandomBit();
}
#if HAVE_SRANDOM
srandom(result);
#else
srand(result);
#endif
return 0;
#endif
OC_arduino_srandom_function(result);
g_isSeeded = 1;
return;
}
void OCFillRandomMem(uint8_t * location, uint16_t len)
#endif /* ARDUINO */
bool OCGetRandomBytes(uint8_t * output, size_t len)
{
if (!location)
if ( (output == NULL) || (len == 0) )
{
return;
return false;
}
for (; len--;)
#if defined(__unix__) || defined(__APPLE__)
FILE* urandom = fopen("/dev/urandom", "r");
if (urandom == NULL)
{
*location++ = OCGetRandomByte();
OIC_LOG(FATAL, OCRANDOM_TAG, "Failed open /dev/urandom!");
assert(false);
return false;
}
}
uint32_t OCGetRandom()
{
uint32_t result = 0;
OCFillRandomMem((uint8_t*) &result, 4);
return result;
}
uint8_t OCGetRandomByte(void)
{
#ifdef HAVE_SRANDOM
return random() & 0x00FF;
#else
return rand() & 0x00FF;
#endif
}
uint32_t OCGetRandomRange(uint32_t firstBound, uint32_t secondBound)
{
uint32_t base;
uint32_t diff;
uint32_t result;
if (firstBound > secondBound)
if (fread(output, sizeof(uint8_t), len, urandom) != len)
{
base = secondBound;
diff = firstBound - secondBound;
OIC_LOG(FATAL, OCRANDOM_TAG, "Failed while reading /dev/urandom!");
assert(false);
fclose(urandom);
return false;
}
else if (firstBound < secondBound)
fclose(urandom);
#elif defined(_WIN32)
/*
* size_t may be 64 bits, but ULONG is always 32.
* If len is larger than the maximum for ULONG, just fail.
* It's unlikely anything ever will want to ask for this much randomness.
*/
if (len > 0xFFFFFFFFULL)
{
base = firstBound;
diff = secondBound - firstBound;
OIC_LOG(FATAL, OCRANDOM_TAG, "Requested number of bytes too large for ULONG");
assert(false);
return false;
}
else
NTSTATUS status = BCryptGenRandom(NULL, output, (ULONG)len, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
if (!BCRYPT_SUCCESS(status))
{
return secondBound;
OIC_LOG_V(FATAL, OCRANDOM_TAG, "BCryptGenRandom failed (%X)!", status);
assert(false);
return false;
}
result = ((float)OCGetRandom()/((float)(0xFFFFFFFF))*(float)diff) + (float) base;
return result;
}
#if defined(__ANDROID__)
uint8_t parseUuidChar(char c)
{
if (isdigit(c))
#elif defined(ARDUINO)
if (!g_isSeeded)
{
return c - '0';
OCSeedRandom();
}
else
size_t i;
for (i = 0; i < len; i++)
{
return c - 'a' + 10;
output[i] = OC_arduino_random_function() & 0x00ff;
}
}
uint8_t parseUuidPart(const char *c)
{
return (parseUuidChar(c[0])<<4) + parseUuidChar(c[1]);
}
#else
#error Unrecognized platform
#endif
OCRandomUuidResult OCGenerateUuid(uint8_t uuid[UUID_SIZE])
return true;
}
uint32_t OCGetRandom()
{
if (!uuid)
uint32_t result = 0;
if (!OCGetRandomBytes((uint8_t*)&result, sizeof(result)))
{
return RAND_UUID_INVALID_PARAM;
OIC_LOG(FATAL, OCRANDOM_TAG, "OCGetRandom failed!");
assert(false);
}
#if defined(__ANDROID__)
char uuidString[UUID_STRING_SIZE];
int8_t ret = OCGenerateUuidString(uuidString);
return result;
}
if (ret < 0)
/* Return the number of leading zeroes in x.
* Binary search algorithm from Section 5-3 of:
* H.S. Warren Jr. Hacker's Delight. Addison-Wesley. 2003.
*/
static int nlz(uint32_t x)
{
if (x == 0)
{
return ret;
return 32;
}
uuid[ 0] = parseUuidPart(&uuidString[0]);
uuid[ 1] = parseUuidPart(&uuidString[2]);
uuid[ 2] = parseUuidPart(&uuidString[4]);
uuid[ 3] = parseUuidPart(&uuidString[6]);
uuid[ 4] = parseUuidPart(&uuidString[9]);
uuid[ 5] = parseUuidPart(&uuidString[11]);
uuid[ 6] = parseUuidPart(&uuidString[14]);
uuid[ 7] = parseUuidPart(&uuidString[16]);
uuid[ 8] = parseUuidPart(&uuidString[19]);
uuid[ 9] = parseUuidPart(&uuidString[21]);
int n = 0;
if (x <= 0x0000FFFF) { n = n + 16; x = x << 16;}
if (x <= 0x00FFFFFF) { n = n + 8; x = x << 8; }
if (x <= 0x0FFFFFFF) { n = n + 4; x = x << 4; }
if (x <= 0x3FFFFFFF) { n = n + 2; x = x << 2; }
if (x <= 0x7FFFFFFF) { n = n + 1;}
uuid[10] = parseUuidPart(&uuidString[24]);
uuid[11] = parseUuidPart(&uuidString[26]);
uuid[12] = parseUuidPart(&uuidString[28]);
uuid[13] = parseUuidPart(&uuidString[30]);
uuid[14] = parseUuidPart(&uuidString[32]);
uuid[15] = parseUuidPart(&uuidString[34]);
return RAND_UUID_OK;
#elif defined(HAVE_UUID_UUID_H)
// note: uuid_t is typedefed as unsigned char[16] on linux/apple
uuid_generate(uuid);
return RAND_UUID_OK;
#else
// Fallback for all platforms is filling the array with random data
OCFillRandomMem(uuid, UUID_SIZE);
return RAND_UUID_OK;
#endif
return n;
}
OCRandomUuidResult OCGenerateUuidString(char uuidString[UUID_STRING_SIZE])
uint32_t OCGetRandomRange(uint32_t firstBound, uint32_t secondBound)
{
if (!uuidString)
if (firstBound == secondBound)
{
return RAND_UUID_INVALID_PARAM;
return secondBound;
}
#if defined(__ANDROID__)
int32_t fd = open("/proc/sys/kernel/random/uuid", O_RDONLY);
if (fd > 0)
uint32_t rangeBase = OC_MIN(firstBound, secondBound);
uint32_t rangeWidth = (firstBound > secondBound) ? (firstBound - secondBound) : (secondBound - firstBound);
/*
* Compute a random number between 0 and rangeWidth. Avoid using floating
* point types to avoid overflow when rangeWidth is large. The condition
* in the while loop will be false with probability at least 1/2.
*/
uint32_t rangeMask = 0xFFFFFFFF >> nlz(rangeWidth);
uint32_t offset = 0;
do
{
ssize_t readResult = read(fd, uuidString, UUID_STRING_SIZE - 1);
close(fd);
if (readResult < 0)
{
return RAND_UUID_READ_ERROR;
}
else if (readResult < UUID_STRING_SIZE - 1)
if(!OCGetRandomBytes((uint8_t*)&offset, sizeof(offset)))
{
uuidString[0] = '\0';
return RAND_UUID_READ_ERROR;
OIC_LOG(FATAL, OCRANDOM_TAG, "OCGetRandomBytes failed");
assert(false);
return rangeBase;
}
offset = offset & rangeMask;
}
while (offset > rangeWidth);
uuidString[UUID_STRING_SIZE - 1] = '\0';
for (char* p = uuidString; *p; ++p)
{
*p = tolower(*p);
}
return RAND_UUID_OK;
}
else
{
close(fd);
return RAND_UUID_READ_ERROR;
}
#elif defined(HAVE_UUID_UUID_H)
uint8_t uuid[UUID_SIZE];
int8_t ret = OCGenerateUuid(uuid);
return rangeBase + offset;
}
if (ret != 0)
bool OCGenerateUuid(uint8_t uuid[UUID_SIZE])
{
if (!uuid)
{
return ret;
OIC_LOG(ERROR, OCRANDOM_TAG, "Invalid parameter");
return false;
}
uuid_unparse_lower(uuid, uuidString);
return RAND_UUID_OK;
#else
uint8_t uuid[UUID_SIZE];
OCGenerateUuid(uuid);
return OCConvertUuidToString(uuid, uuidString);
#endif
return OCGetRandomBytes(uuid, UUID_SIZE);
}
OCRandomUuidResult OCConvertUuidToString(const uint8_t uuid[UUID_SIZE],
bool OCConvertUuidToString(const uint8_t uuid[UUID_SIZE],
char uuidString[UUID_STRING_SIZE])
{
if (uuid == NULL || uuidString == NULL)
{
return RAND_UUID_INVALID_PARAM;
OIC_LOG(ERROR, OCRANDOM_TAG, "Invalid parameter");
return false;
}
......@@ -375,18 +309,20 @@ OCRandomUuidResult OCConvertUuidToString(const uint8_t uuid[UUID_SIZE],
if (ret != UUID_STRING_SIZE - 1)
{
return RAND_UUID_CONVERT_ERROR;
OIC_LOG(ERROR, OCRANDOM_TAG, "snprintf failed");
return false;
}
return RAND_UUID_OK;
return true;
}
OCRandomUuidResult OCConvertStringToUuid(const char uuidString[UUID_STRING_SIZE],
bool OCConvertStringToUuid(const char uuidString[UUID_STRING_SIZE],
uint8_t uuid[UUID_SIZE])
{
if(NULL == uuidString || NULL == uuid)
{
return RAND_UUID_INVALID_PARAM;
OIC_LOG(ERROR, OCRANDOM_TAG, "Invalid parameter");
return false;
}
size_t urnIdx = 0;
......@@ -408,11 +344,12 @@ OCRandomUuidResult OCConvertStringToUuid(const char uuidString[UUID_STRING_SIZE]
}
else
{
return RAND_UUID_CONVERT_ERROR;
OIC_LOG(ERROR, OCRANDOM_TAG, "unexpected string length");
return false;
}
memcpy(uuid, convertedUuid, UUID_SIZE);
return RAND_UUID_OK;
return true;
}
......@@ -29,9 +29,20 @@ target_os = randomtest_env.get('TARGET_OS')
######################################################################
# Build flags
######################################################################
randomtest_env.PrependUnique(CPPPATH = [
'../include',
'../../logger/include',
'../../../oc_logger/include',
])
randomtest_env.AppendUnique(LIBPATH = [randomtest_env.get('BUILD_DIR')])