NaCl runtime fixes
[mono.git] / mono / metadata / rand.c
1 /*
2  * rand.c: System.Security.Cryptography.RNGCryptoServiceProvider support
3  *
4  * Authors:
5  *      Mark Crichton (crichton@gimp.org)
6  *      Patrik Torstensson (p@rxc.se)
7  *      Sebastien Pouliot (sebastien@ximian.com)
8  *
9  * Copyright 2001-2003 Ximian, Inc (http://www.ximian.com)
10  * Copyright 2004-2009 Novell, Inc (http://www.novell.com)
11  */
12
13 #include <config.h>
14 #include <glib.h>
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 #include <fcntl.h>
18 #ifdef HAVE_UNISTD_H
19 #include <unistd.h>
20 #endif
21 #ifdef HAVE_STRING_H
22 #include <string.h>
23 #endif
24
25 #include <mono/metadata/object.h>
26 #include <mono/metadata/rand.h>
27 #include <mono/metadata/exception.h>
28
29 #if !defined(__native_client__) && !defined(HOST_WIN32)
30 #include <sys/socket.h>
31 #include <sys/un.h>
32 #include <errno.h>
33
34 static void
35 get_entropy_from_server (const char *path, guchar *buf, int len)
36 {
37     int file;
38     gint ret;
39     guint offset = 0;
40     struct sockaddr_un egd_addr;
41
42     file = socket (PF_UNIX, SOCK_STREAM, 0);
43     if (file < 0)
44         ret = -1;
45     else {
46         egd_addr.sun_family = AF_UNIX;
47         strncpy (egd_addr.sun_path, path, MONO_SIZEOF_SUNPATH - 1);
48         egd_addr.sun_path [MONO_SIZEOF_SUNPATH-1] = '\0';
49         ret = connect (file, (struct sockaddr *)&egd_addr, sizeof(egd_addr));
50     }
51     if (ret == -1) {
52         if (file >= 0)
53             close (file);
54         g_warning ("Entropy problem! Can't create or connect to egd socket %s", path);
55         mono_raise_exception (mono_get_exception_execution_engine ("Failed to open egd socket"));
56     }
57
58     while (len > 0) {
59         guchar request[2];
60         gint count = 0;
61
62         request [0] = 2; /* block until daemon can return enough entropy */
63         request [1] = len < 255 ? len : 255;
64         while (count < 2) {
65             int sent = write (file, request + count, 2 - count);
66             if (sent >= 0)
67                 count += sent;
68             else if (errno == EINTR)
69                 continue;
70             else {
71                 close (file);
72                 g_warning ("Send egd request failed %d", errno);
73                 mono_raise_exception (mono_get_exception_execution_engine ("Failed to send request to egd socket"));
74             }
75         }
76
77         count = 0;
78         while (count != request [1]) {
79             int received;
80             received = read(file, buf + offset, request [1] - count);
81             if (received > 0) {
82                 count += received;
83                 offset += received;
84             } else if (received < 0 && errno == EINTR) {
85                 continue;
86             } else {
87                 close (file);
88                 g_warning ("Receive egd request failed %d", errno);
89                 mono_raise_exception (mono_get_exception_execution_engine ("Failed to get response from egd socket"));
90             }
91         }
92
93         len -= request [1];
94     }
95
96     close (file);
97 }
98 #endif
99
100 #if defined (HOST_WIN32)
101
102 #include <windows.h>
103 #include <wincrypt.h>
104
105 #ifndef PROV_INTEL_SEC
106 #define PROV_INTEL_SEC          22
107 #endif
108 #ifndef CRYPT_VERIFY_CONTEXT
109 #define CRYPT_VERIFY_CONTEXT    0xF0000000
110 #endif
111
112 MonoBoolean
113 ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_RngOpen (void)
114 {
115         /* FALSE == Local (instance) handle for randomness */
116         return FALSE;
117 }
118
119 gpointer
120 ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_RngInitialize (MonoArray *seed)
121 {
122         HCRYPTPROV provider = 0;
123
124         /* There is no need to create a container for just random data,
125            so we can use CRYPT_VERIFY_CONTEXT (one call) see: 
126            http://blogs.msdn.com/dangriff/archive/2003/11/19/51709.aspx */
127
128         /* We first try to use the Intel PIII RNG if drivers are present */
129         if (!CryptAcquireContext (&provider, NULL, NULL, PROV_INTEL_SEC, CRYPT_VERIFY_CONTEXT)) {
130                 /* not a PIII or no drivers available, use default RSA CSP */
131                 if (!CryptAcquireContext (&provider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFY_CONTEXT)) {
132                         provider = 0;
133                         /* exception will be thrown in managed code */
134                 }
135         }
136
137         /* seed the CSP with the supplied buffer (if present) */
138         if ((provider != 0) && (seed)) {
139                 guint32 len = mono_array_length (seed);
140                 guchar *buf = mono_array_addr (seed, guchar, 0);
141                 /* the call we replace the seed with random - this isn't what is
142                    expected from the class library user */
143                 guchar *data = g_malloc (len);
144                 if (data) {
145                         memcpy (data, buf, len);
146                         /* add seeding material to the RNG */
147                         CryptGenRandom (provider, len, data);
148                         /* zeroize and free */
149                         memset (data, 0, len);
150                         g_free (data);
151                 }
152         }
153
154         return (gpointer) provider;     
155 }
156
157 gpointer
158 ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_RngGetBytes (gpointer handle, MonoArray *arry)
159 {
160         HCRYPTPROV provider = (HCRYPTPROV) handle;
161         guint32 len = mono_array_length (arry);
162         guchar *buf = mono_array_addr (arry, guchar, 0);
163
164         if (!CryptGenRandom (provider, len, buf)) {
165                 CryptReleaseContext (provider, 0);
166                 /* we may have lost our context with CryptoAPI, but all hope isn't lost yet! */
167                 provider = (HCRYPTPROV) ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_RngInitialize (NULL);
168                 if (!CryptGenRandom (provider, len, buf)) {
169                         CryptReleaseContext (provider, 0);
170                         provider = 0;
171                         /* exception will be thrown in managed code */
172                 }
173         }
174         return (gpointer) provider;
175 }
176
177 void
178 ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_RngClose (gpointer handle) 
179 {
180         CryptReleaseContext ((HCRYPTPROV) handle, 0);
181 }
182
183 #elif defined (__native_client__)
184
185 #include <time.h>
186
187 MonoBoolean
188 ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_RngOpen (void)
189 {
190         srand (time (NULL));
191         return TRUE;
192 }
193
194 gpointer
195 ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_RngInitialize (MonoArray *seed)
196 {
197         return -1;
198 }
199
200 gpointer 
201 ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_RngGetBytes (gpointer handle, MonoArray *arry)
202 {       
203         guint32 len = mono_array_length (arry);
204         guchar *buf = mono_array_addr (arry, guchar, 0);
205
206         /* Read until the buffer is filled. This may block if using NAME_DEV_RANDOM. */
207         gint count = 0;
208         gint err;
209
210         do {
211                 if (len - count >= sizeof (long))
212                 {
213                         *(long*)buf = rand();
214                         count += sizeof (long);
215                 }
216                 else if (len - count >= sizeof (short))
217                 {
218                         *(short*)buf = rand();
219                         count += sizeof (short);
220                 }
221                 else if (len - count >= sizeof (char))
222                 {
223                         *buf = rand();
224                         count += sizeof (char);
225                 }
226         } while (count < len);
227
228         return (gpointer)-1L;
229 }
230
231 void
232 ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_RngClose (gpointer handle) 
233 {
234 }
235 #else
236
237 #ifndef NAME_DEV_URANDOM
238 #define NAME_DEV_URANDOM "/dev/urandom"
239 #endif
240
241 static gboolean egd = FALSE;
242 static gint file = -1;
243
244 MonoBoolean
245 ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_RngOpen (void)
246 {
247         if (egd || (file >= 0))
248                 return TRUE;
249
250 #if defined (NAME_DEV_URANDOM)
251         file = open (NAME_DEV_URANDOM, O_RDONLY);
252 #endif
253
254 #if defined (NAME_DEV_RANDOM)
255         if (file < 0)
256                 file = open (NAME_DEV_RANDOM, O_RDONLY);
257 #endif
258
259         if (file < 0) {
260                 const char *socket_path = g_getenv("MONO_EGD_SOCKET");
261                 egd = (socket_path != NULL);
262         }
263
264         /* TRUE == Global handle for randomness */
265         return TRUE;
266 }
267
268 gpointer
269 ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_RngInitialize (MonoArray *seed)
270 {
271         /* if required exception will be thrown in managed code */
272         return ((!egd && (file < 0)) ? NULL : GINT_TO_POINTER (file));
273 }
274
275 gpointer 
276 ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_RngGetBytes (gpointer handle, MonoArray *arry)
277 {
278         gint file = GPOINTER_TO_INT (handle);
279         guint32 len = mono_array_length (arry);
280         guchar *buf = mono_array_addr (arry, guchar, 0);
281
282         if (egd) {
283                 const char *socket_path = g_getenv ("MONO_EGD_SOCKET");
284                 /* exception will be thrown in managed code */
285                 if (socket_path == NULL)
286                         return NULL; 
287                 get_entropy_from_server (socket_path, mono_array_addr (arry, guchar, 0), mono_array_length (arry));
288                 return (gpointer) -1;
289         } else {
290                 /* Read until the buffer is filled. This may block if using NAME_DEV_RANDOM. */
291                 gint count = 0;
292                 gint err;
293
294                 do {
295                         err = read (file, buf + count, len - count);
296                         if (err < 0) {
297                                 if (errno == EINTR)
298                                         continue;
299                                 break;
300                         }
301                         count += err;
302                 } while (count < len);
303
304                 if (err < 0) {
305                         g_warning("Entropy error! Error in read (%s).", strerror (errno));
306                         /* exception will be thrown in managed code */
307                         return NULL;
308                 }
309         }
310
311         /* We do not support PRNG seeding right now but the class library is this */
312
313         return handle;  
314 }
315
316 void
317 ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_RngClose (gpointer handle) 
318 {
319 }
320
321 #endif /* OS definition */