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