Coverage Report

Created: 2026-05-16 06:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-auth/auth-scram-client.c
Line
Count
Source
1
/* Copyright (c) 2021-2023 Dovecot authors, see the included COPYING file */
2
3
#include "lib.h"
4
#include "str.h"
5
#include "strnum.h"
6
#include "buffer.h"
7
#include "array.h"
8
#include "base64.h"
9
#include "hmac.h"
10
#include "sha1.h"
11
#include "sha2.h"
12
#include "randgen.h"
13
#include "safe-memset.h"
14
15
#include "auth-gs2.h"
16
#include "auth-scram-client.h"
17
18
/* c-nonce length */
19
#define SCRAM_CLIENT_NONCE_LEN 64
20
/* Max iteration count accepted by the client */
21
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
22
#  define SCRAM_MAX_ITERATE_COUNT (128 * 4096)
23
#else
24
966
#  define SCRAM_MAX_ITERATE_COUNT (2 * 4096)
25
#endif
26
27
void auth_scram_client_init(struct auth_scram_client *client_r, pool_t pool,
28
          const struct auth_scram_client_settings *set)
29
1.64k
{
30
1.64k
  i_assert(set->hash_method != NULL);
31
32
1.64k
  i_zero(client_r);
33
1.64k
  client_r->pool = pool;
34
1.64k
  client_r->set = *set;
35
1.64k
}
36
37
void auth_scram_client_deinit(struct auth_scram_client *client)
38
1.64k
{
39
1.64k
  if (client->server_signature != NULL) {
40
930
    i_assert(client->set.hash_method != NULL);
41
930
    safe_memset(client->server_signature, 0,
42
930
          client->set.hash_method->digest_size);
43
930
  }
44
1.64k
}
45
46
static void
47
auth_scram_generate_cnonce(struct auth_scram_client *client)
48
1.64k
{
49
1.64k
  unsigned char cnonce[SCRAM_CLIENT_NONCE_LEN+1];
50
1.64k
  size_t i;
51
52
1.64k
  random_fill(cnonce, sizeof(cnonce)-1);
53
54
  /* Make sure cnonce is printable and does not contain ',' */
55
106k
  for (i = 0; i < sizeof(cnonce) - 1; i++) {
56
104k
    cnonce[i] = (cnonce[i] % ('~' - '!')) + '!';
57
104k
    if (cnonce[i] == ',')
58
0
      cnonce[i] = '~';
59
104k
  }
60
1.64k
  cnonce[sizeof(cnonce)-1] = '\0';
61
1.64k
  client->nonce = p_strdup(client->pool, (char *)cnonce);
62
1.64k
}
63
64
static string_t *auth_scram_get_client_first(struct auth_scram_client *client)
65
1.64k
{
66
1.64k
  const char *cbind_type = client->set.cbind_type;
67
1.64k
  enum auth_scram_cbind_server_support cbind_support =
68
1.64k
    client->set.cbind_support;
69
1.64k
  struct auth_gs2_header gs2_header;
70
1.64k
  const char *cfm, *cfm_bare;
71
1.64k
  string_t *str;
72
1.64k
  size_t cfm_bare_offset;
73
74
  /* RFC 5802, Section 7:
75
76
     client-first-message = gs2-header client-first-message-bare
77
     gs2-header      = gs2-cbind-flag "," [ authzid ] ","
78
79
     gs2-cbind-flag  = ("p=" cb-name) / "n" / "y"
80
81
     authzid         = "a=" saslname
82
                       ;; Protocol specific.
83
84
     client-first-message-bare = [reserved-mext ","]
85
                       username "," nonce ["," extensions]
86
87
     username        = "n=" saslname
88
89
     nonce           = "r=" c-nonce [s-nonce]
90
91
     extensions      = attr-val *("," attr-val)
92
                       ;; All extensions are optional,
93
                       ;; i.e., unrecognized attributes
94
                       ;; not defined in this document
95
                       ;; MUST be ignored.
96
     attr-val        = ALPHA "=" value
97
   */
98
99
1.64k
  auth_scram_generate_cnonce(client);
100
101
1.64k
  i_zero(&gs2_header);
102
1.64k
  if (cbind_type == NULL) {
103
1.04k
    gs2_header.cbind.status =
104
1.04k
      AUTH_GS2_CBIND_STATUS_NO_CLIENT_SUPPORT;
105
1.04k
  } else if (cbind_support == AUTH_SCRAM_CBIND_SERVER_SUPPORT_NONE) {
106
0
    gs2_header.cbind.status =
107
0
      AUTH_GS2_CBIND_STATUS_NO_SERVER_SUPPORT;
108
591
  } else {
109
591
    gs2_header.cbind.status = AUTH_GS2_CBIND_STATUS_PROVIDED;
110
591
    gs2_header.cbind.name = cbind_type;
111
591
  }
112
1.64k
  gs2_header.authzid = client->set.authzid,
113
114
1.64k
  str = t_str_new(256);
115
1.64k
  auth_gs2_header_encode(&gs2_header, str);
116
117
1.64k
  cfm_bare_offset = str_len(str);
118
1.64k
  str_append(str, "n=");
119
1.64k
  auth_gs2_encode_username(client->set.authid, str);
120
1.64k
  str_append(str, ",r=");
121
1.64k
  str_append(str, client->nonce);
122
123
1.64k
  cfm = str_c(str);
124
1.64k
  cfm_bare = cfm + cfm_bare_offset;
125
126
1.64k
  client->gs2_header = p_strndup(client->pool, cfm, cfm_bare_offset);
127
1.64k
  client->client_first_message_bare = p_strdup(client->pool, cfm_bare);
128
1.64k
  return str;
129
1.64k
}
130
131
static int
132
auth_scram_parse_server_first(struct auth_scram_client *client,
133
            const unsigned char *input, size_t input_len,
134
            const char **error_r)
135
1.31k
{
136
1.31k
  const char **fields;
137
1.31k
  unsigned int field_count, iter;
138
1.31k
  const char *nonce, *salt, *iter_str;
139
1.31k
  size_t salt_len;
140
141
  /* RFC 5802, Section 7:
142
143
     server-first-message =
144
                       [reserved-mext ","] nonce "," salt ","
145
                       iteration-count ["," extensions]
146
   */
147
148
1.31k
  fields = t_strsplit(t_strndup(input, input_len), ",");
149
1.31k
  field_count = str_array_length(fields);
150
1.31k
  if (field_count < 3) {
151
3
    *error_r = "Invalid first server message";
152
3
    return -1;
153
3
  }
154
155
1.31k
  nonce = fields[0];
156
1.31k
  salt = fields[1];
157
1.31k
  iter_str = fields[2];
158
159
  /* reserved-mext   = "m=" 1*(value-char)
160
   */
161
1.31k
  if (nonce[0] == 'm') {
162
1
    *error_r = "Mandatory extension(s) not supported";
163
1
    return -1;
164
1
  }
165
166
  /* nonce           = "r=" c-nonce [s-nonce]
167
                       ;; Second part provided by server.
168
     c-nonce         = printable
169
     s-nonce         = printable
170
   */
171
1.30k
  if (nonce[0] != 'r' || nonce[1] != '=') {
172
12
    *error_r = "Invalid nonce field in first server message";
173
12
    return -1;
174
12
  }
175
1.29k
  if (!str_begins_with(&nonce[2], client->nonce)) {
176
18
    *error_r = "Incorrect nonce in first server message";
177
18
    return -1;
178
18
  }
179
1.27k
  nonce += 2;
180
181
  /* salt            = "s=" base64
182
   */
183
1.27k
  if (salt[0] != 's' || salt[1] != '=') {
184
16
    *error_r = "Invalid salt field in first server message";
185
16
    return -1;
186
16
  }
187
1.26k
  salt_len = strlen(&salt[2]);
188
1.26k
  client->salt = buffer_create_dynamic(
189
1.26k
    client->pool, MAX_BASE64_DECODED_SIZE(salt_len));
190
1.26k
  if (base64_decode(&salt[2], salt_len, client->salt) < 0) {
191
129
    *error_r = "Invalid base64 encoding for salt field in first server message";
192
129
    return -1;
193
129
  }
194
195
  /* iteration-count = "i=" posit-number
196
                       ;; A positive number.
197
   */
198
1.13k
  if (iter_str[0] != 'i' || iter_str[1] != '=' ||
199
1.12k
      str_to_uint(&iter_str[2], &iter) < 0) {
200
168
    *error_r = "Invalid iteration count field in first server message";
201
168
    return -1;
202
168
  }
203
966
  if (iter > SCRAM_MAX_ITERATE_COUNT) {
204
36
    *error_r = "Iteration count out of range in first server message";
205
36
    return -1;
206
36
  }
207
208
930
  client->server_first_message =
209
930
    p_strndup(client->pool, input, input_len);
210
930
  client->nonce = p_strdup(client->pool, nonce);
211
930
  client->iter = iter;
212
930
  return 0;
213
966
}
214
215
static string_t *auth_scram_get_client_final(struct auth_scram_client *client)
216
930
{
217
930
  const struct hash_method *hmethod = client->set.hash_method;
218
219
930
  i_assert(hmethod != NULL);
220
930
  i_assert(client->salt != NULL);
221
222
930
  const buffer_t *cbind_data = client->set.cbind_data;
223
930
  unsigned char salted_password[hmethod->digest_size];
224
930
  unsigned char client_key[hmethod->digest_size];
225
930
  unsigned char stored_key[hmethod->digest_size];
226
930
  unsigned char client_signature[hmethod->digest_size];
227
930
  unsigned char client_proof[hmethod->digest_size];
228
930
  unsigned char server_key[hmethod->digest_size];
229
930
  struct hmac_context ctx;
230
930
  const void *cbind_input;
231
930
  size_t cbind_input_size;
232
930
  string_t *auth_message, *str;
233
930
  unsigned int k;
234
235
  /* RFC 5802, Section 7:
236
237
     client-final-message-without-proof =
238
                       channel-binding "," nonce [","
239
                       extensions]
240
241
     channel-binding = "c=" base64
242
                       ;; base64 encoding of cbind-input.
243
244
     cbind-data      = 1*OCTET
245
     cbind-input     = gs2-header [ cbind-data ]
246
                       ;; cbind-data MUST be present for
247
                       ;; gs2-cbind-flag of "p" and MUST be absent
248
                       ;; for "y" or "n".
249
250
     nonce           = "r=" c-nonce [s-nonce]
251
                       ;; Second part provided by server.
252
     c-nonce         = printable
253
     s-nonce         = printable
254
   */
255
256
930
  if (client->gs2_header[0] != 'p') {
257
493
    i_assert(cbind_data == NULL);
258
493
    cbind_input = client->gs2_header;
259
493
    cbind_input_size = strlen(client->gs2_header);
260
493
  } else {
261
437
    size_t gs2_header_len = strlen(client->gs2_header);
262
437
    buffer_t *cbind_buf;
263
437
    i_assert(cbind_data != NULL);
264
437
    cbind_buf = t_buffer_create(gs2_header_len + cbind_data->used);
265
437
    buffer_append(cbind_buf, client->gs2_header, gs2_header_len);
266
437
    buffer_append_buf(cbind_buf, cbind_data, 0, SIZE_MAX);
267
437
    cbind_input = cbind_buf->data;
268
437
    cbind_input_size = cbind_buf->used;
269
437
  }
270
930
  str = t_str_new(256);
271
930
  str_append(str, "c=");
272
930
  base64_encode(cbind_input, cbind_input_size, str);
273
930
  str_append(str, ",r=");
274
930
  str_append(str, client->nonce);
275
276
  /* SaltedPassword  := Hi(Normalize(password), salt, i)
277
       FIXME: credentials should be SASLprepped UTF8 data here */
278
930
  auth_scram_hi(hmethod,
279
930
          (const unsigned char *)client->set.password,
280
930
          strlen(client->set.password),
281
930
          client->salt->data, client->salt->used,
282
930
          client->iter, salted_password);
283
284
  /* ClientKey       := HMAC(SaltedPassword, "Client Key") */
285
930
  hmac_init(&ctx, salted_password, sizeof(salted_password), hmethod);
286
930
  hmac_update(&ctx, "Client Key", 10);
287
930
  hmac_final(&ctx, client_key);
288
289
  /* StoredKey       := H(ClientKey) */
290
930
  hash_method_get_digest(hmethod, client_key, sizeof(client_key),
291
930
             stored_key);
292
293
  /* AuthMessage     := client-first-message-bare + "," +
294
                        server-first-message + "," +
295
                        client-final-message-without-proof
296
   */
297
930
  auth_message = t_str_new(512);
298
930
  str_append(auth_message, client->client_first_message_bare);
299
930
  str_append_c(auth_message, ',');
300
930
  str_append(auth_message, client->server_first_message);
301
930
  str_append_c(auth_message, ',');
302
930
  str_append_str(auth_message, str);
303
304
  /* ClientSignature := HMAC(StoredKey, AuthMessage) */
305
930
  hmac_init(&ctx, stored_key, sizeof(stored_key), hmethod);
306
930
  hmac_update(&ctx, str_data(auth_message), str_len(auth_message));
307
930
  hmac_final(&ctx, client_signature);
308
309
  /* ClientProof     := ClientKey XOR ClientSignature */
310
23.8k
  for (k = 0; k < hmethod->digest_size; k++)
311
22.8k
    client_proof[k] = client_key[k] ^ client_signature[k];
312
313
930
  safe_memset(client_key, 0, sizeof(client_key));
314
930
  safe_memset(stored_key, 0, sizeof(stored_key));
315
930
  safe_memset(client_signature, 0, sizeof(client_signature));
316
317
  /* ServerKey       := HMAC(SaltedPassword, "Server Key") */
318
930
  hmac_init(&ctx, salted_password, sizeof(salted_password), hmethod);
319
930
  hmac_update(&ctx, "Server Key", 10);
320
930
  hmac_final(&ctx, server_key);
321
322
  /* ServerSignature := HMAC(ServerKey, AuthMessage) */
323
930
  client->server_signature =
324
930
    p_malloc(client->pool, hmethod->digest_size);
325
930
  hmac_init(&ctx, server_key, sizeof(server_key), hmethod);
326
930
  hmac_update(&ctx, str_data(auth_message), str_len(auth_message));
327
930
  hmac_final(&ctx, client->server_signature);
328
329
930
  safe_memset(salted_password, 0, sizeof(salted_password));
330
331
  /* client-final-message =
332
                       client-final-message-without-proof "," proof
333
334
     proof           = "p=" base64
335
   */
336
930
  str_append(str, ",p=");
337
930
  base64_encode(client_proof, sizeof(client_proof), str);
338
339
930
  return str;
340
930
}
341
342
static int
343
auth_scram_parse_server_final(struct auth_scram_client *client,
344
            const unsigned char *input, size_t input_len,
345
            const char **error_r)
346
360
{
347
360
  const struct hash_method *hmethod = client->set.hash_method;
348
349
360
  i_assert(hmethod != NULL);
350
360
  i_assert(client->server_signature != NULL);
351
352
360
  const char **fields;
353
360
  unsigned int field_count;
354
360
  const char *error, *verifier;
355
360
  string_t *str;
356
357
  /* RFC 5802, Section 7:
358
359
     server-final-message = (server-error / verifier)
360
                       ["," extensions]
361
   */
362
363
360
  fields = t_strsplit(t_strndup(input, input_len), ",");
364
360
  field_count = str_array_length(fields);
365
360
  if (field_count < 1) {
366
1
    *error_r = "Invalid final server message";
367
1
    return -1;
368
1
  }
369
370
359
  error = fields[0];
371
359
  verifier = fields[0];
372
373
  /* server-error = "e=" server-error-value
374
   */
375
359
  if (error[0] == 'e' && error[1] == '=') {
376
10
    *error_r = t_strdup_printf("Server returned error value `%s'",
377
10
             &error[2]);
378
10
    return -1;
379
10
  }
380
381
  /* verifier        = "v=" base64
382
                       ;; base-64 encoded ServerSignature.
383
   */
384
349
  if (verifier[0] != 'v' || verifier[1] != '=') {
385
28
    *error_r = "Invalid verifier field in final server message";
386
28
    return -1;
387
28
  }
388
321
  verifier += 2;
389
390
321
  str = t_str_new(MAX_BASE64_ENCODED_SIZE(hmethod->digest_size));
391
321
  base64_encode(client->server_signature, hmethod->digest_size, str);
392
321
  safe_memset(client->server_signature, 0, hmethod->digest_size);
393
394
321
  bool equal = str_equals_timing_almost_safe(verifier, str_c(str));
395
321
  str_clear_safe(str);
396
397
321
  if (!equal) {
398
20
    *error_r = "Incorrect verifier field in final server message";
399
20
    return -1;
400
20
  }
401
301
  return 0;
402
321
}
403
404
int auth_scram_client_input(struct auth_scram_client *client,
405
          const unsigned char *input, size_t input_len,
406
          const char **error_r)
407
1.67k
{
408
1.67k
  int ret = 0;
409
410
1.67k
  switch (client->state) {
411
0
  case AUTH_SCRAM_CLIENT_STATE_INIT:
412
0
    break;
413
0
  case AUTH_SCRAM_CLIENT_STATE_CLIENT_FIRST:
414
0
    i_unreached();
415
1.31k
  case AUTH_SCRAM_CLIENT_STATE_SERVER_FIRST:
416
1.31k
    ret = auth_scram_parse_server_first(client, input, input_len,
417
1.31k
                error_r);
418
1.31k
    break;
419
0
  case AUTH_SCRAM_CLIENT_STATE_CLIENT_FINAL:
420
0
    i_unreached();
421
360
  case AUTH_SCRAM_CLIENT_STATE_SERVER_FINAL:
422
360
    ret = auth_scram_parse_server_final(client, input, input_len,
423
360
                error_r);
424
360
    break;
425
0
  case AUTH_SCRAM_CLIENT_STATE_CLIENT_FINISH:
426
0
    *error_r = "Server didn't finish authentication";
427
0
    ret = -1;
428
0
    break;
429
0
  case AUTH_SCRAM_CLIENT_STATE_END:
430
0
    i_unreached();
431
1.67k
  }
432
1.67k
  client->state++;
433
434
1.67k
  return ret;
435
1.67k
}
436
437
bool auth_scram_client_state_client_first(struct auth_scram_client *client)
438
0
{
439
0
  return (client->state <= AUTH_SCRAM_CLIENT_STATE_CLIENT_FIRST);
440
0
}
441
442
void auth_scram_client_output(struct auth_scram_client *client,
443
            const unsigned char **output_r,
444
            size_t *output_len_r)
445
2.57k
{
446
2.57k
  string_t *output;
447
448
2.57k
  switch (client->state) {
449
1.64k
  case AUTH_SCRAM_CLIENT_STATE_INIT:
450
1.64k
    client->state = AUTH_SCRAM_CLIENT_STATE_CLIENT_FIRST;
451
    /* Fall through */
452
1.64k
  case AUTH_SCRAM_CLIENT_STATE_CLIENT_FIRST:
453
1.64k
    output = auth_scram_get_client_first(client);
454
1.64k
    *output_r = str_data(output);
455
1.64k
    *output_len_r = str_len(output);
456
1.64k
    break;
457
0
  case AUTH_SCRAM_CLIENT_STATE_SERVER_FIRST:
458
0
    i_unreached();
459
930
  case AUTH_SCRAM_CLIENT_STATE_CLIENT_FINAL:
460
930
    output = auth_scram_get_client_final(client);
461
930
    *output_r = str_data(output);
462
930
    *output_len_r = str_len(output);
463
930
    break;
464
0
  case AUTH_SCRAM_CLIENT_STATE_SERVER_FINAL:
465
0
    i_unreached();
466
0
  case AUTH_SCRAM_CLIENT_STATE_CLIENT_FINISH:
467
0
    *output_r = uchar_empty_ptr;
468
0
    *output_len_r = 0;
469
0
    break;
470
0
  case AUTH_SCRAM_CLIENT_STATE_END:
471
0
    i_unreached();
472
2.57k
  }
473
2.57k
  client->state++;
474
2.57k
}