summaryrefslogtreecommitdiff
path: root/src/random/random.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/random/random.c')
-rw-r--r--src/random/random.c423
1 files changed, 423 insertions, 0 deletions
diff --git a/src/random/random.c b/src/random/random.c
new file mode 100644
index 0000000..d5c45f5
--- /dev/null
+++ b/src/random/random.c
@@ -0,0 +1,423 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif /* not HAVE_CONFIG_H */
+
+#include <string.h>
+#include <nettle/yarrow.h>
+#include <threads.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <webid-oidc/random.h>
+#include <gettext.h>
+#define _(s) dgettext (PACKAGE, s)
+
+struct webid_oidc_random_context
+{
+ struct yarrow256_ctx ctx;
+ enum webid_oidc_random_context_spawn_flags flags;
+ enum webid_oidc_random_context_state state;
+ unsigned int state_progress;
+ uint8_t seed[YARROW256_SEED_FILE_SIZE];
+ mtx_t mutex;
+};
+
+struct webid_oidc_random_context *
+webid_oidc_random_context_alloc (void)
+{
+ struct webid_oidc_random_context *ret =
+ malloc (sizeof (struct webid_oidc_random_context));
+ if (ret && mtx_init (&(ret->mutex), mtx_plain) != thrd_success)
+ {
+ free (ret);
+ ret = NULL;
+ }
+ if (ret)
+ {
+ yarrow256_init (&(ret->ctx), 0, NULL);
+ ret->flags = 0;
+ ret->state = WEBID_OIDC_RANDOM_CONTEXT_WAIT_LOCK_FILE;
+ }
+ return ret;
+}
+
+void
+webid_oidc_random_context_free (struct webid_oidc_random_context *ctx)
+{
+ if (ctx)
+ {
+ mtx_destroy (&(ctx->mutex));
+ }
+ free (ctx);
+}
+
+enum webid_oidc_random_context_state
+webid_oidc_random_context_get_state (const struct webid_oidc_random_context
+ *ctx)
+{
+ return ctx->state;
+}
+
+int
+webid_oidc_random_context_get_wait_bytes (const struct
+ webid_oidc_random_context *ctx,
+ size_t max, size_t start,
+ uint8_t * bytes, size_t *n_bytes)
+{
+ if (ctx->state == WEBID_OIDC_RANDOM_CONTEXT_WAIT_FILE_WRITE)
+ {
+ if (start >= YARROW256_SEED_FILE_SIZE)
+ {
+ *n_bytes = 0;
+ }
+ else
+ {
+ if (max + start > YARROW256_SEED_FILE_SIZE)
+ {
+ max = YARROW256_SEED_FILE_SIZE - start;
+ }
+ memcpy (bytes, ctx->seed + start, max);
+ *n_bytes = YARROW256_SEED_FILE_SIZE - start;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+int
+webid_oidc_random_context_ok (struct webid_oidc_random_context *ctx)
+{
+ switch (ctx->state)
+ {
+ case WEBID_OIDC_RANDOM_CONTEXT_WAIT_LOCK_FILE:
+ ctx->state = WEBID_OIDC_RANDOM_CONTEXT_WAIT_FILE_READ;
+ ctx->state_progress = 0;
+ break;
+ case WEBID_OIDC_RANDOM_CONTEXT_WAIT_FILE_WRITE:
+ ctx->state = WEBID_OIDC_RANDOM_CONTEXT_WAIT_UNLOCK_FILE;
+ break;
+ case WEBID_OIDC_RANDOM_CONTEXT_WAIT_UNLOCK_FILE:
+ ctx->state = WEBID_OIDC_RANDOM_CONTEXT_READY;
+ break;
+ default:
+ return 0;
+ }
+ return 1;
+}
+
+int
+webid_oidc_random_context_bytes (struct webid_oidc_random_context *ctx,
+ size_t n_bytes, const uint8_t * bytes)
+{
+ switch (ctx->state)
+ {
+ case WEBID_OIDC_RANDOM_CONTEXT_WAIT_FILE_READ:
+ if (n_bytes >= (YARROW256_SEED_FILE_SIZE - ctx->state_progress))
+ {
+ n_bytes = (YARROW256_SEED_FILE_SIZE - ctx->state_progress);
+ }
+ memcpy (ctx->seed + ctx->state_progress, bytes, n_bytes);
+ ctx->state_progress += n_bytes;
+ if (ctx->state_progress == YARROW256_SEED_FILE_SIZE)
+ {
+ yarrow256_seed (&(ctx->ctx), YARROW256_SEED_FILE_SIZE, ctx->seed);
+ yarrow256_random (&(ctx->ctx), YARROW256_SEED_FILE_SIZE, ctx->seed);
+ ctx->state = WEBID_OIDC_RANDOM_CONTEXT_WAIT_FILE_WRITE;
+ }
+ break;
+ default:
+ return 0;
+ }
+ return 1;
+}
+
+int
+webid_oidc_random_context_spawn (struct webid_oidc_random_context *parent,
+ struct webid_oidc_random_context *child,
+ enum webid_oidc_random_context_spawn_flags
+ flags)
+{
+ if (!webid_oidc_random_context_get
+ (parent, YARROW256_SEED_FILE_SIZE, child->seed))
+ {
+ return 0;
+ }
+ child->flags = flags;
+ child->state = WEBID_OIDC_RANDOM_CONTEXT_READY;
+ yarrow256_seed (&(child->ctx), YARROW256_SEED_FILE_SIZE, child->seed);
+ return 1;
+}
+
+int
+webid_oidc_random_context_get (struct webid_oidc_random_context *ctx,
+ size_t request_size, uint8_t * data)
+{
+ if (ctx->state == WEBID_OIDC_RANDOM_CONTEXT_READY)
+ {
+ if (!(ctx->flags & WEBID_OIDC_RANDOM_CONTEXT_THREAD_UNSAFE))
+ {
+ if (mtx_lock (&(ctx->mutex)) != thrd_success)
+ {
+ assert (0);
+ }
+ }
+ yarrow256_random (&(ctx->ctx), request_size, data);
+ if (!(ctx->flags & WEBID_OIDC_RANDOM_CONTEXT_THREAD_UNSAFE))
+ {
+ if (mtx_unlock (&(ctx->mutex)) != thrd_success)
+ {
+ assert (0);
+ }
+ }
+ }
+ return (ctx->state == WEBID_OIDC_RANDOM_CONTEXT_READY);
+}
+
+static struct webid_oidc_random_context *global_ctx = NULL;
+
+void
+webid_oidc_random_init (void)
+{
+ char *cache_dir, *pkg_cache_dir, *filename;
+ char *home = getenv ("HOME");
+ char *xdg_cache_home = getenv ("XDG_CACHE_HOME");
+ char *application = getenv ("WEBID_OIDC_APPLICATION_NAME");
+ static const char *default_application = PACKAGE;
+ FILE *seed_file;
+ FILE *system_rng_file;
+ struct flock lock;
+ if (global_ctx)
+ {
+ webid_oidc_random_context_free (global_ctx);
+ global_ctx = NULL;
+ }
+ global_ctx = webid_oidc_random_context_alloc ();
+ if (!global_ctx)
+ {
+ fprintf (stderr, _("Could not set the global random generator up.\n"));
+ abort ();
+ }
+ if (!application)
+ {
+ application = (char *) default_application;
+ }
+ if (xdg_cache_home)
+ {
+ cache_dir = malloc (strlen (xdg_cache_home) + 1);
+ if (!cache_dir)
+ {
+ fprintf (stderr,
+ _
+ ("Could not set the global random generator up: out of memory.\n"));
+ abort ();
+ }
+ strcpy (cache_dir, xdg_cache_home);
+ }
+ else if (home)
+ {
+ cache_dir = malloc (strlen (home) + strlen ("/.cache") + 1);
+ if (!cache_dir)
+ {
+ fprintf (stderr,
+ _
+ ("Could not set the global random generator up: out of memory.\n"));
+ abort ();
+ }
+ strcpy (cache_dir, home);
+ strcat (cache_dir, "/.cache");
+ }
+ else
+ {
+ cache_dir = malloc (strlen ("/var/cache") + 1);
+ if (!cache_dir)
+ {
+ fprintf (stderr,
+ _
+ ("Could not set the global random generator up: out of memory.\n"));
+ abort ();
+ }
+ strcpy (cache_dir, "/var/cache");
+ }
+ pkg_cache_dir = malloc (strlen (cache_dir)
+ + strlen ("/") + strlen (application) + 1);
+ if (!pkg_cache_dir)
+ {
+ fprintf (stderr,
+ _
+ ("Could not set the global random generator up: out of memory.\n"));
+ abort ();
+ }
+ strcpy (pkg_cache_dir, cache_dir);
+ strcat (pkg_cache_dir, "/");
+ strcat (pkg_cache_dir, application);
+ filename = malloc (strlen (pkg_cache_dir) + strlen ("/seed") + 1);
+ if (!filename)
+ {
+ fprintf (stderr,
+ _
+ ("Could not set the global random generator up: out of memory.\n"));
+ abort ();
+ }
+ strcpy (filename, pkg_cache_dir);
+ strcat (filename, "/seed");
+ seed_file = fopen (filename, "r+");
+ if (!seed_file)
+ {
+ fprintf (stderr,
+ _
+ ("Warning: could not open the seed file, maybe the parent directory is missing...\n"));
+ if (mkdir (cache_dir, 0777) != 0)
+ {
+ fprintf (stderr,
+ _("Warning: could not create the cache directory '%s'.\n"),
+ cache_dir);
+ perror (_("when creating the cache directory"));
+ }
+ if (mkdir (pkg_cache_dir, 0777) != 0)
+ {
+ fprintf (stderr,
+ _
+ ("Warning: could not create the package cache directory '%s'.\n"),
+ pkg_cache_dir);
+ perror (_("when creating the package cache directory"));
+ }
+ seed_file = fopen (filename, "w+");
+ }
+ if (!seed_file)
+ {
+ fprintf (stderr, _("Could not open the seed file '%s'.\n"), filename);
+ perror (_("when opening the seed file"));
+ abort ();
+ }
+ system_rng_file = fopen ("/dev/random", "r");
+ while (global_ctx->state != WEBID_OIDC_RANDOM_CONTEXT_READY)
+ {
+ int error;
+ int c;
+ uint8_t byte = 0;
+ size_t i, n;
+ switch (global_ctx->state)
+ {
+ case WEBID_OIDC_RANDOM_CONTEXT_WAIT_LOCK_FILE:
+ lock.l_type = F_WRLCK;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = 0;
+ lock.l_len = 0;
+ error = fcntl (fileno (seed_file), F_SETLKW, &lock);
+ if (error != 0)
+ {
+ fprintf (stderr, _("Could not lock the seed file '%s'.\n"),
+ filename);
+ perror (_("when locking the seed file"));
+ abort ();
+ }
+ if (!webid_oidc_random_context_ok (global_ctx))
+ {
+ assert (0);
+ }
+ break;
+ case WEBID_OIDC_RANDOM_CONTEXT_WAIT_FILE_READ:
+ c = fgetc (seed_file);
+ if (c == EOF && system_rng_file == NULL)
+ {
+ fprintf (stderr,
+ _
+ ("Warning: the seed file '%s' is too short. This weakens the random number generator. Please write more random data in it.\n"),
+ filename);
+ }
+ else if (c == EOF)
+ {
+ c = fgetc (system_rng_file);
+ }
+ byte = c;
+ if (!webid_oidc_random_context_bytes (global_ctx, 1, &byte))
+ {
+ assert (0);
+ }
+ break;
+ case WEBID_OIDC_RANDOM_CONTEXT_WAIT_FILE_WRITE:
+ if (fseek (seed_file, 0, SEEK_SET) != 0)
+ {
+ fprintf (stderr,
+ _("Could not update the seed file '%s'.\n"), filename);
+ perror (_("when rewinding the seed file"));
+ abort ();
+ }
+ if (!webid_oidc_random_context_get_wait_bytes
+ (global_ctx, 0, 0, NULL, &n))
+ {
+ assert (0);
+ }
+ for (i = 0; i < n; i++)
+ {
+ size_t remaining;
+ if (!webid_oidc_random_context_get_wait_bytes
+ (global_ctx, 1, i, &byte, &remaining))
+ {
+ assert (0);
+ }
+ assert (i + remaining == n);
+ fputc (byte, seed_file);
+ }
+ if (!webid_oidc_random_context_ok (global_ctx))
+ {
+ assert (0);
+ }
+ break;
+ case WEBID_OIDC_RANDOM_CONTEXT_WAIT_UNLOCK_FILE:
+ lock.l_type = F_UNLCK;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = 0;
+ lock.l_len = 0;
+ error = fcntl (fileno (seed_file), F_SETLKW, &lock);
+ if (error != 0)
+ {
+ fprintf (stderr, _("Could not unlock the seed file '%s'.\n"),
+ filename);
+ perror (_("when unlocking the seed file"));
+ abort ();
+ }
+ if (!webid_oidc_random_context_ok (global_ctx))
+ {
+ assert (0);
+ }
+ break;
+ default:
+ assert (0);
+ }
+ }
+ fclose (seed_file);
+ fclose (system_rng_file);
+}
+
+void
+webid_oidc_random (size_t request_size, uint8_t * data)
+{
+ static _Thread_local struct webid_oidc_random_context *thrd_ctx = NULL;
+ if (thrd_ctx == NULL)
+ {
+ assert (global_ctx);
+ thrd_ctx = webid_oidc_random_context_alloc ();
+ if (thrd_ctx == NULL)
+ {
+ fprintf (stderr,
+ _
+ ("Could not set the thread-local random generator up.\n"));
+ abort ();
+ }
+ if (!webid_oidc_random_context_spawn
+ (global_ctx, thrd_ctx, WEBID_OIDC_RANDOM_CONTEXT_THREAD_UNSAFE))
+ {
+ fprintf (stderr,
+ _
+ ("The random module has not been initialized. Please call webid_oidc_random_init first.\n"));
+ abort ();
+ }
+ }
+ if (!webid_oidc_random_context_get (thrd_ctx, request_size, data))
+ {
+ assert (0);
+ }
+}