/* $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 #include "../utils.h" #include "providerprivate.h" #include "profileprivate.h" #include "profile.h" #include "../id-ff/providerprivate.h" #include "../id-ff/profile.h" #include "../id-ff/profileprivate.h" #include "../id-ff/serverprivate.h" #include <../id-ff/sessionprivate.h> #include "../xml/private.h" #include "../xml/saml-2.0/samlp2_request_abstract.h" #include "../xml/saml-2.0/samlp2_artifact_resolve.h" #include "../xml/saml-2.0/samlp2_artifact_response.h" #include "../xml/saml-2.0/samlp2_name_id_mapping_response.h" #include "../xml/saml-2.0/samlp2_status_response.h" #include "../xml/saml-2.0/samlp2_response.h" #include "../xml/saml-2.0/saml2_assertion.h" #include "../utils.h" #include "../debug.h" static char* lasso_saml20_profile_build_artifact(LassoProvider *provider); G_GNUC_UNUSED static void remove_all_signatures(LassoNode *node); static char * lasso_saml20_profile_export_to_query(LassoProfile *profile, LassoNode *msg, int sign); /* * Helper functions */ static int get_provider(LassoProfile *profile, LassoProvider **provider_out) { int rc = 0; LassoProvider *provider; LassoServer *server; lasso_bad_param(PROFILE, profile); lasso_extract_node_or_fail(server, profile->server, SERVER, LASSO_PROFILE_ERROR_MISSING_SERVER); provider = lasso_server_get_provider(server, profile->remote_providerID); if (! provider) { return LASSO_SERVER_ERROR_PROVIDER_NOT_FOUND; } *provider_out = provider; cleanup: return 0; } static char * get_url(LassoProvider *provider, char *service, char *binding) { char *meta; char *result; meta = g_strdup_printf("%s %s", service, binding); result = lasso_provider_get_metadata_one(provider, meta); lasso_release_string(meta); return result; } static char * get_response_url(LassoProvider *provider, char *service, char *binding) { char *meta; char *result; meta = g_strdup_printf("%s %s ResponseLocation", service, binding); result = lasso_provider_get_metadata_one(provider, meta); lasso_release_string(meta); if (! result) { result = get_url(provider, service, binding); } return result; } /** * lasso_saml20_profile_generate_artifact * @profile: a #LassoProfile * @part: 0 for request, 1 for response * * Generates an artifact for current request or response and sets @profile * attributes accordingly. * * Return value: the generated artifact (internally allocated, don't free) **/ char* lasso_saml20_profile_generate_artifact(LassoProfile *profile, int part) { lasso_assign_new_string(profile->private_data->artifact, lasso_saml20_profile_build_artifact(LASSO_PROVIDER(profile->server))); if (part == 0) { lasso_assign_new_string(profile->private_data->artifact_message, lasso_node_dump(profile->request)); } else if (part == 1) { lasso_assign_new_string(profile->private_data->artifact_message, lasso_node_dump(profile->response)); } else { /* XXX: RequestDenied here? */ } return profile->private_data->artifact; } static char* lasso_saml20_profile_build_artifact(LassoProvider *provider) { xmlSecByte samlArt[44], *b64_samlArt; char *source_succinct_id; char *ret; source_succinct_id = lasso_sha1(provider->ProviderID); /* Artifact Format is described in saml-bindings-2.0-os, 3.6.4.2. */ memcpy(samlArt, "\000\004", 2); /* type code */ memcpy(samlArt+2, "\000\000", 2); /* XXX: Endpoint index */ memcpy(samlArt+4, source_succinct_id, 20); lasso_build_random_sequence((char*)samlArt+24, 20); xmlFree(source_succinct_id); b64_samlArt = xmlSecBase64Encode(samlArt, 44, 0); ret = g_strdup((char*)b64_samlArt); xmlFree(b64_samlArt); return ret; } static int lasso_saml20_profile_set_response_status2(LassoProfile *profile, const char *code1, const char *code2) { LassoSamlp2StatusResponse *status_response = NULL; LassoSamlp2Status *status = NULL; LassoSamlp2StatusCode *status_code1 = NULL; LassoSamlp2StatusCode *status_code2 = NULL; int rc = 0; lasso_bad_param(PROFILE, profile); lasso_null_param(code1); lasso_extract_node_or_fail(status_response, profile->response, SAMLP2_STATUS_RESPONSE, LASSO_PROFILE_ERROR_MISSING_RESPONSE); if (! LASSO_IS_SAMLP2_STATUS(status_response->Status)) { lasso_assign_new_gobject(status_response->Status, (LassoSamlp2Status*)lasso_samlp2_status_new()); } status = status_response->Status; if (! LASSO_IS_SAMLP2_STATUS_CODE(status->StatusCode)) { lasso_assign_new_gobject(status->StatusCode, (LassoSamlp2StatusCode*)lasso_samlp2_status_code_new()); } status_code1 = status->StatusCode; lasso_assign_string(status_code1->Value, code1); if (code2) { if (! LASSO_IS_SAMLP2_STATUS_CODE(status_code1->StatusCode)) { lasso_assign_new_gobject(status_code1->StatusCode, (LassoSamlp2StatusCode*)lasso_samlp2_status_code_new()); } status_code2 = status_code1->StatusCode; lasso_assign_string(status_code2->Value, code2); } cleanup: return rc; } void lasso_saml20_profile_set_response_status(LassoProfile *profile, const char *status_code_value) { if (strcmp(status_code_value, LASSO_SAML2_STATUS_CODE_SUCCESS) != 0 && strcmp(status_code_value, LASSO_SAML2_STATUS_CODE_VERSION_MISMATCH) != 0 && strcmp(status_code_value, LASSO_SAML2_STATUS_CODE_REQUESTER) != 0) { lasso_saml20_profile_set_response_status2(profile, LASSO_SAML2_STATUS_CODE_RESPONDER, status_code_value); } else { lasso_saml20_profile_set_response_status2(profile, status_code_value, NULL); } } int lasso_saml20_profile_init_artifact_resolve(LassoProfile *profile, const char *msg, LassoHttpMethod method) { char **query_fields; char *artifact_b64 = NULL; xmlChar *provider_succinct_id_b64 = NULL; char *provider_succinct_id[21]; char artifact[45]; LassoSamlp2RequestAbstract *request = NULL; int i = 0; if (method == LASSO_HTTP_METHOD_ARTIFACT_GET) { query_fields = urlencoded_to_strings(msg); for (i=0; query_fields[i]; i++) { if (strncmp((char*)query_fields[i], "SAMLart=", 8) == 0) { lasso_assign_string(artifact_b64, query_fields[i]+8); } xmlFree(query_fields[i]); } lasso_release(query_fields); if (artifact_b64 == NULL) { return LASSO_PROFILE_ERROR_MISSING_ARTIFACT; } } else if (method == LASSO_HTTP_METHOD_ARTIFACT_POST) { artifact_b64 = g_strdup(msg); } else { return critical_error(LASSO_PROFILE_ERROR_INVALID_HTTP_METHOD); } i = xmlSecBase64Decode((xmlChar*)artifact_b64, (xmlChar*)artifact, 45); if (i < 0 || i > 44) { lasso_release_string(artifact_b64); return LASSO_PROFILE_ERROR_INVALID_ARTIFACT; } if (artifact[0] != 0 || artifact[1] != 4) { /* wrong type code */ lasso_release_string(artifact_b64); return LASSO_PROFILE_ERROR_INVALID_ARTIFACT; } /* XXX: index endpoint */ memcpy(provider_succinct_id, artifact+4, 20); provider_succinct_id[20] = 0; provider_succinct_id_b64 = xmlSecBase64Encode((xmlChar*)provider_succinct_id, 20, 0); lasso_assign_new_string(profile->remote_providerID, lasso_server_get_providerID_from_hash( profile->server, (char*)provider_succinct_id_b64)); lasso_release_xml_string(provider_succinct_id_b64); if (profile->remote_providerID == NULL) { return critical_error(LASSO_PROFILE_ERROR_MISSING_REMOTE_PROVIDERID); } lasso_assign_new_gobject(profile->request, lasso_samlp2_artifact_resolve_new()); request = LASSO_SAMLP2_REQUEST_ABSTRACT(profile->request); lasso_assign_new_string(LASSO_SAMLP2_ARTIFACT_RESOLVE(request)->Artifact, artifact_b64); request->ID = lasso_build_unique_id(32); lasso_assign_string(request->Version, "2.0"); request->Issuer = LASSO_SAML2_NAME_ID(lasso_saml2_name_id_new_with_string( LASSO_PROVIDER(profile->server)->ProviderID)); request->IssueInstant = lasso_get_current_time(); request->sign_method = LASSO_SIGNATURE_METHOD_RSA_SHA1; if (profile->server->certificate) { request->sign_type = LASSO_SIGNATURE_TYPE_WITHX509; } else { request->sign_type = LASSO_SIGNATURE_TYPE_SIMPLE; } lasso_assign_new_string(profile->msg_relayState, lasso_get_relaystate_from_query(msg)); return 0; } int lasso_saml20_profile_process_artifact_resolve(LassoProfile *profile, const char *msg) { LassoProvider *remote_provider; int rc; lasso_assign_new_gobject(profile->request, lasso_node_new_from_soap(msg)); if (profile->request == NULL) { return critical_error(LASSO_PROFILE_ERROR_INVALID_MSG); } if (! LASSO_IS_SAMLP2_ARTIFACT_RESOLVE(profile->request)) { return critical_error(LASSO_PROFILE_ERROR_INVALID_MSG); } lasso_assign_string(profile->remote_providerID, LASSO_SAMLP2_REQUEST_ABSTRACT( profile->request)->Issuer->content); remote_provider = g_hash_table_lookup(profile->server->providers, profile->remote_providerID); rc = lasso_provider_verify_signature(remote_provider, msg, "ID", LASSO_MESSAGE_FORMAT_SOAP); lasso_assign_string(profile->private_data->artifact, LASSO_SAMLP2_ARTIFACT_RESOLVE(profile->request)->Artifact); return rc; } int lasso_saml20_profile_build_artifact_response(LassoProfile *profile) { LassoSamlp2StatusResponse *response; LassoNode *resp = NULL; response = LASSO_SAMLP2_STATUS_RESPONSE(lasso_samlp2_artifact_response_new()); if (profile->private_data->artifact_message) { resp = lasso_node_new_from_dump(profile->private_data->artifact_message); lasso_assign_new_gobject(LASSO_SAMLP2_ARTIFACT_RESPONSE(response)->any, resp); } response->ID = lasso_build_unique_id(32); lasso_assign_string(response->Version, "2.0"); response->Issuer = LASSO_SAML2_NAME_ID(lasso_saml2_name_id_new_with_string( LASSO_PROVIDER(profile->server)->ProviderID)); response->IssueInstant = lasso_get_current_time(); lasso_assign_string(response->InResponseTo, LASSO_SAMLP2_REQUEST_ABSTRACT(profile->request)->ID); response->sign_method = LASSO_SIGNATURE_METHOD_RSA_SHA1; if (profile->server->certificate) { response->sign_type = LASSO_SIGNATURE_TYPE_WITHX509; } else { response->sign_type = LASSO_SIGNATURE_TYPE_SIMPLE; } lasso_assign_string(response->private_key_file, profile->server->private_key); lasso_assign_string(response->certificate_file, profile->server->certificate); lasso_assign_new_gobject(profile->response, LASSO_NODE(response)); if (resp == NULL) { lasso_saml20_profile_set_response_status(profile, LASSO_SAML2_STATUS_CODE_REQUESTER); } else { lasso_saml20_profile_set_response_status(profile, LASSO_SAML2_STATUS_CODE_SUCCESS); } lasso_assign_new_string(profile->msg_body, lasso_node_export_to_soap(profile->response)); return 0; } int lasso_saml20_profile_process_artifact_response(LassoProfile *profile, const char *msg) { LassoNode *response; LassoSamlp2ArtifactResponse *artifact_response; /* XXX: handle errors properly */ response = lasso_node_new_from_soap(msg); if (!LASSO_IS_SAMLP2_ARTIFACT_RESPONSE(response)) { lasso_assign_new_gobject(profile->response, lasso_samlp2_response_new()); return LASSO_PROFILE_ERROR_INVALID_ARTIFACT; } artifact_response = LASSO_SAMLP2_ARTIFACT_RESPONSE(response); if (artifact_response->any == NULL) { lasso_assign_new_gobject(profile->response, lasso_samlp2_response_new()); return LASSO_PROFILE_ERROR_MISSING_RESPONSE; } lasso_assign_gobject(profile->response, artifact_response->any); lasso_release_gobject(response); return 0; } /** * lasso_saml20_profile_is_saml_query: * @query: HTTP query string * * Tests the query string to know if the URL is called as the result of a * SAML redirect (action initiated elsewhere) or not. * * Return value: TRUE if SAML query, FALSE otherwise **/ gboolean lasso_profile_is_saml_query(const gchar *query) { gchar *parameters[] = { "SAMLRequest=", "SAMLResponse=", "SAMLart=", NULL }; gint i; g_return_val_if_fail(query, FALSE); for (i=0; parameters[i]; i++) { if (strstr(query, parameters[i])) return TRUE; } return FALSE; } static void lasso_saml20_profile_set_session_from_dump_decrypt( LassoSaml2Assertion *assertion, G_GNUC_UNUSED gpointer data) { if (LASSO_IS_SAML2_ASSERTION(assertion) == FALSE) { return; } if (assertion->Subject != NULL && assertion->Subject->EncryptedID != NULL) { lasso_assign_gobject(assertion->Subject->NameID, assertion->Subject->EncryptedID->original_data); lasso_release_gobject(assertion->Subject->EncryptedID); } } gint lasso_saml20_profile_set_session_from_dump(LassoProfile *profile) { GList *assertions = NULL; lasso_bad_param(PROFILE, profile); if (lasso_session_count_assertions(profile->session) > 0) { assertions = lasso_session_get_assertions(profile->session, NULL); g_list_foreach(assertions, (GFunc)lasso_saml20_profile_set_session_from_dump_decrypt, NULL); lasso_release_list(assertions); } return 0; } /** * lasso_saml20_profile_process_name_identifier_decryption: * @profile: the #LassoProfile object * @name_id: the field containing the #LassoSaml2NameID object * @encrypted_id: the field containing an encrypted #LassoSaml2NameID as a #LassoSaml2EncryptedElement * * Place content of the NameID in the profile nameIdentifier field, if no NameID is present but an * EncryptedElement is, then decrypt it, store it in place of the name_id field and in the * nameIdentifier field of the profile. * * Return value: 0 if successful, * LASSO_PROFILE_ERROR_MISSING_NAME_IDENTIFIER if no NameID can be found, * LASSO_PROFILE_ERROR_MISSING_ENCRYPTION_PRIVATE_KEY if an encryption element is present but no no * decryption key. */ gint lasso_saml20_profile_process_name_identifier_decryption(LassoProfile *profile, LassoSaml2NameID **name_id, LassoSaml2EncryptedElement **encrypted_id) { xmlSecKey *encryption_private_key = NULL; int rc = 0; lasso_bad_param(PROFILE, profile); lasso_null_param(name_id); lasso_null_param(encrypted_id); if (*name_id == NULL && *encrypted_id != NULL) { encryption_private_key = profile->server->private_data->encryption_private_key; if (! LASSO_IS_SAML2_ENCRYPTED_ELEMENT(*encrypted_id)) { return LASSO_PROFILE_ERROR_MISSING_NAME_IDENTIFIER; } if (encrypted_id != NULL && encryption_private_key == NULL) { return LASSO_PROFILE_ERROR_MISSING_ENCRYPTION_PRIVATE_KEY; } rc = lasso_saml2_encrypted_element_decrypt(*encrypted_id, encryption_private_key, &profile->nameIdentifier); if (rc) goto cleanup; if (! LASSO_IS_SAML2_NAME_ID(profile->nameIdentifier)) { rc = LASSO_PROFILE_ERROR_MISSING_NAME_IDENTIFIER; goto cleanup; } // swap the node contents lasso_assign_gobject(*name_id, LASSO_SAML2_NAME_ID(profile->nameIdentifier)); lasso_release_gobject(*encrypted_id); } else { lasso_assign_gobject(profile->nameIdentifier, (LassoNode*)*name_id); } cleanup: return rc; } /** * lasso_saml20_profile_process_any_request: * @profile: a #LassoProfile object * @request_node: a #LassoNode object which will be initialized with the content of @request_msg * @request_msg: a string containing the request message as a SOAP XML message, a query string of * the content of SAMLRequest POST field. * * Parse a request message, initialize the given node object with it, try to extract basic SAML * profile information like the remote_provider_id or the name_id and validate the signature. * * Signature validation status is accessible in profile->signature_status, beware that if signature * validation fails no error code will be returned, you must explicitely verify the * profile->signature_status code. * * Return value: 0 if parsing is successfull (even if signature validation fails), and error code * otherwise. */ int lasso_saml20_profile_process_any_request(LassoProfile *profile, LassoNode *request_node, char *request_msg) { int rc = 0; LassoSaml2NameID *name_id = NULL; LassoProvider *remote_provider = NULL; LassoServer *server = NULL; LassoSamlp2RequestAbstract *request_abstract = NULL; LassoMessageFormat format; xmlDoc *doc = NULL; xmlNode *content = NULL; lasso_bad_param(PROFILE, profile); /* reset signature_status */ profile->signature_status = 0; format = lasso_node_init_from_message_with_format(request_node, request_msg, LASSO_MESSAGE_FORMAT_UNKNOWN, &doc, &content); if (format <= LASSO_MESSAGE_FORMAT_UNKNOWN) { rc = LASSO_PROFILE_ERROR_INVALID_MSG; goto cleanup; } switch (format) { case LASSO_MESSAGE_FORMAT_BASE64: profile->http_request_method = LASSO_HTTP_METHOD_POST; break; case LASSO_MESSAGE_FORMAT_SOAP: profile->http_request_method = LASSO_HTTP_METHOD_SOAP; break; case LASSO_MESSAGE_FORMAT_QUERY: profile->http_request_method = LASSO_HTTP_METHOD_REDIRECT; break; default: rc = LASSO_PROFILE_ERROR_UNSUPPORTED_PROFILE; goto cleanup; } lasso_assign_gobject(profile->request, request_node); if (format == LASSO_MESSAGE_FORMAT_QUERY) { lasso_assign_new_string(profile->msg_relayState, lasso_get_relaystate_from_query(request_msg)); } lasso_extract_node_or_fail(request_abstract, profile->request, SAMLP2_REQUEST_ABSTRACT, LASSO_PROFILE_ERROR_INVALID_MSG); lasso_extract_node_or_fail(server, profile->server, SERVER, LASSO_PROFILE_ERROR_MISSING_SERVER); lasso_extract_node_or_fail(name_id, request_abstract->Issuer, SAML2_NAME_ID, LASSO_PROFILE_ERROR_MISSING_ISSUER); lasso_assign_string(profile->remote_providerID, request_abstract->Issuer->content); remote_provider = lasso_server_get_provider(server, profile->remote_providerID); if (remote_provider == NULL) { rc = LASSO_PROFILE_ERROR_UNKNOWN_PROVIDER; goto cleanup; } /* verify the signature at the request level */ if (content && doc && format != LASSO_MESSAGE_FORMAT_QUERY) { profile->signature_status = lasso_provider_verify_saml_signature(remote_provider, content, doc); } else if (format == LASSO_MESSAGE_FORMAT_QUERY) { profile->signature_status = lasso_provider_verify_query_signature(remote_provider, request_msg); } else { profile->signature_status = LASSO_PROFILE_ERROR_CANNOT_VERIFY_SIGNATURE; } cleanup: lasso_release_doc(doc); return rc; } int lasso_saml20_profile_process_soap_request(LassoProfile *profile, char *request_msg) { int rc = 0; LassoSaml2NameID *issuer = NULL; LassoProvider *remote_provider = NULL; LassoServer *server = NULL; LassoSamlp2RequestAbstract *request_abstract = NULL; lasso_bad_param(PROFILE, profile); profile->signature_status = 0; lasso_assign_new_gobject(profile->request, lasso_node_new_from_soap(request_msg)); profile->http_request_method = LASSO_HTTP_METHOD_SOAP; lasso_extract_node_or_fail(request_abstract, profile->request, SAMLP2_REQUEST_ABSTRACT, LASSO_PROFILE_ERROR_INVALID_MSG); lasso_extract_node_or_fail(server, profile->server, SERVER, LASSO_PROFILE_ERROR_MISSING_SERVER); lasso_extract_node_or_fail(issuer, request_abstract->Issuer, SAML2_NAME_ID, LASSO_PROFILE_ERROR_MISSING_ISSUER); lasso_assign_string(profile->remote_providerID, issuer->content); remote_provider = lasso_server_get_provider(server, profile->remote_providerID); if (remote_provider == NULL) { rc = LASSO_PROFILE_ERROR_UNKNOWN_PROVIDER; goto cleanup; } rc = profile->signature_status = lasso_provider_verify_signature( remote_provider, request_msg, "ID", LASSO_MESSAGE_FORMAT_SOAP); cleanup: return rc; } int lasso_saml20_init_request(LassoProfile *profile, char *remote_provider_id, gboolean first_in_session, LassoSamlp2RequestAbstract *request_abstract, LassoHttpMethod http_method, LassoMdProtocolType protocol_type) { LassoIdentity *identity = NULL; LassoSession *session = NULL; LassoServer *server = NULL; LassoProvider *remote_provider = NULL; LassoSaml2NameID *name_id = NULL; char *remote_provider_id_auto = NULL; int rc = 0; lasso_bad_param(PROFILE, profile); lasso_bad_param(SAMLP2_REQUEST_ABSTRACT, request_abstract); if (http_method < LASSO_HTTP_METHOD_ANY || http_method >= LASSO_HTTP_METHOD_LAST) { message(G_LOG_LEVEL_CRITICAL, "Invalid LassoHttpMethod argument"); return LASSO_PARAM_ERROR_INVALID_VALUE; } /* verify identity and sessions */ lasso_extract_node_or_fail(identity, profile->identity, IDENTITY, LASSO_PROFILE_ERROR_IDENTITY_NOT_FOUND); lasso_extract_node_or_fail(session, profile->session, SESSION, LASSO_PROFILE_ERROR_SESSION_NOT_FOUND); lasso_extract_node_or_fail(server, profile->server, SERVER, LASSO_PROFILE_ERROR_MISSING_SERVER); /* set remote provider Id */ if (! remote_provider_id) { if (first_in_session) { remote_provider_id_auto = lasso_session_get_provider_index(session, 0); } else { remote_provider_id_auto = lasso_server_get_first_providerID(server); } } if (! remote_provider_id && ! remote_provider_id_auto) { rc = LASSO_PROFILE_ERROR_CANNOT_FIND_A_PROVIDER; goto cleanup; } if (remote_provider_id) { lasso_assign_string(profile->remote_providerID, remote_provider_id); } else { lasso_assign_new_string(profile->remote_providerID, remote_provider_id_auto); } rc = get_provider(profile, &remote_provider); if (rc) goto cleanup; /* set the name identifier */ name_id = (LassoSaml2NameID*)lasso_profile_get_nameIdentifier(profile); if (! LASSO_IS_SAML2_NAME_ID(name_id)) { rc = LASSO_PROFILE_ERROR_FEDERATION_NOT_FOUND; goto cleanup; } lasso_assign_gobject(profile->nameIdentifier, (LassoNode*)name_id); /* verify that this provider supports the current http method */ if (http_method == LASSO_HTTP_METHOD_ANY) { http_method = lasso_saml20_provider_get_first_http_method((LassoProvider*)server, remote_provider, protocol_type); } if (http_method == LASSO_HTTP_METHOD_NONE) { rc = LASSO_PROFILE_ERROR_UNSUPPORTED_PROFILE; goto cleanup; } if (! lasso_saml20_provider_accept_http_method( (LassoProvider*)server, remote_provider, protocol_type, http_method, TRUE)) { rc = LASSO_PROFILE_ERROR_UNSUPPORTED_PROFILE; } profile->http_request_method = http_method; /* initialize request fields */ lasso_assign_new_string(request_abstract->ID, lasso_build_unique_id(32)); lasso_assign_string(request_abstract->Version, "2.0"); lasso_assign_new_gobject(request_abstract->Issuer, LASSO_SAML2_NAME_ID(lasso_saml2_name_id_new_with_string( LASSO_PROVIDER(profile->server)->ProviderID))); lasso_assign_new_string(request_abstract->IssueInstant, lasso_get_current_time()); lasso_assign_gobject(profile->request, LASSO_NODE(request_abstract)); cleanup: return rc; } static int lasso_saml20_profile_build_post_request_msg(LassoProfile *profile, LassoProvider *provider, char *service) { int rc = 0; LassoSamlp2RequestAbstract *request_abstract; lasso_bad_param(PROFILE, profile); lasso_bad_param(PROVIDER, provider); lasso_extract_node_or_fail(request_abstract, profile->request, SAMLP2_REQUEST_ABSTRACT, LASSO_PROFILE_ERROR_BUILDING_MESSAGE_FAILED); lasso_assign_new_string(profile->msg_url, get_response_url(provider, service, "HTTP-POST")); if (! profile->msg_url) { return critical_error(LASSO_PROFILE_ERROR_UNKNOWN_PROFILE_URL); } lasso_assign_new_string(profile->msg_body, lasso_node_export_to_base64(LASSO_NODE(request_abstract))); if (! profile->msg_body) { return critical_error(LASSO_PROFILE_ERROR_BUILDING_MESSAGE_FAILED); } cleanup: return rc; } static int lasso_saml20_profile_build_soap_request_msg(LassoProfile *profile, LassoProvider *provider, char *service) { int rc = 0; char *url = NULL; LassoSamlp2RequestAbstract *request_abstract; lasso_bad_param(PROFILE, profile); lasso_bad_param(PROVIDER, provider); lasso_extract_node_or_fail(request_abstract, profile->request, SAMLP2_REQUEST_ABSTRACT, LASSO_PROFILE_ERROR_BUILDING_MESSAGE_FAILED); url = get_url(provider, service, "SOAP"); if (! url) { rc = critical_error(LASSO_PROFILE_ERROR_UNKNOWN_PROFILE_URL); goto cleanup; } lasso_assign_new_string(profile->msg_body, lasso_node_export_to_soap(LASSO_NODE(request_abstract))); lasso_transfer_string(profile->msg_url, url); if (! profile->msg_body) { return LASSO_PROFILE_ERROR_BUILDING_MESSAGE_FAILED; } cleanup: lasso_release_string(url); return rc; } static int lasso_saml20_profile_build_redirect_request_msg(LassoProfile *profile, LassoProvider *provider, char *service, gboolean no_signature) { int rc = 0; char *url = NULL; LassoSamlp2RequestAbstract *request_abstract; lasso_bad_param(PROFILE, profile); lasso_bad_param(PROVIDER, provider); lasso_extract_node_or_fail(request_abstract, profile->request, SAMLP2_REQUEST_ABSTRACT, LASSO_PROFILE_ERROR_BUILDING_MESSAGE_FAILED); if (no_signature) request_abstract->sign_type = LASSO_SIGNATURE_TYPE_NONE; url = get_url(provider, service, "HTTP-Redirect"); rc = lasso_saml20_profile_build_http_redirect(profile, profile->request, lasso_flag_add_signature, url); if (rc) goto cleanup; cleanup: lasso_release_string(url); return rc; } int lasso_saml20_profile_setup_request_signing(LassoProfile *profile) { LassoSamlp2RequestAbstract *request_abstract = NULL; LassoServer *server = NULL; int rc = 0; lasso_extract_node_or_fail(request_abstract, profile->request, SAMLP2_REQUEST_ABSTRACT, LASSO_PROFILE_ERROR_INVALID_MSG); lasso_extract_node_or_fail(server, profile->server, SERVER, LASSO_PROFILE_ERROR_MISSING_SERVER); request_abstract->sign_method = server->signature_method; request_abstract->sign_type = server->certificate ? LASSO_SIGNATURE_TYPE_WITHX509 : LASSO_SIGNATURE_TYPE_SIMPLE; lasso_assign_string(request_abstract->private_key_file, server->private_key); lasso_assign_string(request_abstract->certificate_file, server->certificate); cleanup: return rc; } int lasso_saml20_profile_build_request_msg(LassoProfile *profile, char *service, gboolean no_signature) { LassoProvider *provider; int rc = 0; lasso_bad_param(PROFILE, profile); lasso_profile_clean_msg_info(profile); rc = get_provider(profile, &provider); if (rc) goto cleanup; rc = lasso_saml20_profile_setup_request_signing(profile); if (rc) goto cleanup; switch (profile->http_request_method) { case LASSO_HTTP_METHOD_SOAP: rc = lasso_saml20_profile_build_soap_request_msg(profile, provider, service); break; case LASSO_HTTP_METHOD_POST: rc = lasso_saml20_profile_build_post_request_msg(profile, provider, service); break; case LASSO_HTTP_METHOD_REDIRECT: rc = lasso_saml20_profile_build_redirect_request_msg(profile, provider, service, no_signature); break; case LASSO_HTTP_METHOD_ARTIFACT_GET: case LASSO_HTTP_METHOD_ARTIFACT_POST: rc = LASSO_PROFILE_ERROR_UNSUPPORTED_PROFILE; break; default: rc = LASSO_PROFILE_ERROR_INVALID_HTTP_METHOD; break; } cleanup: return rc; } int lasso_saml20_profile_init_response(LassoProfile *profile, const char *status_code) { LassoSamlp2StatusResponse *status_response = NULL; LassoSamlp2RequestAbstract *request_abstract = NULL; LassoServer *server = NULL; int rc = 0; lasso_bad_param(PROFILE, profile); lasso_extract_node_or_fail(status_response, profile->response, SAMLP2_STATUS_RESPONSE, LASSO_PROFILE_ERROR_MISSING_RESPONSE); lasso_extract_node_or_fail(server, profile->server, SERVER, LASSO_PROFILE_ERROR_MISSING_SERVER); lasso_extract_node_or_fail(request_abstract, profile->request, SAMLP2_REQUEST_ABSTRACT, LASSO_PROFILE_ERROR_MISSING_REQUEST); lasso_assign_new_string(status_response->ID, lasso_build_unique_id(32)); lasso_assign_string(status_response->Version, "2.0"); lasso_assign_new_gobject(status_response->Issuer, LASSO_SAML2_NAME_ID(lasso_saml2_name_id_new_with_string( server->parent.ProviderID))); lasso_assign_new_string(status_response->IssueInstant, lasso_get_current_time()); lasso_assign_string(status_response->InResponseTo, request_abstract->ID); if (status_code) lasso_saml20_profile_set_response_status(profile, status_code); cleanup: return rc; } int lasso_saml20_profile_validate_request(LassoProfile *profile, gboolean needs_identity, LassoSamlp2StatusResponse *status_response, LassoProvider **provider_out) { int rc = 0; LassoSamlp2RequestAbstract *request_abstract = NULL; LassoSaml2NameID *issuer = NULL; LassoIdentity *identity = NULL; LassoProvider *provider = NULL; lasso_bad_param(PROFILE, profile); lasso_bad_param(SAMLP2_STATUS_RESPONSE, status_response); /* verify request presence */ lasso_extract_node_or_fail(request_abstract, profile->request, SAMLP2_REQUEST_ABSTRACT, LASSO_PROFILE_ERROR_MISSING_REQUEST); /* look for identity object */ if (needs_identity) { lasso_extract_node_or_fail(identity, profile->identity, IDENTITY, LASSO_PROFILE_ERROR_IDENTITY_NOT_FOUND); } /* extract provider */ lasso_extract_node_or_fail(issuer, request_abstract->Issuer, SAML2_NAME_ID, LASSO_PROFILE_ERROR_MISSING_ISSUER); lasso_assign_string(profile->remote_providerID, issuer->content); rc = get_provider(profile, &provider); if (rc) goto cleanup; /* init the response */ lasso_assign_gobject(profile->response, &status_response->parent); lasso_saml20_profile_init_response(profile, LASSO_SAML2_STATUS_CODE_SUCCESS); if (profile->signature_status) { message(G_LOG_LEVEL_WARNING, "Request signature is invalid"); lasso_saml20_profile_set_response_status2(profile, LASSO_SAML2_STATUS_CODE_REQUESTER, LASSO_LIB_STATUS_CODE_INVALID_SIGNATURE); return profile->signature_status; } cleanup: if (provider && provider_out) *provider_out = provider; return rc; } static int lasso_saml20_profile_setup_response_signing(LassoProfile *profile) { LassoSamlp2StatusResponse *response_abstract = NULL; LassoServer *server = NULL; int rc = 0; lasso_extract_node_or_fail(response_abstract, profile->response, SAMLP2_STATUS_RESPONSE, LASSO_PROFILE_ERROR_INVALID_MSG); lasso_extract_node_or_fail(server, profile->server, SERVER, LASSO_PROFILE_ERROR_MISSING_SERVER); response_abstract->sign_method = server->signature_method; response_abstract->sign_type = server->certificate ? LASSO_SIGNATURE_TYPE_WITHX509 : LASSO_SIGNATURE_TYPE_SIMPLE; lasso_assign_string(response_abstract->private_key_file, server->private_key); lasso_assign_string(response_abstract->certificate_file, server->certificate); cleanup: return rc; } static int lasso_saml20_profile_build_post_response(LassoProfile *profile, LassoProvider *provider, char *service) { lasso_bad_param(PROFILE, profile); lasso_bad_param(PROVIDER, provider); lasso_assign_new_string(profile->msg_url, get_response_url(provider, service, "HTTP-POST")); if (! profile->msg_url) { return critical_error(LASSO_PROFILE_ERROR_UNKNOWN_PROFILE_URL); } lasso_assign_new_string(profile->msg_body, lasso_node_export_to_base64(profile->request)); if (! profile->msg_body) { return critical_error(LASSO_PROFILE_ERROR_BUILDING_MESSAGE_FAILED); } return 0; } static int lasso_saml20_profile_build_redirect_response(LassoProfile *profile, LassoProvider *provider, char *service, gboolean no_signature) { LassoSamlp2StatusResponse *status_response = NULL; char *url = NULL; int rc = 0; lasso_bad_param(PROFILE, profile); lasso_null_param(service); lasso_extract_node_or_fail(status_response, profile->response, SAMLP2_STATUS_RESPONSE, LASSO_PROFILE_ERROR_MISSING_RESPONSE); if (no_signature) // for authn response status_response->sign_type = LASSO_SIGNATURE_TYPE_NONE; // get url url = get_response_url(provider, service, "HTTP-Redirect"); rc = lasso_saml20_profile_build_http_redirect(profile, profile->response, lasso_flag_add_signature, url); cleanup: lasso_release_string(url); return rc; } static int lasso_saml20_profile_build_soap_response(LassoProfile *profile) { lasso_bad_param(PROFILE, profile); lasso_release_string(profile->msg_url); lasso_assign_new_string(profile->msg_body, lasso_node_export_to_soap(profile->response)); if (! profile->msg_body) { return LASSO_PROFILE_ERROR_BUILDING_RESPONSE_FAILED; } return 0; } /** * lasso_saml20_profile_export_to_query: * @profile: a #LassoProfile * @request_or_response: 0 to encode the request, 1 to encode the response * @sign: TRUE if query must signed, FALSE otherwise * * Create a query following the DEFLATE encoding of the SAML 2.0 HTTP * Redirect binding. * * Return value: a newly allocated string containing the query string if successfull, NULL otherwise. */ static char * lasso_saml20_profile_export_to_query(LassoProfile *profile, LassoNode *msg, int sign) { char *unsigned_query = NULL; char *result = NULL; g_return_val_if_fail(LASSO_IS_NODE(msg), NULL); unsigned_query = lasso_node_build_query(msg); if (profile->msg_relayState) { char *query = unsigned_query; if (strlen(profile->msg_relayState) < 81) { unsigned_query = lasso_url_add_parameters(query, 1, "RelayState", profile->msg_relayState, NULL); query = NULL; } else { g_warning("Refused to encode a RelayState of more than 80 bytes, #3.4.3 of" " saml-bindings-2.0-os"); } } if (sign && lasso_flag_add_signature) { result = lasso_query_sign(unsigned_query, profile->server->signature_method, profile->server->private_key); lasso_release_string(unsigned_query); } else { result = unsigned_query; } return result; } static void remove_signature(LassoNode *node) { LassoNodeClass *klass; if (node == NULL) return; klass = LASSO_NODE_GET_CLASS(node); if (klass->node_data->sign_type_offset != 0) { G_STRUCT_MEMBER(LassoSignatureType, node,klass->node_data->sign_type_offset) = LASSO_SIGNATURE_TYPE_NONE; } } static void remove_all_signatures(LassoNode *node) { LassoNodeClass *klass; struct XmlSnippet *snippet; if (node == NULL) return; klass = LASSO_NODE_GET_CLASS(node); remove_signature(node); snippet = klass->node_data->snippets; while (snippet && snippet->name) { SnippetType type; void *value; GList *elem; value = G_STRUCT_MEMBER(void*, node, snippet->offset); type = snippet->type & 0xff; switch (type) { case SNIPPET_NODE: case SNIPPET_NODE_IN_CHILD: remove_all_signatures(LASSO_NODE(value)); break; case SNIPPET_LIST_NODES: elem = (GList*)value; while (elem) { remove_all_signatures(LASSO_NODE(elem->data)); elem = g_list_next(elem); } break; default: break; } snippet++; } } gint lasso_saml20_profile_build_http_redirect(LassoProfile *profile, LassoNode *msg, gboolean must_sign, const char *url) { char *query; if (url == NULL) { return critical_error(LASSO_PROFILE_ERROR_UNKNOWN_PROFILE_URL); } /* No signature on the XML message */ query = lasso_saml20_profile_export_to_query(profile, msg, must_sign); lasso_assign_new_string(profile->msg_url, lasso_concat_url_query(url, query)); lasso_release(profile->msg_body); lasso_release(query); return 0; } int lasso_saml20_profile_build_response(LassoProfile *profile, char *service, gboolean no_signature, LassoHttpMethod method) { LassoProvider *provider; int rc = 0; lasso_bad_param(PROFILE, profile); lasso_profile_clean_msg_info(profile); rc = get_provider(profile, &provider); if (rc) goto cleanup; rc = lasso_saml20_profile_setup_response_signing(profile); if (rc) goto cleanup; switch (method) { case LASSO_HTTP_METHOD_POST: rc = lasso_saml20_profile_build_post_response(profile, provider, service); break; case LASSO_HTTP_METHOD_REDIRECT: rc = lasso_saml20_profile_build_redirect_response(profile, provider, service, no_signature); break; case LASSO_HTTP_METHOD_SOAP: rc = lasso_saml20_profile_build_soap_response(profile); break; default: rc= LASSO_PROFILE_ERROR_UNSUPPORTED_PROFILE; break; } cleanup: return rc; } /** * lasso_saml20_profile_process_any_response: * @profile: the SAML 2.0 #LassoProfile object * @status_response: the prototype for the response object * @response_msg: the content of the response message * * Generic method for SAML 2.0 protocol message handling. * * It tries to validate a signature on the response msg, the result of this operation is kept inside * profile->signature_status. Use it afterward in your specific profile. Beware that it does not * return an error code if signature validation failed. It let's specific profile accept unsigned * messages. * * Return value: 0 if successful, an error code otherwise. */ int lasso_saml20_profile_process_any_response(LassoProfile *profile, LassoSamlp2StatusResponse *status_response, char *response_msg) { int rc = 0; LassoSaml2NameID *name_id = NULL; LassoProvider *remote_provider = NULL; LassoServer *server = NULL; LassoSamlp2StatusResponse *response_abstract = NULL; LassoSamlp2Status *status = NULL; LassoSamlp2StatusCode *status_code1 = NULL; LassoSamlp2StatusCode *status_code2 = NULL; LassoMessageFormat format; xmlDoc *doc = NULL; xmlNode *content = NULL; lasso_bad_param(PROFILE, profile); lasso_bad_param(SAMLP2_STATUS_RESPONSE, status_response); /* reset signature_status */ profile->signature_status = 0; format = lasso_node_init_from_message_with_format((LassoNode*)status_response, response_msg, LASSO_MESSAGE_FORMAT_UNKNOWN, &doc, &content); if (format <= LASSO_MESSAGE_FORMAT_UNKNOWN) { rc = LASSO_PROFILE_ERROR_INVALID_MSG; goto cleanup; } lasso_assign_gobject(profile->response, (LassoNode*)status_response); lasso_extract_node_or_fail(response_abstract, profile->response, SAMLP2_STATUS_RESPONSE, LASSO_PROFILE_ERROR_INVALID_MSG); lasso_extract_node_or_fail(server, profile->server, SERVER, LASSO_PROFILE_ERROR_MISSING_SERVER); lasso_extract_node_or_fail(name_id, response_abstract->Issuer, SAML2_NAME_ID, LASSO_PROFILE_ERROR_MISSING_ISSUER); lasso_assign_string(profile->remote_providerID, response_abstract->Issuer->content); remote_provider = lasso_server_get_provider(server, profile->remote_providerID); if (remote_provider == NULL) { rc = LASSO_PROFILE_ERROR_UNKNOWN_PROVIDER; goto cleanup; } /* verify the signature at the request level */ if (content && doc && format != LASSO_MESSAGE_FORMAT_QUERY) { profile->signature_status = lasso_provider_verify_saml_signature(remote_provider, content, doc); } else if (format == LASSO_MESSAGE_FORMAT_QUERY) { profile->signature_status = lasso_provider_verify_query_signature(remote_provider, response_msg); } else { profile->signature_status = LASSO_PROFILE_ERROR_CANNOT_VERIFY_SIGNATURE; } /* verify status code */ lasso_extract_node_or_fail(status, status_response->Status, SAMLP2_STATUS, LASSO_PROFILE_ERROR_MISSING_STATUS_CODE); lasso_extract_node_or_fail(status_code1, status->StatusCode, SAMLP2_STATUS_CODE, LASSO_PROFILE_ERROR_MISSING_STATUS_CODE); if (! status_code1->Value) { rc = LASSO_PROFILE_ERROR_MISSING_STATUS_CODE; goto cleanup; } if (status_code1->StatusCode && status_code1->StatusCode->Value) { status_code2 = status_code1->StatusCode; } if (strcmp(status_code1->Value, LASSO_SAML2_STATUS_CODE_SUCCESS) != 0) { message(G_LOG_LEVEL_CRITICAL, "Status Code is not Success on a SAML 2.0 response:" "1st leve «%s» 2nd leve «%s»", status_code1->Value, status_code2 ? status_code2->Value : ""); rc = LASSO_PROFILE_ERROR_STATUS_NOT_SUCCESS; goto cleanup; } cleanup: lasso_release_doc(doc); if (rc == LASSO_PROFILE_ERROR_MISSING_STATUS_CODE) { message(G_LOG_LEVEL_CRITICAL, "Status Code is missing in a SAML 2.0 protocol response"); } return rc; } /** * lasso_saml20_profile_process_soap_response: * * Generic method for processing SAML 2.0 protocol message as a SOAP response. * * Return value: 0 if successful; an error code otherwise. */ int lasso_saml20_profile_process_soap_response(LassoProfile *profile, char *response_msg) { int rc = 0; LassoSaml2NameID *issuer = NULL; LassoProvider *remote_provider = NULL; LassoServer *server = NULL; LassoSamlp2StatusResponse *response_abstract = NULL; lasso_bad_param(PROFILE, profile); lasso_null_param(response_msg); profile->signature_status = 0; lasso_assign_new_gobject(profile->response, lasso_node_new_from_soap(response_msg)); lasso_extract_node_or_fail(response_abstract, profile->response, SAMLP2_STATUS_RESPONSE, LASSO_PROFILE_ERROR_INVALID_MSG); lasso_extract_node_or_fail(server, profile->server, SERVER, LASSO_PROFILE_ERROR_MISSING_SERVER); lasso_extract_node_or_fail(issuer, response_abstract->Issuer, SAML2_NAME_ID, LASSO_PROFILE_ERROR_MISSING_ISSUER); lasso_assign_string(profile->remote_providerID, issuer->content); remote_provider = lasso_server_get_provider(server, profile->remote_providerID); if (remote_provider == NULL) { rc = LASSO_PROFILE_ERROR_UNKNOWN_PROVIDER; goto cleanup; } rc = profile->signature_status = lasso_provider_verify_signature( remote_provider, response_msg, "ID", LASSO_MESSAGE_FORMAT_SOAP); cleanup: return rc; } gint lasso_saml20_build_http_redirect_query_simple(LassoProfile *profile, LassoNode *msg, gboolean must_sign, const char *profile_name, gboolean is_response) { char *idx = NULL; char *url = NULL; LassoProvider *remote_provider = NULL; int rc; remote_provider = g_hash_table_lookup(profile->server->providers, profile->remote_providerID); if (is_response) { idx = g_strdup_printf("%s HTTP-Redirect ResponseLocation", profile_name); url = lasso_provider_get_metadata_one(remote_provider, idx); lasso_release(idx); } if (url == NULL) { idx = g_strdup_printf("%s HTTP-Redirect", profile_name); url = lasso_provider_get_metadata_one(remote_provider, idx); lasso_release(idx); } rc = lasso_saml20_profile_build_http_redirect(profile, msg, must_sign, url); lasso_release(url); return rc; }