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