Top | ![]() |
![]() |
![]() |
![]() |
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.
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.
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; } |
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).
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.
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.
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.
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.
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.
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”.