1 /*
2 ------- Strong random data generation on a Macintosh (pre - OS X) ------
3 
4 --	GENERAL: We aim to generate unpredictable bits without explicit
5 	user interaction. A general review of the problem may be found
6 	in RFC 1750, "Randomness Recommendations for Security", and some
7 	more discussion, of general and Mac-specific issues has appeared
8 	in "Using and Creating Cryptographic- Quality Random Numbers" by
9 	Jon Callas (www.merrymeet.com/jon/usingrandom.html).
10 
11 	The data and entropy estimates provided below are based on my
12 	limited experimentation and estimates, rather than by any
13 	rigorous study, and the entropy estimates tend to be optimistic.
14 	They should not be considered absolute.
15 
16 	Some of the information being collected may be correlated in
17 	subtle ways. That includes mouse positions, timings, and disk
18 	size measurements. Some obvious correlations will be eliminated
19 	by the programmer, but other, weaker ones may remain. The
20 	reliability of the code depends on such correlations being
21 	poorly understood, both by us and by potential interceptors.
22 
23 	This package has been planned to be used with OpenSSL, v. 0.9.5.
24 	It requires the OpenSSL function RAND_add.
25 
26 --	OTHER WORK: Some source code and other details have been
27 	published elsewhere, but I haven't found any to be satisfactory
28 	for the Mac per se:
29 
30 	* The Linux random number generator (by Theodore Ts'o, in
31 	  drivers/char/random.c), is a carefully designed open-source
32 	  crypto random number package. It collects data from a variety
33 	  of sources, including mouse, keyboard and other interrupts.
34 	  One nice feature is that it explicitly estimates the entropy
35 	  of the data it collects. Some of its features (e.g. interrupt
36 	  timing) cannot be reliably exported to the Mac without using
37 	  undocumented APIs.
38 
39 	* Truerand by Don P. Mitchell and Matt Blaze uses variations
40 	  between different timing mechanisms on the same system. This
41 	  has not been tested on the Mac, but requires preemptive
42 	  multitasking, and is hardware-dependent, and can't be relied
43 	  on to work well if only one oscillator is present.
44 
45 	* Cryptlib's RNG for the Mac (RNDMAC.C by Peter Gutmann),
46 	  gathers a lot of information about the machine and system
47 	  environment. Unfortunately, much of it is constant from one
48 	  startup to the next. In other words, the random seed could be
49 	  the same from one day to the next. Some of the APIs are
50 	  hardware-dependent, and not all are compatible with Carbon (OS
51 	  X). Incidentally, the EGD library is based on the UNIX entropy
52 	  gathering methods in cryptlib, and isn't suitable for MacOS
53 	  either.
54 
55 	* Mozilla (and perhaps earlier versions of Netscape) uses the
56 	  time of day (in seconds) and an uninitialized local variable
57 	  to seed the random number generator. The time of day is known
58 	  to an outside interceptor (to within the accuracy of the
59 	  system clock). The uninitialized variable could easily be
60 	  identical between subsequent launches of an application, if it
61 	  is reached through the same path.
62 
63 	* OpenSSL provides the function RAND_screen(), by G. van
64 	  Oosten, which hashes the contents of the screen to generate a
65 	  seed. This is not useful for an extension or for an
66 	  application which launches at startup time, since the screen
67 	  is likely to look identical from one launch to the next. This
68 	  method is also rather slow.
69 
70 	* Using variations in disk drive seek times has been proposed
71 	  (Davis, Ihaka and Fenstermacher, world.std.com/~dtd/;
72 	  Jakobsson, Shriver, Hillyer and Juels,
73 	  www.bell-labs.com/user/shriver/random.html). These variations
74 	  appear to be due to air turbulence inside the disk drive
75 	  mechanism, and are very strongly unpredictable. Unfortunately
76 	  this technique is slow, and some implementations of it may be
77 	  patented (see Shriver's page above.) It of course cannot be
78 	  used with a RAM disk.
79 
80 --	TIMING: On the 601 PowerPC the time base register is guaranteed
81 	to change at least once every 10 addi instructions, i.e. 10
82 	cycles. On a 60 MHz machine (slowest PowerPC) this translates to
83 	a resolution of 1/6 usec. Newer machines seem to be using a 10
84 	cycle resolution as well.
85 
86 	For 68K Macs, the Microseconds() call may be used. See Develop
87 	issue 29 on the Apple developer site
88 	(developer.apple.com/dev/techsupport/develop/issue29/minow.html)
89 	for information on its accuracy and resolution. The code below
90 	has been tested only on PowerPC based machines.
91 
92 	The time from machine startup to the launch of an application in
93 	the startup folder has a variance of about 1.6 msec on a new G4
94 	machine with a defragmented and optimized disk, most extensions
95 	off and no icons on the desktop. This can be reasonably taken as
96 	a lower bound on the variance. Most of this variation is likely
97 	due to disk seek time variability. The distribution of startup
98 	times is probably not entirely even or uncorrelated. This needs
99 	to be investigated, but I am guessing that it not a majpor
100 	problem. Entropy = log2 (1600/0.166) ~= 13 bits on a 60 MHz
101 	machine, ~16 bits for a 450 MHz machine.
102 
103 	User-launched application startup times will have a variance of
104 	a second or more relative to machine startup time. Entropy >~22
105 	bits.
106 
107 	Machine startup time is available with a 1-second resolution. It
108 	is predictable to no better a minute or two, in the case of
109 	people who show up punctually to work at the same time and
110 	immediately start their computer. Using the scheduled startup
111 	feature (when available) will cause the machine to start up at
112 	the same time every day, making the value predictable. Entropy
113 	>~7 bits, or 0 bits with scheduled startup.
114 
115 	The time of day is of course known to an outsider and thus has 0
116 	entropy if the system clock is regularly calibrated.
117 
118 --	KEY TIMING: A  very fast typist (120 wpm) will have a typical
119 	inter-key timing interval of 100 msec. We can assume a variance
120 	of no less than 2 msec -- maybe. Do good typists have a constant
121 	rhythm, like drummers? Since what we measure is not the
122 	key-generated interrupt but the time at which the key event was
123 	taken off the event queue, our resolution is roughly the time
124 	between process switches, at best 1 tick (17 msec). I  therefore
125 	consider this technique questionable and not very useful for
126 	obtaining high entropy data on the Mac.
127 
128 --	MOUSE POSITION AND TIMING: The high bits of the mouse position
129 	are far from arbitrary, since the mouse tends to stay in a few
130 	limited areas of the screen. I am guessing that the position of
131 	the mouse is arbitrary within a 6 pixel square. Since the mouse
132 	stays still for long periods of time, it should be sampled only
133 	after it was moved, to avoid correlated data. This gives an
134 	entropy of log2(6*6) ~= 5 bits per measurement.
135 
136 	The time during which the mouse stays still can vary from zero
137 	to, say, 5 seconds (occasionally longer). If the still time is
138 	measured by sampling the mouse during null events, and null
139 	events are received once per tick, its resolution is 1/60th of a
140 	second, giving an entropy of log2 (60*5) ~= 8 bits per
141 	measurement. Since the distribution of still times is uneven,
142 	this estimate is on the high side.
143 
144 	For simplicity and compatibility across system versions, the
145 	mouse is to be sampled explicitly (e.g. in the event loop),
146 	rather than in a time manager task.
147 
148 --	STARTUP DISK TOTAL FILE SIZE: Varies typically by at least 20k
149 	from one startup to the next, with 'minimal' computer use. Won't
150 	vary at all if machine is started again immediately after
151 	startup (unless virtual memory is on), but any application which
152 	uses the web and caches information to disk is likely to cause
153 	this much variation or more. The variation is probably not
154 	random, but I don't know in what way. File sizes tend to be
155 	divisible by 4 bytes since file format fields are often
156 	long-aligned. Entropy > log2 (20000/4) ~= 12 bits.
157 
158 --	STARTUP DISK FIRST AVAILABLE ALLOCATION BLOCK: As the volume
159 	gets fragmented this could be anywhere in principle. In a
160 	perfectly unfragmented volume this will be strongly correlated
161 	with the total file size on the disk. With more fragmentation
162 	comes less certainty. I took the variation in this value to be
163 	1/8 of the total file size on the volume.
164 
165 --	SYSTEM REQUIREMENTS: The code here requires System 7.0 and above
166 	(for Gestalt and Microseconds calls). All the calls used are
167 	Carbon-compatible.
168 */
169 
170 /*------------------------------ Includes ----------------------------*/
171 
172 #include "Randomizer.h"
173 
174 // Mac OS API
175 #include <Files.h>
176 #include <Folders.h>
177 #include <Events.h>
178 #include <Processes.h>
179 #include <Gestalt.h>
180 #include <Resources.h>
181 #include <LowMem.h>
182 
183 // Standard C library
184 #include <stdlib.h>
185 #include <math.h>
186 
187 /*---------------------- Function declarations -----------------------*/
188 
189 // declared in OpenSSL/crypto/rand/rand.h
190 extern "C" void RAND_add (const void *buf, int num, double entropy);
191 
192 unsigned long GetPPCTimer (bool is601);	// Make it global if needed
193 					// elsewhere
194 
195 /*---------------------------- Constants -----------------------------*/
196 
197 #define kMouseResolution 6		// Mouse position has to differ
198 					// from the last one by this
199 					// much to be entered
200 #define kMousePositionEntropy 5.16	// log2 (kMouseResolution**2)
201 #define kTypicalMouseIdleTicks 300.0	// I am guessing that a typical
202 					// amount of time between mouse
203 					// moves is 5 seconds
204 #define kVolumeBytesEntropy 12.0	// about log2 (20000/4),
205 					// assuming a variation of 20K
206 					// in total file size and
207 					// long-aligned file formats.
208 #define kApplicationUpTimeEntropy 6.0	// Variance > 1 second, uptime
209 					// in ticks
210 #define kSysStartupEntropy 7.0		// Entropy for machine startup
211 					// time
212 
213 
214 /*------------------------ Function definitions ----------------------*/
215 
CRandomizer(void)216 CRandomizer::CRandomizer (void)
217 {
218 	long	result;
219 
220 	mSupportsLargeVolumes =
221 		(Gestalt(gestaltFSAttr, &result) == noErr) &&
222 		((result & (1L << gestaltFSSupports2TBVols)) != 0);
223 
224 	if (Gestalt (gestaltNativeCPUtype, &result) != noErr)
225 	{
226 		mIsPowerPC = false;
227 		mIs601 = false;
228 	}
229 	else
230 	{
231 		mIs601 = (result == gestaltCPU601);
232 		mIsPowerPC = (result >= gestaltCPU601);
233 	}
234 	mLastMouse.h = mLastMouse.v = -10;	// First mouse will
235 						// always be recorded
236 	mLastPeriodicTicks = TickCount();
237 	GetTimeBaseResolution ();
238 
239 	// Add initial entropy
240 	AddTimeSinceMachineStartup ();
241 	AddAbsoluteSystemStartupTime ();
242 	AddStartupVolumeInfo ();
243 	AddFiller ();
244 }
245 
PeriodicAction(void)246 void CRandomizer::PeriodicAction (void)
247 {
248 	AddCurrentMouse ();
249 	AddNow (0.0);	// Should have a better entropy estimate here
250 	mLastPeriodicTicks = TickCount();
251 }
252 
253 /*------------------------- Private Methods --------------------------*/
254 
AddCurrentMouse(void)255 void CRandomizer::AddCurrentMouse (void)
256 {
257 	Point mouseLoc;
258 	unsigned long lastCheck;	// Ticks since mouse was last
259 					// sampled
260 
261 #if TARGET_API_MAC_CARBON
262 	GetGlobalMouse (&mouseLoc);
263 #else
264 	mouseLoc = LMGetMouseLocation();
265 #endif
266 
267 	if (labs (mLastMouse.h - mouseLoc.h) > kMouseResolution/2 &&
268 	    labs (mLastMouse.v - mouseLoc.v) > kMouseResolution/2)
269 		AddBytes (&mouseLoc, sizeof(mouseLoc),
270 				kMousePositionEntropy);
271 
272 	if (mLastMouse.h == mouseLoc.h && mLastMouse.v == mouseLoc.v)
273 		mMouseStill ++;
274 	else
275 	{
276 		double entropy;
277 
278 		// Mouse has moved. Add the number of measurements for
279 		// which it's been still. If the resolution is too
280 		// coarse, assume the entropy is 0.
281 
282 		lastCheck = TickCount() - mLastPeriodicTicks;
283 		if (lastCheck <= 0)
284 			lastCheck = 1;
285 		entropy = log2l
286 			(kTypicalMouseIdleTicks/(double)lastCheck);
287 		if (entropy < 0.0)
288 			entropy = 0.0;
289 		AddBytes (&mMouseStill, sizeof(mMouseStill), entropy);
290 		mMouseStill = 0;
291 	}
292 	mLastMouse = mouseLoc;
293 }
294 
AddAbsoluteSystemStartupTime(void)295 void CRandomizer::AddAbsoluteSystemStartupTime (void)
296 {
297 	unsigned long	now;		// Time in seconds since
298 					// 1/1/1904
299 	GetDateTime (&now);
300 	now -= TickCount() / 60;	// Time in ticks since machine
301 					// startup
302 	AddBytes (&now, sizeof(now), kSysStartupEntropy);
303 }
304 
AddTimeSinceMachineStartup(void)305 void CRandomizer::AddTimeSinceMachineStartup (void)
306 {
307 	AddNow (1.5);			// Uncertainty in app startup
308 					// time is > 1.5 msec (for
309 					// automated app startup).
310 }
311 
AddAppRunningTime(void)312 void CRandomizer::AddAppRunningTime (void)
313 {
314 	ProcessSerialNumber PSN;
315 	ProcessInfoRec		ProcessInfo;
316 
317 	ProcessInfo.processInfoLength = sizeof(ProcessInfoRec);
318 	ProcessInfo.processName = nil;
319 	ProcessInfo.processAppSpec = nil;
320 
321 	GetCurrentProcess (&PSN);
322 	GetProcessInformation (&PSN, &ProcessInfo);
323 
324 	// Now add the amount of time in ticks that the current process
325 	// has been active
326 
327 	AddBytes (&ProcessInfo, sizeof(ProcessInfoRec),
328 			kApplicationUpTimeEntropy);
329 }
330 
AddStartupVolumeInfo(void)331 void CRandomizer::AddStartupVolumeInfo (void)
332 {
333 	short			vRefNum;
334 	long			dirID;
335 	XVolumeParam	pb;
336 	OSErr			err;
337 
338 	if (!mSupportsLargeVolumes)
339 		return;
340 
341 	FindFolder (kOnSystemDisk, kSystemFolderType, kDontCreateFolder,
342 			&vRefNum, &dirID);
343 	pb.ioVRefNum = vRefNum;
344 	pb.ioCompletion = 0;
345 	pb.ioNamePtr = 0;
346 	pb.ioVolIndex = 0;
347 	err = PBXGetVolInfoSync (&pb);
348 	if (err != noErr)
349 		return;
350 
351 	// Base the entropy on the amount of space used on the disk and
352 	// on the next available allocation block. A lot else might be
353 	// unpredictable, so might as well toss the whole block in. See
354 	// comments for entropy estimate justifications.
355 
356 	AddBytes (&pb, sizeof(pb),
357 		kVolumeBytesEntropy +
358 		log2l (((pb.ioVTotalBytes.hi - pb.ioVFreeBytes.hi)
359 				* 4294967296.0D +
360 			(pb.ioVTotalBytes.lo - pb.ioVFreeBytes.lo))
361 				/ pb.ioVAlBlkSiz - 3.0));
362 }
363 
364 /*
365 	On a typical startup CRandomizer will come up with about 60
366 	bits of good, unpredictable data. Assuming no more input will
367 	be available, we'll need some more lower-quality data to give
368 	OpenSSL the 128 bits of entropy it desires. AddFiller adds some
369 	relatively predictable data into the soup.
370 */
371 
AddFiller(void)372 void CRandomizer::AddFiller (void)
373 {
374 	struct
375 	{
376 		ProcessSerialNumber psn;	// Front process serial
377 						// number
378 		RGBColor	hiliteRGBValue;	// User-selected
379 						// highlight color
380 		long		processCount;	// Number of active
381 						// processes
382 		long		cpuSpeed;	// Processor speed
383 		long		totalMemory;	// Total logical memory
384 						// (incl. virtual one)
385 		long		systemVersion;	// OS version
386 		short		resFile;	// Current resource file
387 	} data;
388 
389 	GetNextProcess ((ProcessSerialNumber*) kNoProcess);
390 	while (GetNextProcess (&data.psn) == noErr)
391 		data.processCount++;
392 	GetFrontProcess (&data.psn);
393 	LMGetHiliteRGB (&data.hiliteRGBValue);
394 	Gestalt (gestaltProcClkSpeed, &data.cpuSpeed);
395 	Gestalt (gestaltLogicalRAMSize, &data.totalMemory);
396 	Gestalt (gestaltSystemVersion, &data.systemVersion);
397 	data.resFile = CurResFile ();
398 
399 	// Here we pretend to feed the PRNG completely random data. This
400 	// is of course false, as much of the above data is predictable
401 	// by an outsider. At this point we don't have any more
402 	// randomness to add, but with OpenSSL we must have a 128 bit
403 	// seed before we can start. We just add what we can, without a
404 	// real entropy estimate, and hope for the best.
405 
406 	AddBytes (&data, sizeof(data), 8.0 * sizeof(data));
407 	AddCurrentMouse ();
408 	AddNow (1.0);
409 }
410 
411 //-------------------  LOW LEVEL ---------------------
412 
AddBytes(void * data,long size,double entropy)413 void CRandomizer::AddBytes (void *data, long size, double entropy)
414 {
415 	RAND_add (data, size, entropy * 0.125);	// Convert entropy bits
416 						// to bytes
417 }
418 
AddNow(double millisecondUncertainty)419 void CRandomizer::AddNow (double millisecondUncertainty)
420 {
421 	long time = SysTimer();
422 	AddBytes (&time, sizeof(time), log2l (millisecondUncertainty *
423 			mTimebaseTicksPerMillisec));
424 }
425 
426 //----------------- TIMING SUPPORT ------------------
427 
GetTimeBaseResolution(void)428 void CRandomizer::GetTimeBaseResolution (void)
429 {
430 #ifdef __powerc
431 	long speed;
432 
433 	// gestaltProcClkSpeed available on System 7.5.2 and above
434 	if (Gestalt (gestaltProcClkSpeed, &speed) != noErr)
435 		// Only PowerPCs running pre-7.5.2 are 60-80 MHz
436 		// machines.
437 		mTimebaseTicksPerMillisec =  6000.0D;
438 	// Assume 10 cycles per clock update, as in 601 spec. Seems true
439 	// for later chips as well.
440 	mTimebaseTicksPerMillisec = speed / 1.0e4D;
441 #else
442 	// 68K VIA-based machines (see Develop Magazine no. 29)
443 	mTimebaseTicksPerMillisec = 783.360D;
444 #endif
445 }
446 
SysTimer(void)447 unsigned long CRandomizer::SysTimer (void)	// returns the lower 32
448 						// bit of the chip timer
449 {
450 #ifdef __powerc
451 	return GetPPCTimer (mIs601);
452 #else
453 	UnsignedWide usec;
454 	Microseconds (&usec);
455 	return usec.lo;
456 #endif
457 }
458 
459 #ifdef __powerc
460 // The timebase is available through mfspr on 601, mftb on later chips.
461 // Motorola recommends that an 601 implementation map mftb to mfspr
462 // through an exception, but I haven't tested to see if MacOS actually
463 // does this. We only sample the lower 32 bits of the timer (i.e. a
464 // few minutes of resolution)
465 
GetPPCTimer(register bool is601)466 asm unsigned long GetPPCTimer (register bool is601)
467 {
468 	cmplwi	is601, 0	// Check if 601
469 	bne	_601		// if non-zero goto _601
470 	mftb  	r3		// Available on 603 and later.
471 	blr			// return with result in r3
472 _601:
473 	mfspr r3, spr5  	// Available on 601 only.
474 				// blr inserted automatically
475 }
476 #endif
477