The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/* zxiddec.c  -  Handwritten functions for Decoding Redirect or POST bindings
 * Copyright (c) 2010 Sampo Kellomaki (sampo@iki.fi), All Rights Reserved.
 * Copyright (c) 2006-2009 Symlabs (symlabs@symlabs.com), All Rights Reserved.
 * Author: Sampo Kellomaki (sampo@iki.fi)
 * This is confidential unpublished proprietary source code of the author.
 * NO WARRANTY, not even implied warranties. Contains trade secrets.
 * Distribution prohibited unless authorized in writing.
 * Licensed under Apache License 2.0, see file COPYING.
 * $Id: zxiddec.c,v 1.10 2010-01-08 02:10:09 sampo Exp $
 *
 * 12.8.2006,  created --Sampo
 * 12.10.2007, tweaked for signing SLO and MNI --Sampo
 * 14.4.2008,  added SimpleSign --Sampo
 * 7.10.2008,  added documentation --Sampo
 * 10.3.2010,  added predecode support --Sampo
 */

#include "platform.h"  /* needed on Win32 for pthread_mutex_lock() et al. */

#include "errmac.h"
#include "zxid.h"
#include "zxidpriv.h"
#include "zxidutil.h"
#include "zxidconf.h"
#include "saml2.h"
#include "c/zx-const.h"
#include "c/zx-ns.h"
#include "c/zx-data.h"

/*() Look for issuer in all messages we support. */

/* Called by:  zxid_decode_redir_or_post, zxid_simple_idp_show_an */
struct zx_sa_Issuer_s* zxid_extract_issuer(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, struct zx_root_s* r)
{
  struct zx_sa_Issuer_s* issuer = 0;
  if      (r->Response)             issuer = r->Response->Issuer;
  else if (r->AuthnRequest)         issuer = r->AuthnRequest->Issuer;
  else if (r->LogoutRequest)        issuer = r->LogoutRequest->Issuer;
  else if (r->LogoutResponse)       issuer = r->LogoutResponse->Issuer;
  else if (r->ManageNameIDRequest)  issuer = r->ManageNameIDRequest->Issuer;
  else if (r->ManageNameIDResponse) issuer = r->ManageNameIDResponse->Issuer;
  else {
    ERR("Unknown message type in redirect, post, or simple sign binding %d", 0);
    cgi->sigval = "I";
    cgi->sigmsg = "Unknown message type (SimpleSign, Redir, or POST).";
    ses->sigres = ZXSIG_NO_SIG;
    return 0;
  }
  if (!issuer) {
    ERR("Missing issuer in redirect, post, or simple sign binding %d", 0);
    cgi->sigval = "I";
    cgi->sigmsg = "Issuer not found (SimpleSign, Redir, or POST).";
    ses->sigres = ZXSIG_NO_SIG;
    return 0;
  }
  return issuer;
}

/*(i) Decode redirect or POST binding message. zxid_saml2_redir_enc()
 * performs the opposite operation. chk_dup is really flags
 * 0x01  =  Check dup
 * 0x02  =  Avoid sig check and logging */

/* Called by:  zxid_idp_dispatch, zxid_simple_idp_show_an, zxid_sp_dispatch */
struct zx_root_s* zxid_decode_redir_or_post(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, int chk_dup)
{
  struct zx_sa_Issuer_s* issuer = 0;
  zxid_entity* meta;
  struct zx_str* ss;
  struct zx_str* logpath;
  struct zx_root_s* r = 0;
  struct zx_str id_ss;
  char id_buf[28];
  char sigbuf[512];  /* 192 should be large enough for 1024bit RSA keys */
  int simplesig = 0;
  int msglen, len;
  char* p;
  char* m2;
  char* p2;
  char* msg;
  char* b64msg;
  char* field;
  
  if (cgi->saml_resp && *cgi->saml_resp) {
    field = "SAMLResponse";
    b64msg = cgi->saml_resp;
  } else if (cgi->saml_req && *cgi->saml_req) {
    field = "SAMLRequest";
    b64msg = cgi->saml_req;
  } else {
    ERR("No SAMLRequest or SAMLResponse field?! %p", cgi);
    return 0;
  }
  
  msglen = strlen(b64msg);
  msg = ZX_ALLOC(cf->ctx, SIMPLE_BASE64_PESSIMISTIC_DECODE_LEN(msglen));
  p = unbase64_raw(b64msg, b64msg + msglen, msg, zx_std_index_64);
  *p = 0;
  DD("Msg(%s) x=%x", msg, *msg);

  /* Skip whitespace in the beginning and end of the payload to help correct POST detection. */
  for (m2 = msg; m2 < p; ++m2)
    if (!ONE_OF_4(*m2, ' ', '\t', '\015', '\012'))
      break;
  for (p2 = p-1; m2 < p2; --p2)
    if (!ONE_OF_4(*p2, ' ', '\t', '\015', '\012'))
      break;
  DD("Msg_sans_ws(%.*s) start=%x end=%x", p2-m2+1, m2, *m2, *p2);
  
  if (!(chk_dup & 0x02) && cf->log_level > 1)
    zxlog(cf, 0, 0, 0, 0, 0, 0, ZX_GET_CONTENT(ses->nameid), "N", "W", "REDIRDEC", 0, "sid(%s) len=%d", STRNULLCHK(ses->sid), msglen);

  if (*m2 == '<' && *p2 == '>') {  /* POST profiles do not compress the payload */
    len = p2 - m2 + 1;
    p = m2;
    simplesig = 1;
  } else {
    D("Detected compressed payload. [[m2(%c) %x p2(%c) %x]]", *m2, *m2, *p2, *p2);
    p = zx_zlib_raw_inflate(cf->ctx, p-msg, msg, &len);  /* Redir uses compressed payload. */
    ZX_FREE(cf->ctx, msg);
  }
  
  r = zx_dec_zx_root(cf->ctx, len, p, "decode redir or post");
  if (!r) {
    ERR("Failed to parse redir buf(%.*s)", len, p);
    zxlog(cf, 0, 0, 0, 0, 0, 0, ZX_GET_CONTENT(ses->nameid), "N", "C", "BADXML", 0, "sid(%s) bad redir", STRNULLCHK(ses->sid));
    return 0;
  }

  if (chk_dup & 0x02)
    return r;
  
  issuer = zxid_extract_issuer(cf, cgi, ses, r);
  if (!issuer)
    return 0;

  if (!cgi->sig || !*cgi->sig) {
    D("Redirect or POST was not signed at binding level %d", 0);
log_msg:
    if (cf->log_rely_msg) {
      DD("Logging... %d", 0);
      /* Path will be composed of sha1 hash of the data in p, i.e. the unbase64 data. */
      sha1_safe_base64(id_buf, len, p);
      id_buf[27] = 0;
      id_ss.len = 27;
      id_ss.s = id_buf;
      logpath = zxlog_path(cf, ZX_GET_CONTENT(issuer), &id_ss, ZXLOG_RELY_DIR, ZXLOG_WIR_KIND, 1);
      if (logpath) {
	if (chk_dup & 0x01) {
	  if (zxlog_dup_check(cf, logpath, "Redirect or POST assertion (unsigned)")) {
	    if (cf->dup_msg_fatal) {
	      cgi->err = "C Duplicate message";
	      r = 0;
	    }
	  }
	}
	id_ss.len = len;
	id_ss.s = p;
	zxlog_blob(cf, cf->log_rely_msg, logpath, &id_ss, "dec_redir_post nosig");
      }
    }
    return r;
  }

  meta = zxid_get_ent_ss(cf, ZX_GET_CONTENT(issuer));
  if (!meta) {
    ERR("Unable to find metadata for Issuer(%.*s) in Redir or SimpleSign POST binding", ZX_GET_CONTENT_LEN(issuer), ZX_GET_CONTENT_S(issuer));
    cgi->sigval = "I";
    cgi->sigmsg = "Issuer unknown - metadata exchange may be needed (SimpleSign, Redir, POST).";
    ses->sigres = ZXSIG_NO_SIG;
    goto log_msg;
  }

  /* ----- Signed at binding level ----- */
  
  if (simplesig) {
    /* In SimpleSign the signature is over data inside base64. */
    p2 = p = cgi->sigalg;
    URL_DECODE(p, p2, cgi->sigalg + strlen(cgi->sigalg));
    *p = 0;
#if 1
    /* Original SimpleSign specification was ambiguous about handling of missing
     * relay state. Literal reading of the spec seemed to say that empty relay state
     * should be part of the signature computation. This was reported by yours
     * truly to SSTC, which has since issued errata clarifying that if the relay
     * state is empty, then the RelayState label is omitted from signature
     * computation. This is also consistent with how the redirect binding works. */
    if (cgi->rs && *cgi->rs)
      ss = zx_strf(cf->ctx, "%s=%s&RelayState=%s&SigAlg=%s&Signature=%s",
		   field, msg, cgi->rs, STRNULLCHK(cgi->sigalg), STRNULLCHK(cgi->sig));
    else
      ss = zx_strf(cf->ctx, "%s=%s&SigAlg=%s&Signature=%s",
		   field, msg, STRNULLCHK(cgi->sigalg), STRNULLCHK(cgi->sig));
#else
    cgi->rs = "Fake";
    ss = zx_strf(cf->ctx, "%s=%s&RelayState=%s&SigAlg=%s&Signature=%s",
		 field, msg, STRNULLCHK(cgi->rs), STRNULLCHK(cgi->sigalg), STRNULLCHK(cgi->sig));
#endif
  } else {
    /* In Redir binding, the signature is over base64 and url encoded data. This complicates
     * life as we need to know what the URL looked like prior to CGI processing
     * such as URL decoding. As such processing is done by default to all
     * query string fields, this requires special processing. zxid_parse_cgi()
     * has special case code to prevent URL decoding of SAMLRequest and SAMLResponse
     * fields so the b64msg valiable actually has the URL encoding as well. The
     * unbase64_raw() function is smart enough to unravel the URL decoding on
     * the fly, so it all ends up working fine. */
    if (cgi->rs && *cgi->rs)
      ss = zx_strf(cf->ctx, "%s=%s&RelayState=%s&SigAlg=%s&Signature=%s",
		   field, b64msg, cgi->rs /* *** should be URL encoded or preserved? */,
		   STRNULLCHK(cgi->sigalg), STRNULLCHK(cgi->sig));
    else
      ss = zx_strf(cf->ctx, "%s=%s&SigAlg=%s&Signature=%s",
		   field, b64msg, STRNULLCHK(cgi->sigalg), STRNULLCHK(cgi->sig));
  }
  
  DD("Signed data(%.*s) len=%d sig(%s)", ss->len, ss->s, ss->len, cgi->sig);
  p2 = unbase64_raw(cgi->sig, cgi->sig + strlen(cgi->sig), sigbuf, zx_std_index_64);
  ASSERTOP(p2-sigbuf, <, sizeof(sigbuf));
  
  /* strcmp(cgi->sigalg, SIG_ALGO_RSA_SHA1) would be the right test, but as
   * SigAlg can be arbitrarily URL encoded, we make the match fuzzier. */
  if (cgi->sigalg && strstr(cgi->sigalg, "rsa-sha1")) {
    ses->sigres = zxsig_verify_data(ss->len  /* Adjust for Signature= which we log */
				    - (sizeof("&Signature=")-1 + strlen(cgi->sig)),
				    ss->s, p2-sigbuf, sigbuf,
				    meta->sign_cert, "Simple or Redir SigVfy");
    zxid_sigres_map(ses->sigres, &cgi->sigval, &cgi->sigmsg);
  } else {
    ERR("Unsupported or bad signature algorithm(%s).", STRNULLCHK(cgi->sigalg));
    cgi->sigval = "I";
    cgi->sigmsg = "Unsupported or bad signature algorithm (in SimpleSign, Redir, or POST).";
    ses->sigres = ZXSIG_NO_SIG;
  }
  
  DD("Signed data(%.*s) len=%d", ss->len, ss->s, ss->len);
  if (cf->log_rely_msg) {
    DD("Logging... %d", 0);
    sha1_safe_base64(id_buf, ss->len, ss->s);
    id_buf[27] = 0;
    id_ss.len = 27;
    id_ss.s = id_buf;
    logpath = zxlog_path(cf, ZX_GET_CONTENT(issuer), &id_ss, ZXLOG_RELY_DIR, ZXLOG_WIR_KIND, 1);
    if (logpath) {
      if (zxlog_dup_check(cf, logpath, "Redirect or POST assertion")) {
	if (cf->dup_msg_fatal) {
	  cgi->err = "C Duplicate message";
	  r = 0;
	}
      }
      zxlog_blob(cf, cf->log_rely_msg, logpath, ss, "dec_redir_post sig");
    }
  }
  zx_str_free(cf->ctx, ss);
  return r;
}

/* EOF  --  zxiddec.c */