/* $Id$ * * Lasso - A free implementation of the Liberty Alliance specifications. * * Copyright (C) 2004-2007 Entr'ouvert * http://lasso.entrouvert.org * * Authors: See AUTHORS file in top-level directory. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "../xml/private.h" #include "authentication.h" #include "../xml/sa_sasl_request.h" #include "../xml/sa_sasl_response.h" #include "../xml/soap_binding_correlation.h" #include /* SASL client callbacks (for secret, login, password, ... ) */ static sasl_callback_t lasso_sasl_callbacks[5]; static int lasso_sasl_cb_log(G_GNUC_UNUSED void* context, G_GNUC_UNUSED int priority, G_GNUC_UNUSED const char* message) { return SASL_OK; } static int lasso_sasl_cb_authname(void* context, G_GNUC_UNUSED int id, const char** result, unsigned* len) { LassoUserAccount *account; int ret = SASL_FAIL; *result = NULL; if (len) *len = 0; account = (LassoUserAccount *)context; if (account != NULL && account->login != NULL) { *result = g_strdup(account->login); if (len) *len = strlen(account->login); ret = SASL_OK; } return ret; } static int lasso_sasl_cb_pass(G_GNUC_UNUSED sasl_conn_t* conn, void* context, G_GNUC_UNUSED int id, sasl_secret_t** psecret) { static sasl_secret_t *s; LassoUserAccount *account; int ret = SASL_FAIL; account = (LassoUserAccount *)context; if (account != NULL && account->password != NULL) { s = (sasl_secret_t*) g_malloc0(sizeof(sasl_secret_t) + strlen(account->password)); strcpy((char*)s->data, account->password); s->len = strlen(account->password); *psecret = s; ret = SASL_OK; } return ret; } struct _LassoAuthenticationPrivate { gboolean dispose_has_run; }; static LassoSoapEnvelope* lasso_authentication_build_soap_envelope_internal(const char *refToMessageId, const char *providerId) { LassoSoapEnvelope *envelope; LassoSoapHeader *header; LassoSoapBody *body; LassoSoapBindingCorrelation *correlation; gchar *messageId, *timestamp; /* Body */ body = lasso_soap_body_new(); body->Id = lasso_build_unique_id(32); envelope = lasso_soap_envelope_new(body); /* Header */ header = lasso_soap_header_new(); envelope->Header = header; /* Correlation */ messageId = lasso_build_unique_id(32); timestamp = lasso_get_current_time(); correlation = lasso_soap_binding_correlation_new(messageId, timestamp); correlation->id = lasso_build_unique_id(32); if (refToMessageId != NULL) correlation->refToMessageID = g_strdup(refToMessageId); header->Other = g_list_append(header->Other, correlation); /* Provider */ if (providerId) { LassoSoapBindingProvider *provider = lasso_soap_binding_provider_new(providerId); provider->id = lasso_build_unique_id(32); header->Other = g_list_append(header->Other, provider); } return envelope; } gint lasso_authentication_client_start(LassoAuthentication *authentication) { LassoSaSASLRequest *request; int res; const char *mechusing; const char *out; unsigned int outlen = 0; xmlChar *outbase64; /* Liberty part */ request = LASSO_SA_SASL_REQUEST(LASSO_WSF_PROFILE(authentication)->request); /* sasl part */ res = sasl_client_start(authentication->connection, /* same context from above */ request->mechanism, /* list of mechanisms from the server */ NULL, /* filled in if an interaction is needed */ &out, /* filled in on success */ &outlen, /* filled in on success */ &mechusing); /* mechusing is th resulting best mech to use, so copy it in SASLRequest element */ if (mechusing != NULL) { g_free(request->mechanism); request->mechanism = g_strdup(mechusing); } if (outlen > 0) { outbase64 = xmlSecBase64Encode((xmlChar*)out, outlen, 0); request->Data = g_list_append(request->Data, g_strdup((char*)outbase64)); xmlFree(outbase64); } return res; } gint lasso_authentication_client_step(LassoAuthentication *authentication) { LassoSaSASLRequest *request; LassoSaSASLResponse *response; int res = 0; xmlChar *in = NULL; int inlen = 0; xmlChar *inbase64 = NULL; xmlChar *outbase64; const char *out; unsigned int outlen = 0; /* Liberty part */ request = LASSO_SA_SASL_REQUEST(LASSO_WSF_PROFILE(authentication)->request); response = LASSO_SA_SASL_RESPONSE(LASSO_WSF_PROFILE(authentication)->response); /* sasl part */ if (response->Data != NULL && response->Data->data != NULL) { inbase64 = response->Data->data; in = g_malloc(strlen((char*)inbase64)); inlen = xmlSecBase64Decode(inbase64, in, strlen((char*)inbase64)); res = sasl_client_step(authentication->connection, /* our context */ (char*)in, /* the data from the server */ inlen, /* its length */ NULL, /* prompt_need */ &out, /* client response */ &outlen); /* its length */ if (outlen > 0) { outbase64 = xmlSecBase64Encode((xmlChar*)out, outlen, 0); request->Data = g_list_append(request->Data, g_strdup((char*)outbase64)); xmlFree(outbase64); } } return res; } void lasso_authentication_destroy(LassoAuthentication *authentication) { lasso_node_destroy(LASSO_NODE(authentication)); } char* lasso_authentication_get_mechanism_list(LassoAuthentication *authentication) { int res; const char *result_string; unsigned int string_length; int number_of_mechanisms; if (authentication->connection == NULL) { return NULL; } res = sasl_listmech(authentication->connection, /* The context for this connection */ NULL, /* not supported */ "", /* What to prepend the string with */ " ", /* What to separate mechanisms with */ "", /* What to append to the string */ &result_string, /* The produced string. */ &string_length, /* length of the string */ &number_of_mechanisms); /* Number of mechanisms in the string */ if (result_string == NULL) return NULL; return g_strdup(result_string); } gint lasso_authentication_init_request(LassoAuthentication *authentication, LassoDiscoDescription *description, const gchar *mechanisms, LassoUserAccount *account) { LassoSoapEnvelope *envelope; LassoSaSASLRequest *request; int res; /* global callback for every connection */ static sasl_callback_t global_callbacks[2]; g_return_val_if_fail(LASSO_IS_AUTHENTICATION(authentication), LASSO_PARAM_ERROR_BAD_TYPE_OR_NULL_OBJ); g_return_val_if_fail(LASSO_IS_DISCO_DESCRIPTION(description), LASSO_PARAM_ERROR_BAD_TYPE_OR_NULL_OBJ); g_return_val_if_fail(mechanisms != NULL, LASSO_PARAM_ERROR_INVALID_VALUE); if (description->Endpoint != NULL) { LASSO_WSF_PROFILE(authentication)->msg_url = g_strdup(description->Endpoint); } else if (description->WsdlURI != NULL) { } /* liberty-idwsf-authn-svc-1.1.pdf - page 13 - lignes 342 / 343 : In the case where a single SASL mechanism name is conveyed, the message can contain a so-called initial response (see Section 5.1 of [RFC2222]) in the element. */ request = lasso_sa_sasl_request_new(mechanisms); LASSO_WSF_PROFILE(authentication)->request = LASSO_NODE(request); envelope = lasso_authentication_build_soap_envelope_internal(NULL, NULL); LASSO_WSF_PROFILE(authentication)->soap_envelope_request = envelope; if (envelope == NULL || envelope->Body == NULL || envelope->Body->any == NULL) { return critical_error(LASSO_PROFILE_ERROR_MISSING_REQUEST); } envelope->Body->any = g_list_append(envelope->Body->any, request); /* set up default logging callback */ global_callbacks[0].id = SASL_CB_LOG; global_callbacks[0].proc = lasso_sasl_cb_log; global_callbacks[0].context = NULL; global_callbacks[1].id = SASL_CB_LIST_END; global_callbacks[1].proc = NULL; global_callbacks[1].context = NULL; sasl_client_init(global_callbacks); /* sasl client new connection */ { sasl_callback_t* callback; callback = lasso_sasl_callbacks; callback->id = SASL_CB_AUTHNAME; callback->proc = &lasso_sasl_cb_authname; callback->context = account; callback++; callback->id = SASL_CB_USER; callback->proc = &lasso_sasl_cb_authname; callback->context = account; callback++; callback->id = SASL_CB_PASS; callback->proc = &lasso_sasl_cb_pass; callback->context = account; callback++; callback->id = SASL_CB_GETREALM; callback->proc = NULL; callback->context = NULL; callback++; callback->id = SASL_CB_LIST_END; callback->proc = NULL; callback->context = NULL; } res = sasl_client_new(LASSO_SA_SASL_SERVICE_NAME, NULL, NULL, NULL, lasso_sasl_callbacks, /* new connection callbacks (log, ...) */ 0, &authentication->connection); return res; } gint lasso_authentication_process_request_msg(LassoAuthentication *authentication, const gchar *soap_msg) { LassoSoapEnvelope *envelope; LassoSaSASLResponse *response; LassoUtilityStatus *status; LassoSoapBindingCorrelation *correlation; gchar *messageId; int res = 0; g_return_val_if_fail(LASSO_IS_AUTHENTICATION(authentication), LASSO_PARAM_ERROR_BAD_TYPE_OR_NULL_OBJ); g_return_val_if_fail(LASSO_IS_AUTHENTICATION(authentication), LASSO_PARAM_ERROR_BAD_TYPE_OR_NULL_OBJ); g_return_val_if_fail(soap_msg != NULL, LASSO_PARAM_ERROR_INVALID_VALUE); /* if a previous request and response, then remove */ if (LASSO_IS_SOAP_ENVELOPE(LASSO_WSF_PROFILE(authentication)->soap_envelope_response) \ == TRUE) { lasso_node_destroy(LASSO_NODE(LASSO_WSF_PROFILE(authentication)->\ soap_envelope_response)); LASSO_WSF_PROFILE(authentication)->soap_envelope_response = NULL; LASSO_WSF_PROFILE(authentication)->response = NULL; } if (LASSO_IS_SOAP_ENVELOPE(LASSO_WSF_PROFILE(authentication)->soap_envelope_request) \ == TRUE) { lasso_node_destroy(LASSO_NODE(LASSO_WSF_PROFILE(authentication)->\ soap_envelope_request)); LASSO_WSF_PROFILE(authentication)->soap_envelope_request = NULL; LASSO_WSF_PROFILE(authentication)->request = NULL; } envelope = LASSO_SOAP_ENVELOPE(lasso_node_new_from_dump(soap_msg)); LASSO_WSF_PROFILE(authentication)->soap_envelope_request = envelope; LASSO_WSF_PROFILE(authentication)->request = LASSO_NODE(envelope->Body->any->data); correlation = envelope->Header->Other->data; messageId = correlation->messageID; envelope = lasso_authentication_build_soap_envelope_internal(messageId, NULL); LASSO_WSF_PROFILE(authentication)->soap_envelope_response = envelope; status = lasso_utility_status_new(LASSO_SA_STATUS_CODE_OK); response = lasso_sa_sasl_response_new(status); LASSO_WSF_PROFILE(authentication)->response = LASSO_NODE(response); if (envelope == NULL || envelope->Body == NULL || envelope->Body->any == NULL) { return critical_error(LASSO_PROFILE_ERROR_MISSING_RESPONSE); } envelope->Body->any = g_list_append(envelope->Body->any, response); /* liberty-idwsf-authn-svc-1.1.pdf - page 13 - lignes 359 / 361 : message with multiple mechanism MUST NOT contain any "initial response" data, and MUST be the initial SASL request. See Section 4.5.2.1.2 for details on the returned message in this case. */ /* liberty-idwsf-authn-svc-1.1.pdf - page 13 - lignes 380 / 384 : A NULL string ("") in mechanism list SASLRequest indicates to the authentication server that the client wishes to abort the authentication exchange. */ return res; } gint lasso_authentication_process_response_msg(LassoAuthentication *authentication, const gchar *soap_msg) { LassoSoapEnvelope *envelope; LassoSaSASLRequest *request; LassoSaSASLResponse *response; LassoSoapBindingCorrelation *correlation; gchar *messageId; g_return_val_if_fail(LASSO_IS_AUTHENTICATION(authentication), LASSO_PARAM_ERROR_BAD_TYPE_OR_NULL_OBJ); g_return_val_if_fail(soap_msg != NULL, LASSO_PARAM_ERROR_INVALID_VALUE); /* if a previous request or response, remove */ if (LASSO_IS_SOAP_ENVELOPE(LASSO_WSF_PROFILE(authentication)->soap_envelope_request) \ == TRUE) { lasso_node_destroy(LASSO_NODE(LASSO_WSF_PROFILE(authentication)->\ soap_envelope_request)); LASSO_WSF_PROFILE(authentication)->soap_envelope_request = NULL; LASSO_WSF_PROFILE(authentication)->request = NULL; } if (LASSO_IS_SOAP_ENVELOPE(LASSO_WSF_PROFILE(authentication)->soap_envelope_response) \ == TRUE) { lasso_node_destroy(LASSO_NODE(LASSO_WSF_PROFILE(authentication)->\ soap_envelope_response)); LASSO_WSF_PROFILE(authentication)->soap_envelope_response = NULL; LASSO_WSF_PROFILE(authentication)->response = NULL; } envelope = LASSO_SOAP_ENVELOPE(lasso_node_new_from_dump(soap_msg)); LASSO_WSF_PROFILE(authentication)->soap_envelope_response = envelope; if (envelope == NULL || envelope->Body == NULL || envelope->Body->any == NULL) { return critical_error(LASSO_PROFILE_ERROR_MISSING_RESPONSE); } response = envelope->Body->any->data; if (response == NULL) { return critical_error(LASSO_PROFILE_ERROR_MISSING_RESPONSE); } LASSO_WSF_PROFILE(authentication)->response = LASSO_NODE(response); if (response->Status == NULL || response->Status->code == NULL) { return critical_error(LASSO_PROFILE_ERROR_MISSING_STATUS_CODE); } /* if continue, init another request */ if (g_str_equal(response->Status->code, LASSO_SA_STATUS_CODE_CONTINUE) == TRUE) { correlation = envelope->Header->Other->data; messageId = correlation->messageID; envelope = lasso_authentication_build_soap_envelope_internal(messageId, NULL); LASSO_WSF_PROFILE(authentication)->soap_envelope_request = envelope; request = lasso_sa_sasl_request_new(g_strdup(response->serverMechanism)); LASSO_WSF_PROFILE(authentication)->request = LASSO_NODE(request); envelope->Body->any = g_list_append(envelope->Body->any, request); } return 0; } gint lasso_authentication_server_start(LassoAuthentication *authentication) { LassoSaSASLRequest *request; LassoSaSASLResponse *response; gchar *mechanisms, *chosen; gchar **server_mech_list, **client_mech_list, **smech, **cmech; int nbmech; char *inbase64; xmlChar *outbase64; char *in = NULL; int inlen = 0; const char *out; unsigned int outlen = 0; int res = 0; g_return_val_if_fail(LASSO_IS_AUTHENTICATION(authentication), LASSO_PARAM_ERROR_BAD_TYPE_OR_NULL_OBJ); res = sasl_server_init(NULL, "Lasso"); /* FIXME : should be a param */ res = sasl_server_new(LASSO_SA_SASL_SERVICE_NAME, NULL, NULL, NULL, NULL, NULL, 0, &authentication->connection); /* Liberty part */ request = LASSO_SA_SASL_REQUEST(LASSO_WSF_PROFILE(authentication)->request); response = LASSO_SA_SASL_RESPONSE(LASSO_WSF_PROFILE(authentication)->response); /* if mechanism is NULL, then abort authentication exchange */ chosen = NULL; nbmech = 0; if (g_str_equal(request->mechanism, "") == FALSE) { /* count nb client mechanism list */ client_mech_list = g_strsplit(request->mechanism, " ", 0); cmech = client_mech_list; while (*cmech != NULL) { cmech++; nbmech++; } mechanisms = lasso_authentication_get_mechanism_list(authentication); server_mech_list = g_strsplit(mechanisms, " ", 0); smech = server_mech_list; /* get chosen mechanism */ while (*smech != NULL) { cmech = client_mech_list; while (*cmech != NULL) { if ( g_str_equal(*smech, *cmech) == TRUE) { chosen = g_strdup(*smech); break; } cmech++; } if (chosen != NULL) break; smech++; } } if (chosen == NULL) { g_free(response->Status->code); response->Status->code = g_strdup(LASSO_SA_STATUS_CODE_ABORT); return res; } if (nbmech > 1 && request->Data != NULL) { g_free(response->Status->code); response->Status->code = g_strdup(LASSO_SA_STATUS_CODE_ABORT); return res; } /* decode Data if not NULL */ if (request->Data != NULL && request->Data->data != NULL) { inbase64 = request->Data->data; in = g_malloc(strlen(inbase64)); inlen = xmlSecBase64Decode((xmlChar*)inbase64, (xmlChar*)in, strlen(inbase64)); } /* process sasl request */ res = sasl_server_start(authentication->connection, chosen, in, inlen, &out, /* Might not be NULL terminated */ &outlen); /* set status code in SASLResponse message if not ok */ if (res != SASL_OK) { g_free(response->Status->code); /* continue, set Data in response */ if (res == SASL_CONTINUE) { response->Status->code = g_strdup(LASSO_SA_STATUS_CODE_CONTINUE); response->serverMechanism = g_strdup(request->mechanism); if (outlen > 0) { outbase64 = xmlSecBase64Encode((xmlChar*)out, outlen, 0); response->Data = g_list_append(response->Data, g_strdup((char*)outbase64)); xmlFree(outbase64); } } else { /* abort authentication */ response->Status->code = g_strdup(LASSO_SA_STATUS_CODE_ABORT); } } return res; } gint lasso_authentication_server_step(LassoAuthentication *authentication) { LassoSaSASLRequest *request; LassoSaSASLResponse *response; int res; char *in = NULL; int inlen = 0; const char *out; unsigned int outlen = 0; xmlChar *outbase64, *inbase64; g_return_val_if_fail(LASSO_IS_AUTHENTICATION(authentication), LASSO_PARAM_ERROR_BAD_TYPE_OR_NULL_OBJ); /* Liberty part */ request = LASSO_SA_SASL_REQUEST(LASSO_WSF_PROFILE(authentication)->request); response = LASSO_SA_SASL_RESPONSE(LASSO_WSF_PROFILE(authentication)->response); /* If mechanism is NULL, thene client wants to abort authentication exchange */ if (g_str_equal(request->mechanism, "") == TRUE) { g_free(response->Status->code); response->Status->code = g_strdup(LASSO_SA_STATUS_CODE_ABORT); return 0; } if (request->Data != NULL && request->Data->data != NULL) { inbase64 = request->Data->data; in = g_malloc(strlen((char*)inbase64)); inlen = xmlSecBase64Decode(inbase64, (xmlChar*)in, strlen((char*)inbase64)); } res = sasl_server_step(authentication->connection, in, /* what the client gave */ inlen, /* it's length */ &out, /* Might not be NULL terminated */ &outlen); if (res != SASL_OK) { g_free(response->Status->code); if (res == SASL_CONTINUE) { /* authentication exchange must continue */ response->Status->code = g_strdup(LASSO_SA_STATUS_CODE_ABORT); if (outlen > 0) { outbase64 = xmlSecBase64Encode((xmlChar*)out, outlen, 0); response->Data = g_list_append(response->Data, g_strdup((char*)outbase64)); xmlFree(outbase64); } } else { /* authentication failed, abort exchange */ response->Status->code = g_strdup(LASSO_SA_STATUS_CODE_ABORT); } } return res; } /*****************************************************************************/ /* private methods */ /*****************************************************************************/ static LassoNodeClass *parent_class = NULL; static xmlNode* get_xmlNode(LassoNode *node, gboolean lasso_dump) { xmlNode *xmlnode; xmlnode = parent_class->get_xmlNode(node, lasso_dump); xmlNodeSetName(xmlnode, (xmlChar*)"Authentication"); xmlSetProp(xmlnode, (xmlChar*)"AuthenticationDumpVersion", (xmlChar*)"2"); return xmlnode; } static int init_from_xml(LassoNode *node, xmlNode *xmlnode) { int rc; rc = parent_class->init_from_xml(node, xmlnode); if (rc) return rc; return 0; } /*****************************************************************************/ /* overrided parent class methods */ /*****************************************************************************/ static void dispose(GObject *object) { LassoAuthentication *authentication = LASSO_AUTHENTICATION(object); sasl_dispose(&authentication->connection); if (authentication->private_data->dispose_has_run == TRUE) return; authentication->private_data->dispose_has_run = TRUE; G_OBJECT_CLASS(parent_class)->dispose(object); } static void finalize(GObject *object) { LassoAuthentication *authentication = LASSO_AUTHENTICATION(object); g_free(authentication->private_data); authentication->private_data = NULL; G_OBJECT_CLASS(parent_class)->finalize(object); } /*****************************************************************************/ /* instance and class init functions */ /*****************************************************************************/ static void instance_init(LassoAuthentication *authentication) { authentication->private_data = g_new(LassoAuthenticationPrivate, 1); authentication->private_data->dispose_has_run = FALSE; } static void class_init(LassoAuthenticationClass *klass) { parent_class = g_type_class_peek_parent(klass); LASSO_NODE_CLASS(klass)->get_xmlNode = get_xmlNode; LASSO_NODE_CLASS(klass)->init_from_xml = init_from_xml; G_OBJECT_CLASS(klass)->dispose = dispose; G_OBJECT_CLASS(klass)->finalize = finalize; } GType lasso_authentication_get_type() { static GType this_type = 0; if (!this_type) { static const GTypeInfo this_info = { sizeof(LassoAuthenticationClass), NULL, NULL, (GClassInitFunc) class_init, NULL, NULL, sizeof(LassoAuthentication), 0, (GInstanceInitFunc) instance_init, NULL }; this_type = g_type_register_static(LASSO_TYPE_WSF_PROFILE, "LassoAuthentication", &this_info, 0); } return this_type; } LassoAuthentication* lasso_authentication_new(LassoServer *server) { LassoAuthentication *authentication = NULL; g_return_val_if_fail(LASSO_IS_SERVER(server), NULL); authentication = g_object_new(LASSO_TYPE_AUTHENTICATION, NULL); LASSO_WSF_PROFILE(authentication)->server = server; return authentication; }