1/*
2 * Copyright (c) 2000-2004,2007-2008,2010,2012-2013 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24
25//
26// EntropyManager - manage entropy on the system.
27//
28// Here is our mission:
29// (1) On startup, read the entropy file and seed it into the RNG for initial use
30// (2) Periodically, collect entropy from the system and seed it into the RNG
31// (3) Once in a while, take entropy from the RNG and write it to the entropy file
32//   for use across reboots.
33//
34// This class will fail to operate if the process has (and retains) root privileges.
35// We re-open the entropy file on each use so that we don't work with a "phantom"
36// file that some fool administrator removed yesterday.
37//
38#include "entropy.h"
39#include "dtrace.h"
40#include <sys/sysctl.h>
41#include <mach/clock_types.h>
42#include <mach/mach_time.h>
43#include <errno.h>
44#include <security_utilities/logging.h>
45#include <sys/sysctl.h>
46#include <security_utilities/debugging.h>
47#include <math.h>
48
49/* when true, action() called every 15 seconds */
50#define ENTROPY_QUICK_UPDATE	0
51#if		ENTROPY_QUICK_UPDATE
52#define COLLECT_INTERVAL		15
53#else
54#define COLLECT_INTERVAL		collectInterval
55#endif	//ENTROPY_QUICK_UPDATE
56
57using namespace UnixPlusPlus;
58
59
60//
61// During construction, we perform initial entropy file recovery.
62//
63EntropyManager::EntropyManager(MachPlusPlus::MachServer &srv, const char *entropyFile)
64    : DevRandomGenerator(true), server(srv),
65    mEntropyFilePath(entropyFile), mNextUpdate(Time::now())
66{
67    // Read the entropy file and seed the RNG. It is not an error if we can't find one.
68    try {
69        AutoFileDesc oldEntropyFile(entropyFile, O_RDONLY);
70        char buffer[entropyFileSize];
71        if (size_t size = oldEntropyFile.read(buffer))
72            addEntropy(buffer, size);
73    } catch (...) { }
74
75    // go through a collect/update/reschedule cycle immediately
76    action();
77}
78
79
80//
81// Timer action
82//
83void EntropyManager::action()
84{
85    collectEntropy();
86    updateEntropyFile();
87
88    server.setTimer(this, Time::Interval(COLLECT_INTERVAL));	// drifting reschedule (desired)
89}
90
91
92static const double kBytesOfEntropyToCollect = 240;
93// that gives us a minimum of 2.16 * 10^609 possible combinations.  It's a finite number to be sure...
94
95static const int kExpectedLoops = 10;
96
97// Calculate the amount of entropy in the buffer (per Shannon's Entropy Calculation)
98static double CalculateEntropy(const void* buffer, size_t bufferSize)
99{
100    double sizef = bufferSize;
101    const u_int8_t* charBuffer = (const u_int8_t*) buffer;
102
103    // zero the tabulation array
104    int counts[256];
105    memset(counts, 0, sizeof(counts));
106
107    // tabulate the occurances of each byte in the array
108    size_t i;
109    for (i = 0; i < bufferSize; ++i)
110    {
111        counts[charBuffer[i]] += 1;
112    }
113
114    // calculate the number of bits/byte of entropy
115    double entropy = 0.0;
116
117    for (i = 0; i < 256; ++i)
118    {
119        if (counts[i] > 0)
120        {
121            double p = ((double) counts[i]) / sizef;
122            double term = p * -log2(p);
123            entropy += term;
124        }
125    }
126
127    double entropicBytes = bufferSize * entropy / 8.0;
128
129    return entropicBytes;
130}
131
132
133
134//
135// Collect system timings and seed into the RNG.
136// Note that the sysctl will block until the buffer is full or the timeout expires.
137// We currently use a 1ms timeout, which almost always fills the buffer and
138// does not provide enough of a delay to worry about it. If we ever get worried,
139// we could call longTermActivity on the server object to get another thread going.
140//
141
142void EntropyManager::collectEntropy()
143{
144	SECURITYD_ENTROPY_COLLECT();
145
146    int mib[4];
147    mib[0] = CTL_KERN;
148    mib[1] = KERN_KDEBUG;
149    mib[2] = KERN_KDGETENTROPY;
150    mib[3] = 1;	// milliseconds maximum delay
151
152	mach_timespec_t buffer[timingsToCollect];
153
154	int result;
155
156    double bytesRemaining = kBytesOfEntropyToCollect;
157
158    int loopCount = 0;
159
160    while (bytesRemaining >= 0)
161    {
162        size_t size = sizeof(mach_timespec_t) * timingsToCollect;
163
164        result = sysctl(mib,4, buffer, &size, NULL, 0);
165        if (result == -1) {
166            Syslog::alert("entropy measurement returned no entropy (errno=%d)", errno);
167            sleep(1);
168        }
169        else if (size == 0)
170        {
171            Syslog::alert("entropy measurement returned no entropy.");
172            sleep(1);
173        }
174
175        // remove the non-entropic pieces from the buffer
176        u_int16_t nonEnt[timingsToCollect];
177
178        // treat the received buffer as an array of u_int16 and only take the first two bytes of each
179        u_int16_t *rawEnt = (u_int16_t*) buffer;
180
181        int i;
182        for (i = 0; i < timingsToCollect; ++i)
183        {
184            nonEnt[i] = *rawEnt;
185            rawEnt += 4;
186        }
187
188        SECURITYD_ENTROPY_SEED((void *)nonEnt, (unsigned int) sizeof(nonEnt));
189        addEntropy(nonEnt, sizeof(nonEnt));
190
191        double entropyRead = CalculateEntropy(nonEnt, sizeof(nonEnt));
192        bytesRemaining -= entropyRead;
193
194        loopCount += 1;
195    }
196
197    if (loopCount > kExpectedLoops)
198    {
199        Syslog::alert("Entropy collection fulfillment took %d loops", loopCount);
200    }
201}
202
203
204//
205// (Re)write the entropy file with random data pulled from the RNG
206//
207void EntropyManager::updateEntropyFile()
208{
209    if (Time::now() >= mNextUpdate) {
210        try {
211			SECURITYD_ENTROPY_SAVE((char *)mEntropyFilePath.c_str());
212			mNextUpdate = Time::now() + Time::Interval(updateInterval);
213            secdebug("entropy", "updating %s", mEntropyFilePath.c_str());
214        	char buffer[entropyFileSize];
215			random(buffer, entropyFileSize);
216            AutoFileDesc entropyFile(mEntropyFilePath.c_str(), O_WRONLY | O_TRUNC | O_CREAT, 0600);
217            if (entropyFile.write(buffer) != entropyFileSize)
218                Syslog::warning("short write on entropy file %s", mEntropyFilePath.c_str());
219        } catch (...) {
220            Syslog::warning("error writing entropy file %s", mEntropyFilePath.c_str());
221        }
222    }
223}
224
225