]> git.ipfire.org Git - thirdparty/openssl.git/blob - MacOS/Randomizer.cpp
format strings
[thirdparty/openssl.git] / MacOS / Randomizer.cpp
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
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
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
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
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
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
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
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
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
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
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
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
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
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