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

/*
 * Secure Token Service for DACS
 * An implementation of WS-Trust, it produces secure (SAML) tokens for
 * an identity provider - it issues claims for a given Information Card.
 */

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

#include "icx.h"

typedef struct AppliesToEndpoint {
  char *address;	/* A URL (Relying Party's web page?) */
  X509 *cert;			/* May be NULL */
} AppliesToEndpoint;

typedef enum {
  STS_UNSPEC_PROOF_KEY     = 0,
  STS_SYMMETRIC_PROOF_KEY  = 1,
  STS_ASYMMETRIC_PROOF_KEY = 2,
  STS_NO_PROOF_KEY         = 3
} Sts_proof_key;

/* 4.3.5.1 */
/* WS-TRUST 1.2 */
#define STS_SYMMETRIC_KEY_TYPE_URL1 \
  "http://schemas.xmlsoap.org/ws/2005/02/trust/SymmetricKey"
/* WS-TRUST 1.3 */
#define STS_SYMMETRIC_KEY_TYPE_URL2 \
  "http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey"

/* 4.3.5.2 */
/* WS-TRUST 1.2 */
#define STS_ASYMMETRIC_KEY_TYPE_URL1 \
  "http://schemas.xmlsoap.org/ws/2005/02/trust/PublicKey"
/* WS-TRUST 1.3 */
#define STS_ASYMMETRIC_KEY_TYPE_URL2 \
  "http://docs.oasis-open.org/ws-sx/ws-trust/200512/PublicKey"

/* 4.3.5.3 */
/* WS-TRUST 1.2 */
#define STS_NO_PROOF_KEY_URL1 \
  "http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey"
/* WS-TRUST 1.3 */
#define STS_NO_PROOF_KEY_URL2 \
  "http://docs.oasis-open.org/ws-sx/wstrust/200512/Bearer"

#define STS_BEARER_SUBJECT_CONFIRMATION_URI \
  "urn:oasis:names:tc:SAML:1.0:cm:bearer"
#define STS_HOLDER_OF_KEY_SUBJECT_CONFIRMATION_URI \
  "urn:oasis:names:tc:SAML:1.0:cm:holder-of-key"

static const char *log_module_name = "dacs_sts";

static Sts_request sts_request_element[] = {
  { STS_REQUEST_MESSAGE_ID,    STS_REQUEST_OPT_INFO,
	(xmlChar *) "MessageID", NULL, NULL },
  { STS_REQUEST_TO,            STS_REQUEST_OPT_INFO,
	(xmlChar *) "To", NULL, NULL },
  { STS_REQUEST_EXPIRES,       STS_REQUEST_OPT_INFO,
	(xmlChar *) "Expires", NULL, NULL },
  { STS_REQUEST_USERNAME,      STS_REQUEST_OPT_INFO,
	(xmlChar *) "Username", NULL, NULL },
  { STS_REQUEST_PASSWORD,      STS_REQUEST_OPT_INFO,
	(xmlChar *) "Password", NULL, NULL },
  { STS_REQUEST_SECTOKEN,      STS_REQUEST_OPT_INFO,
	(xmlChar *) "BinarySecurityToken", NULL, NULL },
  { STS_REQUEST_CARDID,        STS_REQUEST_REQ_INFO,
	(xmlChar *) "CardId", NULL, NULL },
  { STS_REQUEST_CARDVERSION,   STS_REQUEST_REQ_INFO,
	(xmlChar *) "CardVersion", NULL, NULL },
  { STS_REQUEST_PPID,          STS_REQUEST_OPT_INFO,
	(xmlChar *) "PPID", NULL, NULL },
  { STS_REQUEST_ENCDATA,       STS_REQUEST_OPT_NODE,
	(xmlChar *) "EncryptedData", NULL, NULL },
  { STS_REQUEST_APPLIES_TO,    STS_REQUEST_OPT_NODE,
	(xmlChar *) "AppliesTo", NULL, NULL },
  { STS_REQUEST_KEY_TYPE,      STS_REQUEST_OPT_INFO,
	(xmlChar *) "KeyType", NULL, NULL },
  { STS_REQUEST_USE_KEY,       STS_REQUEST_OPT_NODE,
	(xmlChar *) "UseKey", NULL, NULL },
  { STS_REQUEST_BINARY_SECRET, STS_REQUEST_OPT_NODE,
	(xmlChar *) "BinarySecret", NULL, NULL },
  { STS_REQUEST_UNDEF,         STS_REQUEST_ERR_INFO,
	NULL, NULL, NULL }
};

static char *
req_value(Sts_request_el el)
{
  char *val;

  val = (char *) sts_request_element[el].value;

  return(val);
}

static xmlNodePtr
req_node(Sts_request_el el)
{
  xmlNodePtr node;

  node = sts_request_element[el].node;

  return(node);
}

static xmlNodePtr
get_request_element(xmlNodePtr ptr, xmlChar *name)
{
  xmlNodePtr child, node;

  if (ptr->type == XML_ELEMENT_NODE) {
	if (xmlStreq(ptr->name, name))
	  return(ptr);

	for (child = ptr->children; child != NULL; child = child->next) {
	  if ((node = get_request_element(child, name)) != NULL)
		return(node);
	}

	while ((ptr = ptr->next) != NULL) {
	  if ((node = get_request_element(ptr, name)) != NULL)
		return(node);
	}
  }

  return(NULL);
}

static int
is_required_request_info(Sts_request *req)
{

  if (req->status == STS_REQUEST_REQ_INFO
	  || req->status == STS_REQUEST_REQ_NODE)
	return(1);

  return(0);
}

#ifdef NOTDEF
static int
is_optional_request_info(Sts_request *req)
{

  if (req->status == STS_REQUEST_OPT_INFO
	  || req->status == STS_REQUEST_OPT_NODE)
	return(1);

  return(0);
}
#endif

/*
 * Extract useful values found anywhere within the Header element of the
 * token request.
 * Return 0 if ok, -1 if any value is not found or another error occurs.
 */
static int
get_request_info(xmlNodePtr root, Sts_request *req)
{
  int i;
  xmlNodePtr env, node, ptr;

  if (root == NULL)
	return(-1);

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

  /* Search the entire document for useful info. */
  for (i = 0; req[i].name != NULL; i++) {
	if ((node = get_request_element(env, req[i].name)) == NULL) {
	  log_msg((LOG_TRACE_LEVEL, "%s element \"%s\" not found",
			   is_required_request_info(&req[i]) ? "Required" : "Optional",
			   req[i].name));
	  if (req[i].status == STS_REQUEST_REQ_INFO
		  || req[i].status == STS_REQUEST_REQ_NODE)
		return(-1);
	  req[i].node = NULL;
	  req[i].value = NULL;
	  continue;
	}

	log_msg((LOG_TRACE_LEVEL, "%s element \"%s\" found",
			 is_required_request_info(&req[i]) ? "Required" : "Optional",
			 req[i].name));
	if (req[i].status == STS_REQUEST_REQ_INFO
		|| req[i].status == STS_REQUEST_OPT_INFO) {
	  /*
	   * If an element has no content (e.g., <foo></foo>), then
	   * its children pointer will be (XXX may be?) NULL.
	   */
	  if ((ptr = node->children) == NULL)
		req[i].value = (xmlChar *) "";
	  else if (ptr->type != XML_TEXT_NODE || ptr->content == NULL)
		return(-1);
	  else
		req[i].value = ptr->content;
	}

	req[i].node = node;
  }

  return(0);
}

static xmlChar *
get_attr(xmlNodePtr node, xmlChar *name)
{
  xmlAttr *attr;

  for (attr = node->properties; attr != NULL; attr = attr->next) {
	if (attr->type != XML_ATTRIBUTE_NODE)
	  return(NULL);
	if (xmlStreq(attr->name, name)) {
	  if (attr->children == NULL || attr->children->type != XML_TEXT_NODE)
		return(NULL);
	  return(attr->children->content);
	}
  }

  return(NULL);
}

static AppliesToEndpoint *
get_request_endpoint(xmlNodePtr at_node)
{
  char *addr, *enc_cert;
  xmlNodePtr epr_node, node;
  AppliesToEndpoint *at_endpoint;
  BIO *bio;
  Ds *ds;

  if ((epr_node = xmlGetChild(at_node, (xmlChar *) "EndpointReference"))
	  == NULL)
	return(NULL);
  if ((addr = xmlGetChildText(epr_node, (xmlChar *) "Address")) == NULL)
	return(NULL);

  at_endpoint = ALLOC(AppliesToEndpoint);
  at_endpoint->address = addr;
  at_endpoint->cert = NULL;

  if ((node = xmlGetChild(epr_node, (xmlChar *) "Identity")) == NULL)
	return(at_endpoint);

  if ((node = xmlGetChild(node, (xmlChar *) "KeyInfo")) == NULL)
	return(NULL);
  if ((node = xmlGetChild(node, (xmlChar *) "X509Data")) == NULL)
	return(NULL);
  if ((enc_cert = xmlGetChildText(node, (xmlChar *) "X509Certificate")) == NULL)
	return(NULL);
  ds = pem_make_cert(enc_cert);

  bio = BIO_new_mem_buf(ds_buf(ds), ds_len(ds) - 1);
  at_endpoint->cert = PEM_read_bio_X509(bio, NULL, NULL, NULL);
  BIO_free(bio);

  return(at_endpoint);
}

/*
 * Return 1 if the token request's AppliesTo EndpointReference is
 * acceptable, -1 otherwise.
 * It's acceptable if any INFOCARD_STS_ENDPOINT directive matches it.
 */
static int
check_appliesto(AppliesToEndpoint *at_endpoint)
{
  int st;
  Kwv_pair *v;

  log_msg((LOG_TRACE_LEVEL, "Checking token request AppliesTo endpoint"));

  if (at_endpoint == NULL || at_endpoint->address == NULL)
	return(-1);

  if ((v = conf_var(CONF_INFOCARD_STS_RP_ENDPOINT)) == NULL) {
	/* No directives means no restriction. */
	log_msg((LOG_DEBUG_LEVEL, "No INFOCARD_STS_RP_ENDPOINT directives used"));
	return(1);
  }

  log_msg((LOG_TRACE_LEVEL, "AppliesTo Address is \"%s\"",
		   at_endpoint->address));
  do {
	log_msg((LOG_TRACE_LEVEL, "Match against directive: \"%s\"", v->val));
	if ((st = icx_match_uri(v->val, at_endpoint->address)) == 1) {
	  log_msg((LOG_TRACE_LEVEL, "Match ok"));
	  return(1);
	}
	if (st == -1) {
	  log_msg((LOG_ERROR_LEVEL, "Invalid INFOCARD_STS_RP_ENDPOINT: \"%s\"",
			   v->val));
	  return(-1);
	}
	log_msg((LOG_TRACE_LEVEL, "Match failed... continuing"));
  } while ((v = v->next) != NULL);

  log_msg((LOG_TRACE_LEVEL, "No INFOCARD_STS_RP_ENDPOINT matched"));
  return(-1);
}

/*
 * Make a list of the claims requested by the Relying Party.
 * Each claim is an Icx_claim.
 */
static Dsvec *
extract_request_claims(xmlNodePtr root)
{
  xmlNodePtr env, first_claim, ptr;
  Dsvec *claims;
  Icx_claim *claim;

  if (root == NULL)
	return(NULL);

  if (root->type != XML_ELEMENT_NODE
	  || !xmlStreq(root->name, (xmlChar *) "Envelope")
	  || root->next != NULL)
	return(NULL);
  env = root;

  first_claim = get_request_element(env, (xmlChar *) "ClaimType");
  if (first_claim == NULL)
	return(NULL);

  claims = dsvec_init(NULL, sizeof(Icx_claim *));
  for (ptr = first_claim; ptr != NULL; ptr = ptr->next) {
	if (xmlStreq(ptr->name, (xmlChar *) "ClaimType")) {
	  xmlChar *attrval;

	  claim = icx_new_claim();
	  if ((attrval = get_attr(ptr, (xmlChar *) "Uri")) == NULL)
		return(NULL);
	  claim->uri = strdirname((char *) attrval);
	  claim->type = icx_lookup_claim_type(claim->uri);
	  claim->name = strbasename((char *) attrval, NULL);
	  claim->label = NULL;
	  claim->value = NULL;
	  dsvec_add_ptr(claims, claim);
	  log_msg((LOG_TRACE_LEVEL, "RP requests claim: URI=\"%s\" name=\"%s\"",
			   claim->uri, claim->name));
	}
  }

#ifdef NOTDEF
claim=icx_new_claim();
claim->uri=ICX_DACS_CLAIM_URI;
claim->type = ICX_DACS_CLAIM;
claim->name = "Claim1";
claim->label = NULL;
claim->value = NULL;
dsvec_add_ptr(claims, claim);
claim=icx_new_claim();
claim->uri=ICX_DACS_CLAIM_URI;
claim->type = ICX_DACS_CLAIM;
claim->name = "Claim2";
claim->label = NULL;
claim->value = NULL;
dsvec_add_ptr(claims, claim);
#endif

  return(claims);
}

#ifdef NOTDEF
#define MODE_CBC					2
#define DIR_DECRYPT					1
#define RIJNDAEL_MAX_IV_SIZE		64
#define RIJNDAEL_MAX_KEY_SIZE		64
#define RIJNDAEL_MAXNR				14
typedef u_int8_t BYTE;
typedef struct {
  u_int8_t mode;
  u_int8_t IV[RIJNDAEL_MAX_IV_SIZE];
} cipherInstance;

typedef struct {  
  u_int8_t direction;
  int keyLen;
  char keyMaterial[RIJNDAEL_MAX_KEY_SIZE+1];
  int Nr;
  u_int32_t rk[4*(RIJNDAEL_MAXNR + 1)];
  u_int32_t ek[4*(RIJNDAEL_MAXNR + 1)];
} keyInstance;
extern int rijndael_cipherInit(cipherInstance *, u_int8_t, char *);
extern int rijndael_padDecrypt(cipherInstance *, keyInstance *, u_int8_t *,
							   int, u_int8_t *);
extern int rijndael_makeKey(keyInstance *, u_int8_t, int, char *);   
extern int rijndael_blockDecrypt(cipherInstance *cipher, keyInstance *key,
								 BYTE *input, int inputLen, BYTE *outBuffer);
#endif

/*
 * The first CipherValue is a base64 encoded encrypted private RSA key;
 * decrypt it using OPENSSL_PKCS1_OAEP_PADDING and the STS's private key.
 *
 * The second CipherValue is a base64 encoded encrypted XML document
 * (a SAML assertion) that has an AttributeValue element that is the base64
 * encoded PPID; decrypt it using the key obtained from the first CipherValue
 * and AES-256-CBC (base64 decode the second CipherValue; the first block
 * is the fixed-length IV and the remainder is the data; the decrypted
 * value may have padding on the end, which needs to be removed - the final
 * decrypted byte is the length of the padding, including the padding
 * count byte (e.g., if the padding count is 3, 3 bytes must be trimmed
 * from the end of the decrypted value).
 */
static char *
decrypt_ppid(xmlChar *cv1, xmlChar *cv2, char *privatekeyblock,
			 char *privatekeykey)
{
  int dec_len, symmetric_key_len, xmldoc_len;
  unsigned int encrypted_key_len, iv_len;
  unsigned char pcb;
  unsigned char *xmldoc;
  unsigned char *encrypted_key, *symmetric_key, *enc_token;
  long enc_token_len;
  RSA *priv_key;
  xmlChar *ppid;
  xmlParserCtxtPtr parser_ctx;
  xmlDocPtr doc;
  xmlNodePtr av, node, root;

  if ((encrypted_key_len = mime_decode_base64((char *) cv1, &encrypted_key))
	  == -1)
	return(NULL);

  priv_key = pem_load_private_key(privatekeyblock, privatekeykey);
  if (priv_key == NULL)
	return(NULL);

  log_msg((LOG_DEBUG_LEVEL, "Decrypting symmetric key..."));
  symmetric_key = (unsigned char *) malloc(RSA_size(priv_key));
  symmetric_key_len = RSA_private_decrypt(encrypted_key_len, encrypted_key,
										  symmetric_key, priv_key,
										  RSA_PKCS1_OAEP_PADDING);
  if (symmetric_key_len == -1) {
	crypto_log_error();
	return(NULL);
  }
  RSA_free(priv_key);
  log_msg((LOG_DEBUG_LEVEL, "Symmetric key (%u bytes): %s",
		   symmetric_key_len, strbtohex(symmetric_key, symmetric_key_len, 0)));

  if ((enc_token_len = mime_decode_base64((char *) cv2, &enc_token)) == -1)
	return(NULL);

  /*
   * AES has a fixed block size of 128 bits (16 bytes) and a key size of
   * 128 (16 bytes), 192 (24 bytes), or 256 bits (32 bytes), whereas Rijndael
   * can be specified with block and key sizes in any multiple of 32 bits,
   * with a minimum of 128 bits and a maximum of 256 bits.
   *
   * In Rijndael, the IV is always the same size as the key, but in AES the
   * IV is always 16 bytes.
   */
  if (crypto_cipher_iv_length("aes-256-cbc", &iv_len) == -1)
	return(NULL);
  log_msg((LOG_TRACE_LEVEL, "IV length is %u bytes", iv_len));

  log_msg((LOG_DEBUG_LEVEL, "Decrypting token..."));
  if ((xmldoc = crypto_decipher(CRYPTO_SYMMETRIC_DECRYPT, "aes-256-cbc",
								symmetric_key, &enc_token, NULL,
								enc_token + iv_len, enc_token_len - iv_len,
								NULL, &dec_len)) == NULL)
	return(NULL);

  /*
   * Get the padding count, compute the original plaintext length, and
   * null-terminate the XML document.
   */
  pcb = xmldoc[dec_len - 1];
  xmldoc_len = dec_len - pcb;
  xmldoc[xmldoc_len] = '\0';
  log_msg((LOG_DEBUG_LEVEL, "Decrypted token is ok, %u bytes", xmldoc_len));
  log_msg((LOG_TRACE_LEVEL, "Token:\n%s", xmldoc));

  log_msg((LOG_TRACE_LEVEL, "Parsing the SAML token..."));
  if ((parser_ctx = xmlNewParserCtxt()) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Unable to initialize parser context"));
	return(NULL);
  }

  doc = xmlCtxtReadMemory(parser_ctx, (const char *) xmldoc, xmldoc_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(NULL);
  }
  log_msg((LOG_TRACE_LEVEL, "Parse succeeded"));

  /* XXX should check token using icx.c:validate_saml11_token() etc. */
  log_msg((LOG_INFO_LEVEL, "SAML token will not be validated"));

  root = xmlDocGetRootElement(doc);
  if ((av = get_request_element(root, (xmlChar *) "AttributeValue")) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Cannot get AttributeValue"));
	return(NULL);
  }

  if ((node = av->children) == NULL
	  || node->type != XML_TEXT_NODE
	  || node->content == NULL) {
	log_msg((LOG_ERROR_LEVEL, "Can't find the PPID value"));
	return(NULL);
  }

  ppid = node->content;

  return((char *) ppid);
}

static char *
sts_uniqid(char *prefix)
{

  return(crypto_make_random_a64(prefix, 20));
}

/*
 * Generate an error response containing ERRMSG for MESSAGE_ID.
 * There are five fault types defined for an IP/STS (6.2)
 * 1. ic:MissingAppliesTo -
 *      request is missing Relying Party identity information
 * 2. ic:InvalidProofKey -
 *      invalid proof key specified in request
 * 3. ic:UnknownInformationCardReference -
 *      unknown InfoCard reference in request
 *      <ic:InformationCardReference>
 *        <ic:CardId>[card ID]</ic:CardId>
 *        <ic:CardVersion>[version]</ic:CardVersion>
 *      </ic:InformationCardReference>
 * 4. ic:FailedRequiredClaims -
 *      could not satisfy required claims in request
 *      <ic:ClaimType Uri="[Claim URI]" /> ...
 * 5. ic:InformationCardRefreshRequired -
 *      stale InfoCard reference specified in request; InfoCard should be
 *      refreshed
 *      <ic:InformationCardReference>
 *        <ic:CardId>[card ID]</ic:CardId>
 *        <ic:CardVersion>[version]</ic:CardVersion>
 *      </ic:InformationCardReference>
 *
 * Custom error messages (6.2.1):
 *   "Identity Providers MAY return custom error messages to Identity
 *   Selectors via SOAP faults that can be displayed by the Identity Selector
 *   user interface. The error message MUST be communicated as an
 *   S:Text element within the S:Reason element of a SOAP fault message.
 *   Multiple S:Text elements MAY be returned with different xml:lang values
 *   and the Identity Selector SHOULD use the one matching the user's locale,
 *   if possible."
 */
static Ds *
sts_error(Sts_error_type type, char *errmsg, char *message_id, Ds *detail)
{
  char *subcode;
  Ds *buf;

  buf = ds_init(NULL);
  ds_concat(buf, "<s:Envelope xmlns:s=|http://www.w3.org/2003/05/soap-envelope| xmlns:a=|http://www.w3.org/2005/08/addressing|>");
  ds_concat(buf, "<s:Header>");
  ds_concat(buf, "<a:Action s:mustUnderstand=|1|>http://www.w3.org/2005/08/addressing/soap/fault</a:Action>");
  if (message_id != NULL)
	ds_asprintf(buf, "<a:RelatesTo>%s</a:RelatesTo>", message_id);
  ds_concat(buf, "</s:Header>");
  ds_concat(buf, "<s:Body>");
  ds_concat(buf, "<s:Fault>");

  ds_concat(buf, "<s:Code>");
  ds_concat(buf, "<s:Value xmlns:a=|http://www.w3.org/2003/05/soap-envelope|>");
  /*
   * SOAP 1.2:
   *   "The message was incorrectly formed or did not contain the appropriate
   *   information in order to succeed. For example, the message could lack
   *   the proper authentication or payment information. It is generally an
   *   indication that the message is not to be resent without change."
   */
  ds_concat(buf, "a:Sender");
  ds_concat(buf, "</s:Value>");

  subcode = NULL;
  switch (type) {
  case STS_APPLIES_TO_ERR:
	subcode = "ic:MissingAppliesTo";
	break;

  case STS_PROOF_KEY_ERR:
	subcode = "ic:InvalidProofKey";
	break;

  case STS_INFOCARD_REF_ERR:
	subcode = "ic:UnknownInformationCardReference";
	break;

  case STS_REQ_CLAIMS_ERR:
	subcode = "ic:FailedRequiredClaims";
	break;

  case STS_REFRESH_ERR:
	subcode = "ic:InformationCardRefreshRequired";
	break;

  case STS_CUSTOM_ERR:
	break;

  default:
	break;
  }

  if (subcode != NULL) {
	ds_concat(buf, "<s:Subcode>");
	ds_asprintf(buf, "<s:Value xmlns:ic=|http://schemas.xmlsoap.org/ws/2005/05/identity|>%s</s:Value>", subcode);
	ds_concat(buf, "</s:Subcode>");
  }

  ds_concat(buf, "</s:Code>");

  ds_concat(buf, "<s:Reason>");
  ds_asprintf(buf, "<s:Text xml:lang=|en|>%s</s:Text>", errmsg);
  ds_concat(buf, "</s:Reason>");

  if (detail != NULL) {
	ds_concat(buf, "<s:Detail>");
	ds_concat(buf, ds_buf(detail));
	ds_concat(buf, "</s:Detail>");
  }

  ds_concat(buf, "</s:Fault>");
  ds_concat(buf, "</s:Body>");
  ds_concat(buf, "</s:Envelope>");

  /* Replace '|' with '"'. */
  ds_subst(buf, (unsigned char) '|', (unsigned char) '"');

  return(buf);
}

static Ds *
sts_saml_assertion(Dsvec *claims, char *assertion_id, char *issuer,
				   char *created, char *expires, Sts_proof_key proof_key_type,
				   Dsvec *audiences)
{
  int i;
  char *certfile, *private_keyfile, *private_key_key, *saml_digest;
  char *saml_signature;
  Ds *canonicalbuf, *cert, *saml, *signature, *signedinfo, *the_digest;
  RSA *priv_key;
  Icx_claim *claim;

  if ((private_key_key = conf_val(CONF_INFOCARD_STS_KEYFILE_PASSWORD)) == NULL)
	log_msg((LOG_DEBUG_LEVEL,
			 "No password was provided for the private key file"));

  if ((private_keyfile = conf_val(CONF_INFOCARD_STS_KEYFILE)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "INFOCARD_STS_KEYFILE must be defined"));
	return(NULL);
  }

  saml = ds_init(NULL);
  ds_asprintf(saml, "<saml:Assertion MajorVersion=|%s| MinorVersion=|%s| AssertionID=|%s| Issuer=|%s| IssueInstant=|%s| xmlns:saml=|urn:oasis:names:tc:SAML:1.0:assertion|>",
			  SAML_TOKEN_MAJOR_VERSION, SAML_TOKEN_MINOR_VERSION,
			  assertion_id, issuer, created);

  if (proof_key_type == STS_NO_PROOF_KEY && dsvec_len(audiences) > 0) {
	ds_asprintf(saml, "<saml:Conditions NotBefore=|%s| NotOnOrAfter=|%s|>",
				created, expires);
	ds_concat(saml, "<saml:AudienceRestrictionCondition>");
	/* One or more Audience elements. */
	for (i = 0; i < dsvec_len(audiences); i++) {
	  char *audience;

	  audience = (char *) dsvec_ptr_index(audiences, i);
	  ds_asprintf(saml, "<saml:Audience>%s</saml:Audience>", audience);
	}
	ds_concat(saml, "</saml:AudienceRestrictionCondition>");
	ds_concat(saml, "</saml:Conditions>");
  }
  else
	ds_asprintf(saml, "<saml:Conditions NotBefore=|%s| NotOnOrAfter=|%s| />",
				created, expires);

  ds_concat(saml, "<saml:AttributeStatement>");
  ds_concat(saml, "<saml:Subject>");
  ds_concat(saml, "<saml:SubjectConfirmation>");
  if (proof_key_type == STS_UNSPEC_PROOF_KEY)
	ds_asprintf(saml, "<saml:ConfirmationMethod>%s</saml:ConfirmationMethod>",
				STS_HOLDER_OF_KEY_SUBJECT_CONFIRMATION_URI);
  else if (proof_key_type == STS_NO_PROOF_KEY)
	ds_asprintf(saml, "<saml:ConfirmationMethod>%s</saml:ConfirmationMethod>",
				STS_BEARER_SUBJECT_CONFIRMATION_URI);
  else {
	/* XXX Unimplemented */
	ds_asprintf(saml, "<saml:ConfirmationMethod>%s</saml:ConfirmationMethod>",
				STS_HOLDER_OF_KEY_SUBJECT_CONFIRMATION_URI);
  }

  if (proof_key_type != STS_NO_PROOF_KEY) {
	/* The proof key. */
	ds_concat(saml,
			  "<dsig:KeyInfo xmlns:dsig=|http://www.w3.org/2000/09/xmldsig#|>");
	ds_concat(saml, "<dsig:X509Data>");

	if ((certfile = conf_val(CONF_INFOCARD_STS_CERTFILE)) == NULL) {
	  log_msg((LOG_ERROR_LEVEL, "INFOCARD_STS_CERTFILE must be defined"));
	  return(NULL);
	}
	cert = pem_cert_load_stripped(certfile);
	ds_concat(saml, "<dsig:X509Certificate>");
	ds_concatn(saml, ds_buf(cert), ds_len(cert) - 1);
	log_msg((LOG_DEBUG_LEVEL, "Inserted cert from %s", certfile));
	ds_concat(saml, "</dsig:X509Certificate>");

	ds_concat(saml, "</dsig:X509Data>");
	ds_concat(saml, "</dsig:KeyInfo>");
  }
  ds_concat(saml, "</saml:SubjectConfirmation>");
  ds_concat(saml, "</saml:Subject>");

  for (i = 0; i < dsvec_len(claims); i++) {
	claim = (Icx_claim *) dsvec_ptr_index(claims, i);
	ds_asprintf(saml,
				"<saml:Attribute AttributeName=|%s| AttributeNamespace=|%s|>",
				claim->name, claim->uri);
	ds_asprintf(saml, "<saml:AttributeValue>%s</saml:AttributeValue>",
				claim->value);
	ds_concat(saml, "</saml:Attribute>");
  }

  ds_concat(saml, "</saml:AttributeStatement>");

  /* Only temporary... this is removed below. */
  ds_concat(saml, "</saml:Assertion>");

  /* Replace '|' with '"'. */
  ds_subst(saml, (unsigned char) '|', (unsigned char) '"');

  canonicalbuf = icx_canonicalize_xml(saml);

  /* See, told you. */
  ds_chop(saml, strlen("</saml:Assertion>") + 1);
  ds_appendc(saml, (int) '\0');

  /* Compute the digest of the InfoCard */
  the_digest = sha1(ds_ucbuf(canonicalbuf), ds_len(canonicalbuf) - 1);

  mime_encode_base64(ds_ucbuf(the_digest), ds_len(the_digest), &saml_digest);

  signedinfo = ds_init(NULL);
  ds_concat(signedinfo, "<dsig:SignedInfo xmlns:dsig=|http://www.w3.org/2000/09/xmldsig#|>");
  ds_concat(signedinfo, "<dsig:CanonicalizationMethod Algorithm=|http://www.w3.org/2001/10/xml-exc-c14n#| />");
  ds_concat(signedinfo, "<dsig:SignatureMethod Algorithm=|http://www.w3.org/2000/09/xmldsig#rsa-sha1| />");
  ds_asprintf(signedinfo, "<dsig:Reference URI=|#%s|>", assertion_id);
  ds_concat(signedinfo, "<dsig:Transforms>");
  ds_concat(signedinfo, "<dsig:Transform Algorithm=|http://www.w3.org/2000/09/xmldsig#enveloped-signature| />");
  ds_concat(signedinfo, "<dsig:Transform Algorithm=|http://www.w3.org/2001/10/xml-exc-c14n#| />");
  ds_concat(signedinfo, "</dsig:Transforms>");
  ds_concat(signedinfo, "<dsig:DigestMethod Algorithm=|http://www.w3.org/2000/09/xmldsig#sha1| />");
  ds_asprintf(signedinfo, "<dsig:DigestValue>%s</dsig:DigestValue>",
			  saml_digest);
  ds_concat(signedinfo, "</dsig:Reference>");
  ds_concat(signedinfo, "</dsig:SignedInfo>");

  /* Replace '|' with '"'. */
  ds_subst(signedinfo, (unsigned char) '|', (unsigned char) '"');

  canonicalbuf = icx_canonicalize_xml(signedinfo);

  /* Compute the signature of the canonicalized digest */
  priv_key = pem_load_private_key(private_keyfile, private_key_key);

  signature = crypto_sign_buf(canonicalbuf, priv_key);
  RSA_free(priv_key);
  mime_encode_base64(ds_ucbuf(signature), ds_len(signature), &saml_signature);

  ds_concat(saml,
			"<dsig:Signature xmlns:dsig=|http://www.w3.org/2000/09/xmldsig#|>");
  ds_concat(saml, ds_buf(signedinfo));
  ds_asprintf(saml, "<dsig:SignatureValue>%s</dsig:SignatureValue>",
			  saml_signature);
  ds_concat(saml, "<dsig:KeyInfo>");
  ds_concat(saml, "<dsig:X509Data>");

  /* Insert the signing certificate(s) */
  if ((certfile = conf_val(CONF_INFOCARD_STS_CERTFILE)) == NULL) {
	log_msg((LOG_ERROR_LEVEL, "INFOCARD_STS_CERTFILE must be defined"));
	return(NULL);
  }
  cert = pem_cert_load_stripped(certfile);
  ds_asprintf(saml, "<dsig:X509Certificate>%s</dsig:X509Certificate>",
			  ds_buf(cert));
  ds_free(cert);

  ds_concat(saml, "</dsig:X509Data>");
  ds_concat(saml, "</dsig:KeyInfo>");
  ds_concat(saml, "</dsig:Signature>");
  ds_concat(saml, "</saml:Assertion>");

  /* Replace '|' with '"'. */
  ds_subst(saml, (unsigned char) '|', (unsigned char) '"');

  return(saml);
}

static int
sts_request_security_token_response(Ds *buf, Dsvec *claims, char *issuer,
									char *assertion_id, char *created,
									char *expires, Sts_proof_key proof_key_type,
									Dsvec *audiences)
{
  int i;
  Ds *saml;
  Icx_claim *claim;

  ds_concat(buf, "<wst:RequestSecurityTokenResponse>");
  ds_concat(buf, "<wst:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</wst:TokenType>");
  ds_concat(buf, "<wst:LifeTime>");
  ds_asprintf(buf, "<wsu:Created>%s</wsu:Created>", created);
  ds_asprintf(buf, "<wsu:Expires>%s</wsu:Expires>", expires);
  ds_concat(buf, "</wst:LifeTime>");
  
  ds_concat(buf, "<wst:RequestedSecurityToken>");
  saml = sts_saml_assertion(claims, assertion_id, issuer, created, expires,
							proof_key_type, audiences);
  ds_concatn(buf, ds_buf(saml), ds_len(saml) - 1);
  ds_concat(buf, "</wst:RequestedSecurityToken>");

  ds_concat(buf, "<wst:RequestedAttachedReference>");
  ds_concat(buf, "<wsse:SecurityTokenReference>");
  ds_concat(buf, "<wsse:KeyIdentifier ValueType=|http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID|>");
  ds_asprintf(buf, "%s", assertion_id);
  ds_concat(buf, "</wsse:KeyIdentifier>");
  ds_concat(buf, "</wsse:SecurityTokenReference>");
  ds_concat(buf, "</wst:RequestedAttachedReference>");
            
  ds_concat(buf, "<wst:RequestedUnattachedReference>");
  ds_concat(buf, "<wsse:SecurityTokenReference>");
  ds_concat(buf, "<wsse:KeyIdentifier ValueType=|http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.0#SAMLAssertionID|>");
  ds_asprintf(buf, "%s", assertion_id);
  ds_concat(buf, "</wsse:KeyIdentifier>");
  ds_concat(buf, "</wsse:SecurityTokenReference>");
  ds_concat(buf, "</wst:RequestedUnattachedReference>");
    
  ds_concat(buf, "<ic:RequestedDisplayToken>");
  ds_concat(buf, "<ic:DisplayToken xml:lang=|en-us|>");
  for (i = 0; i < dsvec_len(claims); i++) {
	claim = (Icx_claim *) dsvec_ptr_index(claims, i);
	ds_asprintf(buf, "<ic:DisplayClaim Uri=|%s/%s|>", claim->uri, claim->name);
	ds_asprintf(buf, "<ic:DisplayTag>%s</ic:DisplayTag>", claim->label);
	ds_asprintf(buf, "<ic:DisplayValue>%s</ic:DisplayValue>", claim->value);
	ds_concat(buf, "</ic:DisplayClaim>");
  }

  ds_concat(buf, "</ic:DisplayToken>");
  ds_concat(buf, "</ic:RequestedDisplayToken>");
  ds_concat(buf, "</wst:RequestSecurityTokenResponse>");

  /* Replace '|' with '"'. */
  ds_subst(buf, (unsigned char) '|', (unsigned char) '"');

  return(0);
}

static Ds *
sts_create_token(char *message_id, char *issuer, Dsvec *claims,
				 Sts_proof_key proof_key_type, Dsvec *audiences)
{
  unsigned int token_lifetime_secs;
  char *assertion_id, *created, *expires, *token_lifetime_str;
  Ds *buf;

  assertion_id = sts_uniqid(INFOCARD_ASSERTION_ID_PREFIX);

  token_lifetime_str = conf_val(CONF_INFOCARD_TOKEN_LIFETIME_SECS);
  if (token_lifetime_str != NULL) {
	if (strnum(token_lifetime_str, STRNUM_UI, &token_lifetime_secs) == -1) {
	  log_msg((LOG_ERROR_LEVEL, "Invalid INFOCARD_TOKEN_LIFETIME_SECS: %s",
			   token_lifetime_str));
	  return(NULL);
	}
  }
  else
	token_lifetime_secs = INFOCARD_DEFAULT_TOKEN_LIFETIME_SECS;
  log_msg((LOG_TRACE_LEVEL, "token_lifetime_secs=\"%u\"", token_lifetime_secs));

  created = icx_gmdatetime(0, 0);
  expires = icx_gmdatetime(0, (long) token_lifetime_secs);
  log_msg((LOG_TRACE_LEVEL, "created=\"%s\"", created));
  log_msg((LOG_TRACE_LEVEL, "expires=\"%s\"", expires));

  buf = ds_init(NULL);
  ds_concat(buf, "<?xml version=|1.0|?>");

  ds_concat(buf, "<S:Envelope xmlns:S=|http://www.w3.org/2003/05/soap-envelope| xmlns:wsa=|http://www.w3.org/2005/08/addressing| xmlns:wsu=|http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd| xmlns:wsse=|http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd| xmlns:ic=|http://schemas.xmlsoap.org/ws/2005/05/identity| xmlns:wst=|http://schemas.xmlsoap.org/ws/2005/02/trust| xmlns:xenc=|http://www.w3.org/2001/04/xmlenc| xmlns:ds=|http://www.w3.org/2000/09/xmldsig#|>");
    
  ds_concat(buf, "<S:Header>");
  ds_concat(buf, "<wsa:Action wsu:Id=|_1|>");
  ds_concat(buf, "http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Issue");
  ds_concat(buf, "</wsa:Action>");
  if (message_id != NULL) {
	ds_concat(buf, "<wsa:RelatesTo wsu:Id=|_2|>");
	ds_asprintf(buf, "%s", message_id);
	ds_concat(buf, "</wsa:RelatesTo>");
  }
  ds_concat(buf, "<wsa:To wsu:id=|3|>");
  ds_concat(buf, "http://www.w3.org/2005/08/addressing/anonymous");
  ds_concat(buf, "</wsa:To>");
  ds_concat(buf, "<wsse:Security S:mustUnderstand=|1|>");
  ds_concat(buf, "<wsu:Timestamp wsu:Id=|_6|>");
  ds_asprintf(buf, "<wsu:Created>%s</wsu:Created>", created);
  ds_asprintf(buf, "<wsu:Expires>%s</wsu:Expires>", expires);
  ds_concat(buf, "</wsu:Timestamp>");
  ds_concat(buf, "</wsse:Security>");
  ds_concat(buf, "</S:Header>");

  ds_concat(buf, "<S:Body wsu:Id=|_10|>");

  /* RequestSecurityTokenResponse */
  sts_request_security_token_response(buf, claims, issuer, assertion_id,
									  created, expires, proof_key_type,
									  audiences);

  ds_concat(buf, "</S:Body>");
  ds_concat(buf, "</S:Envelope>");

  return(buf);
}

/*
 * (9.1, 8.6.2)
 */
static char *
sts_make_ppid(char *applies_to, char *card_id)
{
  char *ppid_str;
  Uri *uri;

  ppid_str = NULL;
  if (applies_to != NULL) {
	if ((uri = uri_parse(applies_to)) != NULL) {
	  char *org_id_string;
	  Ds *org_id_bytes, *rp_ppid_seed;
	  Ds *canonical_card_id, *ppid, *v;

	  org_id_string = uri->host;
	  org_id_bytes = strbtoutf16le((unsigned char *) org_id_string,
								   strlen(org_id_string));
	  rp_ppid_seed = sha256(ds_ucbuf(org_id_bytes), ds_len(org_id_bytes));

	  canonical_card_id = sha256((unsigned char *) card_id, strlen(card_id));
	  v = ds_dsappend(NULL, 0, 2, rp_ppid_seed, canonical_card_id);
	  ppid = sha256(ds_ucbuf(v), ds_len(v));
	  mime_encode_base64(ds_ucbuf(ppid), ds_len(ppid), &ppid_str);
	}
  }

  return(ppid_str);
}

static char *item_type = ITEM_TYPE_INFOCARDS;

int
main(int argc, char **argv)
{
  int i;
  char *card_id, *message_id, *sts_auth_method, *key_type, *p;
  char *errmsg, *request_to, *token_serviceurl, *token_issuer;
  char *ppid, *private_keyfile, *private_key_key, *self_issued_ppid;
  size_t len, post_data_len;
  AppliesToEndpoint *at_endpoint;
  Ds *buf, *post_data;
  Dsvec *audiences, *claims;
  Kwv *kwv;
  Kwv_pair *v;
  Sts_error_type sts_err;
  Sts_proof_key proof_key_type;
  Uri *uri;
  xmlParserCtxtPtr parser_ctx;
  xmlDocPtr doc;
  xmlNodePtr at_node, node, root;

  parser_ctx = NULL;
  errmsg = NULL;
  claims = NULL;
  message_id = NULL;
  request_to = NULL;
  ppid = NULL;
  self_issued_ppid = NULL;

  if (dacs_init(DACS_WEB_SERVICE, &argc, &argv, &kwv, &errmsg) == -1) {
  fail:
	if (parser_ctx != NULL)
	  xmlFreeParserCtxt(parser_ctx);
	if (errmsg == NULL)
	  errmsg = "Internal error";

	buf = sts_error(STS_CUSTOM_ERR, errmsg, message_id, NULL);
	log_msg((LOG_DEBUG_LEVEL, "Error response:\n%s", ds_buf(buf)));

	/* Emit the message. */
	len = ds_len(buf);
	fprintf(stdout, "Content-Type: application/soap+xml;charset=utf-8\n");
	fprintf(stdout, "Content-Length: %u\n\n", (unsigned int) len);
	fprintf(stdout, "%s\n", ds_buf(buf));

	log_msg((LOG_ERROR_LEVEL, "%s", errmsg));
	exit(1);
  }

  if ((p = getenv("DACS_IDENTITY")) != NULL)
	log_msg((LOG_DEBUG_LEVEL, "DACS_IDENTITY=\"%s\"", p));
  if ((p = getenv("REMOTE_ADDR")) != NULL)
	log_msg((LOG_DEBUG_LEVEL, "REMOTE_ADDR=\"%s\"", p));

  if ((private_key_key = conf_val(CONF_INFOCARD_STS_KEYFILE_PASSWORD)) == NULL)
	log_msg((LOG_DEBUG_LEVEL,
			 "No password was provided for the private key file"));

  if ((private_keyfile = conf_val(CONF_INFOCARD_STS_KEYFILE)) == NULL) {
	errmsg = "INFOCARD_STS_KEYFILE must be defined";
	goto fail;
  }

  if ((token_issuer = conf_val(CONF_INFOCARD_TOKEN_ISSUER)) == NULL) {
	errmsg = "INFOCARD_TOKEN_ISSUER must be defined";
	goto fail;
  }
  if (strcaseeq(token_issuer, "self"))
	token_issuer = "http://schemas.xmlsoap.org/ws/2005/05/identity/issuer/self";

  /* XXX this could be a list or regex... */
  if ((token_serviceurl = conf_val(CONF_INFOCARD_STS_URL)) == NULL) {
	errmsg = "INFOCARD_STS_URL must be defined";
	goto fail;
  }
  log_msg((LOG_DEBUG_LEVEL, "token_serviceurl=%s", token_serviceurl));

  {
	Entity_body *eb;

	if ((eb = cgiparse_get_entity_body()) == NULL) {
	  errmsg = "No entity body?";
	  goto fail;
	}

	post_data = ds_setn(NULL, eb->body, eb->content_length);
	post_data_len = ds_len(post_data);
	ds_appendc(post_data, (int) '\0');
	log_msg((LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG,
			 "read post_data (%u bytes):\n%s",
			 post_data_len, ds_buf(post_data)));
  }

  if (!post_data_len) {
	errmsg = "No request?";
	goto fail;
  }

  if ((parser_ctx = xmlNewParserCtxt()) == NULL) {
	errmsg = "Unable to initialize parser context";
	goto fail;
  }

  doc = xmlCtxtReadMemory(parser_ctx, (const char *) ds_buf(post_data),
						  (int) post_data_len, NULL, NULL, 0);
  if (doc == NULL) {
	xmlErrorPtr err;

	err = xmlCtxtGetLastError(parser_ctx);
	errmsg = ds_xprintf("Token parse failed, non-well-formed XML: %s",
						(err != NULL) ? err->message : "null");
	goto fail;
  }

  root = xmlDocGetRootElement(doc);
  if (get_request_info(root, sts_request_element) == -1) {
	errmsg = "Cannot get request info?";
	goto fail;
  }

  for (i = 0; sts_request_element[i].name != NULL; i++) {
	if (sts_request_element[i].value != NULL) {
	  log_msg((LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG, "%s=\"%s\"",
			   xmlPathStr(sts_request_element[i].node),
			   sts_request_element[i].value));
	  if (i == STS_REQUEST_PPID) {
		Ds *fid;

		ppid = (char *) sts_request_element[i].value;
		fid = icx_friendly_identifier(ds_set(NULL, ppid), 1);
		log_msg((LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG,
				 "PPID=\"%s\"", ds_buf(fid)));
		ds_free(fid);
	  }
	}
  }

  message_id = req_value(STS_REQUEST_MESSAGE_ID);
  if (message_id != NULL)
	log_msg((LOG_TRACE_LEVEL, "MessageID=\"%s\"", message_id));

  /* Check that the request was addressed to this STS. */
  if ((request_to = req_value(STS_REQUEST_TO)) != NULL) {
	if (!streq(token_serviceurl, request_to)) {
	  errmsg = "Request's To element does not match";
	  goto fail;
	}
	log_msg((LOG_TRACE_LEVEL, "Request is addressed to this STS: \"%s\"",
			 request_to));
  }
  else
	log_msg((LOG_TRACE_LEVEL,
			 "Cannot check if request is addressed to this STS"));

  /*
   * XXX The content of an AppliesTo node seems to be an endpoint reference:
   * <wsa:EndpointReference>
   *   <wsa:Address>http://ip.fabrikam.com</wsa:Address>
   *   <wsid:Identity> <ds:KeyInfo> <ds:X509Data>
   *     <ds:X509Certificate>...</ds:X509Certificate>
   *   </ds:X509Data> </ds:KeyInfo> </wsid:Identity>
   * </wsa:EndpointReference>
   * The wsid:Identity is absent if the Relying Party does not have a
   * server cert.
   */
  if ((at_node = req_node(STS_REQUEST_APPLIES_TO)) != NULL) {
	if ((at_endpoint = get_request_endpoint(at_node)) != NULL) {
	  log_msg((LOG_TRACE_LEVEL, "Applies to endpoint reference \"%s\"",
			   at_endpoint->address));
	  if (at_endpoint->cert != NULL)
		log_msg((LOG_TRACE_LEVEL, "Endpoint reference cert is present"));
	  if (check_appliesto(at_endpoint) == -1) {
		errmsg = "Endpoint reference check failed";
		goto fail;
	  }
	  log_msg((LOG_DEBUG_LEVEL, "STS_REQUEST_APPLIES_TO permits token"));
	}
  }

  if ((card_id = req_value(STS_REQUEST_CARDID)) == NULL) {
	errmsg = "Cannot find CardId";
	goto fail;
  }
  log_msg((LOG_DEBUG_LEVEL, "CardId=\"%s\"", card_id));
  if ((uri = uri_parse(card_id)) == NULL || uri->path_parts == NULL) {
	errmsg = "Error parsing CardId";
	goto fail;
  }

  proof_key_type = STS_UNSPEC_PROOF_KEY;
  if ((key_type = req_value(STS_REQUEST_KEY_TYPE)) != NULL) {
	log_msg((LOG_TRACE_LEVEL, "KeyType=\"%s\"", key_type));
	if (streq(key_type, STS_SYMMETRIC_KEY_TYPE_URL1)
		|| streq(key_type, STS_SYMMETRIC_KEY_TYPE_URL2)) {
	  /* Symmetric key token */
	  proof_key_type = STS_SYMMETRIC_PROOF_KEY;
	  log_msg((LOG_TRACE_LEVEL, "Request is for symmetric proof key"));
	}
	else if (streq(key_type, STS_ASYMMETRIC_KEY_TYPE_URL1)
			 || streq(key_type, STS_ASYMMETRIC_KEY_TYPE_URL2)) {
	  /* Asymmetric key token */
	  proof_key_type = STS_ASYMMETRIC_PROOF_KEY;
	  log_msg((LOG_TRACE_LEVEL, "Request is for asymmetric proof key"));
	}
	else if (streq(key_type, STS_NO_PROOF_KEY_URL1)
			 || streq(key_type, STS_NO_PROOF_KEY_URL2)) {
	  /* Bearer token */
	  proof_key_type = STS_NO_PROOF_KEY;
	  log_msg((LOG_TRACE_LEVEL, "Request is for no proof key"));
	}
	else {
	  errmsg = "Unrecognized key type request";
	  goto fail;
	}
  }

  audiences = dsvec_init(NULL, sizeof(char *));
  for (v = conf_var(CONF_INFOCARD_AUDIENCE_RESTRICTION); v != NULL;
	   v = v->next) {
	dsvec_add_ptr(audiences, v->val);
  }

  if (proof_key_type == STS_NO_PROOF_KEY) {
	/*
	 * XXX it looks like the "target site URL" ("target scope") that
	 * needs to appear within a saml:AudienceRestrictionCondition element
	 * comes from the RST wst:AppliesTo element.
	 * 4.3.5.3, 4.3.3, 4.1.1.5
	 */
	if (at_node == NULL) {
	  log_msg((LOG_DEBUG_LEVEL, "There is no AppliesTo element"));
	}
	else {
	  /* XXX fish out the AppliesTo and add it to the audiences... */
	}
  }

#ifdef NOTDEF
  {
	char *identity, *username;
	DACS_name dacs_name;

	identity = (char *) dsvec_ptr_index(uri->path_parts,
										dsvec_len(uri->path_parts) - 1);
	if (*identity == '/')
	  identity++;
	log_msg((LOG_DEBUG_LEVEL, "identity=%s", identity));
	if (parse_dacs_name(identity, &dacs_name) != DACS_USER_NAME) {
	  errmsg = "Error parsing DACS identity";
	  goto fail;
	}
	username = dacs_name.username;
	log_msg((LOG_DEBUG_LEVEL, "username=%s", username));
  }
#endif

  /*
   * Check authorization/authentication.
   * Don't log the password, or at least flag it as sensitive.
   *
   * For the username/password credential, one of the following authentication
   * methods must be selected and configured:
   * o empty - no password string must be given
   * o ignore - any password string or no password string are accepted
   * o sts   - the password string must exactly match the value of the
   *           configuration variable Conf:infocard_sts_password
   * o account:<module_name> -
   *           <username> must have an account with the authentication module
   *           <module_name> and the password string must be the password for
   *           that account (not all authentication modules support this)
   * The <username> is ignored, except for the 'account' method.
   */
  errmsg = "Authentication failed";
  if (req_value(STS_REQUEST_USERNAME) != NULL) {
	/* Check username/password credential */
	log_msg((LOG_DEBUG_LEVEL, "Checking UsernamePasswordCredential"));
	if ((sts_auth_method = conf_val(CONF_INFOCARD_STS_PASSWORD_METHOD)) == NULL) {
	  errmsg = "INFOCARD_STS_PASSWORD_METHOD must be defined";
	  goto fail;
	}
	if (strcaseeq(sts_auth_method, "empty")) {
	  char *pw;

	  /* The password is no password. */
	  if ((pw = req_value(STS_REQUEST_PASSWORD)) != NULL && *pw != '\0')
		goto fail;
	}
	else if (strcaseeq(sts_auth_method, "ignore")) {
	  /* Any value is acceptable. */
	}
	else if (strcaseeq(sts_auth_method, "sts")) {
	  char *sts_passwd;

	  sts_passwd = var_ns_get_value(dacs_conf->conf_var_ns, "Conf",
									"infocard_sts_password");
	  if (sts_passwd == NULL) {
		errmsg = "infocard_sts_password must be set";
		goto fail;
	  }

	  if (!streq(req_value(STS_REQUEST_PASSWORD), sts_passwd))
		goto fail;
	}
	else if (strcaseprefix(sts_auth_method, "account:") != NULL) {
	  errmsg = "INFOCARD_STS_PASSWORD_METHOD type 'account' is unsupported";
	  goto fail;
	}
	else {
	  errmsg = ds_xprintf("Unrecognized INFOCARD_STS_PASSWORD_METHOD: %s",
						  sts_auth_method);
	  goto fail;
	}
	log_msg((LOG_DEBUG_LEVEL, "Username/Password credential is ok"));
  }
  else if (req_value(STS_REQUEST_SECTOKEN) != NULL) {
	char *cert_str, *enc_thumbprint, *given_thumbprint;
	Ds *digest;
	Mic_entry *mic;
	Vfs_handle *h;

	/*
	 * Check X.509 client cert credential
	 */

	log_msg((LOG_DEBUG_LEVEL, "Checking X509V3Credential"));
	cert_str = req_value(STS_REQUEST_SECTOKEN);
	log_msg((LOG_TRACE_LEVEL, "cert=\"%s\"", cert_str));
	digest = crypto_cert_thumbprint(cert_str, &given_thumbprint);
	mime_encode_base64(ds_ucbuf(digest), ds_len(digest), &enc_thumbprint);
	log_msg((LOG_DEBUG_LEVEL, "Given thumbprint=\"%s\" (encoded=\"%s\")",
			 given_thumbprint, enc_thumbprint));

	/* Compare with stored thumbprint */
	if ((h = vfs_open_item_type(item_type)) == NULL) {
	  errmsg = ds_xprintf("Can't open item type \"%s\"", item_type);
	  goto fail;
	}

	mic = mic_read(h, NULL, card_id);
	vfs_close(h);
	if (mic == NULL) {
	  errmsg = ds_xprintf("Cannot find managed InfoCard entry, CardId=\"%s\"",
						  card_id);
	  goto fail;
	}
	if (mic->sts_auth->thumbprint == NULL) {
	  errmsg = ds_xprintf("Cannot find stored thumbprint, CardId=\"%s\"",
						  card_id);
	  goto fail;
	}
	log_msg((LOG_DEBUG_LEVEL, "Stored thumbprint=\"%s\"",
			 ds_buf(mic->sts_auth->thumbprint)));

	if (!streq(enc_thumbprint, ds_buf(mic->sts_auth->thumbprint))) {
	  errmsg = "Thumbprint in credential does not match";
	  goto fail;
	}
	log_msg((LOG_DEBUG_LEVEL, "X509V3Credential is ok"));
  }
  else if ((node = req_node(STS_REQUEST_ENCDATA)) != NULL) {
	Mic_entry *mic;
	Vfs_handle *h;
	xmlChar *cipher_value1, *cipher_value2;
	xmlNodePtr cv1, cv2, keyinfo, ptr;

	/*
	 * Check self-issued token credential
	 * Find the two CipherValue elements inside the first KeyInfo.
	 */
	log_msg((LOG_DEBUG_LEVEL, "Checking SelfIssuedCredential"));
	if ((keyinfo = get_request_element(node, (xmlChar *) "KeyInfo")) == NULL) {
	  errmsg = "Can't find KeyInfo";
	  goto fail;
	}
	if ((cv1 = get_request_element(keyinfo, (xmlChar *) "CipherValue"))
		== NULL) {
	  errmsg = "Can't find first CipherValue";
	  goto fail;
	}
	if ((node = cv1->children) == NULL
		|| node->type != XML_TEXT_NODE
		|| node->content == NULL) {
	  errmsg = "Can't find first CipherValue value";
	  goto fail;
	}
	cipher_value1 = node->content;
	log_msg((LOG_TRACE_LEVEL, "first CipherValue=%s", (char *) cipher_value1));

	if ((ptr = keyinfo->next) == NULL) {
	  errmsg = "Can't find second CipherData";
	  goto fail;
	}
	if ((cv2 = get_request_element(ptr, (xmlChar *) "CipherValue")) == NULL) {
	  errmsg = "Can't find second CipherValue";
	  goto fail;
	}
	if ((node = cv2->children) == NULL
		|| node->type != XML_TEXT_NODE
		|| node->content == NULL) {
	  errmsg = "Can't find first CipherValue value";
	  goto fail;
	}
	cipher_value2 = node->content;
	log_msg((LOG_TRACE_LEVEL, "second CipherValue=%s", (char *) cipher_value2));

	if ((self_issued_ppid = decrypt_ppid(cipher_value1, cipher_value2,
										 private_keyfile,
										 private_key_key)) == NULL) {
	  errmsg = "Could not decrypt self-issued InfoCard PPID";
	  goto fail;
	}

	/* PPID is base64 encoded */
	log_msg((LOG_DEBUG_LEVEL | LOG_SENSITIVE_FLAG,
			 "Require self-issued PPID: %s",
			 ds_buf(icx_friendly_identifier(ds_set(NULL, self_issued_ppid), 1))));

	/* Compare with stored self-issued card's PPID */
	if ((h = vfs_open_item_type(item_type)) == NULL) {
	  errmsg = ds_xprintf("Can't open item type \"%s\"", item_type);
	  goto fail;
	}

	mic = mic_read(h, NULL, card_id);
	vfs_close(h);
	if (mic == NULL) {
	  errmsg = ds_xprintf("Cannot find managed InfoCard entry, CardId=\"%s\"",
						  card_id);
	  goto fail;
	}
	if (mic->sts_auth->self_issued_ppid == NULL) {
	  errmsg = ds_xprintf("Cannot find stored self-issued PPID, CardId=\"%s\"",
						  card_id);
	  goto fail;
	}

	if (!streq(self_issued_ppid, mic->sts_auth->self_issued_ppid)) {
	  errmsg = "Self-issued InfoCard PPID in credential does not match";
	  goto fail;
	}
	log_msg((LOG_DEBUG_LEVEL, "Self-issued card credential is ok"));
  }
  else {
	/* Kerberos V5 credential is not supported. */
	goto fail;
  }

  /*
   * Token request has been authorized, continue...
   *
   * Get the claims requested by the Relying Party
   * (mandatory plus approved optional claims)
   */
  claims = extract_request_claims(root);
  log_msg((LOG_DEBUG_LEVEL, "Extracted request claims"));

#ifdef NOTDEF
  if ((username = req_value(STS_REQUEST_USERNAME)) == NULL) {
	char *cert_str;

	if ((cert_str = sts_request_element[STS_REQUEST_SECTOKEN].value) != NULL) {
	  char *thumbprint;
	  Ds *tp;

	  tp = crypto_cert_thumbprint(cert_str, &thumbprint);
	  /* XXX map cert thumbprint to username, or fail */
	  log_msg((LOG_DEBUG_LEVEL, "Mapping thumbprint to username: %s",
			   thumbprint));
	  username = "bob";
	}
	else if (self_issued_ppid != NULL) {
	  username = "bob";
	}
	else {
	  log_msg((LOG_ERROR_LEVEL, "Cannot determine username to get claim data"));
	  return(-1);
	}
  }
#endif

  /*
   * Get the value (DisplayValue) and label (DisplayTag) for each claim that
   * needs to be included in the token.
   *
   * Regarding the PPID (4.3.4, 8.5.14, 9.1):
   *   "The PPID identifies a Subject to a Relying Party in a way such that a
   *   Subject's PPID at one Relying Party cannot be correlated with the
   *   Subject's PPID at another Relying Party. If an Identity Provider offers
   *   the PPID claim type then it MUST generate values for the claim that
   *   have this prescribed privacy characteristic using data present in the
   *   RST request.
   *   When the target scope information is sent in the token request using
   *   the wsp:AppliesTo element, that information can be used by the IP/STS
   *   to generate the appropriate PPID value. When token scope information
   *   is not sent, an Identity Selector SHOULD specify the PPID value it
   *   would like to be used in the issued token by using the ic:PPID
   *   element in the RST request. This SHOULD be produced as described in
   *   Section 4.3.4.1. The IP/STS MAY use this value as is or as an input
   *   seed to a custom function to derive a value for the PPID claim.
   *   When PPID information is included by an Identity Selector in a token
   *   request, it MUST be sent using the following schema element:
   *     <ic:ClientPseudonym>
   *       <ic:PPID> xs:base64Binary </ic:PPID>
   *     </ic:ClientPseudonym>
   *   When the target scope information is not sent in the token request to
   *   an IP/STS, the Identity Provider MUST NOT record the PPID value or
   *   any other Client Pseudonym values included in the RST message. It
   *   MUST NOT record the PPID claim value that it generates."
   *
   * The URL of the RP to which the token will be sent is supplied as the value
   * of wsp:AppliesTo when the RP possesses no certificate.
   *
   * The recommended construct uses SHA-256 to generate a 32-byte PPID,
   * which is bsd64 encoded (4.3.4.1).
   */
  if (ppid == NULL) {
	if (at_endpoint->address != NULL) {
	  ppid = sts_make_ppid(at_endpoint->address, card_id);
	  log_msg((LOG_DEBUG_LEVEL, "PPID is from AppliesTo"));
	}
  }

  if (ppid == NULL) {
	log_msg((LOG_DEBUG_LEVEL, "Note: cannot satisfy a PPID claim..."));
  }

  if ((sts_err = icx_fill_claim_values(sts_request_element, item_type,
									   card_id, ppid,
									   conf_val(CONF_FEDERATION_NAME),
									   conf_val(CONF_JURISDICTION_NAME),
									   claims)) != STS_NO_ERR) {
	errmsg = "Could not fill claim values";
	goto fail;
  }
  log_msg((LOG_DEBUG_LEVEL, "Filled request claims"));

  /*
   * Assemble the token.
   * For convenience and clarity, we will use the '|' character as a double
   * quote when assembling the message.  When the message has been composed,
   * we'll replace every '|' character with a '"'.
   * XXX There is currently no way to escape a '|'
   */
  log_msg((LOG_TRACE_LEVEL, "Creating token..."));
  buf = sts_create_token(message_id, token_issuer, claims, proof_key_type,
						 audiences);

  /* Emit the message. */
  len = ds_len(buf);
  log_msg((LOG_TRACE_LEVEL, "Content-Length: %u", len));
  log_msg((LOG_TRACE_LEVEL | LOG_SENSITIVE_FLAG,
		   "Token output:\n%s", ds_buf(buf)));

  fprintf(stdout, "Content-Type: application/soap+xml;charset=utf-8\n");
  fprintf(stdout, "Content-Length: %u\n\n", (unsigned int) len);
  fprintf(stdout, "%s\n", ds_buf(buf));

  exit(0);
}

