Update to upstream 1.0.1
[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, 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".
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 /* difference with g_strcmp0() here is that two NULLs don't compare equal */
187 static gint
188 _compare_realm (gconstpointer a, gconstpointer b, gpointer user_data)
189 {
190     const gchar *realm1 = (const gchar *) a;
191     const gchar *realm2 = (const gchar *) b;
192     (void) user_data;
193     if (realm1 == NULL)
194         return -1;
195     if (realm2 == NULL)
196         return 1;
197
198     return g_strcmp0 (realm1, realm2);
199 }
200
201 static void
202 _gsignond_digest_plugin_return_digest (GSignondPlugin *plugin,
203                                        const gchar *username,
204                                        const gchar *secret,
205                                        GSignondDictionary *session_data)
206 {
207     g_return_if_fail (plugin != NULL);
208     g_return_if_fail (GSIGNOND_IS_DIGEST_PLUGIN (plugin));
209
210     GSignondSessionData *response = NULL;
211     GSequenceIter *iter;
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,
216                 "Algo");
217     const gchar* nonce = gsignond_dictionary_get_string (session_data,
218                 "Nonce");
219     const gchar* nonce_count = gsignond_dictionary_get_string (session_data,
220                 "NonceCount");
221     const gchar* qop = gsignond_dictionary_get_string (session_data,
222                 "Qop");
223     const gchar* method = gsignond_dictionary_get_string (session_data,
224                 "Method");
225     const gchar* digest_uri = gsignond_dictionary_get_string (session_data,
226                 "DigestUri");
227     const gchar* hentity = gsignond_dictionary_get_string (session_data,
228                 "HEntity");
229
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);
236         return;
237     }
238     iter = g_sequence_lookup (allowed_realms,
239                               (gpointer) realm,
240                               _compare_realm,
241                               NULL);
242     g_sequence_free (allowed_realms);
243     if (!iter) {
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);
249         return;
250     }
251
252     gchar *cnonce = gsignond_generate_nonce ();
253     if (!cnonce) {
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);
259         return;
260     }
261
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);
269         return;
270     }
271     gchar *digest = _gsignond_digest_plugin_compute_md5_digest(algo,
272             username, realm, secret, nonce, nonce_count, cnonce, qop, method,
273             digest_uri, hentity);
274
275     response = gsignond_dictionary_new();
276     gsignond_session_data_set_username(response, username);
277     gsignond_dictionary_set_string(response, "CNonce", cnonce);
278     g_free (cnonce);
279     gsignond_dictionary_set_string(response, "Response", digest);
280     g_free(digest);
281
282     gsignond_plugin_response_final(plugin, response);
283     gsignond_dictionary_unref(response);
284 }
285
286 static void
287 gsignond_digest_plugin_request_initial (
288     GSignondPlugin *plugin,
289     GSignondSessionData *session_data,
290     GSignondDictionary *identity_method_cache,
291     const gchar *mechanism)
292 {
293     g_return_if_fail (plugin != NULL);
294     g_return_if_fail (GSIGNOND_IS_DIGEST_PLUGIN (plugin));
295
296     GSignondDigestPlugin *self = GSIGNOND_DIGEST_PLUGIN (plugin);
297     GSignondDigestPluginPrivate *priv = self->priv;
298
299     g_return_if_fail (priv != NULL);
300
301     const gchar *username = gsignond_session_data_get_username(session_data);
302     const gchar *secret = gsignond_session_data_get_secret(session_data);
303     
304     if (username != NULL && secret != NULL) {
305         _gsignond_digest_plugin_return_digest(plugin, username, secret, session_data);
306         return;
307     }
308
309     if (priv->session_data) {
310         gsignond_dictionary_unref (priv->session_data);
311         priv->session_data = NULL;
312     }
313     gsignond_dictionary_ref (session_data);
314     priv->session_data = session_data;
315
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);
325 }
326
327 static void
328 gsignond_digest_plugin_user_action_finished (
329     GSignondPlugin *plugin,
330     GSignondSignonuiData *signonui_data)
331 {
332     g_return_if_fail (plugin != NULL);
333     g_return_if_fail (GSIGNOND_IS_DIGEST_PLUGIN (plugin));
334
335     GSignondDigestPlugin *self = GSIGNOND_DIGEST_PLUGIN (plugin);
336     GSignondDigestPluginPrivate *priv = self->priv;
337     g_return_if_fail (priv != NULL);
338
339     GSignondSessionData *session_data = NULL;
340     GSignondSignonuiError query_error;
341     gboolean res = gsignond_signonui_data_get_query_error(signonui_data,
342             &query_error);
343     if (res == FALSE) {
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);
348         g_error_free(error);
349     }
350
351     const gchar* username = gsignond_signonui_data_get_username(signonui_data);
352     const gchar* secret = gsignond_signonui_data_get_password(signonui_data);
353     
354     session_data = priv->session_data;
355
356     if (query_error == SIGNONUI_ERROR_NONE &&
357         username != NULL && 
358         secret != NULL &&
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);
363     } else {
364         GError* error = g_error_new(GSIGNOND_ERROR, 
365                 GSIGNOND_ERROR_USER_INTERACTION, "userActionFinished error: %d",
366                 query_error);
367         gsignond_plugin_error (plugin, error);
368         g_error_free(error);
369     }
370 }
371
372 static void
373 gsignond_digest_plugin_refresh (
374     GSignondPlugin *self, 
375     GSignondSessionData *session_data)
376 {
377     gsignond_plugin_refreshed(self, session_data);
378 }
379
380 static void
381 gsignond_plugin_interface_init (GSignondPluginInterface *iface)
382 {
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;
388 }
389
390 static void
391 gsignond_digest_plugin_init (GSignondDigestPlugin *self)
392 {
393     GSignondDigestPluginPrivate *priv =
394         GSIGNOND_DIGEST_PLUGIN_GET_PRIVATE (self);
395     self->priv = priv;
396
397     priv->session_data = NULL;
398
399 }
400
401 enum
402 {
403     PROP_0,
404     PROP_TYPE,
405     PROP_MECHANISMS
406 };
407
408 static void
409 gsignond_digest_plugin_set_property (
410         GObject      *object,
411         guint         property_id,
412         const GValue *value,
413         GParamSpec   *pspec)
414 {
415     switch (property_id)
416     {
417         default:
418             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
419             break;
420     }
421 }
422
423 static void
424 gsignond_digest_plugin_get_property (
425         GObject    *object,
426         guint       prop_id,
427         GValue     *value,
428         GParamSpec *pspec)
429 {
430     GSignondDigestPlugin *digest_plugin = GSIGNOND_DIGEST_PLUGIN (object);
431     (void) digest_plugin;
432     gchar *mechanisms[] = { "digest", NULL };
433     
434     switch (prop_id)
435     {
436         case PROP_TYPE:
437             g_value_set_string (value, "digest");
438             break;
439         case PROP_MECHANISMS:
440             g_value_set_boxed (value, mechanisms);
441             break;
442         default:
443             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
444             break;
445     }
446 }
447
448 static void
449 gsignond_digest_plugin_dispose (GObject *gobject)
450 {
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);
454
455     if (self->priv->session_data) {
456         gsignond_dictionary_unref (self->priv->session_data);
457         self->priv->session_data = NULL;
458     }
459
460     /* Chain up to the parent class */
461     G_OBJECT_CLASS (gsignond_digest_plugin_parent_class)->dispose (
462             gobject);
463 }
464
465 static void
466 gsignond_digest_plugin_class_init (GSignondDigestPluginClass *klass)
467 {
468     GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
469     
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;
473     
474     g_object_class_override_property (gobject_class, PROP_TYPE, "type");
475     g_object_class_override_property (gobject_class, PROP_MECHANISMS,
476             "mechanisms");
477
478     g_type_class_add_private (klass, sizeof (GSignondDigestPluginPrivate));
479 }