325f93b9c04c878ecd3be780bac654df813d8f9d
[profile/ivi/gsignond.git] / src / plugins / digest / gsignond-digest-plugin.c
1 /* vi: set et sw=4 ts=4 cino=t0,(0: */
2 /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 /*
4  * This file is part of gsignond
5  *
6  * Copyright (C) 2013 Intel Corporation.
7  *
8  * Contact: Imran Zaman <imran.zaman@intel.com>
9  *
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.
14  *
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.
19  *
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
23  * 02110-1301 USA
24  */
25 #include <string.h>
26 #include <time.h>
27 #include <fcntl.h>
28 #include <unistd.h>
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>
34
35 /**
36  * SECTION:gsignond-digest-plugin
37  * @short_description: a plugin that performs HTTP Digest authentication
38  * @include: gsignond/gsignond-digest-plugin.h
39  *
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>.
43  * 
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
47  * via gSSO UI.
48  * - realm, "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".
51  * 
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.
55  * 
56  * If some of the data is incorrect or not available, #GSignondPlugin::error
57  * signal is issued instead.
58  * 
59  * #GSignondPlugin:type property is set to "digest", and #GSignondPlugin:mechanisms 
60  * property contains a single entry "digest".
61  */
62 /**
63  * GSignondDigestPlugin:
64  *
65  * Opaque #GSignondDigestPlugin data structure.
66  */
67 /**
68  * GSignondDigestPluginClass:
69  * @parent_class: the parent class structure
70  *
71  * Opaque #GSignondDigestPluginClass data structure.
72  */
73
74
75 static void gsignond_plugin_interface_init (GSignondPluginInterface *iface);
76
77 G_DEFINE_TYPE_WITH_CODE (GSignondDigestPlugin, gsignond_digest_plugin,
78                          G_TYPE_OBJECT,
79                          G_IMPLEMENT_INTERFACE (GSIGNOND_TYPE_PLUGIN,
80                                                 gsignond_plugin_interface_init));
81
82 #define GSIGNOND_DIGEST_PLUGIN_GET_PRIVATE(obj) \
83                                        (G_TYPE_INSTANCE_GET_PRIVATE ((obj),\
84                                         GSIGNOND_TYPE_DIGEST_PLUGIN, \
85                                         GSignondDigestPluginPrivate))
86
87 #define DATA_SET_VALUE(data, key, value) \
88     if (value) { \
89         gsignond_dictionary_set_string(data, key, value); \
90     }
91 #define TO_GUCHAR(data) ((const guchar*)data)
92
93 struct _GSignondDigestPluginPrivate
94 {
95     GSignondSessionData *session_data;
96 };
97
98 static gchar *
99 _gsignond_digest_plugin_compute_md5_digest (
100         const gchar* algo,
101         const gchar* username,
102         const gchar* realm,
103         const gchar* secret,
104         const gchar* nonce,
105         const gchar* nonce_count,
106         const gchar* cnonce,
107         const gchar* qop,
108         const gchar* method,
109         const gchar* digest_uri,
110         const gchar* hentity)
111 {
112     GChecksum *a1 = NULL, *a2 = NULL, *response = NULL;
113     const gchar *ha1 = NULL, *ha2 = NULL;
114     gchar *hresponse = NULL;
115
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));
122
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);
133         a1 = a1_sess;
134     }
135
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));
143     }
144     ha1 = g_checksum_get_string (a1);
145     ha2 = g_checksum_get_string (a2);
146
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);
152     if (qop) {
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);
160     }
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);
166     return hresponse;
167 }
168
169 static void
170 gsignond_digest_plugin_cancel (GSignondPlugin *self)
171 {
172     GError* error = g_error_new(GSIGNOND_ERROR,
173                                 GSIGNOND_ERROR_SESSION_CANCELED,
174                                 "Session cancelled");
175     gsignond_plugin_error (self, error);
176     g_error_free(error);
177 }
178
179 static void
180 gsignond_digest_plugin_request (
181     GSignondPlugin *self,
182     GSignondSessionData *session_data)
183 {
184 }
185
186 static void
187 _gsignond_digest_plugin_return_digest(GSignondPlugin *plugin,
188                                      const gchar *username,
189                                      const gchar *secret,
190                                      GSignondDictionary *session_data)
191 {
192     g_return_if_fail (plugin != NULL);
193     g_return_if_fail (GSIGNOND_IS_DIGEST_PLUGIN (plugin));
194
195     GSignondSessionData *response = NULL;
196     const gchar* realm = gsignond_session_data_get_realm (session_data);
197     const gchar* algo = gsignond_dictionary_get_string (session_data,
198                 "Algo");
199     const gchar* nonce = gsignond_dictionary_get_string (session_data,
200                 "Nonce");
201     const gchar* nonce_count = gsignond_dictionary_get_string (session_data,
202                 "NonceCount");
203     const gchar* qop = gsignond_dictionary_get_string (session_data,
204                 "Qop");
205     const gchar* method = gsignond_dictionary_get_string (session_data,
206                 "Method");
207     const gchar* digest_uri = gsignond_dictionary_get_string (session_data,
208                 "DigestUri");
209     const gchar* hentity = gsignond_dictionary_get_string (session_data,
210                 "HEntity");
211     gchar *cnonce = gsignond_generate_nonce ();
212     if (!cnonce) {
213         GError* error = g_error_new (GSIGNOND_ERROR,
214                         GSIGNOND_ERROR_MISSING_DATA, "Error in generating nonce");
215         gsignond_plugin_error (plugin, error);
216         g_error_free (error);
217         return;
218     }
219
220     if ((!realm || !algo  || !nonce  || !method  || !digest_uri)
221         || (qop && g_strcmp0 (qop, "auth-int") == 0 && !hentity)
222         || (qop && !nonce_count)) {
223         GError* error = g_error_new (GSIGNOND_ERROR,
224                         GSIGNOND_ERROR_MISSING_DATA, "Missing Session Data");
225         gsignond_plugin_error (plugin, error);
226         g_error_free (error);
227         return;
228     }
229     gchar *digest = _gsignond_digest_plugin_compute_md5_digest(algo,
230             username,realm, secret, nonce, nonce_count, cnonce, qop, method,
231             digest_uri, hentity);
232
233     response = gsignond_dictionary_new();
234     gsignond_session_data_set_username(response, username);
235     gsignond_dictionary_set_string(response, "CNonce", cnonce);
236     g_free (cnonce);
237     gsignond_dictionary_set_string(response, "Response", digest);
238     g_free(digest);
239
240     gsignond_plugin_response_final(plugin, response);
241     gsignond_dictionary_unref(response);
242 }
243
244 static void
245 gsignond_digest_plugin_request_initial (
246     GSignondPlugin *plugin,
247     GSignondSessionData *session_data,
248     GSignondDictionary *identity_method_cache,
249     const gchar *mechanism)
250 {
251     g_return_if_fail (plugin != NULL);
252     g_return_if_fail (GSIGNOND_IS_DIGEST_PLUGIN (plugin));
253
254     GSignondDigestPlugin *self = GSIGNOND_DIGEST_PLUGIN (plugin);
255     GSignondDigestPluginPrivate *priv = self->priv;
256
257     g_return_if_fail (priv != NULL);
258
259     const gchar *username = gsignond_session_data_get_username(session_data);
260     const gchar *secret = gsignond_session_data_get_secret(session_data);
261     
262     if (username != NULL && secret != NULL) {
263         _gsignond_digest_plugin_return_digest(plugin, username, secret, session_data);
264         return;
265     }
266
267     if (priv->session_data) {
268         gsignond_dictionary_unref (priv->session_data);
269         priv->session_data = NULL;
270     }
271     gsignond_dictionary_ref (session_data);
272     priv->session_data = session_data;
273
274     GSignondSignonuiData *user_action_data = gsignond_dictionary_new ();
275     DATA_SET_VALUE (user_action_data, "Realm", 
276                     gsignond_session_data_get_realm (session_data));
277     DATA_SET_VALUE (user_action_data, "DigestUri", 
278                     gsignond_dictionary_get_string (session_data, "DigestUri"));
279     gsignond_signonui_data_set_query_username (user_action_data, TRUE);
280     gsignond_signonui_data_set_query_password (user_action_data, TRUE);
281     gsignond_plugin_user_action_required (plugin, user_action_data);
282     gsignond_dictionary_unref (user_action_data);
283 }
284
285 static void
286 gsignond_digest_plugin_user_action_finished (
287     GSignondPlugin *plugin,
288     GSignondSignonuiData *signonui_data)
289 {
290     g_return_if_fail (plugin != NULL);
291     g_return_if_fail (GSIGNOND_IS_DIGEST_PLUGIN (plugin));
292
293     GSignondDigestPlugin *self = GSIGNOND_DIGEST_PLUGIN (plugin);
294     GSignondDigestPluginPrivate *priv = self->priv;
295     g_return_if_fail (priv != NULL);
296
297     GSignondSessionData *session_data = NULL;
298     GSignondSignonuiError query_error;
299     gboolean res = gsignond_signonui_data_get_query_error(signonui_data,
300             &query_error);
301     if (res == FALSE) {
302         GError* error = g_error_new(GSIGNOND_ERROR,
303                                 GSIGNOND_ERROR_USER_INTERACTION,
304                                 "userActionFinished did not return an error value");
305         gsignond_plugin_error (plugin, error);
306         g_error_free(error);
307     }
308
309     const gchar* username = gsignond_signonui_data_get_username(signonui_data);
310     const gchar* secret = gsignond_signonui_data_get_password(signonui_data);
311     
312     session_data = priv->session_data;
313
314     if (query_error == SIGNONUI_ERROR_NONE &&
315         username != NULL && 
316         secret != NULL &&
317         session_data != NULL) {
318         _gsignond_digest_plugin_return_digest(plugin, username, secret, session_data);
319     } else if (query_error == SIGNONUI_ERROR_CANCELED) {
320         gsignond_digest_plugin_cancel (plugin);
321     } else {
322         GError* error = g_error_new(GSIGNOND_ERROR, 
323                 GSIGNOND_ERROR_USER_INTERACTION, "userActionFinished error: %d",
324                 query_error);
325         gsignond_plugin_error (plugin, error);
326         g_error_free(error);
327     }
328 }
329
330 static void
331 gsignond_digest_plugin_refresh (
332     GSignondPlugin *self, 
333     GSignondSessionData *session_data)
334 {
335     gsignond_plugin_refreshed(self, session_data);
336 }
337
338 static void
339 gsignond_plugin_interface_init (GSignondPluginInterface *iface)
340 {
341     iface->cancel = gsignond_digest_plugin_cancel;
342     iface->request_initial = gsignond_digest_plugin_request_initial;
343     iface->request = gsignond_digest_plugin_request;
344     iface->user_action_finished = gsignond_digest_plugin_user_action_finished;
345     iface->refresh = gsignond_digest_plugin_refresh;
346 }
347
348 static void
349 gsignond_digest_plugin_init (GSignondDigestPlugin *self)
350 {
351     GSignondDigestPluginPrivate *priv =
352         GSIGNOND_DIGEST_PLUGIN_GET_PRIVATE (self);
353     self->priv = priv;
354
355     priv->session_data = NULL;
356
357 }
358
359 enum
360 {
361     PROP_0,
362     PROP_TYPE,
363     PROP_MECHANISMS
364 };
365
366 static void
367 gsignond_digest_plugin_set_property (
368         GObject      *object,
369         guint         property_id,
370         const GValue *value,
371         GParamSpec   *pspec)
372 {
373     switch (property_id)
374     {
375         default:
376             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
377             break;
378     }
379 }
380
381 static void
382 gsignond_digest_plugin_get_property (
383         GObject    *object,
384         guint       prop_id,
385         GValue     *value,
386         GParamSpec *pspec)
387 {
388     GSignondDigestPlugin *digest_plugin = GSIGNOND_DIGEST_PLUGIN (object);
389     (void) digest_plugin;
390     gchar *mechanisms[] = { "digest", NULL };
391     
392     switch (prop_id)
393     {
394         case PROP_TYPE:
395             g_value_set_string (value, "digest");
396             break;
397         case PROP_MECHANISMS:
398             g_value_set_boxed (value, mechanisms);
399             break;
400         default:
401             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
402             break;
403     }
404 }
405
406 static void
407 gsignond_digest_plugin_dispose (GObject *gobject)
408 {
409     g_return_if_fail (GSIGNOND_IS_DIGEST_PLUGIN (gobject));
410     GSignondDigestPlugin *self = GSIGNOND_DIGEST_PLUGIN (gobject);
411     g_return_if_fail (self->priv != NULL);
412
413     if (self->priv->session_data) {
414         gsignond_dictionary_unref (self->priv->session_data);
415         self->priv->session_data = NULL;
416     }
417
418     /* Chain up to the parent class */
419     G_OBJECT_CLASS (gsignond_digest_plugin_parent_class)->dispose (
420             gobject);
421 }
422
423 static void
424 gsignond_digest_plugin_class_init (GSignondDigestPluginClass *klass)
425 {
426     GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
427     
428     gobject_class->set_property = gsignond_digest_plugin_set_property;
429     gobject_class->get_property = gsignond_digest_plugin_get_property;
430     gobject_class->dispose = gsignond_digest_plugin_dispose;
431     
432     g_object_class_override_property (gobject_class, PROP_TYPE, "type");
433     g_object_class_override_property (gobject_class, PROP_MECHANISMS,
434             "mechanisms");
435
436     g_type_class_add_private (klass, sizeof (GSignondDigestPluginPrivate));
437 }