1 /* vi: set et sw=4 ts=4 cino=t0,(0: */
2 /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
4 * This file is part of gsignond
6 * Copyright (C) 2013 Intel Corporation.
8 * Contact: Imran Zaman <imran.zaman@intel.com>
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
29 #include <gsignond/gsignond-plugin-interface.h>
30 #include "gsignond-digest-plugin.h"
31 #include <gsignond/gsignond-error.h>
32 #include <gsignond/gsignond-log.h>
33 #include <gsignond/gsignond-utils.h>
36 * SECTION:gsignond-digest-plugin
37 * @short_description: a plugin that performs HTTP Digest authentication
38 * @include: gsignond/gsignond-digest-plugin.h
40 * #GSignondDigestPlugin performs HTTP Digest authentication without exposing
41 * the password to the application. Digest authentication is described in
42 * <ulink url="http://tools.ietf.org/html/rfc2617">RFC 2617</ulink>.
44 * gsignond_plugin_request_initial() @session_data parameter should include
45 * the following string items, whose meaning is described in the RFC:
46 * - username and secret. If they are absent, they are requested from the user
48 * - realm, allowed realms, "Algo", "Nonce", "Method", "DigestUri" - mandatory items.
49 * - "NonceCount", "Qop", "HEntity". "NonceCount" must be present if "Qop" is
50 * present, "HEntity" must be present if "Qop" is present and set to "auth-int".
52 * If the plugin has all the data to calculate the digest, it issues
53 * #GSignondPlugin::response-final signal. @session_data in that signal contains
54 * the username, "CNonce" item and the digest value under the "Response" key.
56 * If some of the data is incorrect or not available, #GSignondPlugin::error
57 * signal is issued instead.
59 * #GSignondPlugin:type property is set to "digest", and #GSignondPlugin:mechanisms
60 * property contains a single entry "digest".
63 * GSignondDigestPlugin:
65 * Opaque #GSignondDigestPlugin data structure.
68 * GSignondDigestPluginClass:
69 * @parent_class: the parent class structure
71 * Opaque #GSignondDigestPluginClass data structure.
75 static void gsignond_plugin_interface_init (GSignondPluginInterface *iface);
77 G_DEFINE_TYPE_WITH_CODE (GSignondDigestPlugin, gsignond_digest_plugin,
79 G_IMPLEMENT_INTERFACE (GSIGNOND_TYPE_PLUGIN,
80 gsignond_plugin_interface_init));
82 #define GSIGNOND_DIGEST_PLUGIN_GET_PRIVATE(obj) \
83 (G_TYPE_INSTANCE_GET_PRIVATE ((obj),\
84 GSIGNOND_TYPE_DIGEST_PLUGIN, \
85 GSignondDigestPluginPrivate))
87 #define DATA_SET_VALUE(data, key, value) \
89 gsignond_dictionary_set_string(data, key, value); \
91 #define TO_GUCHAR(data) ((const guchar*)data)
93 struct _GSignondDigestPluginPrivate
95 GSignondSessionData *session_data;
99 _gsignond_digest_plugin_compute_md5_digest (
101 const gchar* username,
105 const gchar* nonce_count,
109 const gchar* digest_uri,
110 const gchar* hentity)
112 GChecksum *a1 = NULL, *a2 = NULL, *response = NULL;
113 const gchar *ha1 = NULL, *ha2 = NULL;
114 gchar *hresponse = NULL;
116 a1 = g_checksum_new (G_CHECKSUM_MD5);
117 g_checksum_update (a1, TO_GUCHAR(username), strlen(username));
118 g_checksum_update (a1, TO_GUCHAR(":"), 1);
119 g_checksum_update (a1, TO_GUCHAR(realm), strlen(realm));
120 g_checksum_update (a1, TO_GUCHAR(":"), 1);
121 g_checksum_update (a1, TO_GUCHAR(secret), strlen(secret));
123 if (g_strcmp0 (algo, "md5-sess") == 0) {
124 GChecksum *a1_sess = NULL;
125 a1_sess = g_checksum_new (G_CHECKSUM_MD5);
126 ha1 = g_checksum_get_string (a1);
127 g_checksum_update (a1_sess, TO_GUCHAR(ha1), strlen(ha1));
128 g_checksum_update (a1_sess, TO_GUCHAR(":"), 1);
129 g_checksum_update (a1_sess, TO_GUCHAR(nonce), strlen(nonce));
130 g_checksum_update (a1_sess, TO_GUCHAR(":"), 1);
131 g_checksum_update (a1_sess, TO_GUCHAR(cnonce), strlen(cnonce));
132 g_checksum_free (a1);
136 a2 = g_checksum_new (G_CHECKSUM_MD5);
137 g_checksum_update (a2, TO_GUCHAR(method), strlen(method));
138 g_checksum_update (a2, TO_GUCHAR(":"), 1);
139 g_checksum_update (a2, TO_GUCHAR(digest_uri), strlen(digest_uri));
140 if (qop && g_strcmp0 (qop, "auth-int") == 0 && hentity) {
141 g_checksum_update (a2, TO_GUCHAR(":"), 1);
142 g_checksum_update (a2, TO_GUCHAR(hentity), strlen(hentity));
144 ha1 = g_checksum_get_string (a1);
145 ha2 = g_checksum_get_string (a2);
147 response = g_checksum_new (G_CHECKSUM_MD5);
148 g_checksum_update (response, TO_GUCHAR(ha1), strlen(ha1));
149 g_checksum_update (response, TO_GUCHAR(":"), 1);
150 g_checksum_update (response, TO_GUCHAR(nonce), strlen(nonce));
151 g_checksum_update (response, TO_GUCHAR(":"), 1);
153 g_checksum_update (response, TO_GUCHAR(nonce_count),
154 strlen(nonce_count));
155 g_checksum_update (response, TO_GUCHAR(":"), 1);
156 g_checksum_update (response, TO_GUCHAR(cnonce), strlen(cnonce));
157 g_checksum_update (response, TO_GUCHAR(":"), 1);
158 g_checksum_update (response, TO_GUCHAR(qop), strlen(qop));
159 g_checksum_update (response, TO_GUCHAR(":"), 1);
161 g_checksum_update (response, TO_GUCHAR(ha2), strlen(ha2));
162 hresponse = g_strdup(g_checksum_get_string (response));
163 g_checksum_free (response);
164 g_checksum_free (a2);
165 g_checksum_free (a1);
170 gsignond_digest_plugin_cancel (GSignondPlugin *self)
172 GError* error = g_error_new(GSIGNOND_ERROR,
173 GSIGNOND_ERROR_SESSION_CANCELED,
174 "Session cancelled");
175 gsignond_plugin_error (self, error);
180 gsignond_digest_plugin_request (
181 GSignondPlugin *self,
182 GSignondSessionData *session_data)
186 /* difference with g_strcmp0() here is that two NULLs don't compare equal */
188 _compare_realm (gconstpointer a, gconstpointer b, gpointer user_data)
190 const gchar *realm1 = (const gchar *) a;
191 const gchar *realm2 = (const gchar *) b;
198 return g_strcmp0 (realm1, realm2);
202 _gsignond_digest_plugin_return_digest (GSignondPlugin *plugin,
203 const gchar *username,
205 GSignondDictionary *session_data)
207 g_return_if_fail (plugin != NULL);
208 g_return_if_fail (GSIGNOND_IS_DIGEST_PLUGIN (plugin));
210 GSignondSessionData *response = NULL;
212 GSequence* allowed_realms =
213 gsignond_session_data_get_allowed_realms (session_data);
214 const gchar* realm = gsignond_session_data_get_realm (session_data);
215 const gchar* algo = gsignond_dictionary_get_string (session_data,
217 const gchar* nonce = gsignond_dictionary_get_string (session_data,
219 const gchar* nonce_count = gsignond_dictionary_get_string (session_data,
221 const gchar* qop = gsignond_dictionary_get_string (session_data,
223 const gchar* method = gsignond_dictionary_get_string (session_data,
225 const gchar* digest_uri = gsignond_dictionary_get_string (session_data,
227 const gchar* hentity = gsignond_dictionary_get_string (session_data,
230 if (!allowed_realms) {
231 GError* error = g_error_new (GSIGNOND_ERROR,
232 GSIGNOND_ERROR_MISSING_DATA,
233 "Missing realm list");
234 gsignond_plugin_error (plugin, error);
235 g_error_free (error);
238 iter = g_sequence_lookup (allowed_realms,
242 g_sequence_free (allowed_realms);
244 GError* error = g_error_new (GSIGNOND_ERROR,
245 GSIGNOND_ERROR_NOT_AUTHORIZED,
246 "Unauthorized realm");
247 gsignond_plugin_error (plugin, error);
248 g_error_free (error);
252 gchar *cnonce = gsignond_generate_nonce ();
254 GError* error = g_error_new (GSIGNOND_ERROR,
255 GSIGNOND_ERROR_MISSING_DATA,
256 "Error in generating nonce");
257 gsignond_plugin_error (plugin, error);
258 g_error_free (error);
262 if ((!realm || !algo || !nonce || !method || !digest_uri)
263 || (qop && g_strcmp0 (qop, "auth-int") == 0 && !hentity)
264 || (qop && !nonce_count)) {
265 GError* error = g_error_new (GSIGNOND_ERROR,
266 GSIGNOND_ERROR_MISSING_DATA, "Missing Session Data");
267 gsignond_plugin_error (plugin, error);
268 g_error_free (error);
271 gchar *digest = _gsignond_digest_plugin_compute_md5_digest(algo,
272 username, realm, secret, nonce, nonce_count, cnonce, qop, method,
273 digest_uri, hentity);
275 response = gsignond_dictionary_new();
276 gsignond_session_data_set_username(response, username);
277 gsignond_dictionary_set_string(response, "CNonce", cnonce);
279 gsignond_dictionary_set_string(response, "Response", digest);
282 gsignond_plugin_response_final(plugin, response);
283 gsignond_dictionary_unref(response);
287 gsignond_digest_plugin_request_initial (
288 GSignondPlugin *plugin,
289 GSignondSessionData *session_data,
290 GSignondDictionary *identity_method_cache,
291 const gchar *mechanism)
293 g_return_if_fail (plugin != NULL);
294 g_return_if_fail (GSIGNOND_IS_DIGEST_PLUGIN (plugin));
296 GSignondDigestPlugin *self = GSIGNOND_DIGEST_PLUGIN (plugin);
297 GSignondDigestPluginPrivate *priv = self->priv;
299 g_return_if_fail (priv != NULL);
301 const gchar *username = gsignond_session_data_get_username(session_data);
302 const gchar *secret = gsignond_session_data_get_secret(session_data);
304 if (username != NULL && secret != NULL) {
305 _gsignond_digest_plugin_return_digest(plugin, username, secret, session_data);
309 if (priv->session_data) {
310 gsignond_dictionary_unref (priv->session_data);
311 priv->session_data = NULL;
313 gsignond_dictionary_ref (session_data);
314 priv->session_data = session_data;
316 GSignondSignonuiData *user_action_data = gsignond_dictionary_new ();
317 DATA_SET_VALUE (user_action_data, "Realm",
318 gsignond_session_data_get_realm (session_data));
319 DATA_SET_VALUE (user_action_data, "DigestUri",
320 gsignond_dictionary_get_string (session_data, "DigestUri"));
321 gsignond_signonui_data_set_query_username (user_action_data, TRUE);
322 gsignond_signonui_data_set_query_password (user_action_data, TRUE);
323 gsignond_plugin_user_action_required (plugin, user_action_data);
324 gsignond_dictionary_unref (user_action_data);
328 gsignond_digest_plugin_user_action_finished (
329 GSignondPlugin *plugin,
330 GSignondSignonuiData *signonui_data)
332 g_return_if_fail (plugin != NULL);
333 g_return_if_fail (GSIGNOND_IS_DIGEST_PLUGIN (plugin));
335 GSignondDigestPlugin *self = GSIGNOND_DIGEST_PLUGIN (plugin);
336 GSignondDigestPluginPrivate *priv = self->priv;
337 g_return_if_fail (priv != NULL);
339 GSignondSessionData *session_data = NULL;
340 GSignondSignonuiError query_error;
341 gboolean res = gsignond_signonui_data_get_query_error(signonui_data,
344 GError* error = g_error_new(GSIGNOND_ERROR,
345 GSIGNOND_ERROR_USER_INTERACTION,
346 "userActionFinished did not return an error value");
347 gsignond_plugin_error (plugin, error);
351 const gchar* username = gsignond_signonui_data_get_username(signonui_data);
352 const gchar* secret = gsignond_signonui_data_get_password(signonui_data);
354 session_data = priv->session_data;
356 if (query_error == SIGNONUI_ERROR_NONE &&
359 session_data != NULL) {
360 _gsignond_digest_plugin_return_digest(plugin, username, secret, session_data);
361 } else if (query_error == SIGNONUI_ERROR_CANCELED) {
362 gsignond_digest_plugin_cancel (plugin);
364 GError* error = g_error_new(GSIGNOND_ERROR,
365 GSIGNOND_ERROR_USER_INTERACTION, "userActionFinished error: %d",
367 gsignond_plugin_error (plugin, error);
373 gsignond_digest_plugin_refresh (
374 GSignondPlugin *self,
375 GSignondSessionData *session_data)
377 gsignond_plugin_refreshed(self, session_data);
381 gsignond_plugin_interface_init (GSignondPluginInterface *iface)
383 iface->cancel = gsignond_digest_plugin_cancel;
384 iface->request_initial = gsignond_digest_plugin_request_initial;
385 iface->request = gsignond_digest_plugin_request;
386 iface->user_action_finished = gsignond_digest_plugin_user_action_finished;
387 iface->refresh = gsignond_digest_plugin_refresh;
391 gsignond_digest_plugin_init (GSignondDigestPlugin *self)
393 GSignondDigestPluginPrivate *priv =
394 GSIGNOND_DIGEST_PLUGIN_GET_PRIVATE (self);
397 priv->session_data = NULL;
409 gsignond_digest_plugin_set_property (
418 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
424 gsignond_digest_plugin_get_property (
430 GSignondDigestPlugin *digest_plugin = GSIGNOND_DIGEST_PLUGIN (object);
431 (void) digest_plugin;
432 gchar *mechanisms[] = { "digest", NULL };
437 g_value_set_string (value, "digest");
439 case PROP_MECHANISMS:
440 g_value_set_boxed (value, mechanisms);
443 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
449 gsignond_digest_plugin_dispose (GObject *gobject)
451 g_return_if_fail (GSIGNOND_IS_DIGEST_PLUGIN (gobject));
452 GSignondDigestPlugin *self = GSIGNOND_DIGEST_PLUGIN (gobject);
453 g_return_if_fail (self->priv != NULL);
455 if (self->priv->session_data) {
456 gsignond_dictionary_unref (self->priv->session_data);
457 self->priv->session_data = NULL;
460 /* Chain up to the parent class */
461 G_OBJECT_CLASS (gsignond_digest_plugin_parent_class)->dispose (
466 gsignond_digest_plugin_class_init (GSignondDigestPluginClass *klass)
468 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
470 gobject_class->set_property = gsignond_digest_plugin_set_property;
471 gobject_class->get_property = gsignond_digest_plugin_get_property;
472 gobject_class->dispose = gsignond_digest_plugin_dispose;
474 g_object_class_override_property (gobject_class, PROP_TYPE, "type");
475 g_object_class_override_property (gobject_class, PROP_MECHANISMS,
478 g_type_class_add_private (klass, sizeof (GSignondDigestPluginPrivate));