File: passwordbasedauthentication.c

package info (click to toggle)
gvm-libs 22.34.2-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 2,968 kB
  • sloc: ansic: 39,015; makefile: 26
file content (343 lines) | stat: -rw-r--r-- 9,637 bytes parent folder | download
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
/* SPDX-FileCopyrightText: 2020-2023 Greenbone AG
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#include "passwordbasedauthentication.h"
// internal usage to have access to gvm_auth initialized to verify if
// initialization is needed
#include "authutils.c"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// UFC_crypt defines crypt_r when only when __USE_GNU is set
// this shouldn't affect other implementations
#define __USE_GNU
#include <crypt.h>
// INVALID_HASH is used on verify when the given hash is a NULL pointer.
// This is done to not directly jump to exit with a INVALID_HASH result
// but rather keep calculating to make it a little bit harder to guess
// if a user exists or not based on timing.
#define INVALID_HASH "1234567890$"
#ifndef CRYPT_GENSALT_OUTPUT_SIZE
#define CRYPT_GENSALT_OUTPUT_SIZE 192
#endif

#ifndef CRYPT_OUTPUT_SIZE
#define CRYPT_OUTPUT_SIZE 384
#endif

/**
 * @brief Check if a prefix is supported.
 *
 * @param[in]  id  Prefix.
 *
 * @return 1 if supported, else 0.
 */
static int
is_prefix_supported (const char *id)
{
  return strcmp (PREFIX_DEFAULT, id) == 0;
}

// we assume something else than libxcrypt > 3.1; like UFC-crypt
// libxcrypt sets a macro of crypt_gensalt_r to crypt_gensalt_rn
// therefore we could use that mechanism to figure out if we are on
// debian buster or newer.
#ifndef EXTERNAL_CRYPT_GENSALT_R

// used printables within salt
const char ascii64[] =
  "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

/**
 * @brief Try to get random bytes.
 *
 * @param[in]  buf     Destination for bytes.
 * @param[in]  buflen  Number of bytes to get.
 *
 * @return 0 on success, else error.
 */
static int
get_random (char *buf, size_t buflen)
{
  FILE *fp = fopen ("/dev/urandom", "r");
  int result = 0;
  if (fp == NULL)
    {
      result = -1;
      goto exit;
    }
  size_t nread = fread (buf, 1, buflen, fp);
  fclose (fp);
  if (nread < buflen)
    {
      result = -2;
    }

exit:
  return result;
}

/**
 * @brief Generate string suitable for use as setting when hashing a passphrase.
 *
 * If prefix is a NULL pointer, the current best default is used; if rbytes
 * is a NULL pointer, random data will be retrieved from the operating system
 * if possible.
 *
 * @param[in]  prefix  Controls which hash function will be used.
 * @param[in]  count   Controls the computional cost of the hash.
 * @param[in]  rbytes  Should point to nrbytes bytes of random data.
 * @param[in]  nrbytes  Number of bytes in rbytes.
 * @param[out] output   The generated setting string is written here.
 * @param[in]  output_size  Length of output. Must be at least
 *                          CRYPT_GENSALT_OUTPUT_SIZE.
 *
 * @return On success \p output, else NULL.
 */
char *
crypt_gensalt_r (const char *prefix, unsigned long count, const char *rbytes,
                 int nrbytes, char *output, int output_size);
char *
crypt_gensalt_r (const char *prefix, unsigned long count, const char *rbytes,
                 int nrbytes, char *output, int output_size)
{
  char *internal_rbytes = NULL;
  unsigned int written = 0, used = 0;
  unsigned long value = 0;
  if ((rbytes != NULL && nrbytes < 3) || output_size < 16
      || !is_prefix_supported (prefix))
    {
      output[0] = '*';
      goto exit;
    }
  if (rbytes == NULL)
    {
      internal_rbytes = malloc (16);
      if (get_random (internal_rbytes, 16) != 0)
        {
          output[0] = '*';
          goto exit;
        }
      nrbytes = 16;
      rbytes = internal_rbytes;
    }
  written = snprintf (output, output_size, "%srounds=%lu$",
                      prefix == NULL ? PREFIX_DEFAULT : prefix, count);
  while (written + 5 < (unsigned int) output_size
         && used + 3 < (unsigned int) nrbytes && (used * 4 / 3) < 16)
    {
      value = ((unsigned long) rbytes[used + 0] << 0)
              | ((unsigned long) rbytes[used + 1] << 8)
              | ((unsigned long) rbytes[used + 2] << 16);
      output[written] = ascii64[value & 0x3f];
      output[written + 1] = ascii64[(value >> 6) & 0x3f];
      output[written + 2] = ascii64[(value >> 12) & 0x3f];
      output[written + 3] = ascii64[(value >> 18) & 0x3f];
      written += 4;
      used += 3;
    }
  output[written] = '\0';
exit:
  if (internal_rbytes != NULL)
    free (internal_rbytes);
  return output[0] == '*' ? 0 : output;
}

#endif

/**
 * @brief Init PBA.
 *
 * @param[in] pepper  A static hidden addition to the randomly generated salt.
 * @param[in] pepper_size  The size of pepper; it must not be larger than
 *                         MAX_PEPPER_SIZE.
 * @param[in] count        Number of rounds used to calculate the hash. 0 to
 *                         use COUNT_DEFAULT.
 * @param[in] prefix       The algorithm used, if NULL then the most secure
 *                         available algorithm will be used.
 *
 * @return Settings, or NULL on error. Free with pba_finalize.
 */
struct PBASettings *
pba_init (const char *pepper, unsigned int pepper_size, unsigned int count,
          char *prefix)
{
  unsigned int i = 0;
  struct PBASettings *result = NULL;
  if (pepper_size > MAX_PEPPER_SIZE)
    goto exit;
  if (prefix != NULL && !is_prefix_supported (prefix))
    goto exit;
  result = malloc (sizeof (struct PBASettings));
  for (i = 0; i < MAX_PEPPER_SIZE; i++)
    result->pepper[i] = pepper != NULL && i < pepper_size ? pepper[i] : 0;
  result->count = count == 0 ? COUNT_DEFAULT : count;
  result->prefix = prefix == NULL ? PREFIX_DEFAULT : prefix;
exit:
  return result;
}

/**
 * @brief Cleanup PBA settings.
 *
 * @param[in]  settings  PBA settings.
 */
void
pba_finalize (struct PBASettings *settings)
{
  free (settings);
}

/**
 * @brief Check if a PBA settings is PHC compliant.
 *
 * @param[in]  setting  Setting.
 *
 * @return 1 if compliant, else 0.
 */
static int
pba_is_phc_compliant (const char *setting)
{
  if (setting == NULL)
    {
      return 1;
    }
  return strlen (setting) > 1 && setting[0] == '$';
}

/**
 * @brief Create a password hash.
 *
 * @param[in]  setting   PBA settings.
 * @param[in]  password  Password.
 *
 * @return Hash. Must be freed with free().
 */
char *
pba_hash (struct PBASettings *setting, const char *password)
{
  char *result = NULL, *settings = NULL, *tmp, *rslt;
  int i;
  struct crypt_data *data = NULL;

  if (!setting || !password)
    goto exit;
  if (!is_prefix_supported (setting->prefix))
    goto exit;
  settings = malloc (CRYPT_GENSALT_OUTPUT_SIZE);
  if (crypt_gensalt_r (setting->prefix, setting->count, NULL, 0, settings,
                       CRYPT_GENSALT_OUTPUT_SIZE)
      == NULL)
    goto exit;
  tmp = settings + strlen (settings) - 1;
  for (i = MAX_PEPPER_SIZE - 1; i > -1; i--)
    {
      if (setting->pepper[i] != 0)
        tmp[0] = setting->pepper[i];
      tmp--;
    }

  data = calloc (1, sizeof (struct crypt_data));
  rslt = crypt_r (password, settings, data);
  if (rslt == NULL)
    goto exit;
  result = calloc (1, CRYPT_OUTPUT_SIZE);
  memcpy (result, rslt, CRYPT_OUTPUT_SIZE);
  // remove pepper, by jumping to begin of applied pepper within result
  // and overriding it.
  tmp = result + (tmp - settings);
  for (i = 0; i < MAX_PEPPER_SIZE; i++)
    {
      tmp++;
      if (setting->pepper[i] != 0)
        tmp[0] = '0';
    }
exit:
  if (data != NULL)
    free (data);
  if (settings != NULL)
    free (settings);
  return result;
}

/**
 * @brief Verify a password hash.
 *
 * @param[in]  setting   PBA settings.
 * @param[in]  hash      Hash.
 * @param[in]  password  Password.
 *
 * @return Validity. VALID, UPDATE_RECOMMENDED, ...
 */
enum pba_rc
pba_verify_hash (const struct PBASettings *setting, const char *hash,
                 const char *password)
{
  char *cmp, *tmp = NULL;
  struct crypt_data *data = NULL;
  int i = 0;
  enum pba_rc result = ERR;

  char *invalid_hash = calloc (1, CRYPT_OUTPUT_SIZE);
  memset (invalid_hash, 0, CRYPT_OUTPUT_SIZE);
  memcpy (invalid_hash, INVALID_HASH, strlen (INVALID_HASH));

  if (!setting)
    goto exit;
  if (!is_prefix_supported (setting->prefix))
    goto exit;
  if (pba_is_phc_compliant (hash) != 0)
    {
      int hash_size;
      hash_size = hash ? strlen (hash) : strlen (invalid_hash);

      data = calloc (1, sizeof (struct crypt_data));
      // manipulate hash to reapply pepper
      tmp = calloc (1, CRYPT_OUTPUT_SIZE);

      memset (tmp, 0, CRYPT_OUTPUT_SIZE);
      memcpy (tmp, hash ? hash : invalid_hash,
              (hash_size < CRYPT_OUTPUT_SIZE) ? hash_size
                                              : CRYPT_OUTPUT_SIZE - 1);
      cmp = strrchr (tmp, '$');
      for (i = MAX_PEPPER_SIZE - 1; i > -1; i--)
        {
          cmp--;
          if (setting->pepper[i] != 0)
            cmp[0] = setting->pepper[i];
        }
      // some crypt_r implementations cannot handle if password is a
      // NULL pointer and run into SEGMENTATION faults.
      // Therefore we set it to ""
      cmp = crypt_r (password ? password : "", tmp, data);
      if (strcmp (tmp, cmp) == 0)
        result = VALID;
      else
        result = INVALID;
    }
  else
    {
      // assume authutils hash handling
      // initialize gvm_auth utils if not already initialized
      if (initialized == FALSE && gvm_auth_init () != 0)
        {
          goto exit;
        }
      // verify result of gvm_authenticate_classic
      i = gvm_authenticate_classic (NULL, password, hash);
      if (i == 0)
        result = UPDATE_RECOMMENDED;
      else if (i == 1)
        result = INVALID;
    }
exit:
  free (invalid_hash);
  if (data != NULL)
    free (data);
  if (tmp != NULL)
    free (tmp);
  return result;
}