Use new atomis with allocated_heap.
[mono.git] / mono / metadata / rand.c
index 40de928939071e5f3dca7cdc5c3340522fd66aef..97b264b6a31bdd7d17025754557a160bc7d6e630 100644 (file)
 /*
  * rand.c: System.Security.Cryptography.RNGCryptoServiceProvider support
  *
- * Author:
+ * Authors:
  *      Mark Crichton (crichton@gimp.org)
+ *      Patrik Torstensson (p@rxc.se)
+ *     Sebastien Pouliot (sebastien@ximian.com)
  *
- * (C) 2001 Ximian, Inc.
- *
+ * Copyright 2001-2003 Ximian, Inc (http://www.ximian.com)
+ * Copyright 2004-2009 Novell, Inc (http://www.novell.com)
  */
 
-
-/* Ok, the exception handling is bogus.  I need to work on that */
-
 #include <config.h>
 #include <glib.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
+#ifdef HAVE_UNISTD_H
 #include <unistd.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
 
 #include <mono/metadata/object.h>
 #include <mono/metadata/rand.h>
 #include <mono/metadata/exception.h>
 
-#if defined (NAME_DEV_RANDOM) && defined (HAVE_CRYPT_RNG)
-
-#ifndef NAME_DEV_URANDOM
-#define NAME_DEV_URANDOM "/dev/urandom"
-#endif
+#if !defined(__native_client__) && !defined(HOST_WIN32)
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <errno.h>
 
-void 
-ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_InternalGetBytes (MonoObject *self, MonoArray *arry)
+static void
+get_entropy_from_server (const char *path, guchar *buf, int len)
 {
-    guint32 len;
-    gint file;
-    gint err;
-    gint count;
-    guchar *buf;
+    int file;
+    gint ret;
+    guint offset = 0;
+    struct sockaddr_un egd_addr;
 
-    len = mono_array_length(arry);
-    buf = mono_array_addr(arry, guchar, 0);
+    file = socket (PF_UNIX, SOCK_STREAM, 0);
+    if (file < 0)
+        ret = -1;
+    else {
+        egd_addr.sun_family = AF_UNIX;
+        strncpy (egd_addr.sun_path, path, MONO_SIZEOF_SUNPATH - 1);
+        egd_addr.sun_path [MONO_SIZEOF_SUNPATH-1] = '\0';
+        ret = connect (file, (struct sockaddr *)&egd_addr, sizeof(egd_addr));
+    }
+    if (ret == -1) {
+        if (file >= 0)
+            close (file);
+       g_warning ("Entropy problem! Can't create or connect to egd socket %s", path);
+        mono_raise_exception (mono_get_exception_execution_engine ("Failed to open egd socket"));
+    }
 
-    file = open (NAME_DEV_URANDOM, O_RDONLY);
+    while (len > 0) {
+        guchar request[2];
+        gint count = 0;
 
-    if (file < 0)
-           file = open (NAME_DEV_RANDOM, O_RDONLY);
+        request [0] = 2; /* block until daemon can return enough entropy */
+        request [1] = len < 255 ? len : 255;
+        while (count < 2) {
+            int sent = write (file, request + count, 2 - count);
+            if (sent >= 0)
+                count += sent;
+            else if (errno == EINTR)
+                continue;
+            else {
+                close (file);
+                g_warning ("Send egd request failed %d", errno);
+                mono_raise_exception (mono_get_exception_execution_engine ("Failed to send request to egd socket"));
+            }
+        }
 
-    if (file < 0) {
-       g_warning ("Entropy problem! Can't open %s or %s", NAME_DEV_URANDOM, NAME_DEV_RANDOM);
+        count = 0;
+        while (count != request [1]) {
+            int received;
+            received = read(file, buf + offset, request [1] - count);
+            if (received > 0) {
+                count += received;
+                offset += received;
+            } else if (received < 0 && errno == EINTR) {
+                continue;
+            } else {
+                close (file);
+                g_warning ("Receive egd request failed %d", errno);
+                mono_raise_exception (mono_get_exception_execution_engine ("Failed to get response from egd socket"));
+            }
+        }
 
-       /* This needs to be a crypto exception */
-       mono_raise_exception(mono_get_exception_not_implemented());
+        len -= request [1];
     }
 
-    /* Read until the buffer is filled. This may block if using NAME_DEV_RANDOM. */
-    count = 0;
-    do {
-           err = read(file, buf + count, len - count);
-           count += err;
-    } while (err >= 0 && count < len);
+    close (file);
+}
+#endif
 
-    if (err < 0) {
-        g_warning("Entropy error! Error in read.");
-        mono_raise_exception(mono_get_exception_not_implemented());
-    }
+#if defined (HOST_WIN32)
+
+#include <windows.h>
+#include <wincrypt.h>
+
+#ifndef PROV_INTEL_SEC
+#define PROV_INTEL_SEC         22
+#endif
+#ifndef CRYPT_VERIFY_CONTEXT
+#define CRYPT_VERIFY_CONTEXT   0xF0000000
+#endif
 
-    close(file);
+MonoBoolean
+ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_RngOpen (void)
+{
+       /* FALSE == Local (instance) handle for randomness */
+       return FALSE;
 }
 
-void 
-ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_InternalGetNonZeroBytes (MonoObject *self, MonoArray *arry)
+gpointer
+ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_RngInitialize (MonoArray *seed)
 {
-    guint32 len;
-    gint file, i;
-    gint err;
-    guchar byte = 0;
+       HCRYPTPROV provider = 0;
 
-    len = mono_array_length(arry);
+       /* There is no need to create a container for just random data,
+          so we can use CRYPT_VERIFY_CONTEXT (one call) see: 
+          http://blogs.msdn.com/dangriff/archive/2003/11/19/51709.aspx */
 
-    file = open(NAME_DEV_RANDOM, O_RDONLY);
+       /* We first try to use the Intel PIII RNG if drivers are present */
+       if (!CryptAcquireContext (&provider, NULL, NULL, PROV_INTEL_SEC, CRYPT_VERIFY_CONTEXT)) {
+               /* not a PIII or no drivers available, use default RSA CSP */
+               if (!CryptAcquireContext (&provider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFY_CONTEXT)) {
+                       provider = 0;
+                       /* exception will be thrown in managed code */
+               }
+       }
 
-    if (file < 0) {
-        g_warning("Entropy problem! Can't open %s", NAME_DEV_RANDOM);
+       /* seed the CSP with the supplied buffer (if present) */
+       if ((provider != 0) && (seed)) {
+               guint32 len = mono_array_length (seed);
+               guchar *buf = mono_array_addr (seed, guchar, 0);
+               /* the call we replace the seed with random - this isn't what is
+                  expected from the class library user */
+               guchar *data = g_malloc (len);
+               if (data) {
+                       memcpy (data, buf, len);
+                       /* add seeding material to the RNG */
+                       CryptGenRandom (provider, len, data);
+                       /* zeroize and free */
+                       memset (data, 0, len);
+                       g_free (data);
+               }
+       }
 
-        /* This needs to be a crypto exception */
-        mono_raise_exception(mono_get_exception_not_implemented());
-    }
+       return (gpointer) provider;     
+}
 
-    for (i = 0; i < len; i++) {
+gpointer
+ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_RngGetBytes (gpointer handle, MonoArray *arry)
+{
+       HCRYPTPROV provider = (HCRYPTPROV) handle;
+       guint32 len = mono_array_length (arry);
+       guchar *buf = mono_array_addr (arry, guchar, 0);
 
-        do {
-            err = read(file, &byte, 1);
-        } while (byte == 0);
+       if (!CryptGenRandom (provider, len, buf)) {
+               CryptReleaseContext (provider, 0);
+               /* we may have lost our context with CryptoAPI, but all hope isn't lost yet! */
+               provider = (HCRYPTPROV) ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_RngInitialize (NULL);
+               if (!CryptGenRandom (provider, len, buf)) {
+                       CryptReleaseContext (provider, 0);
+                       provider = 0;
+                       /* exception will be thrown in managed code */
+               }
+       }
+       return (gpointer) provider;
+}
 
-        if (err < 0) {
-            g_warning("Entropy error! Error in read.");
-            mono_raise_exception(mono_get_exception_not_implemented());
-        }
+void
+ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_RngClose (gpointer handle) 
+{
+       CryptReleaseContext ((HCRYPTPROV) handle, 0);
+}
 
-        mono_array_set(arry, guchar, i, byte);
-    }
+#elif defined (__native_client__)
 
-    close(file);
+MonoBoolean
+ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_RngOpen (void)
+{
+       srand (time (NULL));
+       return TRUE;
 }
 
-/* This needs to change when I do the Win32 support... */
-#else
-void ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_InternalGetBytes(MonoObject *self, MonoArray *arry)
+gpointer
+ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_RngInitialize (MonoArray *seed)
 {
-    mono_raise_exception(mono_get_exception_not_implemented());
+       return -1;
 }
 
-void ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_InternalGetNonZeroBytes(MonoObject *self, MonoArray *arry)
+gpointer 
+ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_RngGetBytes (gpointer handle, MonoArray *arry)
+{      
+       guint32 len = mono_array_length (arry);
+       guchar *buf = mono_array_addr (arry, guchar, 0);
+
+       /* Read until the buffer is filled. This may block if using NAME_DEV_RANDOM. */
+       gint count = 0;
+       gint err;
+
+       do {
+               if (len - count >= sizeof (long))
+               {
+                       *(long*)buf = rand();
+                       count += sizeof (long);
+               }
+               else if (len - count >= sizeof (short))
+               {
+                       *(short*)buf = rand();
+                       count += sizeof (short);
+               }
+               else if (len - count >= sizeof (char))
+               {
+                       *buf = rand();
+                       count += sizeof (char);
+               }
+       } while (count < len);
+
+       return (gpointer)-1L;
+}
+
+void
+ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_RngClose (gpointer handle) 
 {
-    mono_raise_exception(mono_get_exception_not_implemented());
 }
+#else
 
+#ifndef NAME_DEV_URANDOM
+#define NAME_DEV_URANDOM "/dev/urandom"
 #endif
+
+static gboolean egd = FALSE;
+static gint file = -1;
+
+MonoBoolean
+ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_RngOpen (void)
+{
+       if (egd || (file >= 0))
+               return TRUE;
+
+#if defined (NAME_DEV_URANDOM)
+       file = open (NAME_DEV_URANDOM, O_RDONLY);
+#endif
+
+#if defined (NAME_DEV_RANDOM)
+       if (file < 0)
+               file = open (NAME_DEV_RANDOM, O_RDONLY);
+#endif
+
+       if (file < 0) {
+               const char *socket_path = g_getenv("MONO_EGD_SOCKET");
+               egd = (socket_path != NULL);
+       }
+
+       /* TRUE == Global handle for randomness */
+       return TRUE;
+}
+
+gpointer
+ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_RngInitialize (MonoArray *seed)
+{
+       /* if required exception will be thrown in managed code */
+       return ((!egd && (file < 0)) ? NULL : GINT_TO_POINTER (file));
+}
+
+gpointer 
+ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_RngGetBytes (gpointer handle, MonoArray *arry)
+{
+       gint file = GPOINTER_TO_INT (handle);
+       guint32 len = mono_array_length (arry);
+       guchar *buf = mono_array_addr (arry, guchar, 0);
+
+       if (egd) {
+               const char *socket_path = g_getenv ("MONO_EGD_SOCKET");
+               /* exception will be thrown in managed code */
+               if (socket_path == NULL)
+                       return NULL; 
+               get_entropy_from_server (socket_path, mono_array_addr (arry, guchar, 0), mono_array_length (arry));
+               return (gpointer) -1;
+       } else {
+               /* Read until the buffer is filled. This may block if using NAME_DEV_RANDOM. */
+               gint count = 0;
+               gint err;
+
+               do {
+                       err = read (file, buf + count, len - count);
+                       if (err < 0) {
+                               if (errno == EINTR)
+                                       continue;
+                               break;
+                       }
+                       count += err;
+               } while (count < len);
+
+               if (err < 0) {
+                       g_warning("Entropy error! Error in read (%s).", strerror (errno));
+                       /* exception will be thrown in managed code */
+                       return NULL;
+               }
+       }
+
+       /* We do not support PRNG seeding right now but the class library is this */
+
+       return handle;  
+}
+
+void
+ves_icall_System_Security_Cryptography_RNGCryptoServiceProvider_RngClose (gpointer handle) 
+{
+}
+
+#endif /* OS definition */