From 926b1f1f1e3e382f08cc57fc86d5892649514007 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 14 Mar 2025 13:03:46 +0100 Subject: Add API gpgme_op_random_value. * src/genrandom.c (getrandom_size_t): New. (gpgme_op_random_value): New. * src/gpgme.def: Add new function. * src/libgpgme.vers: Ditto. * src/gpgme.h.in: Add prototype. * tests/run-genrandom.c: Add an option to use the new function. -- The implementation is not optimized but sufficient for our use case. Possible improvements for this and gpgme_op_random_bytes are a cache for random bytes in the context so that we do not need to get out to gpgme for just a few random bytes. GnuPG-bug-id: 6694 --- NEWS | 8 +++-- doc/gpgme.texi | 19 ++++++++++-- src/genrandom.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/gpgme.def | 1 + src/gpgme.h.in | 7 ++++- src/libgpgme.vers | 1 + tests/run-genrandom.c | 83 +++++++++++++++++++++++++++++++++++++++++---------- 7 files changed, 182 insertions(+), 20 deletions(-) diff --git a/NEWS b/NEWS index 481da3a0..7544d42c 100644 --- a/NEWS +++ b/NEWS @@ -1,8 +1,11 @@ Noteworthy changes in version 2.0.0 (unreleased) ------------------------------------------------ - * New function gpgme_op_random_bytes to get cryptographically strng - random bytes from gpg. + * New function gpgme_op_random_bytes to get cryptographically + strong random bytes from gpg. [T6694] + + * New function gpgme_op_random_value to get a cryptographically + strong unsigned integer random value. [T6694] * Removed the gpgme_attr_t enums and their functions which were deprecated since 2003. [rMd54d6eaa64] @@ -16,6 +19,7 @@ Noteworthy changes in version 2.0.0 (unreleased) * Interface changes relative to the 1.24 branch: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ gpgme_op_random_bytes NEW. + gpgme_op_random_value NEW. GPGME_RANDOM_MODE_NORMAL NEW. GPGME_RANDOM_MODE_ZBASE32 NEW. gpgme_attr_t REMOVED. diff --git a/doc/gpgme.texi b/doc/gpgme.texi index a586631e..fa93083c 100644 --- a/doc/gpgme.texi +++ b/doc/gpgme.texi @@ -7142,11 +7142,26 @@ The function @code{gpgme_op_random_bytes} returns random bytes. from gpg. However, if @var{mode} is @code{GPGME_RANDOM_MODE_ZBASE32} @var{bufsize} needs to be at least 31 and will be filled with a string of 30 ASCII characters followed by a Nul; the remainder of the buffer -is not changed. This function has a limit of 1024 bytes to avoid -accidental overuse of the random generator +is not changed. The caller must provide a context @var{ctx} +initialized for GPGME_PROTOCOL_OPENPGP. This function has a limit of +1024 bytes to avoid accidental overuse of the random generator @end deftypefun +@deftypefun {gpgme_error_t} gpgme_op_random_values ( @ + @w{gpgme_ctx_t @var{ctx}}, @ + @w{size_t @var{limit}}, @ + @w{size_t *@var{retval}}) + +@since{2.0.0} + +The function @code{gpgme_op_random_value} returns an unbiased random +value in the range 0 <= value < @var{limit}. The value is returned at +@var{retval} if and only if the function returns with success. The +caller must also provide a context @var{ctx} initialized for +GPGME_PROTOCOL_OPENPGP. +@end deftypefun + @node Miscellaneous diff --git a/src/genrandom.c b/src/genrandom.c index a4baa0b2..0f787cf4 100644 --- a/src/genrandom.c +++ b/src/genrandom.c @@ -58,6 +58,46 @@ do_genrandom (gpgme_ctx_t ctx, gpgme_data_t dataout, size_t length, int zbase) } +/* Store a random value into RETVAL or return an error. */ +static gpgme_error_t +getrandom_size_t (gpgme_ctx_t ctx, size_t *retval) +{ + gpgme_error_t err = 0; + gpgme_data_t data = NULL; + char *datap = NULL; + size_t datalen; + + err = gpgme_data_new (&data); + if (err) + goto leave; + + err = do_genrandom (ctx, data, sizeof (size_t), 0); + if (!err) + err = _gpgme_wait_one (ctx); + if (err) + goto leave; + + datap = gpgme_data_release_and_get_mem (data, &datalen); + data = NULL; + if (!datap) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (datalen != sizeof (size_t)) + { + err = gpg_error (GPG_ERR_INTERNAL); + goto leave; + } + memcpy (retval, datap, datalen); + + leave: + free (datap); + gpgme_data_release (data); + return err; +} + + /* Fill BUFFER of size BUFSIZE with random bytes retrieved from gpg. * If GPGME_RANDOM_MODE_ZBASE32 is used BUFSIZE needs to be at least * 31 and will be filled with a string of 30 ascii characters followed @@ -147,3 +187,46 @@ gpgme_op_random_bytes (gpgme_ctx_t ctx, gpgme_random_mode_t mode, gpgme_data_release (data); return TRACE_ERR (err); } + + + +/* On success the function stores an unbiased random value in the + * range [0,limit) at RETVAL. On error the function returns an error + * code and the value at RETVAL is undefined. A context must be + * provided so that the function can get raw random bytes from the gpg + * enine (i.e. from Libgcrypt). */ +gpgme_error_t +gpgme_op_random_value (gpgme_ctx_t ctx, size_t limit, size_t *retval) +{ + gpgme_error_t err; + size_t t, x; + + TRACE_BEG (DEBUG_CTX, "gpgme_op_random_value", ctx, "limit=%zu", limit); + + if (!ctx || limit < 2 || !retval) + { + err = gpg_error (GPG_ERR_INV_VALUE); + goto leave; + } + + /* This is the OpenBSD algorithm as used by arc4random_uniform and + * described in "Fast Random Integer Generation in an Interval" by + * Daniel Lemire (arXiv:1805.10941v4, 2018). */ + t = (-limit) % limit; + x = 0; /* Avoid (false) compiler warning. */ + do + { + err = getrandom_size_t (ctx, &x); + if (err) + goto leave; + /* fprintf (stderr, "getrandom returned %zu (limit=%zu, t=%zu)\n", */ + /* x, limit, t); */ + } + while (x < t); + x %= limit; + + *retval = x; + + leave: + return TRACE_ERR (err); +} diff --git a/src/gpgme.def b/src/gpgme.def index d203901f..3bdaac4e 100644 --- a/src/gpgme.def +++ b/src/gpgme.def @@ -276,4 +276,5 @@ EXPORTS gpgme_op_setownertrust_start @214 gpgme_op_random_bytes @215 + gpgme_op_random_value @216 ; END diff --git a/src/gpgme.h.in b/src/gpgme.h.in index 9c0c7977..705d9741 100644 --- a/src/gpgme.h.in +++ b/src/gpgme.h.in @@ -2481,10 +2481,15 @@ typedef enum } gpgme_random_mode_t; -/* Fill BUFFER with BUFSIZE random bytes from gpg. */ +/* Fill BUFFER with BUFSIZE random bytes from gpg or return an error. */ gpgme_error_t gpgme_op_random_bytes (gpgme_ctx_t ctx, gpgme_random_mode_t mode, char *buffer, size_t bufsize); +/* Store an unbiased random value in the range [0,LIMIT) at RETVAL or + * return an error. */ +gpgme_error_t gpgme_op_random_value (gpgme_ctx_t ctx, size_t limit, + size_t *retval); + /* diff --git a/src/libgpgme.vers b/src/libgpgme.vers index 83f8c87b..555f9fc0 100644 --- a/src/libgpgme.vers +++ b/src/libgpgme.vers @@ -274,6 +274,7 @@ GPGME_1.0 { gpgme_op_setownertrust_start; gpgme_op_random_bytes; + gpgme_op_random_value; local: *; diff --git a/tests/run-genrandom.c b/tests/run-genrandom.c index 18b8c7ab..f28ef299 100644 --- a/tests/run-genrandom.c +++ b/tests/run-genrandom.c @@ -28,6 +28,8 @@ #include #include #include +#include +#include #include @@ -42,10 +44,13 @@ static int verbose; static int show_usage (int ex) { - fputs ("usage: " PGM " [options]\n\n" + fputs ("usage: " PGM " [options] [LIMIT]\n\n" "Options:\n" " --verbose run in verbose mode\n" " --zbase32 generate 30 zbase32 characters\n" + " --hex return a hex value in LIMIT mode\n" + "\n" + "With LIMIT return a decimal value in the range [0,LIMIT)\n" , stderr); exit (ex); } @@ -60,6 +65,8 @@ main (int argc, char **argv) gpgme_protocol_t protocol = GPGME_PROTOCOL_OPENPGP; gpgme_random_mode_t mode = 0; char buffer[128]; + int hexmode = 0; + int valuemode = 0; if (argc) @@ -85,11 +92,21 @@ main (int argc, char **argv) mode = GPGME_RANDOM_MODE_ZBASE32; argc--; argv++; } + else if (!strcmp (*argv, "--hex")) + { + hexmode = 1; + argc--; argv++; + } else if (!strncmp (*argv, "--", 2)) show_usage (1); } - if (argc) + if (argc == 1) + valuemode = 1; + else if (argc) + show_usage (1); + + if ((valuemode && mode) || (!valuemode && hexmode)) show_usage (1); init_gpgme (protocol); @@ -98,26 +115,62 @@ main (int argc, char **argv) fail_if_err (err); gpgme_set_protocol (ctx, protocol); - err = gpgme_op_random_bytes (ctx, mode, buffer, sizeof buffer); - if (err) + if (valuemode) { - fprintf (stderr, PGM ": error getting random: %s\n", gpg_strerror (err)); - exit (1); - } + size_t limit, value; + + errno = 0; + limit = strtoul (*argv, NULL, 0); + if (errno) + { + fprintf (stderr, PGM ": error parsing LIMIT arg: %s\n", + strerror (errno)); + exit (1); + } + if (limit > SIZE_MAX) + { + fprintf (stderr, PGM ": error parsing LIMIT arg: %s\n", + "too large for size_t"); + exit (1); + } - if (mode == GPGME_RANDOM_MODE_ZBASE32) - puts (buffer); + err = gpgme_op_random_value (ctx, limit, &value); + if (err) + { + fprintf (stderr, PGM ": error getting random: %s\n", + gpg_strerror (err)); + exit (1); + } + + if (hexmode) + printf ("%zx\n", value); + else + printf ("%zu\n", value); + } else { - int i; + err = gpgme_op_random_bytes (ctx, mode, buffer, sizeof buffer); + if (err) + { + fprintf (stderr, PGM ": error getting random: %s\n", + gpg_strerror (err)); + exit (1); + } - for (i=0; i < sizeof buffer; i++) + if (mode == GPGME_RANDOM_MODE_ZBASE32) + puts (buffer); + else { - if (i && !(i%32)) - putchar ('\n'); - printf ("%02x", ((unsigned char *)buffer)[i]); + int i; + + for (i=0; i < sizeof buffer; i++) + { + if (i && !(i%32)) + putchar ('\n'); + printf ("%02x", ((unsigned char *)buffer)[i]); + } + putchar ('\n'); } - putchar ('\n'); } gpgme_release (ctx); -- cgit v1.2.3