GSignondSaslPlugin

GSignondSaslPlugin — SASL authentication plugin for gSSO single sign-on service

Types and Values

Description

The SASL plugin provides a client-side implementation of several commonly used SASL authentication mechanisms: ANONYMOUS, PLAIN, DIGEST-MD5, CRAM-MD5 and SCRAM-SHA-1. The plugin takes a mechanism name, and parameters specific to that mechanism, and (depending on the mechanism) produces a final or an intermidiate response string that the application transmits to the server. If the response string was intermidate, the server should return a challenge string, which is supplied to the plugin, after which another final or intermediate response is produced. If a final response is returned then no further challenges should arrive from the server, and authentication concludes.

SASL framework is specified in RFC 4422.

Specific SASL mechanism specifications are: ANONYMOUS in RFC 4505, PLAIN in RFC 4616, CRAM-MD5 in RFC 2195, DIGEST-MD5 in RFC 2831, SCRAM-SHA-1 in RFC 5802.

The plugin implements the standard GSignondPlugin interface, and after instantiating a plugin object all interactions happen through that interface.

“type” property of the plugin object is set to "sasl".

“mechanisms” property of the plugin object is a list containing the mechanisms above.

Authorization sequence

The authorization sequence begins with issuing gsignond_plugin_request_initial(). The mechanism parameter should be set to one of the mechanisms listed above, and the content of session_data parameter depends on the mechanism and is described in detail below. identity_method_cache parameter is ignored.

The plugin responds to the request with one of the following signals:

  • “response-final” This means the authorization sequence ended successfully, and the final client response, encoded in base64, is delivered in session_data parameter of the signal under "ResponseBase64" key. This signal concludes the sequence. The application then delivers the final response to the server, after which it's able to access the services and resources on the server according to the specific protocol it's implementing.

  • “response” The plugin is requesting to send a response string to the server. The string is also provided in session_data parameter of the signal under "ResponseBase64" key, encoded in base64. The server is then supposed to return a challenge string which the application delivers to the plugin with a gsignond_plugin_request() call via the session_data parameter under "ChallengeBase64" key, encoded in base64. After that there may be another response-challenge cycle, or a final response via “response-final” signal.

  • “error” An error has happened in the authorization sequence and it stops. See below for a description of possible errors.

At any point the application can request to stop the authorization by calling gsignond_plugin_cancel(). The plugin responds with an “error” signal containing a GSIGNOND_ERROR_SESSION_CANCELED error.

Code examples

Example 1. Using various SASL mechanisms

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/* PLEASE NOTE: this example is meant for SASL plugin developers. If you're
 * an application developer who wants to use this plugin, please refer to
 * libgsignon-glib documentation here:
 * http://accounts-sso.gitlab.io/libgsignon-glib
 */
/*
 * Copyright (C) 2012 Intel Corporation.
 *
 * Contact: Alexander Kanavin <alex.kanavin@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#include <gsignond/gsignond-session-data.h>
#include <gsignond/gsignond-plugin-interface.h>
#include <gsignond/gsignond-error.h>
#include <gsignond/gsignond-utils.h>
#include "gsignond-sasl-plugin.h"

static const gchar* allowed_realms[] = { "megahostname", NULL };

//this callback prints the received final response
//the final response should also be sent to the server
static void final_response_callback(GSignondPlugin* plugin, GSignondSessionData* result,
                     gpointer user_data)
{
    const gchar* response = gsignond_dictionary_get_string(GSIGNOND_DICTIONARY(result),
                                                           "ResponseBase64");
    g_print("Authenticated successfully, got final response:\n%s\n",
             response);
}

static void response_callback(GSignondPlugin* plugin, GSignondSessionData* result,
                     gpointer user_data)
{
    //print the received intermediate response
    const gchar* response = gsignond_dictionary_get_string(GSIGNOND_DICTIONARY(result),
                                                           "ResponseBase64");
    g_print("Authenticated successfully, got intermediate response:\n%s\n",
             response);
    
    //here the response should be sent to the server, and the server should
    //respond with a challenge
    //to make the example simpler (and non-functional) we hardcode a challenge
    const gchar* server_challenge = "some challenge";
    
    //submit the challenge to the plugin
    GSignondSessionData* data = gsignond_session_data_new();
    gsignond_dictionary_set_string(GSIGNOND_DICTIONARY(data), "ChallengeBase64", server_challenge);
    gsignond_plugin_request(plugin, data);
    g_object_unref(data);
}

// print an error and exit the mainloop
static void error_callback(GSignondPlugin* plugin, GError* error,
                     gpointer user_data)
{
    g_print("Got an error: %s\n", error->message);
}

static void anonymous_authorization(gpointer plugin)
{
    GSignondSessionData* data = gsignond_session_data_new();

    //fill in necessary data
    gsignond_dictionary_set_string(GSIGNOND_DICTIONARY(data), "AnonymousToken", 
                                   "megauser@example.com");

    //start the authorization
    //any further processing happens in signal callbacks
    gsignond_plugin_request_initial(plugin, data, NULL, "ANONYMOUS");
    g_object_unref(data);
}

static void plain_authorization(gpointer plugin)
{
    GSignondSessionData* data = gsignond_session_data_new();

    //fill in necessary data
    gsignond_session_data_set_username(data, "megauser@example.com");
    gsignond_session_data_set_secret(data, "megapassword");

    //start the authorization
    //any further processing happens in signal callbacks
    gsignond_plugin_request_initial(plugin, data, NULL, "PLAIN");
    g_object_unref(data);
}

static void cram_md5_authorization(gpointer plugin)
{
    GSignondSessionData* data = gsignond_session_data_new();

    //fill in necessary data
    gsignond_session_data_set_username(data, "megauser@example.com");
    gsignond_session_data_set_secret(data, "megapassword");
    //initial server challenge, for simplicty it's hardcoded
    gsignond_dictionary_set_string(GSIGNOND_DICTIONARY(data), "ChallengeBase64", "some challenge");    

    //start the authorization
    //any further processing happens in signal callbacks
    gsignond_plugin_request_initial(plugin, data, NULL, "CRAM-MD5");
    g_object_unref(data);
}

static void digest_md5_authorization(gpointer plugin)
{
    GSignondSessionData* data = gsignond_session_data_new();

    //fill in necessary data
    gsignond_dictionary_set_string(GSIGNOND_DICTIONARY(data), "Service", "megaservice");
    gsignond_dictionary_set_string(GSIGNOND_DICTIONARY(data), "Hostname", "megahostname");
    GSequence* allowed_realms_s = gsignond_copy_array_to_sequence(allowed_realms);
    gsignond_session_data_set_allowed_realms(data, allowed_realms_s);
    g_sequence_free(allowed_realms_s);
    gsignond_session_data_set_username(data, "megauser@example.com");
    gsignond_session_data_set_secret(data, "megapassword");
    //initial server challenge, for simplicty it's hardcoded
    gsignond_dictionary_set_string(GSIGNOND_DICTIONARY(data), "ChallengeBase64", "some challenge");    

    //start the authorization
    //any further processing happens in signal callbacks
    gsignond_plugin_request_initial(plugin, data, NULL, "DIGEST-MD5");
    g_object_unref(data);
}


static void scram_sha1_authorization(gpointer plugin)
{
    GSignondSessionData* data = gsignond_session_data_new();

    //fill in necessary data
    gsignond_session_data_set_username(data, "megauser@example.com");
    gsignond_session_data_set_secret(data, "megapassword");

    //initial server challenge, for simplicty it's hardcoded
    gsignond_dictionary_set_string(GSIGNOND_DICTIONARY(data), "ChallengeBase64", "some challenge");    

    //start the authorization
    //any further processing happens in signal callbacks
    gsignond_plugin_request_initial(plugin, data, NULL, "SCRAM-SHA-1");
    g_object_unref(data);
}


int main (void)
{
#if !GLIB_CHECK_VERSION (2, 36, 0)
    g_type_init ();
#endif

    gpointer plugin = g_object_new(gsignond_sasl_plugin_get_type(), NULL);

    //connect to various signals of the plugin object
    g_signal_connect(plugin, "response-final", G_CALLBACK(final_response_callback), NULL);
    g_signal_connect(plugin, "response", G_CALLBACK(response_callback), NULL);
    g_signal_connect(plugin, "error", G_CALLBACK(error_callback), NULL);

    //how to use various authorization mechanisms
    anonymous_authorization(plugin);
    plain_authorization(plugin);
    cram_md5_authorization(plugin);
    digest_md5_authorization(plugin);
    scram_sha1_authorization(plugin);
        
    g_object_unref(plugin);
    
    return 0;
}

Errors issued via “error” signal

At any point in the authorization process the plugin may issue this signal with an error parameter that is a GError. The error has domain field set to GSIGNOND_ERROR. code field can be one of GSIGNOND_ERROR_NOT_AUTHORIZED (which means an error in the data provided for authorization), GSIGNOND_ERROR_OPERATION_NOT_SUPPORTED (which means there was an error during sasl library initialization), or GSIGNOND_ERROR_WRONG_STATE (which means an incorrect plugin API call was used). message field tells additional details about the exact cause of the error, and it's intended to help programming and debugging, but not meant to be understood by end users directly (although it can be shown to them).

session_data parameter in gsignond_plugin_request_initial()

The session_data parameter contains different mechanism-specific parameters as keys and string values. Here's a list of all possible parameters with explanations for each. See below for what each mechanism needs.

  • "ChallengeBase64" Initial server challenge, encoded in base64.

  • gsignond_session_data_set_username() Authentication identity.

  • gsignond_session_data_set_secret() The password of the authentication identity.

  • gsignond_session_data_set_allowed_realms() List of allowed realms/domains, must exist when either "Hostname" or "Realm" is also supplied.

  • "Authzid" The authorization identity.

  • "AnonymousToken" An anonymous token (for example an email address).

  • "Service" The registered service name of the application service, e.g. “imap”.

  • "Hostname" Should be the local host name of the machine.

  • "Realm" The name of the authentication domain.

  • "Qop" Quality of protection (QOP). Valid values are qop-auth, qop-int, and qop-conf.

  • "ScramSaltedPassword" 40 character long hex-encoded string with the user's hashed password.

  • "CbTlsUnique" This property holds base64 encoded tls-unique channel binding data. As a hint, if you use GnuTLS, the API gnutls_session_channel_binding() can be used to extract channel bindings for a session.

How to use ANONYMOUS mechanism

Issue gsignond_plugin_request_initial() with mechanism set to "ANONYMOUS" and session_data containing an anonymous token. The plugin will return the final response string immediately via “response-final” signal.

How to use PLAIN mechanism

Issue gsignond_plugin_request_initial() with mechanism set to "PLAIN" and session_data containing authentication identity, password, and (optionally) authorization identity. The plugin will return the final response string immediately via “response-final” signal.

How to use CRAM-MD5 mechanism

Issue gsignond_plugin_request_initial() with mechanism set to "CRAM-MD5" and session_data containing authentication identity, password, and initial server challenge. The plugin will return the final response string immediately via “response-final” signal.

How to use DIGEST-MD5 mechanism

Issue gsignond_plugin_request_initial() with mechanism set to "DIGEST-MD5" and session_data containing authentication identity, password, service, hostname, allowed realms list and initial server challenge. Optionally, it can also include realm, QOP and authorization identity.

The plugin will return a response for the server immediately via “response” signal. After receiving another challenge from the server (with gsignond_plugin_request()) the plugin will return a final response via “response-final” signal.

How to use SCRAM-SHA-1 mechanism

Issue gsignond_plugin_request_initial() with mechanism set to "SCRAM-SHA-1" and session_data containing authentication identity, initial server challenge and password. The password can be provided via "ScramSaltedPassword" property or if this property is absent, the normal password property is used. Optionally, also authorization identity and channel binding data can be provided.

This mechanism contains two rounds of response-challenge exchanges (as described above) - gsignond_plugin_request_initial() should be followed by “response”, gsignond_plugin_request(), “response”, gsignond_plugin_request(), and “response-final”.

Functions

Types and Values

struct GSignondSaslPlugin

struct GSignondSaslPlugin {
    GObject parent_instance;

    Gsasl *gsasl_context;
    Gsasl_session *gsasl_session;
    GSignondSessionData* session_data;
};

Opaque structure for the SASL plugin object


struct GSignondSaslPluginClass

struct GSignondSaslPluginClass {
};

Opaque structure for the SASL plugin class

See Also

GSignondPlugin