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
This commit is contained in:
Werner Koch 2025-03-14 13:03:46 +01:00
parent 7568566ef3
commit 926b1f1f1e
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
7 changed files with 183 additions and 21 deletions

8
NEWS
View File

@ -1,8 +1,11 @@
Noteworthy changes in version 2.0.0 (unreleased) Noteworthy changes in version 2.0.0 (unreleased)
------------------------------------------------ ------------------------------------------------
* New function gpgme_op_random_bytes to get cryptographically strng * New function gpgme_op_random_bytes to get cryptographically
random bytes from gpg. 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 * Removed the gpgme_attr_t enums and their functions which were
deprecated since 2003. [rMd54d6eaa64] deprecated since 2003. [rMd54d6eaa64]
@ -16,6 +19,7 @@ Noteworthy changes in version 2.0.0 (unreleased)
* Interface changes relative to the 1.24 branch: * Interface changes relative to the 1.24 branch:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
gpgme_op_random_bytes NEW. gpgme_op_random_bytes NEW.
gpgme_op_random_value NEW.
GPGME_RANDOM_MODE_NORMAL NEW. GPGME_RANDOM_MODE_NORMAL NEW.
GPGME_RANDOM_MODE_ZBASE32 NEW. GPGME_RANDOM_MODE_ZBASE32 NEW.
gpgme_attr_t REMOVED. gpgme_attr_t REMOVED.

View File

@ -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} 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 @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 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 is not changed. The caller must provide a context @var{ctx}
accidental overuse of the random generator initialized for GPGME_PROTOCOL_OPENPGP. This function has a limit of
1024 bytes to avoid accidental overuse of the random generator
@end deftypefun @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 @node Miscellaneous

View File

@ -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. /* Fill BUFFER of size BUFSIZE with random bytes retrieved from gpg.
* If GPGME_RANDOM_MODE_ZBASE32 is used BUFSIZE needs to be at least * 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 * 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); gpgme_data_release (data);
return TRACE_ERR (err); 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);
}

View File

@ -276,4 +276,5 @@ EXPORTS
gpgme_op_setownertrust_start @214 gpgme_op_setownertrust_start @214
gpgme_op_random_bytes @215 gpgme_op_random_bytes @215
gpgme_op_random_value @216
; END ; END

View File

@ -2481,10 +2481,15 @@ typedef enum
} }
gpgme_random_mode_t; 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, gpgme_error_t gpgme_op_random_bytes (gpgme_ctx_t ctx, gpgme_random_mode_t mode,
char *buffer, size_t bufsize); 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);
/* /*

View File

@ -274,6 +274,7 @@ GPGME_1.0 {
gpgme_op_setownertrust_start; gpgme_op_setownertrust_start;
gpgme_op_random_bytes; gpgme_op_random_bytes;
gpgme_op_random_value;
local: local:
*; *;

View File

@ -28,6 +28,8 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <assert.h> #include <assert.h>
#include <errno.h>
#include <stdint.h>
#include <gpgme.h> #include <gpgme.h>
@ -42,10 +44,13 @@ static int verbose;
static int static int
show_usage (int ex) show_usage (int ex)
{ {
fputs ("usage: " PGM " [options]\n\n" fputs ("usage: " PGM " [options] [LIMIT]\n\n"
"Options:\n" "Options:\n"
" --verbose run in verbose mode\n" " --verbose run in verbose mode\n"
" --zbase32 generate 30 zbase32 characters\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); , stderr);
exit (ex); exit (ex);
} }
@ -60,6 +65,8 @@ main (int argc, char **argv)
gpgme_protocol_t protocol = GPGME_PROTOCOL_OPENPGP; gpgme_protocol_t protocol = GPGME_PROTOCOL_OPENPGP;
gpgme_random_mode_t mode = 0; gpgme_random_mode_t mode = 0;
char buffer[128]; char buffer[128];
int hexmode = 0;
int valuemode = 0;
if (argc) if (argc)
@ -85,11 +92,21 @@ main (int argc, char **argv)
mode = GPGME_RANDOM_MODE_ZBASE32; mode = GPGME_RANDOM_MODE_ZBASE32;
argc--; argv++; argc--; argv++;
} }
else if (!strcmp (*argv, "--hex"))
{
hexmode = 1;
argc--; argv++;
}
else if (!strncmp (*argv, "--", 2)) else if (!strncmp (*argv, "--", 2))
show_usage (1); show_usage (1);
} }
if (argc) if (argc == 1)
valuemode = 1;
else if (argc)
show_usage (1);
if ((valuemode && mode) || (!valuemode && hexmode))
show_usage (1); show_usage (1);
init_gpgme (protocol); init_gpgme (protocol);
@ -98,10 +115,45 @@ main (int argc, char **argv)
fail_if_err (err); fail_if_err (err);
gpgme_set_protocol (ctx, protocol); gpgme_set_protocol (ctx, protocol);
if (valuemode)
{
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);
}
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
{
err = gpgme_op_random_bytes (ctx, mode, buffer, sizeof buffer); err = gpgme_op_random_bytes (ctx, mode, buffer, sizeof buffer);
if (err) if (err)
{ {
fprintf (stderr, PGM ": error getting random: %s\n", gpg_strerror (err)); fprintf (stderr, PGM ": error getting random: %s\n",
gpg_strerror (err));
exit (1); exit (1);
} }
@ -119,6 +171,7 @@ main (int argc, char **argv)
} }
putchar ('\n'); putchar ('\n');
} }
}
gpgme_release (ctx); gpgme_release (ctx);
return 0; return 0;