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