]> git.ipfire.org Git - thirdparty/open-vm-tools.git/commitdiff
Add xmlsec1 to open-vm-tools
authorOliver Kurth <okurth@vmware.com>
Fri, 15 Sep 2017 18:23:05 +0000 (11:23 -0700)
committerOliver Kurth <okurth@vmware.com>
Fri, 15 Sep 2017 18:23:05 +0000 (11:23 -0700)
Add the new files to open vm tools and add a switch to
turn it on.  xml-seccurity-c remains the default for
now.

Also clean up the service Makefile, which was using
the client lib (which also dragged in stuff it shouldn't have)
for common code instead of building it in.

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

index 201ff797e37f651778047b8eec38fd3f43b9bdad..26cc33b4d79d987e301bec7754786116b3c26410 100644 (file)
@@ -433,8 +433,32 @@ fi
 AC_ARG_ENABLE([vgauth],
    [AS_HELP_STRING([--disable-vgauth],
      [do not build vgauth.])],
-   [],
-   [enable_vgauth=yes])
+   [
+   enable_vgauth="$enableval"
+   use_xmlsec1=no
+   ],
+   [
+    enable_vgauth=yes
+    use_xmlsec1=no
+   ])
+
+AC_ARG_ENABLE([xmlsec1],
+   [AS_HELP_STRING([--enable-xmlsec1],
+     [build vgauth with xmlsec1 instead of xml-security-c (off by default).])],
+   [use_xmlsec1="$enableval"],
+   [use_xmlsec1=no])
+
+AC_ARG_ENABLE([xml-security-c],
+   [AS_HELP_STRING([--disable-xml-security-c],
+     [build vgauth with xml-security-c instead of xmlsec1 (on by default).])],
+   [
+    if test "$enableval" = "yes"; then
+       use_xmlsec1="no";
+    else
+       use_xmlsec1="yes"
+    fi
+   ],
+   [])
 
 if test "$enable_vgauth" = "yes" ; then
    AC_ARG_ENABLE([caf],
@@ -471,6 +495,19 @@ AC_ARG_WITH([xerces],
    [enable_vgauth=no],
    [with_xerces=yes])
 
+AC_ARG_WITH([xmlsec1],
+   [AS_HELP_STRING([--without-xmlsec1],
+     [compiles without xmlsec1 support (disables vgauth).])],
+   [enable_vgauth=no],
+   [with_xmlsec1=yes])
+
+AC_ARG_WITH([xml2],
+   [AS_HELP_STRING([--without-xml2],
+     [compiles without xml2 support (disables vgauth).])],
+   [enable_vgauth=no],
+   [with_xml2=yes])
+
+
 # Make sure we are building with openssl 1.0.1 and above so that
 # we use only TLSv1_2.
 
@@ -489,7 +526,29 @@ fi
 
 if test "$enable_vgauth" = "yes"; then
    CPPFLAGS="$CPPFLAGS -DUSE_VGAUTH"
-   AC_VMW_DEFAULT_FLAGS([XERCES])
+if test "$use_xmlsec1" = "yes"; then
+   AC_VMW_DEFAULT_FLAGS([XML2])
+   AC_VMW_CHECK_LIB([xml2],
+                    [XML2],
+                    [],
+                    [],
+                    [],
+                    [],
+                    [],
+                    [],
+                    [AC_VMW_LIB_ERROR([XML2], [xml2])])
+
+   AC_VMW_DEFAULT_FLAGS([XMLSEC1])
+   AC_VMW_CHECK_LIB([xmlsec1],
+                    [XMLSEC1],
+                    [],
+                    [xmlsec1-config],
+                    [],
+                    [xmlsec/xmlsec.h],
+                    [xmlSecCheckVersion],
+                    [],
+                    [AC_VMW_LIB_ERROR([XMLSEC1], [xmlsec1])])
+else
    AC_VMW_CHECK_LIB([xerces-c],
                     [XERCES],
                     [],
@@ -510,6 +569,7 @@ if test "$enable_vgauth" = "yes"; then
                     [],
                     [],
                     [AC_VMW_LIB_ERROR([XMLSECURITY], [xml-security-c])])
+fi
 else
    enable_caf=no
 fi
@@ -1241,6 +1301,7 @@ AM_CONDITIONAL(USE_PRINTF_WRAPPERS, test "$bsdPrintfWrappers" = "yes")
 AM_CONDITIONAL(ENABLE_DEPLOYPKG, test "$enable_deploypkg" = "yes")
 AM_CONDITIONAL(ENABLE_GRABBITMQPROXY, test "$enable_grabbitmqproxy" = "yes")
 AM_CONDITIONAL(ENABLE_VGAUTH, test "$enable_vgauth" = "yes")
+AM_CONDITIONAL(USE_XMLSEC1, test "$use_xmlsec1" = "yes")
 AM_CONDITIONAL(ENABLE_CAF, test "$enable_caf" = "yes")
 AM_CONDITIONAL(HAVE_VSOCK, test "$os" = "linux")
 AM_CONDITIONAL(HAVE_MKDTEMP, test "$have_mkdtemp" = "yes")
index 7fedee1f6957c5bd0f8b0dc74034a03fe7714138..8c51d9f2ab94fb3ecc330daa8d786630a6964ab5 100644 (file)
@@ -30,11 +30,23 @@ 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
 
+VGAuthService_SOURCES += ../common/audit.c
+VGAuthService_SOURCES += ../common/certverify.c
+VGAuthService_SOURCES += ../common/i18n.c
+VGAuthService_SOURCES += ../common/prefs.c
+VGAuthService_SOURCES += ../common/usercheck.c
+VGAuthService_SOURCES += ../common/VGAuthLog.c
+VGAuthService_SOURCES += ../common/VGAuthUtil.c
+
 VGAuthServicedir = /etc/vmware-tools/vgauth/schemas
 VGAuthService_SCRIPTS =
 VGAuthService_SCRIPTS += ../serviceImpl/schemas/datatypes.dtd
@@ -49,26 +61,30 @@ VGAuthService_SCRIPTS += ../serviceImpl/schemas/xml.xsd
 VGAuthService_SCRIPTS += ../serviceImpl/schemas/catalog.xml
 
 VGAuthService_CPPFLAGS =
-VGAuthService_CPPFLAGS += -DVMTOOLS_USE_GLIB
 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)/libvmtools
 VGAuthService_CPPFLAGS += -I$(top_srcdir)/vgauth/public
 VGAuthService_CPPFLAGS += -I$(top_srcdir)/vgauth/common
 VGAuthService_CPPFLAGS += -I$(top_srcdir)/vgauth/serviceImpl
 
 VGAuthService_LDADD =
-VGAuthService_LDADD += @VMTOOLS_LIBS@
 VGAuthService_LDADD += @GLIB2_LIBS@
 VGAuthService_LDADD += @GTHREAD_LIBS@
-VGAuthService_LDADD += ../lib/libvgauth.la
+if USE_XMLSEC1
+VGAuthService_LDADD += @XMLSEC1_LIBS@
+else
 VGAuthService_LDADD += @XERCES_LIBS@
 VGAuthService_LDADD += @XMLSECURITY_LIBS@
-VGAuthService_LDADD += @SSL_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-xmlsec1.c b/open-vm-tools/vgauth/serviceImpl/saml-xmlsec1.c
new file mode 100644 (file)
index 0000000..e74ffff
--- /dev/null
@@ -0,0 +1,1554 @@
+/*********************************************************
+ * Copyright (C) 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 saml-xmlsec1.cpp
+ *
+ * Code for authenticating users based on SAML tokens.
+ */
+
+
+/*
+ * XXX TODO
+ *
+ * - Reference verify or confirm its done by sig check
+ *
+ * - refactor to share code with xml-security-c verifiier
+ * - refactor to share with test/sign code
+ *   make things static and tweak names as necessary
+ * - testing
+ * - valgrind
+ *
+ */
+
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include <libxml/tree.h>
+#include <libxml/xmlmemory.h>
+#include <libxml/parser.h>
+#include <libxml/catalog.h>
+#include <libxml/xmlschemas.h>
+
+#include <xmlsec/xmlsec.h>
+#include <xmlsec/xmltree.h>
+#include <xmlsec/xmldsig.h>
+#include <xmlsec/templates.h>
+#include <xmlsec/crypto.h>
+#include <xmlsec/errors.h>
+
+#include <glib.h>
+
+#include "prefs.h"
+#include "serviceInt.h"
+
+static int gClockSkewAdjustment = VGAUTH_PREF_DEFAULT_CLOCK_SKEW_SECS;
+static xmlSchemaPtr gParsedSchemas = NULL;
+static xmlSchemaValidCtxtPtr gSchemaValidateCtx = NULL;
+
+#define CATALOG_FILENAME            "catalog.xml"
+#define SAML_SCHEMA_FILENAME        "saml-schema-assertion-2.0.xsd"
+
+
+/*
+ ******************************************************************************
+ * XmlErrorHandler --                                                    */ /**
+ *
+ * Error handler for xml2.
+ *
+ * @param[in]  ctx           Context (unused).
+ * @param[in]  msg           The error message in printf format.
+ * @param[in]  ...           Any args for the msg.
+ *
+ * XXX This probably wants to live in shared code.
+ *
+ ******************************************************************************
+ */
+
+static void
+XmlErrorHandler(void *ctx,
+                const char *msg,
+                ...)
+{
+   gchar msgStr[1024];
+   va_list argPtr;
+   va_start(argPtr, msg);
+   vsnprintf(msgStr, sizeof msgStr, msg, argPtr);
+   va_end(argPtr);
+
+   /*
+    * XXX are any of these either debug or fatal?  treat all as
+    * warning for now.
+    */
+   g_warning("XML Error: %s", msgStr);
+}
+
+
+/*
+ ******************************************************************************
+ * XmlSecErrorHandler --                                                 */ /**
+ *
+ * Error handler for xmlsec.
+ *
+ * @param[in]  file          The name of the file generating the error.
+ * @param[in]  line          The line number generating the error.
+ * @param[in]  func          The function generating the error.
+ * @param[in]  errorObject   The error specific object.
+ * @param[in]  errorSubject  The error specific subject.
+ * @param[in]  reason        The error code.
+ * @param[in]  msg           The additional error message.
+ *
+ * XXX This probably wants to live in shared code.
+ *
+ ******************************************************************************
+ */
+
+static void
+XmlSecErrorHandler(const char *file,
+                   int line,
+                   const char *func,
+                   const char *errorObject,
+                   const char *errorSubject,
+                   int reason,
+                   const char *msg)
+{
+   /*
+    * XXX are any of these either debug or fatal?  treat all as
+    * warning for now.
+    */
+   g_warning("XMLSec Error: %s:%s(line %d) object %s"
+             " subject %s reason: %d, msg: %s",
+             file, func, line,
+             errorObject ? errorObject : "<UNSET>",
+             errorSubject ? errorSubject : "<UNSET>",
+             reason, msg);
+}
+
+
+/*
+ ******************************************************************************
+ * LoadCatalogAndSchema --                                               */ /**
+ *
+ * Loads the schemas for validation.
+ *
+ * Using a catalog here ala xmllint.  Another option would be an
+ * additional schema acting like a catalog.
+ *
+ * @param[in]   catPath       Path to the catalog file.
+ * @param[in]   schemaPath    Path to the SAML schema file.
+ *
+ * return TRUE on success
+ ******************************************************************************
+ *
+ */
+
+gboolean
+LoadCatalogAndSchema(void)
+{
+   int ret;
+   gboolean retVal = FALSE;
+   xmlSchemaParserCtxtPtr ctx = NULL;
+   gchar *catalogPath = NULL;
+   gchar *schemaPath = NULL;
+   gchar *schemaDir = NULL;
+
+   schemaDir = Pref_GetString(gPrefs,
+                              VGAUTH_PREF_SAML_SCHEMA_DIR,
+                              VGAUTH_PREF_GROUP_NAME_SERVICE,
+                              NULL);
+
+   if (NULL == schemaDir) {
+#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.
+       */
+      schemaDir = 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__, schemaDir, newDir);
+
+         g_free(schemaDir);
+         schemaDir = newDir;
+      }
+#else
+      /*
+       * XXX -- clean this up to make a better default for Linux.
+       */
+      schemaDir = g_build_filename(gInstallDir, "..", "schemas", NULL);
+#endif
+   }
+   Log("%s: Using '%s' for SAML schemas\n", __FUNCTION__, schemaDir);
+   catalogPath = g_build_filename(schemaDir, CATALOG_FILENAME, NULL);
+   schemaPath = g_build_filename(schemaDir, SAML_SCHEMA_FILENAME, NULL);
+
+   xmlInitializeCatalog();
+
+   /*
+    * xmlLoadCatalog() just adds to the default catalog, and won't return an
+    * error if it doesn't exist so long as a default catalog is set.
+    *
+    * So sanity check its existence.
+    */
+   if (!g_file_test(catalogPath, G_FILE_TEST_EXISTS)) {
+      g_warning("Error: catalog file not found at \"%s\"\n", catalogPath);
+      retVal = FALSE;
+      goto done;
+   }
+   ret = xmlLoadCatalog(catalogPath);
+   if (ret < 0) {
+      g_warning("Error: Failed to load catalog at \"%s\"\n", catalogPath);
+      retVal = FALSE;
+      goto done;
+   }
+
+   ctx = xmlSchemaNewParserCtxt(schemaPath);
+   if (NULL == ctx) {
+      g_warning("Failed to create schema parser context\n");
+      retVal = FALSE;
+      goto done;
+   }
+
+   xmlSchemaSetParserErrors(ctx,
+                            (xmlSchemaValidityErrorFunc) XmlErrorHandler,
+                            (xmlSchemaValidityErrorFunc) XmlErrorHandler,
+                            NULL);
+   gParsedSchemas = xmlSchemaParse(ctx);
+   if (NULL == gParsedSchemas) {
+      /*
+       * This shouldn't happen.  Means somebody mucked with our
+       * schemas.
+       */
+      g_warning("Error: Failed to parse schemas\n");
+      retVal = FALSE;
+      goto done;
+   }
+
+   /*
+    * Set up the validaton context for later use.
+    */
+   gSchemaValidateCtx = xmlSchemaNewValidCtxt(gParsedSchemas);
+   if (NULL == gSchemaValidateCtx) {
+      g_warning("Failed to create schema validation context\n");
+      retVal = FALSE;
+      goto done;
+   }
+   xmlSchemaSetValidErrors(gSchemaValidateCtx,
+                           XmlErrorHandler,
+                           XmlErrorHandler,
+                           NULL);
+
+   retVal = TRUE;
+done:
+   if (NULL != ctx) {
+      xmlSchemaFreeParserCtxt(ctx);
+   }
+   g_free(catalogPath);
+   g_free(schemaPath);
+   g_free(schemaDir);
+
+   return retVal;
+}
+
+
+/*
+ ******************************************************************************
+ * FreeSchemas --                                                        */ /**
+ *
+ * Frees global schema data.
+ ******************************************************************************
+ *
+ */
+
+void
+FreeSchemas(void)
+{
+   if (NULL != gSchemaValidateCtx) {
+      xmlSchemaFreeValidCtxt(gSchemaValidateCtx);
+      gSchemaValidateCtx = NULL;
+   }
+   if (NULL != gParsedSchemas) {
+      xmlSchemaFree(gParsedSchemas);
+      gParsedSchemas = NULL;
+   }
+}
+
+
+/*
+ ******************************************************************************
+ * LoadPrefs --                                                          */ /**
+ *
+ * Loads any preferences SAML cares about.
+ ******************************************************************************
+ *
+ */
+
+static void
+LoadPrefs(void)
+{
+   gClockSkewAdjustment = 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__, gClockSkewAdjustment);
+}
+
+
+/*
+ ******************************************************************************
+ * SAML_Init --                                                          */ /**
+ *
+ * Performs any initialization needed for SAML processing.
+ *
+ * @return VGAUTH_E_OK on success, VGAuthError on failure
+ *
+ ******************************************************************************
+ */
+
+VGAuthError
+SAML_Init(void)
+{
+   int ret;
+
+   /*
+    * Init the xml parser
+    */
+   xmlInitParser();
+
+   /*
+    * Verify the xml2 version -- if this is too old
+    * its fatal, so we may want to use a different check.
+    */
+   LIBXML_TEST_VERSION
+
+   /*
+    * Tell libxml to do ID/REF lookups
+    * Tell libxml to complete attributes with defaults from the DTDs
+    */
+   xmlLoadExtDtdDefaultValue = XML_DETECT_IDS | XML_COMPLETE_ATTRS;
+   xmlSubstituteEntitiesDefault(1);
+
+
+   /* set up the xml2 error handler */
+   xmlSetGenericErrorFunc(NULL, XmlErrorHandler);
+
+   /*
+    * Load schemas
+    */
+   if (!LoadCatalogAndSchema()) {
+      g_warning("Failed to load schemas\n");
+      return VGAUTH_E_FAIL;
+   }
+
+   /* init xmlsec */
+   ret = xmlSecInit();
+   if (ret < 0) {
+      g_warning("xmlSecInit() failed %d\n", ret);
+      return VGAUTH_E_FAIL;
+   }
+
+   /*
+    * version check xmlsec1
+    */
+   // XXX may want to make this non-fatal
+   if (xmlSecCheckVersion() != 1) {
+      g_warning("Error: xmlsec1 lib version mismatch\n");
+      return VGAUTH_E_FAIL;
+   }
+
+   /*
+    * init the xmlsec1 crypto app layer
+    */
+   ret = xmlSecCryptoAppInit(NULL);
+   if (ret < 0) {
+      g_warning("xmlSecCryptoAppInit() failed %d\n", ret);
+      return VGAUTH_E_FAIL;
+   }
+
+   /*
+    * Do crypto-engine specific initialization
+    */
+   ret = xmlSecCryptoInit();
+   if (ret < 0) {
+      g_warning("xmlSecCryptoInit() failed %d\n", ret);
+      return VGAUTH_E_FAIL;
+   }
+
+   /*
+    * set up the error callback
+    */
+   xmlSecErrorsSetCallback(XmlSecErrorHandler);
+
+   /*
+    * Load prefs
+    */
+   LoadPrefs();
+
+   return VGAUTH_E_OK;
+}
+
+
+/*
+ ******************************************************************************
+ * SAML_Shutdown --                                                      */ /**
+ *
+ * Performs any clean-up of resources allocated by SAML code.
+ *
+ ******************************************************************************
+ */
+
+void
+SAML_Shutdown()
+{
+   FreeSchemas();
+   xmlSecCryptoShutdown();
+   xmlSecCryptoAppShutdown();
+   xmlSecShutdown();
+
+#if 0
+   /*
+    * This is not thread safe:
+    * http://0pointer.de/blog/projects/beware-of-xmlCleanupParser
+    * and should only be called just before exit()
+    * Because of this, our symbol-checker hates it:  See PR 407137
+    */
+   xmlCleanupParser();
+#endif
+}
+
+
+/*
+ ******************************************************************************
+ * SAML_Reload --                                                        */ /**
+ *
+ * Reload any in-memory state used by the SAML module.
+ *
+ ******************************************************************************
+ */
+
+void
+SAML_Reload()
+{
+   FreeSchemas();
+   LoadPrefs();
+   LoadCatalogAndSchema();
+}
+
+
+/*
+ ******************************************************************************
+ * FreeCertArray --                                                      */ /**
+ *
+ * Frees a simple array of pemCert.
+ *
+ * @param[in]  num      Number of certs in array.
+ * @param[in]  certs    Array of certs to free.
+ *
+ ******************************************************************************
+ */
+static void
+FreeCertArray(int num,
+              gchar **certs)
+{
+   int i;
+
+   for (i = 0; i < num; i++) {
+      g_free(certs[i]);
+   }
+   g_free(certs);
+}
+
+
+/*
+ ******************************************************************************
+ * FindAttrValue --                                                      */ /**
+ *
+ * Returns the value of a attribute in an XML node.
+ *
+ * @param[in] node     XML subtree node.
+ * @param[in] attrName Name of the attribute.
+ *
+ * @return Attribute value if exists.  The caller must free this with xmlFree().
+ *
+ ******************************************************************************
+ */
+
+static xmlChar *
+FindAttrValue(const xmlNodePtr node,
+              const gchar *attrName)
+{
+   xmlAttrPtr attr;
+   xmlChar *name;
+
+   /*
+    * Find the attribute
+    */
+   attr = xmlHasProp(node, attrName);
+   if ((attr == NULL) || (attr->children == NULL)) {
+      return NULL;
+   }
+
+   /*
+    * get the attribute value
+    */
+   name = xmlNodeListGetString(node->doc, attr->children, 1);
+
+   return name;
+}
+
+
+/*
+ ******************************************************************************
+ * RegisterID --                                                         */ /**
+ *
+ * Register the document ID with the xml parser.
+ *
+ * This needs to be done if the document ID doesn't use the standard.
+ * Otherwise the signing fails when setting up the reference.
+ * SAML likes using 'ID' intead of the default 'xml:id', so
+ * this is needed for both signing and verification.
+ *
+ * This is a no-op if the schemas have been loaded since they
+ * set it up.
+ *
+ * See xmlsec1 FAQ 3.2
+ *
+ * Based on https://www.aleksey.com/pipermail/xmlsec/2003/001768.html
+ *
+ * @param[in]  node        The XML node on which to set the ID.
+ * @param[in]  idName      The name of the ID.
+ *
+ * @return TRUE on success.
+ ******************************************************************************
+ */
+
+gboolean
+RegisterID(xmlNodePtr node,
+           const xmlChar *idName)
+{
+   xmlAttrPtr attr;
+   xmlAttrPtr tmp;
+   xmlChar *name;
+
+   /*
+    * find pointer to id attribute
+    */
+   attr = xmlHasProp(node, idName);
+   if ((attr == NULL) || (attr->children == NULL)) {
+      return FALSE;
+   }
+
+   /*
+    * get the attribute (id) value
+    */
+   name = xmlNodeListGetString(node->doc, attr->children, 1);
+   if (name == NULL) {
+      return FALSE;
+   }
+
+   /*
+    * check that we don't have the id already registered
+    */
+   tmp = xmlGetID(node->doc, name);
+   if (tmp != NULL) {
+      xmlFree(name);
+      /* no-op if its already there */
+      return TRUE;
+   }
+
+   /*
+    * finally register id
+    */
+   xmlAddID(NULL, node->doc, name, attr);
+
+   xmlFree(name);
+   return TRUE;
+}
+
+
+/*
+ ******************************************************************************
+ * FindNodeByName --                                                     */ /**
+ *
+ * Searches under the specified node for one with a matching name.
+ *
+ * @param[in] root     XML subtree root under which to search.
+ * @param[in] nodeName Name of node to find.
+ *
+ * @return matching xmlNodePtr or NULL.  Caller should not free this node.
+ *
+ ******************************************************************************
+ */
+
+static xmlNodePtr
+FindNodeByName(xmlNodePtr root,
+               char *nodeName)
+{
+   xmlNodePtr cur;
+
+   cur = root->children;
+   while (cur != NULL) {
+      if (cur->type == XML_ELEMENT_NODE) {
+         if (xmlStrEqual(nodeName, cur->name)) {
+            break;
+         }
+      }
+      cur = cur->next;
+   }
+
+   return cur;
+}
+
+
+/*
+ ******************************************************************************
+ * FindAllNodesByName --                                                 */ /**
+ *
+ * Searches under the specified node for all with a matching name.
+ *
+ * @param[in]  root     XML subtree root under which to search.
+ * @param[in]  nodeName Name of node to find.
+ * @param[out] nodeName Array of matches.
+ *
+ * @return Number of matching nodes.  Caller needs to free the array
+ *         of Nodes, but not the nodes themselves.
+ *
+ ******************************************************************************
+ */
+
+static int
+FindAllNodesByName(xmlNodePtr root,
+                   char *nodeName,
+                   xmlNodePtr **nodes)
+{
+   xmlNodePtr cur;
+   xmlNodePtr *list = NULL;
+   int count = 0;
+
+   cur = root->children;
+   while (cur != NULL) {
+      if (cur->type == XML_ELEMENT_NODE) {
+         if (xmlStrEqual(nodeName, cur->name)) {
+            list = g_realloc_n(list,
+                               sizeof(xmlNodePtr),
+                               count + 1);
+            list[count++] = cur;
+         }
+      }
+      cur = cur->next;
+   }
+   *nodes = list;
+
+   return count;
+}
+
+
+/*
+ ******************************************************************************
+ * ValidateDoc --                                                        */ /**
+ *
+ * Validates the XML document against the schema.
+ *
+ * @param[in]  doc         Parsed XML document.
+ *
+ ******************************************************************************
+ */
+
+gboolean
+ValidateDoc(xmlDocPtr doc)
+{
+   int ret;
+
+   ret = xmlSchemaValidateDoc(gSchemaValidateCtx, doc);
+   if (ret < 0) {
+      g_warning("Failed to validate doc against schema\n");
+   }
+
+   return (ret == 0) ? TRUE : FALSE;
+}
+
+
+/*
+ ******************************************************************************
+ * CheckTimeAttr --                                                      */ /**
+ *
+ * Checks that the given attribute with the given name is a timestamp and
+ * compares it against the current time.
+ *
+ * @param[in]  node         The node 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).
+ *
+ ******************************************************************************
+ */
+
+gboolean
+CheckTimeAttr(const xmlNodePtr node,
+              const gchar *attrName,
+              gboolean notBefore)
+{
+   xmlChar *timeAttr;
+   GTimeVal attrTime;
+   GTimeVal now;
+   glong diff;
+   gboolean retVal;
+
+   timeAttr = FindAttrValue(node, 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.
+       */
+      retVal = TRUE;
+      goto done;
+   }
+
+   if (!g_time_val_from_iso8601(timeAttr, &attrTime)) {
+      g_warning("%s: Could not parse %s value (%s).\n", __FUNCTION__, attrName,
+                timeAttr);
+      retVal = FALSE;
+      goto done;
+   }
+
+   g_get_current_time(&now);
+
+   /*
+    * Check the difference, doing the math so that a positive
+    * value is bad.  Ignore the micros field since precision
+    * is unnecessary here because we see unsynced clocks in
+    * the real world.
+    */
+   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 > gClockSkewAdjustment) {
+      g_debug("%s: FAILED SAML assertion (timeStamp %s, delta %d) %s.\n",
+              __FUNCTION__, timeAttr, (int) diff,
+              notBefore ? "is not yet valid" : "has expired");
+      retVal = FALSE;
+      goto done;
+   }
+
+   retVal = TRUE;
+
+done:
+   if (timeAttr) {
+      xmlFree(timeAttr);
+   }
+   return retVal;
+}
+
+
+/*
+ ******************************************************************************
+ * CheckAudience --                                                      */ /**
+ *
+ * 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.
+ *
+ ******************************************************************************
+ */
+
+gboolean
+CheckAudience(const xmlChar *audience)
+{
+   gboolean ret;
+
+   /*
+    * Our SSO server doesn't set Recipient, so this only gets used by test code
+    * whch uses a simple hostname check.
+    *
+    * Something like a VC UUID might be more accurate in a virtual
+    * machine.
+    */
+
+   ret = strstr(audience, g_get_host_name()) != NULL;
+   g_debug("%s: audience check: token: '%s', host: '%s' ? %d\n",
+           __FUNCTION__,
+           audience, g_get_host_name(), ret);
+   return ret;
+}
+
+
+/*
+ ******************************************************************************
+ * VerifySubject --                                                      */ /**
+ *
+ * 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 parsed SAML token.
+ * @param[out]    subjectRet  The Subject NameId.  Should be g_free()d by
+ *                            caller.
+ *
+ * @return TRUE if the conditions in at least one SubjectConfirmation is met,
+ *         FALSE otherwise.
+ *
+ ******************************************************************************
+ */
+
+gboolean
+VerifySubject(xmlDocPtr doc,
+              gchar **subjectRet)
+{
+   xmlNodePtr subjNode;
+   xmlNodePtr nameIDNode;
+   xmlNodePtr child;
+   gchar *subjectVal = NULL;
+   gboolean retCode = FALSE;
+   gboolean validSubjectFound = FALSE;
+   xmlChar *tmp;
+
+   if (NULL != subjectRet) {
+      *subjectRet = NULL;
+   }
+
+   subjNode = FindNodeByName(xmlDocGetRootElement(doc), "Subject");
+   if (NULL == subjNode) {
+      g_warning("No Subject node found\n");
+      goto done;
+   }
+
+   /*
+    * Pull out the NameID for later checks elsewhere.
+    */
+   nameIDNode = FindNodeByName(subjNode, "NameID");
+   if (NULL == nameIDNode) {
+      g_warning("%s: NameID not found in Subject\n", __FUNCTION__);
+      goto done;
+   }
+   tmp = xmlNodeGetContent(nameIDNode);
+   subjectVal = g_strdup(tmp);
+   xmlFree(tmp);
+
+   /*
+    * Find all the SubjectConfirmation nodes and see if at least one
+    * can be validated.
+    */
+   // XXX cloning xml-security-c here by using a loop.  Might
+   // be cleaner to use FindAllNodeByName()
+   for (child = subjNode->children; child != NULL; child = child->next) {
+      xmlChar *method;
+      xmlNodePtr subjConfirmData;
+
+      if (child->type == XML_ELEMENT_NODE) {
+         if (!xmlStrEqual(child->name, "SubjectConfirmation")) {
+            continue;
+         }
+         method = FindAttrValue(child, "Method");
+         if ((NULL == method) || (0 == *method)) {
+            // should not happen since this is required
+            g_warning("%s: Missing SubjectConfirmation method\n", __FUNCTION__);
+            xmlFree(method);
+            goto done;
+         }
+         if (!xmlStrEqual(method, "urn:oasis:names:tc:SAML:2.0:cm:bearer")) {
+            g_warning("%s: method %s not bearer\n", __FUNCTION__, method);
+            xmlFree(method);
+            continue;
+         }
+         xmlFree(method);
+
+         subjConfirmData = FindNodeByName(child, "SubjectConfirmationData");
+         if (NULL != subjConfirmData) {
+            xmlChar *recipient;
+
+            if (!CheckTimeAttr(subjConfirmData, "NotBefore", TRUE) ||
+                !CheckTimeAttr(subjConfirmData, "NotOnOrAfter", FALSE)) {
+               g_warning("%s: subjConfirmData time check failed\n",
+                         __FUNCTION__);
+               continue;
+            }
+
+            /*
+             * Recipient isn't always there.
+             */
+            recipient = FindAttrValue(subjConfirmData, "Recipient");
+            if ((NULL != recipient) && (0 != *recipient) &&
+                !CheckAudience(recipient)) {
+               g_debug("%s: failed recipient check\n", __FUNCTION__);
+               xmlFree(recipient);
+               continue;
+            }
+            xmlFree(recipient);
+         }
+
+         /*
+          * passed all the checks, we have a match so kick out
+          */
+         validSubjectFound = TRUE;
+         break;
+      }
+   }
+
+   if (validSubjectFound && (NULL != subjectRet)) {
+      *subjectRet = subjectVal;
+   } else {
+      g_free(subjectVal);
+   }
+   retCode = validSubjectFound;
+done:
+   return retCode;
+}
+
+
+/*
+ ******************************************************************************
+ * VerifyConditions --                                                   */ /**
+ *
+ * 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 parsed SAML token.
+ *
+ * @return TRUE if the conditions are met; FALSE otherwise.
+ *
+ ******************************************************************************
+ */
+
+gboolean
+VerifyConditions(xmlDocPtr doc)
+{
+   xmlNodePtr condNode;
+
+   /*
+    * There should be at most one Conditions element and the schema checking
+    * done by the parser should enforce that.
+    */
+   condNode = FindNodeByName(xmlDocGetRootElement(doc), "Conditions");
+   if (NULL == condNode) {
+      // Conditions are optional.
+      g_debug("%s: No Conditions found, accepting\n", __FUNCTION__);
+      return TRUE;
+   }
+
+   if (!CheckTimeAttr(condNode, "NotBefore", TRUE) ||
+       !CheckTimeAttr(condNode, "NotOnOrAfter", FALSE)) {
+      g_warning("%s: Time Conditions failed!\n", __FUNCTION__);
+      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.
+    */
+   if (FindNodeByName(condNode, "Condition") != NULL) {
+      g_warning("%s: Unrecognized condition found!\n", __FUNCTION__);
+      return FALSE;
+   }
+
+   /*
+    * <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.
+    *
+    * Our SSO server doesn't set it, so no point in checking it.
+    */
+
+#if 0
+   // XXX nothing looks at this
+   /*
+    * <OneTimeUse> element is specified to disallow caching. We don't
+    * cache, so it doesn't affect our validation.
+    * However, we need to communicate it to clients so they do not cache.
+    */
+   oneTimeUse = (FindChildByName(condNode, "OneTimeUse")
+                       != NULL);
+#endif
+
+   /*
+    * <ProxyRestriction> only applies if a service wants to make their own
+    * assertions based on a SAML assertion. That should not apply here.
+    */
+
+   return TRUE;
+}
+
+
+/*
+ ******************************************************************************
+ * VerifyReference --                                                    */ /**
+ *
+ * Verifies that the Reference in an XML signtaure is valid.
+ *
+ * XXX This is done in the xml-security-c version, but doesn't seem
+ * to be necessary in xmlsec1, since it appears to do it itself.
+ * xmlsec1 also puts the interesting data off in a semi-hidden list,
+ * suggesting its not useful to callers.
+ *
+ * @param[in] doc         Parsed XML document.
+ * @param[in] dsigCtx     Digitial signature context.
+ *
+ * @return TRUE on sucecss.
+ *
+ ******************************************************************************
+ */
+
+static gboolean
+VerifyReference(xmlDocPtr doc,
+                xmlSecDSigCtxPtr dsigCtx)
+{
+   xmlNodePtr root;
+   xmlChar *idAttr;
+   gboolean retCode = FALSE;
+
+   root = xmlDocGetRootElement(doc);
+   idAttr = xmlGetProp(root, "ID");
+
+   if (NULL == idAttr) {
+      g_warning("No ID attribute found\n");
+      goto done;
+   }
+
+   // XXX if this needs to be done, then the place to look is
+   // the signedInfoReferences in the dsigCtx.
+   // this appears to be filled in after the xmlSecDSigCtxVerify()
+   // (unlike in xml-security-c)
+
+   retCode = TRUE;
+done:
+   if (NULL != idAttr) {
+      xmlFree(idAttr);
+   }
+   return retCode;
+}
+
+
+/*
+ ******************************************************************************
+ * BuildCertChain --                                                     */ /**
+ *
+ * Pulls the certs out of the parsed SAML token, adds them to the
+ * key manager, and returns them as a list.
+ *
+ * @param[in]  x509Node     x509 data node.
+ * @param[in]  mgr          KeyManager
+ * @param[out] numCerts     Number of certs being returned.
+ * @param[out] certChain    Array containing the certs in OpenSSL PEM
+ *                          format.  Array and contents must be g_free()d
+ *                          by caller.
+ *
+ * @return TRUE on success.
+ *
+ ******************************************************************************
+ */
+
+static gboolean
+BuildCertChain(xmlNodePtr x509Node,
+               xmlSecKeysMngrPtr mgr,
+               int *numCerts,
+               gchar ***certChain)
+{
+   gboolean bRet = FALSE;
+   xmlNodePtr *x509CertNodes = NULL;
+   int num;
+   int i;
+   int ret;
+   gchar **certList = NULL;
+
+   num = FindAllNodesByName(x509Node,
+                            (char *) xmlSecNodeX509Certificate,
+                            &x509CertNodes);
+   if (num == 0) {
+      g_warning("Missing x509 certificate node(s)\n");
+      goto done;
+   }
+
+   certList = g_malloc0_n(num + 1, sizeof(gchar *));
+
+   for (i = 0; i < num; i++) {
+      gchar *pemCert = NULL;
+      xmlChar *base64Cert;
+
+      base64Cert = xmlNodeGetContent(x509CertNodes[i]);
+      if (NULL == base64Cert) {
+         g_warning("Missing x509 certificate base64 data\n");
+         goto done;
+      }
+
+      /*
+       * Turn the raw base64 into PEM.  Thanks for being so anal,
+       * OpenSSL.
+       */
+
+      // XXX make this use CertVerify_EncodePEMForSSL() instead,
+      // since it handles corner cases (the newline after the
+      // base64 may exist).  But that means changes to more files,
+      // so do this later
+      pemCert = g_strdup_printf("-----BEGIN CERTIFICATE-----\n"
+                                "%s"
+                                "\n-----END CERTIFICATE-----\n",
+                                base64Cert);
+
+      xmlFree(base64Cert);
+
+      /*
+       * Add cert to the keymanager.
+       */
+      ret = xmlSecCryptoAppKeysMngrCertLoadMemory(mgr,
+                                                  pemCert,
+                                                  strlen(pemCert),
+                                                  xmlSecKeyDataFormatPem,
+                                                  xmlSecKeyDataTypeTrusted);
+      if (ret < 0) {
+         g_warning("Failed to add cert to key manager\n");
+         goto done;
+      }
+
+      // add pemCert to the returned list
+      certList[i] = pemCert;
+   }
+
+   bRet = TRUE;
+   *numCerts = num;
+   *certChain = certList;
+
+done:
+   if (!bRet) {
+      FreeCertArray(num, certList);
+   }
+   g_free(x509CertNodes);
+
+   return bRet;
+}
+
+
+/*
+ ******************************************************************************
+ * VerifySignature --                                                    */ /**
+ *
+ * Verifies the signature on an XML document.
+ *
+ * @param[in]  doc       Parsed XML document.
+ * @param[out] numCerts  Number of certs in the token.
+ * @param[out] certChain Certs in the token. Caller should g_free() array and
+ *                       contents.
+ *
+ * @return TRUE on success.
+ *
+ ******************************************************************************
+ */
+
+gboolean
+VerifySignature(xmlDocPtr doc,
+                int *numCerts,
+                gchar ***certChain)
+{
+   xmlNodePtr dsigNode;
+   xmlNodePtr keyInfoNode;
+   xmlNodePtr x509Node;
+   xmlSecDSigCtxPtr dsigCtx = NULL;
+   xmlSecKeysMngrPtr mgr = NULL;
+   int ret;
+   int num = 0;
+   gchar **certList = NULL;
+   gboolean bRet;
+   gboolean retCode = FALSE;
+
+   *numCerts = 0;
+   *certChain = NULL;
+
+   /*
+    * First pull out the signature to get to the x509 cert.
+    */
+   dsigNode = xmlSecFindNode(xmlDocGetRootElement(doc),
+                             xmlSecNodeSignature, xmlSecDSigNs);
+   if (NULL == dsigNode) {
+      g_warning("Missing signature node\n");
+      goto done;
+   }
+
+   keyInfoNode = xmlSecFindNode(dsigNode, xmlSecNodeKeyInfo,
+                                xmlSecDSigNs);
+   if (NULL == keyInfoNode) {
+      g_warning("Missing KeyInfo node\n");
+      goto done;
+   }
+
+   x509Node = xmlSecFindNode(keyInfoNode, xmlSecNodeX509Data,
+                             xmlSecDSigNs);
+   if (NULL == x509Node) {
+      g_warning("Missing x509 node\n");
+      goto done;
+   }
+
+   /*
+    * Make a key manager to hold the certs.
+    */
+   mgr = xmlSecKeysMngrCreate();
+   if (mgr == NULL) {
+      g_warning("Failed to create key manager");
+      goto done;
+   }
+
+   ret = xmlSecCryptoAppDefaultKeysMngrInit(mgr);
+   if (ret < 0) {
+      g_warning("Failed to init key manager\n");
+      goto done;
+   }
+
+
+   /*
+    * Get the cert chain from the token.
+    *
+    * 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.
+    */
+   bRet = BuildCertChain(x509Node, mgr, &num, &certList);
+   if (FALSE == bRet) {
+      g_warning("Failed to add cert to key manager\n");
+      goto done;
+   }
+
+   /*
+    * Create a signature context with the key manager
+    */
+   dsigCtx = xmlSecDSigCtxCreate(mgr);
+   if (NULL == dsigCtx) {
+      g_warning("Missing signature node\n");
+      goto done;
+   }
+
+   /*
+    * The vgauth service code expects the id to be "ID".  xmlSec
+    * won't handle the URI ref in the signature unless we
+    *
+    * a) use 'xml:id' (the default) instead of "ID"
+    * or
+    * b) register the ID
+    *
+    * We can't control what the SSO server does, so its "b".
+    */
+   bRet = RegisterID(xmlDocGetRootElement(doc), "ID");
+   if (bRet == FALSE) {
+      g_warning("failed to register ID\n");
+      goto done;
+   }
+
+   /*
+    * Verify signature.  This just returns if the signature code worked
+    * or not, not if the signature is correct.
+    */
+   ret = xmlSecDSigCtxVerify(dsigCtx, dsigNode);
+   if (ret < 0) {
+      g_warning("Signature verify failed\n");
+      goto done;
+   }
+
+   // verify the Reference
+   //
+   // XXX these don't seem to exist until after the Verify.
+   // The one we want is in the list of dsigCtx->signedInfoReferences
+   //
+   // XXX is this even necessary, or has it already been handled
+   // by the Verify?
+   //
+   // the xml-security-c version does this before the verify,
+   // but the data needed isn't accessible in xmlsec1 until after
+   // the verify.
+   //
+   bRet = VerifyReference(doc, dsigCtx);
+   if (FALSE == bRet) {
+      g_warning("Failed to verify Reference\n");
+      goto done;
+   }
+
+   /*
+    * Check status to verify the signature is correct.
+    *
+    */
+   if (dsigCtx->status != xmlSecDSigStatusSucceeded) {
+      g_warning("Signature is INVALID\n");
+      goto done;
+   }
+
+   retCode = TRUE;
+   *numCerts = num;
+   *certChain = certList;
+done:
+   if (!retCode) {
+      FreeCertArray(num, certList);
+   }
+   if (dsigCtx) {
+      xmlSecDSigCtxDestroy(dsigCtx);
+   }
+   if (mgr) {
+      xmlSecKeysMngrDestroy(mgr);
+   }
+
+   return retCode;
+}
+
+
+/*
+ ******************************************************************************
+ * VerifySAMLToken --                                                    */ /**
+ *
+ * Verifies a XML text as a SAML token.
+ * Parses the XML, then verifies Subject, Conditions and Signature.
+ *
+ * @param[in]  token     Text of SAML token.
+ * @param[out] subject   Subject of SAML token,  Caller must g_free().
+ * @param[out] numCerts  Number of certs in the token.
+ * @param[out] certChain Certs in the token. Caller should g_free() array and
+ *                       contents.
+ *
+ * @return matching TRUE on success.
+ *
+ ******************************************************************************
+ */
+
+static gboolean
+VerifySAMLToken(const gchar *token,
+                gchar **subject,
+                int *numCerts,
+                gchar ***certChain)
+{
+   xmlDocPtr doc = NULL;
+   int retCode = FALSE;
+   gboolean bRet;
+   /*
+    * If we want to set extra options, use this path.
+    */
+#if PARSE_WITH_OPTIONS
+   xmlParserCtxtPtr parseCtx = NULL;
+
+   parseCtx = xmlCreateDocParserCtxt(token);
+
+   /*
+    * Don't allow extra stuff to be pulled off the net.
+    * The schema validation should prevent this from getting
+    * through, but it might still be nice to prevent network issues
+    * from slowing things down.
+    */
+   xmlCtxtUseOptions(parseCtx, XML_PARSE_NONET);
+   doc = xmlCtxtReadMemory(parseCtx,
+                           token,
+                           strlen(token),
+                           NULL, NULL, 0);
+#else
+   doc = xmlParseMemory(token, strlen(token));
+#endif
+   if ((NULL == doc) || (xmlDocGetRootElement(doc) == NULL)) {
+      g_warning("Failed to parse document\n");
+      goto done;
+   }
+
+   bRet = ValidateDoc(doc);
+   if (FALSE == bRet) {
+      g_warning("Failed to validate token against schema\n");
+      goto done;
+   }
+
+   bRet = VerifySubject(doc, subject);
+   if (FALSE == bRet) {
+      g_warning("Failed to verify Subject node\n");
+      goto done;
+   }
+
+   bRet = VerifyConditions(doc);
+   if (FALSE == bRet) {
+      g_warning("Failed to verify Conditions\n");
+      goto done;
+   }
+
+   bRet = VerifySignature(doc, numCerts, certChain);
+   if (FALSE == bRet) {
+      g_warning("Failed to verify Signature\n");
+      goto done;
+   }
+
+   retCode = TRUE;
+done:
+#if PARSE_WITH_OPTIONS
+   if (NULL != parseCtx) {
+      xmlFreeParserCtxt(parseCtx);
+   }
+#endif
+   if (!retCode && (NULL != subject)) {
+      g_free(*subject);
+      *subject = NULL;
+   }
+   if (doc) {
+      xmlFreeDoc(doc);
+   }
+
+   return retCode;
+}
+
+
+/*
+ ******************************************************************************
+ * 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.  Caller must g_free().
+ * @param[out] verifyAi    The alias info associated with the entry
+ *                         in the alias 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,                // UNUSED
+                       char **userNameOut,                  // UNUSED
+                       char **subjNameOut,
+                       ServiceAliasInfo **verifyAi)         // UNUSED
+{
+   gboolean ret;
+   gchar **certChain = NULL;
+   int num = 0;
+
+   ret = VerifySAMLToken(xmlText,
+                         subjNameOut,
+                         &num,
+                         &certChain);
+
+   // clean up -- this code doesn't look at the chain
+   FreeCertArray(num, certChain);
+
+   // XXX errors may need some work
+   return (ret == TRUE) ? VGAUTH_E_OK : VGAUTH_E_AUTHENTICATION_DENIED;
+}
+
+
+/*
+ ******************************************************************************
+ * 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.  Caller must g_free().
+ * @param[out] verifyAi    The alias info associated with the entry
+ *                         in the alias 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)
+{
+   VGAuthError err;
+   gboolean bRet;
+   int num;
+   gchar **certChain = NULL;
+   ServiceSubject subj;
+
+   *userNameOut = NULL;
+   *subjNameOut = NULL;
+   *verifyAi = NULL;
+
+   bRet = VerifySAMLToken(xmlText,
+                          subjNameOut,
+                          &num,
+                          &certChain);
+
+   if (FALSE == bRet) {
+      return VGAUTH_E_AUTHENTICATION_DENIED;
+   }
+
+   subj.type = SUBJECT_TYPE_NAMED;
+   subj.name = *subjNameOut;
+   err = ServiceVerifyAndCheckTrustCertChainForSubject(num,
+                                                       (const char **) certChain,
+                                                       userName,
+                                                       &subj,
+                                                       userNameOut,
+                                                       verifyAi);
+   g_debug("%s: ServiceVerifyAndCheckTrustCertChainForSubject() "
+           "returned "VGAUTHERR_FMT64"\n", __FUNCTION__, err);
+   FreeCertArray(num, certChain);
+
+   return err;
+}
+