/* Called by: zxid_soap_call_hdr_body, zxid_wsc_call */ struct zx_root_s* zxid_soap_call_raw(zxid_conf* cf, struct zx_str* url, struct zx_e_Envelope_s* env, char** ret_enve) { #ifdef USE_CURL struct zx_root_s* r; struct zx_str* ret; struct zx_str* ss; char soap_action_buf[1024]; char* soap_act; const char* env_start; ss = zx_easy_enc_elem_opt(cf, &env->gg); DD("ss(%.*s) len=%d", ss->len, ss->s, ss->len); if (cf->soap_action_hdr && strcmp(cf->soap_action_hdr,"#inhibit")) { if (!strcmp(cf->soap_action_hdr,"#same")) { if (env->Header && env->Header->Action && ZX_GET_CONTENT_S(env->Header->Action)) { snprintf(soap_action_buf,sizeof(soap_action_buf), "SOAPAction: \"%.*s\"", ZX_GET_CONTENT_LEN(env->Header->Action), ZX_GET_CONTENT_S(env->Header->Action)); soap_action_buf[sizeof(soap_action_buf)-1] = 0; soap_act = soap_action_buf; D("SOAPaction(%s)", soap_action_buf); } else { ERR("e:Envelope/e:Headers/a:Action SOAP header is malformed %p", env->Header); } } else { snprintf(soap_action_buf,sizeof(soap_action_buf), "SOAPAction: \"%s\"", cf->soap_action_hdr); soap_action_buf[sizeof(soap_action_buf)-1] = 0; soap_act = soap_action_buf; } } else soap_act = 0; ret = zxid_http_cli(cf, url->len, url->s, ss->len, ss->s, cf->wsc_soap_content_type, soap_act, 0); zx_str_free(cf->ctx, ss); if (ret_enve) *ret_enve = ret?ret->s:0; if (!ret) return 0; env_start = zxid_locate_soap_Envelope(ret->s); if (!env_start) { ERR("SOAP response does not have Envelope element url(%.*s)", url->len, url->s); D_XML_BLOB(cf, "NO ENVELOPE SOAP RESPONSE", ret->len, ret->s); ZX_FREE(cf->ctx, ret); return 0; } cf->ctx->top1 = 1; /* Stop parsing after first toplevel <e:Envelope> */ r = zx_dec_zx_root(cf->ctx, ret->len - (env_start - ret->s), env_start, "soap_call"); if (!r || !r->Envelope || !r->Envelope->Body) { ERR("Failed to parse SOAP response url(%.*s)", url->len, url->s); D_XML_BLOB(cf, "BAD SOAP RESPONSE", ret->len, ret->s); ZX_FREE(cf->ctx, ret); return 0; } return r; #else ERR("This copy of zxid was compiled to NOT use libcurl. SOAP calls (such as Artifact profile and WSC) are not supported. Add -DUSE_CURL (make ENA_CURL=1) and recompile. %d", 0); return 0; #endif }
/* Called by: zxid_call_trustpdp x6, zxid_pepmap_extract x3 */ struct zx_xac_Attribute_s* zxid_mk_xacml_simple_at(zxid_conf* cf, struct zx_elem_s* father, struct zx_str* atid, struct zx_str* attype, struct zx_str* atissuer, struct zx_str* atvalue) { struct zx_root_s* r; struct zx_xac_Attribute_s* at = zx_NEW_xac_Attribute(cf->ctx, father); at->AttributeId = zx_ref_len_attr(cf->ctx, &at->gg, zx_AttributeId_ATTR, atid->len, atid->s); at->DataType = zx_ref_len_attr(cf->ctx, &at->gg, zx_DataType_ATTR, attype->len, attype->s); if (atissuer) at->Issuer = zx_ref_len_attr(cf->ctx, &at->gg, zx_Issuer_ATTR, atissuer->len, atissuer->s); if (atvalue->s[0] == '<') { /* Looks like the value may be XML data. We need to pass it as XML data structure for * canonicalization to work right (e.g. value is an A7N that is rendered one * way when canonicalized independently, but in different way when canonicalized * as part of a bigger structure - for example sa namespace may be omitted as it * is already supplied by the parent element). */ r = zx_dec_zx_root(cf->ctx, atvalue->len, atvalue->s, "xac at parse"); if (r && r->gg.kids) { at->AttributeValue = zx_new_elem(cf->ctx, &at->gg, zx_xac_AttributeValue_ELEM); at->AttributeValue->kids = r->gg.kids; ZX_FREE(cf->ctx, r); } else { /* XML did not parse, may be its just string data, after all. */ at->AttributeValue = zx_new_str_elem(cf->ctx, &at->gg, zx_xac_AttributeValue_ELEM, atvalue); } } else { at->AttributeValue = zx_new_str_elem(cf->ctx, &at->gg, zx_xac_AttributeValue_ELEM, atvalue); } zx_reverse_elem_lists(&at->gg); return at; }
/* Called by: zxid_add_mapped_attr, zxid_map_val_ss, zxid_mk_sa_attribute */ struct zx_sa_Attribute_s* zxid_mk_sa_attribute_ss(zxid_conf* cf, struct zx_elem_s* father, const char* name, const char* namfmt, struct zx_str* val) { struct zx_root_s* r; struct zx_sa_Attribute_s* at = zx_NEW_sa_Attribute(cf->ctx, father); if (namfmt) at->NameFormat = zx_ref_attr(cf->ctx, &at->gg, zx_NameFormat_ATTR, namfmt); at->Name = zx_dup_attr(cf->ctx, &at->gg, zx_Name_ATTR, name); at->AttributeValue = zx_NEW_sa_AttributeValue(cf->ctx, &at->gg); if (!val) return at; if (val->s[0] == '<') { /* Looks like the value may be XML data. We need to pass it as XML data structure for * canonicalization to work right (e.g. value is an A7N that is rendered one * way when canonicalized independently, but in different way when canonicalized * as part of a bigger structure - for example sa namespace may be omitted as it * is already supplied by the parent element). */ r = zx_dec_zx_root(cf->ctx, val->len, val->s, "sa at parse"); if (r && r->gg.kids) { at->AttributeValue->gg.kids = r->gg.kids; switch (r->gg.kids->g.tok) { case zx_sa_Assertion_ELEM: at->AttributeValue->Assertion = (void*)r->gg.kids; break; case zx_sa_EncryptedAssertion_ELEM: at->AttributeValue->EncryptedAssertion = (void*)r->gg.kids; break; case zx_di12_ResourceOffering_ELEM: at->AttributeValue->ResourceOffering = (void*)r->gg.kids; break; case zx_a_EndpointReference_ELEM: at->AttributeValue->EndpointReference = (void*)r->gg.kids; break; } ZX_FREE(cf->ctx, r); } else { /* XML did not parse, may be its just string data, after all. */ zx_add_content(cf->ctx, &at->AttributeValue->gg, val); } } else { zx_add_content(cf->ctx, &at->AttributeValue->gg, val); } return at; }
/* Called by: zxcot_main */ static int zxid_reg_svc(zxid_conf* cf, int bs_reg, int dry_run, const char* ddimd, const char* duid) { char sha1_name[28]; char path[ZXID_MAX_BUF]; int got; fdtype fd; struct zx_root_s* r; zxid_epr* epr; struct zx_str* ss; struct zx_str* tt; read_all_fd(fdstdin, buf, sizeof(buf)-1, &got); /* Read EPR */ buf[got] = 0; r = zx_dec_zx_root(cf->ctx, got, buf, "cot reg_svc"); if (!r || !r->EndpointReference) { ERR("Failed to parse <EndpointReference> buf(%.*s)", got, buf); return 1; } epr = r->EndpointReference; if (!ZX_SIMPLE_ELEM_CHK(epr->Address)) { ERR("<EndpointReference> MUST have <Address> element buf(%.*s)", got, buf); return 1; } if (!epr->Metadata) { ERR("<EndpointReference> MUST have <Metadata> element buf(%.*s)", got, buf); return 1; } if (!ZX_SIMPLE_ELEM_CHK(epr->Metadata->ProviderID)) { ERR("<EndpointReference> MUST have <Metadata> with <ProviderID> element buf(%.*s)", got, buf); return 1; } if (!epr->Metadata->ServiceType) { ERR("<EndpointReference> MUST have <ServiceType> element buf(%.*s)", got, buf); return 1; } /* *** possibly add something here and double check the required fields are available. */ ss = zx_easy_enc_elem_opt(cf, &epr->gg); if (!ss) return 2; #if 0 // *** wrong tt = ZX_GET_CONTENT(epr->Metadata->ProviderID); #else tt = ZX_GET_CONTENT(epr->Metadata->ServiceType); #endif got = MIN(tt->len, sizeof(path)-1); memcpy(path, tt?tt->s:"", got); path[got] = 0; zxid_fold_svc(path, got); sha1_safe_base64(sha1_name, ss->len, ss->s); sha1_name[27] = 0; if (verbose) fprintf(stderr, "Registering metadata in %s%s,%s\n", ddimd, path, sha1_name); if (dry_run) { if (verbose) fprintf(stderr, "Register EPR dry run. Would have written to path(%s%s,%s). " "You may also want to\n" " touch %s.all/.bs/%s,%s\n\n", ddimd, path, sha1_name, uiddir, path, sha1_name); fflush(stdin); write_all_fd(fdstdout, ss->s, ss->len); zx_str_free(cf->ctx, ss); return 0; } D("Register EPR path(%s%s,%s) in discovery metadata.", ddimd, path, sha1_name); fd = open_fd_from_path(O_CREAT | O_WRONLY | O_TRUNC, 0666, "zxcot -b", 1, "%s%s,%s", ddimd, path, sha1_name); if (fd == BADFD) { perror("open epr for registering"); ERR("Failed to open file for writing: sha1_name(%s,%s) to service registration", path, sha1_name); zx_str_free(cf->ctx, ss); return 1; } write_all_fd(fd, ss->s, ss->len); zx_str_free(cf->ctx, ss); close_file(fd, (const char*)__FUNCTION__); if (bs_reg) { if (verbose) fprintf(stderr, "Activating bootstrap %s.all/.bs/%s,%s", duid, path, sha1_name); if (!dryrun) { fd = open_fd_from_path(O_CREAT | O_WRONLY | O_TRUNC, 0666, "zxcot -bs", 1, "%s.all/.bs/%s,%s", duid, path, sha1_name); if (fd == BADFD) { perror("open epr for bootstrap activation"); ERR("Failed to open file for writing: sha1_name(%s,%s) to bootstrap activation", path, sha1_name); return 1; } write_all_fd(fd, "", 0); close_file(fd, (const char*)__FUNCTION__); } } else { D("You may also want to activate bootstrap by\n touch %s.all/.bs/%s,%s", duid, path, sha1_name); } return 0; }
/* Called by: zxid_call_epr, zxid_wsc_prepare_call, zxid_wsc_valid_resp, zxid_wsp_decorate */ struct zx_e_Envelope_s* zxid_add_env_if_needed(zxid_conf* cf, const char* enve) { struct zx_e_Envelope_s* env; struct zx_root_s* r; struct zx_str* ret; r = zx_dec_zx_root(cf->ctx, strlen(enve), enve, "add_env"); if (!r) { ERR("Malformed XML enve(%s)", enve); return 0; } /* N.B. The lists are in reverse order after the parse. */ env = r->Envelope; if (env) { /* N.B. Maintain the forward order, Header is 1st element of Envelope->kids. */ if (!env->Header) { D("ENV EXISTS, no Header %p %p", env, env->Body); if (!env->Body) env->Body = zx_NEW_e_Body(cf->ctx, &env->gg); env->Header = zx_NEW_e_Header(cf->ctx, &env->gg); } else { D("ENV EXISTS w/Header %p %p", env, env->Body); if (!env->Body) env->Body = zx_NEW_e_Body(cf->ctx, &env->gg); } } else if (r->Body) { D("HERE2 BODY EXISTS %p %p", env, r->Header); env = zx_NEW_e_Envelope(cf->ctx,0); ZX_ADD_KID(env, Body, r->Body); if (r->Header) ZX_ADD_KID(env, Header, r->Header); else env->Header = zx_NEW_e_Header(cf->ctx, &env->gg); /* N.B. Maintain the Forward order: Header is now first element of Envelope->kids. */ } else { /* Resort to stringwise attempt to add envelope. */ ZX_FREE(cf->ctx, r); if (!memcmp(enve, "<?xml ", sizeof("<?xml ")-1)) { /* Ignore common, but unnecessary decl. */ for (enve += sizeof("<?xml "); *enve && !(enve[0] == '?' && enve[1] == '>'); ++enve) ; if (*enve) enve += 2; } /* Must be just payload */ enve = zx_alloc_sprintf(cf->ctx, 0, "%s%s%s", zx_env_body_open, enve, zx_env_body_close); D("HERE3 ADD ENV(%s)", enve); r = zx_dec_zx_root(cf->ctx, strlen(enve), enve, "add_env2"); if (!r) { ERR("Malformed XML enve(%s)", enve); return 0; } env = r->Envelope; #if 0 ret=zx_easy_enc_elem_opt(cf,&env->gg); INFO("ser(%.*s) enve(%s)",ret->len,ret->s,enve); // *** /* The lists are in reverse order after the parse. But since this is a text parse, * wireorder is maintained, thus giving forward order, afterall. */ zx_reverse_elem_lists(&env->gg); #endif } ZX_FREE(cf->ctx, r); if (env->gg.kids != &env->Header->gg) { D("ENV Fixing Header-Body ordering %p", env); env->gg.kids = &env->Header->gg; env->Header->gg.g.n = &env->Body->gg.g; env->Body->gg.g.n = 0; } ret = zx_easy_enc_elem_opt(cf,&env->gg); INFO("ser(%.*s) enve(%s)",ret->len,ret->s,enve); // *** if (!env) ERR("No <e:Envelope> found in input argument. enve(%s)", enve); /* DO NOT: zx_reverse_elem_lists(&env->gg); * ensure forward order for external use */ return env; }
/* 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; }