/*
 * Copyright (c) 2003-2012
 * Distributed Systems Software.  All rights reserved.
 * See the file LICENSE for redistribution information.
 */

/*
 * Lower level InfoCard support
 *
 * This implementation depends on support from libxml2 (the XML toolkit
 * from the GNOME project) and xmlsec1 (the XMLSec Library); the latter
 * is configured to use OpenSSL.
 * http://xmlsoft.org
 * http://www.aleksey.com/xmlsec
 */

/*
 * Portions of this code are based on InformationCard-C (1.0.1)
 * by Ping Identity (http://www.codeplex.com/InformationCard).
 *
 * [The following notice also applies to the data files
 * oasis-sstc-saml-schema-assertion-1.1.h and xmldsig-core-schema.h]
 *
 * Copyright (c) 2007, Ping Identity
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *    * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    * Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *    * Neither the name of Ping Identity, nor the names of its contributors
 *      may be used to endorse or promote products derived from this software
 *      without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY PING IDENTITY ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
 * NO EVENT SHALL PING IDENTITY BE LIABLE FOR ANY DIRECT, INDIRECT, 
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * InfoCard SAML token validation and data extraction.
 * Having received a SAML token (an XML document) from an Identity Selector,
 * a Replying Party can use this code to check the validity of the token
 * (subject to standard and configurable constraints), and then extract
 * various fields (primarily, attributes called "claims") for use by
 * applications.
 *
 * XML Signature:  http://www.w3.org/TR/2002/REC-xmldsig-core-20020212/
 * Canonical XML:  http://www.w3.org/TR/xml-c14n
 * Exclusive XML Canonicalization:
 *                 http://www.w3.org/TR/2002/REC-xml-exc-c14n-20020718/
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2012\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: icx.c 2586 2012-03-15 16:21:40Z brachman $";
#endif

#include "icx.h"

#include <xmlsec/openssl/evp.h>
#include <xmlsec/openssl/x509.h>
#include <xmlsec/openssl/app.h>
#include <libxml/c14n.h>

static const char *log_module_name = "icx";

#ifndef PROG

/* Binary data for the SAML assertion schema */
#include "oasis-sstc-saml-schema-assertion-1.1.h"
#include "xmldsig-core-schema.h"

static const xmlChar *ATTR_MAJOR_VERSION =
  (xmlChar *) "MajorVersion";
static const xmlChar *ATTR_MINOR_VERSION =
  (xmlChar *) "MinorVersion";
static const xmlChar *ATTR_ISSUER =
  (xmlChar *) "Issuer";
static const xmlChar *ATTR_NOT_BEFORE =
  (xmlChar *) "NotBefore";
static const xmlChar *ATTR_NOT_ON_OR_AFTER =
  (xmlChar *) "NotOnOrAfter";
static const xmlChar *ATTR_ASSERTION_ID =
  (xmlChar *) "AssertionID";
static const xmlChar *ATTR_ATTRIBUTE_NAME =
  (xmlChar *) "AttributeName";
static const xmlChar *ATTR_ATTRIBUTE_NAMESPACE =
  (xmlChar *) "AttributeNamespace";
static const xmlChar *SELF_ATTR_ISSUER =
  (xmlChar *) "http://schemas.xmlsoap.org/ws/2005/05/identity/issuer/self";
static const xmlChar *NS_SAML_11 =
  (xmlChar *) "urn:oasis:names:tc:SAML:1.0:assertion";
static const xmlChar *NS_DSIG =
  (xmlChar *) "http://www.w3.org/2000/09/xmldsig#";
static const xmlChar *URL_DSIG =
  (xmlChar *) "http://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd";
static const xmlChar *CONDITIONS = (xmlChar *) "Conditions";
static const xmlChar *AUDIENCE_RESTRICTION_CONDITION =
  (xmlChar *) "AudienceRestrictionCondition";
static const xmlChar *DO_NOT_CACHE_CONDITION =
  (xmlChar *) "DoNotCacheCondition";
static const xmlChar *ATTRIBUTE_STATEMENT =
  (xmlChar *) "AttributeStatement";
static const xmlChar *ATTRIBUTE =
  (xmlChar *) "Attribute";
static const xmlChar *EXPONENT =
  (xmlChar *) "Exponent";
static const xmlChar *MODULUS =
  (xmlChar *) "Modulus";
static const xmlChar *PATH_EXPONENT =
  (xmlChar *) "/saml:Assertion/ds:Signature/ds:KeyInfo/ds:KeyValue/ds:RSAKeyValue/ds:Exponent";
static const xmlChar *PATH_MODULUS =
  (xmlChar *) "/saml:Assertion/ds:Signature/ds:KeyInfo/ds:KeyValue/ds:RSAKeyValue/ds:Modulus";
static const xmlChar *PATH_CONFIRMATION_METHOD =
  (xmlChar *) "/saml:Assertion/saml:AttributeStatement/saml:Subject/saml:SubjectConfirmation/saml:ConfirmationMethod";
static const xmlChar *PATH_SUBJECT_X509CERT =
  (xmlChar *) "/saml:Assertion/saml:AttributeStatement/saml:Subject/saml:SubjectConfirmation/ds:KeyInfo/ds:X509Data/ds:X509Certificate";
static const xmlChar *PATH_KEYINFO_CERT =
  (xmlChar *) "/saml:Assertion/ds:Signature/ds:KeyInfo/ds:X509Data/ds:X509Certificate";
static const xmlChar *PPID =
  (xmlChar *) "privatepersonalidentifier";
static const xmlChar *PRE_SAML =
  (xmlChar *) "saml";
static const xmlChar *PRE_DS =
  (xmlChar *) "ds";

static int decrypt_doc(Icx_ctx *ctx, xmlDocPtr encr_doc);
static int evaluate_saml11_child_conditions(Icx_ctx *ctx,
											xmlNodePtr conditions);
static int evaluate_saml11_replay(Icx_ctx *ctx, xmlDocPtr token_doc);
static int evaluate_saml11_time_conditions(Icx_ctx *ctx, xmlNodePtr conditions);
static int extract_saml11_attributes(Icx_ctx *ctx, xmlDocPtr token_doc);
static int extract_saml11_info(Icx_ctx *ctx, xmlDocPtr token_doc);
static int validate_saml11_conditions(Icx_ctx *ctx, xmlDocPtr token_doc);
static int validate_saml11_self_issued_assertion(Icx_ctx *ctx,
												 xmlDocPtr token_doc);
static int validate_saml11_signature(Icx_ctx *ctx, xmlDocPtr token_doc);
static int validate_saml11_message(Icx_ctx *ctx, xmlDocPtr token_doc);

static xmlParserInputPtr
xmlInternalSchemaEntityLoader(const char *URL, const char *ID, 
							  xmlParserCtxtPtr context)
{

  if (streq(URL, (const char *) NS_SAML_11))
	return(xmlNewStringInputStream(context,
								   oasis_sstc_saml_schema_assertion_1_1));
  else if (streq(URL, (const char *) URL_DSIG)
		   || streq(URL, (const char *) NS_DSIG))
	return(xmlNewStringInputStream(context, xmldsig_core_schema));

  return(NULL);
}

/*
 * Convert a base-64 encoded PPID into a "friendly identifier", as per
 * Identity Selector Interoperability Profile V1.5, (July 2008), Section 8.6.3
 */
Ds *
icx_friendly_identifier(Ds *xppid, int do_decode)
{
  int i;
  unsigned int olen;
  unsigned char *hash_id;
  Ds *fid, *ppid;
  static char *enc_tab = "QL23456789ABCDEFGHJKMNPRSTUVWXYZ";

  if (do_decode) {
	long dec_len;
	unsigned char *dec_ppid;

	if ((dec_len = mime_decode_base64(ds_buf(xppid), &dec_ppid)) == -1)
	  return(NULL);
	ppid = ds_setn(NULL, dec_ppid, dec_len);
  }
  else {
	if (xppid == NULL) {
	  char *s;

	  /* In case someone needs a random identifier in this format... */
	  s = crypto_make_random_string(NULL, 20);
	  ppid = ds_set(NULL, s);
	}
	else
	  ppid = xppid;
  }

  hash_id = crypto_digest("SHA1", ds_buf(ppid), ds_len(ppid), NULL, &olen);
  fid = ds_init(NULL);
  for (i = 0; i < 10; i++) {
	unsigned int raw_value;

	raw_value = hash_id[i] % 32;
	if (i == 3 || i == 7)
	  ds_appendc(fid, (int) '-');
	ds_appendc(fid, (int) enc_tab[raw_value]);
  }
  ds_appendc(fid, (int) '\0');
 
  return(fid);
}

/*
 * INFOCARD_CARD_DEFS_URL is used by dacs_managed_infocard to configure
 * the claims with which to populate a new managed InfoCard.
 * Think of the output of this as the schema for the information "contained"
 * within some managed InfoCard - the schema can be different for different
 * users, different jurisdictions, or even for different instances of a card.
 * Note that it does not provide the values for any claims; the corresponding
 * value for any claim defined (advertised, in a sense) by
 * INFOCARD_CARD_DEFS_URL must be provided by
 * INFOCARD_CARD_FILL_URL.  These two services could be implemented by
 * the same web service.
 *
 * Method: POST
 * Arguments:
 *   DACS_FEDERATION, DACS_JURISDICTION, DACS_IDENTITY, CARD_ID, PPID,
 *   and any other query args attached to INFOCARD_CARD_DEFS_URL
 * Returns an XML document:
 * <!ELEMENT card_defs (claim_def)*>
 * <!ATTLIST claim_defs
 *   card_name    CDATA   #IMPLIED
 *   card_error   CDATA   #IMPLIED
 * >
 * <!ELEMENT claim_def EMPTY>
 * <!ATTLIST claim_def
 *   type         CDATA   #REQUIRED
 *   name         CDATA   #REQUIRED
 *   label        CDATA   #REQUIRED
 *   description  CDATA   #REQUIRED
 * >
 *
 * Example:
 * <?xml version="1.0"?>
 * <card_defs card_version="1">
 * <claim_def type="<TYPE>" name="<NAME>" label="<LABEL>" description="<DESC>"/>
 *  ...
 * </card_defs>
 * where: <TYPE> is the word "standard" or "dacs", or a URI (the URI is not
 *        dereferenced); this establishes a namespace for <NAME> - "standard"
 *        and "dacs" are just short, convenient identifiers for URIs
 *        <NAME> is a unique (relative to <TYPE>) name for this claim
 *        consisting of alphanumerics and underscores (not beginning with a
 *        digit)
 *        <LABEL> is a short (fewer than 15 chars) descriptive blurb that the
 *        Identity Selector will display to identify the claim
 *        (the ic:DisplayTag element in the InfoCard)
 *        <DESC> is a longer descriptive blurb
 *        (the ic:Description element in the InfoCard)
 * If this directive is undefined, a single default claim will be used:
 *   <claim type="dacs" name="dacs_identity" label="DACS Identity"
 *     description="A DACS identity" />
 * If this directive is defined but cannot be dereferenced, InfoCard creation
 * will be aborted.
 *
 * INFOCARD_CARD_FILL_URL is used by dacs_sts to obtain claim values
 * requested by a Relying Party.
 * Arguments:
 *  DACS_FEDERATION, DACS_JURISDICTION, CARD_ID, CARD_VERSION,
 *  CLAIMS (a space-separated list of one or more claim identifiers, each of
 *  the form <TYPE>:<NAME>), and any other query arguments attached to
 *  INFOCARD_CARD_FILL_URL
 * Returns an XML document:
 * <!ELEMENT claim_values (claim_value)*>
 * <!ATTLIST claim_values
 *   card_error   CDATA   #IMPLIED
 * >
 * <!ELEMENT claim_value EMPTY>
 * <!ATTLIST claim_value
 *   type         CDATA   #REQUIRED
 *   name         CDATA   #REQUIRED
 *   label        CDATA   #REQUIRED
 *   value        CDATA   #REQUIRED
 * >
 * Or:
 * <claim_values>
 *  <claim_value type="<TYPE>" name="<NAME>" label="<LABEL>" value="<VALUE>"/>
 *   ...
 * </claim_values>
 * If INFOCARD_CARD_DEFS_URL is undefined, this URL will not be used
 * and the value(s) of the default claim(s) will be used.
 */

static Icx_claim icx_standard_claim_defs[] = {
  { ICX_STANDARD_CLAIM, ICX_STANDARD_CLAIM_URI, "givenname",
	"First Name", "The user's first name", NULL },
  { ICX_STANDARD_CLAIM, ICX_STANDARD_CLAIM_URI, "surname",
	"Last Name", "The user's surname", NULL },
  { ICX_STANDARD_CLAIM, ICX_STANDARD_CLAIM_URI, "emailaddress",
	"Email", "The user's email address", NULL },
  { ICX_STANDARD_CLAIM, ICX_STANDARD_CLAIM_URI, "streetaddress",
	"Street Address", "The user's street address", NULL },
  { ICX_STANDARD_CLAIM, ICX_STANDARD_CLAIM_URI, "locality",
	"Locality Name or City", "The user's city", NULL },
  { ICX_STANDARD_CLAIM, ICX_STANDARD_CLAIM_URI, "stateorprovince",
	"State or Province", "The user's state or province", NULL },
  { ICX_STANDARD_CLAIM, ICX_STANDARD_CLAIM_URI, "postalcode",
	"Postal Code", "The user's postal code or zip code", NULL },
  { ICX_STANDARD_CLAIM, ICX_STANDARD_CLAIM_URI, "country",
	"Country", "The user's country", NULL },
  { ICX_STANDARD_CLAIM, ICX_STANDARD_CLAIM_URI, "homephone",
	"Primary or Home Telephone Number", "The user's phone number", NULL },
  { ICX_STANDARD_CLAIM, ICX_STANDARD_CLAIM_URI, "otherphone",
	"Secondary or Work Telephone Number", "The user's work number", NULL },
  { ICX_STANDARD_CLAIM, ICX_STANDARD_CLAIM_URI, "mobilephone",
	"Mobile Telephone Number", "The user's mobile phone number", NULL },
  { ICX_STANDARD_CLAIM, ICX_STANDARD_CLAIM_URI, "dateofbirth",
	"Date of Birth", "The user's date of birth", NULL },
  { ICX_STANDARD_CLAIM, ICX_STANDARD_CLAIM_URI, "gender",
	"Gender", "The user's gender", NULL },
  { ICX_STANDARD_CLAIM, ICX_STANDARD_CLAIM_URI, "privatepersonalidentifier",
	"PPID", "The user's private personal identifier", NULL },
  { ICX_STANDARD_CLAIM, ICX_STANDARD_CLAIM_URI, "webpage",
	"Web Page", "The user's web page URL", NULL },

  { ICX_UNDEF_CLAIM,  NULL, NULL,
	NULL, NULL, NULL }
};

static Icx_claim icx_default_claim_defs[] = {
  { ICX_STANDARD_CLAIM, ICX_STANDARD_CLAIM_URI, "privatepersonalidentifier",
	"PPID", "The user's private personal identifier", NULL },
  { ICX_DACS_CLAIM, ICX_DACS_CLAIM_URI, "dacs_identity",
	"DACS Identity", "A DACS identity", NULL },
  { ICX_UNDEF_CLAIM,  NULL, NULL,
	NULL, NULL, NULL }
};

#ifdef NOTDEF
static char *
claim_uri(Icx_claim *claim)
{
  char *u;

  if (claim->type == ICX_STANDARD_CLAIM)
	u = ICX_STANDARD_CLAIM_URI;
  else if (claim->type == ICX_DACS_CLAIM)
	u = ICX_DACS_CLAIM_URI;
  else if (claim->type == ICX_CUSTOM_CLAIM) {
	u = var_ns_get_value(dacs_conf->conf_var_ns, "Conf",
						 "infocard_custom_claim_uri");
	if (u == NULL)
	  u = ICX_DACS_CLAIM_URI;
  }
  else
	u = NULL;

  return(u);
}
#endif

Icx_claim *
icx_find_standard_claim(char *name)
{
  Icx_claim *claim;

  for (claim = &icx_standard_claim_defs[0]; claim->uri != NULL; claim++) {
	if (streq(claim->name, name))
	  return(claim);
  }

  return(NULL);
}

char *
xmlPathStr(xmlNodePtr node)
{
  xmlNodePtr x;
  Ds *ds;

  ds = ds_init(NULL);
  x = node;
  while (x != NULL && x->type != XML_DOCUMENT_NODE) {
	if (x->type == XML_ELEMENT_NODE)
	  ds_psprintf(ds, "/%s", x->name);
	x = x->parent;
  }

  return(ds_buf(ds));
}

/*
 * Return attribute N, or NULL.
 * Not as efficient as possible, but simple.
 * May not work as expected if the document is updated between calls.
 */
xmlAttr *
xmlGetAttrN(xmlNodePtr el, unsigned int n)
{
  int i;
  xmlAttr *attr;

  i = 0;
  for (attr = el->properties; attr != NULL; attr = attr->next) {
	if (attr->type == XML_ATTRIBUTE_NODE) {
	  if (i++ == n)
		return(attr);
	}
  }

  return(NULL);
}

int
xmlGetAttrPairN(xmlNodePtr el, unsigned int n, xmlChar **attr_name,
				xmlChar **attr_value)
{
  xmlAttr *attr;

  if ((attr = xmlGetAttrN(el, n)) == NULL)
	return(-1);

  if (attr_name != NULL)
	*attr_name = xmlStrdup(attr->name);

  if (attr_value != NULL) {
	if (attr->children == NULL || attr->children->type != XML_TEXT_NODE)
	  return(-1);

	*attr_value = xmlStrdup(attr->children->content);
  }

  return(0);
}

/*
 * Return the first attribute named ATTR_NAME, or NULL.
 */
xmlAttr *
xmlGetAttr(xmlNodePtr el, xmlChar *attr_name)
{
  xmlAttr *attr;

  for (attr = el->properties; attr != NULL; attr = attr->next) {
	if (attr->type == XML_ATTRIBUTE_NODE && xmlStreq(attr_name, attr->name))
	  return(attr);
  }

  return(NULL);
}

/*
 * Return the value of the first attribute named ATTR_NAME, or NULL.
 */
char *
xmlGetAttrValue(xmlNodePtr el, xmlChar *attr_name)
{
  xmlAttr *attr;

  if ((attr = xmlGetAttr(el, attr_name)) == NULL)
	return(NULL);

  if (attr->children == NULL || attr->children->type != XML_TEXT_NODE)
	return(NULL);

  return((char *) attr->children->content);
}

/*
 * Return the first immediate child of EL named EL_NAME.
 */
xmlNodePtr
xmlGetChild(xmlNodePtr el, xmlChar *el_name)
{
  xmlNodePtr child;

  for (child = el->children; child != NULL; child = child->next) {
	if (xmlStreq(child->name, el_name))
	  return(child);
  }

  return(NULL);
}

/*
 * Return the text content of the first immediate child of EL named EL_NAME.
 */
char *
xmlGetChildText(xmlNodePtr el, xmlChar *el_name)
{
  xmlNodePtr child, val;

  for (child = el->children; child != NULL; child = child->next) {
	if (xmlStreq(child->name, el_name)) {
	  if ((val = child->children) == NULL
		  || val->type != XML_TEXT_NODE || val->next != NULL
		  || val->content == NULL)
		return(NULL);
	  return((char *) val->content);
	}
  }

  return(NULL);
}

Icx_claim *
icx_new_claim(void)
{
  Icx_claim *claim;

  claim = ALLOC(Icx_claim);
  claim->type = ICX_UNDEF_CLAIM;
  claim->uri = NULL;
  claim->name = NULL;
  claim->label = NULL;
  claim->description = NULL;
  claim->value = NULL;

  return(claim);
}

Icx_claim_type
icx_lookup_claim_type(char *uri)
{
  Icx_claim_type type;

  if (streq(uri, ICX_STANDARD_CLAIM_URI))
	type = ICX_STANDARD_CLAIM;
  else if (streq(uri, ICX_DACS_CLAIM_URI))
	type = ICX_DACS_CLAIM;
  else
	type = ICX_USER_CLAIM;

  return(type);
}

/*
 * Find the claim definition uniquely identified by URI and NAME in the
 * list CLAIM_DEFS, or return NULL.
 */
Icx_claim *
icx_lookup_claim(Dsvec *claims, char *uri, char *name)
{
  int i;
  Icx_claim *claim;

  for (i = 0; i < dsvec_len(claims); i++) {
	claim = (Icx_claim *) dsvec_ptr_index(claims, i);

	if (streq(uri, claim->uri) && streq(name, claim->name))
	  return(claim);
  }

  return(NULL);
}

static void
add_query_arg(Dsvec *args, char *name, char *value)
{
  Uri_query_arg *arg;

  arg = ALLOC(Uri_query_arg);
  arg->name = name;
  arg->value = value;
  dsvec_add_ptr(args, arg);
}

/*
 * If INFOCARD_CARD_DEFS_URL is configured, fetch the claim definitions,
 * parse and validate them, and return the list; return NULL if an error
 * occurs.
 * If it's not defined, return the default claim definitions.
 */
int
icx_get_card_defs(char *identity, Icx_card_defs *cd,
				  char *federation_name, char *jurisdiction_name)
{
  char *url;
  Dsvec *more_args;

  if (identity == NULL || federation_name == NULL || jurisdiction_name == NULL)
	return(-1);

  if ((url = conf_val(CONF_INFOCARD_CARD_DEFS_URL)) != NULL) {
	int nclaims, reply_len, status_code;
	char *cookies[2], *reply;
	xmlDocPtr doc;
	xmlNodePtr root, child;
	xmlParserCtxtPtr parser_ctx;
	Credentials *admin_cr;
	Icx_claim *claim, *ppid_claim;

	admin_cr = make_admin_credentials();
	credentials_to_auth_cookies(admin_cr, &cookies[0]);
	cookies[1] = NULL;

	reply_len = -1;
	log_msg((LOG_DEBUG_LEVEL, "Retrieving card definitions from %s", url));
	more_args = dsvec_init(NULL, sizeof(Uri_query_arg *));
	add_query_arg(more_args, "DACS_IDENTITY", identity);
	add_query_arg(more_args, "CARD_ID", cd->card_id);
	add_query_arg(more_args, "CARD_VERSION", cd->card_version);
	add_query_arg(more_args, "DACS_FEDERATION", federation_name);
	add_query_arg(more_args, "DACS_JURISDICTION", jurisdiction_name);

	if (http_post(url, more_args, cookies, &reply, &reply_len, &status_code,
				  NULL) == -1 || status_code != 200 || reply_len <= 0) {
	  log_msg((LOG_ERROR_LEVEL, "Could not invoke %s: %d", url, status_code));
	  return(-1);
	}

	log_msg((LOG_TRACE_LEVEL, "Reply:\n%s", reply));

	if ((parser_ctx = xmlNewParserCtxt()) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Unable to initialize parser context"));
	  return(-1);
	}

	doc = xmlCtxtReadMemory(parser_ctx, (const char *) reply, reply_len,
							NULL, NULL, 0);
	if (doc == NULL) {
	  xmlErrorPtr err;

	  err = xmlCtxtGetLastError(parser_ctx);
	  log_msg((LOG_ERROR_LEVEL, "Token parse failed, non-well-formed XML: %s",
			   (err != NULL) ? err->message : "null"));
	  return(-1);
	}

	root = xmlDocGetRootElement(doc);
	if (root->type != XML_ELEMENT_NODE
		|| !xmlStreq(root->name, (xmlChar *) "card_defs")
		|| root->next != NULL)
	  return(-1);

	if (root->properties != NULL) {
	  char *errmsg;

	  /* XXX unused attributes are ignored */
	  if ((errmsg = xmlGetAttrValue(root, (xmlChar *) "card_error")) != NULL) {
		log_msg((LOG_ERROR_LEVEL,
				 "Error retrieving card definitions: %s", errmsg));
		return(-1);
	  }
	  cd->card_name = xmlGetAttrValue(root, (xmlChar *) "card_name");
	}

	cd->claims = dsvec_init(NULL, sizeof(Icx_claim *));

	/* The PPID is handled internally. */
	claim = icx_new_claim();
	ppid_claim = icx_find_standard_claim((char *) PPID);
	*claim = *ppid_claim;
	dsvec_add_ptr(cd->claims, claim);

	nclaims = 0;
	for (child = root->children; child != NULL; child = child->next) {
	  char *u;

	  if (child->type != XML_ELEMENT_NODE
		  || !xmlStreq(child->name, (xmlChar *) "claim_def"))
		continue;
	  
	  if ((u = (char *) xmlGetAttrValue(child, (xmlChar *) "type")) == NULL)
		return(-1);

	  if (++nclaims == MIC_MAX_DYNAMIC_CLAIMS) {
		log_msg((LOG_ERROR_LEVEL, "Too many claims (max=%d)",
				 MIC_MAX_DYNAMIC_CLAIMS));
		return(-1);
	  }

	  /* XXX any unused attribute is ignored */
	  claim = icx_new_claim();

	  if (streq(u, "dacs")) {
		claim->type = ICX_DACS_CLAIM;
		claim->uri = ICX_DACS_CLAIM_URI;
	  }
	  else if (streq(u, "standard")) {
		claim->type = ICX_STANDARD_CLAIM;
		claim->uri = ICX_STANDARD_CLAIM_URI;
	  }
	  else {
		if (strlen(u) > MIC_MAX_DYNAMIC_URI_CLAIM_SIZE) {
		  log_msg((LOG_ERROR_LEVEL, "URI is too long: \"%s\"", u));
		  return(-1);
		}
		claim->type = ICX_USER_CLAIM;
		claim->uri = u;
	  }

	  if ((claim->name = (char *) xmlGetAttrValue(child, (xmlChar *) "name"))
		  == NULL)
		return(-1);
	  if (strlen(claim->name) > MIC_MAX_DYNAMIC_NAME_CLAIM_SIZE) {
		log_msg((LOG_ERROR_LEVEL, "Name is too long: \"%s\"", claim->name));
		return(-1);
	  }
		
	  if ((claim->label = (char *) xmlGetAttrValue(child, (xmlChar *) "label"))
		  == NULL)
		return(-1);
	  if (strlen(claim->label) > MIC_MAX_DYNAMIC_LABEL_CLAIM_SIZE) {
		log_msg((LOG_ERROR_LEVEL, "Label is too long: \"%s\"", claim->label));
		return(-1);
	  }
		
	  if ((claim->description
		   = (char *) xmlGetAttrValue(child, (xmlChar *) "description"))
		  == NULL)
		return(-1);
	  if (strlen(claim->description) > MIC_MAX_DYNAMIC_DESC_CLAIM_SIZE) {
		log_msg((LOG_ERROR_LEVEL, "Description is too long: \"%s\"",
				 claim->description));
		return(-1);
	  }

	  dsvec_add_ptr(cd->claims, claim);
	  log_msg((LOG_DEBUG_LEVEL, "Claim %d: %s/%s",
			   dsvec_len(cd->claims), claim->uri, claim->name));
	}

	if (dsvec_len(cd->claims) == 0) {
	  log_msg((LOG_ERROR_LEVEL, "No claims?"));
	  return(-1);
	}

	xmlFreeDoc(doc);
	xmlFreeParserCtxt(parser_ctx);
  }
  else {
	int i;

	log_msg((LOG_DEBUG_LEVEL, "Using default claim definitions"));
	cd->claims = dsvec_init(NULL, sizeof(Icx_claim *));
	for (i = 0; icx_default_claim_defs[i].name != NULL; i++) {
	  Icx_claim *claim;

	  claim = icx_new_claim();
	  *claim = icx_default_claim_defs[i];
	  dsvec_add_ptr(cd->claims, claim);
	}
  }

  return(0);
}

/*
 * Retrieve requested claim values (REQ_CLAIMS, a vector of pointers to
 * Icx_claim structures) for IDENTITY.
 * Return 0 if ok, -1 otherwise.
 *
 * XXX this should be capable of returning STS_REQ_CLAIMS_ERR
 */
Sts_error_type
icx_fill_claim_values(Sts_request *req, char *item_type, char *card_id,
					  char *ppid, char *federation_name,
					  char *jurisdiction_name, Dsvec *req_claims)
{
  int i;
  char *url;
  Icx_claim *claim, *ppid_claim;
  Mic_entry *mic;
  Vfs_handle *h;

  if (card_id == NULL) {
	log_msg((LOG_ERROR_LEVEL, "No CardId?"));
	return(STS_CUSTOM_ERR);
  }

  if (dsvec_len(req_claims) == 0) {
	log_msg((LOG_ERROR_LEVEL, "No claims are required?"));
	return(STS_REQ_CLAIMS_ERR);
  }

  log_msg((LOG_DEBUG_LEVEL, "Looking up CardId=\"%s\"", card_id));
  if ((h = vfs_open_item_type(item_type)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Can't open item type \"%s\"", item_type));
	return(STS_CUSTOM_ERR);
  }

  mic = mic_read(h, NULL, card_id);
  vfs_close(h);

  if (mic == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Cannot find account for CardId=\"%s\"",
			 card_id));
	return(STS_CUSTOM_ERR);
  }

  switch (mic->use_mode) {
  case IC_USE_MODE_DACS:
	url = conf_val(CONF_INFOCARD_CARD_FILL_URL);
	if (url == NULL) {
	  if (conf_val(CONF_INFOCARD_CARD_DEFS_URL) != NULL) {
		log_msg((LOG_ERROR_LEVEL, "INFOCARD_CARD_FILL_URL must be defined"));
		return(STS_CUSTOM_ERR);
	  }

	  for (i = 0; i < dsvec_len(req_claims); i++) {
		Icx_claim *claim, *rclaim;

		rclaim = (Icx_claim *) dsvec_ptr_index(req_claims, i);
		claim = icx_lookup_claim(mic->claims, rclaim->uri, rclaim->name);
		if (claim != NULL && claim->value != NULL)
		  rclaim->value = claim->value;
		else
		  rclaim->value = "No value available";
		rclaim->label = claim->label;
		rclaim->description = claim->description;
	  }
	}
	else {
	  int reply_len, status_code;
	  char *cookies[2], *reply;
	  xmlDocPtr doc;
	  xmlNodePtr root, child;
	  xmlParserCtxtPtr parser_ctx;
	  Credentials *admin_cr;
	  Ds *claim_list;
	  Dsvec *more_args;
	  Uri_query_arg *arg;

	  /* Fetch the claim definitions, parse and validate... */
	  admin_cr = make_admin_credentials();
	  credentials_to_auth_cookies(admin_cr, &cookies[0]);
	  cookies[1] = NULL;

	  reply_len = -1;
	  log_msg((LOG_DEBUG_LEVEL, "Retrieving claim definitions from %s", url));
	  more_args = dsvec_init(NULL, sizeof(Uri_query_arg *));
	  add_query_arg(more_args, "DACS_FEDERATION", federation_name);
	  add_query_arg(more_args, "DACS_JURISDICTION", jurisdiction_name);
	  add_query_arg(more_args, "CARD_ID",
					(char *) req[STS_REQUEST_CARDID].value);
	  add_query_arg(more_args, "CARD_VERSION",
					(char *) req[STS_REQUEST_CARDVERSION].value);

	  claim_list = ds_init(NULL);
	  for (i = 0; i < dsvec_len(req_claims); i++) {
		char *t;

		claim = (Icx_claim *) dsvec_ptr_index(req_claims, i);
		if (claim->type == ICX_STANDARD_CLAIM
			&& streq(claim->name, (char *) PPID)) {
		  /* The PPID is handled specially. */
		  log_msg((LOG_DEBUG_LEVEL, "PPID claim was requested"));
		  if (ppid == NULL) {
			log_msg((LOG_ERROR_LEVEL, "PPID is unavailable"));
			return(STS_CUSTOM_ERR);
		  }
		  log_msg((LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG,
				   "PPID=\"%s\"", ppid));

		  /* Fill it now, don't do it externally. */
		  ppid_claim = icx_find_standard_claim((char *) PPID);
		  if (ppid_claim == NULL)
			return(STS_CUSTOM_ERR);

		  *claim = *ppid_claim;
		  claim->value = ppid;
		  continue;
		}

		if (claim->type == ICX_STANDARD_CLAIM)
		  t = "standard";
		else if (claim->type == ICX_DACS_CLAIM)
		  t = "dacs";
		else
		  t = claim->uri;

		ds_asprintf(claim_list, "%s%s:%s",
					(ds_len(claim_list) == 0) ? "" : " ", t, claim->name);
	  }

	  if (ds_len(claim_list) == 0) {
		log_msg((LOG_DEBUG_LEVEL, "No claims need to be filled externally"));
		return(0);
	  }

	  log_msg((LOG_TRACE_LEVEL, "claim_list=\"%s\"", ds_buf(claim_list)));

	  arg = ALLOC(Uri_query_arg);
	  arg->name = "CLAIMS";
	  arg->value = ds_buf(claim_list);
	  dsvec_add_ptr(more_args, arg);

	  if (http_post(url, more_args, cookies, &reply, &reply_len, &status_code,
					NULL) == -1 || status_code != 200 || reply_len <= 0) {
		log_msg((LOG_ERROR_LEVEL, "Could not invoke %s: %d", url, status_code));
		return(STS_REQ_CLAIMS_ERR);
	  }
	  log_msg((LOG_TRACE_LEVEL, "Reply:\n%s", reply));

	  if ((parser_ctx = xmlNewParserCtxt()) == NULL) {
		log_msg((LOG_ERROR_LEVEL, "Unable to initialize parser context"));
		return(STS_CUSTOM_ERR);
	  }

	  doc = xmlCtxtReadMemory(parser_ctx, (const char *) reply, reply_len,
							  NULL, NULL, 0);
	  if (doc == NULL) {
		xmlErrorPtr err;

		err = xmlCtxtGetLastError(parser_ctx);
		log_msg((LOG_ERROR_LEVEL, "Token parse failed, non-well-formed XML: %s",
				 (err != NULL) ? err->message : "null"));
		return(STS_CUSTOM_ERR);
	  }

	  root = xmlDocGetRootElement(doc);
	  if (root->type != XML_ELEMENT_NODE
		  || !xmlStreq(root->name, (xmlChar *) "claim_values")
		  || root->next != NULL)
		return(STS_CUSTOM_ERR);

	  if (root->properties != NULL) {
		char *errmsg;

		/* XXX unused attributes are ignored */
		errmsg = xmlGetAttrValue(root, (xmlChar *) "card_error");
		if (errmsg != NULL) {
		  log_msg((LOG_ERROR_LEVEL, "Error filling claim values: %s", errmsg));
		  return(STS_CUSTOM_ERR);
		}
	  }

	  for (child = root->children; child != NULL; child = child->next) {
		char *name, *u, *uri;
		Icx_claim *claim;

		if (child->type != XML_ELEMENT_NODE
			|| !xmlStreq(child->name, (xmlChar *) "claim_value"))
		  continue;

		/* XXX any unused attribute is ignored */
		if ((u = (char *) xmlGetAttrValue(child, (xmlChar *) "type")) == NULL)
		  return(STS_CUSTOM_ERR);
		if (streq(u, "dacs"))
		  uri = ICX_DACS_CLAIM_URI;
		else if (streq(u, "standard"))
		  uri = ICX_STANDARD_CLAIM_URI;
		else
		  uri = u;
		name = (char *) xmlGetAttrValue(child, (xmlChar *) "name");
		if (name == NULL)
		  return(STS_CUSTOM_ERR);

		/* Add the retrieved claim info to the requested claim. */
		claim = NULL;
		for (i = 0; i < dsvec_len(req_claims); i++) {
		  claim = (Icx_claim *) dsvec_ptr_index(req_claims, i);
		  if (streq(claim->uri, uri) && streq(claim->name, name)) {
			claim->label = (char *) xmlGetAttrValue(child, (xmlChar *) "label");
			if (claim->label == NULL)
			  return(STS_CUSTOM_ERR);

			claim->value = (char *) xmlGetAttrValue(child, (xmlChar *) "value");
			if (claim->value == NULL)
			  return(STS_CUSTOM_ERR);
			if (strlen(claim->value) > MIC_MAX_DYNAMIC_VALUE_CLAIM_SIZE) {
			  log_msg((LOG_ERROR_LEVEL, "Claim value is too long: \"%s\"",
					   claim->value));
			  return(STS_CUSTOM_ERR);
			}
			break;
		  }
		}
		if (i == dsvec_len(req_claims))
		  return(STS_CUSTOM_ERR);

		log_msg((LOG_DEBUG_LEVEL, "Claim: %s/%s", claim->uri, claim->name));
	  }

	  xmlFreeDoc(doc);
	  xmlFreeParserCtxt(parser_ctx);
	}

	break;

  case IC_USE_MODE_STATIC:
  case IC_USE_MODE_ISTATIC:
	/*
	 * Find each requested claim in the entry and copy its value.
	 */
	for (i = 0; i < dsvec_len(req_claims); i++) {
	  Icx_claim *c;

	  claim = (Icx_claim *) dsvec_ptr_index(req_claims, i);
	  log_msg((LOG_TRACE_LEVEL, "Trying to fill %s/%s",
			   claim->uri, claim->name));
	  if (claim->type == ICX_STANDARD_CLAIM
		  && streq(claim->name, (char *) PPID)) {
		/* The PPID is handled specially. */
		log_msg((LOG_DEBUG_LEVEL, "PPID claim was requested"));
		if (ppid == NULL) {
		  log_msg((LOG_ERROR_LEVEL, "PPID is unavailable"));
		  return(STS_CUSTOM_ERR);
		}
		log_msg((LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG,
				 "PPID=\"%s\"", ppid));

		/* Fill it now, don't do it externally. */
		ppid_claim = icx_find_standard_claim((char *) PPID);
		if (ppid_claim == NULL)
		  return(STS_CUSTOM_ERR);

		*claim = *ppid_claim;
		claim->value = ppid;
		continue;
	  }

	  c = icx_lookup_claim(mic->claims, claim->uri, claim->name);
	  if (c != NULL) {
		claim->label = c->label;
		claim->value = c->value;
		log_msg((LOG_TRACE_LEVEL, "Filled claim, value=\"%s\"", claim->value));
	  }
	  else {
		claim->value = "No value available";
		log_msg((LOG_TRACE_LEVEL, "Could not fill claim"));
	  }
	}

	break;

  case IC_USE_MODE_DYNAMIC:
	log_msg((LOG_ERROR_LEVEL, "Use mode \"dynamic\" is not implemented"));
	return(STS_CUSTOM_ERR);
	/* */
	break;

  default:
	return(STS_CUSTOM_ERR);
  }

  return(0);
}

/*
 * Create an XML Schema style date/time string.
 * If T is 0, use the current system clock, otherwise use T.
 * Add DELTA_SECS (which may be positive or negative) to the selected clock
 * value.
 * XXX there is no check for problematic DELTA_SECS values
 *
 * XXX see misc.c:make_datetime()
 * http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/datatypes.html#dateTime
 */
char *
icx_gmdatetime(time_t t, long delta_secs)
{
  char *dt;
  time_t tt;
  struct tm *tm;

  if (t == 0)
	time(&tt);
  else
	tt = t;

  tt += delta_secs;

  if ((tm = gmtime(&tt)) == NULL)
	return(NULL);

  dt = ds_xprintf("%d-%.02d-%.02dT%.02d:%.02d:%.02dZ",
				  tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
				  tm->tm_hour, tm->tm_min, tm->tm_sec);

  return(dt);
}

Ds *
icx_canonicalize_xml(Ds *xml)
{
  xmlDocPtr doc;
  xmlOutputBufferPtr buf;
  xmlParserCtxtPtr parser_ctx;
  Ds *ds;

  if ((parser_ctx = xmlNewParserCtxt()) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Unable to initialize parser context"));
	return(NULL);
  }
	
  doc = xmlCtxtReadMemory(parser_ctx, (const char *) ds_buf(xml),
						  (int) ds_len(xml), NULL, NULL, 0);
  if (doc == NULL) {
	xmlErrorPtr err;

	err = xmlCtxtGetLastError(parser_ctx);
	log_msg((LOG_ERROR_LEVEL,
			 "Token parse failed, non-well-formed XML: %s",
			 (err != NULL) ? err->message : "null"));
	xmlFreeParserCtxt(parser_ctx);
	return(NULL);
  }

  buf = xmlAllocOutputBuffer(NULL);
  if (xmlC14NDocSaveTo(doc, NULL, 0, NULL, 0, buf) < 0) {
	xmlErrorPtr err;

	err = xmlCtxtGetLastError(parser_ctx);
	log_msg((LOG_ERROR_LEVEL, "XML Canonicalization failed: %s",
			 (err != NULL) ? err->message : "null"));
	xmlFreeParserCtxt(parser_ctx);
	return(NULL);
  }

  ds = ds_set(NULL, (char *) buf->buffer->content);

  return(ds);
}

/*
 * Processing of an InfoCard SAML token consists of:
 * 1. running the token through the XML parser
 * 2. validating and decrypting (if necessary) the token
 * 3. validating the SAML message structure
 * 4. protect against token replay attacks
 * 5. validating SAML message conditions
 * 6. validating the signature
 * 7. extracting content and attributes
 */
int
icx_saml11_process_token(Icx_ctx *ctx, const xmlChar *token, size_t token_len)
{
  int st;
  xmlDocPtr encr_doc;
  xmlParserCtxtPtr parser_ctx;

  if (token_len > ctx->max_token_size) {
	log_msg((LOG_ERROR_LEVEL, "SAML token is too large (%lu > %lu)",
			 (unsigned long) token_len,
			 (unsigned long) ctx->max_token_size));
	return(-1);
  }

  /* Parse the token. */
  xmlSetExternalEntityLoader(xmlInternalSchemaEntityLoader);
  if ((parser_ctx = xmlNewParserCtxt()) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Unable to initialize parser context"));
	return(-1);
  }
	
  encr_doc = xmlCtxtReadMemory(parser_ctx,
							   (const char *) token, (int) token_len, 
							   NULL, NULL, 0);
  if (encr_doc == NULL) {
	xmlErrorPtr err;

	err = xmlCtxtGetLastError(parser_ctx);
	log_msg((LOG_ERROR_LEVEL,
			 "Token parse failed, non-well-formed XML: %s",
			 (err != NULL) ? err->message : "null"));
	xmlFreeParserCtxt(parser_ctx);
	return(-1);
  }

  /* Validate and decrypt the token (if necessary). */
  if ((st = decrypt_doc(ctx, encr_doc)) == -1) {
	/* Assume the error has already been reported. */
	xmlFreeDoc(encr_doc);
	xmlFreeParserCtxt(parser_ctx);
	return(-1);		
  }
  if (st == 1)
	log_msg((LOG_TRACE_LEVEL, "Successfully decrypted token"));
  else
	log_msg((LOG_TRACE_LEVEL, "Token was not encrypted"));

  /* Validate SAML message structure */
  if (validate_saml11_message(ctx, encr_doc) == -1) {
	/* Assume the error has already been reported. */
	xmlFreeDoc(encr_doc);
	xmlFreeParserCtxt(parser_ctx);
	return(-1);
  }
  log_msg((LOG_TRACE_LEVEL, "Successfully validated SAML token schema"));

  /* Protect against token replay attacks. */
  if (evaluate_saml11_replay(ctx, encr_doc) == -1) {
	/* Assume the error has already been reported. */
	xmlFreeDoc(encr_doc);
	xmlFreeParserCtxt(parser_ctx);
	return(-1);
  }
  log_msg((LOG_TRACE_LEVEL, "Replay check succeeded"));

  /* Validate SAML message conditions */
  if (validate_saml11_conditions(ctx, encr_doc) == -1) {
	/* Assume the error has already been reported. */
	xmlFreeDoc(encr_doc);
	xmlFreeParserCtxt(parser_ctx);
	return(-1);
  }
  log_msg((LOG_TRACE_LEVEL, "SAML message conditions ok"));
	
  if (validate_saml11_self_issued_assertion(ctx, encr_doc) == -1) {
	/* Assume the error has already been reported. */
	xmlFreeDoc(encr_doc);
	xmlFreeParserCtxt(parser_ctx);
	return(-1);
  }
  if (ctx->issuer == NULL)
	log_msg((LOG_DEBUG_LEVEL, "SAML token is self-issued"));
  else
	log_msg((LOG_DEBUG_LEVEL, "SAML token was issued by %s", ctx->issuer));

  /* Validate signature */
  if (validate_saml11_signature(ctx, encr_doc) == -1) {
	/* Assume the error has already been reported. */
	xmlFreeDoc(encr_doc);
	xmlFreeParserCtxt(parser_ctx);
	return(-1);
  }
  log_msg((LOG_DEBUG_LEVEL, "Token seems valid..."));

  /* Extract useful information from the SAML message */
  if (extract_saml11_info(ctx, encr_doc) == -1) {
	/* Assume the error has already been reported. */
	xmlFreeDoc(encr_doc);
	xmlFreeParserCtxt(parser_ctx);
	return(-1);
  }
  log_msg((LOG_DEBUG_LEVEL, "Extracted token info"));

  xmlFreeDoc(encr_doc);
  xmlFreeParserCtxt(parser_ctx);

  return(0);
}

/*
 * Given a cryptographic context and an XML-encrypted document,
 * replace the document with the decrypted contents. Decryption is done
 * based on the private key in the context, meant to be the SSL private key.
 * Return 0 if the document is not actually encrypted, 1 if decryption is
 * successful, or -1 if decryption fails.
 */
static int
decrypt_doc(Icx_ctx *ctx, xmlDocPtr encr_doc) 
{
  xmlNodePtr root_element, sec_node;
  xmlSecEncCtxPtr encr_ctx;
	
  root_element = xmlDocGetRootElement(encr_doc);
  sec_node = xmlSecFindNode(root_element, xmlSecNodeEncryptedData, xmlSecEncNs);
  if (sec_node == NULL) {
	/* Token has no EncryptedData element. */
	return(0);
  }

  if ((encr_ctx = xmlSecEncCtxCreate(ctx->key_manager)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Failed to create encryption context"));
	return(-1);
  }

  if (xmlSecEncCtxDecrypt(encr_ctx, sec_node) < 0 || encr_ctx->result == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Token decryption failed"));
	xmlSecEncCtxDestroy(encr_ctx);
	return(-1);
  }

  if (encr_ctx->resultReplaced == 0) {		
	log_msg((LOG_ERROR_LEVEL, "Token decryption yielded binary data"));
	xmlSecEncCtxDestroy(encr_ctx);
	return(-1);
  }

  log_msg((LOG_TRACE_LEVEL, "Decrypted token:\n%s", encr_ctx->result->data));

  xmlSecEncCtxDestroy(encr_ctx);
  return(1);
}

static void
saml11_validity_err_rep(void *ctx, const char *msg, ...)
{
  va_list ap;

  va_start(ap, msg);
  log_vmsg((LOG_ERROR_LEVEL, msg, ap));
  va_end(ap);
}

/*
 * Report xmlsec warnings as informational into the logging callback
 */
static void
saml11_validity_warn_rep(void *ctx, const char *msg, ...)
{
  va_list ap;

  va_start(ap, msg);
  log_vmsg((LOG_INFO_LEVEL, msg, ap));
  va_end(ap);
}

/*
 * Given a SAML 1.1 token, validate against the SAML 1.1 schema.
 */
static int
validate_saml11_message(Icx_ctx *ctx, xmlDocPtr token_doc)
{
  int result;
  xmlSchemaPtr schema;
  xmlSchemaParserCtxtPtr schema_ctx;
  xmlSchemaValidCtxtPtr validator;
  xmlNodePtr root;
  xmlChar *MajorVersion;
  xmlChar *MinorVersion;

  schema_ctx = 
	xmlSchemaNewMemParserCtxt((const char *) oasis_sstc_saml_schema_assertion_1_1,
							  sizeof(oasis_sstc_saml_schema_assertion_1_1));
  if (schema_ctx == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Unable to create schema parsing context"));
	return(-1);
  }

  schema = xmlSchemaParse(schema_ctx);
  if (schema == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Unable to parse schema"));
	xmlSchemaFreeParserCtxt(schema_ctx);
	return(-1);
  }

  validator = xmlSchemaNewValidCtxt(schema);
  if (validator == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Unable to create schema validator context"));
	xmlSchemaFree(schema);
	xmlSchemaFreeParserCtxt(schema_ctx);
	return(-1);
  }

  xmlSchemaSetValidErrors(validator, saml11_validity_err_rep,
						  saml11_validity_warn_rep, ctx);

  result = xmlSchemaValidateDoc(validator, token_doc);
  xmlSchemaFreeValidCtxt(validator);
  xmlSchemaFree(schema);
  xmlSchemaFreeParserCtxt(schema_ctx);

  if (result != 0) {
	log_msg((LOG_ERROR_LEVEL, "Schema validation of SAML 1.1 token failed"));
	return(-1);		
  }

  /* Now check major and minor versions */
  root = xmlDocGetRootElement(token_doc);
  MajorVersion = xmlGetNoNsProp(root, ATTR_MAJOR_VERSION);
  if (!xmlStreq(MajorVersion, (xmlChar *) "1")) {
	log_msg((LOG_ERROR_LEVEL, "Unexpected SAML major version received"));
	xmlFree(MajorVersion);
	return(-1);
  }
  xmlFree(MajorVersion);

  MinorVersion = xmlGetNoNsProp(root, ATTR_MINOR_VERSION);
  if (!xmlStreq(MinorVersion, (xmlChar *) "1")
	  && !xmlStreq(MinorVersion, (xmlChar *) "0")) {
	log_msg((LOG_ERROR_LEVEL, "Unexpected SAML minor version received"));
	xmlFree(MinorVersion);
	return(-1);
  }

  xmlFree(MinorVersion);

  return(1);
}

/*
 * Given a SAML 1.1 token, verify the issuer is correct according to the
 * information card profile.
 * Return 1 if ok, -1 otherwise.
 */
static int
validate_saml11_self_issued_assertion(Icx_ctx *ctx, xmlDocPtr token_doc)
{
  int st;
  xmlNodePtr root;
  xmlChar *issuer;

  root = xmlDocGetRootElement(token_doc);
  issuer = xmlGetNoNsProp(root, ATTR_ISSUER);

  if (xmlStreq(SELF_ATTR_ISSUER, issuer)) {
	ctx->issuer = NULL;
	ctx->token->issuer = NULL;
  }
  else {
	ctx->issuer = issuer;
	ctx->token->issuer = strdup((char *) issuer);
  }

  st = 1;
  if (ctx->validate_self_issued) {
	if (ctx->issuer != NULL) {
	  log_msg((LOG_ERROR_LEVEL, "SAML token is not self-issued"));
	  st = -1;
	}
  }
  else {
	/* XXX check for valid issuer? */
	log_msg((LOG_DEBUG_LEVEL, "Do not require self-issued token"));
  }

  return(st);
}

/*
 * Given a SAML 1.1 token, verify all of the conditions included.
 * Return 1 if ok, -1 otherwise.
 */
static int
validate_saml11_conditions(Icx_ctx *ctx, xmlDocPtr token_doc)
{
  xmlNodePtr conditions, root;

  if (!ctx->validate_message_conditions) {
	log_msg((LOG_INFO_LEVEL, "Not checking SAML message conditions"));
	return(1);
  }

  root = xmlDocGetRootElement(token_doc);
  for (conditions = root->children; conditions != NULL;
	   conditions = conditions->next) {
	if (conditions->type == XML_ELEMENT_NODE
		&& xmlStreq(conditions->name, CONDITIONS)
		&& xmlStreq(conditions->ns->href, NS_SAML_11))
	  break;
  }
  if (conditions == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Conditions element missing"));
	return(-1);
  }

  if (evaluate_saml11_time_conditions(ctx, conditions) == -1) {
	/* Assume the error has already been reported. */
	return(-1);
  }
  log_msg((LOG_TRACE_LEVEL, "Time conditions ok"));

  if (evaluate_saml11_child_conditions(ctx, conditions) == -1) {
	/* Assume the error has already been reported. */
	return(-1);
  }
  log_msg((LOG_TRACE_LEVEL, "Child conditions ok"));

  return(1);
}

/*
 * Check if the token's validity period is still in effect.
 * Validates the specific notBefore/notOnOrAfter values within the Conditions
 * element.
 * Return 1 if validity is ok, -1 otherwise.
 */
static int
evaluate_saml11_time_conditions(Icx_ctx *ctx, xmlNodePtr conditions)
{
  int diff_secs, notBeforeMet, notOnOrAfterMet;
  Icx_time_conditions *tc;
  xmlChar *notBefore, *notOnOrAfter;
  time_t now_secs, notBefore_secs, notOnOrAfter_secs;
  
  notBefore = xmlGetNoNsProp(conditions, ATTR_NOT_BEFORE);
  notOnOrAfter = xmlGetNoNsProp(conditions, ATTR_NOT_ON_OR_AFTER);
  if (parse_datetime((char *) notBefore, &notBefore_secs, NULL) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Invalid notBefore datetime: %s", notBefore));
	xmlFree(notBefore);
	xmlFree(notOnOrAfter);
	return(-1);
  }

  if (parse_datetime((char *) notOnOrAfter, &notOnOrAfter_secs, NULL) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Invalid notOnOrAfter datetime: %s",
			 notOnOrAfter));
	xmlFree(notBefore);
	xmlFree(notOnOrAfter);
	return(-1);
  }

  if (notBefore_secs > notOnOrAfter_secs) {
	log_msg((LOG_ERROR_LEVEL, "notBefore is after notOnOrAfter"));
	xmlFree(notBefore);
	xmlFree(notOnOrAfter);
	return(-1);
  }

  now_secs = icx_get_current_time(ctx);

  /*
   * The clock on this machine may be faster or slower than the one used
   * when the token was generated.  Because of this clock skew, we may want to
   * add a "slop factor" when deciding whether the current time is between
   * notBefore and notOnOrAfter.
   * If either clock skew adjustment is -1, then that condition should be
   * ignored - this should only be used while testing.
   * The max_fast_clock_adj_secs is the maximum number of seconds
   */
  tc = &ctx->time_conditions;
  notOnOrAfterMet = 0;
  if (tc->max_fast_clock_adj_secs < 0 || now_secs < notOnOrAfter_secs)
	notOnOrAfterMet = 1;
  else {
	/* Allow for fast clock skew. */
	diff_secs = now_secs - (notOnOrAfter_secs + tc->max_fast_clock_adj_secs);
	if (diff_secs < 0)
	  notOnOrAfterMet = 1;
  }

  if (!notOnOrAfterMet) {
	struct tm *tm;

	tm = gmtime(&now_secs);
	log_msg((LOG_ERROR_LEVEL,
			 "Token has expired (now=%s, NotOnOrAfter=%s, diff=%d)",
			 make_datetime(tm), notOnOrAfter, diff_secs));
	xmlFree(notBefore);
	xmlFree(notOnOrAfter);
	return(-1);
  }

  notBeforeMet = 0;
  if (tc->max_slow_clock_adj_secs < 0 || now_secs >= notBefore_secs)
	notBeforeMet = 1;
  else {
	/* Allow for slow clock skew. */
	diff_secs = (notBefore_secs - tc->max_slow_clock_adj_secs) - now_secs;
	if (diff_secs <= 0)
	  notBeforeMet = 1;
  }

  if (!notBeforeMet) {
	struct tm *tm;

	tm = gmtime(&now_secs);
	log_msg((LOG_ERROR_LEVEL,
			 "Token used before allowed... clock drift? (now=%s, notBefore=%s, diff=%d)",
			 make_datetime(tm), notBefore, diff_secs));
	xmlFree(notBefore);
	xmlFree(notOnOrAfter);
	return(-1);
  }

  xmlFree(notBefore);
  xmlFree(notOnOrAfter);
  return(1);
}

/*
 * Walk the child elements of the SAML conditions, evaluating each. 
 * AudienceRestriction, if present, is evaluated against the context,
 * DoNotCacheCondition is ignored as the library does not cache or report the
 * SAML 1.1 assertion. Other values will trigger a failure.
 */
static int
evaluate_saml11_child_conditions(Icx_ctx *ctx, xmlNodePtr conditions)
{
  xmlNodePtr child;

  for (child = conditions->children; child != NULL; child = child->next) {
	if (child->type != XML_ELEMENT_NODE)
	  continue;

	if (!xmlStreq(child->ns->href, NS_SAML_11)) {
	  log_msg((LOG_ERROR_LEVEL, "Unknown condition encountered: {%s}:%s",
			   child->ns->href, child->name));
	  return(-1);
	}

	if (xmlStreq(child->name, AUDIENCE_RESTRICTION_CONDITION)) {
	  int matched;
	  xmlNodePtr aud;

	  matched = 0;
	  /* Each child is an Audience element, which is a URI. */
	  for (aud = child->children; aud != NULL; aud = aud->next) {
		if (aud->type == XML_ELEMENT_NODE && aud->children != NULL
			&& icx_is_in_audience(ctx, aud->children->content)) {
		  matched = 1;
		  break;
		}
	  }

	  if (!matched) {
		log_msg((LOG_ERROR_LEVEL,
				 "AudienceRestrictionCondition validation failed"));
		return(-1);
	  }
	}
	else if (xmlStreq(child->name, DO_NOT_CACHE_CONDITION)) {
	  /* we do not cache the assertion itself, so ignore */
	}
	else {
	  log_msg((LOG_ERROR_LEVEL, "Condition '%s' cannot be processed",
			   child->name));
	  return(-1);
	}
  }

  return(1);
}

/*
 * Get the replay value from the SAML 1.1 token and call the replay callback.
 * Return -1 if replay is detected, 0 otherwise.
 */
static int
evaluate_saml11_replay(Icx_ctx *ctx, xmlDocPtr token_doc)
{
  xmlNodePtr root;
  xmlChar *assertionId;

  if (!ctx->validate_token_replay)
	return(0);

  root = xmlDocGetRootElement(token_doc);
  assertionId = xmlGetNoNsProp(root, ATTR_ASSERTION_ID);

  if (ctx->replay_detector(ctx, assertionId, -1,
						   ctx->replay_detector_arg) != 0) {
	log_msg((LOG_ERROR_LEVEL,
			 "Replay detected for AssertionID '%s'", assertionId));
	xmlFree(assertionId);
	return(-1);
  }

  xmlFree(assertionId);
  return(0);
}

/*
 * Given a SAML 1.1 token with an enveloped signature and included public key,
 * validate the signature on the token.
 * Return 1 if ok, -1 otherwise.
 */
static int
validate_saml11_signature(Icx_ctx *ctx, xmlDocPtr token_doc)
{
  xmlAttrPtr attr;
  xmlChar *name;
  xmlNodePtr keyinfo, root, sig;
  xmlSecDSigCtxPtr dsig_ctx;
  xmlSecKeyInfoCtxPtr keyinfo_ctx;
  xmlSecKeyPtr key;
  const xmlChar *KEY_INFO = (xmlChar *) "KeyInfo";

  if (!ctx->enable_signature_validation) {
	log_msg((LOG_INFO_LEVEL, "Not checking SAML signature"));
	return(1);
  }

  root = xmlDocGetRootElement(token_doc);
  if ((sig = xmlSecFindNode(root, xmlSecNodeSignature, xmlSecDSigNs)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "SAML token signature not found"));
	return(-1);
  }

#ifdef NOTDEF
  {
	xmlSecKeysMngrPtr keys_mgr;

	keys_mgr = xmlSecKeysMngrCreate();
	if (xmlSecCryptoAppDefaultKeysMngrInit(keys_mgr) < 0) {
	  log_msg((LOG_ERROR_LEVEL, "Cannot initialize key manager"));
	  xmlSecKeysMngrDestroy(keys_mgr);
	  return(-1);
	}
  }
#endif

  if ((dsig_ctx = xmlSecDSigCtxCreate(ctx->key_manager)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Cannot create signature verification context"));
	return(-1);
  }

  /*
   * Register AssertionID as an ID field, since we had to strip such info
   * from the schema.
   */
  attr = xmlHasProp(root, ATTR_ASSERTION_ID);

  /* Get the attribute (id) value */
  name = xmlNodeListGetString(token_doc, attr->children, 1);
  xmlAddID(NULL, token_doc, name, attr);
  xmlFree(name);

  /*
   * Extract the public key from KeyInfo.
   *
   * Note that there can be more than one KeyInfo element; with a managed
   * card token, there may be one in the saml:Subject (with holder-of-key)
   * and one in the dsig:Signature.
   */

  if ((keyinfo = xmlSecFindNode(sig, KEY_INFO, xmlSecDSigNs)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Cannot find signature's KeyInfo node"));
	xmlSecDSigCtxDestroy(dsig_ctx);
	return(-1);
  }
  log_msg((LOG_TRACE_LEVEL, "Found KeyInfo node"));

  keyinfo_ctx = xmlSecKeyInfoCtxCreate(ctx->key_manager);
  keyinfo_ctx->flags = XMLSEC_KEYINFO_FLAGS_X509DATA_DONT_VERIFY_CERTS;

  if ((key = xmlSecKeysMngrGetKey(keyinfo, keyinfo_ctx)) == NULL) {
	xmlChar *p;
	xmlNodePtr certData, certPtr;
	xmlSecKeyDataPtr data;
	BIO *bio;
	Ds *ds;
	X509 *x;

	/*
	 * Sometimes there seem to be problems with the higher-level XMLSec
	 * library functions that are supposed to dig out a public key from a cert
	 * embedded within a <dsig:Signature> element in a managed InfoCard token:
	 *  <dsig:KeyInfo><dsig:X509Data>
	 *    <dsig:X509Certificate></dsig:X509Certificate>
	 *  </dsig:/X509Data></dsig:KeyInfo>
	 * This could well be pilot error, but here is how to code around that
	 * problem.
	 * If signature validation fails incorrectly, look here.
	 *
	 * XXX While this code might be ok for InfoCard in practice, it may not be
	 * sufficient for SAML in general...
	 */
	log_msg((LOG_ERROR_LEVEL, "Ignore xmlSec complaints about the cert..."));
	log_msg((LOG_DEBUG_LEVEL, "Looking for X509Certificate node"));
	certPtr = xmlSecFindNode(keyinfo, (xmlChar *) "X509Certificate",
							 xmlSecDSigNs);
	if (certPtr != NULL && (certData = certPtr->children) != NULL) {
	  if (certData->type == XML_TEXT_NODE
		  && (p = certData->content) != NULL) {
		ds = pem_make_cert((char *) p);
		if ((bio = BIO_new_mem_buf(ds_buf(ds), ds_len(ds) - 1)) != NULL) {
		  if ((x = PEM_read_bio_X509(bio, NULL, NULL, NULL)) != NULL) {
			if ((data = xmlSecOpenSSLX509CertGetKey(x)) != NULL) {
			  key = xmlSecKeyCreate();
			  if (xmlSecKeySetValue(key, data) < 0)
				key = NULL;
			}
		  }
		  BIO_free(bio);
		}
		ds_free(ds);
	  }
	}
  }

  if (key == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Cannot extract signature validation key"));
	xmlSecKeyInfoCtxDestroy(keyinfo_ctx);
	xmlSecDSigCtxDestroy(dsig_ctx);
	return(-1);
  }
  log_msg((LOG_TRACE_LEVEL, "Got KeyInfo key"));

  if (xmlSecCryptoAppDefaultKeysMngrAdoptKey(ctx->key_manager, key) < 0) {
	log_msg((LOG_ERROR_LEVEL, "Cannot import public key"));
	xmlSecKeyInfoCtxDestroy(keyinfo_ctx);
	xmlSecDSigCtxDestroy(dsig_ctx);
	return(-1);
  }

  if (xmlSecDSigCtxVerify(dsig_ctx, sig) < 0) {
	log_msg((LOG_ERROR_LEVEL, "Signature validation failed"));
	xmlSecKeyInfoCtxDestroy(keyinfo_ctx);
	xmlSecDSigCtxDestroy(dsig_ctx);
	return(-1);
  }

  if (dsig_ctx->status != xmlSecDSigStatusSucceeded) {
	log_msg((LOG_ERROR_LEVEL, "Signature is invalid"));
	xmlSecKeyInfoCtxDestroy(keyinfo_ctx);
	xmlSecDSigCtxDestroy(dsig_ctx);
	return(-1);
  }
  log_msg((LOG_DEBUG_LEVEL, "Signature is valid"));

  xmlSecKeyInfoCtxDestroy(keyinfo_ctx);
  xmlSecDSigCtxDestroy(dsig_ctx);

  return(1);
}

static void
add_saml11_claim(Icx_ctx *ctx, xmlChar *name, xmlChar *ns, xmlChar *value)
{
  char *claim_name;

  if (ctx->token == NULL) {
	ctx->token = ALLOC(Ic_token);
	ctx->token->issuer = NULL;
	ctx->token->confirm = IC_CONFIRM_ERROR;
	ctx->token->ppid = NULL;
	ctx->token->exponent = NULL;
	ctx->token->modulus = NULL;
	ctx->token->kwv_claims = kwv_init(8);
  }

  claim_name = ds_xprintf("%s/%s", (char *) ns, (char *) name);

  kwv_add(ctx->token->kwv_claims, claim_name, (char *) value);
  log_msg((LOG_TRACE_LEVEL, "Added SAML claim: %s=\"%s\"",
		   claim_name, (char *) value));
}

static xmlNodePtr
find_attribute_statement(Icx_ctx *ctx, xmlDocPtr token_doc)
{
  xmlNodePtr assertion, attributeStmt, child;

  assertion = xmlDocGetRootElement(token_doc);
  attributeStmt = NULL;

  for (child = assertion->children; child != NULL; child = child->next) {
	if (child->type == XML_ELEMENT_NODE) {
	  if (xmlStreq(child->ns->href, NS_SAML_11)
		  && xmlStreq(child->name, ATTRIBUTE_STATEMENT)) {
		attributeStmt = child;
		break;
	  }
	}
  }

  return(attributeStmt);
}

/*
 * Grab all AttributeName/AttributeValue pairs from the AttributeStatement.
 * Reports the attribute values of the SAML 1.1 assertion to the attribute
 * reporter callback.
 */
static int
extract_saml11_attributes(Icx_ctx *ctx, xmlDocPtr token_doc)
{
  xmlNodePtr attr, attributeStmt;

  if ((attributeStmt = find_attribute_statement(ctx, token_doc)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Unable to find AttributeStatement element"));
	return(-1);
  }

  for (attr = attributeStmt->children; attr != NULL; attr = attr->next) {
	if (attr->type == XML_ELEMENT_NODE) {
	  if (xmlStreq(attr->ns->href, NS_SAML_11)
		  && xmlStreq(attr->name, ATTRIBUTE)) {
		xmlChar *name, *ns, *value;

		name = xmlGetNoNsProp(attr, ATTR_ATTRIBUTE_NAME);
		ns = xmlGetNoNsProp(attr, ATTR_ATTRIBUTE_NAMESPACE);
		value = xmlNodeGetContent(attr);
		log_msg((LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG,
				 "name=\"%s\", ns=\"%s\", value=\"%s\"", name, ns, value));
		add_saml11_claim(ctx, name, ns, value);

		xmlFree(name);
		xmlFree(ns);
		xmlFree(value);
	  }
	}
  }

  return(1);
}

static xmlChar *
extract_saml11_nodeval(Icx_ctx *ctx, const xmlChar *path, xmlDocPtr token_doc)
{
  xmlXPathContextPtr xpath_ctx;
  xmlXPathObjectPtr result;
  xmlNodeSetPtr nodeset;
  xmlNodePtr node;
  xmlChar *content;

  xpath_ctx = xmlXPathNewContext(token_doc);
  xmlXPathRegisterNs(xpath_ctx, PRE_SAML, NS_SAML_11);
  xmlXPathRegisterNs(xpath_ctx, PRE_DS, NS_DSIG);
  result = xmlXPathEval(path, xpath_ctx);

  if (result == NULL
	  || result->type != XPATH_NODESET
	  || result->nodesetval == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Error processing XPath expression, path=\"%s\"",
			 (char *) path));
	xmlXPathFreeContext(xpath_ctx);
	if (result != NULL)
	  xmlXPathFreeObject(result);

	return(NULL);
  }

  nodeset = result->nodesetval;
  if (nodeset->nodeNr != 1) {
	log_msg((LOG_ERROR_LEVEL, "Multiple matches for XPath expression"));
	xmlXPathFreeContext(xpath_ctx);
	xmlXPathFreeObject(result);

	return(NULL);
  }

  node = *nodeset->nodeTab;
  content = xmlNodeGetContent(node);

  xmlXPathFreeContext(xpath_ctx);
  xmlXPathFreeObject(result);

  return(content);
}

/*
 * The SubjectConfirmation method is either "...:holder-of-key" or "...:bearer".
 * If it's the former, the subject confirmation key (aka the proof key) must
 * appear in the KeyInfo child element of the SubjectConfirmation element;
 * for an asymmetric key token, the proof key is a public RSA key value
 * (RSAKeyValue under the KeyValue element).
 */
static int
extract_saml11_token_info(Icx_ctx *ctx, xmlDocPtr token_doc)
{
  xmlChar *content;
  xmlNodePtr attr, attributeStmt;

  log_msg((LOG_DEBUG_LEVEL, "Extracting token claims..."));

  /* Get the subject confirmation method. */
  if ((content = extract_saml11_nodeval(ctx, PATH_CONFIRMATION_METHOD,
										token_doc)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Cannot find subject confirmation method"));
	return(-1);
  }
  if (strsuffix((char *) content, strlen((char *) content), ":holder-of-key")) {
	ctx->token->confirm = IC_CONFIRM_HOLDER;
	log_msg((LOG_TRACE_LEVEL, "Subject confirmation is holder of key"));
  }
  else if (strsuffix((char *) content, strlen((char *) content), ":bearer")) {
	ctx->token->confirm = IC_CONFIRM_BEARER;
	log_msg((LOG_TRACE_LEVEL, "Subject confirmation is bearer (no proof key)"));
  }
  else {
	ctx->token->confirm = IC_CONFIRM_ERROR;
	log_msg((LOG_TRACE_LEVEL, "Subject confirmation is unknown: %s", content));
  }
  xmlFree(content);

  if (ctx->token->confirm == IC_CONFIRM_BEARER) {
	int blen, elen;
	char *enc_cert, *text_exponent, *text_modulus;
	unsigned char *binary_exponent, *binary_modulus;
	BIO *bio;
	Ds *ds;
	EVP_PKEY *pkey;
	X509 *x;

	if (ctx->issuer == NULL) {
	  /* Self-issued */
	  log_msg((LOG_TRACE_LEVEL, "Self-issued InfoCard"));

	  /* Get the public key's RSA modulus. */
	  content = extract_saml11_nodeval(ctx, PATH_MODULUS, token_doc);
	  if (content == NULL) {
		log_msg((LOG_ERROR_LEVEL, "Cannot find Modulus"));
		return(-1);
	  }
	  log_msg((LOG_TRACE_LEVEL, "name=\"%s\", ns=\"%s\", value=\"%s\"",
			   MODULUS, NS_DSIG, content));
	  ctx->token->modulus = ds_set(NULL, (char *) content);
	  xmlFree(content);

	  /* Get the public key's RSA exponent. */
	  if ((content = extract_saml11_nodeval(ctx, PATH_EXPONENT,
											token_doc)) == NULL) {
		log_msg((LOG_ERROR_LEVEL, "Cannot find Exponent"));
		return(-1);
	  }
	  log_msg((LOG_TRACE_LEVEL, "name=\"%s\", ns=\"%s\", value=\"%s\"",
			   EXPONENT, NS_DSIG, content));
	  ctx->token->exponent = ds_set(NULL, (char *) content);
	  xmlFree(content);
	}
	else {
	  log_msg((LOG_TRACE_LEVEL, "Managed InfoCard: \"%s\"", ctx->issuer));

	  content = extract_saml11_nodeval(ctx, PATH_KEYINFO_CERT, token_doc);
	  if (content == NULL) {
		log_msg((LOG_ERROR_LEVEL, "Cannot find ds:X509Certificate"));
		return(-1);
	  }
	  log_msg((LOG_TRACE_LEVEL, "name=\"%s\", ns=\"%s\", value=\"%s\"",
			   MODULUS, NS_DSIG, content));
	  enc_cert = (char *) content;
	  ds = pem_make_cert(enc_cert);

	  bio = BIO_new_mem_buf(ds_buf(ds), ds_len(ds) - 1);
	  x = PEM_read_bio_X509(bio, NULL, NULL, NULL);
	  pkey = X509_get_pubkey(x);
	  BIO_free(bio);

	  blen = BN_num_bytes(pkey->pkey.rsa->n);
	  binary_modulus = malloc(blen);
	  BN_bn2bin(pkey->pkey.rsa->n, binary_modulus);
	  mime_encode_base64(binary_modulus, blen, &text_modulus);
	  ctx->token->modulus = ds_set(NULL, text_modulus);
	  log_msg((LOG_DEBUG_LEVEL, "Modulus=%s", text_modulus));

	  elen = BN_num_bytes(pkey->pkey.rsa->e);
	  binary_exponent = malloc(elen);
	  BN_bn2bin(pkey->pkey.rsa->e, binary_exponent);
	  mime_encode_base64(binary_exponent, elen, &text_exponent);
	  ctx->token->exponent = ds_set(NULL, text_exponent);
	  log_msg((LOG_DEBUG_LEVEL, "Exponent=%s", text_exponent));
	}
  }
  else if (ctx->token->confirm == IC_CONFIRM_HOLDER) {
	int blen, elen;
	char *enc_cert, *text_exponent, *text_modulus;
	unsigned char *binary_exponent, *binary_modulus;
	BIO *bio;
	Ds *ds;
	EVP_PKEY *pkey;
	X509 *x;

	/*
	 * 8.2
	 * When the subject confirmation method is "holder of key", the subject
	 * confirmation key (also referred to as the proof key) MUST be included in
	 * the token in the ds:KeyInfo child element under the
	 * saml:SubjectConfirmation element. The proof key MUST be encoded in the
	 * token as follows:
	 *   o For symmetric key tokens, the proof key is encrypted to the
	 *     recipient of the token in the form of a xenc:EncryptedKey child
	 *     element. The default size of the key is 256 bits, but a different
	 *     size may be specified by the Relying Party.
     *   o For asymmetric key tokens, the proof key is a public RSA key value
	 *     specified as a ds:RSAKeyValue child element under ds:KeyValue
	 *     element. The default size of the key is 2048 bits.
	 */

	/*
	 * Asymmetric proof key
	 */

	/* Get the public cert. */
	content = extract_saml11_nodeval(ctx, PATH_SUBJECT_X509CERT, token_doc);
	if (content == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Cannot find subject X.509 cert"));
	  return(-1);
	}
	enc_cert = (char *) content;
	ds = pem_make_cert(enc_cert);

	bio = BIO_new_mem_buf(ds_buf(ds), ds_len(ds) - 1);
	x = PEM_read_bio_X509(bio, NULL, NULL, NULL);
	pkey = X509_get_pubkey(x);
	BIO_free(bio);

	blen = BN_num_bytes(pkey->pkey.rsa->n);
	binary_modulus = malloc(blen);
	BN_bn2bin(pkey->pkey.rsa->n, binary_modulus);
	mime_encode_base64(binary_modulus, blen, &text_modulus);
	ctx->token->modulus = ds_set(NULL, text_modulus);
	log_msg((LOG_DEBUG_LEVEL, "Modulus=%s", text_modulus));

	elen = BN_num_bytes(pkey->pkey.rsa->e);
	binary_exponent = malloc(elen);
	BN_bn2bin(pkey->pkey.rsa->e, binary_exponent);
	mime_encode_base64(binary_exponent, elen, &text_exponent);
	ctx->token->exponent = ds_set(NULL, text_exponent);
	log_msg((LOG_DEBUG_LEVEL, "Exponent=%s", text_exponent));
  }

  /* Get the token's PPID. */
  if ((attributeStmt = find_attribute_statement(ctx, token_doc)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Cannot find AttributeStatement"));
	return(-1);
  }

  content = NULL;
  for (attr = attributeStmt->children; attr != NULL; attr = attr->next) {
	if (attr->type == XML_ELEMENT_NODE) {
	  if (xmlStreq(attr->ns->href, NS_SAML_11)
		  && xmlStreq(attr->name, ATTRIBUTE)) {
		xmlChar *name;

		name = xmlGetNoNsProp(attr, ATTR_ATTRIBUTE_NAME);
		if (xmlStreq(name, PPID)) {
		  content = xmlNodeGetContent(attr);
		  log_msg((LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG,
				   "name=\"%s\", ns=\"%s\", value=\"%s\"",
				   PPID, NS_SAML_11, content));
		  break;
		}
	  }
	}
  }

  if (content == NULL) {
	log_msg((LOG_DEBUG_LEVEL, "Cannot find PPID claim"));
	ctx->token->ppid = ds_set(NULL, (char *) "");
  }
  else {
	ctx->token->ppid = ds_set(NULL, (char *) content);
	xmlFree(content);
  }

  return(1);
}

/*
 * Given a SAML 1.1 token which has been validated, extract various
 * attributes that might be of interest to an application.
 * XXX validate subject conf as 'sender'
 */
static int
extract_saml11_info(Icx_ctx *ctx, xmlDocPtr token_doc)
{

  if (extract_saml11_token_info(ctx, token_doc) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Unable to get required token info"));
	return(-1);
  }

  if (extract_saml11_attributes(ctx, token_doc) == -1) {
	log_msg((LOG_ERROR_LEVEL, "Unable to get required token attributes"));
	return(-1);
  }

  return(1);
}

void
icx_set_time_conditions(Icx_ctx *ctx, int max_slow_clock_adj_secs,
						int max_fast_clock_adj_secs)
{

  ctx->time_conditions.max_slow_clock_adj_secs = max_slow_clock_adj_secs;
  ctx->time_conditions.max_fast_clock_adj_secs = max_fast_clock_adj_secs;
  log_msg((LOG_TRACE_LEVEL, "Set max_slow_clock_adj_secs to %d",
		   ctx->time_conditions.max_slow_clock_adj_secs));
  log_msg((LOG_TRACE_LEVEL, "Set max_fast_clock_adj_secs to %d",
		   ctx->time_conditions.max_fast_clock_adj_secs));
}

/*
 * The default token replay detection handler is called if no other function
 * has been configured.
 * If validation is turned off, do not do any detection and return 0;
 * if it's turned on, it is an error and return non-zero.
 */
static int
default_token_replay_detector(Icx_ctx *ctx, const xmlChar *replay_string,
							  time_t not_on_or_after, void *detector_context)
{

  if (ctx->validate_token_replay) {
	log_msg((LOG_ERROR_LEVEL, "Default token replay detection fails"));
	return(1);
  }

  log_msg((LOG_ERROR_LEVEL, "Default token replay detection ok"));
  return(0);
}

void
icx_set_replay_detection(Icx_ctx *ctx, Icx_replay_detection_func detector,
						void *detector_arg)
{

  if (!ctx->validate_token_replay || detector == NULL) {
	ctx->replay_detector = NULL;
	ctx->replay_detector_arg = NULL;
	ctx->validate_token_replay = 0;
  }
  else {
	ctx->replay_detector = detector;
	ctx->replay_detector_arg = detector_arg;
	ctx->validate_token_replay = 1;
  }
}

Icx_ctx *
icx_ctx_create(void)
{
  Icx_ctx *ctx;

  ctx = ALLOC(Icx_ctx);
  ctx->now = time(NULL);
  ctx->audience = NULL;
  ctx->issuer = NULL;
  ctx->replay_detector = default_token_replay_detector;
  ctx->replay_detector_arg = NULL;
  ctx->processor = icx_saml11_process_token;
  ctx->max_token_size = ICX_DEFAULT_MAX_TOKEN_SIZE;
  ctx->validate_token_replay = 1;
  ctx->validate_message_conditions = 1;
  ctx->validate_self_issued = 1;
  ctx->enable_signature_validation = 1;

  ctx->token = ALLOC(Ic_token);
  ctx->token->issuer = NULL;
  ctx->token->confirm = IC_CONFIRM_ERROR;
  ctx->token->ppid = NULL;
  ctx->token->exponent = NULL;
  ctx->token->modulus = NULL;
  ctx->token->kwv_claims = kwv_init(8);

  if ((ctx->key_manager = xmlSecKeysMngrCreate()) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Unable to get xmlSec keys manager"));
	free(ctx);
	return NULL;
  }

  if (xmlSecCryptoAppDefaultKeysMngrInit(ctx->key_manager) < 0) {
	log_msg((LOG_ERROR_LEVEL, "Failed to initialize keys manager"));
	xmlSecKeysMngrDestroy(ctx->key_manager);
	free(ctx);
	return(NULL);
  }

  return(ctx);
}

void
icx_ctx_destroy(Icx_ctx *ctx)
{

  if (ctx->key_manager != NULL)
	xmlSecKeysMngrDestroy(ctx->key_manager);

  free(ctx);
}

/*
 *
 */
int
icx_load_certificate(Icx_ctx *ctx, const char *cert_file_name)
{

  if (cert_file_name == NULL || *cert_file_name == '\0') {
	log_msg((LOG_ERROR_LEVEL, "Certificate file name is missing"));
	return(-1);
  }

  /* Get the server's certificate */
  if (xmlSecCryptoAppKeysMngrCertLoad(ctx->key_manager, cert_file_name,
									  xmlSecKeyDataFormatPem,
									  xmlSecKeyDataTypePublic) != 0) {
	log_msg((LOG_ERROR_LEVEL,
			 "Certificate could not be loaded into keys manager"));
	return(-1);
  }

  return(1);
}

int
icx_load_key(Icx_ctx *ctx, const char *key_file_name, const char *password)
{
  xmlSecKeyPtr key;

  if (key_file_name == NULL || *key_file_name == '\0') {
	log_msg((LOG_ERROR_LEVEL, "Certificate file name is missing"));
	return(-1);
  }

  key = xmlSecCryptoAppKeyLoad(key_file_name, xmlSecKeyDataFormatPem,
							   password, NULL, NULL);
  if (key == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Unable to find or use certificate file"));
	return(-1);
  }
	
  /* Set key name to the file name, this is just an example! */
  if (xmlSecKeySetName(key, BAD_CAST key_file_name) < 0) {
	log_msg((LOG_ERROR_LEVEL, "Failed to set key name"));
	xmlSecKeyDestroy(key);
	return(-1);
  }
	
  /*
   * Add the key to the keys manager.  The keys manager is responsible
   * for destroying the key.
   */
  if (xmlSecCryptoAppDefaultKeysMngrAdoptKey(ctx->key_manager, key) < 0) {
	log_msg((LOG_ERROR_LEVEL, "Failed to add key to keys manager"));
	xmlSecKeyDestroy(key);
	return(-1);
  }

  return(1);
}

int
icx_load_pkcs12(Icx_ctx *ctx, const char *pkcs12_file_name,
				  const char *password)
{
  xmlSecKeyPtr key;

  if (pkcs12_file_name == NULL || *pkcs12_file_name == '\0') {
	log_msg((LOG_ERROR_LEVEL, "PKCS12 file name null or blank"));
	return(-1);
  }

  key = xmlSecCryptoAppPkcs12Load(pkcs12_file_name, password, NULL, NULL);
  if (key == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Unable to find or use PKCS12 file"));
	return(-1);
  }
	
  /* Set key name to the file name, this is just an example! */
  if (xmlSecKeySetName(key, BAD_CAST pkcs12_file_name) < 0) {
	log_msg((LOG_ERROR_LEVEL, "Failed to set PKCS12 name"));
	xmlSecKeyDestroy(key);
	return(-1);
  }
	
  /*
   * Add key to the keys manager.  Keys manager is responsible 
   * for destroying the key.
   */
  if (xmlSecCryptoAppDefaultKeysMngrAdoptKey(ctx->key_manager, key) < 0) {
	log_msg((LOG_ERROR_LEVEL, "Failed to add key to keys manager"));
	xmlSecKeyDestroy(key);
	return(-1);
  }

  return(1);
}

time_t
icx_get_current_time(Icx_ctx *ctx)
{

  return(ctx->now);
}

void
icx_set_current_time(Icx_ctx *ctx, time_t current)
{

  ctx->now = current;
}

/*
 * Reset the AudienceRestriction condition for messages to AUDIENCE.
 * If AUDIENCE is NULL, any audience string is ok.
 */
void
icx_set_audience(Icx_ctx *ctx, Dsvec *audience)
{

  if (ctx->audience != NULL) {
	/* XXX free it */
  }

  ctx->audience = audience;
}

Dsvec *
icx_get_audience(Icx_ctx *ctx)
{

  return(ctx->audience);
}

/*
 * Add AUDIENCE to the AudienceRestriction condition for messages.
 */
int
icx_add_audience(Icx_ctx *ctx, char *audience)
{

  if (ctx->audience == NULL)
	ctx->audience = dsvec_init(NULL, sizeof(char *));

  /* XXX should check syntax here instead of in icx_is_in_audience() */

  dsvec_add_ptr(ctx->audience, strdup(audience));

  return(0);
}

int
icx_set_config_audience(Icx_ctx *ctx)
{
  Kwv_pair *v;

  for (v = conf_var(CONF_INFOCARD_AUDIENCE); v != NULL; v = v->next) {
	if (icx_add_audience(ctx, v->val) == -1)
	  return(-1);
  }

  return(0);
}

/*
 * PATTERN is one of the following functions applied to URI_STR:
 *
 * a)  regex         <match-regex>
 *     regex/[flags] <match-regex>
 * (an un-anchored match)
 * (flags are: i == ignore case, e == extended regex)
 *
 * b)  host          <match-hostname>
 * (case insensitive)
 *
 * c) prefix         <match-URI>
 * (<match-URI> is a URI prefix or exact URI match of URI_STR)
 *
 * d) uri            <match-URI>
 * (<match-URI> is an exact URI match of URI_STR)
 *
 * e) any
 * (any URI_STR is ok)
 *
 * Return 1 if there is a match, 0 if there is not a match, and -1 if
 * an error occurs.
 */
int
icx_match_uri(char *pat, char *uri_str)
{
  int i;
  char *p;

  if ((p = strcaseprefix(pat, "regex")) != NULL && (*p == ' ' || *p == '/')) {
	int flags, st;
	char *errmsg;

	errmsg = NULL;
	flags = REG_NOSUB;
	if (*p == '/') {
	  p++;
	  while (*p != ' ' && *p != '\0') {
		if (*p == 'i')
		  flags |= REG_ICASE;
		else if (*p == 'e')
		  flags |= REG_EXTENDED;
		else
		  goto bad_regex;
		p++;
	  }
	}
	if (*p != ' ')
	  goto bad_regex;

	while (*p == ' ')
	  p++;
	if (*p == '\0')
	  goto bad_regex;

	st = strregex(uri_str, p, NULL, flags, NULL, &errmsg);
	if (st == -1) {
	bad_regex:
	  log_msg((LOG_ERROR_LEVEL, "Invalid URI match regex: \"%s\"", pat));
	  if (errmsg != NULL)
		log_msg((LOG_ERROR_LEVEL, "Regex error: %s", errmsg));
	  return(-1);
	}
	else if (st > 0)
	  return(1);
	else
	  return(0);
  }
#ifdef NOTDEF
  else if ((p = strcaseprefix(pat, "fgrep")) != NULL
		   && (*p == ' ' || *p == '/')) {
	int nocase, st;

	st = -1;
	nocase = 0;
	if (*p == '/') {
	  p++;
	  while (*p != ' ' && *p != '\0') {
		if (*p == 'i')
		  nocase = 1;
		else
		  goto bad_fgrep;
		p++;
	  }
	}
	if (*p != ' ')
	  goto bad_fgrep;

	while (*p == ' ')
	  p++;
	if (*p == '\0')
	  goto bad_fgrep;

	if (nocase)
	  st = (strcasestr((char *) audience, p) != NULL);
	else
	  st = (strstr((char *) audience, p) != NULL);

	  if (st == 1)
		return(1);

	  if (st != 0) {
	  bad_fgrep:
		log_msg((LOG_ERROR_LEVEL, "Invalid URI match regex: \"%s\"", pat));
		return(-1);
	  }
  }
#endif
  else if ((p = strcaseprefix(pat, "host ")) != NULL) {
	Uri *uri;

	if ((uri = uri_parse(uri_str)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "URI parse failed: \"%s\"", uri_str));
	  return(-1);
	}

	while (*p == ' ')
	  p++;
	if (*p == '\0') {
	  log_msg((LOG_ERROR_LEVEL, "Invalid host match: \"%s\"", pat));
	  return(-1);
	}

	if (strcaseeq(uri->host, p))
	  return(1);
	return(0);
  }
  else if ((p = strcaseprefix(pat, "prefix ")) != NULL) {
	int match;
	Uri *uri1, *uri2;

	if ((uri1 = uri_parse(uri_str)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "URI parse failed: \"%s\"", uri_str));
	  return(-1);
	}

	while (*p == ' ')
	  p++;
	if (*p == '\0') {
	  log_msg((LOG_ERROR_LEVEL, "Invalid prefix match: \"%s\"", pat));
	  return(-1);
	}

	if ((uri2 = uri_parse(p)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Invalid prefix parse: \"%s\"", pat));
	  return(-1);
	}

	uri_compare(uri1, uri2, &match);
	if (match == dsvec_len(uri2->path_parts))
	  return(1);
	return(0);
  }
  else if ((p = strcaseprefix(pat, "uri ")) != NULL) {
	Uri *uri1, *uri2;

	if ((uri1 = uri_parse(uri_str)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "URI parse failed: \"%s\"", uri_str));
	  return(-1);
	}

	if ((uri2 = uri_parse(p)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "Invalid prefix parse: \"%s\"", pat));
	  return(-1);
	}

	if (uri_compare(uri1, uri2, NULL))
	  return(1);
	return(0);
  }
  else if (strcaseeq(pat, "any")) {
	log_msg((LOG_TRACE_LEVEL, "Any URI is ok"));
	return(1);
  }

  log_msg((LOG_ERROR_LEVEL, "Invalid function: \"%s\"", pat));
  return(-1);
}

/*
 * Return 1 if AUDIENCE is an acceptable AudienceRestriction,
 * 0 otherwise.
 *
 * "If the IP/STS issues a SAML v1.1 bearer token, it SHOULD include a
 * saml:AudienceRestrictionCondition element restricting the token to the
 * target site URL submitted in the token request" (4.3.5.3)
 *
 * For convenience, we allow a set of acceptable audiences to be configured;
 * if any one matches, the restriction is satisfied.
 *
 * Instead of only an exact URI match, any of several matching
 * functions can be specified using the INFOCARD_AUDIENCE directive:
 *     <function> [<space>+ <arg-to-match-against-AudienceRestriction>]
 * If no INFOCARD_AUDIENCE has been set (ctx->audience is NULL),
 * *no* Audience is ok.
 */
int
icx_is_in_audience(Icx_ctx *ctx, const xmlChar *audience)
{
  int i, st;
  char *a;

  log_msg((LOG_TRACE_LEVEL, "Checking InfoCard audience \"%s\"", audience));
  if (ctx->audience == NULL) {
	log_msg((LOG_ERROR_LEVEL, "INFOCARD_AUDIENCE must be defined"));
	return(0);
  }

  for (i = 0; i < dsvec_len(ctx->audience); i++) {
	char *p;

	a = (char *) dsvec_ptr_index(ctx->audience, i);
	log_msg((LOG_DEBUG_LEVEL, "INFOCARD_AUDIENCE: \"%s\"", a));

	if ((st = icx_match_uri(a, (char *) audience)) > 0) {
	  /* A match */
	  log_msg((LOG_TRACE_LEVEL, "Matched: \"%s\"", a));
	  return(1);
	}
	else if (st == 0) {
	  /* Not a match... continue */
	  log_msg((LOG_TRACE_LEVEL, "No audience match... \"%s\"", a));
	}
	else {
	  /* An error */
	  log_msg((LOG_ERROR_LEVEL, "Invalid audience match: \"%s\"", a));
	  return(0);
	}
  }

  log_msg((LOG_TRACE_LEVEL, "No match was found"));
  return(0);
}

/*
 * In context CTX, parse and validate TOKEN.
 * If TOKEN_LEN is zero, the length of TOKEN is its strlen().
 */
int
icx_process_token(Icx_ctx *ctx, const xmlChar *token, size_t token_len)
{
  size_t len;

  if (token_len == 0)
	len = xmlStrlen(token);
  else
	len = token_len;

  return(ctx->processor(ctx, token, len));
}

void
icx_set_max_token_size(Icx_ctx *ctx, size_t max_token_size)
{

  ctx->max_token_size = max_token_size;
  log_msg((LOG_TRACE_LEVEL, "Setting maximum SAML token size to %lu bytes",
		   (unsigned long) ctx->max_token_size));
}

void
icx_set_token_processor(Icx_ctx *ctx, Icx_token_processing_func processor)
{

  if (processor == NULL)
	ctx->processor = icx_saml11_process_token;
  else
	ctx->processor = processor;
}

static void
icx_xmlSec_error_callback(const char *file, int line, const char *func,
						  const char *errorObject, const char *errorSubject,
						  int reason, const char *msg)
{

  log_msg((LOG_ERROR_LEVEL,
		   "xmlSec error %d @%s:%d, obj=\"%s\", subj=\"%s\" msg=\"%s\"",
		   reason, file, line, non_null((char *) errorObject),
		   non_null((char *) errorSubject), msg));
}

/*
 * Initialize the InfoCard system.
 * This must be called before using other functions within the library.
 */
int
icx_init(void)
{

  SSL_load_error_strings();
  SSL_library_init();
  /* NOTE: assuming PRNG is seeded automatically */

  xmlInitParser();
  LIBXML_TEST_VERSION
  xmlLoadExtDtdDefaultValue = XML_SKIP_IDS;
  xmlSubstituteEntitiesDefault(0);

  /* Init xmlsec library */
  if(xmlSecInit() < 0) {
	log_msg((LOG_ERROR_LEVEL, "xmlSec initialization failed."));
	return(-1);
  }

  /* Check loaded library version */
  if (xmlSecCheckVersion() != 1) {
	log_msg((LOG_ERROR_LEVEL,
			 "Loaded xmlSec library version is not compatible."));
	return(-1);
  }

#ifdef XMLSEC_CRYPTO_DYNAMIC_LOADING
  /* Load default crypto engine if we are supporting dynamic
   * loading for xmlsec-crypto libraries. Use the crypto library
   * name ("openssl", "nss", etc.) to load corresponding 
   * xmlsec-crypto library.
   */
  if (xmlSecCryptoDLLoadLibrary(BAD_CAST XMLSEC_CRYPTO) < 0) {
	log_msg((LOG_ERROR_LEVEL,
			 "Unable to load default xmlSec crypto library; check "
			 "that it is installed, check the shared libraries path "
			 "(LD_LIBRARY_PATH), see ldconfig(8), etc."));
	return(-1);
  }
#endif /* XMLSEC_CRYPTO_DYNAMIC_LOADING */

  if (xmlSecCryptoAppInit(NULL) < 0) {
	log_msg((LOG_ERROR_LEVEL, "xmlSec crypto app initialization failed."));
	return(-1);
  }

  if (xmlSecCryptoInit() < 0) {
	log_msg((LOG_ERROR_LEVEL, "xmlSec crypto initialization failed."));
	return(-1);
  }

  xmlSecErrorsSetCallback(icx_xmlSec_error_callback);

  return(1);
}

/*
 * Release all resources associated with infocard system.
 */
void
icx_free(void)
{

  /* Shutdown xmlsec-crypto library */
  xmlSecCryptoShutdown();

  /* Shutdown crypto library */
  xmlSecCryptoAppShutdown();

  /* Shutdown xmlsec library */
  xmlSecShutdown();

  /* Shutdown libxslt/libxml */
  xmlCleanupParser();	
}

#else

typedef struct Fid_test {
  char *b64_input;
  char *expected_fid;
} Fid_test;

/*
 * From
 * http://osis.idcommons.net/wiki/I4:FeatureTest-Selector_Constructs_Site-Specific_Identifiers_for_Self-Issued_Cards
 * and real CardSpace-generated test cases.
 */
static Fid_test fid_test[] = {
  { "XxBQlDZzkiZLZB4+Et40wS2KjfLGAw5bznQq8wW+DRg=", "L68-MZHB-GWD" },
  { "WvA5Huo0c265eAuMu0rW6XF48ghQ+cZ5Osyb+vFhMg8=", "A8E-D69T-BP5" },
  { "TKPvaDbGBMAfotk9OOPaYmJem8/rGz3kT7idXvYooOY=", "HFB-CBHD-3D9" },
  { "Zt3QnAhLn9B35ELwZ9ARcI0VCJdN1lb6n0fbqvI2Q2c=", "8WS-9H3R-Q9J" },
  { NULL,                                           NULL }
};

int
friendly_identifier_test(void)
{
  int i;
  long dec_len;
  unsigned char *dec;
  Ds *ds, *fid;

  for (i = 0; fid_test[i].b64_input != NULL; i++) {
	dec_len = mime_decode_base64(fid_test[i].b64_input, &dec);
	ds = ds_setn(NULL, dec, dec_len);
	if ((fid = icx_friendly_identifier(ds, 0)) == NULL) {
	  printf("Error: b64_input=\"%s\"\n", fid_test[i].b64_input);
	  return(-1);
	}
	printf("Friendly identifier is %s: ", ds_buf(fid));
	if (!streq(ds_buf(fid), fid_test[i].expected_fid)) {
	  printf("Error\n");
	  return(-1);
	}
	printf("ok\n");
  }

  return(0);
}

typedef struct Test_config {
  xmlChar *token;
  size_t length;
  time_t processing_time;
  char *audience;
  char *keyfile;
  char *certfile;
} Test_config;

/*
 * Get the test token, its length, processing time, audience, keyfile, and
 * certfile.
 */
static Test_config *
load_test_config(char *config_file)
{
  char *p;
  Ds *ds;
  Kwv *kwv;
  Kwv_conf conf = { " \t", "\"", NULL, KWV_CONF_DEFAULT, NULL, 8, NULL, NULL };
  Test_config *tc;

  tc = ALLOC(Test_config);
  if ((ds = ds_load_file(NULL, config_file)) == NULL)
	return(NULL);

  if ((kwv = kwv_make_sep(NULL, ds_buf(ds), &conf)) == NULL)
	return(NULL);

  if ((p = kwv_lookup_value(kwv, "time")) == NULL)
	return(NULL);
  strnum(p, STRNUM_TIME_T, &tc->processing_time);

  if ((p = kwv_lookup_value(kwv, "audience")) == NULL)
	return(NULL);
  tc->audience = p;

  if ((p = kwv_lookup_value(kwv, "keyfile")) == NULL)
	return(NULL);
  tc->keyfile = p;

  if ((p = kwv_lookup_value(kwv, "certfile")) == NULL)
	return(NULL);
  tc->certfile = p;

  if ((p = kwv_lookup_value(kwv, "token")) == NULL)
	return(NULL);
  tc->token = (xmlChar *) p;
  tc->length = strlen(p);

  return(tc);
}

static void
usage(void)
{

  fprintf(stderr, "Usage: saml [flags] [tokenfile]\n");
  fprintf(stderr, "-audience URL | none: set the audience restriction\n");
  fprintf(stderr, "-cert certfile:       load certfile into the key manager\n");
  fprintf(stderr, "-drift # | max:       specify clock drift for validation\n");
  fprintf(stderr, "-key keyfile:         load keyfile into the key manager\n");
  fprintf(stderr, "-test:                load built-in test configuration\n");
  fprintf(stderr, "-token tokenfile:     validate the SAML token in tokenfile\n");
  fprintf(stderr, "--:                   end of flag arguments\n");

  exit(1);
}

/*
 * This is currently used only for testing purposes.
 */
int
main(int argc, char **argv)
{
  int audience_flag, drift_secs, i, st, test_flag;
  char *certfile, *drift_str, *errmsg, *keyfile, *tokenfile;
  xmlChar *token;
  size_t token_len;
  Dsvec *audience;
  Icx_ctx *ctx;
  Test_config *tc;

  if (dacs_init(DACS_UTILITY_OPT, &argc, &argv, NULL, &errmsg) == -1) {
    log_msg((LOG_ERROR_LEVEL, "icx: %s", errmsg));
    usage();
	/*NOTREACHED*/
  }

  audience = NULL;
  audience_flag = 1;
  test_flag = 0;
  audience = NULL;
  certfile = NULL;
  drift_secs = ICX_DEFAULT_DRIFT_SECS;
  keyfile = NULL;
  tokenfile = NULL;

  for (i = 1; i < argc; i++) {
	if (streq(argv[i], "-audience")) {
	  if (++i == argc)
		usage();
	  if (streq(argv[i], "none"))
		audience_flag = 0;
	  else {
		if (audience == NULL)
		  audience = dsvec_init(NULL, sizeof(char *));
		dsvec_add_ptr(audience, argv[i]);
	  }
	}
	else if (streq(argv[i], "-cert")) {
	  if (++i == argc)
		usage();
	  certfile = argv[i];
	}
	else if (streq(argv[i], "-drift")) {
	  if (++i == argc)
		usage();
	  drift_str = argv[i];
	  if (streq(drift_str, "max"))
		drift_secs = -1;
	  else {
		if (strnum(drift_str, STRNUM_I, &drift_secs) == -1)
		  usage();
	  }
	}
	else if (streq(argv[i], "-help")) {
	  usage();
	  /*NOTREACHED*/
	}
	else if (streq(argv[i], "-key")) {
	  if (++i == argc)
		usage();
	  keyfile = argv[i];
	}
	else if (streq(argv[i], "-test"))
	  test_flag = 1;
	else if (streq(argv[i], "-token")) {
	  if (++i == argc)
		usage();
	  tokenfile = argv[i];
	}
	else if (streq(argv[i], "--"))
	  break;
	else
	  break;
  }

  if (test_flag) {
	printf("Testing InfoCard support...\n");
	if (friendly_identifier_test() == -1)
	  exit(1);

	printf("Loading tests/icx-test-config\n");
	if ((tc = load_test_config("tests/icx-test-config")) == NULL)
	  exit(1);

	if (keyfile == NULL)
	  keyfile = tc->keyfile;
	if (certfile == NULL)
	  certfile = tc->certfile;
	drift_secs = 10 * 60;
  }

  if (i < argc) {
	if (tokenfile != NULL)
	  usage();
	tokenfile = argv[i++];
  }

  if (i != argc)
	usage();

  if (icx_init() == -1)
	exit(1);

  ctx = icx_ctx_create();

  if (certfile != NULL) {
	if (icx_load_certificate(ctx, certfile) == -1) {
	  log_msg((LOG_ERROR_LEVEL, "Could not load certificate: \"%s\"",
			   certfile));
	  exit(1);
	}
  }

  if (keyfile != NULL) {
	if (icx_load_key(ctx, keyfile, NULL) == -1) {
	  log_msg((LOG_ERROR_LEVEL, "Could not load key: \"%s\"", keyfile));
	  exit(1);
	}
  }

  if (audience_flag) {
	if (audience == NULL) {
	  if (test_flag && tc->audience != NULL) {
		audience = dsvec_init(NULL, sizeof(char *));
		dsvec_add_ptr(audience, tc->audience);
	  }
	  else {
		log_msg((LOG_ERROR_LEVEL, "No audience was specified"));
		exit(1);
	  }
	}
  }
  icx_set_audience(ctx, audience);
  icx_set_replay_detection(ctx, NULL, NULL);

  if (test_flag)
	icx_set_current_time(ctx, tc->processing_time);

  icx_set_time_conditions(ctx, drift_secs, drift_secs);
 
  if (tokenfile == NULL) {
	token = tc->token;
	token_len = tc->length;
  }
  else {
	if (load_file(tokenfile, (char **) &token, &token_len) == -1) {
	  log_msg((LOG_ERROR_LEVEL, "Cannot load \"%s\"", tokenfile));
	  exit(1);
	}
  }

  if (test_flag)
	printf("Token processing... ");
  st = icx_process_token(ctx, token, token_len);

  icx_ctx_destroy(ctx);
  icx_free();

  if (test_flag) {
	if (st == -1)
	  printf("Error\nInfoCard tests failed!\n");
	else
	  printf("ok\nAll InfoCard tests succeeded!\n");
  }
  else {
	if (st == -1)
	  log_msg((LOG_ERROR_LEVEL, "Token is not ok"));
	else
	  log_msg((LOG_DEBUG_LEVEL, "Token is ok"));
  }

  exit(st == -1 ? 1 : 0);
}
#endif
