author | Michael Krelin <hacker@klever.net> | 2005-07-19 13:08:32 (UTC) |
---|---|---|
committer | Michael Krelin <hacker@klever.net> | 2005-07-19 13:08:32 (UTC) |
commit | 4c82851dd5d5644a89d4f269079bf901f763ee33 (patch) (side-by-side diff) | |
tree | b64c8b3c9a1be88e2a9c3f762272e0b4509ba7d9 /lib | |
parent | 907343b0c973eb295bec8795902a6d49744e9174 (diff) | |
download | libopkele-4c82851dd5d5644a89d4f269079bf901f763ee33.zip libopkele-4c82851dd5d5644a89d4f269079bf901f763ee33.tar.gz libopkele-4c82851dd5d5644a89d4f269079bf901f763ee33.tar.bz2 |
initial commit of libopkele - OpenID support library
-rw-r--r-- | lib/.gitignore | 7 | ||||
-rw-r--r-- | lib/Makefile.am | 24 | ||||
-rw-r--r-- | lib/consumer.cc | 316 | ||||
-rw-r--r-- | lib/data.cc | 11 | ||||
-rw-r--r-- | lib/exception.cc | 22 | ||||
-rw-r--r-- | lib/params.cc | 96 | ||||
-rw-r--r-- | lib/secret.cc | 61 | ||||
-rw-r--r-- | lib/server.cc | 169 | ||||
-rw-r--r-- | lib/util.cc | 138 |
9 files changed, 844 insertions, 0 deletions
diff --git a/lib/.gitignore b/lib/.gitignore new file mode 100644 index 0000000..2325be6 --- a/dev/null +++ b/lib/.gitignore @@ -0,0 +1,7 @@ +*.lo +*.o +Makefile.in +libopkele.la +.libs +.deps +Makefile diff --git a/lib/Makefile.am b/lib/Makefile.am new file mode 100644 index 0000000..6f3f9f3 --- a/dev/null +++ b/lib/Makefile.am @@ -0,0 +1,24 @@ +lib_LTLIBRARIES = libopkele.la + +INCLUDES = \ + -I${top_srcdir}/include/ \ + ${KONFORKA_CFLAGS} \ + ${OPENSSL_CFLAGS} \ + ${MIMETIC_CFLAGS} \ + ${LIBCURL_CPPFLAGS} \ + ${PCREPP_CFLAGS} +LDADD = \ + ${LIBCURL} \ + ${PCREPP_LIBS} \ + ${MIMETIC_LIBS} \ + ${OPENSSL_LIBS} \ + ${KONFORKA_LIBS} + +libopkele_la_SOURCES = \ + params.cc \ + util.cc \ + server.cc \ + secret.cc \ + data.cc \ + consumer.cc \ + exception.cc diff --git a/lib/consumer.cc b/lib/consumer.cc new file mode 100644 index 0000000..bd76b61 --- a/dev/null +++ b/lib/consumer.cc @@ -0,0 +1,316 @@ +#include <algorithm> +#include <opkele/util.h> +#include <opkele/exception.h> +#include <opkele/data.h> +#include <opkele/consumer.h> +#include <openssl/sha.h> +#include <openssl/hmac.h> +#include <mimetic/mimetic.h> +#include <curl/curl.h> +#include <pcre++.h> + +#include <iostream> + +/* silly mimetic */ +#undef PACKAGE +#undef PACKAGE_BUGREPORT +#undef PACKAGE_NAME +#undef PACKAGE_STRING +#undef PACKAGE_TARNAME +#undef PACKAGE_VERSION +#undef VERSION + +#include "config.h" + +namespace opkele { + using namespace std; + + class curl_t { + public: + CURL *_c; + + curl_t() : _c(0) { } + curl_t(CURL *c) : _c(c) { } + ~curl_t() throw() { if(_c) curl_easy_cleanup(_c); } + + curl_t& operator=(CURL *c) { if(_c) curl_easy_cleanup(_c); _c=c; return *this; } + + operator const CURL*(void) const { return _c; } + operator CURL*(void) { return _c; } + }; + + static CURLcode curl_misc_sets(CURL* c) { + CURLcode r; + (r=curl_easy_setopt(c,CURLOPT_FOLLOWLOCATION,1)) + || (r=curl_easy_setopt(c,CURLOPT_MAXREDIRS,5)) + || (r=curl_easy_setopt(c,CURLOPT_DNS_CACHE_TIMEOUT,120)) + || (r=curl_easy_setopt(c,CURLOPT_DNS_USE_GLOBAL_CACHE,1)) + || (r=curl_easy_setopt(c,CURLOPT_USERAGENT,PACKAGE_NAME"/"PACKAGE_VERSION)) + || (r=curl_easy_setopt(c,CURLOPT_TIMEOUT,20)) + ; + return r; + } + + static size_t _curl_tostring(void *ptr,size_t size,size_t nmemb,void *stream) { + string *str = (string*)stream; + size_t bytes = size*nmemb; + size_t get = min(16384-str->length(),bytes); + str->append((const char*)ptr,get); + return get; + } + + assoc_t consumer_t::associate(const string& server) { + 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()"); + string request = + "openid.mode=associate" + "&openid.assoc_type=HMAC-SHA1" + "&openid.session_type=DH-SHA1" + "&openid.dh_consumer_public="; + request += util::url_encode(util::bignum_to_base64(dh->pub_key)); + curl_t curl = curl_easy_init(); + if(!curl) + throw exception_curl(OPKELE_CP_ "failed to curl_easy_init()"); + string response; + CURLcode r; + (r=curl_misc_sets(curl)) + || (r=curl_easy_setopt(curl,CURLOPT_URL,server.c_str())) + || (r=curl_easy_setopt(curl,CURLOPT_POST,1)) + || (r=curl_easy_setopt(curl,CURLOPT_POSTFIELDS,request.data())) + || (r=curl_easy_setopt(curl,CURLOPT_POSTFIELDSIZE,request.length())) + || (r=curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,_curl_tostring)) + || (r=curl_easy_setopt(curl,CURLOPT_WRITEDATA,&response)) + ; + if(r) + throw exception_curl(OPKELE_CP_ "failed to curl_easy_setopt()",r); + if(r=curl_easy_perform(curl)) + throw exception_curl(OPKELE_CP_ "failed to curl_easy_perform()",r); + params_t p; p.parse_keyvalues(response); + if(p.has_param("assoc_type") && p.get_param("assoc_type")!="HMAC-SHA1") + throw bad_input(OPKELE_CP_ "unsupported assoc_type"); + string st; + if(p.has_param("session_type")) st = p.get_param("session_type"); + if((!st.empty()) && st!="DH-SHA1") + throw bad_input(OPKELE_CP_ "unsupported session_type"); + secret_t secret; + if(st.empty()) { + secret.from_base64(p.get_param("mac_key")); + }else{ + util::bignum_t s_pub = util::base64_to_bignum(p.get_param("dh_server_public")); + vector<unsigned char> ck(DH_size(dh)); + int cklen = DH_compute_key(&(ck.front()),s_pub,dh); + if(cklen<0) + throw exception_openssl(OPKELE_CP_ "failed to DH_compute_key()"); + ck.resize(cklen); + // OpenID algorithm requires extra zero in case of set bit here + if(ck[0]&0x80) ck.insert(ck.begin(),1,0); + unsigned char key_sha1[SHA_DIGEST_LENGTH]; + SHA1(&(ck.front()),ck.size(),key_sha1); + secret.enxor_from_base64(key_sha1,p.get_param("enc_mac_key")); + } + int expires_in = 0; + if(p.has_param("expires_in")) { + expires_in = util::string_to_long(p.get_param("expires_in")); + }else if(p.has_param("issued") && p.has_param("expiry")) { + expires_in = util::w3c_to_time(p.get_param("expiry"))-util::w3c_to_time(p.get_param("issued")); + }else + throw bad_input(OPKELE_CP_ "no expiration information"); + return store_assoc(server,p.get_param("assoc_handle"),secret,expires_in); + } + + string consumer_t::checkid_immediate(const string& identity,const string& return_to,const string& trust_root) { + return checkid_(mode_checkid_immediate,identity,return_to,trust_root); + } + string consumer_t::checkid_setup(const string& identity,const string& return_to,const string& trust_root) { + return checkid_(mode_checkid_setup,identity,return_to,trust_root); + } + string consumer_t::checkid_(mode_t mode,const string& identity,const string& return_to,const string& trust_root) { + params_t p; + if(mode==mode_checkid_immediate) + p["mode"]="checkid_immediate"; + else if(mode==mode_checkid_setup) + p["mode"]="checkid_setup"; + else + throw bad_input(OPKELE_CP_ "unknown checkid_* mode"); + string iurl = util::canonicalize_url(identity); + string server, delegate; + retrieve_links(iurl,server,delegate); + p["identity"] = delegate.empty()?iurl:delegate; + if(!trust_root.empty()) + p["trust_root"] = trust_root; + p["return_to"] = return_to; + try { + try { + string ah = find_assoc(server)->handle(); + p["assoc_handle"] = ah; + }catch(failed_lookup& fl) { + string ah = associate(server)->handle(); + p["assoc_handle"] = ah; + } + }catch(exception& e) { } + return p.append_query(server); + } + + void consumer_t::id_res(const params_t& pin,const string& identity) { + if(pin.has_param("openid.user_setup_url")) + throw id_res_setup(OPKELE_CP_ "assertion failed, setup url provided",pin.get_param("openid.user_setup_url")); + string server,delegate; + retrieve_links(identity.empty()?pin.get_param("openid.identity"):util::canonicalize_url(identity),server,delegate); + try { + assoc_t assoc = retrieve_assoc(server,pin.get_param("openid.assoc_handle")); + const string& sigenc = pin.get_param("openid.sig"); + mimetic::Base64::Decoder b; + vector<unsigned char> sig; + mimetic::decode( + sigenc.begin(),sigenc.end(), b, + back_insert_iterator<vector<unsigned char> >(sig) ); + const string& slist = pin.get_param("openid.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 += ':'; + f.insert(0,"openid."); + kv += pin.get_param(f); + kv += '\n'; + if(co==string::npos) + break; + p = co+1; + } + secret_t secret = assoc->secret(); + 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); + if(sig.size()!=md_len || memcmp(&(sig.front()),md,md_len)) + throw id_res_mismatch(OPKELE_CP_ "signature mismatch"); + }catch(failed_lookup& e) { /* XXX: more specific? */ + const string& slist = pin.get_param("openid.signed"); + string::size_type pp = 0; + params_t p; + while(true) { + string::size_type co = slist.find(',',pp); + string f = "openid."; + f += (co==string::npos)?slist.substr(pp):slist.substr(pp,co-pp); + p[f] = pin.get_param(f); + if(co==string::npos) + break; + pp = co+1; + } + p["openid.assoc_handle"] = pin.get_param("openid.assoc_handle"); + p["openid.sig"] = pin.get_param("openid.sig"); + p["openid.signed"] = pin.get_param("openid.signed"); + try { + string ih = pin.get_param("openid.invalidate_handle"); + p["openid.invalidate_handle"] = ih; + }catch(failed_lookup& fl) { } + try { + check_authentication(server,p); + }catch(failed_check_authentication& fca) { + throw id_res_failed(OPKELE_CP_ "failed to check_authentication()"); + } + } + } + + void consumer_t::check_authentication(const string& server,const params_t& p) { + string request = "openid.mode=check_authentication"; + for(params_t::const_iterator i=p.begin();i!=p.end();++i) { + if(i->first!="openid.mode") { + request += '&'; + request += i->first; + request += '='; + request += util::url_encode(i->second); + } + } + curl_t curl = curl_easy_init(); + if(!curl) + throw exception_curl(OPKELE_CP_ "failed to curl_easy_init()"); + string response; + CURLcode r; + (r=curl_misc_sets(curl)) + || (r=curl_easy_setopt(curl,CURLOPT_URL,server.c_str())) + || (r=curl_easy_setopt(curl,CURLOPT_POST,1)) + || (r=curl_easy_setopt(curl,CURLOPT_POSTFIELDS,request.data())) + || (r=curl_easy_setopt(curl,CURLOPT_POSTFIELDSIZE,request.length())) + || (r=curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,_curl_tostring)) + || (r=curl_easy_setopt(curl,CURLOPT_WRITEDATA,&response)) + ; + if(r) + throw exception_curl(OPKELE_CP_ "failed to curl_easy_setopt()",r); + if(r=curl_easy_perform(curl)) + throw exception_curl(OPKELE_CP_ "failed to curl_easy_perform()",r); + params_t pp; pp.parse_keyvalues(response); + if(pp.has_param("invalidate_handle")) + invalidate_assoc(server,pp.get_param("invalidate_handle")); + if(pp.has_param("is_valid")) { + if(pp.get_param("is_valid")=="true") + return; + }else if(pp.has_param("lifetime")) { + if(util::string_to_long(pp.get_param("lifetime"))) + return; + } + throw failed_check_authentication(OPKELE_CP_ "failed to verify response"); + } + + void consumer_t::retrieve_links(const string& url,string& server,string& delegate) { + server.erase(); + delegate.erase(); + curl_t curl = curl_easy_init(); + if(!curl) + throw exception_curl(OPKELE_CP_ "failed to curl_easy_init()"); + string html; + CURLcode r; + (r=curl_misc_sets(curl)) + || (r=curl_easy_setopt(curl,CURLOPT_URL,url.c_str())) + || (r=curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,_curl_tostring)) + || (r=curl_easy_setopt(curl,CURLOPT_WRITEDATA,&html)) + ; + if(r) + throw exception_curl(OPKELE_CP_ "failed to curl_easy_setopt()",r); + r = curl_easy_perform(curl); + if(r && r!=CURLE_WRITE_ERROR) + throw exception_curl(OPKELE_CP_ "failed to curl_easy_perform()",r); + pcrepp::Pcre bre("<body\\b",PCRE_CASELESS); + // strip out everything past body + if(bre.search(html)) + html.erase(bre.get_match_start()); + pcrepp::Pcre hdre("<head[^>]*>",PCRE_CASELESS); + if(!hdre.search(html)) + throw bad_input(OPKELE_CP_ "failed to find head"); + html.erase(0,hdre.get_match_end()+1); + pcrepp::Pcre lre("<link\\b([^>]+)>",PCRE_CASELESS), + rre("\\brel=['\"]([^'\"]+)['\"]",PCRE_CASELESS), + hre("\\bhref=['\"]([^'\"]+)['\"]",PCRE_CASELESS); + while(lre.search(html)) { + string attrs = lre[0]; + html.erase(0,lre.get_match_end()+1); + if(!(rre.search(attrs)&&hre.search(attrs))) + continue; + if(rre[0]=="openid.server") { + server = hre[0]; + if(!delegate.empty()) + break; + }else if(rre[0]=="openid.delegate") { + delegate = hre[0]; + if(!server.empty()) + break; + } + } + if(server.empty()) + throw failed_assertion(OPKELE_CP_ "The location has no openid.server declaration"); + } + + assoc_t consumer_t::find_assoc(const string& server) { + throw failed_lookup(OPKELE_CP_ "no find_assoc() provided"); + } + +} diff --git a/lib/data.cc b/lib/data.cc new file mode 100644 index 0000000..c040430 --- a/dev/null +++ b/lib/data.cc @@ -0,0 +1,11 @@ +#include <opkele/data.h> + +namespace opkele { + + namespace data { + + const char *_default_p = "155172898181473697471232257763715539915724801966915404479707795314057629378541917580651227423698188993727816152646631438561595825688188889951272158842675419950341258706556549803580104870537681476726513255747040765857479291291572334510643245094715007229621094194349783925984760375594985848253359305585439638443"; + const char *_default_g = "2"; + + } +} diff --git a/lib/exception.cc b/lib/exception.cc new file mode 100644 index 0000000..b7c1702 --- a/dev/null +++ b/lib/exception.cc @@ -0,0 +1,22 @@ +#include <openssl/err.h> +#include <curl/curl.h> +#include <opkele/exception.h> + +namespace opkele { + + exception_openssl::exception_openssl(OPKELE_E_PARS) + : _error(ERR_peek_last_error()), + _ssl_string(ERR_error_string(_error,0)), + exception(OPKELE_E_CONS_ w+" ["+_ssl_string+']') { + } + + exception_curl::exception_curl(OPKELE_E_PARS) + : _error(CURLE_OK), + exception_network(OPKELE_E_CONS) { } + exception_curl::exception_curl(OPKELE_E_PARS,CURLcode e) + : _error(e), + _curl_string(curl_easy_strerror(e)), + exception_network(OPKELE_E_CONS_ w+" ["+_curl_string+']') { + } + +} diff --git a/lib/params.cc b/lib/params.cc new file mode 100644 index 0000000..14f1a53 --- a/dev/null +++ b/lib/params.cc @@ -0,0 +1,96 @@ +#include <opkele/types.h> +#include <opkele/exception.h> +#include <opkele/util.h> +#include <openssl/sha.h> +#include <openssl/hmac.h> +#include <mimetic/mimetic.h> + +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; + string::size_type nl = kv.find('\n',co+1); + if(nl==string::npos) + throw bad_input(OPKELE_CP_ "malformed input"); + insert(value_type(kv.substr(p,co-p),kv.substr(co+1,nl-co-1))); + p = nl+1; + } + } + + 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); + mimetic::Base64::Encoder b(0); + sig.erase(); + mimetic::encode( + md,md+md_len, b, + back_insert_iterator<string>(sig) ); + } + + 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) { + if(p) + rv += '&'; + else + p = true; + 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/secret.cc b/lib/secret.cc new file mode 100644 index 0000000..ae8a3c5 --- a/dev/null +++ b/lib/secret.cc @@ -0,0 +1,61 @@ +#include <algorithm> +#include <functional> +#include <opkele/types.h> +#include <opkele/exception.h> +#include <mimetic/mimetic.h> + +namespace opkele { + using namespace std; + + template<class __a1,class __a2,class __r> + struct bitwise_xor : public binary_function<__a1,__a2,__r> { + __r operator() (const __a1& a1,const __a2& a2) const { + return a1^a2; + } + }; + + void secret_t::enxor_to_base64(const unsigned char *key_sha1,string& rv) const { + if(size()!=20) + throw bad_input(OPKELE_CP_ "wrong secret size"); + vector<unsigned char> tmp; + transform( + begin(), end(), + key_sha1, + back_insert_iterator<vector<unsigned char> >(tmp), + bitwise_xor<unsigned char,unsigned char,unsigned char>() ); + mimetic::Base64::Encoder b(0); + mimetic::encode( + tmp.begin(),tmp.end(), b, + back_insert_iterator<string>(rv) ); + } + + void secret_t::enxor_from_base64(const unsigned char *key_sha1,const string& b64) { + mimetic::Base64::Decoder b; + clear(); + mimetic::decode( + b64.begin(),b64.end(), b, + back_insert_iterator<secret_t>(*this) ); + transform( + begin(), end(), + key_sha1, + begin(), + bitwise_xor<unsigned char,unsigned char,unsigned char>() ); + } + + void secret_t::to_base64(string& rv) const { + if(size()!=20) + throw bad_input(OPKELE_CP_ "wrong secret size"); + mimetic::Base64::Encoder b(0); + mimetic::encode( + begin(),end(), b, + back_insert_iterator<string>(rv) ); + } + + void secret_t::from_base64(const string& b64) { + mimetic::Base64::Decoder b; + mimetic::decode( + b64.begin(),b64.end(), b, + back_insert_iterator<secret_t>(*this) ); + } + +} diff --git a/lib/server.cc b/lib/server.cc new file mode 100644 index 0000000..51d4554 --- a/dev/null +++ b/lib/server.cc @@ -0,0 +1,169 @@ +#include <vector> +#include <openssl/sha.h> +#include <openssl/hmac.h> +#include <mimetic/mimetic.h> +#include <opkele/util.h> +#include <opkele/exception.h> +#include <opkele/server.h> +#include <opkele/data.h> + +namespace opkele { + using namespace std; + + void server_t::associate(const params_t& pin,params_t& pout) { + util::dh_t dh; + util::bignum_t c_pub; + unsigned char key_sha1[SHA_DIGEST_LENGTH]; + enum { + sess_cleartext, + sess_dh_sha1 + } st = sess_cleartext; + if( + pin.has_param("openid.session_type") + && pin.get_param("openid.session_type")=="DH-SHA1" ) { + /* TODO: fallback to cleartext in case of exceptions here? */ + if(!(dh = DH_new())) + throw exception_openssl(OPKELE_CP_ "failed to DH_new()"); + c_pub = util::base64_to_bignum(pin.get_param("openid.dh_consumer_public")); + if(pin.has_param("openid.dh_modulus")) + dh->p = util::base64_to_bignum(pin.get_param("openid.dh_modulus")); + else + dh->p = util::dec_to_bignum(data::_default_p); + if(pin.has_param("openid.dh_gen")) + dh->g = util::base64_to_bignum(pin.get_param("openid.dh_gen")); + else + dh->g = util::dec_to_bignum(data::_default_g); + if(!DH_generate_key(dh)) + throw exception_openssl(OPKELE_CP_ "failed to DH_generate_key()"); + vector<unsigned char> ck(DH_size(dh)); + int cklen = DH_compute_key(&(ck.front()),c_pub,dh); + if(cklen<0) + throw exception_openssl(OPKELE_CP_ "failed to DH_compute_key()"); + ck.resize(cklen); + // OpenID algorithm requires extra zero in case of set bit here + if(ck[0]&0x80) ck.insert(ck.begin(),1,0); + SHA1(&(ck.front()),ck.size(),key_sha1); + st = sess_dh_sha1; + } + assoc_t assoc = alloc_assoc(mode_associate); + time_t now = time(0); + pout.clear(); + pout["assoc_type"] = assoc->assoc_type(); + pout["assoc_handle"] = assoc->handle(); + /* TODO: eventually remove deprecated stuff */ + pout["issued"] = util::time_to_w3c(now); + pout["expiry"] = util::time_to_w3c(now+assoc->expires_in()); + pout["expires_in"] = util::long_to_string(assoc->expires_in()); + secret_t secret = assoc->secret(); + switch(st) { + case sess_dh_sha1: + pout["session_type"] = "DH-SHA1"; + pout["dh_server_public"] = util::bignum_to_base64(dh->pub_key); + secret.enxor_to_base64(key_sha1,pout["enc_mac_key"]); + break; + default: + secret.to_base64(pout["mac_key"]); + break; + } + } + + void server_t::checkid_immediate(const params_t& pin,string& return_to,params_t& pout) { + checkid_(mode_checkid_immediate,pin,return_to,pout); + } + + void server_t::checkid_setup(const params_t& pin,string& return_to,params_t& pout) { + checkid_(mode_checkid_setup,pin,return_to,pout); + } + + void server_t::checkid_(mode_t mode,const params_t& pin,string& return_to,params_t& pout) { + if(mode!=mode_checkid_immediate && mode!=mode_checkid_setup) + throw bad_input(OPKELE_CP_ "invalid checkid_* mode"); + assoc_t assoc; + try { + assoc = retrieve_assoc(pin.get_param("openid.assoc_handle")); + }catch(failed_lookup& fl) { + // no handle specified or no valid handle found, going dumb + assoc = alloc_assoc(mode_checkid_setup); + } + string trust_root; + try { + trust_root = pin.get_param("openid.trust_root"); + }catch(failed_lookup& fl) { } + string identity = pin.get_param("openid.identity"); + return_to = pin.get_param("openid.return_to"); + validate(*assoc,pin,identity,trust_root); + pout.clear(); + pout["mode"] = "id_res"; + pout["assoc_handle"] = assoc->handle(); + if(pin.has_param("openid.assoc_handle") && assoc->stateless()) + pout["invalidate_handle"] = pin.get_param("openid.assoc_handle"); + pout["identity"] = identity; + pout["return_to"] = return_to; + /* TODO: eventually remove deprecated stuff */ + time_t now = time(0); + pout["issued"] = util::time_to_w3c(now); + pout["valid_to"] = util::time_to_w3c(now+120); + pout["exipres_in"] = "120"; + pout.sign(assoc->secret(),pout["sig"],pout["signed"]="mode,identity,return_to"); + } + + void server_t::check_authentication(const params_t& pin,params_t& pout) { + vector<unsigned char> sig; + mimetic::Base64::Decoder b; + const string& sigenc = pin.get_param("openid.sig"); + mimetic::decode( + sigenc.begin(),sigenc.end(), b, + back_insert_iterator<vector<unsigned char> >(sig)); + assoc_t assoc; + try { + assoc = retrieve_assoc(pin.get_param("openid.assoc_handle")); + }catch(failed_lookup& fl) { + throw failed_assertion(OPKELE_CP_ "invalid handle or handle not specified"); + } + if(!assoc->stateless()) + throw stateful_handle(OPKELE_CP_ "will not do check_authentication on a stateful handle"); + const string& slist = pin.get_param("openid.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 += ':'; + if(f=="mode") + kv += "id_res"; + else { + f.insert(0,"openid."); + kv += pin.get_param(f); + } + kv += '\n'; + if(co==string::npos) + break; + p = co+1; + } + secret_t secret = assoc->secret(); + 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); + pout.clear(); + if(sig.size()==md_len && !memcmp(&(sig.front()),md,md_len)) { + pout["is_valid"]="true"; + pout["lifetime"]="60"; /* TODO: eventually remove deprecated stuff */ + }else{ + pout["is_valid"]="false"; + pout["lifetime"]="0"; /* TODO: eventually remove deprecated stuff */ + } + if(pin.has_param("openid.invalidate_handle")) { + string h = pin.get_param("openid.invalidate_handle"); + try { + assoc_t assoc = retrieve_assoc(h); + }catch(invalid_handle& ih) { + pout["invalidate_handle"] = h; + }catch(failed_lookup& fl) { } + } + } + +} diff --git a/lib/util.cc b/lib/util.cc new file mode 100644 index 0000000..1e7335c --- a/dev/null +++ b/lib/util.cc @@ -0,0 +1,138 @@ +#include <errno.h> +#include <cassert> +#include <vector> +#include <string> +#include <mimetic/mimetic.h> +#include <curl/curl.h> +#include "opkele/util.h" +#include "opkele/exception.h" + +namespace opkele { + using namespace std; + + namespace util { + + /* + * big numerics + */ + + BIGNUM *base64_to_bignum(const string& b64) { + vector<unsigned char> bin; + mimetic::Base64::Decoder b; + mimetic::decode( + b64.begin(),b64.end(), b, + back_insert_iterator<vector<unsigned char> >(bin) ); + BIGNUM *rv = BN_bin2bn(&(bin.front()),bin.size(),0); + if(!rv) + throw failed_conversion(OPKELE_CP_ "failed to BN_bin2bn()"); + return rv; + } + + BIGNUM *dec_to_bignum(const string& dec) { + BIGNUM *rv = 0; + if(!BN_dec2bn(&rv,dec.c_str())) + throw failed_conversion(OPKELE_CP_ "failed to BN_dec2bn()"); + return rv; + } + + string bignum_to_base64(const BIGNUM *bn) { + vector<unsigned char> bin(BN_num_bytes(bn)); + int l = BN_bn2bin(bn,&(bin.front())); + string rv; + mimetic::Base64::Encoder b(0); + mimetic::encode( + bin.begin(),bin.begin()+l, b, + back_insert_iterator<string>(rv) ); + return rv; + } + + /* + * w3c times + */ + + string time_to_w3c(time_t t) { + struct tm tm_t; + if(!gmtime_r(&t,&tm_t)) + throw failed_conversion(OPKELE_CP_ "failed to BN_dec2bn()"); + char rv[25]; + if(!strftime(rv,sizeof(rv)-1,"%Y-%m-%dT%H:%M:%SZ",&tm_t)) + throw failed_conversion(OPKELE_CP_ "failed to strftime()"); + return rv; + } + + time_t w3c_to_time(const string& w) { + struct tm tm_t; + memset(&tm_t,0,sizeof(tm_t)); + if( + sscanf( + w.c_str(), + "%04d-%02d-%02dT%02d:%02d:%02dZ", + &tm_t.tm_year,&tm_t.tm_mon,&tm_t.tm_mday, + &tm_t.tm_hour,&tm_t.tm_min,&tm_t.tm_sec + ) != 6 ) + throw failed_conversion(OPKELE_CP_ "failed to sscanf()"); + tm_t.tm_mon--; + tm_t.tm_year-=1900; + time_t rv = mktime(&tm_t); + if(rv==(time_t)-1) + throw failed_conversion(OPKELE_CP_ "failed to mktime()"); + return rv; + } + + /* + * + */ + + string canonicalize_url(const string& url) { + string rv = url; + // strip leading and trailing spaces + string::size_type i = rv.find_first_not_of(" \t\r\n"); + if(i==string::npos) + throw bad_input(OPKELE_CP_ "empty URL"); + if(i) + rv.erase(0,i); + i = rv.find_last_not_of(" \t\r\n"); + assert(i!=string::npos); + if(i<(rv.length()-1)) + rv.erase(i+1); + // add missing http:// + i = rv.find("://"); + if(i==string::npos) { // primitive. but do we need more? + rv.insert(0,"http://"); + i = sizeof("http://")-1; + }else{ + i += sizeof("://")-1; + } + if(rv.find('/',i)==string::npos) + rv += '/'; + return rv; + } + + string url_encode(const string& str) { + char * t = curl_escape(str.c_str(),str.length()); + if(!t) + throw failed_conversion(OPKELE_CP_ "failed to curl_escape()"); + string rv(t); + curl_free(t); + return rv; + } + + string long_to_string(long l) { + char rv[32]; + int r=snprintf(rv,sizeof(rv),"%ld",l); + if(r<0 || r>=sizeof(rv)) + throw failed_conversion(OPKELE_CP_ "failed to snprintf()"); + return rv; + } + + long string_to_long(const string& s) { + char *endptr = 0; + long rv = strtol(s.c_str(),&endptr,10); + if((!endptr) || endptr==s.c_str()) + throw failed_conversion(OPKELE_CP_ "failed to strtol()"); + return rv; + } + + } + +} |