From: Oliver Kurth Date: Tue, 19 Feb 2019 20:51:31 +0000 (-0800) Subject: Back out the previous change to remove support for building with X-Git-Tag: stable-11.0.0~218 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=cf60bf56b021452e7a92c42a767156b2a64e1ac0;p=thirdparty%2Fopen-vm-tools.git Back out the previous change to remove support for building with xml-security-c and xerces-c. --- diff --git a/open-vm-tools/configure.ac b/open-vm-tools/configure.ac index 2c1409b2b..aa1c650c6 100644 --- a/open-vm-tools/configure.ac +++ b/open-vm-tools/configure.ac @@ -501,38 +501,74 @@ fi AC_ARG_ENABLE([vgauth], [AS_HELP_STRING([--disable-vgauth], - [do not build vgauth])], + [do not build vgauth.])], [ enable_vgauth="$enableval" + use_xmlsec1=yes ], [ if test "$os" = "linux"; then enable_vgauth=yes + use_xmlsec1=yes else enable_vgauth=no + use_xmlsec1=yes fi ]) +AC_ARG_ENABLE([xmlsec1], + [AS_HELP_STRING([--enable-xmlsec1], + [build vgauth with xmlsec1 instead of xml-security-c (on by default).])], + [use_xmlsec1="$enableval"], + [use_xmlsec1=yes]) + +AC_ARG_ENABLE([xmlsecurity], + [AS_HELP_STRING([--enable-xmlsecurity], + [build vgauth with xml-security-c instead of xmlsec1 (off by default).])], + [ + if test "$enableval" = "yes"; then + use_xmlsec1="no" + else + use_xmlsec1="yes" + fi + ], + []) + + # -# Check for openssl, xmlsec1, xml2 +# Check for openssl, xerces-c and xml-security-c # AC_ARG_WITH([ssl], [AS_HELP_STRING([--without-ssl], - [compiles without openssl (disables vgauth)])], - [if test "$withval" = "no"; then enable_vgauth="no"; fi], - [with_ssl="yes"]) + [compiles without openssl support (disables vgauth).])], + [ + enable_vgauth=no + ], + [with_ssl=yes]) + +AC_ARG_WITH([xmlsecurity], + [AS_HELP_STRING([--without-xmlsecurity], + [compiles without xml-security-c support (disables vgauth).])], + [enable_vgauth=no], + [with_xmlsecurity=yes]) + +AC_ARG_WITH([xerces], + [AS_HELP_STRING([--without-xerces], + [compiles without xerces support (disables vgauth).])], + [enable_vgauth=no], + [with_xerces=yes]) AC_ARG_WITH([xmlsec1], [AS_HELP_STRING([--without-xmlsec1], - [compiles without xmlsec1 (disables vgauth)])], - [if test "$withval" = "no"; then enable_vgauth="no"; fi], - [with_xmlsec1="yes"]) + [compiles without xmlsec1 support (disables vgauth).])], + [enable_vgauth=no], + [with_xmlsec1=yes]) AC_ARG_WITH([xml2], [AS_HELP_STRING([--without-xml2], - [compiles without xml2 (disables vgauth)])], - [if test "$withval" = "no"; then enable_vgauth="no"; fi], - [with_xml2="yes"]) + [compiles without xml2 support (disables vgauth).])], + [enable_vgauth=no], + [with_xml2=yes]) AC_ARG_WITH([tirpc], [AS_HELP_STRING([--without-tirpc], @@ -543,7 +579,7 @@ AC_ARG_WITH([tirpc], # Make sure we are building with openssl 1.0.1 and above so that # we use only TLSv1_2. -if test "$enable_vgauth" = "yes"; then +if test "$enable_vgauth" = "yes" ; then AC_VMW_DEFAULT_FLAGS([SSL]) AC_VMW_CHECK_LIB([ssl], [SSL], @@ -554,32 +590,59 @@ if test "$enable_vgauth" = "yes"; then [BIO_new_file], [], [AC_VMW_LIB_ERROR([SSL], [ssl])]) +fi +if test "$enable_vgauth" = "yes"; then CPPFLAGS="$CPPFLAGS -DUSE_VGAUTH" - AC_VMW_DEFAULT_FLAGS([XML2]) - AC_VMW_CHECK_LIB([xml2], - [XML2], - [], - [], - [], - [], - [], - [], - [AC_VMW_LIB_ERROR([XML2], [xml2])]) + if test "$use_xmlsec1" = "yes"; then + AC_VMW_DEFAULT_FLAGS([XML2]) + AC_VMW_CHECK_LIB([xml2], + [XML2], + [], + [], + [], + [], + [], + [], + [AC_VMW_LIB_ERROR([XML2], [xml2])]) # Multiple distros built xmlsec1 with -DXMLSEC_NO_SIZE_T but # their xmlssec1-config --cflags doesn't properly report it, # so force it on. - AC_VMW_DEFAULT_FLAGS([XMLSEC1]) - AC_VMW_CHECK_LIB([xmlsec1], - [XMLSEC1], - [], - [xmlsec1-config], - [], - [xmlsec/xmlsec.h], - [xmlSecCheckVersionExt], - [XMLSEC1_CPPFLAGS="$XMLSEC1_CPPFLAGS -DXMLSEC_NO_SIZE_T"], - [AC_VMW_LIB_ERROR([XMLSEC1], [xmlsec1])]) + AC_VMW_DEFAULT_FLAGS([XMLSEC1]) + AC_VMW_CHECK_LIB([xmlsec1], + [XMLSEC1], + [], + [xmlsec1-config], + [], + [xmlsec/xmlsec.h], + [xmlSecCheckVersionExt], + [XMLSEC1_CPPFLAGS="$XMLSEC1_CPPFLAGS -DXMLSEC_NO_SIZE_T"], + [AC_VMW_LIB_ERROR([XMLSEC1], [xmlsec1])]) + + else + AC_VMW_DEFAULT_FLAGS([XERCES]) + AC_VMW_CHECK_LIB([xerces-c], + [XERCES], + [], + [], + [], + [], + [], + [], + [AC_VMW_LIB_ERROR([XERCES], [xerces])]) + + AC_VMW_DEFAULT_FLAGS([XMLSECURITY]) + AC_VMW_CHECK_LIB([xml-security-c], + [XMLSECURITY], + [], + [], + [], + [], + [], + [], + [AC_VMW_LIB_ERROR([XMLSECURITY], [xmlsecurity])]) + fi fi # @@ -1356,11 +1419,12 @@ AM_CONDITIONAL(HAVE_PAM, test "$with_pam" = "yes") AM_CONDITIONAL(USE_SLASH_PROC, test "$os" = "linux") AM_CONDITIONAL(ENABLE_DEPLOYPKG, test "$enable_deploypkg" = "yes") AM_CONDITIONAL(ENABLE_VGAUTH, test "$enable_vgauth" = "yes") +AM_CONDITIONAL(USE_XMLSEC1, test "$use_xmlsec1" = "yes") AM_CONDITIONAL(HAVE_VSOCK, test "$os" = "linux") AM_CONDITIONAL(HAVE_MKDTEMP, test "$have_mkdtemp" = "yes") AM_CONDITIONAL(HAVE_UDEV, test "$have_udev" = "yes") AM_CONDITIONAL(ENABLE_RESOLUTIONKMS, test "x$enable_resolutionkms" = "xyes") -AM_CONDITIONAL(VGAUTH_USE_CXX, test "$with_icu" = "yes") +AM_CONDITIONAL(VGAUTH_USE_CXX, test "$with_icu" = "yes" -o "$use_xmlsec1" != "yes") AM_CONDITIONAL(ENABLE_LIBAPPMONITOR, test "$enable_libappmonitor" = "yes") if test "$have_xsm" != "yes"; then diff --git a/open-vm-tools/vgauth/service/Makefile.am b/open-vm-tools/vgauth/service/Makefile.am index 8e21c4725..c1cee1e66 100644 --- a/open-vm-tools/vgauth/service/Makefile.am +++ b/open-vm-tools/vgauth/service/Makefile.am @@ -1,5 +1,5 @@ ################################################################################ -### Copyright (C) 2014-2019 VMware, Inc. All rights reserved. +### Copyright (C) 2014-2018 VMware, Inc. All rights reserved. ### ### This program is free software; you can redistribute it and/or modify ### it under the terms of version 2 of the GNU General Public License as @@ -30,7 +30,11 @@ VGAuthService_SOURCES += ../serviceImpl/filePosix.c VGAuthService_SOURCES += ../serviceImpl/netPosix.c VGAuthService_SOURCES += ../serviceImpl/proto.c VGAuthService_SOURCES += ../serviceImpl/random.c +if USE_XMLSEC1 VGAuthService_SOURCES += ../serviceImpl/saml-xmlsec1.c +else +VGAuthService_SOURCES += ../serviceImpl/saml-xml-security-c.cpp +endif VGAuthService_SOURCES += ../serviceImpl/service.c VGAuthService_SOURCES += ../serviceImpl/ticket.c VGAuthService_SOURCES += ../serviceImpl/verify.c @@ -59,7 +63,12 @@ VGAuthService_SCRIPTS += ../serviceImpl/schemas/catalog.xml VGAuthService_CPPFLAGS = VGAuthService_CPPFLAGS += @GLIB2_CPPFLAGS@ +if USE_XMLSEC1 VGAuthService_CPPFLAGS += @XMLSEC1_CPPFLAGS@ +else +VGAuthService_CPPFLAGS += @XERCES_CPPFLAGS@ +VGAuthService_CPPFLAGS += @XMLSECURITY_CPPFLAGS@ +endif VGAuthService_CPPFLAGS += @SSL_CPPFLAGS@ VGAuthService_CPPFLAGS += -I$(top_srcdir)/vgauth/public VGAuthService_CPPFLAGS += -I$(top_srcdir)/vgauth/common @@ -68,7 +77,14 @@ VGAuthService_CPPFLAGS += -I$(top_srcdir)/vgauth/serviceImpl VGAuthService_LDADD = VGAuthService_LDADD += @GLIB2_LIBS@ VGAuthService_LDADD += @GTHREAD_LIBS@ +if USE_XMLSEC1 VGAuthService_LDADD += @XMLSEC1_LIBS@ +else +VGAuthService_LDADD += @XERCES_LIBS@ +VGAuthService_LDADD += @XMLSECURITY_LIBS@ +VGAuthService_LDADD += -lxerces-c +VGAuthService_LDADD += -lxml-security-c +endif VGAuthService_LDADD += @SSL_LIBS@ VGAuthService_LDADD += -lssl VGAuthService_LDADD += -lcrypto diff --git a/open-vm-tools/vgauth/serviceImpl/saml-xml-security-c.cpp b/open-vm-tools/vgauth/serviceImpl/saml-xml-security-c.cpp new file mode 100644 index 000000000..a70f60297 --- /dev/null +++ b/open-vm-tools/vgauth/serviceImpl/saml-xml-security-c.cpp @@ -0,0 +1,1263 @@ +/********************************************************* + * Copyright (C) 2011-2017 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation version 2.1 and no 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 Lesser GNU General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + *********************************************************/ + +/** + * @file saml-xml-security-c.cpp + * + * Code for authenticating users based on SAML tokens. + */ + +#include +#include +#include +#include + +#undef WIN32_LEAN_AND_MEAN // XSEC unconditionally redefines this +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * XXX + * + * Optimization idea: stash a hash (SHA512) of a valid token, and bypass + * the full assertion process when we see that token again. The expiration + * date of the token must also be saved off (and beware the time skew issue). + * + * Note that there's some extra complexity here: + * + * 1 - AddAlias sets up a cert/user mapping + * 2 - a SAML token is used (and cached) using this cert/user combo + * 3 - RemoveAlias removes the combo + * 4 - the cached token still works + * + * So the cache should only bypass the token validation, not the certificate + * check in ServiceVerifyAndCheckTrustCertChainForSubject() + * + * Also TBD is how much this buys us in the real world. With short + * token lifetimes, its less interesting. Its also possible that + * it will have no measureable affect because the token verification + * will be lost in the noise of the API plumbing from VC->hostd->VMX->tools. + * + * The security folks have signed off on this, so long as we store only + * in memory. + * + */ + +/* + * XXX + * + * We should be a lot smarter about this, but this gets QE + * moving. + */ +#define SAML_TOKEN_PREFIX "saml:" +#define SAML_TOKEN_SSO_PREFIX "saml2:" + +extern "C" { +#include "prefs.h" +#include "serviceInt.h" +} +#include "samlInt.hpp" + + +/** + * Error handler used to log warnings from the XML parser. + */ + +class SAMLErrorHandler : public ErrorHandler { +public: + static void + printWarning(const SAXParseException &e, + const char *msg) + { + SAMLStringWrapper nativeMsg(e.getMessage()); + + /* + * XXX + * + * These functions were inlined on older compilers but are exported + * from libstdc++.so on newer compilers (4.4.3). Avoid using them to + * avoid the newer dependency. + * + * _ZNSo9_M_insertIyEERSoT_@@GLIBCXX_3.4.9 + * std::basic_ostream >& + * std::basic_ostream >:: + * _M_insert(unsigned long long) + * aka: operator<<(uint64_t) + * + * _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_i@@GLIBCXX_3.4.9 + * std::basic_ostream >& + * std::__ostream_insert > + * (std::basic_ostream >&, + * char const*, int) + * aka: operator<<(std::string) + * + */ + Debug("SAML: %s: %s (line=%d, col=%d)\n", + msg, nativeMsg.c_str(), + (int) e.getLineNumber(), (int) e.getColumnNumber()); + +#ifdef avoid_this_usage + /* + * I'm tired of defining format modifier macros, so let's use + * stringstream to handle e.getLineNumber()'s return type. + */ + + std::stringstream ss; + + ss << msg << ": " << nativeMsg.c_str() << "" << " (line=" << + e.getLineNumber() << ", col=" << e.getColumnNumber() << ")"; + + Debug("SAML: %s.\n", ss.str().c_str()); +#endif + } + + void + warning(const SAXParseException &e) + { + printWarning(e, "warning"); + } + + void + error(const SAXParseException &e) + { + printWarning(e, "error"); + } + + void + fatalError (const SAXParseException &e) + { + printWarning(e, "fatal error"); + } + + void + resetErrors() + { + } +}; + + + +/** + * The XML schema files needed to perform validating parsing of the + * SAML assertions. Note: the order is important, since schemas need + * to be loaded before any schema that depends on them, so don't change + * the order. + */ +static const char *schemas[] = { + "xml.xsd", + "XMLSchema.xsd", + "xmldsig-core-schema.xsd", + "xenc-schema.xsd", + "saml-schema-assertion-2.0.xsd", +}; + + +/** + * An in-memory cache for XML schemas. + */ +static XMLGrammarPool *pool = NULL; + +static int clockSkewAdjustment = VGAUTH_PREF_DEFAULT_CLOCK_SKEW_SECS; + +static bool SAMLLoadSchema(XercesDOMParser &parser, + const SAMLGlibString &schemaDir, + const char *filename); +static DOMDocument *SAMLValidateSchemaAndParse(XercesDOMParser &parser, + const char *xmlText); + +static bool SAMLCheckSubject(const DOMDocument *doc, + SAMLTokenData &token); + +static bool SAMLCheckConditions(const DOMDocument *doc, + SAMLTokenData &token); + +static bool SAMLCheckTimeAttr(const DOMElement *elem, const char *attrName, + bool beforeNow); + +static bool SAMLCheckAudience(const XMLCh *audience); + +static bool SAMLCheckSignature(DOMDocument *doc, + vector &certs); + +static bool SAMLCheckReference(const DOMDocument *doc, DSIGSignature *sig); + +static DOMElement *SAMLFindChildByName(const DOMElement *elem, + const char *name); + +static auto_ptr SAMLFindKey(const XSECEnv &secEnv, + const DOMElement *sigElem); + + +/* + ****************************************************************************** + * SAML_Init -- */ /** + * + * Performs any initialization needed for SAML processing. + * + * @return VGAUTH_E_OK on success, VGAuthError on failure + * + ****************************************************************************** + */ + +VGAuthError +SAML_Init() +{ + try { + XMLPlatformUtils::Initialize(); + XSECPlatformUtils::Initialise(); + + auto_ptr myPool = SAMLCreateAndPopulateGrammarPool(); + if (NULL == myPool.get()) { + return VGAUTH_E_FAIL; + } + + pool = myPool.release(); + + clockSkewAdjustment = Pref_GetInt(gPrefs, VGAUTH_PREF_CLOCK_SKEW_SECS, + VGAUTH_PREF_GROUP_NAME_SERVICE, + VGAUTH_PREF_DEFAULT_CLOCK_SKEW_SECS); + Log("%s: Allowing %d of clock skew for SAML date validation\n", + __FUNCTION__, clockSkewAdjustment); + + return VGAUTH_E_OK; + } catch (const XMLException& e) { + SAMLStringWrapper msg(e.getMessage()); + + Warning("Failed to initialize Xerces: %s.\n", msg.c_str()); + return VGAUTH_E_FAIL; + } catch (...) { + // We're called from C code, so don't let any exceptions out. + Warning("%s: Unexpected exception.\n", __FUNCTION__); + return VGAUTH_E_FAIL; + } +} + + +/* + ****************************************************************************** + * SAMLCreateAndPopulateGrammarPool -- */ /** + * + * Creates a grammar pool that is populates with cached grammars representing + * the XML schemas needed for SAML validation. + * + * @return A heap allocated grammar pool (must be freed with operator + * delete) or NULL on failure. + * + ****************************************************************************** + */ + +auto_ptr +SAMLCreateAndPopulateGrammarPool() +{ + auto_ptr newPool(new XMLGrammarPoolImpl(XMLPlatformUtils::fgMemoryManager)); + + /* + * Create a parser instance to load all the schemas, so they can + * be cached for later. In addition to making parsing faster, we + * need to cache them so that Xerces does not try to download + * schemas from the web when one is referenced or imported by another + * schema. + */ + XercesDOMParser parser(NULL, XMLPlatformUtils::fgMemoryManager, + newPool.get()); + + gchar *dir = Pref_GetString(gPrefs, VGAUTH_PREF_SAML_SCHEMA_DIR, + VGAUTH_PREF_GROUP_NAME_SERVICE, NULL); + if (NULL == dir) { +#ifdef _WIN32 + /* + * To make life easier for the Windows installer, assume + * the schema directory is next to the executable. Also + * check in ../ in case we're in a dev environment. + */ + dir = g_build_filename(gInstallDir, "schemas", NULL); + if (!(g_file_test(dir, G_FILE_TEST_EXISTS) && + g_file_test(dir, G_FILE_TEST_IS_DIR))) { + + gchar *newDir = g_build_filename(gInstallDir, "..", "schemas", NULL); + + Debug("%s: schemas not found in Windows install loc '%s'," + " trying dev location of '%s'\n", __FUNCTION__, dir, newDir); + + g_free(dir); + dir = newDir; + } +#else + /* + * XXX -- clean this up to make a better default for Linux. + */ + dir = g_build_filename(gInstallDir, "..", "schemas", NULL); +#endif + } + Log("%s: Using '%s' for SAML schemas\n", __FUNCTION__, dir); + SAMLGlibString schemaDir(dir); + + for (unsigned int i = 0; i < G_N_ELEMENTS(schemas); i++) { + if (!SAMLLoadSchema(parser, schemaDir, schemas[i])) { + return auto_ptr(NULL); + } + } + + return newPool; +} + + +/* + ****************************************************************************** + * SAML_Shutdown -- */ /** + * + * Performs any clean-up of resources needed for SAML processing. + * + ****************************************************************************** + */ + +void +SAML_Shutdown() +{ + try { + delete pool; + pool = NULL; + XSECPlatformUtils::Terminate(); + XMLPlatformUtils::Terminate(); + } catch (...) { + // We're called from C code, so don't let any exceptions out. + Warning("%s: Unexpected exception.\n", __FUNCTION__); + } +} + + +/* + ****************************************************************************** + * SAML_Reload -- */ /** + * + * Reload any in-memory state used by the SAML module. + * + ****************************************************************************** + */ + +void +SAML_Reload() +{ + ASSERT(pool != NULL); + + auto_ptr myPool = SAMLCreateAndPopulateGrammarPool(); + if (NULL == myPool.get()) { + Warning("%s: Failed to reload SAML state. Using old settings.\n", + __FUNCTION__); + return; + } + + delete pool; + pool = myPool.release(); +} + + +/* + ****************************************************************************** + * SAMLLoadSchema -- */ /** + * + * Loads a schema into the grammar pool used by the given parser. + * + * @param[in] parser The parser to load the schema with. + * @param[in] schemaDir The full path to the directory containing the schema. + * @param[in] filename The name of the XML schema file. + * + * @return true if the schema file was successfully loaded, false otherwise. + * + ****************************************************************************** + */ + +static bool +SAMLLoadSchema(XercesDOMParser &parser, + const SAMLGlibString &schemaDir, + const char *filename) +{ + SAMLGlibString schemaPath(g_build_filename(schemaDir.c_str(), filename, + NULL)); + Grammar *g = parser.loadGrammar(schemaPath.c_str(), + Grammar::SchemaGrammarType, true); + if (g == NULL) { + /* + * The parser complains even with official schemas, so we don't + * normally set an error handler. However, this should not fail since + * we control these files, so try again with logging, so we can see + * what went wrong. + */ + SAMLErrorHandler errorHandler; + parser.setErrorHandler(&errorHandler); + + g = parser.loadGrammar(schemaPath.c_str(), Grammar::SchemaGrammarType, + true); + + Warning("Failed to load XML Schema from %s.\n", schemaPath.c_str()); + return false; + } + + return true; +} + + +/* + ****************************************************************************** + * SAML_VerifyBearerToken -- */ /** + * + * Determines whether the SAML bearer token can be used to authenticate. + * A token consists of a single SAML assertion. + * + * This is currently only used from the test code. + * + * @param[in] xmlText The text of the SAML assertion. + * @param[in] userName Optional username to authenticate as. + * @param[out] userNameOut The user that the token has authenticated as. + * @param[out] subjNameOut The subject in the token. + * @param[out] verifySi The subjectInfo associated with the entry + * in the ID provider store used to verify the + * SAML cert. + * + * @return VGAUTH_E_OK on success, VGAuthError on failure + * + ****************************************************************************** + */ + +VGAuthError +SAML_VerifyBearerToken(const char *xmlText, + const char *userName, + char **userNameOut, + char **subjNameOut, + ServiceAliasInfo **verifyAi) +{ + try { + vector certs; + VGAuthError err; + SAMLTokenData token; + + err = SAMLVerifyAssertion(xmlText, token, certs); + if (VGAUTH_E_OK != err) { + return err; + } + + return err; + } catch (XSECException &e) { + SAMLStringWrapper msg(e.getMsg()); + + Warning("XSec exception while verifying assertion: %s.\n", msg.c_str()); + return VGAUTH_E_FAIL; + } catch (const XMLException& e) { + SAMLStringWrapper msg(e.getMessage()); + + Warning("Xerces exception while verifying assertion: %s.\n", + msg.c_str()); + return VGAUTH_E_FAIL; + } catch (...) { + // We're called from C code, so don't let any exceptions out. + Warning("Unexpected exception.\n"); + return VGAUTH_E_FAIL; + } +} + + +/* + ****************************************************************************** + * SAML_VerifyBearerTokenAndChain -- */ /** + * + * Determines whether the SAML bearer token can be used to authenticate. + * A token consists of a single SAML assertion. + * The token must first be verified, then the certificate chain used + * verify it must be checked against the appropriate certificate store. + * + * @param[in] xmlText The text of the SAML assertion. + * @param[in] userName Optional username to authenticate as. + * @param[out] userNameOut The user that the token has authenticated as. + * @param[out] subjNameOut The subject in the token. + * @param[out] verifySi The subjectInfo associated with the entry + * in the ID provider store used to verify the + * SAML cert. + * + * @return VGAUTH_E_OK on success, VGAuthError on failure + * + ****************************************************************************** + */ + +VGAuthError +SAML_VerifyBearerTokenAndChain(const char *xmlText, + const char *userName, + char **userNameOut, + char **subjNameOut, + ServiceAliasInfo **verifyAi) +{ + *userNameOut = NULL; + *subjNameOut = NULL; + *verifyAi = NULL; + + try { + vector certs; + VGAuthError err; + SAMLTokenData token; + char **pemCerts; + ServiceSubject subj; + int i; + + err = SAMLVerifyAssertion(xmlText, token, certs); + if (VGAUTH_E_OK != err) { + return err; + } + + pemCerts = (char **) g_malloc0(sizeof(char *) * certs.size()); + for (i = 0; i < (int) certs.size(); i++) { + pemCerts[i] = g_strdup(certs[i].c_str()); + } + subj.type = SUBJECT_TYPE_NAMED; + if (subjNameOut) { + *subjNameOut = g_strdup(token.subjectName.c_str()); + } + subj.name = g_strdup(token.subjectName.c_str()); + err = ServiceVerifyAndCheckTrustCertChainForSubject((int) certs.size(), + (const char **) pemCerts, + userName, + &subj, + userNameOut, + verifyAi); + Debug("%s: ServiceVerifyAndCheckTrustCertChainForSubject() returned " VGAUTHERR_FMT64 "\n", __FUNCTION__, err); + + for (i = 0; i < (int) certs.size(); i++) { + g_free(pemCerts[i]); + } + g_free(pemCerts); + g_free(subj.name); + return err; + } catch (XSECException &e) { + SAMLStringWrapper msg(e.getMsg()); + + Warning("XSec exception while verifying assertion: %s.\n", msg.c_str()); + return VGAUTH_E_FAIL; + } catch (const XMLException& e) { + SAMLStringWrapper msg(e.getMessage()); + + Warning("Xerces exception while verifying assertion: %s.\n", + msg.c_str()); + return VGAUTH_E_FAIL; + } catch (...) { + // We're called from C code, so don't let any exceptions out. + Warning("Unexpected exception.\n"); + return VGAUTH_E_FAIL; + } +} + + +/* + ****************************************************************************** + * SAMLVerifyAssertion -- */ /** + * + * Performs the following checks to validate a SAML assertion. + * 1) Checks that the XML document is well formed according to the SAML 2.0 + * Assertion XML schema. + * 2) Check that the assertion is signed by a certificate contained within + * the assertion. + * 3) TODO: Check that the assertion contains a Subject element, and that + * Subject element should contain a SubjectConfirmation element. The + * SubjectConfirmation method must be "bearer" + * ("urn:oasis:names:tc:SAML:2.0:cm:bearer"). + * 4) The Conditions element for the assertion must be met in terms of + * any "NotBefore" or "NotOnOrAfter" information. + * The chain of certs used to verify the signature will be returned via @a + * certs. + * + * @param[in] xmlText + * @param[out] token The interesting bits extracted from the xmlText. + * @param[out] certs If the SAML assertion is verified, then this will + * contain the certificate chain for the issuer. + * Each certificate will be base64 encoded (but without + * the PEM-style bookends), with the issuer's cert + * at element 0. + * + * @return VGAUTH_E_OK on success, VGAuthError on failure + * + ****************************************************************************** + */ + +VGAuthError +SAMLVerifyAssertion(const char *xmlText, + SAMLTokenData &token, + vector &certs) +{ + XercesDOMParser parser(NULL, XMLPlatformUtils::fgMemoryManager, pool); + SAMLErrorHandler errorHandler; + SecurityManager sm; + + parser.setErrorHandler(&errorHandler); + + // prevent the billion laughs attack -- put a limit on entity expansions + sm.setEntityExpansionLimit(100); + parser.setSecurityManager(&sm); + + DOMDocument *doc = SAMLValidateSchemaAndParse(parser, xmlText); + if (NULL == doc) { + return VGAUTH_E_AUTHENTICATION_DENIED; + } + + const DOMElement *s = SAMLFindChildByName(doc->getDocumentElement(), + SAML_TOKEN_PREFIX"Subject"); + if (NULL == s) { + Debug("Couldn't find " SAML_TOKEN_PREFIX " in token\n"); + s = SAMLFindChildByName(doc->getDocumentElement(), + SAML_TOKEN_SSO_PREFIX"Subject"); + if (NULL == s) { + Debug("Couldn't find " SAML_TOKEN_SSO_PREFIX " in token\n"); + Warning("No recognized tags in token; punting\n"); + return VGAUTH_E_AUTHENTICATION_DENIED; + } else { + Debug("Found " SAML_TOKEN_SSO_PREFIX " in token\n"); + token.isSSOToken = true; + token.ns = SAML_TOKEN_SSO_PREFIX; + } + } else { + Debug("Found " SAML_TOKEN_PREFIX " in token\n"); + token.isSSOToken = false; + token.ns = SAML_TOKEN_PREFIX; + } + + if (!SAMLCheckSubject(doc, token)) { + return VGAUTH_E_AUTHENTICATION_DENIED; + } + + if (!SAMLCheckConditions(doc, token)) { + return VGAUTH_E_AUTHENTICATION_DENIED; + } + + if (!SAMLCheckSignature(doc, certs)) { + return VGAUTH_E_AUTHENTICATION_DENIED; + } + + return VGAUTH_E_OK; +} + + +/* + ****************************************************************************** + * SAMLValidateSchemaAndParse -- */ /** + * + * Checks that the XML document is well formed according to the SAML 2.0 + * Assertion XML schema. + * + * @param[in] parser The parser to use with the XML document. + * @param[in] xmlText The text of the SAML assertion. + * + * @return A pointer to a DOMDocument instance that represents the parsed + * SAML assertion or NULL if the document was not valid. The memory + * used by the DOMDocument is owned by the parser. + * + ****************************************************************************** + */ + +static DOMDocument * +SAMLValidateSchemaAndParse(XercesDOMParser &parser, + const char *xmlText) +{ + parser.setDoNamespaces(true); + parser.setDoSchema(true); + parser.setValidationScheme(AbstractDOMParser::Val_Always); + parser.useCachedGrammarInParse(true); + + MemBufInputSource in(reinterpret_cast(xmlText), + strlen(xmlText), "VGAuthSamlAssertion"); + + parser.parse(in); + + xsecsize_t errorCount = parser.getErrorCount(); + if (errorCount > 0) { + Debug("Encountered %u errors while parsing SAML assertion.\n", + (unsigned int) errorCount); + return NULL; + } + + DOMDocument *doc = parser.getDocument(); + ASSERT(doc != NULL); + + return doc; +} + + +/* + ****************************************************************************** + * SAMLCheckSubject -- */ /** + * + * Extracts the name of the subject and enforces any conditions in + * SubjectConfirmation elements. + * Subjects are described in section 2.4 of the SAML Core specification. + * + * Example Subject XML: + * + * + * scott@example.org + * + * + * + * + * + * + * + * @param[in] doc The DOM representation of the SAML assertions. + * @param[in/out] token Information about the token to be populated. + * + * @return true if the conditions in at least one SubjectConfirmation is met, + * false otherwise. + * + ****************************************************************************** + */ + +static bool +SAMLCheckSubject(const DOMDocument *doc, + SAMLTokenData &token) +{ + const DOMElement *subject; + char *name = g_strdup_printf("%sSubject", + token.ns.c_str()); + subject = SAMLFindChildByName(doc->getDocumentElement(), name); + g_free(name); + + if (NULL == subject) { + // Should not happen, since this is required element in the schema. + Log("%s: Missing subject element!\n", __FUNCTION__); +// ASSERT(0); + return false; + } + + const DOMElement *nameID; + name = g_strdup_printf("%sNameID", token.ns.c_str()); + nameID = SAMLFindChildByName(subject, name); + g_free(name); + if (NULL == nameID) { + /* + * The schema allows BaseID, NameID, or EncryptedID. The library code + * for the SSO server only supports NameID. EncryptedID is really + * complicated (and we don't have decryption keys, so let's not + * support it for now. + */ + + Log("%s: No NameID element for the subject.\n", __FUNCTION__); + return false; + } + + token.subjectName = SAMLStringWrapper(nameID->getTextContent()).c_str(); + Debug("%s: subjectName: '%s'\n", __FUNCTION__, token.subjectName.c_str()); + + /* + * TODO: Investigate: NameID elements can have a NameQualifier attribute. + * This smells like a domain name, and we might want to include it with + * subject name (\subjectName). + */ + + /* + * Find all the SubjectConfirmation nodes and see if at least one can be + * verified. + */ + + name = g_strdup_printf("%sSubjectConfirmation", token.ns.c_str()); + XMLT scName(name); + g_free(name); + for (DOMElement *child = subject->getFirstElementChild(); child != NULL; + child = child->getNextElementSibling()) { + + if (!XMLString::equals(child->getNodeName(), scName.getUnicodeStr())) { + continue; + } + + const XMLCh *method = child->getAttribute(MAKE_UNICODE_STRING("Method")); + if ((NULL == method) || (0 == *method)) { + // Should not happen, since this is a required attribute. + ASSERT(0); + Debug("%s: Missing confirmation method.\n", __FUNCTION__); + continue; + } + + if (!XMLString::equals( + MAKE_UNICODE_STRING("urn:oasis:names:tc:SAML:2.0:cm:bearer"), + method)) { + Debug("%s: Non-bearer confirmation method in token", __FUNCTION__); + continue; + } + + const DOMElement *subjConfirmData; + name = g_strdup_printf("%sSubjectConfirmationData", token.ns.c_str()); + subjConfirmData = SAMLFindChildByName(child, name); + g_free(name); + if (NULL != subjConfirmData) { + if (!SAMLCheckTimeAttr(subjConfirmData, "NotBefore", true) || + !SAMLCheckTimeAttr(subjConfirmData, "NotOnOrAfter", false)) { + Warning("%s: subjConfirmData time check failed\n", __FUNCTION__); + continue; + } + + const XMLCh *recipient; + recipient = subjConfirmData->getAttribute( + MAKE_UNICODE_STRING("Recipient")); + /* + * getAttribute() returns a 0-length string, not NULL, if it can't + * find what it wants. + */ + if ((0 != XMLString::stringLen(recipient)) && + !SAMLCheckAudience(recipient)) { + Debug("%s: failed recipient check\n", __FUNCTION__); + continue; + } + } + + return true; + } + + Debug("%s: Could not verify using any SubjectConfirmation elements\n", + __FUNCTION__); + return false; +} + + +/* + ****************************************************************************** + * SAMLCheckConditions -- */ /** + * + * Enforces conditions specified by the "saml:Conditions" element + * under the root element. + * Conditions are described in section 2.5 of the SAML Core specification. + * + * Example Conditions XML: + * + * + * https://sp.example.com/SAML2 + * + * + * + * @param[in] doc The DOM representation of the SAML assertions. + * + * @return true if the conditions are met; false otherwise. + * + ****************************************************************************** + */ + +static bool +SAMLCheckConditions(const DOMDocument *doc, + SAMLTokenData &token) +{ + /* + * There should be at most one Conditions element and the schema checking + * done by the parser should enforce that. + */ + char *name = g_strdup_printf("%sConditions", token.ns.c_str()); + const DOMElement *conditions = SAMLFindChildByName(doc->getDocumentElement(), + name); + g_free(name); + if (NULL == conditions) { + // Conditions are optional. + return true; + } + + if (!SAMLCheckTimeAttr(conditions, "NotBefore", true) || + !SAMLCheckTimeAttr(conditions, "NotOnOrAfter", false)) { + return false; + } + + /* + * is a generic element, intended as an extension point. + * We don't know about any. According to the general processng rules, if + * we find a condition we don't know about, the result of the validation + * is "indeterminate" and we should reject the assertion. + */ + name = g_strdup_printf("%sCondition", token.ns.c_str()); + if (SAMLFindChildByName(conditions, name) != NULL) { + Log("%s: Unrecognized condition found!\n", __FUNCTION__); + g_free(name); + return false; + } + g_free(name); + + /* + * defines a set a URIs that describe what + * audience the assertioned is addressed to or intended for. + * But it's very generic. From the spec (section 2.5.1.4): + * A URI reference that identifies an intended audience. The URI + * reference MAY identify a document that describes the terms and + * conditions of audience membership. It MAY also contain the unique + * identifier URI from a SAML name identifier that describes a system + * entity. + * Some searching online shows people using http:/// as the + * URI, but let's wait until we get some feedback from the SSO team. + * TODO: Validate it using SAMLCheckAudience(). + */ + + /* + * element is specified to disallow caching. We don't + * cache, so it doesn't affect out validation. + * However, we need to communicate it to clients so they do not cache. + */ + name = g_strdup_printf("%sOneTimeUse", token.ns.c_str()); + token.oneTimeUse = (SAMLFindChildByName(conditions, name) + != NULL); + g_free(name); + + /* + * only applies if a service wants to make their own + * assertions based on a SAML assertion. That should not apply here. + */ + + return true; +} + + +/* + ****************************************************************************** + * SAMLCheckTimeAttr -- */ /** + * + * Checks that the given attribute with the given name is a timestamp and + * compares it against the current time. + * + * @param[in] elem The element containing the attribute. + * @param[in] attrName The name of the attribute. + * @param[in] notBefore Whether the condition given by the attribute + * should be in the past or 'now' (true). + * + ****************************************************************************** + */ + +static bool +SAMLCheckTimeAttr(const DOMElement *elem, + const char *attrName, + bool notBefore) +{ + const XMLCh *timeAttr = elem->getAttribute(MAKE_UNICODE_STRING(attrName)); + if ((NULL == timeAttr) || (0 == *timeAttr)) { + /* + * The presence of all time restrictions in SAML are optional, so if + * the attribute is not present, that is fine. + */ + return true; + } + + SAMLStringWrapper timeStr(timeAttr); + GTimeVal attrTime; + + if (!g_time_val_from_iso8601(timeStr.c_str(), &attrTime)) { + Log("%s: Could not parse %s value (%s).\n", __FUNCTION__, attrName, + timeStr.c_str()); + return false; + } + + GTimeVal now; + g_get_current_time(&now); + + glong diff; + + /* + * Check the difference, doing the math so that a positive + * value is bad. Ignore the micros since we're letting clock + * skew add a fudge-factor. + */ + if (notBefore) { + // expect time <= now + diff = attrTime.tv_sec - now.tv_sec; + } else { + // expect now <= time + diff = now.tv_sec - attrTime.tv_sec; + } + + /* + * A negative value is fine, a postive value + * greater than the clock skew range is bad. + */ + if (diff > clockSkewAdjustment) { + Warning("%s: FAILED SAML assertion (timeStamp %s, delta %d) %s.\n", + __FUNCTION__, timeStr.c_str(), (int) diff, + notBefore ? "is not yet valid" : "has expired"); + return false; + } + + return true; +} + + +/* + ****************************************************************************** + * SAMLCheckAudience -- */ /** + * + * Checks whether the given audience URI refers to this machine. + * + * @param[in] audience An audience URI that a token is targetted for. + * + * @return True if the audience URI refers to this machine, false otherwise. + * + ****************************************************************************** + */ + +static bool +SAMLCheckAudience(const XMLCh *audience) +{ + bool ret; + + /* + * XXX This should be much better. Ideally it should check that it refers + * to the hostname of a URL or matches some kind of URN. Also, this is + * where the VC UUID can be used when running in a VM. + * We should accept: + * URL: ://[/stuff] + * URN: urn:vmware:vgauth:::[vgauth_client_app_name] + * Glib has a basic URL and we should use it. + * TODO: Need a RpcIn call into the VMX to get the VC UUID, since it is not + * currently exposed. (Could be NamespaceDB, but then need to make a separate + * workflow for pushing the VC UUIDs out to VMs.) + */ + + ret = strstr(SAMLStringWrapper(audience).c_str(), + g_get_host_name()) != NULL; + Debug("%s: audience check: token: '%s', host: '%s' ? %d\n", + __FUNCTION__, + SAMLStringWrapper(audience).c_str(), + g_get_host_name(), ret); + return ret; +} + + +/* + ****************************************************************************** + * SAMLCheckSignature -- */ /** + * + * Finds the signature in the SAML assertion, then extracts the X509 + * from that, then checks that the signature is valid. + * + * @param[in] doc The document of which to check the signature. + * @param[out] certs The base64 encoded certificates present in the + * signature. + * + * @return true if the signature if valid, false otherwise. + * + ****************************************************************************** + */ + +static bool +SAMLCheckSignature(DOMDocument *doc, + vector &certs) +{ + DOMElement *sigElem = SAMLFindChildByName(doc->getDocumentElement(), + "ds:Signature"); + if (NULL == sigElem) { + Warning("%s: No top level signature found.\n", __FUNCTION__); + return false; + } + + XSECEnv secEnv(doc); + + auto_ptr keyInfo = SAMLFindKey(secEnv, sigElem); + if (keyInfo.get() == NULL) { + Warning("%s: No X509 data found as part of the signature.\n", + __FUNCTION__); + return false; + } + + if (keyInfo->getCertificateListSize() == 0) { + Warning("%s: No X509 certificates found in the signature\n", + __FUNCTION__); + return false; + } + + const XSECCryptoX509 *x509 = keyInfo->getCertificateCryptoItem(0); + ASSERT(NULL != x509); + + XSECProvider prov; + DSIGSignature *sig = prov.newSignatureFromDOM(doc, sigElem); + + sig->load(); + sig->setSigningKey(x509->clonePublicKey()); + + if (!SAMLCheckReference(doc, sig)) { + return false; + } + + if (!sig->verify()) { + Warning("%s: Signature check failed: %s.\n", __FUNCTION__, + SAMLStringWrapper(sig->getErrMsgs()).c_str()); + return false; + } + + for (int i = 0; i < keyInfo->getCertificateListSize(); i++) { + const XSECCryptoX509 *cert = keyInfo->getCertificateCryptoItem(i); + certs.push_back(string(cert->getDEREncodingSB().rawCharBuffer())); + } + + return true; +} + + +/* + ****************************************************************************** + * SAMLCheckReference -- */ /** + * + * Checks that the given signature refers to (and thus was computed over) + * the root element of the document. This ensures that the entire document + * is protected/endorsed by the signature. + * See the SAML Core specification, section 5.4.2. + * + * @param[in] doc The document in which contains the signature. + * @param[in] sig The signature + * + * @return true if the signature refers to the whole document, or false + * otherwise. + * + ****************************************************************************** + */ + +static bool +SAMLCheckReference(const DOMDocument *doc, + DSIGSignature *sig) +{ + DOMElement *rootElem = doc->getDocumentElement(); + + const XMLCh *id = rootElem->getAttribute(MAKE_UNICODE_STRING("ID")); + if (NULL == id) { + Debug("%s: NULL ID attribute.\n", __FUNCTION__); + return false; + } + + XMLSize_t idLen = XMLString::stringLen(id); + if (0 == idLen) { + Debug("%s: Root element has no or an empty ID attribute.\n", + __FUNCTION__); + return false; + } + + /* + * At least one reference should contain a URI that refers to the root + * element. To do so, that URI should be "#" followed by the value of + * the ID element of the root node; for example if the ID is "SAML" the + * URI must be "#SAML". + * + * TODO: The vmacore implementation of SAML parsing, used by clients + * validating tokens, allows for multiple references and considers if + * at least one matches. However, the SAML spec (section 5.4.2) requires + * that there be only one reference element in the signature. Currently + * we follow the vmacore behavior. + */ + + XMLT uriPrefix("#"); + XMLSize_t prefixLen = XMLString::stringLen(uriPrefix.getUnicodeStr()); + + DSIGReferenceList *references = sig->getReferenceList(); + DSIGReferenceList::size_type numReferences = references->getSize(); + for (DSIGReferenceList::size_type i = 0; i < numReferences; i++) { + DSIGReference *ref = references->item(i); + const XMLCh *uri = ref->getURI(); + + if (uri != NULL && + XMLString::startsWith(uri, uriPrefix.getUnicodeStr()) && + XMLString::equals(id, uri + prefixLen)) { + return true; + } + } + + Debug("%s: No matching reference found in the signature for ID '%s'.\n", + __FUNCTION__, SAMLStringWrapper(id).c_str()); + return false; +} + + +/* + ****************************************************************************** + * SAMLFindChildByName -- */ /** + * + * Finds the first element that is a child of the given element which + * matches the given node name. + * + * TODO: Investigate using getLocalName() and getNamespaceURI() to + * identify the child, since in "ds:Signature" "ds" is an alias to as longer + * URI, and that URI should be used instead (it's more stable). + * + * @param[in] elem The element to search the children of. + * @param[in] name The name of the child element + * + * @return A pointer to the DOMElement matching the name, or NULL if + * no such element is found. + * + ****************************************************************************** + */ + +static DOMElement * +SAMLFindChildByName(const DOMElement *elem, + const char *name) +{ + XMLT sigNodeName(name); + DOMElement *childElem; + + for (childElem = elem->getFirstElementChild(); + childElem != NULL; childElem = childElem->getNextElementSibling()) { + if (XMLString::equals(childElem->getNodeName(), + sigNodeName.getUnicodeStr())) { + break; + } + } + + return childElem; +} + + +/* + ****************************************************************************** + * SAMLFindKey -- */ /** + * + * Finds the first ds:X509Data element under the given ds:Signature element. + * + * @param[in] secEnv A XSEC environment to create the object from. + * @param[in] sigElem The root element of the signuture. + * + * @return A pointer to a DSIGKeyInfoX509 object, which must be freed using + * operator delete, or NULL if no ds:X509Data element is found. + * + ****************************************************************************** + */ + +static auto_ptr +SAMLFindKey(const XSECEnv &secEnv, + const DOMElement *sigElem) +{ + DOMNodeList *keyInfos = + sigElem->getElementsByTagName(MAKE_UNICODE_STRING("ds:X509Data")); + + if (keyInfos->getLength() == 0) { + return auto_ptr(NULL); + } + + auto_ptr keyInfo(new DSIGKeyInfoX509(&secEnv, + keyInfos->item(0))); + + keyInfo->load(); + + return keyInfo; +} diff --git a/open-vm-tools/vgauth/serviceImpl/saml-xmlsec1.c b/open-vm-tools/vgauth/serviceImpl/saml-xmlsec1.c index 5975ce37d..9fa2aab29 100644 --- a/open-vm-tools/vgauth/serviceImpl/saml-xmlsec1.c +++ b/open-vm-tools/vgauth/serviceImpl/saml-xmlsec1.c @@ -1,5 +1,5 @@ /********************************************************* - * Copyright (C) 2016-2019 VMware, Inc. All rights reserved. + * Copyright (C) 2016-2018 VMware, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published @@ -46,33 +46,6 @@ #include "certverify.h" #include "vmxlog.h" -/* - * XXX - * - * Optimization idea: stash a hash (SHA512) of a valid token, and bypass - * the full assertion process when we see that token again. The expiration - * date of the token must also be saved off (and beware the time skew issue). - * - * Note that there's some extra complexity here: - * - * 1 - AddAlias sets up a cert/user mapping - * 2 - a SAML token is used (and cached) using this cert/user combo - * 3 - RemoveAlias removes the combo - * 4 - the cached token still works - * - * So the cache should only bypass the token validation, not the certificate - * check in ServiceVerifyAndCheckTrustCertChainForSubject() - * - * Also TBD is how much this buys us in the real world. With short - * token lifetimes, its less interesting. Its also possible that - * it will have no measureable affect because the token verification - * will be lost in the noise of the API plumbing from VC->hostd->VMX->tools. - * - * The security folks have signed off on this, so long as we store only - * in memory. - * - */ - static int gClockSkewAdjustment = VGAUTH_PREF_DEFAULT_CLOCK_SKEW_SECS; static xmlSchemaPtr gParsedSchemas = NULL; static xmlSchemaValidCtxtPtr gSchemaValidateCtx = NULL; @@ -1269,9 +1242,9 @@ VerifySignature(xmlDocPtr doc, /* * Get the cert chain from the token. * - * xmlsec1 wants to validate the cert chain in the tokeni - * so it needs the full chain, not just the public key from - * the first cert. + * Unlike xml-security-c, xmlsec1 wants to validate the cert + * chain in the token so it needs the full chain, not just + * the public key from the first cert. * * Also save it off for later use by the alias store check. */ @@ -1317,8 +1290,8 @@ VerifySignature(xmlDocPtr doc, } /* - * No need to verify the Reference explicitly because the - * xmlsec1 library takes care of it. + * The xml-security-c verifies the Reference explicitly; this + * isn't needed for xmlsec1 because the library does it. */ /* diff --git a/open-vm-tools/vgauth/serviceImpl/samlInt.hpp b/open-vm-tools/vgauth/serviceImpl/samlInt.hpp new file mode 100644 index 000000000..e59a34d4f --- /dev/null +++ b/open-vm-tools/vgauth/serviceImpl/samlInt.hpp @@ -0,0 +1,141 @@ +/********************************************************* + * Copyright (C) 2011-2016 VMware, Inc. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation version 2.1 and no 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 Lesser GNU General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + *********************************************************/ + +/** + * @file samlInt.hpp + * + * Functions that only need to be used within the SAML module or + * for testing thereof. + */ + +#ifndef _SAMLINT_H_ +#define _SAMLINT_H_ + +#include +#include + +#include + +#include + +#include "VGAuthError.h" + + +using namespace std; + +#ifdef XERCES_CPP_NAMESPACE_USE +XERCES_CPP_NAMESPACE_USE +#endif + + +/** + * Inherit from this class to disallow copy-construction and + * assignment. (Similar to boost::noncopyable) + */ + +class Noncopyable { +protected: + Noncopyable() + { + } + +private: + // Disallow copy-construction, assignment. + Noncopyable(const Noncopyable &); + const Noncopyable& operator=(const Noncopyable &); +}; + +/** + * A simple wrapper to convert Xerces's XMLCh strings to UTF-8, and manage + * the memory for the converted string. Instances of this class are immutable. + */ + +class SAMLStringWrapper : public Noncopyable { +public: + SAMLStringWrapper(const XMLCh *str) : + m_str(XMLString::transcode(str)) + { + } + + ~SAMLStringWrapper() + { + XMLString::release(const_cast(&m_str)); + } + + const char * + c_str() const + { + return m_str; + } + +private: + const char *m_str; +}; + +/** + * Wrapper class for strings allocated by GLib. The object takes ownership of + * the string's memory. + */ + +class SAMLGlibString { +public: + SAMLGlibString(gchar *str) : + m_str(str) + { + } + + SAMLGlibString(const SAMLGlibString &s) : + m_str(g_strdup(s.m_str)) + { + } + + ~SAMLGlibString() + { + g_free(const_cast(m_str)); + } + +const char * + c_str() const + { + return m_str; + } + +private: + SAMLGlibString &operator=(const SAMLGlibString &); // immuatable + + const gchar *m_str; +}; + +/* + * Holds data extracted from a SAML token. + */ +struct SAMLTokenData { + string subjectName; + vector issuerCerts; + bool oneTimeUse; + bool isSSOToken; // set if token came from VMware SSO server + string ns; +}; + + +auto_ptr SAMLCreateAndPopulateGrammarPool(); + +VGAuthError SAMLVerifyAssertion(const char *xmlText, + SAMLTokenData &token, + vector &certs); +#endif // ifndef _SAMLINT_H_