-rw-r--r-- | lib/Makefile.am | 7 | ||||
-rw-r--r-- | lib/basic_rp.cc | 311 | ||||
-rw-r--r-- | lib/consumer.cc | 4 | ||||
-rw-r--r-- | lib/discovery.cc | 161 | ||||
-rw-r--r-- | lib/extension.cc | 6 | ||||
-rw-r--r-- | lib/extension_chain.cc | 12 | ||||
-rw-r--r-- | lib/openid_message.cc | 228 | ||||
-rw-r--r-- | lib/params.cc | 101 | ||||
-rw-r--r-- | lib/prequeue_rp.cc | 81 | ||||
-rw-r--r-- | lib/server.cc | 2 | ||||
-rw-r--r-- | lib/sreg.cc | 54 | ||||
-rw-r--r-- | lib/util.cc | 71 |
12 files changed, 871 insertions, 167 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am index 989de28..c58ec3f 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -2,9 +2,9 @@ lib_LTLIBRARIES = libopkele.la AM_CPPFLAGS = ${CPPFLAGS_DEBUG} DEFAULT_INCLUDES = -I${top_builddir} INCLUDES = \ - -I${top_srcdir}/include/ \ + -I${top_builddir}/include/ -I${top_srcdir}/include/ \ ${KONFORKA_CFLAGS} \ ${OPENSSL_CFLAGS} \ ${LIBCURL_CPPFLAGS} \ ${PCRE_CFLAGS} ${EXPAT_CFLAGS} ${TIDY_CFLAGS} @@ -25,7 +25,10 @@ libopkele_la_SOURCES = \ extension.cc \ sreg.cc \ extension_chain.cc \ curl.cc expat.cc \ - discovery.cc + discovery.cc \ + basic_rp.cc \ + prequeue_rp.cc \ + openid_message.cc libopkele_la_LDFLAGS = \ -version-info 2:0:0 diff --git a/lib/basic_rp.cc b/lib/basic_rp.cc new file mode 100644 index 0000000..763a391 --- a/dev/null +++ b/lib/basic_rp.cc @@ -0,0 +1,311 @@ +#include <openssl/sha.h> +#include <openssl/hmac.h> +#include <opkele/basic_rp.h> +#include <opkele/exception.h> +#include <opkele/uris.h> +#include <opkele/data.h> +#include <opkele/util.h> +#include <opkele/curl.h> + +namespace opkele { + + static void dh_get_secret( + secret_t& secret, const basic_openid_message& om, + const char *exp_assoc, const char *exp_sess, + util::dh_t& dh, + size_t d_len, unsigned char *(*d_fun)(const unsigned char*,size_t,unsigned char*) ) try { + if(om.get_field("assoc_type")!=exp_assoc || om.get_field("session_type")!=exp_sess) + throw bad_input(OPKELE_CP_ "Unexpected associate response"); + util::bignum_t s_pub = util::base64_to_bignum(om.get_field("dh_server_public")); + vector<unsigned char> ck(DH_size(dh)+1); + unsigned char *ckptr = &(ck.front())+1; + int cklen = DH_compute_key(ckptr,s_pub,dh); + if(cklen<0) + throw exception_openssl(OPKELE_CP_ "failed to DH_compute_key()"); + if(cklen && (*ckptr)&0x80) { + (*(--ckptr))=0; ++cklen; } + unsigned char key_digest[d_len]; + secret.enxor_from_base64((*d_fun)(ckptr,cklen,key_digest),om.get_field("enc_mac_key")); + }catch(opkele::failed_lookup& ofl) { + throw bad_input(OPKELE_CP_ "Incoherent response from OP"); + } OPKELE_RETHROW + + static void direct_request(basic_openid_message& oum,const basic_openid_message& inm,const string& OP) { + util::curl_pick_t curl = util::curl_pick_t::easy_init(); + if(!curl) + throw exception_curl(OPKELE_CP_ "failed to initialize curl"); + string request = inm.query_string(); + CURLcode r; + (r=curl.misc_sets()) + || (r=curl.easy_setopt(CURLOPT_URL,OP.c_str())) + || (r=curl.easy_setopt(CURLOPT_POST,1)) + || (r=curl.easy_setopt(CURLOPT_POSTFIELDS,request.data())) + || (r=curl.easy_setopt(CURLOPT_POSTFIELDSIZE,request.length())) + || (r=curl.set_write()); + if(r) + throw exception_curl(OPKELE_CP_ "failed to set curly options",r); + if( (r=curl.easy_perform()) ) + throw exception_curl(OPKELE_CP_ "failed to perform curly request",r); + oum.from_keyvalues(curl.response); + } + + + assoc_t basic_RP::associate(const string& OP) { + util::dh_t dh = DH_new(); + if(!dh) + throw exception_openssl(OPKELE_CP_ "failed to DH_new()"); + dh->p = util::dec_to_bignum(data::_default_p); + dh->g = util::dec_to_bignum(data::_default_g); + if(!DH_generate_key(dh)) + throw exception_openssl(OPKELE_CP_ "failed to DH_generate_key()"); + openid_message_t req; + req.set_field("ns",OIURI_OPENID20); + req.set_field("mode","associate"); + req.set_field("dh_modulus",util::bignum_to_base64(dh->p)); + req.set_field("dh_gen",util::bignum_to_base64(dh->g)); + req.set_field("dh_consumer_public",util::bignum_to_base64(dh->pub_key)); + openid_message_t res; + req.set_field("assoc_type","HMAC-SHA256"); + req.set_field("session_type","DH-SHA256"); + secret_t secret; + int expires_in; + try { + direct_request(res,req,OP); + dh_get_secret( secret, res, + "HMAC-SHA256", "DH-SHA256", + dh, SHA256_DIGEST_LENGTH, SHA256 ); + expires_in = util::string_to_long(res.get_field("expires_in")); + }catch(exception& e) { + try { + req.set_field("assoc_type","HMAC-SHA1"); + req.set_field("session_type","DH-SHA1"); + direct_request(res,req,OP); + dh_get_secret( secret, res, + "HMAC-SHA1", "DH-SHA1", + dh, SHA_DIGEST_LENGTH, SHA1 ); + expires_in = util::string_to_long(res.get_field("expires_in")); + }catch(bad_input& e) { + throw dumb_RP(OPKELE_CP_ "OP failed to supply an association"); + } + } + return store_assoc( + OP, res.get_field("assoc_handle"), + res.get_field("assoc_type"), secret, + expires_in ); + } + + basic_openid_message& basic_RP::checkid_( + basic_openid_message& rv, + mode_t mode, + const string& return_to,const string& realm, + extension_t *ext) { + rv.reset_fields(); + rv.set_field("ns",OIURI_OPENID20); + if(mode==mode_checkid_immediate) + rv.set_field("mode","checkid_immediate"); + else if(mode==mode_checkid_setup) + rv.set_field("mode","checkid_setup"); + else + throw bad_input(OPKELE_CP_ "unknown checkid_* mode"); + if(realm.empty() && return_to.empty()) + throw bad_input(OPKELE_CP_ "At least one of realm and return_to must be non-empty"); + if(!realm.empty()) { + rv.set_field("realm",realm); + rv.set_field("trust_root",realm); + } + if(!return_to.empty()) + rv.set_field("return_to",return_to); + const openid_endpoint_t& ep = get_endpoint(); + rv.set_field("claimed_id",ep.claimed_id); + rv.set_field("identity",ep.local_id); + try { + rv.set_field("assoc_handle",find_assoc(ep.uri)->handle()); + }catch(dumb_RP& drp) { + }catch(failed_lookup& fl) { + try { + rv.set_field("assoc_handle",associate(ep.uri)->handle()); + }catch(dumb_RP& drp) { } + } OPKELE_RETHROW + if(ext) ext->checkid_hook(rv); + return rv; + } + + class signed_part_message_proxy : public basic_openid_message { + public: + const basic_openid_message& x; + set<string> signeds; + + signed_part_message_proxy(const basic_openid_message& xx) : x(xx) { + const string& slist = x.get_field("signed"); + string::size_type p = 0; + while(true) { + string::size_type co = slist.find(',',p); + string f = (co==string::npos) + ?slist.substr(p):slist.substr(p,co-p); + signeds.insert(f); + if(co==string::npos) break; + p = co+1; + } + } + + bool has_field(const string& n) const { + return signeds.find(n)!=signeds.end() && x.has_field(n); } + const string& get_field(const string& n) const { + if(signeds.find(n)==signeds.end()) + throw failed_lookup(OPKELE_CP_ "The field isn't known to be signed"); + return x.get_field(n); } + + fields_iterator fields_begin() const { + return signeds.begin(); } + fields_iterator fields_end() const { + return signeds.end(); } + }; + + static void parse_query(const string& u,string::size_type q, + map<string,string>& p) { + if(q==string::npos) + return; + assert(u[q]=='?'); + ++q; + string::size_type l = u.size(); + while(q<l) { + string::size_type eq = u.find('=',q); + string::size_type am = u.find('&',q); + if(am==string::npos) { + if(eq==string::npos) { + p[""] = u.substr(q); + }else{ + p[u.substr(q,eq-q)] = u.substr(eq+1); + } + break; + }else{ + if(eq==string::npos || eq>am) { + p[""] = u.substr(q,eq-q); + }else{ + p[u.substr(q,eq-q)] = u.substr(eq+1,am-eq-1); + } + q = ++am; + } + } + } + + void basic_RP::id_res(const basic_openid_message& om,extension_t *ext) { + bool o2 = om.has_field("ns") + && om.get_field("ns")==OIURI_OPENID20; + if( (!o2) && om.has_field("user_setup_url")) + throw id_res_setup(OPKELE_CP_ "assertion failed, setup url provided", + om.get_field("user_setup_url")); + string m = om.get_field("mode"); + if(o2 && m=="setup_needed") + throw id_res_setup(OPKELE_CP_ "setup needed, no setup url provided"); + if(m=="cancel") + throw id_res_cancel(OPKELE_CP_ "authentication cancelled"); + bool go_dumb=false; + try { + string OP = o2 + ?om.get_field("op_endpoint") + :get_endpoint().uri; + assoc_t assoc = retrieve_assoc( + OP,om.get_field("assoc_handle")); + if(om.get_field("sig")!=util::base64_signature(assoc,om)) + throw id_res_mismatch(OPKELE_CP_ "signature mismatch"); + }catch(dumb_RP& drp) { + go_dumb=true; + }catch(failed_lookup& e) { + go_dumb=true; + } OPKELE_RETHROW + if(go_dumb) { + try { + string OP = o2 + ?om.get_field("op_endpoint") + :get_endpoint().uri; + check_authentication(OP,om); + }catch(failed_check_authentication& fca) { + throw id_res_failed(OPKELE_CP_ "failed to check_authentication()"); + } OPKELE_RETHROW + } + signed_part_message_proxy signeds(om); + if(o2) { + check_nonce(om.get_field("op_endpoint"), + om.get_field("response_nonce")); + static const char *mustsign[] = { + "op_endpoint", "return_to", "response_nonce", "assoc_handle", + "claimed_id", "identity" }; + for(int ms=0;ms<(sizeof(mustsign)/sizeof(*mustsign));++ms) { + if(om.has_field(mustsign[ms]) && !signeds.has_field(mustsign[ms])) + throw bad_input(OPKELE_CP_ string("Field '")+mustsign[ms]+"' is not signed against the specs"); + } + if( ( + (om.has_field("claimed_id")?1:0) + ^ + (om.has_field("identity")?1:0) + )&1 ) + throw bad_input(OPKELE_CP_ "claimed_id and identity must be either both present or both absent"); + + string turl = util::rfc_3986_normalize_uri(get_this_url()); + util::strip_uri_fragment_part(turl); + string rurl = util::rfc_3986_normalize_uri(om.get_field("return_to")); + util::strip_uri_fragment_part(rurl); + string::size_type + tq = turl.find('?'), rq = rurl.find('?'); + if( + ((tq==string::npos)?turl:turl.substr(0,tq)) + != + ((rq==string::npos)?rurl:rurl.substr(0,rq)) + ) + throw id_res_bad_return_to(OPKELE_CP_ "return_to url doesn't match request url"); + map<string,string> tp; parse_query(turl,tq,tp); + map<string,string> rp; parse_query(rurl,rq,rp); + for(map<string,string>::const_iterator rpi=rp.begin();rpi!=rp.end();++rpi) { + map<string,string>::const_iterator tpi = tp.find(rpi->first); + if(tpi==tp.end()) + throw id_res_bad_return_to(OPKELE_CP_ string("Parameter '")+rpi->first+"' from return_to is missing from the request"); + if(tpi->second!=rpi->second) + throw id_res_bad_return_to(OPKELE_CP_ string("Parameter '")+rpi->first+"' from return_to doesn't matche the request"); + } + + if(om.has_field("claimed_id")) { + verify_OP( + om.get_field("op_endpoint"), + om.get_field("claimed_id"), + om.get_field("identity") ); + } + + } + if(ext) ext->id_res_hook(om,signeds); + } + + class check_auth_message_proxy : public basic_openid_message { + public: + const basic_openid_message& x; + + check_auth_message_proxy(const basic_openid_message& xx) : x(xx) { } + + bool has_field(const string& n) const { return x.has_field(n); } + const string& get_field(const string& n) const { + static const string checkauthmode="check_authentication"; + return (n=="mode")?checkauthmode:x.get_field(n); } + bool has_ns(const string& uri) const {return x.has_ns(uri); } + string get_ns(const string& uri) const { return x.get_ns(uri); } + fields_iterator fields_begin() const { + return x.fields_begin(); } + fields_iterator fields_end() const { + return x.fields_end(); } + }; + + void basic_RP::check_authentication(const string& OP, + const basic_openid_message& om){ + openid_message_t res; + direct_request(res,check_auth_message_proxy(om),OP); + if(res.has_field("is_valid")) { + if(res.get_field("is_valid")=="true") { + if(res.has_field("invalidate_handle")) + invalidate_assoc(OP,res.get_field("invalidate_handle")); + return; + } + } + throw failed_check_authentication( + OPKELE_CP_ "failed to verify response"); + } + +} diff --git a/lib/consumer.cc b/lib/consumer.cc index 3c3b4f8..ebda262 100644 --- a/lib/consumer.cc +++ b/lib/consumer.cc @@ -153,9 +153,9 @@ namespace opkele { }catch(failed_lookup& fl) { string ah = associate(server)->handle(); p["assoc_handle"] = ah; } - if(ext) ext->checkid_hook(p,identity); + if(ext) ext->checkid_hook(p); return p.append_query(server); } void consumer_t::id_res(const params_t& pin,const string& identity,extension_t *ext) { @@ -221,9 +221,9 @@ namespace opkele { }catch(failed_check_authentication& fca) { throw id_res_failed(OPKELE_CP_ "failed to check_authentication()"); } } - if(ext) ext->id_res_hook(pin,ps,identity); + if(ext) ext->id_res_hook(pin,ps); } void consumer_t::check_authentication(const string& server,const params_t& p) { string request = "openid.mode=check_authentication"; diff --git a/lib/discovery.cc b/lib/discovery.cc index d868308..93409f4 100644 --- a/lib/discovery.cc +++ b/lib/discovery.cc @@ -17,12 +17,29 @@ namespace opkele { using std::list; using xrd::XRD_t; using xrd::service_t; + /* TODO: the whole discovery thing needs cleanup and optimization due to + * many changes of concept. */ + static const char *whitespace = " \t\r\n"; static const char *i_leaders = "=@+$!("; static const size_t max_html = 16384; + static const struct service_type_t { + const char *uri; + const char *forceid; + } service_types[] = { + { STURI_OPENID20_OP, IDURI_SELECT20 }, + { STURI_OPENID20, 0 }, + { STURI_OPENID11, 0 }, + { STURI_OPENID10, 0 } + }; + enum { + st_index_1 = 2, st_index_2 = 1 + }; + + static inline bool is_qelement(const XML_Char *n,const char *qen) { return !strcasecmp(n,qen); } static inline bool is_element(const XML_Char *n,const char *en) { @@ -47,9 +64,9 @@ namespace opkele { public: string xri_proxy; enum { - xmode_html = 1, xmode_xrd = 2 + xmode_html = 1, xmode_xrd = 2, xmode_cid = 4 }; int xmode; string xrds_location; @@ -83,36 +100,66 @@ namespace opkele { throw exception_curl(OPKELE_CP_ "failed to set curly options",r); } ~idigger_t() throw() { } - void discover(idiscovery_t& result,const string& identity) { - result.clear(); + string discover(endpoint_discovery_iterator& oi,const string& identity) { + string rv; + idiscovery_t idis; string::size_type fsc = identity.find_first_not_of(whitespace); if(fsc==string::npos) - throw bad_input(OPKELE_CP_ "whtiespace-only identity"); + throw bad_input(OPKELE_CP_ "whitespace-only identity"); string::size_type lsc = identity.find_last_not_of(whitespace); assert(lsc!=string::npos); if(!strncasecmp(identity.c_str()+fsc,"xri://",sizeof("xri://")-1)) fsc += sizeof("xri://")-1; if((fsc+1)>=lsc) throw bad_input(OPKELE_CP_ "not a character of importance in identity"); string id(identity,fsc,lsc-fsc+1); + idis.clear(); if(strchr(i_leaders,id[0])) { - result.normalized_id = id; - result.xri_identity = true; - /* TODO: further canonicalize xri identity? Like folding case or whatever... */ - discover_at( - result, - xri_proxy + util::url_encode(id)+ - "?_xrd_r=application/xrd+xml;sep=false", xmode_xrd); - if(status_code!=100) - throw failed_xri_resolution(OPKELE_CP_ - "XRI resolution failed with '"+status_string+"' message",status_code); - if(result.xrd.canonical_ids.empty()) - throw opkele::failed_discovery(OPKELE_CP_ "No CanonicalID for XRI identity found"); - result.canonicalized_id = result.xrd.canonical_ids.begin()->second; + /* TODO: further normalize xri identity? Like folding case + * or whatever... */ + rv = idis.normalized_id = id; + idis.xri_identity = true; + set<string> cids; + for(const struct service_type_t *st=service_types; + st<&service_types[sizeof(service_types)/sizeof(*service_types)];++st) { + idis.clear(); + discover_at( idis, + xri_proxy + util::url_encode(id)+ + "?_xrd_t="+util::url_encode(st->uri)+ + "&_xrd_r=application/xrd%2Bxml" + ";sep=true;refs=true", + xmode_xrd ); + if(status_code==241) continue; + if(status_code!=100) + throw failed_xri_resolution(OPKELE_CP_ + "XRI resolution failed with '"+status_string+"' message" + ", while looking for SEP with type '"+st->uri+"'", status_code); + if(idis.xrd.canonical_ids.empty()) + throw opkele::failed_discovery(OPKELE_CP_ "No CanonicalID found for XRI identity found"); + string cid = idis.xrd.canonical_ids.begin()->second; + if(cids.find(cid)==cids.end()) { + cids.insert(cid); + idis.clear(); + discover_at( idis, + xri_proxy + util::url_encode(id)+ + "?_xrd_t="+util::url_encode(st->uri)+ + "&_xrd_r=application/xrd%2Bxml" + ";sep=true;refs=true", + xmode_xrd ); + if(status_code==241) continue; + if(status_code!=100) + throw failed_xri_resolution(OPKELE_CP_ + "XRI resolution failed with '"+status_string+"' message" + ", while looking for SEP with type '"+st->uri+"'" + " on canonical id", status_code); + } + idis.canonicalized_id = cid; + queue_endpoints(oi,idis,st); + } }else{ - result.xri_identity = false; + idis.xri_identity = false; if(id.find("://")==string::npos) id.insert(0,"http://"); string::size_type fp = id.find('#'); if(fp!=string::npos) { @@ -121,26 +168,35 @@ namespace opkele { id.erase(fp); else if(qp>fp) id.erase(fp,qp-fp); } - result.normalized_id = util::rfc_3986_normalize_uri(id); - discover_at(result,id,xmode_html|xmode_xrd); + rv = idis.normalized_id = util::rfc_3986_normalize_uri(id); + discover_at(idis,id,xmode_html|xmode_xrd); const char * eu = 0; CURLcode r = easy_getinfo(CURLINFO_EFFECTIVE_URL,&eu); if(r) throw exception_curl(OPKELE_CP_ "failed to get CURLINFO_EFFECTIVE_URL",r); - result.canonicalized_id = util::rfc_3986_normalize_uri(eu); /* XXX: strip fragment part? */ + string cid = util::strip_uri_fragment_part( idis.canonicalized_id = util::rfc_3986_normalize_uri(eu) ); if(xrds_location.empty()) { - html2xrd(result.xrd); + html2xrd(oi,idis); }else{ - discover_at(result,xrds_location,xmode_xrd); - if(result.xrd.empty()) - html2xrd(result.xrd); + idis.clear(); + idis.canonicalized_id = cid; + discover_at(idis,xrds_location,xmode_xrd); + if(idis.xrd.empty()) + html2xrd(oi,idis); + else{ + for(const service_type_t *st=service_types; + st<&service_types[sizeof(service_types)/sizeof(*service_types)];++st) + queue_endpoints(oi,idis,st); + } } } + return rv; } - void discover_at(idiscovery_t& result,const string& url,int xm) { + void discover_at(idiscovery_t& idis,const string& url,int xm) { + DOUT_("Doing discovery at " << url); CURLcode r = easy_setopt(CURLOPT_URL,url.c_str()); if(r) throw exception_curl(OPKELE_CP_ "failed to set culry urlie",r); @@ -151,9 +207,9 @@ namespace opkele { xrds_location.clear(); save_html.clear(); save_html.reserve(max_html); } - xrd = &result.xrd; + xrd = &idis.xrd; r = easy_perform(); if(r && r!=CURLE_WRITE_ERROR) throw exception_curl(OPKELE_CP_ "failed to perform curly request",r); @@ -198,19 +254,23 @@ namespace opkele { parser_choked = false; } cdata = 0; xrd_service = 0; skipping = 0; + pt_stack.clear(); status_code = 100; status_string.clear(); } - void html2xrd(XRD_t& x) { - if(!html_openid1.uris.empty()) { - html_openid1.types.insert(STURI_OPENID11); - x.services.add(-1,html_openid1); - } + void html2xrd(endpoint_discovery_iterator& oi,idiscovery_t& id) { + XRD_t& x = id.xrd; if(!html_openid2.uris.empty()) { html_openid2.types.insert(STURI_OPENID20); x.services.add(-1,html_openid2); + queue_endpoints(oi,id,&service_types[st_index_2]); + } + if(!html_openid1.uris.empty()) { + html_openid1.types.insert(STURI_OPENID11); + x.services.add(-1,html_openid1); + queue_endpoints(oi,id,&service_types[st_index_1]); } } size_t write(void *p,size_t s,size_t nm) { @@ -309,9 +369,10 @@ namespace opkele { cdata = &status_string; pt_stack.push_back(n); break; } - } + }else + ++a; } }else if(is_qelement(n,NSURI_XRD "\tExpires")) { assert(xrd); cdata_buf.clear(); @@ -435,12 +496,42 @@ namespace opkele { skipping = -1; } } + void queue_endpoints(endpoint_discovery_iterator& oi, + const idiscovery_t &id, + const service_type_t *st) { + openid_endpoint_t ep; + ep.claimed_id = id.canonicalized_id; + for(xrd::services_t::const_iterator isvc=id.xrd.services.begin(); + isvc!=id.xrd.services.end(); ++isvc) { + const xrd::service_t svc = isvc->second; + if(svc.types.find(st->uri)==svc.types.end()) continue; + for(xrd::uris_t::const_iterator iu=svc.uris.begin();iu!=svc.uris.end();++iu) { + ep.uri = iu->second; + if(st->forceid) { + ep.local_id = ep.claimed_id = st->forceid; + *(oi++) = ep; + }else{ + if(svc.local_ids.empty()) { + ep.local_id = ep.claimed_id; + *(oi++) = ep; + }else{ + for(xrd::local_ids_t::const_iterator ilid=svc.local_ids.begin(); + ilid!=svc.local_ids.end(); ++ilid) { + ep.local_id = ilid->second; + *(oi++) = ep; + } + } + } + } + } + } + }; - void idiscover(idiscovery_t& result,const string& identity) { + string idiscover(endpoint_discovery_iterator oi,const string& identity) { idigger_t idigger; - idigger.discover(result,identity); + return idigger.discover(oi,identity); } } diff --git a/lib/extension.cc b/lib/extension.cc index 8f22562..6451249 100644 --- a/lib/extension.cc +++ b/lib/extension.cc @@ -2,14 +2,14 @@ #include <opkele/extension.h> namespace opkele { - void extension_t::checkid_hook(params_t& /* p */,const string& /* identity */ ) { + void extension_t::checkid_hook(basic_openid_message&) { throw not_implemented(OPKELE_CP_ "Consumer checkid_hook not implemented"); } - void extension_t::id_res_hook(const params_t& /* p */,const params_t& /* sp */,const string& /* identity */) { + void extension_t::id_res_hook(const basic_openid_message&,const basic_openid_message&) { throw not_implemented(OPKELE_CP_ "Consumer id_res_hook not implemented"); } - void extension_t::checkid_hook(const params_t& /* pin */,params_t& /* pout */) { + void extension_t::checkid_hook(const basic_openid_message&,basic_openid_message&) { throw not_implemented(OPKELE_CP_ "Server checkid_hook not implemented"); } } diff --git a/lib/extension_chain.cc b/lib/extension_chain.cc index 16537dc..5c2afd9 100644 --- a/lib/extension_chain.cc +++ b/lib/extension_chain.cc @@ -2,15 +2,15 @@ #include <opkele/extension_chain.h> namespace opkele { - void extension_chain_t::checkid_hook(params_t& p,const string& identity) { - for(iterator i=begin();i!=end();++i) (*i)->checkid_hook(p,identity); + void extension_chain_t::checkid_hook(basic_openid_message& om){ + for(iterator i=begin();i!=end();++i) (*i)->checkid_hook(om); } - void extension_chain_t::id_res_hook(const params_t& p,const params_t& sp,const string& identity) { - for(iterator i=begin();i!=end();++i) (*i)->id_res_hook(p,sp,identity); + void extension_chain_t::id_res_hook(const basic_openid_message& om,const basic_openid_message& sp) { + for(iterator i=begin();i!=end();++i) (*i)->id_res_hook(om,sp); } - void extension_chain_t::checkid_hook(const params_t& pin,params_t& pout) { - for(iterator i=begin();i!=end();++i) (*i)->checkid_hook(pin,pout); + void extension_chain_t::checkid_hook(const basic_openid_message& inm,basic_openid_message& oum) { + for(iterator i=begin();i!=end();++i) (*i)->checkid_hook(inm,oum); } } diff --git a/lib/openid_message.cc b/lib/openid_message.cc new file mode 100644 index 0000000..3b08748 --- a/dev/null +++ b/lib/openid_message.cc @@ -0,0 +1,228 @@ +#include <cassert> +#include <opkele/types.h> +#include <opkele/exception.h> +#include <opkele/util.h> +#include <opkele/debug.h> + +#include "config.h" + +namespace opkele { + using std::input_iterator_tag; + using std::unary_function; + + struct __om_copier : public unary_function<const string&,void> { + public: + const basic_openid_message& from; + basic_openid_message& to; + + __om_copier(basic_openid_message& to,const basic_openid_message& from) + : from(from), to(to) { + to.reset_fields(); + } + + result_type operator()(argument_type f) { + to.set_field(f,from.get_field(f)); } + }; + + basic_openid_message::basic_openid_message(const basic_openid_message& x) { + x.copy_to(*this); + } + void basic_openid_message::copy_to(basic_openid_message& x) const { + for_each(fields_begin(),fields_end(), + __om_copier(x,*this) ); + } + + struct __om_ns_finder : public unary_function<const string&,bool> { + public: + const basic_openid_message& om; + const string& uri; + + __om_ns_finder(const basic_openid_message& om, + const string& uri) : om(om), uri(uri) { } + + result_type operator()(argument_type f) { + return + (!strncmp(f.c_str(),"ns.",sizeof("ns.")-1)) + && om.get_field(f)==uri ; + } + }; + + bool basic_openid_message::has_ns(const string& uri) const { + fields_iterator ei = fields_end(); + fields_iterator i = find_if(fields_begin(),fields_end(), + __om_ns_finder(*this,uri)); + return !(i==ei); + } + string basic_openid_message::get_ns(const string& uri) const { + fields_iterator ei = fields_end(); + fields_iterator i = find_if(fields_begin(),fields_end(), + __om_ns_finder(*this,uri)); + if(i==ei) + throw failed_lookup(OPKELE_CP_ string("failed to find namespace ")+uri); + return i->substr(3); + } + + struct __om_query_builder : public unary_function<const string&,void> { + public: + const basic_openid_message& om; + string& rv; + bool first; + + __om_query_builder(string& rv,const basic_openid_message& om) + : om(om), first(true), rv(rv) { + for_each(om.fields_begin(),om.fields_end(),*this); + } + __om_query_builder(string& rv,const basic_openid_message& om,const string& url) + : om(om), first(true), rv(rv) { + rv = url; + if(rv.find('?')==string::npos) + rv += '?'; + else + first = false; + for_each(om.fields_begin(),om.fields_end(),*this); + } + + result_type operator()(argument_type f) { + if(first) + first = false; + else + rv += '&'; + rv += "openid."; rv+= f; + rv += '='; + rv += util::url_encode(om.get_field(f)); + } + }; + + string basic_openid_message::append_query(const string& url) const { + string rv; + return __om_query_builder(rv,*this,url).rv; + } + string basic_openid_message::query_string() const { + string rv; + return __om_query_builder(rv,*this).rv; + } + + void basic_openid_message::reset_fields() { + throw not_implemented(OPKELE_CP_ "reset_fields() not implemented"); + } + void basic_openid_message::set_field(const string& n,const string& v) { + throw not_implemented(OPKELE_CP_ "set_field() not implemented"); + } + void basic_openid_message::reset_field(const string& n) { + throw not_implemented(OPKELE_CP_ "reset_field() not implemented"); + } + + void basic_openid_message::from_keyvalues(const string& kv) { + reset_fields(); + string::size_type p = 0; + while(true) { + string::size_type co = kv.find(':',p); + if(co==string::npos) + break; +#ifndef POSTELS_LAW + string::size_type nl = kv.find('\n',co+1); + if(nl==string::npos) + throw bad_input(OPKELE_CP_ "malformed input"); + if(nl>co) + insert(value_type(kv.substr(p,co-p),kv.substr(co+1,nl-co-1))); + p = nl+1; +#else /* POSTELS_LAW */ + string::size_type lb = kv.find_first_of("\r\n",co+1); + if(lb==string::npos) { + set_field(kv.substr(p,co-p),kv.substr(co+1)); + break; + } + if(lb>co) + set_field(kv.substr(p,co-p),kv.substr(co+1,lb-co-1)); + string::size_type nolb = kv.find_first_not_of("\r\n",lb); + if(nolb==string::npos) + break; + p = nolb; +#endif /* POSTELS_LAW */ + } + } + + void basic_openid_message::add_to_signed(const string& fields) { + string::size_type fnc = fields.find_first_not_of(","); + if(fnc==string::npos) + throw bad_input(OPKELE_CP_ "Trying to add nothing in particular to the list of signed fields"); + string signeds; + try { + signeds = get_field("signed"); + string::size_type lnc = signeds.find_last_not_of(","); + if(lnc==string::npos) + signeds.assign(fields,fnc,fields.size()-fnc); + else{ + string::size_type ss = signeds.size(); + if(lnc==(ss-1)) { + signeds+= ','; + signeds.append(fields,fnc,fields.size()-fnc); + }else{ + if(lnc<(ss-2)) + signeds.replace(lnc+2,ss-lnc-2, + fields,fnc,fields.size()-fnc); + else + signeds.append(fields,fnc,fields.size()-fnc); + } + } + }catch(failed_lookup&) { + signeds.assign(fields,fnc,fields.size()-fnc); + } + set_field("signed",signeds); + } + + string basic_openid_message::find_ns(const string& uri,const char *pfx) const { + if(has_field("ns")) + return get_ns(uri); + return pfx; + } + string basic_openid_message::allocate_ns(const string& uri,const char *pfx) { + if(!has_field("ns")) + return pfx; + if(has_ns(uri)) + throw bad_input(OPKELE_CP_ "OpenID message already contains namespace"); + string rv = pfx; + if(has_field("ns."+rv)) { + string::reference c=rv[rv.length()]; + for(c='a';c<='z' && has_field("ns."+rv);++c); + if(c=='z') + throw exception(OPKELE_CP_ "Failed to allocate namespace"); + } + set_field("ns."+rv,uri); + return rv; + } + + void openid_message_t::copy_to(basic_openid_message& x) const { + x.reset_fields(); + for(const_iterator i=begin();i!=end();++i) + x.set_field(i->first,i->second); + } + + bool openid_message_t::has_field(const string& n) const { + return find(n)!=end(); + } + const string& openid_message_t::get_field(const string& n) const { + const_iterator i=find(n); + if(i==end()) + throw failed_lookup(OPKELE_CP_ n+": no such field"); + return i->second; + } + + openid_message_t::fields_iterator openid_message_t::fields_begin() const { + return util::map_keys_iterator<const_iterator,string,const string&,const string*>(begin(),end()); + } + openid_message_t::fields_iterator openid_message_t::fields_end() const { + return util::map_keys_iterator<const_iterator,string,const string&,const string*>(end(),end()); + } + + void openid_message_t::reset_fields() { + clear(); + } + void openid_message_t::set_field(const string& n,const string& v) { + insert(value_type(n,v)); + } + void openid_message_t::reset_field(const string& n) { + erase(n); + } + +} diff --git a/lib/params.cc b/lib/params.cc index 7a572c1..6805516 100644 --- a/lib/params.cc +++ b/lib/params.cc @@ -8,114 +8,23 @@ namespace opkele { using namespace std; - bool params_t::has_param(const string& n) const { - return find(n)!=end(); - } - const string& params_t::get_param(const string& n) const { - const_iterator i = find(n); - if(i==end()) - throw failed_lookup(OPKELE_CP_ n+": no such parameter"); - return i->second; - } - string& params_t::get_param(const string& n) { - iterator i = find(n); - if(i==end()) - throw failed_lookup(OPKELE_CP_ n+": no such parameter"); - return i->second; - } - - void params_t::parse_keyvalues(const string& kv) { - clear(); - string::size_type p = 0; - while(true) { - string::size_type co = kv.find(':',p); - if(co==string::npos) - break; -#ifndef POSTELS_LAW - string::size_type nl = kv.find('\n',co+1); - if(nl==string::npos) - throw bad_input(OPKELE_CP_ "malformed input"); - if(nl>co) - insert(value_type(kv.substr(p,co-p),kv.substr(co+1,nl-co-1))); - p = nl+1; -#else /* POSTELS_LAW */ - string::size_type lb = kv.find_first_of("\r\n",co+1); - if(lb==string::npos) { - insert(value_type(kv.substr(p,co-p),kv.substr(co+1))); - break; - } - if(lb>co) - insert(value_type(kv.substr(p,co-p),kv.substr(co+1,lb-co-1))); - string::size_type nolb = kv.find_first_not_of("\r\n",lb); - if(nolb==string::npos) - break; - p = nolb; -#endif /* POSTELS_LAW */ - } - } - - void params_t::sign(secret_t secret,string& sig,const string& slist,const char *prefix) const { - string kv; - string::size_type p = 0; - while(true) { - string::size_type co = slist.find(',',p); - string f = (co==string::npos)?slist.substr(p):slist.substr(p,co-p); - kv += f; - kv += ':'; - if(prefix) f.insert(0,prefix); - kv += get_param(f); - kv += '\n'; - if(co==string::npos) - break; - p = co+1; - } - unsigned int md_len = 0; - unsigned char *md = HMAC( - EVP_sha1(), - &(secret.front()),secret.size(), - (const unsigned char *)kv.data(),kv.length(), - 0,&md_len); - sig = util::encode_base64(md,md_len); - } - string params_t::append_query(const string& url,const char *prefix) const { string rv = url; bool p = true; if(rv.find('?')==string::npos) { - rv += '?'; - p = false; - } - for(const_iterator i=begin();i!=end();++i) { + rv += '?'; p = false; } + for(fields_iterator i=fields_begin();i!=fields_end();++i) { if(p) rv += '&'; else p = true; - rv += prefix; - rv += i->first; + if(prefix) rv += prefix; + rv += *i; rv += '='; - rv += util::url_encode(i->second); + rv += util::url_encode(get_field(*i)); } return rv; } - string params_t::query_string(const char *prefix) const { - string rv; - for(const_iterator i=begin();i!=end();++i) { - if(!rv.empty()) - rv += '&'; - rv += prefix; - rv += i->first; - rv += '='; - rv += util::url_encode(i->second); - } - return rv; - } - - ostream& operator << (ostream& o,const params_t& p) { - for(params_t::const_iterator i=p.begin();i!=p.end();++i) - o << i->first << ':' << i->second << '\n'; - return o; - } - } diff --git a/lib/prequeue_rp.cc b/lib/prequeue_rp.cc new file mode 100644 index 0000000..e242f87 --- a/dev/null +++ b/lib/prequeue_rp.cc @@ -0,0 +1,81 @@ +#include <iostream> +#include <openssl/sha.h> +#include <openssl/hmac.h> +#include <opkele/exception.h> +#include <opkele/prequeue_rp.h> +#include <opkele/discovery.h> +#include <opkele/uris.h> +#include <opkele/data.h> +#include <opkele/util.h> +#include <opkele/curl.h> +#include <opkele/debug.h> + +namespace opkele { + + class __OP_verifier_good_input : public exception { + public: + __OP_verifier_good_input(OPKELE_E_PARS) + : exception(OPKELE_E_CONS) { } + }; + + class OP_verifier : public iterator<output_iterator_tag,openid_endpoint_t,void> { + public: + const string& OP; + const string& id; + + OP_verifier(const string& o,const string& i) + : OP(o), id(i) { } + + OP_verifier& operator*() { return *this; } + OP_verifier& operator=(const openid_endpoint_t& oep) { + if(oep.uri==OP) { + if(oep.claimed_id==IDURI_SELECT20 + || oep.local_id==IDURI_SELECT20 ) + throw bad_input(OPKELE_CP_ "claimed_id is an OP-Id"); + if(oep.local_id==id) + throw __OP_verifier_good_input(OPKELE_CP_ "Found corresponding endpoint"); + } + return *this; + } + + OP_verifier& operator++() { return *this; } + OP_verifier& operator++(int) { return *this; } + }; + + void prequeue_RP::verify_OP(const string& OP,const string& claimed_id,const string& identity) const { + try { + idiscover(OP_verifier(OP,identity),claimed_id); + throw id_res_unauthorized(OPKELE_CP_ + "OP is not authorized to make an assertion regarding the identity"); + }catch(__OP_verifier_good_input& ovgi) { + } + } + + class endpoint_queuer : public iterator<output_iterator_tag,openid_endpoint_t,void> { + public: + prequeue_RP& rp; + + endpoint_queuer(prequeue_RP& rp) : rp(rp) { } + + endpoint_queuer& operator*() { return *this; } + endpoint_queuer& operator=(const openid_endpoint_t& oep) { + rp.queue_endpoint(oep); return *this; } + + endpoint_queuer& operator++() { return *this; } + endpoint_queuer& operator++(int) { return *this; } + }; + + void prequeue_RP::initiate(const string& usi) { + begin_queueing(); + set_normalized_id( idiscover(endpoint_queuer(*this),usi) ); + end_queueing(); + } + + void prequeue_RP::set_normalized_id(const string& nid) { + } + + const string prequeue_RP::get_normalized_id() const { + throw not_implemented(OPKELE_CP_ "get_normalized_id() is not implemented"); + } + +} diff --git a/lib/server.cc b/lib/server.cc index 282521e..776f1ae 100644 --- a/lib/server.cc +++ b/lib/server.cc @@ -108,9 +108,9 @@ namespace opkele { pout["valid_to"] = util::time_to_w3c(now+120); pout["exipres_in"] = "120"; pout["signed"]="mode,identity,return_to"; if(ext) ext->checkid_hook(pin,pout); - pout.sign(assoc->secret(),pout["sig"],pout["signed"]); + pout["sig"] = util::base64_signature(assoc,pout); } void server_t::check_authentication(const params_t& pin,params_t& pout) { vector<unsigned char> sig; diff --git a/lib/sreg.cc b/lib/sreg.cc index 03edf57..7e2d588 100644 --- a/lib/sreg.cc +++ b/lib/sreg.cc @@ -27,9 +27,9 @@ namespace opkele { bool operator==(const struct _sreg_field& fd,const string& fn) { return fd.fieldname==fn; } - void sreg_t::checkid_hook(params_t& p,const string& /* identity */) { + void sreg_t::checkid_hook(basic_openid_message& om) { string fr, fo; for(fields_iterator f=fields_BEGIN;f<fields_END;++f) { if(f->fieldbit&fields_required) { if(!fr.empty()) fr+=","; @@ -39,21 +39,32 @@ namespace opkele { if(!fo.empty()) fo+=","; fo += f->fieldname; } } - p["ns.sreg"] = OIURI_SREG11; - if(!fr.empty()) p["sreg.required"]=fr; - if(!fo.empty()) p["sreg.optional"]=fo; - if(!policy_url.empty()) p["sreg.policy_url"]=policy_url; + string pfx = om.allocate_ns(OIURI_SREG11,"sreg"); + if(!fr.empty()) om.set_field(pfx+".required",fr); + if(!fo.empty()) om.set_field(pfx+".optional",fo); + if(!policy_url.empty()) om.set_field(pfx+".policy_url",policy_url); } - void sreg_t::id_res_hook(const params_t& /* p */,const params_t& sp,const string& /* identity */) { + void sreg_t::id_res_hook(const basic_openid_message& om,const basic_openid_message& sp) { clear(); + string pfx; + try { + pfx = om.find_ns(OIURI_SREG11,"sreg"); + }catch(failed_lookup& fl) { + try { + pfx = om.find_ns(OIURI_SREG10,"sreg"); + }catch(failed_lookup& fl) { + return; + } + } + pfx += '.'; for(fields_iterator f=fields_BEGIN;f<fields_END;++f) { - string fn = "sreg."; fn+=f->fieldname; - if(!sp.has_param(fn)) continue; + string fn = pfx; fn+=f->fieldname; + if(!sp.has_field(fn)) continue; has_fields |= f->fieldbit; - response[f->fieldbit]=sp.get_param(fn); + response[f->fieldbit]=sp.get_field(fn); } } const string& sreg_t::get_field(fieldbit_t fb) const { @@ -93,34 +104,37 @@ namespace opkele { } return rv; } - void sreg_t::checkid_hook(const params_t& pin,params_t& pout) { + void sreg_t::checkid_hook(const basic_openid_message& inm,basic_openid_message& oum) { + string ins = inm.find_ns(OIURI_SREG11,"sreg"); fields_optional = 0; fields_required = 0; policy_url.erase(); fields_response = 0; try { - string fl = pin.get_param("openid.sreg.required"); + string fl = inm.get_field(ins+".required"); fields_required = fields_list_to_bitmask(fl); }catch(failed_lookup&) { } try { - string fl = pin.get_param("openid.sreg.optional"); + string fl = inm.get_field(ins+".optional"); fields_optional = fields_list_to_bitmask(fl); }catch(failed_lookup&) { } try { - policy_url = pin.get_param("openid.sreg.policy_url"); + policy_url = inm.get_field(ins+".policy_url"); }catch(failed_lookup&) { } - setup_response(pin,pout); + setup_response(inm,oum); + string ons = oum.allocate_ns(OIURI_SREG11,"sreg"); fields_response &= has_fields; + string signeds = "ns."+ons; for(fields_iterator f=fields_BEGIN;f<fields_END;++f) { if(!(f->fieldbit&fields_response)) continue; - if(!pout["signed"].empty()) - pout["signed"] +=','; - string pn = "sreg."; pn += f->fieldname; - pout["signed"] += pn; - pout[pn] = get_field(f->fieldbit); + signeds +=','; + string pn = ons; pn += '.'; pn += f->fieldname; + signeds += pn; + oum.set_field(pn,get_field(f->fieldbit)); } + oum.add_to_signed(signeds); } - void sreg_t::setup_response(const params_t& /* pin */,params_t& /* pout */) { + void sreg_t::setup_response(const basic_openid_message& /* inm */,basic_openid_message& /* oum */) { fields_response = (fields_required|fields_optional)&has_fields; } } diff --git a/lib/util.cc b/lib/util.cc index a9b9bed..54d6535 100644 --- a/lib/util.cc +++ b/lib/util.cc @@ -6,12 +6,18 @@ #include <string> #include <stack> #include <openssl/bio.h> #include <openssl/evp.h> +#include <openssl/hmac.h> #include <curl/curl.h> #include "opkele/util.h" #include "opkele/exception.h" +#include <config.h> +#ifdef HAVE_DEMANGLE +# include <cxxabi.h> +#endif + namespace opkele { using namespace std; namespace util { @@ -204,10 +210,9 @@ namespace opkele { s = false; else if(rv=="https:") s = true; else{ - /* TODO: support more schemes. - * e.g. xri. How do we normalize + /* TODO: support more schemes. e.g. xri. How do we normalize * xri? */ rv.append(uri,colon+1,ul-colon-1); return rv; @@ -310,7 +315,69 @@ namespace opkele { } return rv; } + string& strip_uri_fragment_part(string& u) { + string::size_type q = u.find('?'), f = u.find('#'); + if(q==string::npos) { + if(f!=string::npos) + u.erase(f); + }else{ + if(f!=string::npos) { + if(f<q) + u.erase(f,q-f); + else + u.erase(f); + } + } + return u; + } + + string abi_demangle(const char *mn) { +#ifndef HAVE_DEMANGLE + return mn; +#else /* !HAVE_DEMANGLE */ + int dstat; + char *demangled = abi::__cxa_demangle(mn,0,0,&dstat); + if(dstat) + return mn; + string rv = demangled; + free(demangled); + return rv; +#endif /* !HAVE_DEMANGLE */ + } + + string base64_signature(const assoc_t& assoc,const basic_openid_message& om) { + const string& slist = om.get_field("signed"); + string kv; + string::size_type p=0; + while(true) { + string::size_type co = slist.find(',',p); + string f = (co==string::npos) + ?slist.substr(p):slist.substr(p,co-p); + kv += f; + kv += ':'; + kv += om.get_field(f); + kv += '\n'; + if(co==string::npos) break; + p = co+1; + } + const secret_t& secret = assoc->secret(); + const EVP_MD *evpmd; + const string& at = assoc->assoc_type(); + if(at=="HMAC-SHA256") + evpmd = EVP_sha256(); + else if(at=="HMAC-SHA1") + evpmd = EVP_sha1(); + else + throw unsupported(OPKELE_CP_ "unknown association type"); + unsigned int md_len = 0; + unsigned char *md = HMAC(evpmd, + &(secret.front()),secret.size(), + (const unsigned char*)kv.data(),kv.length(), + 0,&md_len); + return encode_base64(md,md_len); + } + } } |