aboutsummaryrefslogtreecommitdiffstats
path: root/src/genrandom.c
blob: a4baa0b2fa8d57fd77cf970e331d98809b01dba5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/* 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 <https://gnu.org/licenses/>.
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#if HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <assert.h>

#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;
}


/* 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);
}