Coverage Report

Created: 2024-02-25 06:14

/src/PROJ/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
#ifndef CURL_DISABLE_DIGEST_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 "curl_sha512_256.h"
42
#include "vtls/vtls.h"
43
#include "warnless.h"
44
#include "strtok.h"
45
#include "strcase.h"
46
#include "curl_printf.h"
47
#include "rand.h"
48
49
/* The last #include files should be: */
50
#include "curl_memory.h"
51
#include "memdebug.h"
52
53
0
#define SESSION_ALGO 1 /* for algos with this bit set */
54
55
0
#define ALGO_MD5 0
56
0
#define ALGO_MD5SESS (ALGO_MD5 | SESSION_ALGO)
57
0
#define ALGO_SHA256 2
58
0
#define ALGO_SHA256SESS (ALGO_SHA256 | SESSION_ALGO)
59
0
#define ALGO_SHA512_256 4
60
0
#define ALGO_SHA512_256SESS (ALGO_SHA512_256 | SESSION_ALGO)
61
62
#if !defined(USE_WINDOWS_SSPI)
63
0
#define DIGEST_QOP_VALUE_AUTH             (1 << 0)
64
0
#define DIGEST_QOP_VALUE_AUTH_INT         (1 << 1)
65
0
#define DIGEST_QOP_VALUE_AUTH_CONF        (1 << 2)
66
67
0
#define DIGEST_QOP_VALUE_STRING_AUTH      "auth"
68
#define DIGEST_QOP_VALUE_STRING_AUTH_INT  "auth-int"
69
#define DIGEST_QOP_VALUE_STRING_AUTH_CONF "auth-conf"
70
#endif
71
72
bool Curl_auth_digest_get_pair(const char *str, char *value, char *content,
73
                               const char **endptr)
74
0
{
75
0
  int c;
76
0
  bool starts_with_quote = FALSE;
77
0
  bool escape = FALSE;
78
79
0
  for(c = DIGEST_MAX_VALUE_LENGTH - 1; (*str && (*str != '=') && c--);)
80
0
    *value++ = *str++;
81
0
  *value = 0;
82
83
0
  if('=' != *str++)
84
    /* eek, no match */
85
0
    return FALSE;
86
87
0
  if('\"' == *str) {
88
    /* This starts with a quote so it must end with one as well! */
89
0
    str++;
90
0
    starts_with_quote = TRUE;
91
0
  }
92
93
0
  for(c = DIGEST_MAX_CONTENT_LENGTH - 1; *str && c--; str++) {
94
0
    if(!escape) {
95
0
      switch(*str) {
96
0
      case '\\':
97
0
        if(starts_with_quote) {
98
          /* the start of an escaped quote */
99
0
          escape = TRUE;
100
0
          continue;
101
0
        }
102
0
        break;
103
104
0
      case ',':
105
0
        if(!starts_with_quote) {
106
          /* This signals the end of the content if we didn't get a starting
107
             quote and then we do "sloppy" parsing */
108
0
          c = 0; /* the end */
109
0
          continue;
110
0
        }
111
0
        break;
112
113
0
      case '\r':
114
0
      case '\n':
115
        /* end of string */
116
0
        if(starts_with_quote)
117
0
          return FALSE; /* No closing quote */
118
0
        c = 0;
119
0
        continue;
120
121
0
      case '\"':
122
0
        if(starts_with_quote) {
123
          /* end of string */
124
0
          c = 0;
125
0
          continue;
126
0
        }
127
0
        else
128
0
          return FALSE;
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 or SHA-512/256 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, data->conn->host.name, 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
#ifdef CURL_HAVE_SHA512_256
607
0
          digest->algo = ALGO_SHA512_256;
608
#else  /* ! CURL_HAVE_SHA512_256 */
609
          return CURLE_NOT_BUILT_IN;
610
#endif /* ! CURL_HAVE_SHA512_256 */
611
0
        }
612
0
        else if(strcasecompare(content, "SHA-512-256-SESS")) {
613
0
#ifdef CURL_HAVE_SHA512_256
614
0
          digest->algo = ALGO_SHA512_256SESS;
615
#else  /* ! CURL_HAVE_SHA512_256 */
616
          return CURLE_NOT_BUILT_IN;
617
#endif /* ! CURL_HAVE_SHA512_256 */
618
0
        }
619
0
        else
620
0
          return CURLE_BAD_CONTENT_ENCODING;
621
0
      }
622
0
      else if(strcasecompare(value, "userhash")) {
623
0
        if(strcasecompare(content, "true")) {
624
0
          digest->userhash = TRUE;
625
0
        }
626
0
      }
627
0
      else {
628
        /* Unknown specifier, ignore it! */
629
0
      }
630
0
    }
631
0
    else
632
0
      break; /* We're done here */
633
634
    /* Pass all additional spaces here */
635
0
    while(*chlg && ISBLANK(*chlg))
636
0
      chlg++;
637
638
    /* Allow the list to be comma-separated */
639
0
    if(',' == *chlg)
640
0
      chlg++;
641
0
  }
642
643
  /* We had a nonce since before, and we got another one now without
644
     'stale=true'. This means we provided bad credentials in the previous
645
     request */
646
0
  if(before && !digest->stale)
647
0
    return CURLE_BAD_CONTENT_ENCODING;
648
649
  /* We got this header without a nonce, that's a bad Digest line! */
650
0
  if(!digest->nonce)
651
0
    return CURLE_BAD_CONTENT_ENCODING;
652
653
  /* "<algo>-sess" protocol versions require "auth" or "auth-int" qop */
654
0
  if(!digest->qop && (digest->algo & SESSION_ALGO))
655
0
    return CURLE_BAD_CONTENT_ENCODING;
656
657
0
  return CURLE_OK;
658
0
}
659
660
/*
661
 * auth_create_digest_http_message()
662
 *
663
 * This is used to generate an HTTP DIGEST response message ready for sending
664
 * to the recipient.
665
 *
666
 * Parameters:
667
 *
668
 * data    [in]     - The session handle.
669
 * userp   [in]     - The user name.
670
 * passwdp [in]     - The user's password.
671
 * request [in]     - The HTTP request.
672
 * uripath [in]     - The path of the HTTP uri.
673
 * digest  [in/out] - The digest data struct being used and modified.
674
 * outptr  [in/out] - The address where a pointer to newly allocated memory
675
 *                    holding the result will be stored upon completion.
676
 * outlen  [out]    - The length of the output message.
677
 *
678
 * Returns CURLE_OK on success.
679
 */
680
static CURLcode auth_create_digest_http_message(
681
                  struct Curl_easy *data,
682
                  const char *userp,
683
                  const char *passwdp,
684
                  const unsigned char *request,
685
                  const unsigned char *uripath,
686
                  struct digestdata *digest,
687
                  char **outptr, size_t *outlen,
688
                  void (*convert_to_ascii)(unsigned char *, unsigned char *),
689
                  CURLcode (*hash)(unsigned char *, const unsigned char *,
690
                                   const size_t))
691
0
{
692
0
  CURLcode result;
693
0
  unsigned char hashbuf[32]; /* 32 bytes/256 bits */
694
0
  unsigned char request_digest[65];
695
0
  unsigned char ha1[65];    /* 64 digits and 1 zero byte */
696
0
  unsigned char ha2[65];    /* 64 digits and 1 zero byte */
697
0
  char userh[65];
698
0
  char *cnonce = NULL;
699
0
  size_t cnonce_sz = 0;
700
0
  char *userp_quoted;
701
0
  char *realm_quoted;
702
0
  char *nonce_quoted;
703
0
  char *response = NULL;
704
0
  char *hashthis = NULL;
705
0
  char *tmp = NULL;
706
707
0
  memset(hashbuf, 0, sizeof(hashbuf));
708
0
  if(!digest->nc)
709
0
    digest->nc = 1;
710
711
0
  if(!digest->cnonce) {
712
0
    char cnoncebuf[33];
713
0
    result = Curl_rand_hex(data, (unsigned char *)cnoncebuf,
714
0
                           sizeof(cnoncebuf));
715
0
    if(result)
716
0
      return result;
717
718
0
    result = Curl_base64_encode(cnoncebuf, strlen(cnoncebuf),
719
0
                                &cnonce, &cnonce_sz);
720
0
    if(result)
721
0
      return result;
722
723
0
    digest->cnonce = cnonce;
724
0
  }
725
726
0
  if(digest->userhash) {
727
0
    hashthis = aprintf("%s:%s", userp, digest->realm ? digest->realm : "");
728
0
    if(!hashthis)
729
0
      return CURLE_OUT_OF_MEMORY;
730
731
0
    hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis));
732
0
    free(hashthis);
733
0
    convert_to_ascii(hashbuf, (unsigned char *)userh);
734
0
  }
735
736
  /*
737
    If the algorithm is "MD5" or unspecified (which then defaults to MD5):
738
739
      A1 = unq(username-value) ":" unq(realm-value) ":" passwd
740
741
    If the algorithm is "MD5-sess" then:
742
743
      A1 = H(unq(username-value) ":" unq(realm-value) ":" passwd) ":"
744
           unq(nonce-value) ":" unq(cnonce-value)
745
  */
746
747
0
  hashthis = aprintf("%s:%s:%s", userp, digest->realm ? digest->realm : "",
748
0
                     passwdp);
749
0
  if(!hashthis)
750
0
    return CURLE_OUT_OF_MEMORY;
751
752
0
  hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis));
753
0
  free(hashthis);
754
0
  convert_to_ascii(hashbuf, ha1);
755
756
0
  if(digest->algo & SESSION_ALGO) {
757
    /* nonce and cnonce are OUTSIDE the hash */
758
0
    tmp = aprintf("%s:%s:%s", ha1, digest->nonce, digest->cnonce);
759
0
    if(!tmp)
760
0
      return CURLE_OUT_OF_MEMORY;
761
762
0
    hash(hashbuf, (unsigned char *) tmp, strlen(tmp));
763
0
    free(tmp);
764
0
    convert_to_ascii(hashbuf, ha1);
765
0
  }
766
767
  /*
768
    If the "qop" directive's value is "auth" or is unspecified, then A2 is:
769
770
      A2 = Method ":" digest-uri-value
771
772
    If the "qop" value is "auth-int", then A2 is:
773
774
      A2 = Method ":" digest-uri-value ":" H(entity-body)
775
776
    (The "Method" value is the HTTP request method as specified in section
777
    5.1.1 of RFC 2616)
778
  */
779
780
0
  hashthis = aprintf("%s:%s", request, uripath);
781
0
  if(!hashthis)
782
0
    return CURLE_OUT_OF_MEMORY;
783
784
0
  if(digest->qop && strcasecompare(digest->qop, "auth-int")) {
785
    /* We don't support auth-int for PUT or POST */
786
0
    char hashed[65];
787
0
    char *hashthis2;
788
789
0
    hash(hashbuf, (const unsigned char *)"", 0);
790
0
    convert_to_ascii(hashbuf, (unsigned char *)hashed);
791
792
0
    hashthis2 = aprintf("%s:%s", hashthis, hashed);
793
0
    free(hashthis);
794
0
    hashthis = hashthis2;
795
0
  }
796
797
0
  if(!hashthis)
798
0
    return CURLE_OUT_OF_MEMORY;
799
800
0
  hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis));
801
0
  free(hashthis);
802
0
  convert_to_ascii(hashbuf, ha2);
803
804
0
  if(digest->qop) {
805
0
    hashthis = aprintf("%s:%s:%08x:%s:%s:%s", ha1, digest->nonce, digest->nc,
806
0
                       digest->cnonce, digest->qop, ha2);
807
0
  }
808
0
  else {
809
0
    hashthis = aprintf("%s:%s:%s", ha1, digest->nonce, ha2);
810
0
  }
811
812
0
  if(!hashthis)
813
0
    return CURLE_OUT_OF_MEMORY;
814
815
0
  hash(hashbuf, (unsigned char *) hashthis, strlen(hashthis));
816
0
  free(hashthis);
817
0
  convert_to_ascii(hashbuf, request_digest);
818
819
  /* For test case 64 (snooped from a Mozilla 1.3a request)
820
821
     Authorization: Digest username="testuser", realm="testrealm", \
822
     nonce="1053604145", uri="/64", response="c55f7f30d83d774a3d2dcacf725abaca"
823
824
     Digest parameters are all quoted strings.  Username which is provided by
825
     the user will need double quotes and backslashes within it escaped.
826
     realm, nonce, and opaque will need backslashes as well as they were
827
     de-escaped when copied from request header.  cnonce is generated with
828
     web-safe characters.  uri is already percent encoded.  nc is 8 hex
829
     characters.  algorithm and qop with standard values only contain web-safe
830
     characters.
831
  */
832
0
  userp_quoted = auth_digest_string_quoted(digest->userhash ? userh : userp);
833
0
  if(!userp_quoted)
834
0
    return CURLE_OUT_OF_MEMORY;
835
0
  if(digest->realm)
836
0
    realm_quoted = auth_digest_string_quoted(digest->realm);
837
0
  else {
838
0
    realm_quoted = malloc(1);
839
0
    if(realm_quoted)
840
0
      realm_quoted[0] = 0;
841
0
  }
842
0
  if(!realm_quoted) {
843
0
    free(userp_quoted);
844
0
    return CURLE_OUT_OF_MEMORY;
845
0
  }
846
0
  nonce_quoted = auth_digest_string_quoted(digest->nonce);
847
0
  if(!nonce_quoted) {
848
0
    free(realm_quoted);
849
0
    free(userp_quoted);
850
0
    return CURLE_OUT_OF_MEMORY;
851
0
  }
852
853
0
  if(digest->qop) {
854
0
    response = aprintf("username=\"%s\", "
855
0
                       "realm=\"%s\", "
856
0
                       "nonce=\"%s\", "
857
0
                       "uri=\"%s\", "
858
0
                       "cnonce=\"%s\", "
859
0
                       "nc=%08x, "
860
0
                       "qop=%s, "
861
0
                       "response=\"%s\"",
862
0
                       userp_quoted,
863
0
                       realm_quoted,
864
0
                       nonce_quoted,
865
0
                       uripath,
866
0
                       digest->cnonce,
867
0
                       digest->nc,
868
0
                       digest->qop,
869
0
                       request_digest);
870
871
    /* Increment nonce-count to use another nc value for the next request */
872
0
    digest->nc++;
873
0
  }
874
0
  else {
875
0
    response = aprintf("username=\"%s\", "
876
0
                       "realm=\"%s\", "
877
0
                       "nonce=\"%s\", "
878
0
                       "uri=\"%s\", "
879
0
                       "response=\"%s\"",
880
0
                       userp_quoted,
881
0
                       realm_quoted,
882
0
                       nonce_quoted,
883
0
                       uripath,
884
0
                       request_digest);
885
0
  }
886
0
  free(nonce_quoted);
887
0
  free(realm_quoted);
888
0
  free(userp_quoted);
889
0
  if(!response)
890
0
    return CURLE_OUT_OF_MEMORY;
891
892
  /* Add the optional fields */
893
0
  if(digest->opaque) {
894
0
    char *opaque_quoted;
895
    /* Append the opaque */
896
0
    opaque_quoted = auth_digest_string_quoted(digest->opaque);
897
0
    if(!opaque_quoted) {
898
0
      free(response);
899
0
      return CURLE_OUT_OF_MEMORY;
900
0
    }
901
0
    tmp = aprintf("%s, opaque=\"%s\"", response, opaque_quoted);
902
0
    free(response);
903
0
    free(opaque_quoted);
904
0
    if(!tmp)
905
0
      return CURLE_OUT_OF_MEMORY;
906
907
0
    response = tmp;
908
0
  }
909
910
0
  if(digest->algorithm) {
911
    /* Append the algorithm */
912
0
    tmp = aprintf("%s, algorithm=%s", response, digest->algorithm);
913
0
    free(response);
914
0
    if(!tmp)
915
0
      return CURLE_OUT_OF_MEMORY;
916
917
0
    response = tmp;
918
0
  }
919
920
0
  if(digest->userhash) {
921
    /* Append the userhash */
922
0
    tmp = aprintf("%s, userhash=true", response);
923
0
    free(response);
924
0
    if(!tmp)
925
0
      return CURLE_OUT_OF_MEMORY;
926
927
0
    response = tmp;
928
0
  }
929
930
  /* Return the output */
931
0
  *outptr = response;
932
0
  *outlen = strlen(response);
933
934
0
  return CURLE_OK;
935
0
}
936
937
/*
938
 * Curl_auth_create_digest_http_message()
939
 *
940
 * This is used to generate an HTTP DIGEST response message ready for sending
941
 * to the recipient.
942
 *
943
 * Parameters:
944
 *
945
 * data    [in]     - The session handle.
946
 * userp   [in]     - The user name.
947
 * passwdp [in]     - The user's password.
948
 * request [in]     - The HTTP request.
949
 * uripath [in]     - The path of the HTTP uri.
950
 * digest  [in/out] - The digest data struct being used and modified.
951
 * outptr  [in/out] - The address where a pointer to newly allocated memory
952
 *                    holding the result will be stored upon completion.
953
 * outlen  [out]    - The length of the output message.
954
 *
955
 * Returns CURLE_OK on success.
956
 */
957
CURLcode Curl_auth_create_digest_http_message(struct Curl_easy *data,
958
                                              const char *userp,
959
                                              const char *passwdp,
960
                                              const unsigned char *request,
961
                                              const unsigned char *uripath,
962
                                              struct digestdata *digest,
963
                                              char **outptr, size_t *outlen)
964
0
{
965
0
  if(digest->algo <= ALGO_MD5SESS)
966
0
    return auth_create_digest_http_message(data, userp, passwdp,
967
0
                                           request, uripath, digest,
968
0
                                           outptr, outlen,
969
0
                                           auth_digest_md5_to_ascii,
970
0
                                           Curl_md5it);
971
972
0
  if(digest->algo <= ALGO_SHA256SESS)
973
0
    return auth_create_digest_http_message(data, userp, passwdp,
974
0
                                           request, uripath, digest,
975
0
                                           outptr, outlen,
976
0
                                           auth_digest_sha256_to_ascii,
977
0
                                           Curl_sha256it);
978
0
#ifdef CURL_HAVE_SHA512_256
979
0
  if(digest->algo <= ALGO_SHA512_256SESS)
980
0
    return auth_create_digest_http_message(data, userp, passwdp,
981
0
                                           request, uripath, digest,
982
0
                                           outptr, outlen,
983
0
                                           auth_digest_sha256_to_ascii,
984
0
                                           Curl_sha512_256it);
985
0
#endif /* CURL_HAVE_SHA512_256 */
986
987
  /* Should be unreachable */
988
0
  return CURLE_BAD_CONTENT_ENCODING;
989
0
}
990
991
/*
992
 * Curl_auth_digest_cleanup()
993
 *
994
 * This is used to clean up the digest specific data.
995
 *
996
 * Parameters:
997
 *
998
 * digest    [in/out] - The digest data struct being cleaned up.
999
 *
1000
 */
1001
void Curl_auth_digest_cleanup(struct digestdata *digest)
1002
0
{
1003
0
  Curl_safefree(digest->nonce);
1004
0
  Curl_safefree(digest->cnonce);
1005
0
  Curl_safefree(digest->realm);
1006
0
  Curl_safefree(digest->opaque);
1007
0
  Curl_safefree(digest->qop);
1008
0
  Curl_safefree(digest->algorithm);
1009
1010
0
  digest->nc = 0;
1011
0
  digest->algo = ALGO_MD5; /* default algorithm */
1012
0
  digest->stale = FALSE; /* default means normal, not stale */
1013
0
  digest->userhash = FALSE;
1014
0
}
1015
#endif  /* !USE_WINDOWS_SSPI */
1016
1017
#endif  /* !CURL_DISABLE_DIGEST_AUTH */