/* genrandom.c - Wrapper around gpg --gen-random * Copyright (C) 2025 g10 Code GmbH * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * GPGME is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #include #include #include "gpgme.h" #include "debug.h" #include "context.h" #include "ops.h" static gpgme_error_t do_genrandom (gpgme_ctx_t ctx, gpgme_data_t dataout, size_t length, int zbase) { gpgme_error_t err; const char *argv[4]; char countbuf[35]; if (ctx->protocol != GPGME_PROTOCOL_OPENPGP) return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL); err = _gpgme_op_reset (ctx, 1/*synchronous*/); if (err) return err; snprintf (countbuf, sizeof countbuf, "%zu", length); argv[0] = "--gen-random"; argv[1] = zbase? "30" : "2"; argv[2] = countbuf; argv[3] = NULL; err = _gpgme_engine_op_getdirect (ctx->engine, argv, dataout, 0); if (!err) err = _gpgme_wait_one (ctx); return err; } /* 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 * by a Nul; the remainder of the buffer is not changed. In all other * modes the entire buffer will be filled with binary data. The * function has a limit of 1024 bytes to avoid accidental overuse of * the random generator. */ gpgme_error_t gpgme_op_random_bytes (gpgme_ctx_t ctx, gpgme_random_mode_t mode, char *buffer, size_t bufsize) { gpgme_error_t err = 0; gpgme_data_t data = NULL; char *datap = NULL; size_t datalen; TRACE_BEG (DEBUG_CTX, "gpgme_op_random_bytes", ctx, "mode=%d size=%zu", mode, bufsize); if (!ctx || !buffer || !bufsize) err = gpg_error (GPG_ERR_INV_VALUE); else if (mode == GPGME_RANDOM_MODE_ZBASE32) { /* The output is expected to be 30 ascii characters followed by * a trailing Nul. */ if (bufsize < 31) err = gpg_error (GPG_ERR_BUFFER_TOO_SHORT); } else if (mode) err = gpg_error (GPG_ERR_INV_VALUE); else if (bufsize > 1024) /* More or an less arbitrary limit. */ err = gpg_error (GPG_ERR_TOO_LARGE); if (err) goto leave; err = gpgme_data_new (&data); if (err) goto leave; err = do_genrandom (ctx, data, bufsize, (mode == GPGME_RANDOM_MODE_ZBASE32)); 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 > bufsize) { err = gpg_error (GPG_ERR_INTERNAL); goto leave; } if (mode == GPGME_RANDOM_MODE_ZBASE32) { /* Strip trailing LF. */ while (datalen && (datap[datalen-1] == '\n' || datap[datalen-1] == '\r')) datalen--; if (datalen != 30) { /* 30 is the holy count, not 29, not 31 and never 32. */ err = gpg_error (GPG_ERR_INTERNAL); goto leave; } memcpy (buffer, datap, datalen); buffer[datalen] = 0; } else { if (datalen != bufsize) { err = gpg_error (GPG_ERR_INTERNAL); goto leave; } memcpy (buffer, datap, datalen); } leave: free (datap); 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); }