1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * soup-auth-digest.c: HTTP Digest Authentication
5 * Copyright (C) 2001-2003, Ximian, Inc.
14 #include "soup-auth-digest.h"
16 #include "soup-message-private.h"
27 /* These are provided by the server */
30 SoupAuthDigestQop qop_options;
31 SoupAuthDigestAlgorithm algorithm;
34 /* These are generated by the client */
37 SoupAuthDigestQop qop;
38 } SoupAuthDigestPrivate;
39 #define SOUP_AUTH_DIGEST_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_DIGEST, SoupAuthDigestPrivate))
41 static void recompute_hex_a1 (SoupAuthDigestPrivate *priv);
44 * SOUP_TYPE_AUTH_DIGEST:
46 * A #GType corresponding to HTTP "Digest" authentication.
47 * #SoupSessions support this by default; if you want to disable
48 * support for it, call soup_session_remove_feature_by_type(),
49 * passing %SOUP_TYPE_AUTH_DIGEST.
54 G_DEFINE_TYPE (SoupAuthDigest, soup_auth_digest, SOUP_TYPE_AUTH)
57 soup_auth_digest_init (SoupAuthDigest *digest)
62 soup_auth_digest_finalize (GObject *object)
64 SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (object);
68 g_free (priv->domain);
69 g_free (priv->cnonce);
71 memset (priv->hex_urp, 0, sizeof (priv->hex_urp));
72 memset (priv->hex_a1, 0, sizeof (priv->hex_a1));
74 G_OBJECT_CLASS (soup_auth_digest_parent_class)->finalize (object);
77 SoupAuthDigestAlgorithm
78 soup_auth_digest_parse_algorithm (const char *algorithm)
80 if (!algorithm || !g_ascii_strcasecmp (algorithm, "MD5"))
81 return SOUP_AUTH_DIGEST_ALGORITHM_MD5;
82 else if (!g_ascii_strcasecmp (algorithm, "MD5-sess"))
83 return SOUP_AUTH_DIGEST_ALGORITHM_MD5_SESS;
89 soup_auth_digest_get_algorithm (SoupAuthDigestAlgorithm algorithm)
91 if (algorithm == SOUP_AUTH_DIGEST_ALGORITHM_MD5)
92 return g_strdup ("MD5");
93 else if (algorithm == SOUP_AUTH_DIGEST_ALGORITHM_MD5_SESS)
94 return g_strdup ("MD5-sess");
100 soup_auth_digest_parse_qop (const char *qop)
102 GSList *qop_values, *iter;
103 SoupAuthDigestQop out = 0;
105 g_return_val_if_fail (qop != NULL, 0);
107 qop_values = soup_header_parse_list (qop);
108 for (iter = qop_values; iter; iter = iter->next) {
109 if (!g_ascii_strcasecmp (iter->data, "auth"))
110 out |= SOUP_AUTH_DIGEST_QOP_AUTH;
111 else if (!g_ascii_strcasecmp (iter->data, "auth-int"))
112 out |= SOUP_AUTH_DIGEST_QOP_AUTH_INT;
114 soup_header_free_list (qop_values);
120 soup_auth_digest_get_qop (SoupAuthDigestQop qop)
124 out = g_string_new (NULL);
125 if (qop & SOUP_AUTH_DIGEST_QOP_AUTH)
126 g_string_append (out, "auth");
127 if (qop & SOUP_AUTH_DIGEST_QOP_AUTH_INT) {
128 if (qop & SOUP_AUTH_DIGEST_QOP_AUTH)
129 g_string_append (out, ",");
130 g_string_append (out, "auth-int");
133 return g_string_free (out, FALSE);
137 soup_auth_digest_update (SoupAuth *auth, SoupMessage *msg,
138 GHashTable *auth_params)
140 SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth);
141 const char *stale, *qop;
145 g_free (priv->domain);
146 g_free (priv->nonce);
147 g_free (priv->opaque);
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"));
155 qop = g_hash_table_lookup (auth_params, "qop");
157 qop_options = soup_auth_digest_parse_qop (qop);
158 /* We only support auth */
159 if (!(qop_options & SOUP_AUTH_DIGEST_QOP_AUTH))
161 priv->qop = SOUP_AUTH_DIGEST_QOP_AUTH;
165 priv->algorithm = soup_auth_digest_parse_algorithm (g_hash_table_lookup (auth_params, "algorithm"));
166 if (priv->algorithm == -1)
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);
175 g_free (priv->cnonce);
177 memset (priv->hex_urp, 0, sizeof (priv->hex_urp));
178 memset (priv->hex_a1, 0, sizeof (priv->hex_a1));
185 soup_auth_digest_get_protection_space (SoupAuth *auth, SoupURI *source_uri)
187 SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth);
188 GSList *space = NULL;
190 char **dvec, *d, *dir, *slash;
193 if (!priv->domain || !*priv->domain) {
194 /* If no domain directive, the protection space is the
197 return g_slist_prepend (NULL, g_strdup (""));
200 dvec = g_strsplit (priv->domain, " ", 0);
201 for (dix = 0; dvec[dix] != NULL; dix++) {
206 uri = soup_uri_new (d);
207 if (uri && uri->scheme == source_uri->scheme &&
208 uri->port == source_uri->port &&
209 !strcmp (uri->host, source_uri->host))
210 dir = g_strdup (uri->path);
218 slash = strrchr (dir, '/');
219 if (slash && !slash[1])
222 space = g_slist_prepend (space, dir);
231 soup_auth_digest_compute_hex_urp (const char *username,
233 const char *password,
238 checksum = g_checksum_new (G_CHECKSUM_MD5);
239 g_checksum_update (checksum, (guchar *)username, strlen (username));
240 g_checksum_update (checksum, (guchar *)":", 1);
241 g_checksum_update (checksum, (guchar *)realm, strlen (realm));
242 g_checksum_update (checksum, (guchar *)":", 1);
243 g_checksum_update (checksum, (guchar *)password, strlen (password));
244 strncpy (hex_urp, g_checksum_get_string (checksum), 33);
245 g_checksum_free (checksum);
249 soup_auth_digest_compute_hex_a1 (const char *hex_urp,
250 SoupAuthDigestAlgorithm algorithm,
255 if (algorithm == SOUP_AUTH_DIGEST_ALGORITHM_MD5) {
256 /* In MD5, A1 is just user:realm:password, so hex_A1
259 /* You'd think you could say "sizeof (hex_a1)" here,
260 * but you'd be wrong.
262 memcpy (hex_a1, hex_urp, 33);
266 /* In MD5-sess, A1 is hex_urp:nonce:cnonce */
268 checksum = g_checksum_new (G_CHECKSUM_MD5);
269 g_checksum_update (checksum, (guchar *)hex_urp, strlen (hex_urp));
270 g_checksum_update (checksum, (guchar *)":", 1);
271 g_checksum_update (checksum, (guchar *)nonce, strlen (nonce));
272 g_checksum_update (checksum, (guchar *)":", 1);
273 g_checksum_update (checksum, (guchar *)cnonce, strlen (cnonce));
274 strncpy (hex_a1, g_checksum_get_string (checksum), 33);
275 g_checksum_free (checksum);
280 recompute_hex_a1 (SoupAuthDigestPrivate *priv)
282 soup_auth_digest_compute_hex_a1 (priv->hex_urp,
290 soup_auth_digest_authenticate (SoupAuth *auth, const char *username,
291 const char *password)
293 SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth);
296 g_clear_pointer (&priv->cnonce, g_free);
297 g_clear_pointer (&priv->user, g_free);
299 /* Create client nonce */
300 bgen = g_strdup_printf ("%p:%lu:%lu",
302 (unsigned long) getpid (),
303 (unsigned long) time (0));
304 priv->cnonce = g_base64_encode ((guchar *)bgen, strlen (bgen));
307 priv->user = g_strdup (username);
309 /* compute "URP" (user:realm:password) */
310 soup_auth_digest_compute_hex_urp (username, auth->realm,
311 password ? password : "",
314 /* And compute A1 from that */
315 recompute_hex_a1 (priv);
319 soup_auth_digest_is_authenticated (SoupAuth *auth)
321 return SOUP_AUTH_DIGEST_GET_PRIVATE (auth)->cnonce != NULL;
325 soup_auth_digest_compute_response (const char *method,
328 SoupAuthDigestQop qop,
338 checksum = g_checksum_new (G_CHECKSUM_MD5);
339 g_checksum_update (checksum, (guchar *)method, strlen (method));
340 g_checksum_update (checksum, (guchar *)":", 1);
341 g_checksum_update (checksum, (guchar *)uri, strlen (uri));
342 strncpy (hex_a2, g_checksum_get_string (checksum), 33);
343 g_checksum_free (checksum);
346 checksum = g_checksum_new (G_CHECKSUM_MD5);
347 g_checksum_update (checksum, (guchar *)hex_a1, strlen (hex_a1));
348 g_checksum_update (checksum, (guchar *)":", 1);
349 g_checksum_update (checksum, (guchar *)nonce, strlen (nonce));
350 g_checksum_update (checksum, (guchar *)":", 1);
355 g_snprintf (tmp, 9, "%.8x", nc);
356 g_checksum_update (checksum, (guchar *)tmp, strlen (tmp));
357 g_checksum_update (checksum, (guchar *)":", 1);
358 g_checksum_update (checksum, (guchar *)cnonce, strlen (cnonce));
359 g_checksum_update (checksum, (guchar *)":", 1);
361 if (!(qop & SOUP_AUTH_DIGEST_QOP_AUTH))
362 g_warn_if_reached ();
363 g_checksum_update (checksum, (guchar *)"auth", strlen ("auth"));
364 g_checksum_update (checksum, (guchar *)":", 1);
367 g_checksum_update (checksum, (guchar *)hex_a2, 32);
368 strncpy (response, g_checksum_get_string (checksum), 33);
369 g_checksum_free (checksum);
373 authentication_info_cb (SoupMessage *msg, gpointer data)
375 SoupAuth *auth = data;
376 SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth);
378 GHashTable *auth_params;
381 if (auth != soup_message_get_auth (msg))
384 header = soup_message_headers_get_one (msg->response_headers,
385 soup_auth_is_for_proxy (auth) ?
386 "Proxy-Authentication-Info" :
387 "Authentication-Info");
388 g_return_if_fail (header != NULL);
390 auth_params = soup_header_parse_param_list (header);
394 nextnonce = g_strdup (g_hash_table_lookup (auth_params, "nextnonce"));
396 g_free (priv->nonce);
397 priv->nonce = nextnonce;
400 soup_header_free_param_list (auth_params);
404 soup_auth_digest_get_authorization (SoupAuth *auth, SoupMessage *msg)
406 SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth);
407 char response[33], *token;
408 char *url, *algorithm;
412 uri = soup_message_get_uri (msg);
413 g_return_val_if_fail (uri != NULL, NULL);
414 url = soup_uri_to_string (uri, TRUE);
416 soup_auth_digest_compute_response (msg->method, url, priv->hex_a1,
417 priv->qop, priv->nonce,
418 priv->cnonce, priv->nc,
421 out = g_string_new ("Digest ");
423 soup_header_g_string_append_param_quoted (out, "username", priv->user);
424 g_string_append (out, ", ");
425 soup_header_g_string_append_param_quoted (out, "realm", auth->realm);
426 g_string_append (out, ", ");
427 soup_header_g_string_append_param_quoted (out, "nonce", priv->nonce);
428 g_string_append (out, ", ");
429 soup_header_g_string_append_param_quoted (out, "uri", url);
430 g_string_append (out, ", ");
431 algorithm = soup_auth_digest_get_algorithm (priv->algorithm);
432 g_string_append_printf (out, "algorithm=%s", algorithm);
434 g_string_append (out, ", ");
435 soup_header_g_string_append_param_quoted (out, "response", response);
438 g_string_append (out, ", ");
439 soup_header_g_string_append_param_quoted (out, "opaque", priv->opaque);
443 char *qop = soup_auth_digest_get_qop (priv->qop);
445 g_string_append (out, ", ");
446 soup_header_g_string_append_param_quoted (out, "cnonce", priv->cnonce);
447 g_string_append_printf (out, ", nc=%.8x, qop=%s",
456 token = g_string_free (out, FALSE);
458 soup_message_add_header_handler (msg,
460 soup_auth_is_for_proxy (auth) ?
461 "Proxy-Authentication-Info" :
462 "Authentication-Info",
463 G_CALLBACK (authentication_info_cb),
469 soup_auth_digest_class_init (SoupAuthDigestClass *auth_digest_class)
471 SoupAuthClass *auth_class = SOUP_AUTH_CLASS (auth_digest_class);
472 GObjectClass *object_class = G_OBJECT_CLASS (auth_digest_class);
474 g_type_class_add_private (auth_digest_class, sizeof (SoupAuthDigestPrivate));
476 auth_class->scheme_name = "Digest";
477 auth_class->strength = 5;
479 auth_class->get_protection_space = soup_auth_digest_get_protection_space;
480 auth_class->update = soup_auth_digest_update;
481 auth_class->authenticate = soup_auth_digest_authenticate;
482 auth_class->is_authenticated = soup_auth_digest_is_authenticated;
483 auth_class->get_authorization = soup_auth_digest_get_authorization;
485 object_class->finalize = soup_auth_digest_finalize;