Coverage Report

Created: 2025-07-23 06:03

/src/libsoup/libsoup/auth/soup-auth-digest.c
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
2
/*
3
 * soup-auth-digest.c: HTTP Digest Authentication
4
 *
5
 * Copyright (C) 2001-2003, Ximian, Inc.
6
 */
7
8
#ifdef HAVE_CONFIG_H
9
#include <config.h>
10
#endif
11
12
#include <string.h>
13
14
#include "auth/soup-auth-digest-private.h"
15
#include "soup.h"
16
#include "soup-message-private.h"
17
#include "soup-message-headers-private.h"
18
#include "soup-uri-utils-private.h"
19
20
#ifdef G_OS_WIN32
21
#include <process.h>
22
#endif
23
24
struct _SoupAuthDigest {
25
  SoupAuth parent;
26
};
27
28
typedef struct {
29
  char                    *user;
30
  char                     hex_urp[33];
31
  char                     hex_a1[33];
32
33
  /* These are provided by the server */
34
  char                    *nonce;
35
  char                    *opaque;
36
  SoupAuthDigestQop        qop_options;
37
  SoupAuthDigestAlgorithm  algorithm;
38
  char                    *domain;
39
40
  /* These are generated by the client */
41
  char                    *cnonce;
42
  int                      nc;
43
  SoupAuthDigestQop        qop;
44
} SoupAuthDigestPrivate;
45
46
static void recompute_hex_a1 (SoupAuthDigestPrivate *priv);
47
48
/**
49
 * SoupAuthDigest:
50
 *
51
 * HTTP "Digest" authentication.
52
 *
53
 * [class@Session]s support this by default; if you want to disable
54
 * support for it, call [method@Session.remove_feature_by_type]
55
 * passing %SOUP_TYPE_AUTH_DIGEST.
56
 *
57
 */
58
59
G_DEFINE_FINAL_TYPE_WITH_PRIVATE (SoupAuthDigest, soup_auth_digest, SOUP_TYPE_AUTH)
60
61
static void
62
soup_auth_digest_init (SoupAuthDigest *digest)
63
0
{
64
0
}
65
66
static void
67
soup_auth_digest_finalize (GObject *object)
68
0
{
69
0
  SoupAuthDigestPrivate *priv = soup_auth_digest_get_instance_private (SOUP_AUTH_DIGEST (object));
70
71
0
  g_free (priv->user);
72
0
  g_free (priv->nonce);
73
0
  g_free (priv->domain);
74
0
  g_free (priv->cnonce);
75
0
        g_free (priv->opaque);
76
77
0
  memset (priv->hex_urp, 0, sizeof (priv->hex_urp));
78
0
  memset (priv->hex_a1, 0, sizeof (priv->hex_a1));
79
80
0
  G_OBJECT_CLASS (soup_auth_digest_parent_class)->finalize (object);
81
0
}
82
83
SoupAuthDigestAlgorithm
84
soup_auth_digest_parse_algorithm (const char *algorithm)
85
0
{
86
0
  if (!algorithm || !g_ascii_strcasecmp (algorithm, "MD5"))
87
0
    return SOUP_AUTH_DIGEST_ALGORITHM_MD5;
88
0
  else if (!g_ascii_strcasecmp (algorithm, "MD5-sess"))
89
0
    return SOUP_AUTH_DIGEST_ALGORITHM_MD5_SESS;
90
0
  else
91
0
    return -1;
92
0
}
93
94
char *
95
soup_auth_digest_get_algorithm (SoupAuthDigestAlgorithm algorithm)
96
0
{
97
0
  if (algorithm == SOUP_AUTH_DIGEST_ALGORITHM_MD5)
98
0
    return g_strdup ("MD5");
99
0
  else if (algorithm == SOUP_AUTH_DIGEST_ALGORITHM_MD5_SESS)
100
0
    return g_strdup ("MD5-sess");
101
0
  else
102
0
    return NULL;
103
0
}
104
105
SoupAuthDigestQop
106
soup_auth_digest_parse_qop (const char *qop)
107
0
{
108
0
  GSList *qop_values, *iter;
109
0
  SoupAuthDigestQop out = 0;
110
111
0
  g_return_val_if_fail (qop != NULL, 0);
112
113
0
  qop_values = soup_header_parse_list (qop);
114
0
  for (iter = qop_values; iter; iter = iter->next) {
115
0
    if (!g_ascii_strcasecmp (iter->data, "auth"))
116
0
      out |= SOUP_AUTH_DIGEST_QOP_AUTH;
117
0
    else if (!g_ascii_strcasecmp (iter->data, "auth-int"))
118
0
      out |= SOUP_AUTH_DIGEST_QOP_AUTH_INT;
119
0
  }
120
0
  soup_header_free_list (qop_values);
121
122
0
  return out;
123
0
}
124
125
char *
126
soup_auth_digest_get_qop (SoupAuthDigestQop qop)
127
0
{
128
0
  GString *out;
129
130
0
  out = g_string_new (NULL);
131
0
  if (qop & SOUP_AUTH_DIGEST_QOP_AUTH)
132
0
    g_string_append (out, "auth");
133
0
  if (qop & SOUP_AUTH_DIGEST_QOP_AUTH_INT) {
134
0
    if (qop & SOUP_AUTH_DIGEST_QOP_AUTH)
135
0
      g_string_append (out, ",");
136
0
    g_string_append (out, "auth-int");
137
0
  }
138
139
0
  return g_string_free (out, FALSE);
140
0
}
141
142
static gboolean
143
soup_auth_digest_update (SoupAuth *auth, SoupMessage *msg,
144
       GHashTable *auth_params)
145
0
{
146
0
  SoupAuthDigest *auth_digest = SOUP_AUTH_DIGEST (auth);
147
0
  SoupAuthDigestPrivate *priv = soup_auth_digest_get_instance_private (auth_digest);
148
0
  const char *stale, *qop;
149
0
  guint qop_options;
150
0
  gboolean ok = TRUE;
151
152
0
        if (!soup_auth_get_realm (auth) || !g_hash_table_lookup (auth_params, "nonce"))
153
0
                return FALSE;
154
155
0
  g_free (priv->domain);
156
0
  g_free (priv->nonce);
157
0
  g_free (priv->opaque);
158
159
0
  priv->nc = 1;
160
161
0
  priv->domain = g_strdup (g_hash_table_lookup (auth_params, "domain"));
162
0
  priv->nonce = g_strdup (g_hash_table_lookup (auth_params, "nonce"));
163
0
  priv->opaque = g_strdup (g_hash_table_lookup (auth_params, "opaque"));
164
165
0
  qop = g_hash_table_lookup (auth_params, "qop");
166
0
  if (qop) {
167
0
    qop_options = soup_auth_digest_parse_qop (qop);
168
    /* We only support auth */
169
0
    if (!(qop_options & SOUP_AUTH_DIGEST_QOP_AUTH))
170
0
      ok = FALSE;
171
0
    priv->qop = SOUP_AUTH_DIGEST_QOP_AUTH;
172
0
  } else
173
0
    priv->qop = 0;
174
175
0
  priv->algorithm = soup_auth_digest_parse_algorithm (g_hash_table_lookup (auth_params, "algorithm"));
176
0
  if (priv->algorithm == -1)
177
0
    ok = FALSE;
178
179
0
        if (ok) {
180
0
                stale = g_hash_table_lookup (auth_params, "stale");
181
0
                if (stale && !g_ascii_strcasecmp (stale, "TRUE") && *priv->hex_urp)
182
0
                        recompute_hex_a1 (priv);
183
0
                else {
184
0
                        g_free (priv->user);
185
0
                        priv->user = NULL;
186
0
                        g_free (priv->cnonce);
187
0
                        priv->cnonce = NULL;
188
0
                        memset (priv->hex_urp, 0, sizeof (priv->hex_urp));
189
0
                        memset (priv->hex_a1, 0, sizeof (priv->hex_a1));
190
0
                }
191
0
        }
192
193
0
  return ok;
194
0
}
195
196
static GSList *
197
soup_auth_digest_get_protection_space (SoupAuth *auth, GUri *source_uri)
198
0
{
199
0
  SoupAuthDigest *auth_digest = SOUP_AUTH_DIGEST (auth);
200
0
  SoupAuthDigestPrivate *priv = soup_auth_digest_get_instance_private (auth_digest);
201
0
  GSList *space = NULL;
202
0
  GUri *uri;
203
0
  char **dvec, *d, *dir, *slash;
204
0
  int dix;
205
206
0
  if (!priv->domain || !*priv->domain) {
207
    /* If no domain directive, the protection space is the
208
     * whole server.
209
     */
210
0
    return g_slist_prepend (NULL, g_strdup (""));
211
0
  }
212
213
0
  dvec = g_strsplit (priv->domain, " ", 0);
214
0
  for (dix = 0; dvec[dix] != NULL; dix++) {
215
0
    d = dvec[dix];
216
0
    if (*d == '/')
217
0
      dir = g_strdup (d);
218
0
    else {
219
0
      uri = g_uri_parse (d, SOUP_HTTP_URI_FLAGS, NULL);
220
0
      if (uri &&
221
0
                            g_strcmp0 (g_uri_get_scheme (uri), g_uri_get_scheme (source_uri)) == 0 &&
222
0
          g_uri_get_port (uri) == g_uri_get_port (source_uri) &&
223
0
          !g_strcmp0 (g_uri_get_host (uri), g_uri_get_host (source_uri)))
224
0
        dir = g_strdup (g_uri_get_path (uri));
225
0
      else
226
0
        dir = NULL;
227
0
      if (uri)
228
0
        g_uri_unref (uri);
229
0
    }
230
231
0
    if (dir) {
232
0
      slash = strrchr (dir, '/');
233
0
      if (slash && !slash[1])
234
0
        *slash = '\0';
235
236
0
      space = g_slist_prepend (space, dir);
237
0
    }
238
0
  }
239
0
  g_strfreev (dvec);
240
241
0
  return space;
242
0
}
243
244
void
245
soup_auth_digest_compute_hex_urp (const char *username,
246
          const char *realm,
247
          const char *password,
248
          char        hex_urp[33])
249
0
{
250
0
  GChecksum *checksum;
251
252
0
  checksum = g_checksum_new (G_CHECKSUM_MD5);
253
0
  g_checksum_update (checksum, (guchar *)username, strlen (username));
254
0
  g_checksum_update (checksum, (guchar *)":", 1);
255
0
  g_checksum_update (checksum, (guchar *)realm, strlen (realm));
256
0
  g_checksum_update (checksum, (guchar *)":", 1);
257
0
  g_checksum_update (checksum, (guchar *)password, strlen (password));
258
0
        g_strlcpy (hex_urp, g_checksum_get_string (checksum), 33);
259
0
  g_checksum_free (checksum);
260
0
}
261
262
void
263
soup_auth_digest_compute_hex_a1 (const char              *hex_urp,
264
         SoupAuthDigestAlgorithm  algorithm,
265
         const char              *nonce,
266
         const char              *cnonce,
267
         char                     hex_a1[33])
268
0
{
269
0
  if (algorithm == SOUP_AUTH_DIGEST_ALGORITHM_MD5) {
270
    /* In MD5, A1 is just user:realm:password, so hex_A1
271
     * is just hex_urp.
272
     */
273
    /* You'd think you could say "sizeof (hex_a1)" here,
274
     * but you'd be wrong.
275
     */
276
0
    memcpy (hex_a1, hex_urp, 33);
277
0
  } else {
278
0
    GChecksum *checksum;
279
280
    /* In MD5-sess, A1 is hex_urp:nonce:cnonce */
281
282
0
                g_assert (nonce && cnonce);
283
284
0
    checksum = g_checksum_new (G_CHECKSUM_MD5);
285
0
    g_checksum_update (checksum, (guchar *)hex_urp, strlen (hex_urp));
286
0
    g_checksum_update (checksum, (guchar *)":", 1);
287
0
    g_checksum_update (checksum, (guchar *)nonce, strlen (nonce));
288
0
    g_checksum_update (checksum, (guchar *)":", 1);
289
0
    g_checksum_update (checksum, (guchar *)cnonce, strlen (cnonce));
290
0
                g_strlcpy (hex_a1, g_checksum_get_string (checksum), 33);
291
0
    g_checksum_free (checksum);
292
0
  }
293
0
}
294
295
static void
296
recompute_hex_a1 (SoupAuthDigestPrivate *priv)
297
0
{
298
0
  soup_auth_digest_compute_hex_a1 (priv->hex_urp,
299
0
           priv->algorithm,
300
0
           priv->nonce,
301
0
           priv->cnonce,
302
0
           priv->hex_a1);
303
0
}
304
305
static void
306
soup_auth_digest_authenticate (SoupAuth *auth, const char *username,
307
             const char *password)
308
0
{
309
0
  SoupAuthDigest *auth_digest = SOUP_AUTH_DIGEST (auth);
310
0
  SoupAuthDigestPrivate *priv = soup_auth_digest_get_instance_private (auth_digest);
311
0
  char *bgen;
312
313
0
  g_clear_pointer (&priv->cnonce, g_free);
314
0
  g_clear_pointer (&priv->user, g_free);
315
316
  /* Create client nonce */
317
0
  bgen = g_strdup_printf ("%p:%lu:%lu",
318
0
        auth,
319
0
        (unsigned long) getpid (),
320
0
        (unsigned long) time (0));
321
0
  priv->cnonce = g_base64_encode ((guchar *)bgen, strlen (bgen));
322
0
  g_free (bgen);
323
324
0
  priv->user = g_strdup (username);
325
326
  /* compute "URP" (user:realm:password) */
327
0
  soup_auth_digest_compute_hex_urp (username, soup_auth_get_realm (auth),
328
0
            password ? password : "",
329
0
            priv->hex_urp);
330
331
  /* And compute A1 from that */
332
0
  recompute_hex_a1 (priv);
333
0
}
334
335
static gboolean
336
soup_auth_digest_is_authenticated (SoupAuth *auth)
337
0
{
338
0
  SoupAuthDigestPrivate *priv = soup_auth_digest_get_instance_private (SOUP_AUTH_DIGEST (auth));
339
340
0
  return priv->cnonce != NULL;
341
0
}
342
343
void
344
soup_auth_digest_compute_response (const char        *method,
345
           const char        *uri,
346
           const char        *hex_a1,
347
           SoupAuthDigestQop  qop,
348
           const char        *nonce,
349
           const char        *cnonce,
350
           int                nc,
351
           char               response[33])
352
0
{
353
0
  char hex_a2[33];
354
0
  GChecksum *checksum;
355
356
  /* compute A2 */
357
0
  checksum = g_checksum_new (G_CHECKSUM_MD5);
358
0
  g_checksum_update (checksum, (guchar *)method, strlen (method));
359
0
  g_checksum_update (checksum, (guchar *)":", 1);
360
0
  g_checksum_update (checksum, (guchar *)uri, strlen (uri));
361
0
  memcpy (hex_a2, g_checksum_get_string (checksum), sizeof (char) * 33);
362
0
  g_checksum_free (checksum);
363
364
  /* compute KD */
365
0
  checksum = g_checksum_new (G_CHECKSUM_MD5);
366
0
  g_checksum_update (checksum, (guchar *)hex_a1, strlen (hex_a1));
367
0
  g_checksum_update (checksum, (guchar *)":", 1);
368
0
  g_checksum_update (checksum, (guchar *)nonce, strlen (nonce));
369
0
  g_checksum_update (checksum, (guchar *)":", 1);
370
371
0
  if (qop) {
372
0
    char tmp[9];
373
374
0
                g_assert (cnonce);
375
376
0
    g_snprintf (tmp, 9, "%.8x", nc);
377
0
    g_checksum_update (checksum, (guchar *)tmp, strlen (tmp));
378
0
    g_checksum_update (checksum, (guchar *)":", 1);
379
0
    g_checksum_update (checksum, (guchar *)cnonce, strlen (cnonce));
380
0
    g_checksum_update (checksum, (guchar *)":", 1);
381
382
0
    if (!(qop & SOUP_AUTH_DIGEST_QOP_AUTH))
383
0
      g_warn_if_reached ();
384
0
    g_checksum_update (checksum, (guchar *)"auth", strlen ("auth"));
385
0
    g_checksum_update (checksum, (guchar *)":", 1);
386
0
  }
387
388
0
  g_checksum_update (checksum, (guchar *)hex_a2, 32);
389
0
  memcpy (response, g_checksum_get_string (checksum), sizeof (char) * 33);
390
0
  g_checksum_free (checksum);
391
0
}
392
393
static void
394
authentication_info_cb (SoupMessage *msg, gpointer data)
395
0
{
396
0
  SoupAuth *auth = data;
397
0
  SoupAuthDigest *auth_digest = SOUP_AUTH_DIGEST (auth);
398
0
  SoupAuthDigestPrivate *priv = soup_auth_digest_get_instance_private (auth_digest);
399
0
  const char *header;
400
0
  GHashTable *auth_params;
401
0
  char *nextnonce;
402
403
0
  if (auth != soup_message_get_auth (msg))
404
0
    return;
405
406
0
  header = soup_message_headers_get_one_common (soup_message_get_response_headers (msg),
407
0
                                                      soup_auth_is_for_proxy (auth) ?
408
0
                                                      SOUP_HEADER_PROXY_AUTHENTICATION_INFO :
409
0
                                                      SOUP_HEADER_AUTHENTICATION_INFO);
410
0
  g_return_if_fail (header != NULL);
411
412
0
  auth_params = soup_header_parse_param_list (header);
413
0
  if (!auth_params)
414
0
    return;
415
416
0
  nextnonce = g_strdup (g_hash_table_lookup (auth_params, "nextnonce"));
417
0
  if (nextnonce) {
418
0
    g_free (priv->nonce);
419
0
    priv->nonce = nextnonce;
420
0
  }
421
422
0
  soup_header_free_param_list (auth_params);
423
0
}
424
425
static char *
426
soup_auth_digest_get_authorization (SoupAuth *auth, SoupMessage *msg)
427
0
{
428
0
  SoupAuthDigest *auth_digest = SOUP_AUTH_DIGEST (auth);
429
0
  SoupAuthDigestPrivate *priv = soup_auth_digest_get_instance_private (auth_digest);
430
0
  char response[33], *token;
431
0
  char *url, *algorithm;
432
0
  GString *out;
433
0
  GUri *uri;
434
435
0
  uri = soup_message_get_uri (msg);
436
0
  g_return_val_if_fail (uri != NULL, NULL);
437
0
  url = soup_uri_get_path_and_query (uri);
438
439
0
        g_assert (priv->nonce);
440
0
        g_assert (!priv->qop || priv->cnonce);
441
442
0
  soup_auth_digest_compute_response (soup_message_get_method (msg), url, priv->hex_a1,
443
0
             priv->qop, priv->nonce,
444
0
             priv->cnonce, priv->nc,
445
0
             response);
446
447
0
  out = g_string_new ("Digest ");
448
449
0
  soup_header_g_string_append_param_quoted (out, "username", priv->user);
450
0
  g_string_append (out, ", ");
451
0
  soup_header_g_string_append_param_quoted (out, "realm", soup_auth_get_realm (auth));
452
0
  g_string_append (out, ", ");
453
0
  soup_header_g_string_append_param_quoted (out, "nonce", priv->nonce);
454
0
  g_string_append (out, ", ");
455
0
  soup_header_g_string_append_param_quoted (out, "uri", url);
456
0
  g_string_append (out, ", ");
457
0
  algorithm = soup_auth_digest_get_algorithm (priv->algorithm);
458
0
  g_string_append_printf (out, "algorithm=%s", algorithm);
459
0
  g_free (algorithm);
460
0
  g_string_append (out, ", ");
461
0
  soup_header_g_string_append_param_quoted (out, "response", response);
462
463
0
  if (priv->opaque) {
464
0
    g_string_append (out, ", ");
465
0
    soup_header_g_string_append_param_quoted (out, "opaque", priv->opaque);
466
0
  }
467
468
0
  if (priv->qop) {
469
0
    char *qop = soup_auth_digest_get_qop (priv->qop);
470
471
0
    g_string_append (out, ", ");
472
0
    soup_header_g_string_append_param_quoted (out, "cnonce", priv->cnonce);
473
0
    g_string_append_printf (out, ", nc=%.8x, qop=%s",
474
0
          priv->nc, qop);
475
0
    g_free (qop);
476
0
  }
477
478
0
  g_free (url);
479
480
0
  priv->nc++;
481
482
0
  token = g_string_free (out, FALSE);
483
484
0
  soup_message_add_header_handler (msg,
485
0
           "got_headers",
486
0
           soup_auth_is_for_proxy (auth) ?
487
0
           "Proxy-Authentication-Info" :
488
0
           "Authentication-Info",
489
0
           G_CALLBACK (authentication_info_cb),
490
0
           auth);
491
0
  return token;
492
0
}
493
494
static void
495
soup_auth_digest_class_init (SoupAuthDigestClass *auth_digest_class)
496
0
{
497
0
  SoupAuthClass *auth_class = SOUP_AUTH_CLASS (auth_digest_class);
498
0
  GObjectClass *object_class = G_OBJECT_CLASS (auth_digest_class);
499
500
0
  auth_class->scheme_name = "Digest";
501
0
  auth_class->strength = 5;
502
503
0
  auth_class->get_protection_space = soup_auth_digest_get_protection_space;
504
0
  auth_class->update = soup_auth_digest_update;
505
0
  auth_class->authenticate = soup_auth_digest_authenticate;
506
0
  auth_class->is_authenticated = soup_auth_digest_is_authenticated;
507
0
  auth_class->get_authorization = soup_auth_digest_get_authorization;
508
509
0
  object_class->finalize = soup_auth_digest_finalize;
510
0
}