Example #1
0
File: zxidwsc.c Project: kiwiroy/zx
/* Called by:  covimp_test x3, zxid_call_epr, zxid_wsc_valid_resp */
int zxid_wsc_valid_re_env(zxid_conf* cf, zxid_ses* ses, const char* az_cred, struct zx_e_Envelope_s* env, const char* enve)
{
  int n_refs = 0;
  struct zxsig_ref refs[ZXID_N_WSF_SIGNED_HEADERS];
  struct timeval ourts;
  struct timeval srcts = {0,501000};
  zxid_entity* wsc_meta;
  struct zx_wsse_Security_s* sec;
  struct zx_e_Header_s* hdr;
  struct zx_str* issuer;
  struct zx_str* logpath;
  struct zx_str* relto;
  struct zx_str ss;
  zxid_cgi cgi;

  GETTIMEOFDAY(&ourts, 0);
  zxid_set_fault(cf, ses, 0);
  zxid_set_tas3_status(cf, ses, 0);

  if (cf->valid_opt & ZXID_VALID_OPT_SKIP_RESP_HDR) {
    ERR("WARNING! Important response security validations disabled by VALID_OPT=0x%x", cf->valid_opt);
    return 1;
  }
  
  if (!env) {
    ERR("No <e:Envelope> found. enve(%s)", STRNULLCHK(enve));
    zxid_set_fault(cf, ses, zxid_mk_fault(cf, 0, TAS3_PEP_RS_IN, "e:Server", "No SOAP Envelope found.", "IDStarMsgNotUnderstood", 0, 0, 0));
    return 0;
  }
  hdr = env->Header;
  if (!hdr) {
    ERR("No <e:Header> found. %d", 0);
    zxid_set_fault(cf, ses, zxid_mk_fault(cf, 0, TAS3_PEP_RS_IN, "e:Server", "No SOAP Header found.", "IDStarMsgNotUnderstood", 0, 0, 0));
    return 0;
  }
  if (!ZX_SIMPLE_ELEM_CHK(hdr->MessageID)) {
    ERR("No <a:MessageID> found. %d", 0);
    zxid_set_fault(cf, ses, zxid_mk_fault(cf, 0, TAS3_PEP_RS_IN, "e:Server", "No MessageID header found.", "IDStarMsgNotUnderstood", 0, 0, 0));
    return 0;
  }
  relto = ZX_GET_CONTENT(hdr->RelatesTo);
  if (relto && relto->len) {
    if (ses->wsc_msgid) {
      if (strlen(ses->wsc_msgid) == relto->len
	  && !memcmp(ses->wsc_msgid, relto->s, relto->len)) {
	D("RelatesTo check OK %d",1);
      } else {
	/* N.B. [SOAPBinding2] p.27, ll.818-822 indicates RelatesTo checking as SHOULD. */
	if (cf->relto_fatal) {
	  ERR("<a:RelatesTo> (%.*s) does not match request msgid(%s).", relto->len, relto->s, ses->wsc_msgid);
	  zxid_set_fault(cf, ses, zxid_mk_fault(cf, 0, TAS3_PEP_RS_IN, "e:Server", "RelatesTo in response does not match request MessageID.", "InvalidRefToMsgID", 0, 0, 0));
	  return 0;
	} else {
	  INFO("<a:RelatesTo> (%.*s) does not match request msgid(%s), but configured to ignore this error (RELTO_FATAL=0).", relto->len, relto->s, ses->wsc_msgid);
	}
      }
    } else {
      INFO("Session does not have wsc_msgid. Skipping <a:RelatesTo> check. %d",0);
    }
  } else {
    if (cf->relto_fatal) {
      ERR("No <a:RelatesTo> found. %d", 0);
      zxid_set_fault(cf, ses, zxid_mk_fault(cf, 0, TAS3_PEP_RS_IN, "e:Server", "No RelatesTo header found in reply.", "IDStarMsgNotUnderstood", 0, 0, 0));
      return 0;
    } else {
      INFO("No <a:RelatesTo> found, but configured to ignore this (RELTO_FATAL=0). %d", 0);
      D("No RelTo OK enve(%s)", STRNULLCHK(enve));
    }
  }

  if (!hdr->Sender || !hdr->Sender->providerID && !hdr->Sender->affiliationID) {
    ERR("No <b:Sender> found (or missing providerID or affiliationID). %p", hdr->Sender);
    zxid_set_fault(cf, ses, zxid_mk_fault(cf, 0, TAS3_PEP_RS_IN, "e:Server", "No b:Sender header found (or missing providerID or affiliationID).", "IDStarMsgNotUnderstood", 0, 0, 0));
    return 0;
  }
  issuer = &hdr->Sender->providerID->g;
  
  /* Validate message signature (*** add Issuer trusted check, CA validation, etc.) */
  
  if (!(sec = hdr->Security)) {
    ERR("No <wsse:Security> found. %d", 0);
    zxid_set_fault(cf, ses, zxid_mk_fault(cf, 0, TAS3_PEP_RS_IN, "e:Server", "No wsse:Security header found.", "IDStarMsgNotUnderstood", 0, 0, 0));
    return 0;
  }

  wsc_meta = zxid_get_ent_ss(cf, issuer);
  if (!wsc_meta) {
    ses->sigres = ZXSIG_NO_SIG;
    if (cf->nosig_fatal) {
      INFO("Unable to find SAML metadata for Sender(%.*s), but configured to ignore this problem (NOSIG_FATAL=0).", issuer->len, issuer->s);
    } else {
      ERR("Unable to find SAML metadata for Sender(%.*s).", issuer->len, issuer->s);
      zxid_set_fault(cf, ses, zxid_mk_fault(cf, 0, TAS3_PEP_RS_IN, "e:Server", "No unable to find SAML metadata for sender.", "ProviderIDNotValid", 0, 0, 0));
      return 0;
    }
  }
  
  if (!sec->Signature || !sec->Signature->SignedInfo || !sec->Signature->SignedInfo->Reference) {
    ses->sigres = ZXSIG_NO_SIG;
    if (cf->wsp_nosig_fatal) {
      ERR("No Security/Signature found. %p", sec->Signature);
      zxid_set_fault(cf, ses, zxid_mk_fault(cf, 0, TAS3_PEP_RS_IN, "e:Server", "No wsse:Security/ds:Signature found.", TAS3_STATUS_NOSIG, 0, 0, 0));
      return 0;
    } else {
      INFO("No Security/Signature found, but configured to ignore this problem (WSP_NOSIG_FATAL=0). %p", sec->Signature);
    }
  } else {
    ZERO(refs, sizeof(refs));
    n_refs = zxid_hunt_sig_parts(cf, n_refs, refs, sec->Signature->SignedInfo->Reference, hdr, env->Body);
    /* *** Consider adding BDY and STR */
    ses->sigres = zxsig_validate(cf->ctx, wsc_meta?wsc_meta->sign_cert:0, sec->Signature, n_refs, refs);
    zxid_sigres_map(ses->sigres, &cgi.sigval, &cgi.sigmsg);
    if (cf->sig_fatal && ses->sigres) {
      ERR("Fail due to failed message signature sigres=%d", ses->sigres);
      zxid_set_fault(cf, ses, zxid_mk_fault(cf, 0, TAS3_PEP_RS_IN, "e:Server", "Message signature did not validate.", TAS3_STATUS_BADSIG, 0, 0, 0));
      return 0;
    }
  }
  if (!zxid_timestamp_chk(cf, ses, sec->Timestamp, &ourts, &srcts, TAS3_PEP_RS_IN, "e:Server"))
    return 0;

  if (hdr->UsageDirective) {
    if (hdr->UsageDirective->Obligation && ZX_GET_CONTENT(hdr->UsageDirective->Obligation->AttributeAssignment)) {
      ses->rcvd_usagedir = zx_str_to_c(cf->ctx, ZX_GET_CONTENT(hdr->UsageDirective->Obligation->AttributeAssignment));
      D("Found TAS3 UsageDirective with obligation(%s)", ses->rcvd_usagedir);
    } else if (ZX_GET_CONTENT(hdr->UsageDirective)) {
      ses->rcvd_usagedir = zx_str_to_c(cf->ctx, ZX_GET_CONTENT(hdr->UsageDirective));
      D("Found unknown UsageDirective(%s)", ses->rcvd_usagedir);
    } else {
      ERR("UsageDirective empty or not understood. %p", hdr->UsageDirective->Dict);
    }
  }

  zxid_ses_to_pool(cf, ses);
  zxid_snarf_eprs_from_ses(cf, ses);  /* Harvest attributes and bootstrap(s) */

  if (hdr->Status && hdr->Status->code
      && (hdr->Status->code->g.len != 2
	  || hdr->Status->code->g.s[0] != 'O'
	  || hdr->Status->code->g.s[1] != 'K')) {
    ERR("TAS3 or app level error code(%.*s)", hdr->Status->code->g.len, hdr->Status->code->g.s);
    return 0;
  }
  
  /* Call Rs-In PDP */

  if (!zxid_query_ctlpt_pdp(cf, ses, az_cred, env, TAS3_PEP_RS_IN, "e:Client", cf->pepmap_rsin)) {
    return 0;
  }
  
  /* *** execute (or store for future execution) the obligations. */
  
  ss.s = (char*)enve;
  ss.len = strlen(enve);
  logpath = zxlog_path(cf, issuer, ZX_GET_CONTENT(hdr->MessageID),
		       ZXLOG_RELY_DIR, ZXLOG_MSG_KIND, 1);
  if (zxlog_dup_check(cf, logpath, "validate response")) {
    if (cf->dup_msg_fatal) {
      zxlog_blob(cf, cf->log_rely_msg, logpath, &ss, "validate response dup err");
      zxid_set_fault(cf, ses, zxid_mk_fault(cf, 0, TAS3_PEP_RS_IN, "e:Server", "Duplicate Message.", "DuplicateMsg", 0, 0, 0));
      return 0;
    } else {
      INFO("Duplicate message detected, but configured to ignore this (DUP_MSG_FATAL=0). %d",0);
    }
  }
  zxlog_blob(cf, cf->log_rely_msg, logpath, &ss, "validate response");
  zxlog(cf, &ourts, &srcts, 0, issuer, 0, ses->a7n?&ses->a7n->ID->g:0, ZX_GET_CONTENT(ses->nameid), "N", "K", "VALID", logpath->s, 0);
  return 1;
}
Example #2
0
/* 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);
  ASSERTOPI(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;
}