#include "entropy.h"
#include "dtrace.h"
#include <sys/sysctl.h>
#include <mach/clock_types.h>
#include <mach/mach_time.h>
#include <errno.h>
#include <security_utilities/logging.h>
#include <sys/sysctl.h>
#include <security_utilities/debugging.h>
#include <math.h>
#define ENTROPY_QUICK_UPDATE 0
#if ENTROPY_QUICK_UPDATE
#define COLLECT_INTERVAL 15
#else
#define COLLECT_INTERVAL collectInterval
#endif //ENTROPY_QUICK_UPDATE
using namespace UnixPlusPlus;
EntropyManager::EntropyManager(MachPlusPlus::MachServer &srv, const char *entropyFile)
: DevRandomGenerator(true), server(srv),
mEntropyFilePath(entropyFile), mNextUpdate(Time::now())
{
try {
AutoFileDesc oldEntropyFile(entropyFile, O_RDONLY);
char buffer[entropyFileSize];
if (size_t size = oldEntropyFile.read(buffer))
addEntropy(buffer, size);
} catch (...) { }
action();
}
void EntropyManager::action()
{
collectEntropy();
updateEntropyFile();
server.setTimer(this, Time::Interval(COLLECT_INTERVAL)); }
static const double kBytesOfEntropyToCollect = 240;
static const int kExpectedLoops = 10;
static double CalculateEntropy(const void* buffer, size_t bufferSize)
{
double sizef = bufferSize;
const u_int8_t* charBuffer = (const u_int8_t*) buffer;
int counts[256];
memset(counts, 0, sizeof(counts));
size_t i;
for (i = 0; i < bufferSize; ++i)
{
counts[charBuffer[i]] += 1;
}
double entropy = 0.0;
for (i = 0; i < 256; ++i)
{
if (counts[i] > 0)
{
double p = ((double) counts[i]) / sizef;
double term = p * -log2(p);
entropy += term;
}
}
double entropicBytes = bufferSize * entropy / 8.0;
return entropicBytes;
}
void EntropyManager::collectEntropy()
{
SECURITYD_ENTROPY_COLLECT();
int mib[4];
mib[0] = CTL_KERN;
mib[1] = KERN_KDEBUG;
mib[2] = KERN_KDGETENTROPY;
mib[3] = 1;
mach_timespec_t buffer[timingsToCollect];
int result;
double bytesRemaining = kBytesOfEntropyToCollect;
int loopCount = 0;
while (bytesRemaining >= 0)
{
size_t size = sizeof(mach_timespec_t) * timingsToCollect;
result = sysctl(mib,4, buffer, &size, NULL, 0);
if (result == -1) {
Syslog::alert("entropy measurement returned no entropy (errno=%d)", errno);
sleep(1);
}
else if (size == 0)
{
Syslog::alert("entropy measurement returned no entropy.");
sleep(1);
}
u_int16_t nonEnt[timingsToCollect];
u_int16_t *rawEnt = (u_int16_t*) buffer;
int i;
for (i = 0; i < timingsToCollect; ++i)
{
nonEnt[i] = *rawEnt;
rawEnt += 4;
}
SECURITYD_ENTROPY_SEED((void *)nonEnt, (unsigned int) sizeof(nonEnt));
addEntropy(nonEnt, sizeof(nonEnt));
double entropyRead = CalculateEntropy(nonEnt, sizeof(nonEnt));
bytesRemaining -= entropyRead;
loopCount += 1;
}
if (loopCount > kExpectedLoops)
{
Syslog::alert("Entropy collection fulfillment took %d loops", loopCount);
}
}
void EntropyManager::updateEntropyFile()
{
if (Time::now() >= mNextUpdate) {
try {
SECURITYD_ENTROPY_SAVE((char *)mEntropyFilePath.c_str());
mNextUpdate = Time::now() + Time::Interval(updateInterval);
secdebug("entropy", "updating %s", mEntropyFilePath.c_str());
char buffer[entropyFileSize];
random(buffer, entropyFileSize);
AutoFileDesc entropyFile(mEntropyFilePath.c_str(), O_WRONLY | O_TRUNC | O_CREAT, 0600);
if (entropyFile.write(buffer) != entropyFileSize)
Syslog::warning("short write on entropy file %s", mEntropyFilePath.c_str());
} catch (...) {
Syslog::warning("error writing entropy file %s", mEntropyFilePath.c_str());
}
}
}