author | Michael Krelin <hacker@klever.net> | 2008-02-02 21:10:12 (UTC) |
---|---|---|
committer | Michael Krelin <hacker@klever.net> | 2008-02-02 21:10:12 (UTC) |
commit | 3658759966cbadb7b50457d446f3436b6f7987da (patch) (side-by-side diff) | |
tree | b215da5b5212b60aa1ec965df28070b4bff587bc | |
parent | a8f733c88d87abe422ecaa405df385bad562e60f (diff) | |
download | libopkele-3658759966cbadb7b50457d446f3436b6f7987da.zip libopkele-3658759966cbadb7b50457d446f3436b6f7987da.tar.gz libopkele-3658759966cbadb7b50457d446f3436b6f7987da.tar.bz2 |
moved uri matching into separate procedure
Signed-off-by: Michael Krelin <hacker@klever.net>
-rw-r--r-- | include/opkele/util.h | 8 | ||||
-rw-r--r-- | lib/basic_op.cc | 35 | ||||
-rw-r--r-- | lib/util.cc | 30 |
3 files changed, 41 insertions, 32 deletions
diff --git a/include/opkele/util.h b/include/opkele/util.h index 719f951..bc1a0ea 100644 --- a/include/opkele/util.h +++ b/include/opkele/util.h @@ -1,176 +1,184 @@ #ifndef __OPKELE_UTIL_H #define __OPKELE_UTIL_H #include <time.h> #include <string> #include <vector> #include <openssl/bn.h> #include <openssl/dh.h> #include <opkele/types.h> namespace opkele { using std::string; using std::vector; /** * @brief opkele utils namespace */ namespace util { /** * Convenience class encapsulating SSL BIGNUM object for the purpose of * automatical freeing. */ class bignum_t { public: BIGNUM *_bn; bignum_t() : _bn(0) { } bignum_t(BIGNUM *bn) : _bn(bn) { } ~bignum_t() throw() { if(_bn) BN_free(_bn); } bignum_t& operator=(BIGNUM *bn) { if(_bn) BN_free(_bn); _bn = bn; return *this; } operator const BIGNUM*(void) const { return _bn; } operator BIGNUM*(void) { return _bn; } }; /** * Convenience clas encapsulating SSL DH object for the purpose of * automatic freeing. */ class dh_t { public: DH *_dh; dh_t() : _dh(0) { } dh_t(DH *dh) : _dh(dh) { } ~dh_t() throw() { if(_dh) DH_free(_dh); } dh_t& operator=(DH *dh) { if(_dh) DH_free(_dh); _dh = dh; return *this; } operator const DH*(void) const { return _dh; } operator DH*(void) { return _dh; } DH* operator->() { return _dh; } const DH* operator->() const { return _dh; } }; /** * Convert base64-encoded SSL BIGNUM to internal representation. * @param b64 base64-encoded number * @return SSL BIGNUM * @throw failed_conversion in case of error */ BIGNUM *base64_to_bignum(const string& b64); /** * Convert decimal representation to SSL BIGNUM. * @param dec decimal representation * @return resulting BIGNUM * @throw failed_conversion in case of error */ BIGNUM *dec_to_bignum(const string& dec); /** * Convert SSL BIGNUM data to base64 encoded string. * @param bn BIGNUM * @return base64encoded string */ string bignum_to_base64(const BIGNUM *bn); /** * Convert internal time representation to w3c format * @param t internal representation * @return w3c time * @throw failed_conversion in case of error */ string time_to_w3c(time_t t); /** * Convert W3C time representation to internal time_t * @param w w3c representation * @return converted time * @throw failed_conversion in case of error */ time_t w3c_to_time(const string& w); /** * Encode string to the representation suitable for using in URL. * @param str string to encode * @return encoded string * @throw failed_conversion in case of failure */ string url_encode(const string& str); /** * Make string suitable for using as x(ht)ml attribute. * @param str string to escape * @return escaped string */ string attr_escape(const string& str); /** * Convert number to string * @param l number * @return string representation * @throw failed_conversion in case of failure */ string long_to_string(long l); /** * Convert string to number * @param s string, containing the number * @return the number * @throw failed_conversion in case of failure */ long string_to_long(const string& s); /** * Encode binary data using base64. * @param data pointer to binary data * @param length length of data * @return encoded data */ string encode_base64(const void *data,size_t length); /** * Decode binary data from base64 representation. * @param data base64-encoded data * @param rv container for decoded binary */ void decode_base64(const string& data,vector<unsigned char>& rv); /** * Normalize http(s) URI according to RFC3986, section 6. URI is * expected to have scheme: in front of it. * @param uri URI * @return normalized URI * @throw not_implemented in case of non-httpi(s) URI * @throw bad_input in case of malformed URI */ string rfc_3986_normalize_uri(const string& uri); + /** + * Match URI against realm + * @param uri URI to match + * @param realm realm to match against + * @return true if URI matches realm + */ + bool uri_matches_realm(const string& uri,const string& realm); + string& strip_uri_fragment_part(string& uri); string abi_demangle(const char* mn); string base64_signature(const assoc_t& assoc,const basic_openid_message& om); class change_mode_message_proxy : public basic_openid_message { public: const basic_openid_message& x; const string& mode; change_mode_message_proxy(const basic_openid_message& xx,const string& m) : x(xx), mode(m) { } bool has_field(const string& n) const { return x.has_field(n); } const string& get_field(const string& n) const { return (n=="mode")?mode: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(); } }; } } #endif /* __OPKELE_UTIL_H */ diff --git a/lib/basic_op.cc b/lib/basic_op.cc index f7573aa..11ffb48 100644 --- a/lib/basic_op.cc +++ b/lib/basic_op.cc @@ -1,356 +1,327 @@ #include <time.h> #include <cassert> -#include <algorithm> #include <openssl/sha.h> #include <openssl/hmac.h> #include <opkele/data.h> #include <opkele/basic_op.h> #include <opkele/exception.h> #include <opkele/util.h> #include <opkele/uris.h> namespace opkele { - using std::pair; - using std::mismatch; void basic_op::reset_vars() { assoc.reset(); return_to.clear(); realm.clear(); claimed_id.clear(); identity.clear(); invalidate_handle.clear(); } bool basic_op::has_return_to() const { return !return_to.empty(); } const string& basic_op::get_return_to() const { if(return_to.empty()) throw no_return_to(OPKELE_CP_ "No return_to URL provided with request"); return return_to; } const string& basic_op::get_realm() const { assert(!realm.empty()); return realm; } bool basic_op::has_identity() const { return !identity.empty(); } const string& basic_op::get_claimed_id() const { if(claimed_id.empty()) throw non_identity(OPKELE_CP_ "attempting to retrieve claimed_id of non-identity related request"); assert(!identity.empty()); return claimed_id; } const string& basic_op::get_identity() const { if(identity.empty()) throw non_identity(OPKELE_CP_ "attempting to retrieve identity of non-identity related request"); assert(!claimed_id.empty()); return identity; } bool basic_op::is_id_select() const { return identity==IDURI_SELECT20; } void basic_op::select_identity(const string& c,const string& i) { claimed_id = c; identity = i; } void basic_op::set_claimed_id(const string& c) { claimed_id = c; } basic_openid_message& basic_op::associate( basic_openid_message& oum, const basic_openid_message& inm) try { assert(inm.get_field("mode")=="associate"); util::dh_t dh; util::bignum_t c_pub; unsigned char key_digest[SHA256_DIGEST_LENGTH]; size_t d_len = 0; enum { sess_cleartext, sess_dh_sha1, sess_dh_sha256 } st = sess_cleartext; string sts = inm.get_field("session_type"); string ats = inm.get_field("assoc_type"); if(sts=="DH-SHA1" || sts=="DH-SHA256") { if(!(dh = DH_new())) throw exception_openssl(OPKELE_CP_ "failed to DH_new()"); c_pub = util::base64_to_bignum(inm.get_field("dh_consumer_public")); try { dh->p = util::base64_to_bignum(inm.get_field("dh_modulus")); }catch(failed_lookup&) { dh->p = util::dec_to_bignum(data::_default_p); } try { dh->g = util::base64_to_bignum(inm.get_field("dh_gen")); }catch(failed_lookup&) { 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)+1); unsigned char *ckptr = &(ck.front())+1; int cklen = DH_compute_key(ckptr,c_pub,dh); if(cklen<0) throw exception_openssl(OPKELE_CP_ "failed to DH_compute_key()"); if(cklen && (*ckptr)&0x80) { (*(--ckptr)) = 0; ++cklen; } if(sts=="DH-SHA1") { SHA1(ckptr,cklen,key_digest); d_len = SHA_DIGEST_LENGTH; }else if(sts=="DH-SHA256") { SHA256(ckptr,cklen,key_digest); d_len = SHA256_DIGEST_LENGTH; }else throw internal_error(OPKELE_CP_ "I thought I knew the session type"); }else throw unsupported(OPKELE_CP_ "Unsupported session_type"); assoc_t assoc; if(ats=="HMAC-SHA1") assoc = alloc_assoc(ats,SHA_DIGEST_LENGTH,true); else if(ats=="HMAC-SHA256") assoc = alloc_assoc(ats,SHA256_DIGEST_LENGTH,true); else throw unsupported(OPKELE_CP_ "Unsupported assoc_type"); oum.reset_fields(); oum.set_field("ns",OIURI_OPENID20); oum.set_field("assoc_type",assoc->assoc_type()); oum.set_field("assoc_handle",assoc->handle()); oum.set_field("expires_in",util::long_to_string(assoc->expires_in())); secret_t secret = assoc->secret(); if(sts=="DH-SHA1" || sts=="DH-SHA256") { if(d_len != secret.size()) throw bad_input(OPKELE_CP_ "Association secret and session MAC are not of the same size"); oum.set_field("session_type",sts); oum.set_field("dh_server_public",util::bignum_to_base64(dh->pub_key)); string b64; secret.enxor_to_base64(key_digest,b64); oum.set_field("enc_mac_key",b64); }else /* TODO: support cleartext over encrypted connection */ throw unsupported(OPKELE_CP_ "Unsupported session type"); return oum; } catch(unsupported& u) { oum.reset_fields(); oum.set_field("ns",OIURI_OPENID20); oum.set_field("error",u.what()); oum.set_field("error_code","unsupported-type"); oum.set_field("session_type","DH-SHA256"); oum.set_field("assoc_type","HMAC-SHA256"); return oum; } void basic_op::checkid_(const basic_openid_message& inm, extension_t *ext) { reset_vars(); string mode = inm.get_field("mode"); if(mode=="checkid_setup") mode = mode_checkid_setup; else if(mode=="checkid_immediate") mode = mode_checkid_immediate; else throw bad_input(OPKELE_CP_ "Invalid checkid_* mode"); try { assoc = retrieve_assoc(invalidate_handle=inm.get_field("assoc_handle")); invalidate_handle.clear(); }catch(failed_lookup&) { // no handle specified or no valid assoc found, go dumb assoc = alloc_assoc("HMAC-SHA256",SHA256_DIGEST_LENGTH,true); } try { openid2 = (inm.get_field("ns")==OIURI_OPENID20); }catch(failed_lookup&) { openid2 = false; } try { return_to = inm.get_field("return_to"); }catch(failed_lookup&) { } if(openid2) { try { realm = inm.get_field("realm"); }catch(failed_lookup&) { try { realm = inm.get_field("trust_root"); }catch(failed_lookup&) { if(return_to.empty()) throw bad_input(OPKELE_CP_ "Both realm and return_to are unset"); realm = return_to; } } }else{ try { realm = inm.get_field("trust_root"); }catch(failed_lookup&) { if(return_to.empty()) throw bad_input(OPKELE_CP_ "Both realm and return_to are unset"); realm = return_to; } } try { identity = inm.get_field("identity"); try { claimed_id = inm.get_field("claimed_id"); }catch(failed_lookup&) { if(openid2) throw bad_input(OPKELE_CP_ "claimed_id and identity must be either both present or both absent"); } }catch(failed_lookup&) { if(openid2 && inm.has_field("claimed_id")) throw bad_input(OPKELE_CP_ "claimed_id and identity must be either both present or both absent"); } verify_return_to(); } basic_openid_message& basic_op::id_res(basic_openid_message& om) { assert(assoc); assert(!return_to.empty()); assert(!is_id_select()); time_t now = time(0); struct tm gmt; gmtime_r(&now,&gmt); char w3timestr[24]; if(!strftime(w3timestr,sizeof(w3timestr),"%Y-%m-%dT%H:%M:%SZ",&gmt)) throw failed_conversion(OPKELE_CP_ "Failed to build time string for nonce" ); om.set_field("ns",OIURI_OPENID20); om.set_field("mode","id_res"); om.set_field("op_endpoint",get_op_endpoint()); string ats = "ns,mode,op_endpoint,return_to,response_nonce," "assoc_handle,signed"; if(!identity.empty()) { om.set_field("identity",identity); om.set_field("claimed_id",claimed_id); ats += ",identity,claimed_id"; } om.set_field("return_to",return_to); string nonce = w3timestr; om.set_field("response_nonce",alloc_nonce(nonce,assoc->stateless())); if(!invalidate_handle.empty()) { om.set_field("invalidate_handle",invalidate_handle); ats += ",invalidate_handle"; } om.set_field("assoc_handle",assoc->handle()); om.add_to_signed(ats); om.set_field("sig",util::base64_signature(assoc,om)); return om; } basic_openid_message& basic_op::cancel(basic_openid_message& om) { assert(!return_to.empty()); om.set_field("ns",OIURI_OPENID20); om.set_field("mode","cancel"); return om; } basic_openid_message& basic_op::error(basic_openid_message& om, const string& error,const string& contact, const string& reference ) { assert(!return_to.empty()); om.set_field("ns",OIURI_OPENID20); om.set_field("mode","error"); om.set_field("error",error); om.set_field("contact",contact); om.set_field("reference",reference); return om; } basic_openid_message& basic_op::setup_needed( basic_openid_message& oum,const basic_openid_message& inm) { assert(mode==mode_checkid_immediate); assert(!return_to.empty()); if(openid2) { oum.set_field("ns",OIURI_OPENID20); oum.set_field("mode","setup_needed"); }else{ oum.set_field("mode","id_res"); static const string setupmode = "checkid_setup"; oum.set_field("user_setup_url", util::change_mode_message_proxy(inm,setupmode) .append_query(get_op_endpoint())); } return oum; } basic_openid_message& basic_op::check_authentication( basic_openid_message& oum, const basic_openid_message& inm) try { assert(inm.get_field("mode")=="check_authentication"); oum.reset_fields(); oum.set_field("ns",OIURI_OPENID20); bool o2; try { o2 = (inm.get_field("ns")==OIURI_OPENID20); }catch(failed_lookup&) { o2 = false; } string nonce; if(o2) { try { if(!check_nonce(nonce = inm.get_field("response_nonce"))) throw failed_check_authentication(OPKELE_CP_ "Invalid nonce"); }catch(failed_lookup&) { throw failed_check_authentication(OPKELE_CP_ "No nonce provided with check_authentication request"); } } try { assoc = retrieve_assoc(inm.get_field("assoc_handle")); if(!assoc->stateless()) throw failed_check_authentication(OPKELE_CP_ "Will not do check_authentication on a stateful handle"); }catch(failed_lookup&) { throw failed_check_authentication(OPKELE_CP_ "No assoc_handle or invalid assoc_handle specified with check_authentication request"); } static const string idresmode = "id_res"; try { if(util::base64_signature(assoc,util::change_mode_message_proxy(inm,idresmode))!=inm.get_field("sig")) throw failed_check_authentication(OPKELE_CP_ "Signature mismatch"); }catch(failed_lookup&) { throw failed_check_authentication(OPKELE_CP_ "failed to calculate signature"); } oum.set_field("is_valid","true"); try { string h = inm.get_field("invalidate_handle"); try { assoc_t ih = retrieve_assoc(h); }catch(invalid_handle& ih) { oum.set_field("invalidate_handle",h); }catch(failed_lookup& ih) { oum.set_field("invalidate_handle",h); } }catch(failed_lookup&) { } if(o2) { assert(!nonce.empty()); invalidate_nonce(nonce); } return oum; }catch(failed_check_authentication& ) { oum.set_field("is_valid","false"); return oum; } void basic_op::verify_return_to() { - string nrealm = opkele::util::rfc_3986_normalize_uri(realm); - if(nrealm.find('#')!=string::npos) + if(realm.find('#')!=string::npos) throw opkele::bad_realm(OPKELE_CP_ "authentication realm contains URI fragment"); - string nrt = opkele::util::rfc_3986_normalize_uri(return_to); - string::size_type pr = nrealm.find("://"); - string::size_type prt = nrt.find("://"); - assert(!(pr==string::npos || prt==string::npos)); - pr += sizeof("://")-1; - prt += sizeof("://")-1; - if(!strncmp(nrealm.c_str()+pr,"*.",2)) { - pr = nrealm.find('.',pr); - prt = nrt.find('.',prt); - assert(pr!=string::npos); - if(prt==string::npos) - throw bad_return_to( - OPKELE_CP_ "return_to URL doesn't match realm"); - // TODO: check for overgeneralized realm - } - string::size_type lr = nrealm.length(); - string::size_type lrt = nrt.length(); - if( (lrt-prt) < (lr-pr) ) - throw bad_return_to( - OPKELE_CP_ "return_to URL doesn't match realm"); - pair<const char*,const char*> mp = mismatch( - nrealm.c_str()+pr,nrealm.c_str()+lr, - nrt.c_str()+prt); - if( (*(mp.first-1))!='/' - && !strchr("/?#",*mp.second) ) - throw bad_return_to( - OPKELE_CP_ "return_to URL doesn't match realm"); + if(!util::uri_matches_realm(return_to,realm)) + throw bad_return_to(OPKELE_CP_ "return_to URL doesn't match realm"); } } diff --git a/lib/util.cc b/lib/util.cc index b7bc437..b85a377 100644 --- a/lib/util.cc +++ b/lib/util.cc @@ -1,402 +1,432 @@ #include <errno.h> #include <cassert> #include <cctype> #include <cstring> #include <vector> #include <string> #include <stack> +#include <algorithm> #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 { /* * base64 */ string encode_base64(const void *data,size_t length) { BIO *b64 = 0, *bmem = 0; try { b64 = BIO_new(BIO_f_base64()); if(!b64) throw exception_openssl(OPKELE_CP_ "failed to BIO_new() base64 encoder"); BIO_set_flags(b64,BIO_FLAGS_BASE64_NO_NL); bmem = BIO_new(BIO_s_mem()); BIO_set_flags(b64,BIO_CLOSE); if(!bmem) throw exception_openssl(OPKELE_CP_ "failed to BIO_new() memory buffer"); BIO_push(b64,bmem); if(((size_t)BIO_write(b64,data,length))!=length) throw exception_openssl(OPKELE_CP_ "failed to BIO_write()"); if(BIO_flush(b64)!=1) throw exception_openssl(OPKELE_CP_ "failed to BIO_flush()"); char *rvd; long rvl = BIO_get_mem_data(bmem,&rvd); string rv(rvd,rvl); BIO_free_all(b64); return rv; }catch(...) { if(b64) BIO_free_all(b64); throw; } } void decode_base64(const string& data,vector<unsigned char>& rv) { BIO *b64 = 0, *bmem = 0; rv.clear(); try { bmem = BIO_new_mem_buf((void*)data.data(),data.size()); if(!bmem) throw exception_openssl(OPKELE_CP_ "failed to BIO_new_mem_buf()"); b64 = BIO_new(BIO_f_base64()); if(!b64) throw exception_openssl(OPKELE_CP_ "failed to BIO_new() base64 decoder"); BIO_set_flags(b64,BIO_FLAGS_BASE64_NO_NL); BIO_push(b64,bmem); unsigned char tmp[512]; size_t rb = 0; while((rb=BIO_read(b64,tmp,sizeof(tmp)))>0) rv.insert(rv.end(),tmp,&tmp[rb]); BIO_free_all(b64); }catch(...) { if(b64) BIO_free_all(b64); throw; } } /* * big numerics */ BIGNUM *base64_to_bignum(const string& b64) { vector<unsigned char> bin; decode_base64(b64,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)+1); unsigned char *binptr = &(bin.front())+1; int l = BN_bn2bin(bn,binptr); if(l && (*binptr)&0x80){ (*(--binptr)) = 0; ++l; } return encode_base64(binptr,l); } /* * 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) { int fraction; 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 ) && ( sscanf( w.c_str(), "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", &tm_t.tm_year,&tm_t.tm_mon,&tm_t.tm_mday, &tm_t.tm_hour,&tm_t.tm_min,&tm_t.tm_sec, &fraction ) != 7 ) ) 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-timezone; } /* * */ 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 attr_escape(const string& str) { static const char *unsafechars = "<>&\n\"'"; string rv; string::size_type p=0; while(true) { string::size_type us = str.find_first_of(unsafechars,p); if(us==string::npos) { if(p!=str.length()) rv.append(str,p,str.length()-p); return rv; } rv.append(str,p,us-p); rv += "&#"; rv += long_to_string((long)str[us]); rv += ';'; p = us+1; } } string long_to_string(long l) { char rv[32]; int r=snprintf(rv,sizeof(rv),"%ld",l); if(r<0 || r>=(int)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; } /* * Normalize URL according to the rules, described in rfc 3986, section 6 * * - uppercase hex triplets (e.g. %ab -> %AB) * - lowercase scheme and host * - decode %-encoded characters, specified as unreserved in rfc 3986, section 2.3, * that is - [:alpha:][:digit:]._~- * - remove dot segments * - remove empty and default ports * - if there's no path component, add '/' */ string rfc_3986_normalize_uri(const string& uri) { static const char *whitespace = " \t\r\n"; string rv; string::size_type ns = uri.find_first_not_of(whitespace); if(ns==string::npos) throw bad_input(OPKELE_CP_ "Can't normalize empty URI"); string::size_type colon = uri.find(':',ns); if(colon==string::npos) throw bad_input(OPKELE_CP_ "No scheme specified in URI"); transform( uri.begin()+ns, uri.begin()+colon+1, back_inserter(rv), ::tolower ); bool s; string::size_type ul = uri.find_last_not_of(whitespace)+1; if(ul <= (colon+3)) throw bad_input(OPKELE_CP_ "Unexpected end of URI being normalized encountered"); if(uri[colon+1]!='/' || uri[colon+2]!='/') throw bad_input(OPKELE_CP_ "Unexpected input in URI being normalized after scheme component"); if(rv=="http:") s = false; else if(rv=="https:") s = true; else{ /* TODO: support more schemes. e.g. xri. How do we normalize * xri? */ rv.append(uri,colon+1,ul-colon-1); return rv; } rv += "//"; string::size_type interesting = uri.find_first_of(":/#?",colon+3); if(interesting==string::npos) { transform( uri.begin()+colon+3,uri.begin()+ul, back_inserter(rv), ::tolower ); rv += '/'; return rv; } transform( uri.begin()+colon+3,uri.begin()+interesting, back_inserter(rv), ::tolower ); bool qf = false; char ic = uri[interesting]; if(ic==':') { string::size_type ni = uri.find_first_of("/#?%",interesting+1); const char *nptr = uri.data()+interesting+1; char *eptr = 0; long port = strtol(nptr,&eptr,10); if( (port>0) && (port<65535) && port!=(s?443:80) ) { char tmp[8]; snprintf(tmp,sizeof(tmp),":%ld",port); rv += tmp; } if(ni==string::npos) { rv += '/'; return rv; } interesting = ni; }else if(ic!='/') { rv += '/'; rv += ic; qf = true; ++interesting; } string::size_type n = interesting; char tmp[3] = { 0,0,0 }; stack<string::size_type> psegs; psegs.push(rv.length()); string pseg; for(;n<ul;) { string::size_type unsafe = uri.find_first_of(qf?"%":"%/?#",n); if(unsafe==string::npos) { pseg.append(uri,n,ul-n-1); n = ul-1; }else{ pseg.append(uri,n,unsafe-n); n = unsafe; } char c = uri[n++]; if(c=='%') { if((n+1)>=ul) throw bad_input(OPKELE_CP_ "Unexpected end of URI encountered while parsing percent-encoded character"); tmp[0] = uri[n++]; tmp[1] = uri[n++]; if(!( isxdigit(tmp[0]) && isxdigit(tmp[1]) )) throw bad_input(OPKELE_CP_ "Invalid percent-encoded character in URI being normalized"); int cc = strtol(tmp,0,16); if( isalpha(cc) || isdigit(cc) || strchr("._~-",cc) ) pseg += cc; else{ pseg += '%'; pseg += toupper(tmp[0]); pseg += toupper(tmp[1]); } }else if(qf) { rv += pseg; rv += c; pseg.clear(); }else if(n>=ul || strchr("?/#",c)) { if(pseg.empty() || pseg==".") { }else if(pseg=="..") { if(psegs.size()>1) { rv.resize(psegs.top()); psegs.pop(); } }else{ psegs.push(rv.length()); if(c!='/') { pseg += c; qf = true; } rv += '/'; rv += pseg; } if(c=='/' && (n>=ul || strchr("?#",uri[n])) ) { rv += '/'; if(n<ul) qf = true; }else if(strchr("?#",c)) { if(psegs.size()==1 && psegs.top()==rv.length()) rv += '/'; if(pseg.empty()) rv += c; qf = true; } pseg.clear(); }else{ pseg += c; } } if(!pseg.empty()) { if(!qf) rv += '/'; rv += pseg; } 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; } + bool uri_matches_realm(const string& uri,const string& realm) { + string nrealm = opkele::util::rfc_3986_normalize_uri(realm); + string nu = opkele::util::rfc_3986_normalize_uri(uri); + string::size_type pr = nrealm.find("://"); + string::size_type pu = nu.find("://"); + assert(!(pr==string::npos || pu==string::npos)); + pr += sizeof("://")-1; + pu += sizeof("://")-1; + if(!strncmp(nrealm.c_str()+pr,"*.",2)) { + pr = nrealm.find('.',pr); + pu = nu.find('.',pu); + assert(pr!=string::npos); + if(pu==string::npos) + return false; + // TODO: check for overgeneralized realm + } + string::size_type lr = nrealm.length(); + string::size_type lu = nu.length(); + if( (lu-pu) < (lr-pr) ) + return false; + pair<const char*,const char*> mp = mismatch( + nrealm.c_str()+pr,nrealm.c_str()+lr, + nu.c_str()+pu); + if( (*(mp.first-1))!='/' + && !strchr("/?#",*mp.second) ) + return false; + return true; + } + 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); } } } |