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