/* * 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()); } } }