]> mj.ucw.cz Git - libucw.git/commitdiff
Random: Implemented strong random source.
authorPavel Charvat <pchar@ucw.cz>
Tue, 22 Mar 2022 15:49:04 +0000 (15:49 +0000)
committerPavel Charvat <pchar@ucw.cz>
Tue, 22 Mar 2022 18:06:59 +0000 (18:06 +0000)
ucw/random-strong.c
ucw/random-test.c
ucw/random-test.t
ucw/random.h

index 3b19f5fdf5770e3d6c15d812328c9de9b1698e4f..f5728233eada8cb786fb2e7d3bee3b3033e88fe1 100644 (file)
 #include <ucw/threads.h>
 
 #include <errno.h>
+#include <fcntl.h>
 #include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
 
 #ifdef CONFIG_UCW_GETRANDOM
 #include <sys/random.h>
@@ -96,3 +99,188 @@ bool strongrand_getrandom_mem_try(void *buf UNUSED, size_t size UNUSED)
   return false;
 }
 #endif
+
+/*** Generic interface ***/
+
+void strongrand_close(struct strongrand *c)
+{
+  if (c && c->close)
+    c->close(c);
+}
+
+void strongrand_reset(struct strongrand *c)
+{
+  if (c->reset)
+    c->reset(c);
+}
+
+void strongrand_mem(struct strongrand *c, void *ptr, size_t size)
+{
+  ASSERT(c->read);
+  while (size)
+    {
+      int n = MIN(size, 0x7fffffff);
+      c->read(c, ptr, n);
+      size -= n;
+      ptr = (byte *)ptr + n;
+    }
+}
+
+/*** Kernel random source ***/
+
+struct strongrand_std {
+  struct strongrand sr;
+  int fd;              // Non-negative descriptor or special STRONGRAND_FD_x value
+  uint flags;          // STRONGRAND_STD_x
+  uint buf_size;       // Size of @buf or 0
+  uint buf_avail;      // Remaining buffered bytes, <= @buf_size
+  pid_t last_pid;      // Last known process id; maintained with STRONGRAND_STD_x_RESET
+  byte buf[];          // Buffered randomness
+};
+
+// Use getrandom()
+#define STRONGRAND_FD_KERNEL_GETRANDOM -1
+
+// Not yet open /dev/[u]random
+#define STRONGRAND_FD_KERNEL_DEVICE -2
+
+static void strongrand_std_close(struct strongrand *sr)
+{
+  struct strongrand_std *srs = (struct strongrand_std *)sr;
+  if (srs->fd >= 0)
+    close(srs->fd);
+  DBG("RANDOM[%s]: Closed", sr->name);
+  xfree(srs);
+}
+
+static void strongrand_std_reset(struct strongrand *sr)
+{
+  struct strongrand_std *srs = (struct strongrand_std *)sr;
+  srs->buf_avail = 0;
+}
+
+static void strongrand_std_open_device(struct strongrand_std *srs)
+{
+  ASSERT(srs->fd == STRONGRAND_FD_KERNEL_DEVICE);
+  ASSERT(srs->flags & (STRONGRAND_STD_URANDOM | STRONGRAND_STD_RANDOM));
+  const char *path = (srs->flags & STRONGRAND_STD_URANDOM) ? "/dev/urandom" : "/dev/random";
+  int fd = open(path, O_RDONLY);
+  if (fd < 0)
+    die("RANDOM[%s]: Cannot open %s: %m", srs->sr.name, path);
+  srs->fd = fd;
+  DBG("RANDOM[%s]: Opened device, path=%s, fd=%d", srs->sr.name, path, fd);
+}
+
+static void strongrand_std_read_unbuffered(struct strongrand *sr, byte *ptr, int size)
+{
+  struct strongrand_std *srs = (struct strongrand_std *)sr;
+#ifdef CONFIG_UCW_GETRANDOM
+  if (srs->fd == STRONGRAND_FD_KERNEL_GETRANDOM)
+    {
+      while (size)
+       {
+         int n = getrandom(ptr, size, (srs->flags & STRONGRAND_STD_RANDOM) ? GRND_RANDOM : 0);
+         if (n < 0)
+           {
+             if (errno == EINTR || errno == EAGAIN)
+               continue;
+             die("RANDOM[%s]: Failed to read %u bytes with getrandom(): %m", srs->sr.name, size);
+           }
+         DBG("RANDOM[%s]: Read %u bytes with getrandom()", srs->sr.name, n);
+         ASSERT(n <= size);
+         ptr += n;
+         size -= n;
+       }
+      return;
+    }
+#endif
+  if (srs->fd < 0)
+    {
+      ASSERT(srs->flags & STRONGRAND_STD_DELAYED);
+      strongrand_std_open_device(srs);
+    }
+  while (size)
+    {
+      int n = read(srs->fd, ptr, size);
+      if (n < 0)
+       {
+         if (errno == EINTR || errno == EAGAIN)
+           continue;
+         die("RANDOM[%s]: Failed to read %u bytes: %m", srs->sr.name, size);
+       }
+      ASSERT(n <= size);
+      DBG("RANDOM[%s]: Read %u bytes", srs->sr.name, n);
+      ptr += n;
+      size -= n;
+    }
+}
+
+static void strongrand_std_read_buffered(struct strongrand *sr, byte *ptr, int size)
+{
+  struct strongrand_std *srs = (struct strongrand_std *)sr;
+  if (srs->flags & (STRONGRAND_STD_AUTO_RESET | STRONGRAND_STD_ASSERT_RESET))
+    {
+      pid_t pid = getpid();
+      if (pid != srs->last_pid)
+       {
+         DBG("RANDOM[%s]: Detected changed pid from %u to %u", sr->name, (uint)srs->last_pid, (uint)pid);
+         srs->last_pid = pid;
+         if (srs->flags & STRONGRAND_STD_ASSERT_RESET)
+           ASSERT(!srs->buf_avail);
+         srs->buf_avail = 0;
+       }
+    }
+  while (size)
+    {
+      if (!srs->buf_avail)
+       {
+         DBG("RANDOM[%s]: Refilling buffer", srs->sr.name);
+         strongrand_std_read_unbuffered(sr, srs->buf, srs->buf_size);
+         srs->buf_avail = srs->buf_size;
+       }
+      uint n = MIN(srs->buf_avail, (uint)size);
+      memcpy(ptr, srs->buf + (srs->buf_size - srs->buf_avail), n);
+      srs->buf_avail -= n;
+      size -= n;
+      ptr += n;
+    }
+}
+
+static struct strongrand *strongrand_std_open_internal(int fd, const char *name, uint buf_size, uint flags)
+{
+  struct strongrand_std *srs = xmalloc(sizeof(*srs) + buf_size);
+  bzero(srs, sizeof(*srs));
+  srs->sr.name = name;
+  srs->sr.close = strongrand_std_close;
+  srs->sr.reset = buf_size ? strongrand_std_reset : NULL;
+  srs->sr.read = buf_size ? strongrand_std_read_buffered : strongrand_std_read_unbuffered;
+  srs->fd = fd;
+  srs->flags = flags;
+  srs->buf_size = buf_size;
+  if (flags & (STRONGRAND_STD_AUTO_RESET | STRONGRAND_STD_ASSERT_RESET))
+    srs->last_pid = getpid();
+  return &srs->sr;
+}
+
+struct strongrand *strongrand_std_open(uint buf_size, uint flags)
+{
+  const char *name;
+  if (flags & STRONGRAND_STD_URANDOM)
+    {
+      ASSERT(!(flags & STRONGRAND_STD_RANDOM));
+      name = "urandom";
+    }
+  else
+    {
+      ASSERT(flags & STRONGRAND_STD_RANDOM);
+      name = "random";
+    }
+#ifdef CONFIG_UCW_GETRANDOM
+  if (strongrand_getrandom_detect())
+    return strongrand_std_open_internal(STRONGRAND_FD_KERNEL_GETRANDOM, name, buf_size, flags);
+#endif
+  struct strongrand *sr = strongrand_std_open_internal(STRONGRAND_FD_KERNEL_DEVICE, name, buf_size, flags);
+  if (!(flags & STRONGRAND_STD_DELAYED))
+    strongrand_std_open_device((struct strongrand_std *)sr);
+  return sr;
+}
index 1e51aa74fb97ce7599b5e9cc915398ece02446cb..2fd375956b1c2e08b8008aa551d39e0d72a4d867 100644 (file)
@@ -20,6 +20,7 @@
 #include <unistd.h>
 
 static int o_test_fast;
+static int o_test_strong;
 
 static double o_bench_scale = 1.0;
 static int o_bench_only_safe;
@@ -27,6 +28,7 @@ static int o_bench_all;
 static int o_bench_libs;
 static int o_bench_legacy;
 static int o_bench_fast;
+static int o_bench_strong;
 
 static struct opt_section options = {
   OPT_ITEMS {
@@ -35,10 +37,12 @@ static struct opt_section options = {
     OPT_HELP("Options:"),
     OPT_HELP_OPTION,
     OPT_BOOL(0, "test-fast", o_test_fast, 0, "\tTest fast random generator"),
+    OPT_BOOL(0, "test-strong", o_test_strong, 0, "\tTest strong random generator"),
     OPT_BOOL(0, "bench-all", o_bench_all, 0, "\tBench everything"),
     OPT_BOOL(0, "bench-libs", o_bench_libs, 0, "\tBench libc"),
     OPT_BOOL(0, "bench-legacy", o_bench_legacy, 0, "\tBench legacy interface"),
     OPT_BOOL(0, "bench-fast", o_bench_fast, 0, "\tBench fast random generator"),
+    OPT_BOOL(0, "bench-strong", o_bench_strong, 0, "\tBench strong random generator"),
     OPT_DOUBLE(0, "bench-scale", o_bench_scale, OPT_REQUIRED_VALUE, "<scale>\tIncrease/decrease the length of benchmark (default: 1.0)"),
     OPT_BOOL(0, "bench-only-safe", o_bench_only_safe, 0, "\tDon't benchmark too verbose or risky functions"),
     OPT_END,
@@ -79,6 +83,23 @@ static void test_fast(void)
   printf("OK\n");
 }
 
+static void test_strong(void)
+{
+  byte buf[100];
+  for (uint j = 0; j < 2; j++)
+    {
+      struct strongrand *r = strongrand_std_open((j == 0) ? 0 : 128, STRONGRAND_STD_URANDOM);
+      for (uint i = 1; i <= 100; i++)
+       {
+         strongrand_mem(r, buf, i);
+         if (i % 10 == 0)
+           strongrand_reset(r);
+       }
+      strongrand_close(r);
+    }
+  printf("OK\n");
+}
+
 #define BENCH(func) BENCH_SLOW(1, func)
 #define BENCH_SLOW(mult, func) \
   do { \
@@ -189,6 +210,30 @@ static void bench_fast(void)
   fastrand_delete(r);
 }
 
+static void bench_strong(void)
+{
+  byte buf[100];
+  struct strongrand *r;
+
+  msg(L_INFO, "Benchmarking unbuffered strong random generator");
+  r = strongrand_std_open(0, STRONGRAND_STD_URANDOM);
+  BENCH_SLOW(50, strongrand_mem(r, buf, 1));
+  BENCH_SLOW(200, strongrand_mem(r, buf, 100));
+  strongrand_close(r);
+
+  msg(L_INFO, "Benchmarking buffered strong random generator");
+  r = strongrand_std_open(128, STRONGRAND_STD_URANDOM);
+  BENCH_SLOW(50, strongrand_mem(r, buf, 1));
+  BENCH_SLOW(200, strongrand_mem(r, buf, 100));
+  strongrand_close(r);
+
+  msg(L_INFO, "Benchmarking buffered strong random generator with autoreset");
+  r = strongrand_std_open(128, STRONGRAND_STD_URANDOM | STRONGRAND_STD_AUTO_RESET);
+  BENCH_SLOW(50, strongrand_mem(r, buf, 1));
+  BENCH_SLOW(200, strongrand_mem(r, buf, 100));
+  strongrand_close(r);
+}
+
 int main(int argc UNUSED, char **argv)
 {
   cf_def_file = INSTALL_CONFIG_DIR "/common";
@@ -196,6 +241,8 @@ int main(int argc UNUSED, char **argv)
 
   if (o_test_fast)
     test_fast();
+  if (o_test_strong)
+    test_strong();
 
   if (o_bench_all || o_bench_libs)
     bench_libs();
@@ -203,6 +250,8 @@ int main(int argc UNUSED, char **argv)
     bench_legacy();
   if (o_bench_all || o_bench_fast)
     bench_fast();
+  if (o_bench_all || o_bench_strong)
+    bench_strong();
 
   return 0;
 }
index 0f866cd0aaeebbfc85a9f50ff9933b00164f18cc..0027f60d804d08c8396de475c221b2f7337e2fa0 100644 (file)
@@ -1,3 +1,7 @@
 Name:  random-fast
 Run:   ../obj/ucw/random-test --test-fast
 Out:   OK
+
+Name:  random-strong
+Run:   ../obj/ucw/random-test --test-strong
+Out:   OK
index 1d3b8a1450f5bd855321a317534ec1b8e4019218..ec31b0dec292e9a71f453afa3635ff61cf269165 100644 (file)
 #define fastrand_max_u64 ucw_fastrand_max_u64
 #define fastrand_mem ucw_fastrand_mem
 #define fastrand_double ucw_fastrand_double
+#define strongrand_close ucw_strongrand_close
+#define strongrand_reset ucw_strongrand_reset
+#define strongrand_mem ucw_strongrand_mem
+#define strongrand_std_open ucw_strongrand_std_open
 #define strongrand_getrandom_mem_try ucw_strongrand_getrandom_mem_try
 #endif
 
@@ -104,7 +108,61 @@ double fastrand_double(struct fastrand *c);
 
 /* random-strong.c */
 
-/* FIXME: interface for strong randomness */
+/**
+ * Context structure for a strong random generator.
+ * Useful for things like generating random ids or cryptography.
+ * The library is generally thread-safe, but each context must be used by at most one thread at one time.
+ *
+ * Also be careful with fork(). Unless said otherwise, it's forbidden to use the context
+ * in a forked child, we can even assert changes of pid. See strongrand_reset() if you
+ * need that.
+ **/
+struct strongrand {
+  const char *name;                            /* Context name for logging. */
+  void (*read)(struct strongrand *sr, byte *ptr, int size); /* (mandatory) Generate @size random bytes. */
+  void (*close)(struct strongrand *sr);                /* Called from strongrand_close(). */
+  void (*reset)(struct strongrand *sr);                /* Called from strongrand_reset(). */
+};
+
+/** Close a previously opened context. **/
+void strongrand_close(struct strongrand *c);
+
+/**
+ * If you want to use the same context in multiple forked subprocesses, you must
+ * call this function after the fork() to flush any buffered randomness.
+ * Some future back-ends could use it for even more things, for example
+ * to re-connect somewhere.
+ **/
+void strongrand_reset(struct strongrand *sr);
+
+/** Generate @size random bytes. **/
+void strongrand_mem(struct strongrand *c, void *ptr, size_t size);
+
+/** Kernel's random generator. **/
+
+/**
+ * Open kernel's random generator.
+ * Requires exactly one of these flags, among others:
+ * -- STRONGRAND_STD_URANDOM
+ * -- STRONGRAND_STD_RANDOM
+ *
+ * @buf_size can be 0 for unbuffered reading.
+ *
+ * If at least one of the following conditions are met,
+ * strongrand_reset() after fork() is not necessary:
+ * -- zero @buf_size
+ * -- STRONGRAND_STD_AUTO_RESET flag
+ * -- no read since open (empty buffer)
+ **/
+struct strongrand *strongrand_std_open(uint buf_size, uint flags);
+
+enum strongrand_std_flags {
+  STRONGRAND_STD_URANDOM = 0x1,                // Use kernel's "urandom" source.
+  STRONGRAND_STD_RANDOM = 0x2,         // Use kernel's "random" source.
+  STRONGRAND_STD_DELAYED = 0x4,                // Delay non-trivial initializations like opening file descriptor to first read.
+  STRONGRAND_STD_AUTO_RESET = 0x8,     // Automatically do strongrand_reset() after changes of getpid(); adds overhead to each read.
+  STRONGRAND_STD_ASSERT_RESET = 0x10,  // Assert correct usage of strongrand_reset() after changes of getpid(); for debugging.
+};
 
 // Internals
 bool strongrand_getrandom_mem_try(void *buf, size_t size);