Logo Search packages:      
Sourcecode: openssl version File versions  Download package

Randomizer.cpp

/* 
------- 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

Generated by  Doxygen 1.6.0   Back to index