summaryrefslogtreecommitdiffabout
path: root/lib
Side-by-side diff
Diffstat (limited to 'lib') (more/less context) (ignore whitespace changes)
-rw-r--r--lib/Makefile.am7
-rw-r--r--lib/basic_rp.cc311
-rw-r--r--lib/consumer.cc4
-rw-r--r--lib/discovery.cc161
-rw-r--r--lib/extension.cc6
-rw-r--r--lib/extension_chain.cc12
-rw-r--r--lib/openid_message.cc228
-rw-r--r--lib/params.cc101
-rw-r--r--lib/prequeue_rp.cc81
-rw-r--r--lib/server.cc2
-rw-r--r--lib/sreg.cc54
-rw-r--r--lib/util.cc71
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
@@ -4,5 +4,5 @@ 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} \
@@ -27,5 +27,8 @@ libopkele_la_SOURCES = \
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
@@ -155,5 +155,5 @@ namespace opkele {
p["assoc_handle"] = ah;
}
- if(ext) ext->checkid_hook(p,identity);
+ if(ext) ext->checkid_hook(p);
return p.append_query(server);
}
@@ -223,5 +223,5 @@ namespace opkele {
}
}
- if(ext) ext->id_res_hook(pin,ps,identity);
+ if(ext) ext->id_res_hook(pin,ps);
}
diff --git a/lib/discovery.cc b/lib/discovery.cc
index d868308..93409f4 100644
--- a/lib/discovery.cc
+++ b/lib/discovery.cc
@@ -19,8 +19,25 @@ namespace opkele {
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);
@@ -49,5 +66,5 @@ namespace opkele {
enum {
- xmode_html = 1, xmode_xrd = 2
+ xmode_html = 1, xmode_xrd = 2, xmode_cid = 4
};
int xmode;
@@ -85,9 +102,10 @@ namespace opkele {
~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);
@@ -97,20 +115,49 @@ namespace opkele {
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://");
@@ -123,22 +170,31 @@ namespace opkele {
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)
@@ -153,5 +209,5 @@ namespace opkele {
save_html.reserve(max_html);
}
- xrd = &result.xrd;
+ xrd = &idis.xrd;
r = easy_perform();
@@ -200,15 +256,19 @@ namespace opkele {
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]);
}
}
@@ -311,5 +371,6 @@ namespace opkele {
break;
}
- }
+ }else
+ ++a;
}
}else if(is_qelement(n,NSURI_XRD "\tExpires")) {
@@ -437,9 +498,39 @@ namespace opkele {
}
+ 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
@@ -4,11 +4,11 @@
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
@@ -4,12 +4,12 @@
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
@@ -10,112 +10,21 @@ 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
@@ -110,5 +110,5 @@ namespace opkele {
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);
}
diff --git a/lib/sreg.cc b/lib/sreg.cc
index 03edf57..7e2d588 100644
--- a/lib/sreg.cc
+++ b/lib/sreg.cc
@@ -29,5 +29,5 @@ namespace opkele {
}
- 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) {
@@ -41,17 +41,28 @@ namespace opkele {
}
}
- 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);
}
}
@@ -95,31 +106,34 @@ namespace opkele {
}
- 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
@@ -8,8 +8,14 @@
#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;
@@ -206,6 +212,5 @@ namespace opkele {
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?
*/
@@ -312,4 +317,66 @@ namespace opkele {
}
+ 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);
+ }
+
}