X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/securityd/src/entropy.cpp diff --git a/securityd/src/entropy.cpp b/securityd/src/entropy.cpp new file mode 100644 index 00000000..5839c2a4 --- /dev/null +++ b/securityd/src/entropy.cpp @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2000-2004,2007-2008,2010,2012-2013 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +// +// EntropyManager - manage entropy on the system. +// +// Here is our mission: +// (1) On startup, read the entropy file and seed it into the RNG for initial use +// (2) Periodically, collect entropy from the system and seed it into the RNG +// (3) Once in a while, take entropy from the RNG and write it to the entropy file +// for use across reboots. +// +// This class will fail to operate if the process has (and retains) root privileges. +// We re-open the entropy file on each use so that we don't work with a "phantom" +// file that some fool administrator removed yesterday. +// +#include "entropy.h" +#include "dtrace.h" +#include +#include +#include +#include +#include +#include +#include +#include + +/* when true, action() called every 15 seconds */ +#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; + + +// +// During construction, we perform initial entropy file recovery. +// +EntropyManager::EntropyManager(MachPlusPlus::MachServer &srv, const char *entropyFile) + : DevRandomGenerator(true), server(srv), + mEntropyFilePath(entropyFile), mNextUpdate(Time::now()) +{ + // Read the entropy file and seed the RNG. It is not an error if we can't find one. + try { + AutoFileDesc oldEntropyFile(entropyFile, O_RDONLY); + char buffer[entropyFileSize]; + if (size_t size = oldEntropyFile.read(buffer)) + addEntropy(buffer, size); + } catch (...) { } + + // go through a collect/update/reschedule cycle immediately + action(); +} + + +// +// Timer action +// +void EntropyManager::action() +{ + collectEntropy(); + updateEntropyFile(); + + server.setTimer(this, Time::Interval(COLLECT_INTERVAL)); // drifting reschedule (desired) +} + + +static const double kBytesOfEntropyToCollect = 240; +// that gives us a minimum of 2.16 * 10^609 possible combinations. It's a finite number to be sure... + +static const int kExpectedLoops = 10; + +// Calculate the amount of entropy in the buffer (per Shannon's Entropy Calculation) +static double CalculateEntropy(const void* buffer, size_t bufferSize) +{ + double sizef = bufferSize; + const u_int8_t* charBuffer = (const u_int8_t*) buffer; + + // zero the tabulation array + int counts[256]; + memset(counts, 0, sizeof(counts)); + + // tabulate the occurances of each byte in the array + size_t i; + for (i = 0; i < bufferSize; ++i) + { + counts[charBuffer[i]] += 1; + } + + // calculate the number of bits/byte of entropy + 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; +} + + + +// +// Collect system timings and seed into the RNG. +// Note that the sysctl will block until the buffer is full or the timeout expires. +// We currently use a 1ms timeout, which almost always fills the buffer and +// does not provide enough of a delay to worry about it. If we ever get worried, +// we could call longTermActivity on the server object to get another thread going. +// + +void EntropyManager::collectEntropy() +{ + SECURITYD_ENTROPY_COLLECT(); + + int mib[4]; + mib[0] = CTL_KERN; + mib[1] = KERN_KDEBUG; + mib[2] = KERN_KDGETENTROPY; + mib[3] = 1; // milliseconds maximum delay + + 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); + } + + // remove the non-entropic pieces from the buffer + u_int16_t nonEnt[timingsToCollect]; + + // treat the received buffer as an array of u_int16 and only take the first two bytes of each + 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); + } +} + + +// +// (Re)write the entropy file with random data pulled from the RNG +// +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()); + } + } +} +