Coverage Report

Created: 2023-03-26 06:11

/src/curl/lib/vauth/digest.c
Line
Count
Source (jump to first uncovered line)
1
/***************************************************************************
2
 *                                  _   _ ____  _
3
 *  Project                     ___| | | |  _ \| |
4
 *                             / __| | | | |_) | |
5
 *                            | (__| |_| |  _ <| |___
6
 *                             \___|\___/|_| \_\_____|
7
 *
8
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9
 *
10
 * This software is licensed as described in the file COPYING, which
11
 * you should have received as part of this distribution. The terms
12
 * are also available at https://curl.se/docs/copyright.html.
13
 *
14
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15
 * copies of the Software, and permit persons to whom the Software is
16
 * furnished to do so, under the terms of the COPYING file.
17
 *
18
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19
 * KIND, either express or implied.
20
 *
21
 * SPDX-License-Identifier: curl
22
 *
23
 * RFC2831 DIGEST-MD5 authentication
24
 * RFC7616 DIGEST-SHA256, DIGEST-SHA512-256 authentication
25
 *
26
 ***************************************************************************/
27
28
#include "curl_setup.h"
29
30
#if !defined(CURL_DISABLE_CRYPTO_AUTH)
31
32
#include <curl/curl.h>
33
34
#include "vauth/vauth.h"
35
#include "vauth/digest.h"
36
#include "urldata.h"
37
#include "curl_base64.h"
38
#include "curl_hmac.h"
39
#include "curl_md5.h"
40
#include "curl_sha256.h"
41
#include "vtls/vtls.h"
42
#include "warnless.h"
43
#include "strtok.h"
44
#include "strcase.h"
45
#include "curl_printf.h"
46
#include "rand.h"
47
48
/* The last #include files should be: */
49
#include "curl_memory.h"
50
#include "memdebug.h"
51
52
0
#define SESSION_ALGO 1 /* for algos with this bit set */
53
54
2.29k
#define ALGO_MD5 0
55
0
#define ALGO_MD5SESS (ALGO_MD5 | SESSION_ALGO)
56
0
#define ALGO_SHA256 2
57
0
#define ALGO_SHA256SESS (ALGO_SHA256 | SESSION_ALGO)
58
0
#define ALGO_SHA512_256 4
59
0
#define ALGO_SHA512_256SESS (ALGO_SHA512_256 | SESSION_ALGO)
60
61
#if !defined(USE_WINDOWS_SSPI)
62
0
#define DIGEST_QOP_VALUE_AUTH             (1 << 0)
63
0
#define DIGEST_QOP_VALUE_AUTH_INT         (1 << 1)
64
0
#define DIGEST_QOP_VALUE_AUTH_CONF        (1 << 2)
65
66
0
#define DIGEST_QOP_VALUE_STRING_AUTH      "auth"
67
#define DIGEST_QOP_VALUE_STRING_AUTH_INT  "auth-int"
68
#define DIGEST_QOP_VALUE_STRING_AUTH_CONF "auth-conf"
69
#endif
70
71
bool Curl_auth_digest_get_pair(const char *str, char *value, char *content,
72
                               const char **endptr)
73
0
{
74
0
  int c;
75
0
  bool starts_with_quote = FALSE;
76
0
  bool escape = FALSE;
77
78
0
  for(c = DIGEST_MAX_VALUE_LENGTH - 1; (*str && (*str != '=') && c--);)
79
0
    *value++ = *str++;
80
0
  *value = 0;
81
82
0
  if('=' != *str++)
83
    /* eek, no match */
84
0
    return FALSE;
85
86
0
  if('\"' == *str) {
87
    /* This starts with a quote so it must end with one as well! */
88
0
    str++;
89
0
    starts_with_quote = TRUE;
90
0
  }
91
92
0
  for(c = DIGEST_MAX_CONTENT_LENGTH - 1; *str && c--; str++) {
93
0
    if(!escape) {
94
0
      switch(*str) {
95
0
      case '\\':
96
0
        if(starts_with_quote) {
97
          /* the start of an escaped quote */
98
0
          escape = TRUE;
99
0
          continue;
100
0
        }
101
0
        break;
102
103
0
      case ',':
104
0
        if(!starts_with_quote) {
105
          /* This signals the end of the content if we didn't get a starting
106
             quote and then we do "sloppy" parsing */
107
0
          c = 0; /* the end */
108
0
          continue;
109
0
        }
110
0
        break;
111
112
0
      case '\r':
113
0
      case '\n':
114
        /* end of string */
115
0
        if(starts_with_quote)
116
0
          return FALSE; /* No closing quote */
117
0
        c = 0;
118
0
        continue;
119
120
0
      case '\"':
121
0
        if(starts_with_quote) {
122
          /* end of string */
123
0
          c = 0;
124
0
          continue;
125
0
        }
126
0
        else
127
0
          return FALSE;
128
0
        break;
129
0
      }
130
0
    }
131
132
0
    escape = FALSE;
133
0
    *content++ = *str;
134
0
  }
135
0
  if(escape)
136
0
    return FALSE; /* No character after backslash */
137
138
0
  *content = 0;
139
0
  *endptr = str;
140
141
0
  return TRUE;
142
0
}
143
144
#if !defined(USE_WINDOWS_SSPI)
145
/* Convert md5 chunk to RFC2617 (section 3.1.3) -suitable ascii string */
146
static void auth_digest_md5_to_ascii(unsigned char *source, /* 16 bytes */
147
                                     unsigned char *dest) /* 33 bytes */
148
0
{
149
0
  int i;
150
0
  for(i = 0; i < 16; i++)
151
0
    msnprintf((char *) &dest[i * 2], 3, "%02x", source[i]);
152
0
}
153
154
/* Convert sha256 chunk to RFC7616 -suitable ascii string */
155
static void auth_digest_sha256_to_ascii(unsigned char *source, /* 32 bytes */
156
                                     unsigned char *dest) /* 65 bytes */
157
0
{
158
0
  int i;
159
0
  for(i = 0; i < 32; i++)
160
0
    msnprintf((char *) &dest[i * 2], 3, "%02x", source[i]);
161
0
}
162
163
/* Perform quoted-string escaping as described in RFC2616 and its errata */
164
static char *auth_digest_string_quoted(const char *source)
165
0
{
166
0
  char *dest;
167
0
  const char *s = source;
168
0
  size_t n = 1; /* null terminator */
169
170
  /* Calculate size needed */
171
0
  while(*s) {
172
0
    ++n;
173
0
    if(*s == '"' || *s == '\\') {
174
0
      ++n;
175
0
    }
176
0
    ++s;
177
0
  }
178
179
0
  dest = malloc(n);
180
0
  if(dest) {
181
0
    char *d = dest;
182
0
    s = source;
183
0
    while(*s) {
184
0
      if(*s == '"' || *s == '\\') {
185
0
        *d++ = '\\';
186
0
      }
187
0
      *d++ = *s++;
188
0
    }
189
0
    *d = '\0';
190
0
  }
191
192
0
  return dest;
193
0
}
194
195
/* Retrieves the value for a corresponding key from the challenge string
196
 * returns TRUE if the key could be found, FALSE if it does not exists
197
 */
198
static bool auth_digest_get_key_value(const char *chlg,
199
                                      const char *key,
200
                                      char *value,
201
                                      size_t max_val_len,
202
                                      char end_char)
203
0
{
204
0
  char *find_pos;
205
0
  size_t i;
206
207
0
  find_pos = strstr(chlg, key);
208
0
  if(!find_pos)
209
0
    return FALSE;
210
211
0
  find_pos += strlen(key);
212
213
0
  for(i = 0; *find_pos && *find_pos != end_char && i < max_val_len - 1; ++i)
214
0
    value[i] = *find_pos++;
215
0
  value[i] = '\0';
216
217
0
  return TRUE;
218
0
}
219
220
static CURLcode auth_digest_get_qop_values(const char *options, int *value)
221
0
{
222
0
  char *tmp;
223
0
  char *token;
224
0
  char *tok_buf = NULL;
225
226
  /* Initialise the output */
227
0
  *value = 0;
228
229
  /* Tokenise the list of qop values. Use a temporary clone of the buffer since
230
     strtok_r() ruins it. */
231
0
  tmp = strdup(options);
232
0
  if(!tmp)
233
0
    return CURLE_OUT_OF_MEMORY;
234
235
0
  token = strtok_r(tmp, ",", &tok_buf);
236
0
  while(token) {
237
0
    if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH))
238
0
      *value |= DIGEST_QOP_VALUE_AUTH;
239
0
    else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_INT))
240
0
      *value |= DIGEST_QOP_VALUE_AUTH_INT;
241
0
    else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_CONF))
242
0
      *value |= DIGEST_QOP_VALUE_AUTH_CONF;
243
244
0
    token = strtok_r(NULL, ",", &tok_buf);
245
0
  }
246
247
0
  free(tmp);
248
249
0
  return CURLE_OK;
250
0
}
251
252
/*
253
 * auth_decode_digest_md5_message()
254
 *
255
 * This is used internally to decode an already encoded DIGEST-MD5 challenge
256
 * message into the separate attributes.
257
 *
258
 * Parameters:
259
 *
260
 * chlgref [in]     - The challenge message.
261
 * nonce   [in/out] - The buffer where the nonce will be stored.
262
 * nlen    [in]     - The length of the nonce buffer.
263
 * realm   [in/out] - The buffer where the realm will be stored.
264
 * rlen    [in]     - The length of the realm buffer.
265
 * alg     [in/out] - The buffer where the algorithm will be stored.
266
 * alen    [in]     - The length of the algorithm buffer.
267
 * qop     [in/out] - The buffer where the qop-options will be stored.
268
 * qlen    [in]     - The length of the qop buffer.
269
 *
270
 * Returns CURLE_OK on success.
271
 */
272
static CURLcode auth_decode_digest_md5_message(const struct bufref *chlgref,
273
                                               char *nonce, size_t nlen,
274
                                               char *realm, size_t rlen,
275
                                               char *alg, size_t alen,
276
                                               char *qop, size_t qlen)
277
0
{
278
0
  const char *chlg = (const char *) Curl_bufref_ptr(chlgref);
279
280
  /* Ensure we have a valid challenge message */
281
0
  if(!Curl_bufref_len(chlgref))
282
0
    return CURLE_BAD_CONTENT_ENCODING;
283
284
  /* Retrieve nonce string from the challenge */
285
0
  if(!auth_digest_get_key_value(chlg, "nonce=\"", nonce, nlen, '\"'))
286
0
    return CURLE_BAD_CONTENT_ENCODING;
287
288
  /* Retrieve realm string from the challenge */
289
0
  if(!auth_digest_get_key_value(chlg, "realm=\"", realm, rlen, '\"')) {
290
    /* Challenge does not have a realm, set empty string [RFC2831] page 6 */
291
0
    strcpy(realm, "");
292
0
  }
293
294
  /* Retrieve algorithm string from the challenge */
295
0
  if(!auth_digest_get_key_value(chlg, "algorithm=", alg, alen, ','))
296
0
    return CURLE_BAD_CONTENT_ENCODING;
297
298
  /* Retrieve qop-options string from the challenge */
299
0
  if(!auth_digest_get_key_value(chlg, "qop=\"", qop, qlen, '\"'))
300
0
    return CURLE_BAD_CONTENT_ENCODING;
301
302
0
  return CURLE_OK;
303
0
}
304
305
/*
306
 * Curl_auth_is_digest_supported()
307
 *
308
 * This is used to evaluate if DIGEST is supported.
309
 *
310
 * Parameters: None
311
 *
312
 * Returns TRUE as DIGEST as handled by libcurl.
313
 */
314
bool Curl_auth_is_digest_supported(void)
315
0
{
316
0
  return TRUE;
317
0
}
318
319
/*
320
 * Curl_auth_create_digest_md5_message()
321
 *
322
 * This is used to generate an already encoded DIGEST-MD5 response message
323
 * ready for sending to the recipient.
324
 *
325
 * Parameters:
326
 *
327
 * data    [in]     - The session handle.
328
 * chlg    [in]     - The challenge message.
329
 * userp   [in]     - The user name.
330
 * passwdp [in]     - The user's password.
331
 * service [in]     - The service type such as http, smtp, pop or imap.
332
 * out     [out]    - The result storage.
333
 *
334
 * Returns CURLE_OK on success.
335
 */
336
CURLcode Curl_auth_create_digest_md5_message(struct Curl_easy *data,
337
                                             const struct bufref *chlg,
338
                                             const char *userp,
339
                                             const char *passwdp,
340
                                             const char *service,
341
                                             struct bufref *out)
342
0
{
343
0
  size_t i;
344
0
  struct MD5_context *ctxt;
345
0
  char *response = NULL;
346
0
  unsigned char digest[MD5_DIGEST_LEN];
347
0
  char HA1_hex[2 * MD5_DIGEST_LEN + 1];
348
0
  char HA2_hex[2 * MD5_DIGEST_LEN + 1];
349
0
  char resp_hash_hex[2 * MD5_DIGEST_LEN + 1];
350
0
  char nonce[64];
351
0
  char realm[128];
352
0
  char algorithm[64];
353
0
  char qop_options[64];
354
0
  int qop_values;
355
0
  char cnonce[33];
356
0
  char nonceCount[] = "00000001";
357
0
  char method[]     = "AUTHENTICATE";
358
0
  char qop[]        = DIGEST_QOP_VALUE_STRING_AUTH;
359
0
  char *spn         = NULL;
360
361
  /* Decode the challenge message */
362
0
  CURLcode result = auth_decode_digest_md5_message(chlg,
363
0
                                                   nonce, sizeof(nonce),
364
0
                                                   realm, sizeof(realm),
365
0
                                                   algorithm,
366
0
                                                   sizeof(algorithm),
367
0
                                                   qop_options,
368
0
                                                   sizeof(qop_options));
369
0
  if(result)
370
0
    return result;
371
372
  /* We only support md5 sessions */
373
0
  if(strcmp(algorithm, "md5-sess") != 0)
374
0
    return CURLE_BAD_CONTENT_ENCODING;
375
376
  /* Get the qop-values from the qop-options */
377
0
  result = auth_digest_get_qop_values(qop_options, &qop_values);
378
0
  if(result)
379
0
    return result;
380
381
  /* We only support auth quality-of-protection */
382
0
  if(!(qop_values & DIGEST_QOP_VALUE_AUTH))
383
0
    return CURLE_BAD_CONTENT_ENCODING;
384
385
  /* Generate 32 random hex chars, 32 bytes + 1 null-termination */
386
0
  result = Curl_rand_hex(data, (unsigned char *)cnonce, sizeof(cnonce));
387
0
  if(result)
388
0
    return result;
389
390
  /* So far so good, now calculate A1 and H(A1) according to RFC 2831 */
391
0
  ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
392
0
  if(!ctxt)
393
0
    return CURLE_OUT_OF_MEMORY;
394
395
0
  Curl_MD5_update(ctxt, (const unsigned char *) userp,
396
0
                  curlx_uztoui(strlen(userp)));
397
0
  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
398
0
  Curl_MD5_update(ctxt, (const unsigned char *) realm,
399
0
                  curlx_uztoui(strlen(realm)));
400
0
  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
401
0
  Curl_MD5_update(ctxt, (const unsigned char *) passwdp,
402
0
                  curlx_uztoui(strlen(passwdp)));
403
0
  Curl_MD5_final(ctxt, digest);
404
405
0
  ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
406
0
  if(!ctxt)
407
0
    return CURLE_OUT_OF_MEMORY;
408
409
0
  Curl_MD5_update(ctxt, (const unsigned char *) digest, MD5_DIGEST_LEN);
410
0
  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
411
0
  Curl_MD5_update(ctxt, (const unsigned char *) nonce,
412
0
                  curlx_uztoui(strlen(nonce)));
413
0
  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
414
0
  Curl_MD5_update(ctxt, (const unsigned char *) cnonce,
415
0
                  curlx_uztoui(strlen(cnonce)));
416
0
  Curl_MD5_final(ctxt, digest);
417
418
  /* Convert calculated 16 octet hex into 32 bytes string */
419
0
  for(i = 0; i < MD5_DIGEST_LEN; i++)
420
0
    msnprintf(&HA1_hex[2 * i], 3, "%02x", digest[i]);
421
422
  /* Generate our SPN */
423
0
  spn = Curl_auth_build_spn(service, realm, NULL);
424
0
  if(!spn)
425
0
    return CURLE_OUT_OF_MEMORY;
426
427
  /* Calculate H(A2) */
428
0
  ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
429
0
  if(!ctxt) {
430
0
    free(spn);
431
432
0
    return CURLE_OUT_OF_MEMORY;
433
0
  }
434
435
0
  Curl_MD5_update(ctxt, (const unsigned char *) method,
436
0
                  curlx_uztoui(strlen(method)));
437
0
  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
438
0
  Curl_MD5_update(ctxt, (const unsigned char *) spn,
439
0
                  curlx_uztoui(strlen(spn)));
440
0
  Curl_MD5_final(ctxt, digest);
441
442
0
  for(i = 0; i < MD5_DIGEST_LEN; i++)
443
0
    msnprintf(&HA2_hex[2 * i], 3, "%02x", digest[i]);
444
445
  /* Now calculate the response hash */
446
0
  ctxt = Curl_MD5_init(Curl_DIGEST_MD5);
447
0
  if(!ctxt) {
448
0
    free(spn);
449
450
0
    return CURLE_OUT_OF_MEMORY;
451
0
  }
452
453
0
  Curl_MD5_update(ctxt, (const unsigned char *) HA1_hex, 2 * MD5_DIGEST_LEN);
454
0
  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
455
0
  Curl_MD5_update(ctxt, (const unsigned char *) nonce,
456
0
                  curlx_uztoui(strlen(nonce)));
457
0
  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
458
459
0
  Curl_MD5_update(ctxt, (const unsigned char *) nonceCount,
460
0
                  curlx_uztoui(strlen(nonceCount)));
461
0
  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
462
0
  Curl_MD5_update(ctxt, (const unsigned char *) cnonce,
463
0
                  curlx_uztoui(strlen(cnonce)));
464
0
  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
465
0
  Curl_MD5_update(ctxt, (const unsigned char *) qop,
466
0
                  curlx_uztoui(strlen(qop)));
467
0
  Curl_MD5_update(ctxt, (const unsigned char *) ":", 1);
468
469
0
  Curl_MD5_update(ctxt, (const unsigned char *) HA2_hex, 2 * MD5_DIGEST_LEN);
470
0
  Curl_MD5_final(ctxt, digest);
471
472
0
  for(i = 0; i < MD5_DIGEST_LEN; i++)
473
0
    msnprintf(&resp_hash_hex[2 * i], 3, "%02x", digest[i]);
474
475
  /* Generate the response */
476
0
  response = aprintf("username=\"%s\",realm=\"%s\",nonce=\"%s\","
477
0
                     "cnonce=\"%s\",nc=\"%s\",digest-uri=\"%s\",response=%s,"
478
0
                     "qop=%s",
479
0
                     userp, realm, nonce,
480
0
                     cnonce, nonceCount, spn, resp_hash_hex, qop);
481
0
  free(spn);
482
0
  if(!response)
483
0
    return CURLE_OUT_OF_MEMORY;
484
485
  /* Return the response. */
486
0
  Curl_bufref_set(out, response, strlen(response), curl_free);
487
0
  return result;
488
0
}
489
490
/*
491
 * Curl_auth_decode_digest_http_message()
492
 *
493
 * This is used to decode an HTTP DIGEST challenge message into the separate
494
 * attributes.
495
 *
496
 * Parameters:
497
 *
498
 * chlg    [in]     - The challenge message.
499
 * digest  [in/out] - The digest data struct being used and modified.
500
 *
501
 * Returns CURLE_OK on success.
502
 */
503
CURLcode Curl_auth_decode_digest_http_message(const char *chlg,
504
                                              struct digestdata *digest)
505
0
{
506
0
  bool before = FALSE; /* got a nonce before */
507
0
  bool foundAuth = FALSE;
508
0
  bool foundAuthInt = FALSE;
509
0
  char *token = NULL;
510
0
  char *tmp = NULL;
511
512
  /* If we already have received a nonce, keep that in mind */
513
0
  if(digest->nonce)
514
0
    before = TRUE;
515
516
  /* Clean up any former leftovers and initialise to defaults */
517
0
  Curl_auth_digest_cleanup(digest);
518
519
0
  for(;;) {
520
0
    char value[DIGEST_MAX_VALUE_LENGTH];
521
0
    char content[DIGEST_MAX_CONTENT_LENGTH];
522
523
    /* Pass all additional spaces here */
524
0
    while(*chlg && ISBLANK(*chlg))
525
0
      chlg++;
526
527
    /* Extract a value=content pair */
528
0
    if(Curl_auth_digest_get_pair(chlg, value, content, &chlg)) {
529
0
      if(strcasecompare(value, "nonce")) {
530
0
        free(digest->nonce);
531
0
        digest->nonce = strdup(content);
532
0
        if(!digest->nonce)
533
0
          return CURLE_OUT_OF_MEMORY;
534
0
      }
535
0
      else if(strcasecompare(value, "stale")) {
536
0
        if(strcasecompare(content, "true")) {
537
0
          digest->stale = TRUE;
538
0
          digest->nc = 1; /* we make a new nonce now */
539
0
        }
540
0
      }
541
0
      else if(strcasecompare(value, "realm")) {
542
0
        free(digest->realm);
543
0
        digest->realm = strdup(content);
544
0
        if(!digest->realm)
545
0
          return CURLE_OUT_OF_MEMORY;
546
0
      }
547
0
      else if(strcasecompare(value, "opaque")) {
548
0
        free(digest->opaque);
549
0
        digest->opaque = strdup(content);
550
0
        if(!digest->opaque)
551
0
          return CURLE_OUT_OF_MEMORY;
552
0
      }
553
0
      else if(strcasecompare(value, "qop")) {
554
0
        char *tok_buf = NULL;
555
        /* Tokenize the list and choose auth if possible, use a temporary
556
           clone of the buffer since strtok_r() ruins it */
557
0
        tmp = strdup(content);
558
0
        if(!tmp)
559
0
          return CURLE_OUT_OF_MEMORY;
560
561
0
        token = strtok_r(tmp, ",", &tok_buf);
562
0
        while(token) {
563
          /* Pass additional spaces here */
564
0
          while(*token && ISBLANK(*token))
565
0
            token++;
566
0
          if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH)) {
567
0
            foundAuth = TRUE;
568
0
          }
569
0
          else if(strcasecompare(token, DIGEST_QOP_VALUE_STRING_AUTH_INT)) {
570
0
            foundAuthInt = TRUE;
571
0
          }
572
0
          token = strtok_r(NULL, ",", &tok_buf);
573
0
        }
574
575
0
        free(tmp);
576
577
        /* Select only auth or auth-int. Otherwise, ignore */
578
0
        if(foundAuth) {
579
0
          free(digest->qop);
580
0
          digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH);
581
0
          if(!digest->qop)
582
0
            return CURLE_OUT_OF_MEMORY;
583
0
        }
584
0
        else if(foundAuthInt) {
585
0
          free(digest->qop);
586
0
          digest->qop = strdup(DIGEST_QOP_VALUE_STRING_AUTH_INT);
587
0
          if(!digest->qop)
588
0
            return CURLE_OUT_OF_MEMORY;
589
0
        }
590
0
      }
591
0
      else if(strcasecompare(value, "algorithm")) {
592
0
        free(digest->algorithm);
593
0
        digest->algorithm = strdup(content);
594
0
        if(!digest->algorithm)
595
0
          return CURLE_OUT_OF_MEMORY;
596
597
0
        if(strcasecompare(content, "MD5-sess"))
598
0
          digest->algo = ALGO_MD5SESS;
599
0
        else if(strcasecompare(content, "MD5"))
600
0
          digest->algo = ALGO_MD5;
601
0
        else if(strcasecompare(content, "SHA-256"))
602
0
          digest->algo = ALGO_SHA256;
603
0
        else if(strcasecompare(content, "SHA-256-SESS"))
604
0
          digest->algo = ALGO_SHA256SESS;
605
0
        else if(strcasecompare(content, "SHA-512-256"))
606
0
          digest->algo = ALGO_SHA512_256;
607
0
        else if(strcasecompare(content, "SHA-512-256-SESS"))
608
0
          digest->algo = ALGO_SHA512_256SESS;
609
0
        else
610
0
          return CURLE_BAD_CONTENT_ENCODING;
611
0
      }
612
0
      else if(strcasecompare(value, "userhash")) {
613
0
        if(strcasecompare(content, "true")) {
614
0
          digest->userhash = TRUE;
615
0
        }
616
0
      }
617
0
      else {
618
        /* Unknown specifier, ignore it! */
619
0
      }
620
0
    }
621
0
    else
622
0
      break; /* We're done here */
623
624
    /* Pass all additional spaces here */
625
0
    while(*chlg && ISBLANK(*chlg))
626
0
      chlg++;
627
628
    /* Allow the list to be comma-separated */
629
0
    if(',' == *chlg)
630
0
      chlg++;
631
0
  }
632
633
  /* We had a nonce since before, and we got another one now without
634
     'stale=true'. This means we provided bad credentials in the previous
635
     request */
636
0
  if(before && !digest->stale)
637
0
    return CURLE_BAD_CONTENT_ENCODING;
638
639
  /* We got this header without a nonce, that's a bad Digest line! */
640
0
  if(!digest->nonce)
641
0
    return CURLE_BAD_CONTENT_ENCODING;
642
643
  /* "<algo>-sess" protocol versions require "auth" or "auth-int" qop */
644
0
  if(!digest->qop && (digest->algo & SESSION_ALGO))
645
0
    return CURLE_BAD_CONTENT_ENCODING;
646
647
0
  return CURLE_OK;
648
0
}
649
650
/*
651
 * auth_create_digest_http_message()
652
 *
653
 * This is used to generate an HTTP DIGEST response message ready for sending
654
 * to the recipient.
655
 *
656
 * Parameters:
657
 *
658
 * data    [in]     - The session handle.
659
 * userp   [in]     - The user name.
660
 * passwdp [in]     - The user's password.
661
 * request [in]     - The HTTP request.
662
 * uripath [in]     - The path of the HTTP uri.
663
 * digest  [in/out] - The digest data struct being used and modified.
664
 * outptr  [in/out] - The address where a pointer to newly allocated memory
665
 *                    holding the result will be stored upon completion.
666
 * outlen  [out]    - The length of the output message.
667
 *
668
 * Returns CURLE_OK on success.
669
 */
670
static CURLcode auth_create_digest_http_message(
671
                  struct Curl_easy *data,
672
                  const char *userp,
673
                  const char *passwdp,
674
                  const unsigned char *request,
675
                  const unsigned char *uripath,
676
                  struct digestdata *digest,
677
                  char **outptr, size_t *outlen,
678
                  void (*convert_to_ascii)(unsigned char *, unsigned char *),
679
                  CURLcode (*hash)(unsigned char *, const unsigned char *,
680
                                   const size_t))
681
0
{
682
0
  CURLcode result;
683
0
  unsigned char hashbuf[32]; /* 32 bytes/256 bits */
684
0
  unsigned char request_digest[65];
685
0
  unsigned char ha1[65];    /* 64 digits and 1 zero byte */
686
0
  unsigned char ha2[65];    /* 64 digits and 1 zero byte */
687
0
  char userh[65];
688
0
  char *cnonce = NULL;
689
0
  size_t cnonce_sz = 0;
690
0
  char *userp_quoted;
691
0
  char *realm_quoted;
692
0
  char *nonce_quoted;
693
0
  char *response = NULL;
694
0
  char *hashthis = NULL;
695
0
  char *tmp = NULL;
696
697
0
  memset(hashbuf, 0, sizeof(hashbuf));
698
0
  if(!digest->nc)
699
0
    digest->nc = 1;
700
701
0
  if(!digest->cnonce) {
702
0
    char cnoncebuf[33];
703
0
    result = Curl_rand_hex(data, (unsigned char *)cnoncebuf,
704
0
                           sizeof(cnoncebuf));
705
0
    if(result)
706
0
      return result;
707
708
0
    result = Curl_base64_encode(cnoncebuf, strlen(cnoncebuf),
709
0
                                &cnonce, &cnonce_sz);
710
0
    if(result)
711
0
      return result;
712
713
0
    digest->cnonce = cnonce;
714
0
  }
715
716
0
  if(digest->userhash) {
717
0
    hashthis = aprintf("%s:%s", userp, digest->realm ? digest->realm : "");
718
0
    if(!hashthis)
719
0
      return CURLE_OUT_OF_MEMORY;
720
721
0
    hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis));
722
0
    free(hashthis);
723
0
    convert_to_ascii(hashbuf, (unsigned char *)userh);
724
0
  }
725
726
  /*
727
    If the algorithm is "MD5" or unspecified (which then defaults to MD5):
728
729
      A1 = unq(username-value) ":" unq(realm-value) ":" passwd
730
731
    If the algorithm is "MD5-sess" then:
732
733
      A1 = H(unq(username-value) ":" unq(realm-value) ":" passwd) ":"
734
           unq(nonce-value) ":" unq(cnonce-value)
735
  */
736
737
0
  hashthis = aprintf("%s:%s:%s", userp, digest->realm ? digest->realm : "",
738
0
                     passwdp);
739
0
  if(!hashthis)
740
0
    return CURLE_OUT_OF_MEMORY;
741
742
0
  hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis));
743
0
  free(hashthis);
744
0
  convert_to_ascii(hashbuf, ha1);
745
746
0
  if(digest->algo & SESSION_ALGO) {
747
    /* nonce and cnonce are OUTSIDE the hash */
748
0
    tmp = aprintf("%s:%s:%s", ha1, digest->nonce, digest->cnonce);
749
0
    if(!tmp)
750
0
      return CURLE_OUT_OF_MEMORY;
751
752
0
    hash(hashbuf, (unsigned char *) tmp, strlen(tmp));
753
0
    free(tmp);
754
0
    convert_to_ascii(hashbuf, ha1);
755
0
  }
756
757
  /*
758
    If the "qop" directive's value is "auth" or is unspecified, then A2 is:
759
760
      A2 = Method ":" digest-uri-value
761
762
    If the "qop" value is "auth-int", then A2 is:
763
764
      A2 = Method ":" digest-uri-value ":" H(entity-body)
765
766
    (The "Method" value is the HTTP request method as specified in section
767
    5.1.1 of RFC 2616)
768
  */
769
770
0
  hashthis = aprintf("%s:%s", request, uripath);
771
0
  if(!hashthis)
772
0
    return CURLE_OUT_OF_MEMORY;
773
774
0
  if(digest->qop && strcasecompare(digest->qop, "auth-int")) {
775
    /* We don't support auth-int for PUT or POST */
776
0
    char hashed[65];
777
0
    char *hashthis2;
778
779
0
    hash(hashbuf, (const unsigned char *)"", 0);
780
0
    convert_to_ascii(hashbuf, (unsigned char *)hashed);
781
782
0
    hashthis2 = aprintf("%s:%s", hashthis, hashed);
783
0
    free(hashthis);
784
0
    hashthis = hashthis2;
785
0
  }
786
787
0
  if(!hashthis)
788
0
    return CURLE_OUT_OF_MEMORY;
789
790
0
  hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis));
791
0
  free(hashthis);
792
0
  convert_to_ascii(hashbuf, ha2);
793
794
0
  if(digest->qop) {
795
0
    hashthis = aprintf("%s:%s:%08x:%s:%s:%s", ha1, digest->nonce, digest->nc,
796
0
                       digest->cnonce, digest->qop, ha2);
797
0
  }
798
0
  else {
799
0
    hashthis = aprintf("%s:%s:%s", ha1, digest->nonce, ha2);
800
0
  }
801
802
0
  if(!hashthis)
803
0
    return CURLE_OUT_OF_MEMORY;
804
805
0
  hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis));
806
0
  free(hashthis);
807
0
  convert_to_ascii(hashbuf, request_digest);
808
809
  /* For test case 64 (snooped from a Mozilla 1.3a request)
810
811
     Authorization: Digest username="testuser", realm="testrealm", \
812
     nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca"
813
814
     Digest parameters are all quoted strings.  Username which is provided by
815
     the user will need double quotes and backslashes within it escaped.
816
     realm, nonce, and opaque will need backslashes as well as they were
817
     de-escaped when copied from request header.  cnonce is generated with
818
     web-safe characters.  uri is already percent encoded.  nc is 8 hex
819
     characters.  algorithm and qop with standard values only contain web-safe
820
     characters.
821
  */
822
0
  userp_quoted = auth_digest_string_quoted(digest->userhash ? userh : userp);
823
0
  if(!userp_quoted)
824
0
    return CURLE_OUT_OF_MEMORY;
825
0
  if(digest->realm)
826
0
    realm_quoted = auth_digest_string_quoted(digest->realm);
827
0
  else {
828
0
    realm_quoted = malloc(1);
829
0
    if(realm_quoted)
830
0
      realm_quoted[0] = 0;
831
0
  }
832
0
  if(!realm_quoted) {
833
0
    free(userp_quoted);
834
0
    return CURLE_OUT_OF_MEMORY;
835
0
  }
836
0
  nonce_quoted = auth_digest_string_quoted(digest->nonce);
837
0
  if(!nonce_quoted) {
838
0
    free(realm_quoted);
839
0
    free(userp_quoted);
840
0
    return CURLE_OUT_OF_MEMORY;
841
0
  }
842
843
0
  if(digest->qop) {
844
0
    response = aprintf("username=\"%s\", "
845
0
                       "realm=\"%s\", "
846
0
                       "nonce=\"%s\", "
847
0
                       "uri=\"%s\", "
848
0
                       "cnonce=\"%s\", "
849
0
                       "nc=%08x, "
850
0
                       "qop=%s, "
851
0
                       "response=\"%s\"",
852
0
                       userp_quoted,
853
0
                       realm_quoted,
854
0
                       nonce_quoted,
855
0
                       uripath,
856
0
                       digest->cnonce,
857
0
                       digest->nc,
858
0
                       digest->qop,
859
0
                       request_digest);
860
861
    /* Increment nonce-count to use another nc value for the next request */
862
0
    digest->nc++;
863
0
  }
864
0
  else {
865
0
    response = aprintf("username=\"%s\", "
866
0
                       "realm=\"%s\", "
867
0
                       "nonce=\"%s\", "
868
0
                       "uri=\"%s\", "
869
0
                       "response=\"%s\"",
870
0
                       userp_quoted,
871
0
                       realm_quoted,
872
0
                       nonce_quoted,
873
0
                       uripath,
874
0
                       request_digest);
875
0
  }
876
0
  free(nonce_quoted);
877
0
  free(realm_quoted);
878
0
  free(userp_quoted);
879
0
  if(!response)
880
0
    return CURLE_OUT_OF_MEMORY;
881
882
  /* Add the optional fields */
883
0
  if(digest->opaque) {
884
0
    char *opaque_quoted;
885
    /* Append the opaque */
886
0
    opaque_quoted = auth_digest_string_quoted(digest->opaque);
887
0
    if(!opaque_quoted) {
888
0
      free(response);
889
0
      return CURLE_OUT_OF_MEMORY;
890
0
    }
891
0
    tmp = aprintf("%s, opaque=\"%s\"", response, opaque_quoted);
892
0
    free(response);
893
0
    free(opaque_quoted);
894
0
    if(!tmp)
895
0
      return CURLE_OUT_OF_MEMORY;
896
897
0
    response = tmp;
898
0
  }
899
900
0
  if(digest->algorithm) {
901
    /* Append the algorithm */
902
0
    tmp = aprintf("%s, algorithm=%s", response, digest->algorithm);
903
0
    free(response);
904
0
    if(!tmp)
905
0
      return CURLE_OUT_OF_MEMORY;
906
907
0
    response = tmp;
908
0
  }
909
910
0
  if(digest->userhash) {
911
    /* Append the userhash */
912
0
    tmp = aprintf("%s, userhash=true", response);
913
0
    free(response);
914
0
    if(!tmp)
915
0
      return CURLE_OUT_OF_MEMORY;
916
917
0
    response = tmp;
918
0
  }
919
920
  /* Return the output */
921
0
  *outptr = response;
922
0
  *outlen = strlen(response);
923
924
0
  return CURLE_OK;
925
0
}
926
927
/*
928
 * Curl_auth_create_digest_http_message()
929
 *
930
 * This is used to generate an HTTP DIGEST response message ready for sending
931
 * to the recipient.
932
 *
933
 * Parameters:
934
 *
935
 * data    [in]     - The session handle.
936
 * userp   [in]     - The user name.
937
 * passwdp [in]     - The user's password.
938
 * request [in]     - The HTTP request.
939
 * uripath [in]     - The path of the HTTP uri.
940
 * digest  [in/out] - The digest data struct being used and modified.
941
 * outptr  [in/out] - The address where a pointer to newly allocated memory
942
 *                    holding the result will be stored upon completion.
943
 * outlen  [out]    - The length of the output message.
944
 *
945
 * Returns CURLE_OK on success.
946
 */
947
CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data,
948
                                              const char *userp,
949
                                              const char *passwdp,
950
                                              const unsigned char *request,
951
                                              const unsigned char *uripath,
952
                                              struct digestdata *digest,
953
                                              char **outptr, size_t *outlen)
954
0
{
955
0
  if(digest->algo <= ALGO_MD5SESS)
956
0
    return auth_create_digest_http_message(data, userp, passwdp,
957
0
                                           request, uripath, digest,
958
0
                                           outptr, outlen,
959
0
                                           auth_digest_md5_to_ascii,
960
0
                                           Curl_md5it);
961
0
  DEBUGASSERT(digest->algo <= ALGO_SHA512_256SESS);
962
0
  return auth_create_digest_http_message(data, userp, passwdp,
963
0
                                         request, uripath, digest,
964
0
                                         outptr, outlen,
965
0
                                         auth_digest_sha256_to_ascii,
966
0
                                         Curl_sha256it);
967
0
}
968
969
/*
970
 * Curl_auth_digest_cleanup()
971
 *
972
 * This is used to clean up the digest specific data.
973
 *
974
 * Parameters:
975
 *
976
 * digest    [in/out] - The digest data struct being cleaned up.
977
 *
978
 */
979
void Curl_auth_digest_cleanup(struct digestdata *digest)
980
2.29k
{
981
2.29k
  Curl_safefree(digest->nonce);
982
2.29k
  Curl_safefree(digest->cnonce);
983
2.29k
  Curl_safefree(digest->realm);
984
2.29k
  Curl_safefree(digest->opaque);
985
2.29k
  Curl_safefree(digest->qop);
986
2.29k
  Curl_safefree(digest->algorithm);
987
988
2.29k
  digest->nc = 0;
989
2.29k
  digest->algo = ALGO_MD5; /* default algorithm */
990
2.29k
  digest->stale = FALSE; /* default means normal, not stale */
991
2.29k
  digest->userhash = FALSE;
992
2.29k
}
993
#endif  /* !USE_WINDOWS_SSPI */
994
995
#endif  /* CURL_DISABLE_CRYPTO_AUTH */