From d33b47eb282d6b46378e88df3693caa7ce4f1cce Mon Sep 17 00:00:00 2001 From: Vivien Kraus Date: Wed, 1 Jan 2020 00:00:00 +0100 Subject: Add a random number generator. --- src/ChangeLog | 5 + src/Makefile.am | 1 + src/libwebidoidc.c | 2 + src/random/ChangeLog | 11 + src/random/Makefile.am | 20 ++ src/random/generate-random.c | 42 ++++ src/random/libwebidoidc-random.c | 35 ++++ src/random/random.c | 423 +++++++++++++++++++++++++++++++++++++++ src/random/webid-oidc/random.h | 57 ++++++ src/scm/webid-oidc/stubs.scm | 4 +- src/scm/webid-oidc/testing.scm | 1 + 11 files changed, 600 insertions(+), 1 deletion(-) create mode 100644 src/random/ChangeLog create mode 100644 src/random/Makefile.am create mode 100644 src/random/generate-random.c create mode 100644 src/random/libwebidoidc-random.c create mode 100644 src/random/random.c create mode 100644 src/random/webid-oidc/random.h (limited to 'src') diff --git a/src/ChangeLog b/src/ChangeLog index 75ed3d1..e2e9bea 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -3,3 +3,8 @@ * Makefile.am (%canon_reldir%_libwebidoidc_la_SOURCES): the common code is considered a source. * utilities.h: Put the common code in that header. + + * Makefile.am: build the "random" submodule. + + * libwebidoidc.c (init_webidoidc): Initialize the "random" submodule. + diff --git a/src/Makefile.am b/src/Makefile.am index 93fbada..732a941 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -24,6 +24,7 @@ install_go_targets = install-webidoidcgoDATA install_mod_targets = install-webidoidcmodDATA install-dist_webidoidcmodDATA include %reldir%/base64/Makefile.am +include %reldir%/random/Makefile.am include %reldir%/pre-inst/Makefile.am include %reldir%/inst/Makefile.am include %reldir%/scm/Makefile.am diff --git a/src/libwebidoidc.c b/src/libwebidoidc.c index 09af3fd..af26ae0 100644 --- a/src/libwebidoidc.c +++ b/src/libwebidoidc.c @@ -1,9 +1,11 @@ #define N_(s) void init_webidoidc_base64 (void); +void init_webidoidc_random (void); void init_webidoidc (void) { N_("This is the main function."); init_webidoidc_base64 (); + init_webidoidc_random (); } diff --git a/src/random/ChangeLog b/src/random/ChangeLog new file mode 100644 index 0000000..b2a1cc3 --- /dev/null +++ b/src/random/ChangeLog @@ -0,0 +1,11 @@ +2020-11-26 Vivien Kraus + + * libwebidoidc-random.c: Fix the submodule to use the package name. + (fopen_cache_seed): Fix in this function. + +2020-11-25 Vivien Kraus + + * libwebidoidc-random.c: New submodule. + + * generate-random.c: New program. + diff --git a/src/random/Makefile.am b/src/random/Makefile.am new file mode 100644 index 0000000..fbd548c --- /dev/null +++ b/src/random/Makefile.am @@ -0,0 +1,20 @@ +noinst_LTLIBRARIES += %reldir%/libwebidoidc-random.la +noinst_PROGRAMS += %reldir%/generate-random +EXTRA_DIST += %reldir%/libwebidoidc-random.x +BUILT_SOURCES += %reldir%/libwebidoidc-random.x +include_HEADERS += %reldir%/webid-oidc/random.h + +%canon_reldir%_libwebidoidc_random_la_LIBADD = $(GUILE_LIBS) $(NETTLE_LIBS) +%canon_reldir%_libwebidoidc_random_la_SOURCES = \ + %reldir%/libwebidoidc-random.c \ + %reldir%/random.c \ + %reldir%/webid-oidc/random.h +%canon_reldir%_generate_random_LDADD = %reldir%/libwebidoidc-random.la $(GUILE_LIBS) + +AM_CFLAGS += -I %reldir% -I $(srcdir)/%reldir% + +INDENTED += \ + %reldir%/generate-random.c \ + $(%canon_reldir%_libwebidoidc_random_la_SOURCES) + +%reldir%/libwebidoidc-random.o: %reldir%/libwebidoidc-random.x diff --git a/src/random/generate-random.c b/src/random/generate-random.c new file mode 100644 index 0000000..bde01b9 --- /dev/null +++ b/src/random/generate-random.c @@ -0,0 +1,42 @@ +#ifdef HAVE_CONFIG_H +#include +#endif /* HAVE_CONFIG_H */ + +#include +#include +#include +#include + +#define _(s) gettext (s) + +SCM webidoidc_random_g (SCM length); + +int init_webidoidc_random (void); + +static void +run (void *params, int argc, char *argv[]) +{ + size_t n_bytes; + char *end; + SCM data; + (void) params; + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + if (argc != 2 + || (n_bytes = strtoull (argv[1], &end, 10)) == 0 || *end != '\0') + { + fprintf (stderr, _("Usage: generate-random [NUMBER OF BYTES]\n")); + exit (1); + } + init_webidoidc_random (); + data = webidoidc_random_g (scm_from_size_t (n_bytes)); + printf ("%s\n", scm_to_locale_string (data)); +} + +int +main (int argc, char *argv[]) +{ + scm_boot_guile (argc, argv, run, NULL); + return 0; +} diff --git a/src/random/libwebidoidc-random.c b/src/random/libwebidoidc-random.c new file mode 100644 index 0000000..2bea9bb --- /dev/null +++ b/src/random/libwebidoidc-random.c @@ -0,0 +1,35 @@ +#include +#include +#include +#include +#include + +#define _(s) dgettext (PACKAGE, s) + +/* Register "random", a guile function to generate random data. */ +void init_webidoidc_random (void); + +SCM_DEFINE (webidoidc_random_g, "random", 1, 0, 0, (SCM length), + "Get @var{length} random bytes, and encode them in a base64-url string.") +{ + size_t c_length = scm_to_size_t (length); + uint8_t *data = scm_gc_malloc_pointerless (c_length, "random data"); + webid_oidc_random (c_length, data); + return wrap_bytevector (c_length, data); +} + +SCM_DEFINE (webidoidc_random_init_g, "random-init!", 0, 0, 0, (void), + "Re-initialize the random number generator so that future threads are not predictable.") +{ + webid_oidc_random_init (); + return SCM_UNDEFINED; +} + +void +init_webidoidc_random (void) +{ + webid_oidc_random_init (); +#ifndef SCM_MAGIC_SNARFER +#include "libwebidoidc-random.x" +#endif /* not SCM_MAGIC_SNARFER */ +} 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 +#endif /* not HAVE_CONFIG_H */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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); + } +} diff --git a/src/random/webid-oidc/random.h b/src/random/webid-oidc/random.h new file mode 100644 index 0000000..ae1adfc --- /dev/null +++ b/src/random/webid-oidc/random.h @@ -0,0 +1,57 @@ +#ifndef H_RANDOM_INCLUDED +#define H_RANDOM_INCLUDED + +struct webid_oidc_random_context; +typedef struct webid_oidc_random_context webid_oidc_random_context; + +/* These functions allocate data, they are not reentrant. */ +struct webid_oidc_random_context *webid_oidc_random_context_alloc (void); +void webid_oidc_random_context_free (struct webid_oidc_random_context *ctx); + +enum webid_oidc_random_context_state +{ + WEBID_OIDC_RANDOM_CONTEXT_READY = 0, + WEBID_OIDC_RANDOM_CONTEXT_WAIT_LOCK_FILE, + WEBID_OIDC_RANDOM_CONTEXT_WAIT_FILE_READ, + WEBID_OIDC_RANDOM_CONTEXT_WAIT_FILE_WRITE, + WEBID_OIDC_RANDOM_CONTEXT_WAIT_UNLOCK_FILE +}; + +typedef enum webid_oidc_random_context_state webid_oidc_random_context_state; + +enum webid_oidc_random_context_state +webid_oidc_random_context_get_state (const struct webid_oidc_random_context + *ctx); +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); +int webid_oidc_random_context_ok (struct webid_oidc_random_context *ctx); +int webid_oidc_random_context_bytes (struct webid_oidc_random_context *ctx, + size_t n_bytes, const uint8_t * bytes); + +enum webid_oidc_random_context_spawn_flags +{ + WEBID_OIDC_RANDOM_CONTEXT_THREAD_UNSAFE = 1 +}; +typedef enum webid_oidc_random_context_spawn_flags + webid_oidc_random_context_spawn_flags; + +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); + +int webid_oidc_random_context_get (struct webid_oidc_random_context *ctx, + size_t request_size, uint8_t * data); + +/* These functions set up and use a global state. Neither are + reentrant. */ +void webid_oidc_random_init (void); + +/* This function is thread-safe. */ +void webid_oidc_random (size_t request_size, uint8_t * data); + +#endif /* not H_RANDOM_INCLUDED */ diff --git a/src/scm/webid-oidc/stubs.scm b/src/scm/webid-oidc/stubs.scm index 12006e6..b022ef2 100644 --- a/src/scm/webid-oidc/stubs.scm +++ b/src/scm/webid-oidc/stubs.scm @@ -15,4 +15,6 @@ (export base64-encode - (fix-base64-decode . base64-decode)) + (fix-base64-decode . base64-decode) + random + random-init!) diff --git a/src/scm/webid-oidc/testing.scm b/src/scm/webid-oidc/testing.scm index 40284cf..d4b7f4d 100644 --- a/src/scm/webid-oidc/testing.scm +++ b/src/scm/webid-oidc/testing.scm @@ -24,4 +24,5 @@ (error->str error)) (raise-exception error)) (lambda () + (random-init!) (f)))) -- cgit v1.2.3