lasso/lasso/saml-2.0/provider.c

601 lines
17 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
*/
#define _POSIX_SOURCE
#include "../xml/private.h"
#include <xmlsec/base64.h>
#include <xmlsec/xmltree.h>
#include "providerprivate.h"
#include "../id-ff/providerprivate.h"
#include "../utils.h"
#include "./provider.h"
#include "../xml/saml-2.0/saml2_attribute.h"
#include "../xml/saml-2.0/saml2_xsd.h"
const char *profile_names[LASSO_MD_PROTOCOL_TYPE_LAST] = {
"", /* No fedterm in SAML 2.0 */
"NameIDMappingService", /*IDPSSODescriptor*/
"", /* No rni in SAML 2.0 */
"SingleLogoutService", /*SSODescriptor*/
"SingleSignOnService", /*IDPSSODescriptor*/
"ArtifactResolutionService", /*SSODescriptor*/
"ManageNameIDService", /*SSODescriptor*/
"AssertionIDRequestService", /* IDPSSODescriptor,
AuthnAuhtorityDescriptor,
PDPDescriptor,
AttributeAuthorityDescriptor */
"AuthnQueryService", /*AuthnAuthorityDescriptor*/
"AuthzService", /*PDPDescriptor*/
"AttributeService" /*AttributeAuthorityDescriptor*/
};
static void add_assertion_consumer_url_to_list(gchar *key, G_GNUC_UNUSED gpointer value, GList **list);
static const char*
binding_uri_to_identifier(const char *uri)
{
if (strcmp(uri, LASSO_SAML2_METADATA_BINDING_SOAP) == 0) {
return "SOAP";
} else if (strcmp(uri, LASSO_SAML2_METADATA_BINDING_REDIRECT) == 0) {
return "HTTP-Redirect";
} else if (strcmp(uri, LASSO_SAML2_METADATA_BINDING_POST) == 0) {
return "HTTP-POST";
} else if (strcmp(uri, LASSO_SAML2_METADATA_BINDING_ARTIFACT) == 0) {
return "HTTP-Artifact";
} else if (strcmp(uri, LASSO_SAML2_METADATA_BINDING_PAOS) == 0) {
return "PAOS";
} else if (strcmp(uri, LASSO_SAML2_METADATA_BINDING_URI) == 0) {
return "URI";
}
return NULL;
}
static gboolean
checkSaml2MdNode(xmlNode *t, char *name)
{
return xmlSecCheckNodeName(t,
BAD_CAST name,
BAD_CAST LASSO_SAML2_METADATA_HREF);
}
static xmlChar*
getSaml2MdProp(xmlNode *t, char *name) {
return xmlGetProp(t, BAD_CAST name);
}
static gboolean
hasSaml2MdProp(xmlNode *t, char *name) {
return xmlHasProp(t, BAD_CAST name) != NULL;
}
static gboolean
xsdIsTrue(xmlChar *value)
{
if (value && strcmp((char*)value, "true") == 0)
return TRUE;
return FALSE;
}
static void
load_endpoint_type(xmlNode *xmlnode, LassoProvider *provider, LassoProviderRole role)
{
xmlChar *binding = xmlGetProp(xmlnode, BAD_CAST "Binding");
char *name = NULL;
char *response_name = NULL;
LassoProviderPrivate *private_data = provider->private_data;
const char *binding_s = NULL;
xmlChar *value = NULL;
xmlChar *response_value = NULL;
binding_s = binding_uri_to_identifier((char*)binding);
if (! binding_s) {
message(G_LOG_LEVEL_CRITICAL, "XXX: unknown binding: %s", binding);
goto cleanup;
}
/* get endpoint location */
value = getSaml2MdProp(xmlnode, LASSO_SAML2_METADATA_ATTRIBUTE_LOCATION);
if (value == NULL) {
message(G_LOG_LEVEL_CRITICAL, "XXX: missing location for element %s", xmlnode->name);
goto cleanup;
}
/* special case of AssertionConsumerService */
if (checkSaml2MdNode(xmlnode, LASSO_SAML2_METADATA_ELEMENT_ASSERTION_CONSUMER_SERVICE)) {
xmlChar *index = getSaml2MdProp(xmlnode, LASSO_SAML2_METADATA_ATTRIBUTE_INDEX);
xmlChar *is_default = getSaml2MdProp(xmlnode, LASSO_SAML2_METADATA_ATTRIBUTE_ISDEFAULT);
if (xsdIsTrue(is_default)) {
lasso_assign_string(private_data->default_assertion_consumer, (char*)index);
}
name = g_strdup_printf(LASSO_SAML2_METADATA_ELEMENT_ASSERTION_CONSUMER_SERVICE
" %s %s",
binding_s,
index);
lasso_release_xml_string(index);
lasso_release_xml_string(is_default);
} else {
name = g_strdup_printf("%s %s", xmlnode->name, binding_s);
}
lasso_release_xml_string(binding);
/* Response endpoint ? */
response_value = getSaml2MdProp(xmlnode, LASSO_SAML2_METADATA_ATTRIBUTE_RESPONSE_LOCATION);
if (response_value) {
response_name = g_strdup_printf("%s "
LASSO_SAML2_METADATA_ATTRIBUTE_RESPONSE_LOCATION,
name);
_lasso_provider_add_metadata_value_for_role(provider, role, response_name,
(char*)response_value);
}
_lasso_provider_add_metadata_value_for_role(provider, role, name, (char*)value);
cleanup:
lasso_release_xml_string(value);
lasso_release_xml_string(response_value);
lasso_release_string(name);
lasso_release_string(response_name);
}
static gboolean
load_descriptor(xmlNode *xmlnode, LassoProvider *provider, LassoProviderRole role)
{
static char * const descriptor_attrs[] = {
LASSO_SAML2_METADATA_ATTRIBUTE_VALID_UNTIL,
LASSO_SAML2_METADATA_ATTRIBUTE_CACHE_DURATION,
LASSO_SAML2_METADATA_ATTRIBUTE_AUTHN_REQUEST_SIGNED,
LASSO_SAML2_METADATA_ATTRIBUTE_WANT_AUTHN_REQUEST_SIGNED,
LASSO_SAML2_METADATA_ATTRIBUTE_ERROR_URL
};
int i;
xmlNode *t;
xmlChar *value;
LassoProviderPrivate *pdata = provider->private_data;
char *token, *saveptr;
/* check protocol support enumeration */
value = getSaml2MdProp(xmlnode,
LASSO_SAML2_METADATA_ATTRIBUTE_PROTOCOL_SUPPORT_ENUMERATION);
token = strtok_r((char*) value, " ", &saveptr);
while (token) {
if (strcmp(token, LASSO_SAML2_PROTOCOL_HREF) == 0)
break;
token = strtok_r(NULL, " ", &saveptr);
}
if (g_strcmp0(token, LASSO_SAML2_PROTOCOL_HREF) != 0) {
lasso_release_xml_string(value);
message(G_LOG_LEVEL_WARNING, "%s descriptor does not support SAML 2.0 protocol", xmlnode->name);
return FALSE;
}
lasso_release_xml_string(value);
/* add role to supported roles for the provider */
pdata->roles |= role;
t = xmlSecGetNextElementNode(xmlnode->children);
while (t) {
if (checkSaml2MdNode(t,
LASSO_SAML2_METADATA_ELEMENT_KEY_DESCRIPTOR)) {
_lasso_provider_load_key_descriptor(provider, t);
} else if (checkSaml2MdNode(t,
LASSO_SAML2_ASSERTION_ELEMENT_ATTRIBUTE) && role == LASSO_PROVIDER_ROLE_IDP) {
LassoSaml2Attribute *attribute;
attribute = (LassoSaml2Attribute*) lasso_node_new_from_xmlNode(t);
lasso_list_add_new_gobject(pdata->attributes,
attribute);
} else if (hasSaml2MdProp(t, LASSO_SAML2_METADATA_ATTRIBUTE_BINDING)) {
load_endpoint_type(t, provider, role);
} else {
value = xmlNodeGetContent(t);
_lasso_provider_add_metadata_value_for_role(provider, role, (char*)t->name,
(char*)value);
lasso_release_xml_string(value);
}
t = xmlSecGetNextElementNode(t->next);
}
for (i = 0; descriptor_attrs[i]; i++) {
value = getSaml2MdProp(xmlnode, descriptor_attrs[i]);
if (value == NULL) {
continue;
}
_lasso_provider_add_metadata_value_for_role(provider, role, descriptor_attrs[i],
(char*)value);
lasso_release_xml_string(value);
}
return TRUE;
}
gboolean
lasso_saml20_provider_load_metadata(LassoProvider *provider, xmlNode *root_node)
{
xmlNode *node, *descriptor_node;
xmlChar *providerID;
LassoProviderPrivate *pdata = provider->private_data;
static const struct {
char *name;
LassoProviderRole role;
} descriptors[] = {
{ LASSO_SAML2_METADATA_ELEMENT_IDP_SSO_DESCRIPTOR,
LASSO_PROVIDER_ROLE_IDP },
{ LASSO_SAML2_METADATA_ELEMENT_SP_SSO_DESCRIPTOR,
LASSO_PROVIDER_ROLE_SP },
{ LASSO_SAML2_METADATA_ELEMENT_ATTRIBUTE_AUTHORITY_DESCRIPTOR,
LASSO_PROVIDER_ROLE_ATTRIBUTE_AUTHORITY },
{ LASSO_SAML2_METADATA_ELEMENT_PDP_DESCRIPTOR,
LASSO_PROVIDER_ROLE_AUTHZ_AUTHORITY },
{ LASSO_SAML2_METADATA_ELEMENT_AUTHN_DESCRIPTOR,
LASSO_PROVIDER_ROLE_AUTHN_AUTHORITY },
{ NULL, 0 }
};
/* find a root node for the metadata file */
if (xmlSecCheckNodeName(root_node,
BAD_CAST LASSO_SAML2_METADATA_ELEMENT_ENTITY_DESCRIPTOR,
BAD_CAST LASSO_SAML2_METADATA_HREF)) {
node = root_node;
} else if (xmlSecCheckNodeName(root_node,
BAD_CAST LASSO_SAML2_METADATA_ELEMENT_ENTITIES_DESCRIPTOR,
BAD_CAST LASSO_SAML2_METADATA_HREF)) {
node = xmlSecFindChild(root_node,
BAD_CAST LASSO_SAML2_METADATA_ELEMENT_ENTITY_DESCRIPTOR,
BAD_CAST LASSO_SAML2_METADATA_HREF);
}
g_return_val_if_fail (node, FALSE);
providerID = xmlGetProp(node, (xmlChar*)"entityID");
g_return_val_if_fail(providerID, FALSE);
lasso_assign_string(provider->ProviderID, (char*)providerID);
lasso_release_xml_string(providerID);
/* initialize roles */
pdata->roles = LASSO_PROVIDER_ROLE_NONE;
lasso_set_string_from_prop(&pdata->valid_until, node,
BAD_CAST LASSO_SAML2_METADATA_ATTRIBUTE_VALID_UNTIL,
BAD_CAST LASSO_SAML2_METADATA_HREF);
lasso_set_string_from_prop(&pdata->cache_duration, node,
BAD_CAST LASSO_SAML2_METADATA_ATTRIBUTE_CACHE_DURATION,
BAD_CAST LASSO_SAML2_METADATA_HREF);
descriptor_node = xmlSecGetNextElementNode(node->children);
while (descriptor_node) {
int i = 0;
while (descriptors[i].name) {
char *name = descriptors[i].name;
LassoProviderRole role = descriptors[i].role;
if (checkSaml2MdNode(descriptor_node, name)) {
load_descriptor(descriptor_node,
provider,
role);
}
i++;
}
if (checkSaml2MdNode(descriptor_node,
LASSO_SAML2_METADATA_ELEMENT_ORGANIZATION)) {
lasso_assign_xml_node(pdata->organization, descriptor_node); }
descriptor_node = xmlSecGetNextElementNode(descriptor_node->next);
}
return TRUE;
}
LassoHttpMethod
lasso_saml20_provider_get_first_http_method(LassoProvider *provider,
LassoProvider *remote_provider, LassoMdProtocolType protocol_type)
{
LassoHttpMethod method = LASSO_HTTP_METHOD_NONE;
int i;
const char *possible_bindings[] = {
"HTTP-POST",
"HTTP-Redirect",
"HTTP-Artifact",
"SOAP",
"PAOS",
NULL
};
LassoHttpMethod method_bindings[] = {
LASSO_HTTP_METHOD_POST,
LASSO_HTTP_METHOD_REDIRECT,
LASSO_HTTP_METHOD_ARTIFACT_GET,
LASSO_HTTP_METHOD_SOAP,
LASSO_HTTP_METHOD_PAOS
};
for (i=0; possible_bindings[i] && method == LASSO_HTTP_METHOD_NONE; i++) {
char *s;
const GList *l1, *l2;
s = g_strdup_printf("%s %s",
profile_names[protocol_type],
possible_bindings[i]);
l1 = lasso_provider_get_metadata_list(provider, s);
l2 = lasso_provider_get_metadata_list(remote_provider, s);
if (l1 && l2) {
method = method_bindings[i];
}
}
return method;
}
gboolean
lasso_saml20_provider_check_assertion_consumer_service_url(LassoProvider *provider, const gchar *url, const gchar *binding)
{
GHashTable *descriptor;
GList *l = NULL, *r = NULL, *candidate = NULL;
char *name;
const char *binding_s = NULL;
int lname;
descriptor = provider->private_data->Descriptors;
if (descriptor == NULL || url == NULL || binding == NULL)
return FALSE;
binding_s = binding_uri_to_identifier(binding);
if (binding_s == NULL) {
return FALSE;
}
g_hash_table_foreach(descriptor,
(GHFunc)add_assertion_consumer_url_to_list,
&r);
name = g_strdup_printf(LASSO_SAML2_METADATA_ELEMENT_ASSERTION_CONSUMER_SERVICE
" %s ", binding_s);
lname = strlen(name);
for (l = r; l; l = g_list_next(l)) {
char *b = l->data;
if (strncmp(name, b, lname) == 0) {
candidate = lasso_provider_get_metadata_list_for_role(provider, LASSO_PROVIDER_ROLE_SP, b);
if (candidate && candidate->data && strcmp(candidate->data, url) == 0)
break;
else
candidate = NULL;
}
}
g_free(name);
g_list_free(r);
if (candidate)
return TRUE;
else
return FALSE;
}
gchar*
lasso_saml20_provider_get_assertion_consumer_service_url(LassoProvider *provider,
int service_id)
{
GList *l = NULL;
char *sid;
char *name;
const char *possible_bindings[] = {
"HTTP-Artifact",
"HTTP-POST",
"HTTP-Redirect",
NULL
};
int i;
if (service_id == -1) {
sid = g_strdup(provider->private_data->default_assertion_consumer);
} else {
sid = g_strdup_printf("%d", service_id);
}
for (i=0; possible_bindings[i]; i++) {
name = g_strdup_printf(LASSO_SAML2_METADATA_ELEMENT_ASSERTION_CONSUMER_SERVICE
" %s %s",
possible_bindings[i], sid);
l = lasso_provider_get_metadata_list_for_role(provider,
LASSO_PROVIDER_ROLE_SP,
name);
lasso_release_string(name);
if (l != NULL)
break;
}
lasso_release_string(sid);
if (l)
return g_strdup(l->data);
return NULL;
}
static void
add_assertion_consumer_url_to_list(gchar *key, G_GNUC_UNUSED gpointer value, GList **list)
{
if (strncmp(key, "sp AssertionConsumerService", 24) == 0) {
lasso_list_add_new_string(*list, key);
}
}
gchar*
lasso_saml20_provider_get_assertion_consumer_service_url_by_binding(LassoProvider *provider,
const gchar *binding)
{
GHashTable *descriptor;
GList *l = NULL, *r = NULL;
char *name;
const char *binding_s = NULL;
int lname;
descriptor = provider->private_data->Descriptors;
if (descriptor == NULL)
return NULL;
binding_s = binding_uri_to_identifier(binding);
if (binding_s == NULL) {
return NULL;
}
g_hash_table_foreach(descriptor,
(GHFunc)add_assertion_consumer_url_to_list,
&r);
name = g_strdup_printf("sp "
LASSO_SAML2_METADATA_ELEMENT_ASSERTION_CONSUMER_SERVICE
" %s ", binding_s);
lname = strlen(name);
for (l = r; l; l = g_list_next(l)) {
char *b = l->data;
if (strncmp(name, b, lname) == 0) {
l = g_hash_table_lookup(descriptor, b);
break;
}
}
lasso_release_string(name);
lasso_release_list(r);
if (l) {
return g_strdup(l->data);
}
return NULL;
}
gchar*
lasso_saml20_provider_get_assertion_consumer_service_binding(LassoProvider *provider,
int service_id)
{
GHashTable *descriptor;
GList *l = NULL;
char *sid;
char *name;
char *binding = NULL;
const char *possible_bindings[] = {
"HTTP-POST",
"HTTP-Redirect",
"HTTP-Artifact",
"SOAP",
NULL
};
int i;
if (service_id == -1) {
sid = g_strdup(provider->private_data->default_assertion_consumer);
} else {
sid = g_strdup_printf("%d", service_id);
}
descriptor = provider->private_data->Descriptors;
if (descriptor == NULL)
return NULL;
for (i=0; possible_bindings[i]; i++) {
name = g_strdup_printf(LASSO_SAML2_METADATA_ELEMENT_ASSERTION_CONSUMER_SERVICE
" %s %s",
possible_bindings[i], sid);
l = lasso_provider_get_metadata_list_for_role(provider, LASSO_PROVIDER_ROLE_SP, name);
lasso_release_string(name);
if (l != NULL) {
binding = g_strdup(possible_bindings[i]);
break;
}
}
lasso_release_string(sid);
return binding;
}
gboolean
lasso_saml20_provider_accept_http_method(LassoProvider *provider, LassoProvider *remote_provider,
LassoMdProtocolType protocol_type, LassoHttpMethod http_method,
gboolean initiate_profile)
{
char *protocol_profile;
static const char *http_methods[] = {
NULL,
NULL,
NULL,
NULL,
"HTTP-Post",
"HTTP-Redirect",
"SOAP",
"HTTP-Artifact",
"HTTP-Artifact",
NULL
};
gboolean rc = FALSE;
LassoProviderRole initiating_role;
initiating_role = remote_provider->role;
if (remote_provider->role == LASSO_PROVIDER_ROLE_SP) {
provider->role = LASSO_PROVIDER_ROLE_IDP;
}
if (remote_provider->role == LASSO_PROVIDER_ROLE_IDP) {
provider->role = LASSO_PROVIDER_ROLE_SP;
}
if (initiate_profile)
initiating_role = provider->role;
/* exclude bad input */
if (http_method > (int)G_N_ELEMENTS(http_methods) || http_method < 0 || http_methods[http_method+1] == NULL) {
return FALSE;
}
protocol_profile = g_strdup_printf("%s %s", profile_names[protocol_type],
http_methods[http_method+1]);
/* special hack for single sign on */
if (protocol_type == LASSO_MD_PROTOCOL_TYPE_SINGLE_SIGN_ON) {
/* no need to check for the response, it uses another canal
* (AssertionConsumerService) */
rc = (lasso_provider_get_metadata_list(remote_provider, protocol_profile) != NULL);
} else {
if (lasso_provider_get_metadata_list(provider, protocol_profile) &&
lasso_provider_get_metadata_list(remote_provider, protocol_profile)) {
rc = TRUE;
}
}
lasso_release_string(protocol_profile);
return rc;
}
/**
* lasso_provider_saml2_node_encrypt:
* @provider: a #LassoProvider object
* @lasso_node: a #LassoNode object
*
* Dump the node object to an XML fragment, then encrypt this fragment using encryption key of
* @provider, then encapsulate the resulting encrypted content into a #LassoSaml2EncryptedElement.
*
* Return value: a newly created #LassoSaml2EncryptedElement if successfull, NULL otherwise.
*/
LassoSaml2EncryptedElement*
lasso_provider_saml2_node_encrypt(const LassoProvider *provider, LassoNode *lasso_node)
{
LassoSaml2EncryptedElement *saml2_encrypted_element;
g_return_val_if_fail(LASSO_IS_PROVIDER (provider), NULL);
g_return_val_if_fail(LASSO_IS_NODE (lasso_node), NULL);
saml2_encrypted_element = lasso_node_encrypt(lasso_node,
lasso_provider_get_encryption_public_key(provider),
lasso_provider_get_encryption_sym_key_type(provider),
provider->ProviderID);
return saml2_encrypted_element;
}