random: Add a RNG selection interface and system RNG wrapper.
authorWerner Koch <wk@gnupg.org>
Mon, 3 Dec 2012 19:41:28 +0000 (20:41 +0100)
committerWerner Koch <wk@gnupg.org>
Mon, 3 Dec 2012 19:47:38 +0000 (20:47 +0100)
* random/random-system.c: New.
* random/Makefile.am (librandom_la_SOURCES): Add new module.
* random/random.c (struct rng_types): New.
(_gcry_set_preferred_rng_type, _gcry_get_rng_type): New.
(_gcry_random_initialize, gcry_random_add_bytes, do_randomize)
(_gcry_set_random_seed_file, _gcry_update_random_seed_file)
(_gcry_fast_random_poll): Dispatch to the actual RNG.
* src/gcrypt.h.in (GCRYCTL_SET_PREFERRED_RNG_TYPE): New.
GCRYCTL_GET_CURRENT_RNG_TYPE): New.
(gcry_rng_types): New.
* src/global.c (print_config): Print the TNG type.
(global_init, _gcry_vcontrol): Implement the new control codes.
* doc/gcrypt.texi (Controlling the library): Document the new control
codes.

* tests/benchmark.c (main): Add options to test the RNG types.
* tests/random.c (main): Add new options.
(print_hex): Print to stderr.
(progress_cb, rng_type): New.
(check_rng_type_switching, check_early_rng_type_switching): New.
(run_all_rng_tests): New.
--

The purpose of this change is to allow applications with moderate
random requirements to use the system's RNG (e.g. /dev/urandom).  The
type switching logic makes sure that existing applications won't be
affected by this change.  A library is in almost all cases not able to
degrade the quality of the RNG.  The definition of "degrade" comes
from our own assertion of the quality/trustworthiness of the RNGs:

The most trustworthy RNG is the CSPRNG which dates back to the early
GnuPG days.  It is quite conservative and often requires more seeding
than might be justified.  GCRY_RNG_TYPE_STANDARD is the default unless
the process is in FIPS mode.

The second trustworthy RNG is the FIPS recommended X9.81 AES based
implementation.  It is seeded by the system's RNG.  GCRY_RNG_TYPE_FIPS
is the only available RNG if running in FIPS mode.

The third trustworthy RNG is a mere wrapper around the system's native
RNG.  Thus there is no extra step on top of what, for example,
/dev/random provides.  GCRY_RNG_TYPE_SYSTEM may be used by
applications which would use /dev/random or /dev/urandom instead.

NEWS
doc/gcrypt.texi
random/Makefile.am
random/rand-internal.h
random/random-system.c [new file with mode: 0644]
random/random.c
random/random.h
src/gcrypt.h.in
src/global.c
tests/benchmark.c
tests/random.c

diff --git a/NEWS b/NEWS
index a0fd09b..45b892f 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -9,7 +9,10 @@ Noteworthy changes in version 1.6.0 (unreleased)
  * The deprecated message digest debug macros have been removed.  Use
    gcry_md_debug instead.
 
- * Add support for the IDEA cipher algorithm.
+ * Added support for the IDEA cipher algorithm.
+
+ * Added a random number generator to directly use the system's RNG.
+   Also added an interface to prefer the use of a specified RNG.
 
  * Interface changes relative to the 1.5.0 release:
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -28,6 +31,11 @@ Noteworthy changes in version 1.6.0 (unreleased)
  gcry_md_start_debug    REMOVED (macro).
  gcry_md_stop_debug     REMOVED (macro).
  GCRYCTL_SET_ENFORCED_FIPS_FLAG  NEW.
+ GCRYCTL_SET_PREFERRED_RNG_TYPE  NEW.
+ GCRYCTL_GET_CURRENT_RNG_TYPE    NEW.
+ GCRY_RNG_TYPE_STANDARD          NEW.
+ GCRY_RNG_TYPE_FIPS              NEW.
+ GCRY_RNG_TYPE_SYSTEM            NEW.
 
 
 Noteworthy changes in version 1.5.0 (2011-06-29)
index 66a05d5..fa24def 100644 (file)
@@ -849,6 +849,37 @@ the library such as @code{gcry_check_version}. Note that Libgcrypt will
 reject an attempt to switch to the enforced fips mode during or after
 the intialization.
 
+@item GCRYCTL_SET_PREFERRED_RNG_TYPE; Arguments: int
+These are advisory commands to select a certain random number
+generator.  They are only advisory because libraries may not know what
+an application actually wants or vice versa.  Thus Libgcrypt employs a
+priority check to select the actually used RNG.  If an applications
+selects a lower priority RNG but a library requests a higher priority
+RNG Libgcrypt will switch to the higher priority RNG.  Applications
+and libaries should use these control codes before
+@code{gcry_check_version}.  The available generators are:
+@table @code
+@item GCRY_RNG_TYPE_STANDARD
+A conservative standard generator based on the ``Continuously Seeded
+Pseudo Random Number Generator'' designed by Peter Gutmann.
+@item GCRY_RNG_TYPE_FIPS
+A deterministic random number generator conforming to he document
+``NIST-Recommended Random Number Generator Based on ANSI X9.31
+Appendix A.2.4 Using the 3-Key Triple DES and AES Algorithms''
+(2005-01-31).  This implementation uses the AES variant.
+@item GCRY_RNG_TYPE_SYSTEM
+A wrapper around the system's native RNG.  On Unix system these are
+usually the /dev/random and /dev/urandom devices.
+@end table
+The default is @code{GCRY_RNG_TYPE_STANDARD} unless FIPS mode as been
+enabled; in which case @code{GCRY_RNG_TYPE_FIPS} is used and locked
+against further changes.
+
+@item GCRYCTL_GETT_CURRENT_RNG_TYPE; Arguments: int *
+This command stores the type of the currently used RNG as an integer
+value at the provided address.
+
+
 @item GCRYCTL_SELFTEST; Arguments: none
 This may be used at anytime to have the library run all implemented
 self-tests.  It works in standard and in FIPS mode.  Returns 0 on
index 603226d..c9d587a 100644 (file)
@@ -35,6 +35,7 @@ random.c random.h \
 rand-internal.h \
 random-csprng.c \
 random-fips.c \
+random-system.c \
 rndhw.c
 
 if USE_RANDOM_DAEMON
index a72d88c..f59a102 100644 (file)
@@ -87,6 +87,14 @@ gcry_err_code_t _gcry_rngfips_run_external_test (void *context,
 void _gcry_rngfips_deinit_external_test (void *context);
 
 
+/*-- random-system.c --*/
+void _gcry_rngsystem_initialize (int full);
+void _gcry_rngsystem_dump_stats (void);
+int  _gcry_rngsystem_is_faked (void);
+gcry_error_t _gcry_rngsystem_add_bytes (const void *buf, size_t buflen,
+                                        int quality);
+void _gcry_rngsystem_randomize (void *buffer, size_t length,
+                                enum gcry_random_level level);
 
 
 
diff --git a/random/random-system.c b/random/random-system.c
new file mode 100644 (file)
index 0000000..0ef9d24
--- /dev/null
@@ -0,0 +1,243 @@
+/* random-system.c - wrapper around the system's RNG
+ * Copyright (C) 2012  Free Software Foundation, Inc.
+ *
+ * This file is part of Libgcrypt.
+ *
+ * Libgcrypt is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * Libgcrypt is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+   This RNG is merely wrapper around the system's native RNG.  For
+   example on Unix systems it directly uses /dev/{u,}random.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <unistd.h>
+#ifdef HAVE_GETTIMEOFDAY
+#include <sys/time.h>
+#endif
+
+#include "g10lib.h"
+#include "random.h"
+#include "rand-internal.h"
+#include "ath.h"
+
+/* This is the lock we use to serialize access to this RNG.  The extra
+   integer variable is only used to check the locking state; that is,
+   it is not meant to be thread-safe but merely as a failsafe feature
+   to assert proper locking.  */
+static ath_mutex_t system_rng_lock;
+static int system_rng_is_locked;
+
+
+/* --- Local prototypes ---  */
+
+
+
+\f
+/* --- Functions  --- */
+
+/* Basic initialization is required to initialize mutexes and
+   do a few checks on the implementation.  */
+static void
+basic_initialization (void)
+{
+  static int initialized;
+  int my_errno;
+
+  if (initialized)
+    return;
+  initialized = 1;
+
+  my_errno = ath_mutex_init (&system_rng_lock);
+  if (my_errno)
+    log_fatal ("failed to create the System RNG lock: %s\n",
+               strerror (my_errno));
+  system_rng_is_locked = 0;
+
+  /* Make sure that we are still using the values we traditionally
+     used for the random levels.  */
+  gcry_assert (GCRY_WEAK_RANDOM == 0
+               && GCRY_STRONG_RANDOM == 1
+               && GCRY_VERY_STRONG_RANDOM == 2);
+
+}
+
+
+/* Acquire the system_rng_lock.  */
+static void
+lock_rng (void)
+{
+  int my_errno;
+
+  my_errno = ath_mutex_lock (&system_rng_lock);
+  if (my_errno)
+    log_fatal ("failed to acquire the System RNG lock: %s\n",
+               strerror (my_errno));
+  system_rng_is_locked = 1;
+}
+
+
+/* Release the system_rng_lock.  */
+static void
+unlock_rng (void)
+{
+  int my_errno;
+
+  system_rng_is_locked = 0;
+  my_errno = ath_mutex_unlock (&system_rng_lock);
+  if (my_errno)
+    log_fatal ("failed to release the System RNG lock: %s\n",
+               strerror (my_errno));
+}
+
+
+/* Helper variables for read_cb().
+
+   The _gcry_rnd*_gather_random interface does not allow to provide a
+   data pointer.  Thus we need to use a global variable for
+   communication.  However, the then required locking is anyway a good
+   idea because it does not make sense to have several readers of (say
+   /dev/random).  It is easier to serve them one after the other.  */
+static unsigned char *read_cb_buffer;   /* The buffer.  */
+static size_t         read_cb_size;     /* Size of the buffer.  */
+static size_t         read_cb_len;      /* Used length.  */
+
+
+/* Callback for _gcry_rnd*_gather_random.  */
+static void
+read_cb (const void *buffer, size_t length, enum random_origins origin)
+{
+  const unsigned char *p = buffer;
+
+  (void)origin;
+
+  gcry_assert (system_rng_is_locked);
+  gcry_assert (read_cb_buffer);
+
+  /* Note that we need to protect against gatherers returning more
+     than the requested bytes (e.g. rndw32).  */
+  while (length-- && read_cb_len < read_cb_size)
+    {
+      read_cb_buffer[read_cb_len++] = *p++;
+    }
+}
+
+
+/* Fill BUFFER with LENGTH bytes of random at quality LEVEL.  The
+   function either succeeds or terminates the process in case of a
+   fatal error. */
+static void
+get_random (void *buffer, size_t length, int level)
+{
+  int rc;
+
+  gcry_assert (buffer);
+
+  read_cb_buffer = buffer;
+  read_cb_size   = length;
+  read_cb_len    = 0;
+
+#if USE_RNDLINUX
+  rc = _gcry_rndlinux_gather_random (read_cb, 0, length, level);
+#elif USE_RNDUNIX
+  rc = _gcry_rndunix_gather_random (read_cb, 0, length, level);
+#elif USE_RNDW32
+  do
+    {
+      rc = _gcry_rndw32_gather_random (read_cb, 0, length, level);
+    }
+  while (rc >= 0 && read_cb_len < read_cb_size);
+#else
+  rc = -1;
+#endif
+
+  if (rc < 0 || read_cb_len != read_cb_size)
+    {
+      log_fatal ("error reading random from system RNG (rc=%d)\n", rc);
+    }
+}
+
+
+\f
+/* --- Public Functions --- */
+
+/* Initialize this random subsystem.  If FULL is false, this function
+   merely calls the basic initialization of the module and does not do
+   anything more.  Doing this is not really required but when running
+   in a threaded environment we might get a race condition
+   otherwise. */
+void
+_gcry_rngsystem_initialize (int full)
+{
+  basic_initialization ();
+  if (!full)
+    return;
+  /* Nothing more to initialize.  */
+  return;
+}
+
+
+/* Print some statistics about the RNG.  */
+void
+_gcry_rngsystem_dump_stats (void)
+{
+  /* Not yet implemented.  */
+}
+
+
+/* This function returns true if no real RNG is available or the
+   quality of the RNG has been degraded for test purposes.  */
+int
+_gcry_rngsystem_is_faked (void)
+{
+  return 0;  /* Faked random is not supported.  */
+}
+
+
+/* Add BUFLEN bytes from BUF to the internal random pool.  QUALITY
+   should be in the range of 0..100 to indicate the goodness of the
+   entropy added, or -1 for goodness not known. */
+gcry_error_t
+_gcry_rngsystem_add_bytes (const void *buf, size_t buflen, int quality)
+{
+  (void)buf;
+  (void)buflen;
+  (void)quality;
+  return 0;  /* Not implemented. */
+}
+
+
+/* Public function to fill the buffer with LENGTH bytes of
+   cryptographically strong random bytes.  Level GCRY_WEAK_RANDOM is
+   here mapped to GCRY_STRONG_RANDOM, GCRY_STRONG_RANDOM is strong
+   enough for most usage, GCRY_VERY_STRONG_RANDOM is good for key
+   generation stuff but may be very slow.  */
+void
+_gcry_rngsystem_randomize (void *buffer, size_t length,
+                           enum gcry_random_level level)
+{
+  _gcry_rngsystem_initialize (1);  /* Auto-initialize if needed.  */
+
+  if (level != GCRY_VERY_STRONG_RANDOM)
+    level = GCRY_STRONG_RANDOM;
+
+  lock_rng ();
+  get_random (buffer, length, level);
+  unlock_rng ();
+}
index 9a5b166..e56eb8a 100644 (file)
 static void (*progress_cb) (void *,const char*,int,int, int );
 static void *progress_cb_data;
 
+/* Flags indicating the requested RNG types.  */
+static struct
+{
+  int standard;
+  int fips;
+  int system;
+} rng_types;
+
+
 /* This is the lock we use to protect the buffer used by the nonce
    generation.  */
 static ath_mutex_t nonce_buffer_lock;
@@ -73,6 +82,55 @@ _gcry_random_progress (const char *what, int printchar, int current, int total)
 }
 
 
+/* Set the preferred RNG type.  This may be called at any time even
+   before gcry_check_version.  Thus we can't assume any thread system
+   initialization.  A type of 0 is used to indicate that any Libgcrypt
+   initialization has been done.*/
+void
+_gcry_set_preferred_rng_type (int type)
+{
+  static int any_init;
+
+  if (!type)
+    {
+      any_init = 1;
+    }
+  else if (type == GCRY_RNG_TYPE_STANDARD)
+    {
+      rng_types.standard = 1;
+    }
+  else if (any_init)
+    {
+      /* After any initialization has been done we only allow to
+         upgrade to the standard RNG (handled above).  All other
+         requests are ignored.  The idea is that the application needs
+         to declare a preference for a weaker RNG as soon as possible
+         and before any library sets a preference.  We assume that a
+         library which uses Libgcrypt calls an init function very
+         early.  This way --- even if the library gets initialized
+         early by the application --- it is unlikely that it can
+         select a lower priority RNG.
+
+         This scheme helps to ensure that existing unmodified
+         applications (e.g. gpg2), which don't known about the new RNG
+         selection system, will continue to use the standard RNG and
+         not be tricked by some library to use a lower priority RNG.
+         There are some loopholes here but at least most GnuPG stuff
+         should be save because it calls src_c{gcry_control
+         (GCRYCTL_SUSPEND_SECMEM_WARN);} quite early and thus inhibits
+         switching to a low priority RNG.
+       */
+    }
+  else if (type == GCRY_RNG_TYPE_FIPS)
+    {
+      rng_types.fips = 1;
+    }
+  else if (type == GCRY_RNG_TYPE_SYSTEM)
+    {
+      rng_types.system = 1;
+    }
+}
+
 
 /* Initialize this random subsystem.  If FULL is false, this function
    merely calls the basic initialization of the module and does not do
@@ -96,11 +154,39 @@ _gcry_random_initialize (int full)
 
   if (fips_mode ())
     _gcry_rngfips_initialize (full);
+  else if (rng_types.standard)
+    _gcry_rngcsprng_initialize (full);
+  else if (rng_types.fips)
+    _gcry_rngfips_initialize (full);
+  else if (rng_types.system)
+    _gcry_rngsystem_initialize (full);
   else
     _gcry_rngcsprng_initialize (full);
 }
 
 
+/* Return the current RNG type.  IGNORE_FIPS_MODE is a flag used to
+   skip the test for FIPS.  This is useful, so that we are able to
+   return the type of the RNG even before we have setup FIPS mode
+   (note that FIPS mode is enabled by default until it is switched off
+   by the initialization).  This is mostly useful for the regression
+   test.  */
+int
+_gcry_get_rng_type (int ignore_fips_mode)
+{
+  if (!ignore_fips_mode && fips_mode ())
+    return GCRY_RNG_TYPE_FIPS;
+  else if (rng_types.standard)
+    return GCRY_RNG_TYPE_STANDARD;
+  else if (rng_types.fips)
+    return GCRY_RNG_TYPE_FIPS;
+  else if (rng_types.system)
+    return GCRY_RNG_TYPE_SYSTEM;
+  else
+    return GCRY_RNG_TYPE_STANDARD;
+}
+
+
 void
 _gcry_random_dump_stats (void)
 {
@@ -178,7 +264,13 @@ gcry_random_add_bytes (const void *buf, size_t buflen, int quality)
 {
   if (fips_mode ())
     return 0; /* No need for this in fips mode.  */
-  else
+  else if (rng_types.standard)
+    return _gcry_rngcsprng_add_bytes (buf, buflen, quality);
+  else if (rng_types.fips)
+    return 0;
+  else if (rng_types.system)
+    return 0;
+  else /* default */
     return _gcry_rngcsprng_add_bytes (buf, buflen, quality);
 }
 
@@ -189,7 +281,13 @@ do_randomize (void *buffer, size_t length, enum gcry_random_level level)
 {
   if (fips_mode ())
     _gcry_rngfips_randomize (buffer, length, level);
-  else
+  else if (rng_types.standard)
+    _gcry_rngcsprng_randomize (buffer, length, level);
+  else if (rng_types.fips)
+    _gcry_rngfips_randomize (buffer, length, level);
+  else if (rng_types.system)
+    _gcry_rngsystem_randomize (buffer, length, level);
+  else /* default */
     _gcry_rngcsprng_randomize (buffer, length, level);
 }
 
@@ -244,7 +342,13 @@ _gcry_set_random_seed_file (const char *name)
 {
   if (fips_mode ())
     ; /* No need for this in fips mode.  */
-  else
+  else if (rng_types.standard)
+    _gcry_rngcsprng_set_seed_file (name);
+  else if (rng_types.fips)
+    ;
+  else if (rng_types.system)
+    ;
+  else /* default */
     _gcry_rngcsprng_set_seed_file (name);
 }
 
@@ -256,7 +360,13 @@ _gcry_update_random_seed_file (void)
 {
   if (fips_mode ())
     ; /* No need for this in fips mode.  */
-  else
+  else if (rng_types.standard)
+    _gcry_rngcsprng_update_seed_file ();
+  else if (rng_types.fips)
+    ;
+  else if (rng_types.system)
+    ;
+  else /* default */
     _gcry_rngcsprng_update_seed_file ();
 }
 
@@ -274,7 +384,13 @@ _gcry_fast_random_poll (void)
 {
   if (fips_mode ())
     ; /* No need for this in fips mode.  */
-  else
+  else if (rng_types.standard)
+    _gcry_rngcsprng_fast_poll ();
+  else if (rng_types.fips)
+    ;
+  else if (rng_types.system)
+    ;
+  else /* default */
     _gcry_rngcsprng_fast_poll ();
 }
 
index 5f6a42c..aae07ab 100644 (file)
@@ -26,7 +26,9 @@
 void _gcry_register_random_progress (void (*cb)(void *,const char*,int,int,int),
                                      void *cb_data );
 
+void _gcry_set_preferred_rng_type (int type);
 void _gcry_random_initialize (int full);
+int  _gcry_get_rng_type (int ignore_fips_mode);
 void _gcry_random_dump_stats(void);
 void _gcry_secure_random_alloc(void);
 void _gcry_enable_quick_random_gen (void);
index 4d34567..dae8d1c 100644 (file)
@@ -288,7 +288,9 @@ enum gcry_ctl_cmds
     GCRYCTL_SELFTEST = 57,
     /* Note: 58 .. 62 are used internally.  */
     GCRYCTL_DISABLE_HWF = 63,
-    GCRYCTL_SET_ENFORCED_FIPS_FLAG = 64
+    GCRYCTL_SET_ENFORCED_FIPS_FLAG = 64,
+    GCRYCTL_SET_PREFERRED_RNG_TYPE = 65,
+    GCRYCTL_GET_CURRENT_RNG_TYPE = 66
   };
 
 /* Perform various operations defined by CMD. */
@@ -1119,6 +1121,14 @@ gpg_error_t gcry_kdf_derive (const void *passphrase, size_t passphraselen,
  *                                  *
  ************************************/
 
+/* The type of the random number generator.  */
+enum gcry_rng_types
+  {
+    GCRY_RNG_TYPE_STANDARD   = 1, /* The default CSPRNG generator.  */
+    GCRY_RNG_TYPE_FIPS       = 2, /* The FIPS X9.31 AES generator.  */
+    GCRY_RNG_TYPE_SYSTEM     = 3  /* The system's native generator. */
+  };
+
 /* The possible values for the random quality.  The rule of thumb is
    to use STRONG for session keys and VERY_STRONG for key material.
    WEAK is usually an alias for STRONG and should not be used anymore
index 4ce869a..f280a7b 100644 (file)
@@ -1,6 +1,7 @@
 /* global.c  - global control functions
  * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003
- *               2004, 2005, 2006, 2008, 2011  Free Software Foundation, Inc.
+ *               2004, 2005, 2006, 2008, 2011,
+ *               2012  Free Software Foundation, Inc.
  *
  * This file is part of Libgcrypt.
  *
@@ -101,6 +102,9 @@ global_init (void)
     return;
   any_init_done = 1;
 
+  /* Tell the random module that we have seen an init call.  */
+  _gcry_set_preferred_rng_type (0);
+
   /* Initialize our portable thread/mutex wrapper.  */
   err = ath_init ();
   if (err)
@@ -308,6 +312,21 @@ print_config ( int (*fnc)(FILE *fp, const char *format, ...), FILE *fp)
   fnc (fp, "fips-mode:%c:%c:\n",
        fips_mode ()? 'y':'n',
        _gcry_enforced_fips_mode ()? 'y':'n' );
+  /* The currently used RNG type.  */
+  {
+    const char *s;
+
+    i = _gcry_get_rng_type (0);
+    switch (i)
+      {
+      case GCRY_RNG_TYPE_STANDARD: s = "standard"; break;
+      case GCRY_RNG_TYPE_FIPS:     s = "fips"; break;
+      case GCRY_RNG_TYPE_SYSTEM:   s = "system"; break;
+      default: BUG ();
+      }
+    fnc (fp, "rng-type:%s:%d:\n", s, i);
+  }
+
 }
 
 
@@ -328,6 +347,7 @@ _gcry_vcontrol (enum gcry_ctl_cmds cmd, va_list arg_ptr)
       break;
 
     case GCRYCTL_ENABLE_QUICK_RANDOM:
+      _gcry_set_preferred_rng_type (0);
       _gcry_enable_quick_random_gen ();
       break;
 
@@ -373,16 +393,19 @@ _gcry_vcontrol (enum gcry_ctl_cmds cmd, va_list arg_ptr)
       break;
 
     case GCRYCTL_DISABLE_SECMEM_WARN:
+      _gcry_set_preferred_rng_type (0);
       _gcry_secmem_set_flags ((_gcry_secmem_get_flags ()
                               | GCRY_SECMEM_FLAG_NO_WARNING));
       break;
 
     case GCRYCTL_SUSPEND_SECMEM_WARN:
+      _gcry_set_preferred_rng_type (0);
       _gcry_secmem_set_flags ((_gcry_secmem_get_flags ()
                               | GCRY_SECMEM_FLAG_SUSPEND_WARNING));
       break;
 
     case GCRYCTL_RESUME_SECMEM_WARN:
+      _gcry_set_preferred_rng_type (0);
       _gcry_secmem_set_flags ((_gcry_secmem_get_flags ()
                               & ~GCRY_SECMEM_FLAG_SUSPEND_WARNING));
       break;
@@ -393,15 +416,18 @@ _gcry_vcontrol (enum gcry_ctl_cmds cmd, va_list arg_ptr)
       break;
 
     case GCRYCTL_SET_RANDOM_SEED_FILE:
+      _gcry_set_preferred_rng_type (0);
       _gcry_set_random_seed_file (va_arg (arg_ptr, const char *));
       break;
 
     case GCRYCTL_UPDATE_RANDOM_SEED_FILE:
+      _gcry_set_preferred_rng_type (0);
       if ( fips_is_operational () )
         _gcry_update_random_seed_file ();
       break;
 
     case GCRYCTL_SET_VERBOSITY:
+      _gcry_set_preferred_rng_type (0);
       _gcry_set_log_verbosity (va_arg (arg_ptr, int));
       break;
 
@@ -447,12 +473,14 @@ _gcry_vcontrol (enum gcry_ctl_cmds cmd, va_list arg_ptr)
       break;
 
     case GCRYCTL_SET_THREAD_CBS:
+      _gcry_set_preferred_rng_type (0);
       err = ath_install (va_arg (arg_ptr, void *));
       if (!err)
        global_init ();
       break;
 
     case GCRYCTL_FAST_POLL:
+      _gcry_set_preferred_rng_type (0);
       /* We need to do make sure that the random pool is really
          initialized so that the poll function is not a NOP. */
       _gcry_random_initialize (1);
@@ -463,6 +491,7 @@ _gcry_vcontrol (enum gcry_ctl_cmds cmd, va_list arg_ptr)
 
     case GCRYCTL_SET_RNDEGD_SOCKET:
 #if USE_RNDEGD
+      _gcry_set_preferred_rng_type (0);
       err = _gcry_rndegd_set_socket_name (va_arg (arg_ptr, const char *));
 #else
       err = gpg_error (GPG_ERR_NOT_SUPPORTED);
@@ -470,12 +499,14 @@ _gcry_vcontrol (enum gcry_ctl_cmds cmd, va_list arg_ptr)
       break;
 
     case GCRYCTL_SET_RANDOM_DAEMON_SOCKET:
+      _gcry_set_preferred_rng_type (0);
       _gcry_set_random_daemon_socket (va_arg (arg_ptr, const char *));
       break;
 
     case GCRYCTL_USE_RANDOM_DAEMON:
       /* We need to do make sure that the random pool is really
          initialized so that the poll function is not a NOP. */
+      _gcry_set_preferred_rng_type (0);
       _gcry_random_initialize (1);
       _gcry_use_random_daemon (!! va_arg (arg_ptr, int));
       break;
@@ -487,6 +518,7 @@ _gcry_vcontrol (enum gcry_ctl_cmds cmd, va_list arg_ptr)
     case GCRYCTL_PRINT_CONFIG:
       {
         FILE *fp = va_arg (arg_ptr, FILE *);
+        _gcry_set_preferred_rng_type (0);
         print_config (fp?fprintf:_gcry_log_info_with_dummy_fp, fp);
       }
       break;
@@ -494,6 +526,7 @@ _gcry_vcontrol (enum gcry_ctl_cmds cmd, va_list arg_ptr)
     case GCRYCTL_OPERATIONAL_P:
       /* Returns true if the library is in an operational state.  This
          is always true for non-fips mode.  */
+      _gcry_set_preferred_rng_type (0);
       if (_gcry_fips_test_operational ())
         err = GPG_ERR_GENERAL; /* Used as TRUE value */
       break;
@@ -510,6 +543,7 @@ _gcry_vcontrol (enum gcry_ctl_cmds cmd, va_list arg_ptr)
          the library has already been initialized into fips mode, a
          selftest is triggered.  It is not possible to put the libraty
          into fips mode after having passed the initialization. */
+      _gcry_set_preferred_rng_type (0);
       if (!any_init_done)
         {
           /* Not yet intialized at all.  Set a flag so that we are put
@@ -602,14 +636,34 @@ _gcry_vcontrol (enum gcry_ctl_cmds cmd, va_list arg_ptr)
     case GCRYCTL_SET_ENFORCED_FIPS_FLAG:
       if (!any_init_done)
         {
-          /* Not yet intialized at all.  Set the enforced fips mode flag */
+          /* Not yet initialized at all.  Set the enforced fips mode flag */
+          _gcry_set_preferred_rng_type (0);
           _gcry_set_enforced_fips_mode ();
         }
       else
         err = GPG_ERR_GENERAL;
       break;
 
+    case GCRYCTL_SET_PREFERRED_RNG_TYPE:
+      /* This may be called before gcry_check_version.  */
+      {
+        int i = va_arg (arg_ptr, int);
+        /* Note that we may not pass 0 to _gcry_set_preferred_rng_type.  */
+        if (i > 0)
+          _gcry_set_preferred_rng_type (i);
+      }
+      break;
+
+    case GCRYCTL_GET_CURRENT_RNG_TYPE:
+      {
+        int *ip = va_arg (arg_ptr, int*);
+        if (ip)
+          *ip = _gcry_get_rng_type (!any_init_done);
+      }
+      break;
+
     default:
+      _gcry_set_preferred_rng_type (0);
       /* A call to make sure that the dummy code is linked in.  */
       _gcry_compat_identification ();
       err = GPG_ERR_INV_OP;
index 106e01b..61badd5 100644 (file)
@@ -1151,6 +1151,23 @@ main( int argc, char **argv )
           use_random_daemon = 1;
           argc--; argv++;
         }
+      else if (!strcmp (*argv, "--prefer-standard-rng"))
+        {
+          /* This is anyway the default, but we may want to use it for
+             debugging. */
+          gcry_control (GCRYCTL_SET_PREFERRED_RNG_TYPE, GCRY_RNG_TYPE_STANDARD);
+          argc--; argv++;
+        }
+      else if (!strcmp (*argv, "--prefer-fips-rng"))
+        {
+          gcry_control (GCRYCTL_SET_PREFERRED_RNG_TYPE, GCRY_RNG_TYPE_FIPS);
+          argc--; argv++;
+        }
+      else if (!strcmp (*argv, "--prefer-system-rng"))
+        {
+          gcry_control (GCRYCTL_SET_PREFERRED_RNG_TYPE, GCRY_RNG_TYPE_SYSTEM);
+          argc--; argv++;
+        }
       else if (!strcmp (*argv, "--no-blinding"))
         {
           no_blinding = 1;
index 3e25363..a46d754 100644 (file)
 
 #include "../src/gcrypt.h"
 
+#define PGM "random"
+
+
 static int verbose;
+static int debug;
+static int with_progress;
 
 static void
 die (const char *format, ...)
@@ -40,6 +45,7 @@ die (const char *format, ...)
   va_list arg_ptr;
 
   va_start (arg_ptr, format);
+  fputs ( PGM ": ", stderr);
   vfprintf (stderr, format, arg_ptr);
   va_end (arg_ptr);
   exit (1);
@@ -52,6 +58,7 @@ inf (const char *format, ...)
   va_list arg_ptr;
 
   va_start (arg_ptr, format);
+  fputs ( PGM ": ", stderr);
   vfprintf (stderr, format, arg_ptr);
   va_end (arg_ptr);
 }
@@ -62,13 +69,25 @@ print_hex (const char *text, const void *buf, size_t n)
 {
   const unsigned char *p = buf;
 
-  fputs (text, stdout);
+  inf ("%s", text);
   for (; n; n--, p++)
-    printf ("%02X", *p);
-  putchar ('\n');
+    fprintf (stderr, "%02X", *p);
+  putc ('\n', stderr);
+}
+
+
+static void
+progress_cb (void *cb_data, const char *what, int printchar,
+             int current, int total)
+{
+  (void)cb_data;
+
+  inf ("progress (%s %c %d %d)\n", what, printchar, current, total);
+  fflush (stderr);
 }
 
 
+
 static int
 writen (int fd, const void *buf, size_t nbytes)
 {
@@ -251,34 +270,270 @@ check_nonce_forking (void)
 }
 
 
+static int
+rng_type (void)
+{
+  int rngtype;
+  if (gcry_control (GCRYCTL_GET_CURRENT_RNG_TYPE, &rngtype))
+    die ("retrieving RNG type failed\n");
+  return rngtype;
+}
+
+
+static void
+check_rng_type_switching (void)
+{
+  int rngtype, initial;
+  char tmp1[4];
+
+  if (verbose)
+    inf ("checking whether RNG type switching works\n");
+
+  rngtype = rng_type ();
+  if (debug)
+    inf ("rng type: %d\n", rngtype);
+  initial = rngtype;
+  gcry_randomize (tmp1, sizeof tmp1, GCRY_STRONG_RANDOM);
+  if (debug)
+    print_hex ("  sample: ", tmp1, sizeof tmp1);
+  if (rngtype != rng_type ())
+    die ("RNG type unexpectedly changed\n");
+
+  gcry_control (GCRYCTL_SET_PREFERRED_RNG_TYPE, GCRY_RNG_TYPE_SYSTEM);
+
+  rngtype = rng_type ();
+  if (debug)
+    inf ("rng type: %d\n", rngtype);
+  if (rngtype != initial)
+    die ("switching to System RNG unexpectedly succeeded\n");
+  gcry_randomize (tmp1, sizeof tmp1, GCRY_STRONG_RANDOM);
+  if (debug)
+    print_hex ("  sample: ", tmp1, sizeof tmp1);
+  if (rngtype != rng_type ())
+    die ("RNG type unexpectedly changed\n");
+
+  gcry_control (GCRYCTL_SET_PREFERRED_RNG_TYPE, GCRY_RNG_TYPE_FIPS);
+
+  rngtype = rng_type ();
+  if (debug)
+    inf ("rng type: %d\n", rngtype);
+  if (rngtype != initial)
+    die ("switching to FIPS RNG unexpectedly succeeded\n");
+  gcry_randomize (tmp1, sizeof tmp1, GCRY_STRONG_RANDOM);
+  if (debug)
+    print_hex ("  sample: ", tmp1, sizeof tmp1);
+  if (rngtype != rng_type ())
+    die ("RNG type unexpectedly changed\n");
+
+  gcry_control (GCRYCTL_SET_PREFERRED_RNG_TYPE, GCRY_RNG_TYPE_STANDARD);
+
+  rngtype = rng_type ();
+  if (debug)
+    inf ("rng type: %d\n", rngtype);
+  if (rngtype != GCRY_RNG_TYPE_STANDARD)
+    die ("switching to standard RNG failed\n");
+  gcry_randomize (tmp1, sizeof tmp1, GCRY_STRONG_RANDOM);
+  if (debug)
+    print_hex ("  sample: ", tmp1, sizeof tmp1);
+  if (rngtype != rng_type ())
+    die ("RNG type unexpectedly changed\n");
+}
+
+
+static void
+check_early_rng_type_switching (void)
+{
+  int rngtype, initial;
+
+  if (verbose)
+    inf ("checking whether RNG type switching works in the early stage\n");
+
+  rngtype = rng_type ();
+  if (debug)
+    inf ("rng type: %d\n", rngtype);
+  initial = rngtype;
+
+  gcry_control (GCRYCTL_SET_PREFERRED_RNG_TYPE, GCRY_RNG_TYPE_SYSTEM);
+
+  rngtype = rng_type ();
+  if (debug)
+    inf ("rng type: %d\n", rngtype);
+  if (initial >= GCRY_RNG_TYPE_SYSTEM && rngtype != GCRY_RNG_TYPE_SYSTEM)
+    die ("switching to System RNG failed\n");
+
+  gcry_control (GCRYCTL_SET_PREFERRED_RNG_TYPE, GCRY_RNG_TYPE_FIPS);
+
+  rngtype = rng_type ();
+  if (debug)
+    inf ("rng type: %d\n", rngtype);
+  if (initial >= GCRY_RNG_TYPE_FIPS && rngtype != GCRY_RNG_TYPE_FIPS)
+    die ("switching to FIPS RNG failed\n");
+
+  gcry_control (GCRYCTL_SET_PREFERRED_RNG_TYPE, GCRY_RNG_TYPE_STANDARD);
+
+  rngtype = rng_type ();
+  if (debug)
+    inf ("rng type: %d\n", rngtype);
+  if (rngtype != GCRY_RNG_TYPE_STANDARD)
+    die ("switching to standard RNG failed\n");
+}
 
 
+/* Because we want to check initialization behaviour, we need to
+   fork/exec this program with several command line arguments.  We use
+   system, so that these tests work also on Windows.  */
+static void
+run_all_rng_tests (const char *program)
+{
+  static const char *options[] = {
+    "--early-rng-check",
+    "--early-rng-check --prefer-standard-rng",
+    "--early-rng-check --prefer-fips-rng",
+    "--early-rng-check --prefer-system-rng",
+    "--prefer-standard-rng",
+    "--prefer-fips-rng",
+    "--prefer-system-rng",
+    NULL
+  };
+  int idx;
+  size_t len, maxlen;
+  char *cmdline;
+
+  maxlen = 0;
+  for (idx=0; options[idx]; idx++)
+    {
+      len = strlen (options[idx]);
+      if (len > maxlen)
+        maxlen = len;
+    }
+  maxlen += strlen (program);
+  maxlen += strlen (" --in-recursion --verbose --debug --progress");
+  maxlen++;
+  cmdline = malloc (maxlen + 1);
+  if (!cmdline)
+    die ("out of core\n");
+
+  for (idx=0; options[idx]; idx++)
+    {
+      if (verbose)
+        inf ("now running with options '%s'\n", options[idx]);
+      strcpy (cmdline, program);
+      strcat (cmdline, " --in-recursion");
+      if (verbose)
+        strcat (cmdline, " --verbose");
+      if (debug)
+        strcat (cmdline, " --debug");
+      if (with_progress)
+        strcat (cmdline, " --progress");
+      strcat (cmdline, " ");
+      strcat (cmdline, options[idx]);
+      if (system (cmdline))
+        die ("running '%s' failed\n", cmdline);
+    }
 
+  free (cmdline);
+}
 
 int
 main (int argc, char **argv)
 {
-  int debug = 0;
+  int last_argc = -1;
+  int early_rng = 0;
+  int in_recursion = 0;
+  const char *program = NULL;
+
+  if (argc)
+    {
+      program = *argv;
+      argc--; argv++;
+    }
+  else
+    die ("argv[0] missing\n");
 
-  if ((argc > 1) && (! strcmp (argv[1], "--verbose")))
-    verbose = 1;
-  else if ((argc > 1) && (! strcmp (argv[1], "--debug")))
-    verbose = debug = 1;
+  while (argc && last_argc != argc )
+    {
+      last_argc = argc;
+      if (!strcmp (*argv, "--"))
+        {
+          argc--; argv++;
+          break;
+        }
+      else if (!strcmp (*argv, "--help"))
+        {
+          fputs ("usage: random [options]\n", stdout);
+          exit (0);
+        }
+      else if (!strcmp (*argv, "--verbose"))
+        {
+          verbose = 1;
+          argc--; argv++;
+        }
+      else if (!strcmp (*argv, "--debug"))
+        {
+          debug = verbose = 1;
+          argc--; argv++;
+        }
+      else if (!strcmp (*argv, "--progress"))
+        {
+          argc--; argv++;
+          with_progress = 1;
+        }
+      else if (!strcmp (*argv, "--in-recursion"))
+        {
+          in_recursion = 1;
+          argc--; argv++;
+        }
+      else if (!strcmp (*argv, "--early-rng-check"))
+        {
+          early_rng = 1;
+          argc--; argv++;
+        }
+      else if (!strcmp (*argv, "--prefer-standard-rng"))
+        {
+          /* This is anyway the default, but we may want to use it for
+             debugging. */
+          gcry_control (GCRYCTL_SET_PREFERRED_RNG_TYPE, GCRY_RNG_TYPE_STANDARD);
+          argc--; argv++;
+        }
+      else if (!strcmp (*argv, "--prefer-fips-rng"))
+        {
+          gcry_control (GCRYCTL_SET_PREFERRED_RNG_TYPE, GCRY_RNG_TYPE_FIPS);
+          argc--; argv++;
+        }
+      else if (!strcmp (*argv, "--prefer-system-rng"))
+        {
+          gcry_control (GCRYCTL_SET_PREFERRED_RNG_TYPE, GCRY_RNG_TYPE_SYSTEM);
+          argc--; argv++;
+        }
+    }
 
 #ifndef HAVE_W32_SYSTEM
   signal (SIGPIPE, SIG_IGN);
 #endif
 
+  if (early_rng)
+    check_early_rng_type_switching ();
+
   gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
   if (!gcry_check_version (GCRYPT_VERSION))
     die ("version mismatch\n");
 
+  if (with_progress)
+    gcry_set_progress_handler (progress_cb, NULL);
+
   gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
   if (debug)
     gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1u, 0);
 
-  check_forking ();
-  check_nonce_forking ();
+  if (!in_recursion)
+    {
+      check_forking ();
+      check_nonce_forking ();
+    }
+  check_rng_type_switching ();
+
+  if (!in_recursion)
+    run_all_rng_tests (program);
 
   return 0;
 }