1607 lines
50 KiB
C
1607 lines
50 KiB
C
/* $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 <xmlsec/base64.h>
|
|
|
|
#include "../utils.h"
|
|
#include "./providerprivate.h"
|
|
#include "./profileprivate.h"
|
|
#include "./profile.h"
|
|
#include "./provider.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 "../id-ff/login.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 "../xml/misc_text_node.h"
|
|
#include "../utils.h"
|
|
#include "../debug.h"
|
|
|
|
static char* lasso_saml20_profile_build_artifact(LassoProvider *provider);
|
|
static int lasso_saml20_profile_export_to_query(LassoProfile *profile, LassoNode *msg, char **query,
|
|
LassoSignatureMethod method, const char *private_key, const char *private_key_password);
|
|
static gint lasso_profile_saml20_build_artifact_get_request_msg(LassoProfile *profile,
|
|
const char *service);
|
|
static gint lasso_profile_saml20_build_artifact_post_request_msg(LassoProfile *profile,
|
|
const char *service);
|
|
static gint lasso_profile_saml20_build_artifact_get_response_msg(LassoProfile *profile,
|
|
const char *service);
|
|
static gint lasso_profile_saml20_build_artifact_post_response_msg(LassoProfile *profile,
|
|
const char *service);
|
|
static gboolean has_signature(LassoNode *node, LassoSignatureMethod *signature_method,
|
|
char **private_key_file, char **private_key_password);
|
|
static char* lasso_saml20_profile_generate_artifact(LassoProfile *profile, int part);
|
|
|
|
#define check_msg_body \
|
|
if (! profile->msg_body) { \
|
|
return critical_error(LASSO_PROFILE_ERROR_BUILDING_MESSAGE_FAILED); \
|
|
}
|
|
|
|
/*
|
|
* 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, const char *service, const 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, const char *service, const 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;
|
|
}
|
|
|
|
static const char*
|
|
http_method_to_binding(LassoHttpMethod method) {
|
|
switch (method) {
|
|
case LASSO_HTTP_METHOD_POST:
|
|
return "HTTP-POST";
|
|
case LASSO_HTTP_METHOD_REDIRECT:
|
|
return "HTTP-Redirect";
|
|
case LASSO_HTTP_METHOD_SOAP:
|
|
return "SOAP";
|
|
case LASSO_HTTP_METHOD_ARTIFACT_GET:
|
|
case LASSO_HTTP_METHOD_ARTIFACT_POST:
|
|
return "HTTP-Artifact";
|
|
case LASSO_HTTP_METHOD_PAOS:
|
|
return "PAOS";
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Artifact Handling functions
|
|
*/
|
|
|
|
/**
|
|
* 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)
|
|
**/
|
|
static char*
|
|
lasso_saml20_profile_generate_artifact(LassoProfile *profile, int part)
|
|
{
|
|
LassoNode *what = NULL;
|
|
lasso_assign_new_string(profile->private_data->artifact,
|
|
lasso_saml20_profile_build_artifact(&profile->server->parent));
|
|
if (part == 0) {
|
|
what = profile->request;
|
|
} else if (part == 1) {
|
|
what = profile->response;
|
|
} else {
|
|
/* XXX: RequestDenied here? */
|
|
}
|
|
/* Remove signature at the response level, if needed if will be on the ArtifactResponse */
|
|
lasso_node_remove_signature(what);
|
|
/* Keep an XML copy of the response for later retrieval */
|
|
lasso_assign_new_string(profile->private_data->artifact_message,
|
|
lasso_node_export_to_xml(what));
|
|
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* this function factorize all case for producing SAML artifact messages
|
|
*/
|
|
static gint
|
|
lasso_profile_saml20_build_artifact_msg(LassoProfile *profile,
|
|
const char *url, int request_or_response, int get_or_post)
|
|
{
|
|
char *artifact = lasso_saml20_profile_generate_artifact(profile, request_or_response);
|
|
|
|
if (artifact == NULL) {
|
|
return critical_error(LASSO_PROFILE_ERROR_BUILDING_QUERY_FAILED);
|
|
}
|
|
/* hack... */
|
|
if (LASSO_IS_LOGIN(profile)) {
|
|
LassoLogin *login = (LassoLogin*)profile;
|
|
lasso_assign_string(login->assertionArtifact, artifact);
|
|
}
|
|
|
|
if (get_or_post == 0) {
|
|
char *query;
|
|
if (profile->msg_relayState) {
|
|
query = lasso_url_add_parameters(NULL, 0, LASSO_SAML2_FIELD_ARTIFACT, artifact, "RelayState",
|
|
profile->msg_relayState, NULL);
|
|
} else {
|
|
query = lasso_url_add_parameters(NULL, 0, LASSO_SAML2_FIELD_ARTIFACT, artifact, NULL);
|
|
}
|
|
lasso_assign_new_string(profile->msg_url,
|
|
lasso_concat_url_query(url, query));
|
|
lasso_release_string(query);
|
|
} else {
|
|
lasso_assign_string(profile->msg_url, url);
|
|
lasso_assign_string(profile->msg_body, artifact);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
enum {
|
|
REQUEST = 0,
|
|
RESPONSE = 1,
|
|
GET = 0,
|
|
POST = 1
|
|
};
|
|
|
|
static gint
|
|
lasso_profile_saml20_build_artifact_get_request_msg(LassoProfile *profile, const char *url)
|
|
{
|
|
return lasso_profile_saml20_build_artifact_msg(profile, url, REQUEST, GET);
|
|
}
|
|
|
|
static gint
|
|
lasso_profile_saml20_build_artifact_post_request_msg(LassoProfile *profile, const char *url)
|
|
{
|
|
return lasso_profile_saml20_build_artifact_msg(profile, url, REQUEST, POST);
|
|
}
|
|
|
|
static gint
|
|
lasso_profile_saml20_build_artifact_get_response_msg(LassoProfile *profile, const char *url)
|
|
{
|
|
return lasso_profile_saml20_build_artifact_msg(profile, url, RESPONSE, GET);
|
|
}
|
|
|
|
static gint
|
|
lasso_profile_saml20_build_artifact_post_response_msg(LassoProfile *profile, const char *url)
|
|
{
|
|
return lasso_profile_saml20_build_artifact_msg(profile, url, RESPONSE, POST);
|
|
}
|
|
|
|
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;
|
|
int rc = 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], LASSO_SAML2_FIELD_ARTIFACT "=", 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();
|
|
|
|
lasso_check_good_rc(lasso_profile_saml20_setup_message_signature(profile,
|
|
(LassoNode*)request));
|
|
|
|
cleanup:
|
|
return rc;
|
|
}
|
|
|
|
int
|
|
lasso_saml20_profile_process_artifact_resolve(LassoProfile *profile, const char *msg)
|
|
{
|
|
LassoProvider *remote_provider;
|
|
int rc = 0;
|
|
|
|
/* FIXME: parse only one time the message, reuse the parsed document for signature
|
|
* validation */
|
|
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 = lasso_server_get_provider(profile->server, profile->remote_providerID);
|
|
|
|
profile->signature_status = lasso_provider_verify_signature(remote_provider, msg, "ID",
|
|
LASSO_MESSAGE_FORMAT_SOAP);
|
|
|
|
switch (lasso_profile_get_signature_verify_hint(profile)) {
|
|
case LASSO_PROFILE_SIGNATURE_VERIFY_HINT_MAYBE:
|
|
rc = profile->signature_status;
|
|
break;
|
|
case LASSO_PROFILE_SIGNATURE_VERIFY_HINT_IGNORE:
|
|
break;
|
|
default:
|
|
g_assert(0);
|
|
break;
|
|
}
|
|
|
|
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 = NULL;
|
|
int rc = 0;
|
|
|
|
if ( ! LASSO_IS_SAMLP2_REQUEST_ABSTRACT(profile->request)) {
|
|
return LASSO_PROFILE_ERROR_MISSING_REQUEST;
|
|
}
|
|
/* Setup the response */
|
|
response = LASSO_SAMLP2_STATUS_RESPONSE(lasso_samlp2_artifact_response_new());
|
|
lasso_assign_new_gobject(profile->response, response);
|
|
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);
|
|
/* Add content */
|
|
if (profile->private_data->artifact_message) {
|
|
xmlDoc *doc;
|
|
xmlNode *node;
|
|
char *content = profile->private_data->artifact_message;
|
|
doc = lasso_xml_parse_memory(content, strlen(content));
|
|
if (doc) {
|
|
node = xmlDocGetRootElement(doc);
|
|
lasso_assign_new_gobject(LASSO_SAMLP2_ARTIFACT_RESPONSE(response)->any,
|
|
lasso_misc_text_node_new_with_xml_node(node));
|
|
lasso_release_doc(doc);
|
|
lasso_saml20_profile_set_response_status(profile,
|
|
LASSO_SAML2_STATUS_CODE_SUCCESS, NULL);
|
|
} else {
|
|
lasso_saml20_profile_set_response_status(profile,
|
|
LASSO_SAML2_STATUS_CODE_RESPONDER,
|
|
LASSO_PRIVATE_STATUS_CODE_FAILED_TO_RESTORE_ARTIFACT);
|
|
}
|
|
} else {
|
|
/* if no artifact is present, it is a success anyway */
|
|
lasso_saml20_profile_set_response_status(profile,
|
|
LASSO_SAML2_STATUS_CODE_SUCCESS, NULL);
|
|
}
|
|
/* Setup the signature */
|
|
lasso_check_good_rc(lasso_profile_saml20_setup_message_signature(profile,
|
|
(LassoNode*)response));
|
|
/* Serialize the message */
|
|
lasso_assign_new_string(profile->msg_body, lasso_node_export_to_soap(profile->response));
|
|
cleanup:
|
|
return rc;
|
|
}
|
|
|
|
int
|
|
lasso_saml20_profile_process_artifact_response(LassoProfile *profile, const char *msg)
|
|
{
|
|
LassoSamlp2ArtifactResponse *artifact_response;
|
|
int rc = 0;
|
|
|
|
artifact_response = (LassoSamlp2ArtifactResponse*)lasso_samlp2_artifact_response_new();
|
|
lasso_check_good_rc(lasso_saml20_profile_process_any_response(profile,
|
|
&artifact_response->parent, NULL, msg));
|
|
/* XXX: check signature status */
|
|
goto_cleanup_if_fail_with_rc(profile->response != NULL,
|
|
critical_error(LASSO_PROFILE_ERROR_INVALID_RESPONSE));
|
|
if (artifact_response->any == NULL) {
|
|
rc = LASSO_PROFILE_ERROR_MISSING_RESPONSE;
|
|
} else {
|
|
if (LASSO_IS_SAMLP2_REQUEST_ABSTRACT(artifact_response->any)) {
|
|
lasso_assign_gobject(profile->request, artifact_response->any);
|
|
} else if (LASSO_IS_SAMLP2_STATUS_RESPONSE(artifact_response->any)) {
|
|
lasso_assign_gobject(profile->response, artifact_response->any);
|
|
} else {
|
|
rc = LASSO_PROFILE_ERROR_INVALID_RESPONSE;
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
lasso_release_gobject(artifact_response);
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* 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[] = {
|
|
LASSO_SAML2_FIELD_REQUEST "=", LASSO_SAML2_FIELD_RESPONSE "=",
|
|
LASSO_SAML2_FIELD_ARTIFACT "=", 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, LassoProfile *profile)
|
|
{
|
|
if (LASSO_IS_SAML2_ASSERTION(assertion) == FALSE) {
|
|
return;
|
|
}
|
|
|
|
if (assertion->Subject != NULL && ! assertion->Subject->NameID && assertion->Subject->EncryptedID != NULL) {
|
|
if (assertion->Subject->EncryptedID->original_data) { /* already decrypted */
|
|
lasso_assign_gobject(assertion->Subject->NameID,
|
|
assertion->Subject->EncryptedID->original_data);
|
|
lasso_release_gobject(assertion->Subject->EncryptedID);
|
|
} else { /* decrypt */
|
|
int rc = 0;
|
|
rc = lasso_saml2_encrypted_element_decrypt(assertion->Subject->EncryptedID,
|
|
lasso_server_get_encryption_private_key(profile->server),
|
|
(LassoNode**) &assertion->Subject->NameID);
|
|
if (rc == 0) {
|
|
lasso_release_gobject(assertion->Subject->EncryptedID);
|
|
} else {
|
|
message(G_LOG_LEVEL_WARNING, "Could not decrypt EncrypteID from"
|
|
" assertion in session dump: %s", lasso_strerror(rc));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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,
|
|
profile);
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* Request handling functions
|
|
*/
|
|
|
|
/**
|
|
* 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 successful (even if signature validation fails), and error code
|
|
* otherwise.
|
|
*/
|
|
int
|
|
lasso_saml20_profile_process_any_request(LassoProfile *profile,
|
|
LassoNode *request_node,
|
|
const char *request_msg)
|
|
{
|
|
int rc = 0;
|
|
LassoSaml2NameID *name_id = NULL;
|
|
LassoProvider *remote_provider = 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(name_id, request_abstract->Issuer, SAML2_NAME_ID,
|
|
LASSO_PROFILE_ERROR_MISSING_ISSUER);
|
|
lasso_assign_string(profile->remote_providerID, request_abstract->Issuer->content);
|
|
|
|
rc = get_provider(profile, &remote_provider);
|
|
goto_cleanup_if_fail(rc == 0);
|
|
|
|
/* 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,
|
|
const char *request_msg)
|
|
{
|
|
int rc = 0;
|
|
LassoSaml2NameID *issuer = NULL;
|
|
LassoProvider *remote_provider = 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(issuer, request_abstract->Issuer, SAML2_NAME_ID,
|
|
LASSO_PROFILE_ERROR_MISSING_ISSUER);
|
|
lasso_assign_string(profile->remote_providerID, issuer->content);
|
|
|
|
rc = get_provider(profile, &remote_provider);
|
|
goto_cleanup_if_fail(rc == 0);
|
|
|
|
profile->signature_status = lasso_provider_verify_signature(
|
|
remote_provider, request_msg, "ID", LASSO_MESSAGE_FORMAT_SOAP);
|
|
|
|
switch (lasso_profile_get_signature_verify_hint(profile)) {
|
|
case LASSO_PROFILE_SIGNATURE_VERIFY_HINT_MAYBE:
|
|
rc = profile->signature_status;
|
|
break;
|
|
case LASSO_PROFILE_SIGNATURE_VERIFY_HINT_IGNORE:
|
|
break;
|
|
default:
|
|
g_assert(0);
|
|
}
|
|
|
|
cleanup:
|
|
return rc;
|
|
}
|
|
|
|
int
|
|
lasso_saml20_profile_init_request(LassoProfile *profile,
|
|
const char *remote_provider_id,
|
|
gboolean first_in_session,
|
|
LassoSamlp2RequestAbstract *request_abstract,
|
|
LassoHttpMethod http_method,
|
|
LassoMdProtocolType protocol_type)
|
|
{
|
|
LassoServer *server = NULL;
|
|
LassoSession *session = 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_REDIRECT &&
|
|
http_method != LASSO_HTTP_METHOD_POST &&
|
|
http_method != LASSO_HTTP_METHOD_ARTIFACT_GET &&
|
|
http_method != LASSO_HTTP_METHOD_ARTIFACT_POST &&
|
|
http_method != LASSO_HTTP_METHOD_SOAP &&
|
|
http_method != LASSO_HTTP_METHOD_PAOS) {
|
|
return critical_error(LASSO_PROFILE_ERROR_INVALID_HTTP_METHOD);
|
|
}
|
|
|
|
/* verify server and session object */
|
|
lasso_extract_node_or_fail(server, profile->server, SERVER,
|
|
LASSO_PROFILE_ERROR_MISSING_SERVER);
|
|
if (LASSO_IS_SESSION(profile->session)) {
|
|
session = profile->session;
|
|
}
|
|
|
|
/* set remote provider Id */
|
|
if (! remote_provider_id) {
|
|
if (first_in_session) {
|
|
if (! session) {
|
|
return LASSO_PROFILE_ERROR_SESSION_NOT_FOUND;
|
|
}
|
|
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)) {
|
|
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));
|
|
|
|
/* set signature */
|
|
lasso_check_good_rc(lasso_profile_saml20_setup_message_signature(profile, profile->request));
|
|
|
|
cleanup:
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
lasso_saml20_profile_build_redirect_request_msg(LassoProfile *profile, const char *url)
|
|
{
|
|
return lasso_saml20_profile_build_http_redirect(profile,
|
|
profile->request,
|
|
url);
|
|
}
|
|
|
|
static int
|
|
lasso_saml20_profile_build_post_request_msg(LassoProfile *profile,
|
|
const char *url)
|
|
{
|
|
lasso_assign_string(profile->msg_url, url);
|
|
lasso_assign_new_string(profile->msg_body,
|
|
lasso_node_export_to_base64(profile->request));
|
|
check_msg_body;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
lasso_saml20_profile_build_soap_request_msg(LassoProfile *profile, const char *url)
|
|
{
|
|
lasso_assign_string(profile->msg_url, url);
|
|
lasso_assign_new_string(profile->msg_body,
|
|
lasso_node_export_to_soap(profile->request));
|
|
check_msg_body;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* the url parameters is special for this function, it does not give the destination of the message
|
|
* (it's implicit for the caller of this function) but where response should be posted later).
|
|
*/
|
|
static int
|
|
lasso_profile_saml20_build_paos_request_msg(LassoProfile *profile, const char *url)
|
|
{
|
|
lasso_assign_new_string(profile->msg_body,
|
|
lasso_node_export_to_paos_request(profile->request,
|
|
profile->server->parent.ProviderID, url,
|
|
profile->msg_relayState));
|
|
check_msg_body;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
lasso_saml20_profile_build_request_msg(LassoProfile *profile, const char *service,
|
|
LassoHttpMethod method, const char *_url)
|
|
{
|
|
LassoProvider *provider;
|
|
char *made_url = NULL, *url;
|
|
int rc = 0;
|
|
|
|
lasso_bad_param(PROFILE, profile);
|
|
|
|
lasso_profile_clean_msg_info(profile);
|
|
lasso_check_good_rc(get_provider(profile, &provider));
|
|
url = (char*)_url;
|
|
|
|
/* check presence of a request */
|
|
if (! LASSO_IS_SAMLP2_REQUEST_ABSTRACT(profile->request)) {
|
|
return critical_error(LASSO_PROFILE_ERROR_MISSING_REQUEST);
|
|
}
|
|
|
|
/* if not explicitely given, automatically determine an URI from the metadatas */
|
|
if (url == NULL) {
|
|
made_url = url = get_url(provider, service, http_method_to_binding(method));
|
|
}
|
|
|
|
if (url) {
|
|
lasso_assign_string(((LassoSamlp2RequestAbstract*)profile->request)->Destination,
|
|
url);
|
|
}
|
|
|
|
switch (method) {
|
|
case LASSO_HTTP_METHOD_SOAP:
|
|
rc = lasso_saml20_profile_build_soap_request_msg(profile, url);
|
|
break;
|
|
case LASSO_HTTP_METHOD_POST:
|
|
rc = lasso_saml20_profile_build_post_request_msg(profile, url);
|
|
break;
|
|
case LASSO_HTTP_METHOD_REDIRECT:
|
|
rc = lasso_saml20_profile_build_redirect_request_msg(profile, url);
|
|
break;
|
|
case LASSO_HTTP_METHOD_ARTIFACT_GET:
|
|
rc = lasso_profile_saml20_build_artifact_get_request_msg(profile, url);
|
|
break;
|
|
case LASSO_HTTP_METHOD_ARTIFACT_POST:
|
|
rc = lasso_profile_saml20_build_artifact_post_request_msg(profile, url);
|
|
break;
|
|
case LASSO_HTTP_METHOD_PAOS:
|
|
rc = lasso_profile_saml20_build_paos_request_msg(profile, url);
|
|
default:
|
|
rc = LASSO_PROFILE_ERROR_INVALID_HTTP_METHOD;
|
|
break;
|
|
}
|
|
|
|
cleanup:
|
|
lasso_release_string(made_url);
|
|
return rc;
|
|
|
|
}
|
|
|
|
/*
|
|
* Response handling functions
|
|
*/
|
|
|
|
int
|
|
lasso_saml20_profile_set_response_status(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;
|
|
}
|
|
|
|
|
|
int
|
|
lasso_saml20_profile_init_response(LassoProfile *profile, LassoSamlp2StatusResponse *status_response,
|
|
const char *status_code1, const char *status_code2)
|
|
{
|
|
int rc = 0;
|
|
|
|
lasso_bad_param(PROFILE, profile);
|
|
if (! LASSO_IS_SAMLP2_STATUS_RESPONSE(status_response))
|
|
return LASSO_PROFILE_ERROR_MISSING_RESPONSE;
|
|
lasso_assign_gobject(profile->response, status_response);
|
|
|
|
lasso_assign_new_string(status_response->ID, lasso_build_unique_id(32));
|
|
lasso_assign_string(status_response->Version, "2.0");
|
|
if (LASSO_IS_SERVER(profile->server)) {
|
|
lasso_assign_new_gobject(status_response->Issuer,
|
|
LASSO_SAML2_NAME_ID(lasso_saml2_name_id_new_with_string(
|
|
profile->server->parent.ProviderID)));
|
|
}
|
|
lasso_assign_new_string(status_response->IssueInstant, lasso_get_current_time());
|
|
if (LASSO_IS_SAMLP2_REQUEST_ABSTRACT(profile->request)) {
|
|
lasso_assign_string(status_response->InResponseTo,
|
|
((LassoSamlp2RequestAbstract*)profile->request)->ID);
|
|
}
|
|
lasso_check_good_rc(lasso_profile_saml20_setup_message_signature(profile,
|
|
profile->response));
|
|
if (status_code1) {
|
|
lasso_saml20_profile_set_response_status(profile,
|
|
status_code1, status_code2);
|
|
}
|
|
|
|
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_saml20_profile_init_response(profile, status_response,
|
|
LASSO_SAML2_STATUS_CODE_SUCCESS, NULL);
|
|
|
|
if (lasso_profile_get_signature_verify_hint(profile) == LASSO_PROFILE_SIGNATURE_VERIFY_HINT_MAYBE &&
|
|
profile->signature_status) {
|
|
lasso_saml20_profile_set_response_status(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;
|
|
|
|
}
|
|
|
|
/**
|
|
* lasso_saml20_profile_export_to_query:
|
|
* @profile: a #LassoProfile
|
|
* @msg: a #LassoNode to export as a query
|
|
* @query: an ouput variable to store the result
|
|
* @signature_method: the signature method for signing the query
|
|
* @private_key_file:(allow-none): the private key to eventually sign the query
|
|
* @private_key_password:(allow-none): the password of the private key if there is one
|
|
*
|
|
* Create a query following the DEFLATE encoding of the SAML 2.0 HTTP
|
|
* Redirect binding. If the root message node has an XML signature, signature is removed and query
|
|
* is signed.
|
|
*
|
|
* Return value: 0 if successful, an error code otherwise.
|
|
*/
|
|
static int
|
|
lasso_saml20_profile_export_to_query(LassoProfile *profile, LassoNode *msg, char **query,
|
|
LassoSignatureMethod signature_method, const char *private_key_file,
|
|
const char *private_key_password) {
|
|
char *unsigned_query = NULL;
|
|
char *result = NULL;
|
|
int rc = 0;
|
|
|
|
unsigned_query = lasso_node_build_query(msg);
|
|
goto_cleanup_if_fail_with_rc(unsigned_query != NULL,
|
|
LASSO_PROFILE_ERROR_BUILDING_QUERY_FAILED);
|
|
if (profile->msg_relayState) {
|
|
unsigned_query = lasso_url_add_parameters(unsigned_query, 1, "RelayState",
|
|
profile->msg_relayState, NULL);
|
|
|
|
if (strlen(profile->msg_relayState) > 80) {
|
|
message(G_LOG_LEVEL_WARNING, "Encoded a RelayState of more than 80 bytes, "
|
|
"see #3.4.3 of saml-bindings-2.0-os");
|
|
}
|
|
}
|
|
if (signature_method && private_key_file && lasso_flag_add_signature) {
|
|
result = lasso_query_sign(unsigned_query, signature_method, private_key_file,
|
|
private_key_password);
|
|
goto_cleanup_if_fail_with_rc(result != NULL,
|
|
LASSO_PROFILE_ERROR_BUILDING_QUERY_FAILED);
|
|
lasso_transfer_string(*query, result);
|
|
} else {
|
|
lasso_transfer_string(*query, unsigned_query);
|
|
}
|
|
cleanup:
|
|
lasso_release_string(unsigned_query);
|
|
lasso_release_string(result);
|
|
return rc;
|
|
}
|
|
|
|
static gboolean
|
|
has_signature(LassoNode *node, LassoSignatureMethod *method, char **private_key_file,
|
|
char **private_key_password) {
|
|
LassoNodeClass *klass;
|
|
LassoSignatureType sign_type;
|
|
LassoSignatureMethod sign_method;
|
|
char *key;
|
|
char *password;
|
|
|
|
if (node == NULL)
|
|
return FALSE;
|
|
|
|
/* new signature parameters storage */
|
|
lasso_node_get_signature(node, &sign_type, &sign_method, &key, &password, NULL);
|
|
if (sign_type) {
|
|
*method = sign_method;
|
|
lasso_assign_string(*private_key_file, key);
|
|
lasso_assign_string(*private_key_password, password);
|
|
return TRUE;
|
|
}
|
|
|
|
klass = LASSO_NODE_GET_CLASS(node);
|
|
/* follow the class parenting chain */
|
|
while (klass && LASSO_IS_NODE_CLASS(klass)) {
|
|
if (klass && klass->node_data && klass->node_data->sign_type_offset != 0) {
|
|
if (G_STRUCT_MEMBER(LassoSignatureType, node,
|
|
klass->node_data->sign_type_offset)
|
|
!= LASSO_SIGNATURE_TYPE_NONE) {
|
|
*method = G_STRUCT_MEMBER(LassoSignatureMethod, node,
|
|
klass->node_data->sign_method_offset);
|
|
lasso_assign_string(*private_key_file, G_STRUCT_MEMBER(char*, node,
|
|
klass->node_data->private_key_file_offset));
|
|
/** FIXME: retrieve the stored key password */
|
|
*private_key_password = NULL;
|
|
return TRUE;
|
|
}
|
|
}
|
|
klass = g_type_class_peek_parent(klass);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* lasso_saml20_profile_build_http_redirect:
|
|
* @profile: a #LassoProfile object
|
|
* @msg: a #LassoNode object representing a SAML 2.0 message
|
|
* @must_sign: wheter to sign the query message using query signatures
|
|
* @url: the URL where the query is targeted
|
|
*
|
|
* Build an HTTP URL with a query-string following the SAML 2.0 HTTP-Redirect binding rules,
|
|
* eventually sign it. Any signature at the message level is removed.
|
|
*
|
|
* Return value: 0 if successful, an error code otherwise.
|
|
*/
|
|
gint
|
|
lasso_saml20_profile_build_http_redirect(LassoProfile *profile,
|
|
LassoNode *msg,
|
|
const char *url)
|
|
{
|
|
char *query = NULL;
|
|
int rc = 0;
|
|
LassoSignatureMethod signature_method = 0;
|
|
char *private_key_file = NULL;
|
|
char *private_key_password = NULL;
|
|
|
|
goto_cleanup_if_fail_with_rc (url != NULL, LASSO_PROFILE_ERROR_UNKNOWN_PROFILE_URL);
|
|
/* if message is signed, remove XML signature, add query signature */
|
|
if (has_signature(msg, &signature_method, (char **)&private_key_file,
|
|
(char **)&private_key_password)) {
|
|
lasso_node_remove_signature(msg);
|
|
}
|
|
lasso_check_good_rc(lasso_saml20_profile_export_to_query(profile, msg, &query,
|
|
signature_method, private_key_file, private_key_password));
|
|
|
|
lasso_assign_new_string(profile->msg_url, lasso_concat_url_query(url, query));
|
|
lasso_release(profile->msg_body);
|
|
lasso_release(query);
|
|
|
|
cleanup:
|
|
lasso_release_string(private_key_file);
|
|
lasso_release_string(private_key_password);
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
lasso_saml20_profile_build_redirect_response_msg(LassoProfile *profile, const char *url)
|
|
{
|
|
return lasso_saml20_profile_build_http_redirect(profile,
|
|
profile->response,
|
|
url);
|
|
}
|
|
|
|
static int
|
|
lasso_saml20_profile_build_post_response_msg(LassoProfile *profile, const char *url)
|
|
{
|
|
lasso_assign_string(profile->msg_url, url);
|
|
lasso_assign_new_string(profile->msg_body, lasso_node_export_to_base64(profile->response));
|
|
check_msg_body;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
lasso_saml20_profile_build_soap_response_msg(LassoProfile *profile)
|
|
{
|
|
lasso_release_string(profile->msg_url);
|
|
lasso_assign_new_string(profile->msg_body, lasso_node_export_to_soap(profile->response));
|
|
check_msg_body;
|
|
return 0;
|
|
}
|
|
|
|
|
|
int
|
|
lasso_saml20_profile_build_response_msg(LassoProfile *profile, char *service,
|
|
LassoHttpMethod method, const char *_url)
|
|
{
|
|
LassoProvider *provider;
|
|
char *made_url = NULL, *url;
|
|
int rc = 0;
|
|
|
|
lasso_bad_param(PROFILE, profile);
|
|
|
|
lasso_profile_clean_msg_info(profile);
|
|
lasso_check_good_rc(get_provider(profile, &provider));
|
|
url = (char*)_url;
|
|
|
|
/* check presence of a request */
|
|
if (! LASSO_IS_SAMLP2_STATUS_RESPONSE(profile->response)) {
|
|
return critical_error(LASSO_PROFILE_ERROR_MISSING_RESPONSE);
|
|
}
|
|
|
|
/* if not explicitely given, automatically determine an URI from the metadatas */
|
|
if (url == NULL && service && method != LASSO_HTTP_METHOD_SOAP) {
|
|
made_url = url = get_response_url(provider, service, http_method_to_binding(method));
|
|
}
|
|
|
|
/* only asynchronous bindings needs an URL for the response, SOAP does not need it, and PAOS
|
|
* is special (response is a SOAP request !?! ) */
|
|
if (! url) {
|
|
switch (method) {
|
|
case LASSO_HTTP_METHOD_POST:
|
|
case LASSO_HTTP_METHOD_REDIRECT:
|
|
case LASSO_HTTP_METHOD_ARTIFACT_GET:
|
|
case LASSO_HTTP_METHOD_ARTIFACT_POST:
|
|
case LASSO_HTTP_METHOD_PAOS:
|
|
goto_cleanup_with_rc(critical_error(LASSO_PROFILE_ERROR_UNKNOWN_PROFILE_URL));
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (method) {
|
|
case LASSO_HTTP_METHOD_POST:
|
|
rc = lasso_saml20_profile_build_post_response_msg(profile, url);
|
|
break;
|
|
case LASSO_HTTP_METHOD_REDIRECT:
|
|
rc = lasso_saml20_profile_build_redirect_response_msg(profile, url);
|
|
break;
|
|
case LASSO_HTTP_METHOD_SOAP:
|
|
rc = lasso_saml20_profile_build_soap_response_msg(profile);
|
|
break;
|
|
case LASSO_HTTP_METHOD_ARTIFACT_GET:
|
|
rc = lasso_profile_saml20_build_artifact_get_response_msg(profile, url);
|
|
break;
|
|
case LASSO_HTTP_METHOD_ARTIFACT_POST:
|
|
rc = lasso_profile_saml20_build_artifact_post_response_msg(profile, url);
|
|
break;
|
|
default:
|
|
rc= LASSO_PROFILE_ERROR_UNSUPPORTED_PROFILE;
|
|
break;
|
|
}
|
|
|
|
cleanup:
|
|
lasso_release_string(made_url);
|
|
return rc;
|
|
}
|
|
|
|
static gboolean
|
|
_lasso_saml20_is_valid_issuer(LassoSaml2NameID *name_id) {
|
|
if (! LASSO_IS_SAML2_NAME_ID(name_id))
|
|
return FALSE;
|
|
|
|
if (name_id->Format && g_strcmp0(name_id->Format, LASSO_SAML2_NAME_IDENTIFIER_FORMAT_ENTITY) != 0) {
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* 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,
|
|
LassoHttpMethod *response_method,
|
|
const char *response_msg)
|
|
{
|
|
int rc = 0;
|
|
LassoProvider *remote_provider = NULL;
|
|
LassoServer *server = NULL;
|
|
LassoSamlp2StatusResponse *response_abstract = NULL;
|
|
LassoSamlp2Status *status = NULL;
|
|
LassoSamlp2StatusCode *status_code1 = NULL;
|
|
LassoMessageFormat format;
|
|
gboolean missing_issuer = FALSE;
|
|
LassoProfileSignatureVerifyHint signature_verify_hint;
|
|
|
|
xmlDoc *doc = NULL;
|
|
xmlNode *content = NULL;
|
|
|
|
lasso_bad_param(PROFILE, profile);
|
|
lasso_bad_param(SAMLP2_STATUS_RESPONSE, status_response);
|
|
|
|
signature_verify_hint = lasso_profile_get_signature_verify_hint(profile);
|
|
/* 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;
|
|
}
|
|
if (response_method) {
|
|
switch (format) {
|
|
case LASSO_MESSAGE_FORMAT_SOAP:
|
|
*response_method = LASSO_HTTP_METHOD_SOAP;
|
|
break;
|
|
case LASSO_MESSAGE_FORMAT_QUERY:
|
|
*response_method = LASSO_HTTP_METHOD_REDIRECT;
|
|
break;
|
|
case LASSO_MESSAGE_FORMAT_BASE64:
|
|
*response_method = LASSO_HTTP_METHOD_POST;
|
|
break;
|
|
default:
|
|
return LASSO_PROFILE_ERROR_UNSUPPORTED_PROFILE;
|
|
}
|
|
}
|
|
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);
|
|
if (_lasso_saml20_is_valid_issuer(response_abstract->Issuer)) {
|
|
lasso_assign_string(profile->remote_providerID, response_abstract->Issuer->content);
|
|
remote_provider = lasso_server_get_provider(server, profile->remote_providerID);
|
|
} else {
|
|
missing_issuer = TRUE;
|
|
}
|
|
|
|
if (remote_provider) {
|
|
/* verify the signature at the message 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_DS_ERROR_SIGNATURE_VERIFICATION_FAILED;
|
|
}
|
|
} else {
|
|
rc = LASSO_SERVER_ERROR_PROVIDER_NOT_FOUND;
|
|
profile->signature_status = LASSO_SERVER_ERROR_PROVIDER_NOT_FOUND;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* 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 (g_strcmp0(status_code1->Value,
|
|
LASSO_SAML2_STATUS_CODE_SUCCESS) != 0)
|
|
{
|
|
LassoSamlp2StatusCode *status_code2 = status_code1->StatusCode;
|
|
rc = LASSO_PROFILE_ERROR_STATUS_NOT_SUCCESS;
|
|
|
|
if (!status_code2)
|
|
goto cleanup;
|
|
|
|
if (!status_code2->Value)
|
|
goto cleanup;
|
|
/* FIXME: what to do with secondary status code ? */
|
|
}
|
|
|
|
cleanup:
|
|
lasso_release_doc(doc);
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
if ((signature_verify_hint == LASSO_PROFILE_SIGNATURE_VERIFY_HINT_MAYBE) &&
|
|
profile->signature_status) {
|
|
return LASSO_PROFILE_ERROR_CANNOT_VERIFY_SIGNATURE;
|
|
}
|
|
if (missing_issuer) {
|
|
return LASSO_PROFILE_ERROR_MISSING_ISSUER;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* 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,
|
|
const 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_SERVER_ERROR_PROVIDER_NOT_FOUND;
|
|
goto cleanup;
|
|
}
|
|
|
|
profile->signature_status = lasso_provider_verify_signature(
|
|
remote_provider, response_msg, "ID", LASSO_MESSAGE_FORMAT_SOAP);
|
|
switch (lasso_profile_get_signature_verify_hint(profile)) {
|
|
case LASSO_PROFILE_SIGNATURE_VERIFY_HINT_MAYBE:
|
|
rc = profile->signature_status;
|
|
break;
|
|
case LASSO_PROFILE_SIGNATURE_VERIFY_HINT_IGNORE:
|
|
break;
|
|
default:
|
|
g_assert(0);
|
|
break;
|
|
}
|
|
|
|
cleanup:
|
|
return rc;
|
|
}
|
|
|
|
gint
|
|
lasso_saml20_profile_build_http_redirect_query_simple(LassoProfile *profile,
|
|
LassoNode *msg,
|
|
const char *profile_name,
|
|
gboolean is_response)
|
|
{
|
|
char *idx = NULL;
|
|
char *url = NULL;
|
|
LassoProvider *remote_provider = NULL;
|
|
int rc = 0;
|
|
|
|
|
|
goto_cleanup_if_fail_with_rc(profile->remote_providerID != NULL,
|
|
LASSO_PROFILE_ERROR_MISSING_REMOTE_PROVIDERID);
|
|
remote_provider = lasso_server_get_provider(profile->server,
|
|
profile->remote_providerID);
|
|
goto_cleanup_if_fail_with_rc(LASSO_IS_PROVIDER(remote_provider),
|
|
LASSO_SERVER_ERROR_PROVIDER_NOT_FOUND);
|
|
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);
|
|
}
|
|
/* remove signature at the message level */
|
|
rc = lasso_saml20_profile_build_http_redirect(profile, msg, url);
|
|
cleanup:
|
|
lasso_release(url);
|
|
return rc;
|
|
}
|
|
|
|
gint
|
|
lasso_profile_saml20_setup_message_signature(LassoProfile *profile, LassoNode *request_or_response)
|
|
{
|
|
lasso_bad_param(PROFILE, profile);
|
|
|
|
switch (lasso_profile_get_signature_hint(profile)) {
|
|
case LASSO_PROFILE_SIGNATURE_HINT_MAYBE:
|
|
if (! lasso_flag_sign_messages) {
|
|
message(G_LOG_LEVEL_WARNING, "message should be signed but no-sign-messages flag is " \
|
|
"activated, so it won't be");
|
|
return 0;
|
|
}
|
|
break;
|
|
case LASSO_PROFILE_SIGNATURE_HINT_FORBID:
|
|
return 0;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (! LASSO_IS_SERVER(profile->server)) {
|
|
return LASSO_PROFILE_ERROR_MISSING_SERVER;
|
|
}
|
|
if (! profile->server->private_key) {
|
|
return LASSO_DS_ERROR_PRIVATE_KEY_LOAD_FAILED;
|
|
}
|
|
if (LASSO_IS_SAMLP2_REQUEST_ABSTRACT(request_or_response)) {
|
|
LassoSamlp2RequestAbstract *request;
|
|
|
|
request = (LassoSamlp2RequestAbstract*)request_or_response;
|
|
if (profile->server->certificate) {
|
|
request->sign_type = LASSO_SIGNATURE_TYPE_WITHX509;
|
|
} else {
|
|
request->sign_type = LASSO_SIGNATURE_TYPE_SIMPLE;
|
|
}
|
|
request->sign_method = LASSO_SIGNATURE_METHOD_RSA_SHA1;
|
|
lasso_assign_string(request->private_key_file,
|
|
profile->server->private_key);
|
|
lasso_assign_string(request->certificate_file,
|
|
profile->server->certificate);
|
|
lasso_node_set_signature(request_or_response, request->sign_type,
|
|
request->sign_method, profile->server->private_key,
|
|
profile->server->private_key_password,
|
|
profile->server->certificate);
|
|
} else if (LASSO_IS_SAMLP2_STATUS_RESPONSE(request_or_response)) {
|
|
LassoSamlp2StatusResponse *response;
|
|
|
|
response = (LassoSamlp2StatusResponse*)request_or_response;
|
|
if (profile->server->certificate) {
|
|
response->sign_type = LASSO_SIGNATURE_TYPE_WITHX509;
|
|
} else {
|
|
response->sign_type = LASSO_SIGNATURE_TYPE_SIMPLE;
|
|
}
|
|
response->sign_method = LASSO_SIGNATURE_METHOD_RSA_SHA1;
|
|
lasso_assign_string(response->private_key_file,
|
|
profile->server->private_key);
|
|
lasso_assign_string(response->certificate_file,
|
|
profile->server->certificate);
|
|
lasso_node_set_signature(request_or_response, response->sign_type,
|
|
response->sign_method, profile->server->private_key,
|
|
profile->server->private_key_password,
|
|
profile->server->certificate);
|
|
} else {
|
|
return LASSO_PARAM_ERROR_BAD_TYPE_OR_NULL_OBJ;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* lasso_saml20_profile_setup_subject:
|
|
* @profile: a #LassoProfile object
|
|
* @subject: a #LassoSaml2Subject object
|
|
*
|
|
* Encrypt subject if necessary.
|
|
*/
|
|
int
|
|
lasso_saml20_profile_setup_subject(LassoProfile *profile,
|
|
LassoSaml2Subject *subject)
|
|
{
|
|
LassoProvider *remote_provider;
|
|
|
|
remote_provider = lasso_server_get_provider(profile->server, profile->remote_providerID);
|
|
g_return_val_if_fail (LASSO_IS_PROVIDER(remote_provider), LASSO_ERROR_CAST_FAILED);
|
|
if (! (lasso_provider_get_encryption_mode(remote_provider) & LASSO_ENCRYPTION_MODE_NAMEID)) {
|
|
return 0;
|
|
}
|
|
return lasso_saml20_profile_setup_encrypted_node(remote_provider,
|
|
(LassoNode**)subject->NameID,
|
|
(LassoNode**)subject->EncryptedID);
|
|
}
|
|
|
|
gint
|
|
lasso_saml20_profile_setup_encrypted_node(LassoProvider *provider,
|
|
LassoNode **node_to_encrypt, LassoNode **node_destination)
|
|
{
|
|
LassoNode *encrypted_node;
|
|
|
|
if (! LASSO_IS_PROVIDER(provider)) {
|
|
return critical_error(LASSO_SERVER_ERROR_PROVIDER_NOT_FOUND);
|
|
}
|
|
encrypted_node = (LassoNode*)lasso_node_encrypt(*node_to_encrypt,
|
|
lasso_provider_get_encryption_public_key(provider),
|
|
lasso_provider_get_encryption_sym_key_type(provider),
|
|
provider->ProviderID);
|
|
if (! encrypted_node) {
|
|
return LASSO_DS_ERROR_ENCRYPTION_FAILED;
|
|
}
|
|
lasso_assign_new_gobject(*node_destination, encrypted_node);
|
|
lasso_release_gobject(*node_to_encrypt);
|
|
return 0;
|
|
}
|