]> git.ipfire.org Git - thirdparty/open-vm-tools.git/commitdiff
Back out the previous change to remove support for building with
authorOliver Kurth <okurth@vmware.com>
Tue, 19 Feb 2019 20:51:31 +0000 (12:51 -0800)
committerOliver Kurth <okurth@vmware.com>
Tue, 19 Feb 2019 20:51:31 +0000 (12:51 -0800)
xml-security-c and xerces-c.

open-vm-tools/configure.ac
open-vm-tools/vgauth/service/Makefile.am
open-vm-tools/vgauth/serviceImpl/saml-xml-security-c.cpp [new file with mode: 0644]
open-vm-tools/vgauth/serviceImpl/saml-xmlsec1.c
open-vm-tools/vgauth/serviceImpl/samlInt.hpp [new file with mode: 0644]

index 2c1409b2b63dff6392c1167891257eabe4bbe6a7..aa1c650c69e4a9c58e31e8313e4d9a96170642e4 100644 (file)
@@ -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
index 8e21c472521b64523682c7b9344f02b745ba1c60..c1cee1e66384126db85f01af5c35af91d0771bf3 100644 (file)
@@ -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 (file)
index 0000000..a70f602
--- /dev/null
@@ -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 <iostream>
+#include <memory>
+#include <sstream>
+#include <vector>
+
+#undef WIN32_LEAN_AND_MEAN  // XSEC unconditionally redefines this
+#include <xsec/dsig/DSIGKeyInfoX509.hpp>
+#include <xsec/dsig/DSIGReference.hpp>
+#include <xsec/dsig/DSIGReferenceList.hpp>
+#include <xsec/framework/XSECEnv.hpp>
+#include <xsec/framework/XSECException.hpp>
+#include <xsec/framework/XSECProvider.hpp>
+#include <xsec/utils/XSECDOMUtils.hpp>
+
+#include <xercesc/dom/DOMNode.hpp>
+#include <xercesc/framework/MemBufInputSource.hpp>
+#include <xercesc/framework/MemoryManager.hpp>
+#include <xercesc/framework/XMLGrammarPool.hpp>
+#include <xercesc/framework/XMLGrammarPoolImpl.hpp>
+#include <xercesc/parsers/XercesDOMParser.hpp>
+#include <xercesc/sax/ErrorHandler.hpp>
+#include <xercesc/sax/SAXParseException.hpp>
+#include <xercesc/util/PlatformUtils.hpp>
+#include <xercesc/util/XMLString.hpp>
+#include <xercesc/validators/common/Grammar.hpp>
+
+/*
+ * 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<char, std::char_traits<char> >&
+       *       std::basic_ostream<char, std::char_traits<char> >::
+       *       _M_insert<unsigned long long>(unsigned long long)
+       *  aka: operator<<(uint64_t)
+       *
+       * _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_i@@GLIBCXX_3.4.9
+       *    std::basic_ostream<char, std::char_traits<char> >&
+       *       std::__ostream_insert<char, std::char_traits<char> >
+       *       (std::basic_ostream<char, std::char_traits<char> >&,
+       *       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<string> &certs);
+
+static bool SAMLCheckReference(const DOMDocument *doc, DSIGSignature *sig);
+
+static DOMElement *SAMLFindChildByName(const DOMElement *elem,
+                                       const char *name);
+
+static auto_ptr<DSIGKeyInfoX509> 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<XMLGrammarPool> 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<XMLGrammarPool>
+SAMLCreateAndPopulateGrammarPool()
+{
+   auto_ptr<XMLGrammarPool> 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<XMLGrammarPool>(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<XMLGrammarPool> 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<string> 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<string> 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<string> &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<const XMLByte *>(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:
+ * <saml:Subject>
+ *    <saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">
+ *       scott@example.org
+ *    </saml:NameID>
+ *    <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
+ *       <saml:SubjectConfirmationData NotOnOrAfter="2011-12-08T00:42:10Z">
+ *       </saml:SubjectConfirmationData>
+ *    </saml:SubjectConfirmation>
+ * </saml:Subject>
+ *
+ * @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 (<NameQualifier>\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:
+ *    <saml:Conditions NotBefore="2011-12-08T00:41:10Z"
+ *     NotOnOrAfter="2011-12-08T00:42:10Z">
+ *       <saml:AudienceRestriction>
+ *          <saml:Audience>https://sp.example.com/SAML2</saml:Audience>
+ *       </saml:AudienceRestriction>
+ *    </saml:Conditions>
+ *
+ * @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;
+   }
+
+   /*
+    * <Condition> 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);
+
+   /*
+    * <AudienceRestriction> 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://<hostname>/ as the
+    * URI, but let's wait until we get some feedback from the SSO team.
+    * TODO: Validate it using SAMLCheckAudience().
+    */
+
+   /*
+    * <OneTimeUse> 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);
+
+   /*
+    * <ProxyRestriction> 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: <scheme_name>://<host_name>[/stuff]
+    *   URN: urn:vmware:vgauth:<vc_domain_name>:<vc_vm_uuid>:[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<string> &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<DSIGKeyInfoX509> 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<DSIGKeyInfoX509>
+SAMLFindKey(const XSECEnv &secEnv,
+            const DOMElement *sigElem)
+{
+   DOMNodeList *keyInfos =
+      sigElem->getElementsByTagName(MAKE_UNICODE_STRING("ds:X509Data"));
+
+   if (keyInfos->getLength() == 0) {
+      return auto_ptr<DSIGKeyInfoX509>(NULL);
+   }
+
+   auto_ptr<DSIGKeyInfoX509> keyInfo(new DSIGKeyInfoX509(&secEnv,
+                                                         keyInfos->item(0)));
+
+   keyInfo->load();
+
+   return keyInfo;
+}
index 5975ce37dfc95ca531b87a7fe88bd47933cfe80f..9fa2aab299fa348118fd79ba76b6e34043ab389a 100644 (file)
@@ -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
 #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 (file)
index 0000000..e59a34d
--- /dev/null
@@ -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 <string>
+#include <vector>
+
+#include <xercesc/framework/XMLGrammarPool.hpp>
+
+#include <glib.h>
+
+#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<char **>(&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<char *>(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<string> issuerCerts;
+   bool oneTimeUse;
+   bool isSSOToken;           // set if token came from VMware SSO server
+   string ns;
+};
+
+
+auto_ptr<XMLGrammarPool> SAMLCreateAndPopulateGrammarPool();
+
+VGAuthError SAMLVerifyAssertion(const char *xmlText,
+                                SAMLTokenData &token,
+                                vector<string> &certs);
+#endif // ifndef _SAMLINT_H_