From 65bc7052357f4f45dfb438cb66807d3392b364f9 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Mon, 31 Mar 2014 10:54:27 +0200 Subject: [PATCH] profile: add two new class methods, lasso_profile_get_issuer and lasso_profile_get_in_response_to (#4378) The goal of those two methods is to allow IdP and SP to load metadata dynamically without processing completely the incoming. Currently it's impossible as message parsing and signature checking is done in the same function. --- lasso/id-ff/profile.c | 115 ++++++++++++++++++++++++++++++++ lasso/id-ff/profile.h | 2 + lasso/xml/private.h | 2 + lasso/xml/tools.c | 151 +++++++++++++++++++++++++++++++++--------- lasso/xml/xml.h | 1 + tests/basic_tests.c | 15 +++++ tests/data/response-1 | 4 +- 7 files changed, 255 insertions(+), 35 deletions(-) diff --git a/lasso/id-ff/profile.c b/lasso/id-ff/profile.c index 9518b9a5..f2614681 100644 --- a/lasso/id-ff/profile.c +++ b/lasso/id-ff/profile.c @@ -54,6 +54,8 @@ #endif #include "../lasso_config.h" +#include + /*****************************************************************************/ /* public functions */ /*****************************************************************************/ @@ -734,6 +736,119 @@ lasso_profile_get_signature_status(LassoProfile *profile) return profile->signature_status; } +static xmlChar * +extract_issuer(xmlTextReader *reader) +{ + const xmlChar *name; + const xmlChar *ns_uri; + xmlNode *node; + + name = xmlTextReaderConstLocalName(reader); + ns_uri = xmlTextReaderConstNamespaceUri(reader); + + if (strcmp((const char*)name, "Issuer")) + return NULL; + if (strcmp((const char*)ns_uri, LASSO_SAML2_ASSERTION_HREF)) + return NULL; + node = xmlTextReaderExpand(reader); + return xmlNodeGetContent(node); +} + + +/** + * lasso_profile_get_issuer: + * @message: the HTTP query, POST content or SOAP message + * + * Extract the issuer of a message. + * + * Return value:(transfer full): Returns the issuer of the given message. + */ +char* +lasso_profile_get_issuer(const char *message) +{ + xmlTextReader *reader; + char *result = NULL; + int count = 0, ret; + xmlChar *xml_result = NULL; + xmlChar *to_free = NULL; + + + reader = lasso_xmltextreader_from_message(message, &to_free); + if (! reader) + goto cleanup; + ret = xmlTextReaderRead(reader); + while (ret == 1) { + int node_type = xmlTextReaderNodeType(reader); + if (node_type == 1) { + count += 1; + xml_result = extract_issuer(reader); + if (xml_result) + break; + } + if (count == 3) { + break; + } + ret = xmlTextReaderRead(reader); + } + if (! xml_result) + goto cleanup; + result = g_strdup((char *)xml_result); +cleanup: + if (xml_result) + lasso_release_xml_string(xml_result); + if (reader) + xmlFreeTextReader(reader); + if (to_free) + lasso_release_xml_string(to_free); + return result; +} + +/** + * lasso_profile_get_request_id: + * @message: the HTTP query, POST content or SOAP message + * + * Extract the issuer of a message. + * + * Return value:(transfer full): Returns the issuer of the given message. + */ +char* +lasso_profile_get_in_response_to(const char *message) +{ + xmlTextReader *reader; + char *result = NULL; + int ret; + int node_type = 0; + xmlChar *xml_result = NULL; + xmlChar *to_free = NULL; + + + reader = lasso_xmltextreader_from_message(message, &to_free); + if (! reader) + goto cleanup; + ret = xmlTextReaderRead(reader); + while (ret == 1) { + node_type = xmlTextReaderNodeType(reader); + if (node_type == 1) { + break; + } + ret = xmlTextReaderRead(reader); + } + if (node_type != 1) + goto cleanup; + xml_result = xmlTextReaderGetAttribute(reader, BAD_CAST "InResponseTo"); + if (! xml_result) + goto cleanup; + result = g_strdup((char*)xml_result); +cleanup: + if (reader) + xmlFreeTextReader(reader); + if (xml_result) + lasso_release_xml_string(xml_result); + if (to_free) + lasso_release_xml_string(to_free); + return result; +} + /*****************************************************************************/ /* overridden parent class methods */ /*****************************************************************************/ diff --git a/lasso/id-ff/profile.h b/lasso/id-ff/profile.h index 7fb3e729..14a37fb8 100644 --- a/lasso/id-ff/profile.h +++ b/lasso/id-ff/profile.h @@ -211,6 +211,8 @@ LASSO_EXPORT LassoProfileSignatureVerifyHint lasso_profile_get_signature_verify_ LASSO_EXPORT LassoProviderRole lasso_profile_sso_role_with(LassoProfile *profile, const char *remote_provider_id); LASSO_EXPORT lasso_error_t lasso_profile_get_signature_status(LassoProfile *profile); +LASSO_EXPORT char* lasso_profile_get_issuer(const char *message); +LASSO_EXPORT char* lasso_profile_get_in_response_to(const char *message); #ifdef __cplusplus } diff --git a/lasso/xml/private.h b/lasso/xml/private.h index 1d3042ae..6f7d911d 100644 --- a/lasso/xml/private.h +++ b/lasso/xml/private.h @@ -335,6 +335,8 @@ void lasso_xmlnode_add_saml2_signature_template(xmlNode *node, LassoSignatureCon gchar* lasso_xmlnode_build_deflated_query(xmlNode *xmlnode); +xmlTextReader *lasso_xmltextreader_from_message(const char *message, xmlChar **to_free); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/lasso/xml/tools.c b/lasso/xml/tools.c index 337b2c6a..bb434a56 100644 --- a/lasso/xml/tools.c +++ b/lasso/xml/tools.c @@ -1310,13 +1310,51 @@ lasso_get_query_string_param_value(const char *qs, const char *param_key, const } } +unsigned char* +lasso_inflate(unsigned char *input, size_t len) +{ + z_stream zstr; + unsigned char *output; + int z_err; + + zstr.zalloc = NULL; + zstr.zfree = NULL; + zstr.opaque = NULL; + + output = g_malloc(len*10); + zstr.avail_in = len; + zstr.next_in = (unsigned char*)input; + zstr.total_in = 0; + zstr.avail_out = len*10; + zstr.total_out = 0; + zstr.next_out = output; + + z_err = inflateInit2(&zstr, -MAX_WBITS); + if (z_err != Z_OK) { + message(G_LOG_LEVEL_CRITICAL, "Failed to inflateInit"); + lasso_release_string(output); + return FALSE; + } + + z_err = inflate(&zstr, Z_FINISH); + if (z_err != Z_STREAM_END) { + message(G_LOG_LEVEL_CRITICAL, "Failed to inflate"); + inflateEnd(&zstr); + lasso_release_string(output); + return NULL; + } + output[zstr.total_out] = 0; + inflateEnd(&zstr); + + return output; +} + + gboolean lasso_node_init_from_deflated_query_part(LassoNode *node, char *deflate_string) { int len; xmlChar *b64_zre, *zre, *re; - z_stream zstr; - int z_err; xmlDoc *doc; xmlNode *root; @@ -1326,40 +1364,15 @@ lasso_node_init_from_deflated_query_part(LassoNode *node, char *deflate_string) len = xmlSecBase64Decode(b64_zre, zre, len*4); xmlFree(b64_zre); - zstr.zalloc = NULL; - zstr.zfree = NULL; - zstr.opaque = NULL; - - zstr.avail_in = len; - re = xmlMalloc(len*10); - zstr.next_in = (xmlChar*)zre; - zstr.total_in = 0; - zstr.avail_out = len*10; - zstr.total_out = 0; - zstr.next_out = re; - - z_err = inflateInit2(&zstr, -MAX_WBITS); - if (z_err != Z_OK) { - message(G_LOG_LEVEL_CRITICAL, "Failed to inflateInit"); - xmlFree(zre); - xmlFree(re); - return FALSE; - } - - z_err = inflate(&zstr, Z_FINISH); - if (z_err != Z_STREAM_END) { - message(G_LOG_LEVEL_CRITICAL, "Failed to inflate"); - inflateEnd(&zstr); - xmlFree(zre); - xmlFree(re); - return FALSE; - } - re[zstr.total_out] = 0; - inflateEnd(&zstr); + re = lasso_inflate(zre, len); xmlFree(zre); + if (! re) + return FALSE; + doc = lasso_xml_parse_memory((char*)re, strlen((char*)re)); - xmlFree(re); + lasso_release_string(re); + root = xmlDocGetRootElement(doc); lasso_node_init_from_xml(node, root); lasso_release_doc(doc); @@ -2826,3 +2839,75 @@ lasso_xmlnode_add_saml2_signature_template(xmlNode *node, LassoSignatureContext g_assert_not_reached(); } } + +static char* +get_saml_message(char **query_fields) { + int i; + char *field, *t; + + for (i=0; (field=query_fields[i]); i++) { + t = strchr(field, '='); + if (t == NULL) + continue; + *t = 0; + if (strcmp(field, LASSO_SAML2_FIELD_ENCODING) == 0) { + return t+1; + } + if (strcmp(field, LASSO_SAML2_FIELD_REQUEST) == 0 || strcmp(field, LASSO_SAML2_FIELD_RESPONSE) == 0) { + return t+1; + } + } + return NULL; +} + +/** + * lasso_xmltextreader_from_message: + * @message: the HTTP query, POST content or SOAP message + * + * Try to parse the passed message and create an xmlTextReader from it. + */ +xmlTextReader * +lasso_xmltextreader_from_message(const char *message, xmlChar **to_free) { + size_t len = strlen(message); + char *needle; + char **query_fields = NULL; + char *decoded_message = NULL; + xmlTextReader *reader = NULL; + + if (message[0] != '<') { + needle = strchr(message, '='); + if (needle) { + ptrdiff_t needle_pos = (needle-message); + if (len - needle_pos > 2) { // query + query_fields = urlencoded_to_strings(needle+1); + message = get_saml_message(query_fields); + if (! message) + goto cleanup; + len = strlen(message); + } + } + if (is_base64(message)) { + int rc; + decoded_message = g_malloc(len); + rc = xmlSecBase64Decode((xmlChar*)message, (xmlChar*)decoded_message, len); + if (rc < 0) + goto cleanup; + len = rc; + message = (char*)lasso_inflate((unsigned char*) decoded_message, rc); + lasso_release_string(decoded_message); + if (! message) + goto cleanup; + } + } + + if (message[0] == '<') // XML case + reader = xmlReaderForMemory(message, len, "", NULL, XML_PARSE_NOENT | + XML_PARSE_NONET); + +cleanup: + if (query_fields) + lasso_release(query_fields); + if (decoded_message && to_free) + *to_free = BAD_CAST decoded_message; + return reader; +} diff --git a/lasso/xml/xml.h b/lasso/xml/xml.h index 30f80141..c4e42795 100644 --- a/lasso/xml/xml.h +++ b/lasso/xml/xml.h @@ -35,6 +35,7 @@ extern "C" { #include #include +#include #include "../export.h" #include "../errors.h" diff --git a/tests/basic_tests.c b/tests/basic_tests.c index 5ccdb413..5ac07de3 100644 --- a/tests/basic_tests.c +++ b/tests/basic_tests.c @@ -2082,6 +2082,18 @@ START_TEST(test15_ds_key_info) } END_TEST +/* test load federation */ +START_TEST(test16_test_get_issuer) +{ + char *content = NULL; + size_t len = 0; + + g_file_get_contents(TESTSDATADIR "/response-1", &content, &len, NULL); + check_str_equals(lasso_profile_get_issuer(content), "gefssstg"); + check_str_equals(lasso_profile_get_in_response_to(content), "xx"); +} +END_TEST + Suite* basic_suite() { @@ -2101,6 +2113,7 @@ basic_suite() TCase *tc_load_metadata = tcase_create("Test loading a federation metadata file"); TCase *tc_key = tcase_create("Test loading and manipulating LassoKey objects"); TCase *tc_key_info = tcase_create("Test creating and dumping ds:KeyInfo nodes"); + TCase *tc_get_issuer = tcase_create("Test get_issuer and get_request_id"); suite_add_tcase(s, tc_server_load_dump_empty_string); suite_add_tcase(s, tc_server_load_dump_random_string); @@ -2117,6 +2130,7 @@ basic_suite() suite_add_tcase(s, tc_load_metadata); suite_add_tcase(s, tc_key); suite_add_tcase(s, tc_key_info); + suite_add_tcase(s, tc_get_issuer); tcase_add_test(tc_server_load_dump_empty_string, test01_server_load_dump_empty_string); tcase_add_test(tc_server_load_dump_random_string, test02_server_load_dump_random_string); @@ -2133,6 +2147,7 @@ basic_suite() tcase_add_test(tc_load_metadata, test13_test_lasso_server_load_metadata); tcase_add_test(tc_key, test14_lasso_key); tcase_add_test(tc_key_info, test15_ds_key_info); + tcase_add_test(tc_get_issuer, test16_test_get_issuer); tcase_set_timeout(tc_load_metadata, 10); return s; } diff --git a/tests/data/response-1 b/tests/data/response-1 index c4d24214..5732e092 100644 --- a/tests/data/response-1 +++ b/tests/data/response-1 @@ -1,4 +1,4 @@ -gefssstggefssstg +gefssstggefssstg @@ -54,4 +54,4 @@ oKYPjJxpj5sK9iB5yW8= -999999500Webrooturn:oasis:names:tc:SAML:2.0:ac:classes:unspecifiedUser999999500Test999999500@apctest.ge.com \ No newline at end of file +999999500Webrooturn:oasis:names:tc:SAML:2.0:ac:classes:unspecifiedUser999999500Test999999500@apctest.ge.com