Merge pull request #2323 from esdrubal/servicemodel
[mono.git] / mono / utils / mono-rand.c
1 /*
2  * mono-rand.c: 
3  *
4  * Authors:
5  *      Mark Crichton (crichton@gimp.org)
6  *      Patrik Torstensson (p@rxc.se)
7  *      Sebastien Pouliot (sebastien@ximian.com)
8  *      Ludovic Henry (ludovic.henry@xamarin.com)
9  *
10  * Copyright 2001-2003 Ximian, Inc (http://www.ximian.com)
11  * Copyright 2004-2009 Novell, Inc (http://www.novell.com)
12  * Copyright 2001 Xamarin, Inc (http://www.novell.com)
13  */
14
15
16 #include <glib.h>
17 #include <config.h>
18
19 #include "atomic.h"
20 #include "mono-rand.h"
21 #include "mono-threads.h"
22 #include "metadata/exception.h"
23 #include "metadata/object.h"
24
25 #ifdef HOST_WIN32
26
27 #include <windows.h>
28 #include <wincrypt.h>
29
30 #ifndef PROV_INTEL_SEC
31 #define PROV_INTEL_SEC          22
32 #endif
33 #ifndef CRYPT_VERIFY_CONTEXT
34 #define CRYPT_VERIFY_CONTEXT    0xF0000000
35 #endif
36
37 /**
38  * mono_rand_open:
39  *
40  * Returns: True if random source is global, false if mono_rand_init can be called repeatedly to get randomness instances.
41  *
42  * Initializes entire RNG system. Must be called once per process before calling mono_rand_init.
43  */
44 gboolean
45 mono_rand_open (void)
46 {
47         return FALSE;
48 }
49
50 /**
51  * mono_rand_init:
52  * @seed: A string containing seed data
53  * @seed_size: Length of seed string
54  *
55  * Returns: On success, a non-NULL handle which can be used to fetch random data from mono_rand_try_get_bytes. On failure, NULL.
56  *
57  * Initializes an RNG client.
58  */
59 gpointer
60 mono_rand_init (guchar *seed, gint seed_size)
61 {
62         HCRYPTPROV provider = 0;
63
64         /* There is no need to create a container for just random data,
65          * so we can use CRYPT_VERIFY_CONTEXT (one call) see: 
66          * http://blogs.msdn.com/dangriff/archive/2003/11/19/51709.aspx */
67
68         /* We first try to use the Intel PIII RNG if drivers are present */
69         if (!CryptAcquireContext (&provider, NULL, NULL, PROV_INTEL_SEC, CRYPT_VERIFY_CONTEXT)) {
70                 /* not a PIII or no drivers available, use default RSA CSP */
71                 if (!CryptAcquireContext (&provider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFY_CONTEXT)) {
72                         /* exception will be thrown in managed code */
73                         provider = 0;
74                 }
75         }
76
77         /* seed the CSP with the supplied buffer (if present) */
78         if (provider != 0 && seed) {
79                 /* the call we replace the seed with random - this isn't what is
80                  * expected from the class library user */
81                 guchar *data = g_malloc (seed_size);
82                 if (data) {
83                         memcpy (data, seed, seed_size);
84                         /* add seeding material to the RNG */
85                         CryptGenRandom (provider, seed_size, data);
86                         /* zeroize and free */
87                         memset (data, 0, seed_size);
88                         g_free (data);
89                 }
90         }
91
92         return (gpointer) provider;
93 }
94
95 /**
96  * mono_rand_try_get_bytes:
97  * @handle: A pointer to an RNG handle. Handle is set to NULL on failure.
98  * @buffer: A buffer into which to write random data.
99  * @buffer_size: Number of bytes to write into buffer.
100  *
101  * Returns: FALSE on failure, TRUE on success.
102  *
103  * Extracts bytes from an RNG handle.
104  */
105 gboolean
106 mono_rand_try_get_bytes (gpointer *handle, guchar *buffer, gint buffer_size)
107 {
108         HCRYPTPROV provider;
109
110         g_assert (handle);
111         provider = (HCRYPTPROV) *handle;
112
113         if (!CryptGenRandom (provider, buffer_size, buffer)) {
114                 CryptReleaseContext (provider, 0);
115                 /* we may have lost our context with CryptoAPI, but all hope isn't lost yet! */
116                 provider = (HCRYPTPROV) mono_rand_init (NULL, 0);
117                 if (!CryptGenRandom (provider, buffer_size, buffer)) {
118                         /* exception will be thrown in managed code */
119                         CryptReleaseContext (provider, 0);
120                         *handle = 0;
121                         return FALSE;
122                 }
123         }
124         return TRUE;
125 }
126
127 /**
128  * mono_rand_close:
129  * @handle: An RNG handle.
130  * @buffer: A buffer into which to write random data.
131  * @buffer_size: Number of bytes to write into buffer.
132  *
133  * Releases an RNG handle.
134  */
135 void
136 mono_rand_close (gpointer handle)
137 {
138         CryptReleaseContext ((HCRYPTPROV) handle, 0);
139 }
140
141 #elif defined (HAVE_SYS_UN_H) && !defined(__native_client__)
142
143 #include <errno.h>
144 #include <fcntl.h>
145 #include <unistd.h>
146 #include <sys/socket.h>
147 #include <sys/types.h>
148 #include <sys/un.h>
149
150 #ifndef NAME_DEV_URANDOM
151 #define NAME_DEV_URANDOM "/dev/urandom"
152 #endif
153
154 static gboolean use_egd = FALSE;
155 static gint file = -1;
156
157 static void
158 get_entropy_from_egd (const char *path, guchar *buffer, int buffer_size)
159 {
160         struct sockaddr_un egd_addr;
161         gint file;
162         gint ret;
163         guint offset = 0;
164
165         file = socket (PF_UNIX, SOCK_STREAM, 0);
166         if (file < 0) {
167                 ret = -1;
168         } else {
169                 egd_addr.sun_family = AF_UNIX;
170                 strncpy (egd_addr.sun_path, path, sizeof (egd_addr.sun_path) - 1);
171                 egd_addr.sun_path [sizeof (egd_addr.sun_path) - 1] = '\0';
172                 ret = connect (file, (struct sockaddr*) &egd_addr, sizeof (egd_addr));
173         }
174         if (ret == -1) {
175                 if (file >= 0)
176                         close (file);
177                 g_warning ("Entropy problem! Can't create or connect to egd socket %s", path);
178                 mono_raise_exception (mono_get_exception_execution_engine ("Failed to open egd socket"));
179         }
180
181         while (buffer_size > 0) {
182                 guchar request [2];
183                 gint count = 0;
184
185                 /* block until daemon can return enough entropy */
186                 request [0] = 2;
187                 request [1] = buffer_size < 255 ? buffer_size : 255;
188                 while (count < 2) {
189                         int sent = write (file, request + count, 2 - count);
190                         if (sent >= 0) {
191                                 count += sent;
192                         } else if (errno == EINTR) {
193                                 continue;
194                         } else {
195                                 close (file);
196                                 g_warning ("Send egd request failed %d", errno);
197                                 mono_raise_exception (mono_get_exception_execution_engine ("Failed to send request to egd socket"));
198                         }
199                 }
200
201                 count = 0;
202                 while (count != request [1]) {
203                         int received;
204                         received = read (file, buffer + offset, request [1] - count);
205                         if (received > 0) {
206                                 count += received;
207                                 offset += received;
208                         } else if (received < 0 && errno == EINTR) {
209                                 continue;
210                         } else {
211                                 close (file);
212                                 g_warning ("Receive egd request failed %d", errno);
213                                 mono_raise_exception (mono_get_exception_execution_engine ("Failed to get response from egd socket"));
214                         }
215                 }
216
217                 buffer_size -= request [1];
218         }
219
220         close (file);
221 }
222
223 gboolean
224 mono_rand_open (void)
225 {
226         static gint32 status = 0;
227         if (status != 0 || InterlockedCompareExchange (&status, 1, 0) != 0) {
228                 while (status != 2)
229                         mono_thread_info_yield ();
230                 return TRUE;
231         }
232
233 #ifdef NAME_DEV_URANDOM
234         file = open (NAME_DEV_URANDOM, O_RDONLY);
235 #endif
236 #ifdef NAME_DEV_RANDOM
237         if (file < 0)
238                 file = open (NAME_DEV_RANDOM, O_RDONLY);
239 #endif
240         if (file < 0)
241                 use_egd = g_getenv("MONO_EGD_SOCKET") != NULL;
242
243         status = 2;
244
245         return TRUE;
246 }
247
248 gpointer
249 mono_rand_init (guchar *seed, gint seed_size)
250 {
251         // file < 0 is expected in the egd case
252         return (!use_egd && file < 0) ? NULL : GINT_TO_POINTER (file);
253 }
254
255 gboolean
256 mono_rand_try_get_bytes (gpointer *handle, guchar *buffer, gint buffer_size)
257 {
258         g_assert (handle);
259
260         if (use_egd) {
261                 const char *socket_path = g_getenv ("MONO_EGD_SOCKET");
262                 /* exception will be thrown in managed code */
263                 if (socket_path == NULL) {
264                         *handle = NULL;
265                         return FALSE;
266                 }
267                 get_entropy_from_egd (socket_path, buffer, buffer_size);
268         } else {
269                 /* Read until the buffer is filled. This may block if using NAME_DEV_RANDOM. */
270                 gint count = 0;
271                 gint err;
272
273                 do {
274                         err = read (file, buffer + count, buffer_size - count);
275                         if (err < 0) {
276                                 if (errno == EINTR)
277                                         continue;
278                                 g_warning("Entropy error! Error in read (%s).", strerror (errno));
279                                 /* exception will be thrown in managed code */
280                                 return FALSE;
281                         }
282                         count += err;
283                 } while (count < buffer_size);
284         }
285         return TRUE;
286 }
287
288 void
289 mono_rand_close (gpointer provider)
290 {
291 }
292
293 #else
294
295 #include <stdlib.h>
296 #include <time.h>
297
298 gboolean
299 mono_rand_open (void)
300 {
301         static gint32 status = 0;
302         if (status != 0 || InterlockedCompareExchange (&status, 1, 0) != 0) {
303                 while (status != 2)
304                         mono_thread_info_yield ();
305                 return TRUE;
306         }
307
308         srand (time (NULL));
309
310         status = 2;
311
312         return TRUE;
313 }
314
315 gpointer
316 mono_rand_init (guchar *seed, gint seed_size)
317 {
318         return "srand"; // NULL will be interpreted as failure; return arbitrary nonzero pointer
319 }
320
321 gboolean
322 mono_rand_try_get_bytes (gpointer *handle, guchar *buffer, gint buffer_size)
323 {
324         gint count = 0;
325
326         do {
327                 if (buffer_size - count >= sizeof (gint32) && RAND_MAX >= 0xFFFFFFFF) {
328                         *(gint32*) buffer = rand();
329                         count += sizeof (gint32);
330                         buffer += sizeof (gint32) / sizeof (guchar);
331                 } else if (buffer_size - count >= sizeof (gint16) && RAND_MAX >= 0xFFFF) {
332                         *(gint16*) buffer = rand();
333                         count += sizeof (gint16);
334                         buffer += sizeof (gint16) / sizeof (guchar);
335                 } else if (buffer_size - count >= sizeof (gint8) && RAND_MAX >= 0xFF) {
336                         *(gint8*) buffer = rand();
337                         count += sizeof (gint8);
338                         buffer += sizeof (gint8) / sizeof (guchar);
339                 }
340         } while (count < buffer_size);
341
342         return TRUE;
343 }
344
345 void
346 mono_rand_close (gpointer provider)
347 {
348 }
349
350 #endif
351
352 /**
353  * mono_rand_try_get_uint32:
354  * @handle: A pointer to an RNG handle. Handle is set to NULL on failure.
355  * @val: A pointer to a 32-bit unsigned int, to which the result will be written.
356  * @min: Result will be greater than or equal to this value.
357  * @max: Result will be less than or equal to this value.
358  *
359  * Returns: FALSE on failure, TRUE on success.
360  *
361  * Extracts one 32-bit unsigned int from an RNG handle.
362  */
363 gboolean
364 mono_rand_try_get_uint32 (gpointer *handle, guint32 *val, guint32 min, guint32 max)
365 {
366         g_assert (val);
367         if (!mono_rand_try_get_bytes (handle, (guchar*) val, sizeof (guint32)))
368                 return FALSE;
369
370         double randomDouble = ((gdouble) *val) / ( ((double)G_MAXUINT32) + 1 ); // Range is [0,1)
371         *val = (guint32) (randomDouble * (max - min + 1) + min);
372
373         g_assert (*val >= min);
374         g_assert (*val <= max);
375
376         return TRUE;
377 }