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