mirror of https://github.com/openssl/openssl.git
				
				
				
			
		
			
				
	
	
		
			477 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			477 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
| /* 
 | |
| ------- Strong random data generation on a Macintosh (pre - OS X) ------
 | |
| 		
 | |
| --	GENERAL: We aim to generate unpredictable bits without explicit
 | |
| 	user interaction. A general review of the problem may be found
 | |
| 	in RFC 1750, "Randomness Recommendations for Security", and some
 | |
| 	more discussion, of general and Mac-specific issues has appeared
 | |
| 	in "Using and Creating Cryptographic- Quality Random Numbers" by
 | |
| 	Jon Callas (www.merrymeet.com/jon/usingrandom.html).
 | |
| 
 | |
| 	The data and entropy estimates provided below are based on my
 | |
| 	limited experimentation and estimates, rather than by any
 | |
| 	rigorous study, and the entropy estimates tend to be optimistic.
 | |
| 	They should not be considered absolute.
 | |
| 
 | |
| 	Some of the information being collected may be correlated in
 | |
| 	subtle ways. That includes mouse positions, timings, and disk
 | |
| 	size measurements. Some obvious correlations will be eliminated
 | |
| 	by the programmer, but other, weaker ones may remain. The
 | |
| 	reliability of the code depends on such correlations being
 | |
| 	poorly understood, both by us and by potential interceptors.
 | |
| 
 | |
| 	This package has been planned to be used with OpenSSL, v. 0.9.5.
 | |
| 	It requires the OpenSSL function RAND_add. 
 | |
| 
 | |
| --	OTHER WORK: Some source code and other details have been
 | |
| 	published elsewhere, but I haven't found any to be satisfactory
 | |
| 	for the Mac per se:
 | |
| 
 | |
| 	* The Linux random number generator (by Theodore Ts'o, in
 | |
| 	  drivers/char/random.c), is a carefully designed open-source
 | |
| 	  crypto random number package. It collects data from a variety
 | |
| 	  of sources, including mouse, keyboard and other interrupts.
 | |
| 	  One nice feature is that it explicitly estimates the entropy
 | |
| 	  of the data it collects. Some of its features (e.g. interrupt
 | |
| 	  timing) cannot be reliably exported to the Mac without using
 | |
| 	  undocumented APIs.
 | |
| 
 | |
| 	* Truerand by Don P. Mitchell and Matt Blaze uses variations
 | |
| 	  between different timing mechanisms on the same system. This
 | |
| 	  has not been tested on the Mac, but requires preemptive
 | |
| 	  multitasking, and is hardware-dependent, and can't be relied
 | |
| 	  on to work well if only one oscillator is present.
 | |
| 
 | |
| 	* Cryptlib's RNG for the Mac (RNDMAC.C by Peter Gutmann),
 | |
| 	  gathers a lot of information about the machine and system
 | |
| 	  environment. Unfortunately, much of it is constant from one
 | |
| 	  startup to the next. In other words, the random seed could be
 | |
| 	  the same from one day to the next. Some of the APIs are
 | |
| 	  hardware-dependent, and not all are compatible with Carbon (OS
 | |
| 	  X). Incidentally, the EGD library is based on the UNIX entropy
 | |
| 	  gathering methods in cryptlib, and isn't suitable for MacOS
 | |
| 	  either.
 | |
| 
 | |
| 	* Mozilla (and perhaps earlier versions of Netscape) uses the
 | |
| 	  time of day (in seconds) and an uninitialized local variable
 | |
| 	  to seed the random number generator. The time of day is known
 | |
| 	  to an outside interceptor (to within the accuracy of the
 | |
| 	  system clock). The uninitialized variable could easily be
 | |
| 	  identical between subsequent launches of an application, if it
 | |
| 	  is reached through the same path.
 | |
| 
 | |
| 	* OpenSSL provides the function RAND_screen(), by G. van
 | |
| 	  Oosten, which hashes the contents of the screen to generate a
 | |
| 	  seed. This is not useful for an extension or for an
 | |
| 	  application which launches at startup time, since the screen
 | |
| 	  is likely to look identical from one launch to the next. This
 | |
| 	  method is also rather slow.
 | |
| 
 | |
| 	* Using variations in disk drive seek times has been proposed
 | |
| 	  (Davis, Ihaka and Fenstermacher, world.std.com/~dtd/;
 | |
| 	  Jakobsson, Shriver, Hillyer and Juels,
 | |
| 	  www.bell-labs.com/user/shriver/random.html). These variations
 | |
| 	  appear to be due to air turbulence inside the disk drive
 | |
| 	  mechanism, and are very strongly unpredictable. Unfortunately
 | |
| 	  this technique is slow, and some implementations of it may be
 | |
| 	  patented (see Shriver's page above.) It of course cannot be
 | |
| 	  used with a RAM disk.
 | |
| 
 | |
| --	TIMING: On the 601 PowerPC the time base register is guaranteed
 | |
| 	to change at least once every 10 addi instructions, i.e. 10
 | |
| 	cycles. On a 60 MHz machine (slowest PowerPC) this translates to
 | |
| 	a resolution of 1/6 usec. Newer machines seem to be using a 10
 | |
| 	cycle resolution as well.
 | |
| 	
 | |
| 	For 68K Macs, the Microseconds() call may be used. See Develop
 | |
| 	issue 29 on the Apple developer site
 | |
| 	(developer.apple.com/dev/techsupport/develop/issue29/minow.html)
 | |
| 	for information on its accuracy and resolution. The code below
 | |
| 	has been tested only on PowerPC based machines.
 | |
| 
 | |
| 	The time from machine startup to the launch of an application in
 | |
| 	the startup folder has a variance of about 1.6 msec on a new G4
 | |
| 	machine with a defragmented and optimized disk, most extensions
 | |
| 	off and no icons on the desktop. This can be reasonably taken as
 | |
| 	a lower bound on the variance. Most of this variation is likely
 | |
| 	due to disk seek time variability. The distribution of startup
 | |
| 	times is probably not entirely even or uncorrelated. This needs
 | |
| 	to be investigated, but I am guessing that it not a majpor
 | |
| 	problem. Entropy = log2 (1600/0.166) ~= 13 bits on a 60 MHz
 | |
| 	machine, ~16 bits for a 450 MHz machine.
 | |
| 
 | |
| 	User-launched application startup times will have a variance of
 | |
| 	a second or more relative to machine startup time. Entropy >~22
 | |
| 	bits.
 | |
| 
 | |
| 	Machine startup time is available with a 1-second resolution. It
 | |
| 	is predictable to no better a minute or two, in the case of
 | |
| 	people who show up punctually to work at the same time and
 | |
| 	immediately start their computer. Using the scheduled startup
 | |
| 	feature (when available) will cause the machine to start up at
 | |
| 	the same time every day, making the value predictable. Entropy
 | |
| 	>~7 bits, or 0 bits with scheduled startup.
 | |
| 
 | |
| 	The time of day is of course known to an outsider and thus has 0
 | |
| 	entropy if the system clock is regularly calibrated.
 | |
| 
 | |
| --	KEY TIMING: A  very fast typist (120 wpm) will have a typical
 | |
| 	inter-key timing interval of 100 msec. We can assume a variance
 | |
| 	of no less than 2 msec -- maybe. Do good typists have a constant
 | |
| 	rhythm, like drummers? Since what we measure is not the
 | |
| 	key-generated interrupt but the time at which the key event was
 | |
| 	taken off the event queue, our resolution is roughly the time
 | |
| 	between process switches, at best 1 tick (17 msec). I  therefore
 | |
| 	consider this technique questionable and not very useful for
 | |
| 	obtaining high entropy data on the Mac.
 | |
| 
 | |
| --	MOUSE POSITION AND TIMING: The high bits of the mouse position
 | |
| 	are far from arbitrary, since the mouse tends to stay in a few
 | |
| 	limited areas of the screen. I am guessing that the position of
 | |
| 	the mouse is arbitrary within a 6 pixel square. Since the mouse
 | |
| 	stays still for long periods of time, it should be sampled only
 | |
| 	after it was moved, to avoid correlated data. This gives an
 | |
| 	entropy of log2(6*6) ~= 5 bits per measurement.
 | |
| 
 | |
| 	The time during which the mouse stays still can vary from zero
 | |
| 	to, say, 5 seconds (occasionally longer). If the still time is
 | |
| 	measured by sampling the mouse during null events, and null
 | |
| 	events are received once per tick, its resolution is 1/60th of a
 | |
| 	second, giving an entropy of log2 (60*5) ~= 8 bits per
 | |
| 	measurement. Since the distribution of still times is uneven,
 | |
| 	this estimate is on the high side.
 | |
| 
 | |
| 	For simplicity and compatibility across system versions, the
 | |
| 	mouse is to be sampled explicitly (e.g. in the event loop),
 | |
| 	rather than in a time manager task.
 | |
| 
 | |
| --	STARTUP DISK TOTAL FILE SIZE: Varies typically by at least 20k
 | |
| 	from one startup to the next, with 'minimal' computer use. Won't
 | |
| 	vary at all if machine is started again immediately after
 | |
| 	startup (unless virtual memory is on), but any application which
 | |
| 	uses the web and caches information to disk is likely to cause
 | |
| 	this much variation or more. The variation is probably not
 | |
| 	random, but I don't know in what way. File sizes tend to be
 | |
| 	divisible by 4 bytes since file format fields are often
 | |
| 	long-aligned. Entropy > log2 (20000/4) ~= 12 bits.
 | |
| 	
 | |
| --	STARTUP DISK FIRST AVAILABLE ALLOCATION BLOCK: As the volume
 | |
| 	gets fragmented this could be anywhere in principle. In a
 | |
| 	perfectly unfragmented volume this will be strongly correlated
 | |
| 	with the total file size on the disk. With more fragmentation
 | |
| 	comes less certainty. I took the variation in this value to be
 | |
| 	1/8 of the total file size on the volume.
 | |
| 
 | |
| --	SYSTEM REQUIREMENTS: The code here requires System 7.0 and above
 | |
| 	(for Gestalt and Microseconds calls). All the calls used are
 | |
| 	Carbon-compatible.
 | |
| */
 | |
| 
 | |
| /*------------------------------ Includes ----------------------------*/
 | |
| 
 | |
| #include "Randomizer.h"
 | |
| 
 | |
| // Mac OS API
 | |
| #include <Files.h>
 | |
| #include <Folders.h>
 | |
| #include <Events.h>
 | |
| #include <Processes.h>
 | |
| #include <Gestalt.h>
 | |
| #include <Resources.h>
 | |
| #include <LowMem.h>
 | |
| 
 | |
| // Standard C library
 | |
| #include <stdlib.h>
 | |
| #include <math.h>
 | |
| 
 | |
| /*---------------------- Function declarations -----------------------*/
 | |
| 
 | |
| // declared in OpenSSL/crypto/rand/rand.h
 | |
| extern "C" void RAND_add (const void *buf, int num, double entropy);
 | |
| 
 | |
| unsigned long GetPPCTimer (bool is601);	// Make it global if needed
 | |
| 					// elsewhere
 | |
| 
 | |
| /*---------------------------- Constants -----------------------------*/
 | |
| 
 | |
| #define kMouseResolution 6		// Mouse position has to differ
 | |
| 					// from the last one by this
 | |
| 					// much to be entered
 | |
| #define kMousePositionEntropy 5.16	// log2 (kMouseResolution**2)
 | |
| #define kTypicalMouseIdleTicks 300.0	// I am guessing that a typical
 | |
| 					// amount of time between mouse
 | |
| 					// moves is 5 seconds
 | |
| #define kVolumeBytesEntropy 12.0	// about log2 (20000/4),
 | |
| 					// assuming a variation of 20K
 | |
| 					// in total file size and
 | |
| 					// long-aligned file formats.
 | |
| #define kApplicationUpTimeEntropy 6.0	// Variance > 1 second, uptime
 | |
| 					// in ticks  
 | |
| #define kSysStartupEntropy 7.0		// Entropy for machine startup
 | |
| 					// time
 | |
| 
 | |
| 
 | |
| /*------------------------ Function definitions ----------------------*/
 | |
| 
 | |
| CRandomizer::CRandomizer (void)
 | |
| {
 | |
| 	long	result;
 | |
| 	
 | |
| 	mSupportsLargeVolumes =
 | |
| 		(Gestalt(gestaltFSAttr, &result) == noErr) &&
 | |
| 		((result & (1L << gestaltFSSupports2TBVols)) != 0);
 | |
| 	
 | |
| 	if (Gestalt (gestaltNativeCPUtype, &result) != noErr)
 | |
| 	{
 | |
| 		mIsPowerPC = false;
 | |
| 		mIs601 = false;
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 		mIs601 = (result == gestaltCPU601);
 | |
| 		mIsPowerPC = (result >= gestaltCPU601);
 | |
| 	}
 | |
| 	mLastMouse.h = mLastMouse.v = -10;	// First mouse will
 | |
| 						// always be recorded
 | |
| 	mLastPeriodicTicks = TickCount();
 | |
| 	GetTimeBaseResolution ();
 | |
| 	
 | |
| 	// Add initial entropy
 | |
| 	AddTimeSinceMachineStartup ();
 | |
| 	AddAbsoluteSystemStartupTime ();
 | |
| 	AddStartupVolumeInfo ();
 | |
| 	AddFiller ();
 | |
| }
 | |
| 
 | |
| void CRandomizer::PeriodicAction (void)
 | |
| {
 | |
| 	AddCurrentMouse ();
 | |
| 	AddNow (0.0);	// Should have a better entropy estimate here
 | |
| 	mLastPeriodicTicks = TickCount();
 | |
| }
 | |
| 
 | |
| /*------------------------- Private Methods --------------------------*/
 | |
| 
 | |
| void CRandomizer::AddCurrentMouse (void)
 | |
| {
 | |
| 	Point mouseLoc;
 | |
| 	unsigned long lastCheck;	// Ticks since mouse was last
 | |
| 					// sampled
 | |
| 
 | |
| #if TARGET_API_MAC_CARBON
 | |
| 	GetGlobalMouse (&mouseLoc);
 | |
| #else
 | |
| 	mouseLoc = LMGetMouseLocation();
 | |
| #endif
 | |
| 	
 | |
| 	if (labs (mLastMouse.h - mouseLoc.h) > kMouseResolution/2 &&
 | |
| 	    labs (mLastMouse.v - mouseLoc.v) > kMouseResolution/2)
 | |
| 		AddBytes (&mouseLoc, sizeof (mouseLoc),
 | |
| 				kMousePositionEntropy);
 | |
| 	
 | |
| 	if (mLastMouse.h == mouseLoc.h && mLastMouse.v == mouseLoc.v)
 | |
| 		mMouseStill ++;
 | |
| 	else
 | |
| 	{
 | |
| 		double entropy;
 | |
| 		
 | |
| 		// Mouse has moved. Add the number of measurements for
 | |
| 		// which it's been still. If the resolution is too
 | |
| 		// coarse, assume the entropy is 0.
 | |
| 
 | |
| 		lastCheck = TickCount() - mLastPeriodicTicks;
 | |
| 		if (lastCheck <= 0)
 | |
| 			lastCheck = 1;
 | |
| 		entropy = log2l
 | |
| 			(kTypicalMouseIdleTicks/(double)lastCheck);
 | |
| 		if (entropy < 0.0)
 | |
| 			entropy = 0.0;
 | |
| 		AddBytes (&mMouseStill, sizeof (mMouseStill), entropy);
 | |
| 		mMouseStill = 0;
 | |
| 	}
 | |
| 	mLastMouse = mouseLoc;
 | |
| }
 | |
| 
 | |
| void CRandomizer::AddAbsoluteSystemStartupTime (void)
 | |
| {
 | |
| 	unsigned long	now;		// Time in seconds since
 | |
| 					// 1/1/1904
 | |
| 	GetDateTime (&now);
 | |
| 	now -= TickCount() / 60;	// Time in ticks since machine
 | |
| 					// startup
 | |
| 	AddBytes (&now, sizeof (now), kSysStartupEntropy);
 | |
| }
 | |
| 
 | |
| void CRandomizer::AddTimeSinceMachineStartup (void)
 | |
| {
 | |
| 	AddNow (1.5);			// Uncertainty in app startup
 | |
| 					// time is > 1.5 msec (for
 | |
| 					// automated app startup).
 | |
| }
 | |
| 
 | |
| void CRandomizer::AddAppRunningTime (void)
 | |
| {
 | |
| 	ProcessSerialNumber PSN;
 | |
| 	ProcessInfoRec		ProcessInfo;
 | |
| 	
 | |
| 	ProcessInfo.processInfoLength = sizeof (ProcessInfoRec);
 | |
| 	ProcessInfo.processName = nil;
 | |
| 	ProcessInfo.processAppSpec = nil;
 | |
| 	
 | |
| 	GetCurrentProcess (&PSN);
 | |
| 	GetProcessInformation (&PSN, &ProcessInfo);
 | |
| 
 | |
| 	// Now add the amount of time in ticks that the current process
 | |
| 	// has been active
 | |
| 
 | |
| 	AddBytes (&ProcessInfo, sizeof (ProcessInfoRec),
 | |
| 			kApplicationUpTimeEntropy);
 | |
| }
 | |
| 
 | |
| void CRandomizer::AddStartupVolumeInfo (void)
 | |
| {
 | |
| 	short			vRefNum;
 | |
| 	long			dirID;
 | |
| 	XVolumeParam	pb;
 | |
| 	OSErr			err;
 | |
| 	
 | |
| 	if (!mSupportsLargeVolumes)
 | |
| 		return;
 | |
| 		
 | |
| 	FindFolder (kOnSystemDisk, kSystemFolderType, kDontCreateFolder,
 | |
| 			&vRefNum, &dirID);
 | |
| 	pb.ioVRefNum = vRefNum;
 | |
| 	pb.ioCompletion = 0;
 | |
| 	pb.ioNamePtr = 0;
 | |
| 	pb.ioVolIndex = 0;
 | |
| 	err = PBXGetVolInfoSync (&pb);
 | |
| 	if (err != noErr)
 | |
| 		return;
 | |
| 		
 | |
| 	// Base the entropy on the amount of space used on the disk and
 | |
| 	// on the next available allocation block. A lot else might be
 | |
| 	// unpredictable, so might as well toss the whole block in. See
 | |
| 	// comments for entropy estimate justifications.
 | |
| 
 | |
| 	AddBytes (&pb, sizeof (pb),
 | |
| 		kVolumeBytesEntropy +
 | |
| 		log2l (((pb.ioVTotalBytes.hi - pb.ioVFreeBytes.hi)
 | |
| 				* 4294967296.0D +
 | |
| 			(pb.ioVTotalBytes.lo - pb.ioVFreeBytes.lo))
 | |
| 				/ pb.ioVAlBlkSiz - 3.0));
 | |
| }
 | |
| 
 | |
| /*
 | |
| 	On a typical startup CRandomizer will come up with about 60
 | |
| 	bits of good, unpredictable data. Assuming no more input will
 | |
| 	be available, we'll need some more lower-quality data to give
 | |
| 	OpenSSL the 128 bits of entropy it desires. AddFiller adds some
 | |
| 	relatively predictable data into the soup.
 | |
| */
 | |
| 
 | |
| void CRandomizer::AddFiller (void)
 | |
| {
 | |
| 	struct
 | |
| 	{
 | |
| 		ProcessSerialNumber psn;	// Front process serial
 | |
| 						// number
 | |
| 		RGBColor	hiliteRGBValue;	// User-selected
 | |
| 						// highlight color
 | |
| 		long		processCount;	// Number of active
 | |
| 						// processes
 | |
| 		long		cpuSpeed;	// Processor speed
 | |
| 		long		totalMemory;	// Total logical memory
 | |
| 						// (incl. virtual one)
 | |
| 		long		systemVersion;	// OS version
 | |
| 		short		resFile;	// Current resource file
 | |
| 	} data;
 | |
| 	
 | |
| 	GetNextProcess ((ProcessSerialNumber*) kNoProcess);
 | |
| 	while (GetNextProcess (&data.psn) == noErr)
 | |
| 		data.processCount++;
 | |
| 	GetFrontProcess (&data.psn);
 | |
| 	LMGetHiliteRGB (&data.hiliteRGBValue);
 | |
| 	Gestalt (gestaltProcClkSpeed, &data.cpuSpeed);
 | |
| 	Gestalt (gestaltLogicalRAMSize, &data.totalMemory);
 | |
| 	Gestalt (gestaltSystemVersion, &data.systemVersion);
 | |
| 	data.resFile = CurResFile ();
 | |
| 	
 | |
| 	// Here we pretend to feed the PRNG completely random data. This
 | |
| 	// is of course false, as much of the above data is predictable
 | |
| 	// by an outsider. At this point we don't have any more
 | |
| 	// randomness to add, but with OpenSSL we must have a 128 bit
 | |
| 	// seed before we can start. We just add what we can, without a
 | |
| 	// real entropy estimate, and hope for the best.
 | |
| 
 | |
| 	AddBytes (&data, sizeof(data), 8.0 * sizeof(data));
 | |
| 	AddCurrentMouse ();
 | |
| 	AddNow (1.0);
 | |
| }
 | |
| 
 | |
| //-------------------  LOW LEVEL ---------------------
 | |
| 
 | |
| void CRandomizer::AddBytes (void *data, long size, double entropy)
 | |
| {
 | |
| 	RAND_add (data, size, entropy * 0.125);	// Convert entropy bits
 | |
| 						// to bytes
 | |
| }
 | |
| 
 | |
| void CRandomizer::AddNow (double millisecondUncertainty)
 | |
| {
 | |
| 	long time = SysTimer();
 | |
| 	AddBytes (&time, sizeof (time), log2l (millisecondUncertainty *
 | |
| 			mTimebaseTicksPerMillisec));
 | |
| }
 | |
| 
 | |
| //----------------- TIMING SUPPORT ------------------
 | |
| 
 | |
| void CRandomizer::GetTimeBaseResolution (void)
 | |
| {	
 | |
| #ifdef __powerc
 | |
| 	long speed;
 | |
| 	
 | |
| 	// gestaltProcClkSpeed available on System 7.5.2 and above
 | |
| 	if (Gestalt (gestaltProcClkSpeed, &speed) != noErr)
 | |
| 		// Only PowerPCs running pre-7.5.2 are 60-80 MHz
 | |
| 		// machines.
 | |
| 		mTimebaseTicksPerMillisec =  6000.0D;
 | |
| 	// Assume 10 cycles per clock update, as in 601 spec. Seems true
 | |
| 	// for later chips as well.
 | |
| 	mTimebaseTicksPerMillisec = speed / 1.0e4D;
 | |
| #else
 | |
| 	// 68K VIA-based machines (see Develop Magazine no. 29)
 | |
| 	mTimebaseTicksPerMillisec = 783.360D;
 | |
| #endif
 | |
| }
 | |
| 
 | |
| unsigned long CRandomizer::SysTimer (void)	// returns the lower 32
 | |
| 						// bit of the chip timer
 | |
| {
 | |
| #ifdef __powerc
 | |
| 	return GetPPCTimer (mIs601);
 | |
| #else
 | |
| 	UnsignedWide usec;
 | |
| 	Microseconds (&usec);
 | |
| 	return usec.lo;
 | |
| #endif
 | |
| }
 | |
| 
 | |
| #ifdef __powerc
 | |
| // The timebase is available through mfspr on 601, mftb on later chips.
 | |
| // Motorola recommends that an 601 implementation map mftb to mfspr
 | |
| // through an exception, but I haven't tested to see if MacOS actually
 | |
| // does this. We only sample the lower 32 bits of the timer (i.e. a
 | |
| // few minutes of resolution)
 | |
| 
 | |
| asm unsigned long GetPPCTimer (register bool is601)
 | |
| {
 | |
| 	cmplwi	is601, 0	// Check if 601
 | |
| 	bne	_601		// if non-zero goto _601
 | |
| 	mftb  	r3		// Available on 603 and later.
 | |
| 	blr			// return with result in r3
 | |
| _601:
 | |
| 	mfspr r3, spr5  	// Available on 601 only.
 | |
| 				// blr inserted automatically
 | |
| }
 | |
| #endif
 |